001 · 2026
The Peruvian legal corpus, as git history
Peru publishes its laws, but not as data you can search, diff, or trust the history of. The engine behind legalize.pe turns the whole corpus into Markdown and version-controls it with git, every reform a commit dated to the day the norm was published. I built the read side: the web app people actually use, and the API that serves the corpus and its history.
- Role
- Design engineer · web app, public API, git-history reader. With Railly Hugo on the scraping engine
- Status
- Open source · live · pre-1.0

What the corpus is
Two repos, on purpose. The corpus is plain Markdown plus git history, so `git log` on the 1993 Constitution replays its full 32-commit amendment history and anyone can clone the raw data and build on it. The engine is the monorepo that produces and serves it: scraper, CLI, web app, API and an MCP server so AI clients can query the law directly. It holds 21,244 norms today, 11,045 national and 10,199 regional across 26 jurisdictions.
How it's built
What I built
The web app and the API, not the scraper. On the front: a bilingual es/en interface with a language toggle that does not flash on load, a command palette with a platform-aware shortcut hint, and an accessibility and craft pass over the whole UI. On the back: the endpoints that read the corpus history, plus search and generated OG images.
Decision
The wall was serverless, not scraping
The history API worked locally for a boring reason: there was a clone of the corpus on disk and git plumbing to read it with. On Vercel there is no clone. So reading history became one factory behind a single shape, with two backends underneath: local git through simple-git when a repo path exists, the GitHub API when running on Vercel. Pulling the diff parser out of both meant the code that turns a commit into hunks and lines got tested once instead of twice. Around it, three smaller versions of the same lesson: the search index had to be bundled into the function instead of read off disk, GET params had to come from request.url, and the API routes had to be excluded from ISR before they would stop crashing.
Honest scope
This is a collaboration and most of the engine is not mine. Railly Hugo wrote the scraper, the corpus pipeline and the data model, and has roughly twice my commits on it. What I owned is the read side: the web app, the API, and a hardening pass that closed seven open issues at once. The corpus is live and open source, and still pre-1.0, so the honest status is a growing corpus, expect breaking changes, not a finished product.