Under the hood

One idempotent pipeline, from GitHub to a live board.

No webhooks to babysit, no warehouse to feed. Limn pulls your PR activity on a schedule, stores it leanly, and serves a deliberately thin read layer to the timeline. Here’s the whole machine.

01 / Pipeline
  1. 01gh CLI / OAuthtoken source
  2. 02GitHub APIGraphQL + REST
  3. 03Syncevery 5 min · idempotent
  4. 04SQLite | Postgreslocal | cloud
  5. 05Fastify APIlean read layer
  6. 06React SPAvis-timeline
02 / Sync pipeline

How the board stays current.

The sync is the load-bearing part — and it’s built to be boring: safe to re-run, cheap to keep current, and quick to fill a fresh repo.

Triggers

A node-cron job runs every five minutes, plus on repo-add and on a manual deep sync. In the cloud, the periodic pass follows the user: a tenant with no open tab for 15 minutes stops being re-synced, so idle accounts don’t burn API quota. Local is always on.

Two-phase backfill

A never-synced repo fills in seconds: a fast ~14-day foreground pass paints the recent board, then the deep backfill walks back to 90 days in the background, continuing the same cursor so no page is fetched twice.

Incremental + overlap

After that, each run re-walks from the last sync minus a 20-minute overlap. GitHub doesn’t bump a PR’s updatedAt for everything that matters — a CI run finishing, a thread resolving — so the overlap deliberately re-checks the trailing window and reconciles.

One fat query

Each repo is one paginated GraphQL query — 25 PRs a page, newest-first — walked until a PR predates the window. Per-commit changed-file paths come from REST and are cached permanently, since a commit SHA is immutable.

Idempotent by structure

Every entity upserts on its GitHub node id; timeline events upsert on a deterministic dedupe key. Re-running — after a crash, a cancel, or an overlapping window — is always safe, because conflict targets reconcile instead of duplicating.

Lean storage

By default Limn skips the bulky, regenerable text — PR descriptions, diff hunks, commit messages — keeping the DB small. It’s hydrated on demand when you open a PR and cached in your browser’s IndexedDB, so an unchanged PR never re-downloads. Flip one flag to store everything for fully-offline detail.

03 / Two modes

Local-first. Cloud when you need it.

A single environment variable selects the whole stack. The query layer is written once against a portable async surface, so the same code drives SQLite on your laptop and Postgres on a server.

Local

default
  • Runs entirely on your machine — SQLite, no hosted backend.
  • Authenticates with your logged-in gh CLI; stores no credentials.
  • One synthesized account; opens straight to the Activity console.
  • Runs the Pro intelligence layer, including agentic review & fix (opt-in).
  • npx pierre-review and you’re in.

Cloud

multi-tenant
  • A public landing, GitHub-App OAuth, per-user accounts.
  • Postgres, with every entity owned and isolated by account.
  • Encrypted per-user tokens; sessions behind a sealed cookie.
  • Self-hostable on Railway from the same image.
  • Sync follows active users to keep quota lean.
Security model
  • Per-account isolation is load-bearing: every list query filters by account, every id-addressed read scopes ownership, and a cross-account IDOR check (verify:isolation) guards the query layer.
  • Cloud OAuth tokens are sealed with AES-256-GCM and decrypted per request — never held in a module-level cache.
  • Local mode stores no credentials at all: it borrows your authenticated gh CLI and talks to a SQLite file on your disk.
04 / Run locally

What happens when you run the command.

01It checks for an authenticated gh CLI and reads a short-lived token — nothing is written to disk.
02It opens a SQLite file under your home directory and runs migrations.
03A single Fastify process serves the API and the SPA, the scheduler starts, and your browser opens to the Activity console.

Requires Node ≥ 20 and gh auth login. Installed globally? The short pierre command does the same.

zsh · ~/work
$ npx pierre-review
05 / Roadmap

What’s next.

Limn is useful today, and deliberately scoped. Here’s where it’s heading — listed because it’s planned, not because it’s done.

AI

Metered advanced AI

Pay-as-you-go for Claude Review, AI Analysis and AI Fix at API list price — no key of your own required, usage tracked in the same in-app credits.

AI

BYO AI endpoints

OpenAI-compatible endpoints — Bedrock, self-hosted, open models — so you choose the model and where your code goes, for cost and privacy control.

Integrations

Deeper Jira/Linear integration

Ticket links on PRs ship today. Next: pulling ticket status and titles into the board, so a stalled PR is visibly a stalled ticket.

Integrations

Email digests

The same sprint report and repo digests that reach Slack today, delivered to an inbox — for the teams whose “one place” isn’t Slack.

Platform

Hosted cloud Pro rollout

Pro runs in the local and self-hosted deployment today; the hosted cloud tier is rolling out, same features, zero setup.

Platform

A phone-friendly build

Limn is a dense, desktop-first tool today. A responsive build for skimming the feed and triaging “My Turn” from your phone is planned.

Want something Limn doesn’t do yet? Open an issue.

Ready to see your team on one board?