Skip to content

About

Internal PM tool: GitHub-issue roadmap with two-way sync, planning layer, AI reads, insights pipeline, and an accounts mini-CRM

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gh-roadmap

Internal PM tool. Mirrors GitHub issues from a product repo with two-way sync, adds a planning layer (roadmap / list / kanban), AI-assisted reads, a customer-insights pipeline, and a lightweight accounts mini-CRM. Fastify + better-sqlite3 + Octokit backend serving a Vite + React frontend from the same process. Runs locally; optional Google sign-in for small-team use.

Architecture & locked design decisions live in CLAUDE.md — read it before changing anything.

Features

Six tabs:

  • Roadmap — drag issues across a time × bucket grid, with TODO + Backlog meta columns. Configurable bucketing (none / label / assignee / milestone) and time axis (week / month / quarter).
  • List — dense sortable table.
  • Kanban — mirrors one GitHub Projects v2 board (env-pinned).
  • Insights — browse the product repo's insights/*.md, an inbox of captured drafts, AI-extracted fields, and publish-to-PR.
  • Accounts — customer axis: account index (signal-derived + CRM-ingested) → drawer with timeline, cares-about issues, an AI "Acme story" read, and an editable mini-CRM profile (ARR / renewal / owner / tier / segment / …). Manual create or bulk JSON/CSV ingest.
  • Progress — a PM "how are things today" spine: verdict → AI read → needs-you-now (at-risk) → schedule + momentum, plus an "On your plate" PM-action report.

Cross-cutting:

  • Two-way GitHub sync — title / body / state / labels / assignee / milestone / comments mirror both ways; app-only planning fields (plannedMonth, roadmapNotes, position, …) stay in SQLite.
  • Flow engine — per-issue state (shipping / in-review / in-code / discussing / stalled / cold / fresh / closed) derived from PR + comment + event signals; the flow pill renders everywhere.
  • At-risk + schedule health — schedule on-time % as the headline, gated momentum as secondary, recalibrated at-risk with daily snapshots.
  • AI surfaces — issue summary, Progress read, insight extraction, account read — all regenerable, via any OpenAI-compatible endpoint, with per-task model overrides set in the UI.
  • AI cost controls — per-workspace guardrails set in the header "AI" popover (admin only): a max_tokens cap per request, a requests-per-minute rate limit, and a daily token budget that hard-stops AI (503) until UTC midnight. 0 = unlimited. Usage meter on /api/meta.
  • Customer-signal linking — insights ↔ issues ↔ accounts, two-way, bumping at-risk severity.
  • Capture API — unauthenticated localhost endpoint (POST /api/insights/capture) for agents/curl to drop raw signal into the inbox; the Insights header shows a copy-for-agent brief.
  • Repo file viewer — read-only view of repo files referenced in issues.
  • Drawer affordances — expand-to-modal toggle + "copy for agent" on the issue drawer.
  • Google OAuth login — optional, with domain whitelist + admin gating (see below).
  • Workspace export / import — full backup + restore of the SQLite-held planning layer.

Run

cp .env.example .env        # fill in GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO (rest optional)
pnpm install
pnpm dev                    # tsx watch — boots API + web on one port
# or
pnpm build && pnpm start

Open http://localhost:3000 — the web app is served at /, the API under /api/*. The port auto-bumps on EADDRINUSE; the live port is written to .runtime-port so sibling tools can discover it.

pnpm typecheck              # tsc --strict over both api and web — the build gate (no test suite)

Configuration

All config is env vars (see .env.example for the annotated list). Highlights:

Var Purpose
GITHUB_OWNER / GITHUB_REPO Issues repo. Required.
GITHUB_TOKEN Service-identity PAT (Option A). Set this or the GitHub App trio below.
GITHUB_APP_ID / GITHUB_APP_INSTALLATION_ID / GITHUB_APP_PRIVATE_KEY Service-identity GitHub App (Option B). Take precedence over GITHUB_TOKEN; the installation token is minted in-app and auto-renews. Private key accepts raw PEM, literal-\n PEM, or base64.
GITHUB_PROJECT_NUMBER Pin the Kanban tab to one Projects v2 board. Optional.
GITHUB_WEBHOOK_SECRET HMAC secret for the inbound webhook. Optional.
INSIGHTS_GITHUB_REPO Point insights sync at a different repo than the issues repo (owner/repo). Optional.
AI_BASE_URL / AI_API_KEY / AI_MODEL OpenAI-compatible endpoint for AI surfaces. Unset → AI routes 503 and the UI hides them.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET Set both to require Google sign-in. Leave blank for single-user localhost mode.
ALLOWED_EMAIL_DOMAIN / ADMIN_EMAILS / SESSION_SECRET Domain whitelist, admin gating, cookie signing. Optional.
GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRET Set both for per-user GitHub write identity (writes act as the caller's GitHub account). Requires Google login. Optional.
TOKEN_ENC_KEY 32-byte key (hex/base64) encrypting stored user GitHub tokens at rest. Required when GitHub OAuth is set.
DB_PATH SQLite file location. Defaults to ./data/roadmap.db.

Deploying (Docker)

The Dockerfile builds a production image (Node, port 3000); the GitHub Actions workflow pushes it via OIDC. Operational notes:

  • Persist the data volume. Mount a volume at the DB_PATH location (default /app/data). The SQLite file holds the planning layer (planned weeks, ordering, notes), user roles, linked GitHub tokens, and accounts — none of it re-derivable from GitHub. Losing the volume loses planning work. The in-app export (admin → Data) is the backup path.
  • PORT overrides the listen port (default 3000).
  • Generate TOKEN_ENC_KEY with openssl rand -hex 32. Rotating it invalidates all stored user GitHub tokens (users just reconnect).
  • SESSION_SECRET should be set explicitly in a team deploy (it defaults to the Google client secret); rotating it logs everyone out.
  • Public HTTPS origin is assumed by the OAuth callbacks — register <app-origin>/api/auth/callback (Google) and <app-origin>/api/github/callback (GitHub).

Auth & identity at a glance

There are three independent identity layers, each toggled by its own env vars. They stack but none requires the next unless noted:

  1. Service identity (always on) — the shared GitHub client for reads, sync, and (by default) every write. A PAT (GITHUB_TOKEN) or a GitHub App (GITHUB_APP_ID + GITHUB_APP_INSTALLATION_ID + GITHUB_APP_PRIVATE_KEY). See "GitHub auth: App vs PAT" below.
  2. Login gate (optional) — Google sign-in. Off by default; gates who can open the app. See "Auth modes" + "Roles" below.
  3. Per-user GitHub write identity (optional, needs layer 2) — each user links their own GitHub account in-app so their writes are attributed to them instead of the service identity. The connect flow (avatar menu button + auto-prompt) is built in; it just needs the OAuth env vars. See "Per-user GitHub write identity" below.

Auth modes

  • Single-user (default). Leave GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET blank — no login, every request is a local admin. The original localhost pattern.
  • Team (Google OAuth). Set both client vars to gate the whole app behind "Sign in with Google". Register <app-origin>/api/auth/callback as the redirect URI. ALLOWED_EMAIL_DOMAIN restricts sign-in to one domain. ADMIN_EMAILS (comma-separated) is the immutable bootstrap-admin list — always admins, never demotable in-app (blank = every signed-in user is an admin).

Roles (team mode)

Three roles: viewer (read-only — every non-GET request 403s, except logout and insight capture), editor (all app writes), admin (editor + user roles + AI settings + export/import). Newly signed-in users default to viewer; an admin promotes them via the header Users panel (GET /api/users, PATCH /api/users/:email/role). With auth off the role system is dormant — the local user is admin. Viewer write affordances are hidden in the UI; the server enforces regardless.

Multi-pod workspaces

Several pods share one instance: one DB and one reconcile loop mirror the whole product repo; each pod is a read-time view via its own master filter. Planning fields, health snapshots, AI model overrides, and the Progress/PM-action AI caches are per-pod; accounts + insights stay shared org-wide. Any signed-in user can view any pod and switch via the header pod control (hidden with a single pod); admins manage pods there too (create / rename / archive — never delete). New issues are auto-labeled pod:<slug> for the active pod. Editing a pod's base filter is admin-only; per-user narrowing is the client-side Filter popover.

Per-user GitHub write identity (team mode, optional)

By default every GitHub write goes through the shared GITHUB_TOKEN. Set GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRET + TOKEN_ENC_KEY and user-initiated GitHub writes (issues, comments, Kanban moves, insight PRs) are made as the caller's own GitHub account instead. One-time setup: register either a classic OAuth App (callback <app-origin>/api/github/callback, scope repo) or a GitHub App with user authorization (same callback; disable "Expire user authorization tokens" — no refresh rotation is built; permissions are then scoped to the app's installation, which orgs tend to prefer). Each user connects once — either from the avatar menu ("Connect GitHub") or via the prompt that appears the first time a write is attempted (409 github_not_linked). Tokens are validated against the target repo before being stored AES-256-GCM-encrypted; a revoked token auto-unlinks on the next write (409 github_reauth_required). Reads and background sync keep using the service token. Requires Google login; the server refuses to boot on partial config.

GitHub auth: App vs PAT

  • PAT — fastest. Fine-grained personal access token with repo (read/write) + project scope (project powers the Kanban tab). Single-user, 5,000 req/hr.
  • GitHub App — recommended for a team. Installation tokens get 15,000 req/hr, aren't tied to a person, and writes appear as the app's bot (app-name[bot]). Set GITHUB_APP_ID + GITHUB_APP_INSTALLATION_ID + GITHUB_APP_PRIVATE_KEY (they take precedence over GITHUB_TOKEN); the app mints + auto-renews the ~1h installation token in-app. Install permissions: Issues, Pull requests, Contents (read/write), Metadata (read), org Projects (read/write, for the Kanban tab).

Webhook setup (optional)

Boot-time + nightly reconcile catch any missed events, so the webhook is not required.

Production: repo settings → Webhooks → add https://<host>/webhook/github, content type application/json, secret matches GITHUB_WEBHOOK_SECRET, subscribe to Issues and Issue comments.

Local dev: forward events via smee.io:

npx smee -u https://smee.io/<your-channel> -t http://localhost:3000/webhook/github

API

The OpenAPI definition is the source of truth — it's kept current with every endpoint change:

  • JSON: http://localhost:<port>/api/openapi.json
  • YAML: http://localhost:<port>/api/openapi.yaml

Route groups (api/src/routes/):

Area Routes
Issues list / patch (two-way) / create, /roadmap PATCH (app-only planning fields)
Comments list / create / patch / delete (two-way)
Meta & config counts, rate limit, current user; workspace_config GET/PATCH (pod base filter — admin)
Workspaces (pods) list, switch active (rm_workspace cookie), create / rename / archive (admin)
Roadmap surfaces flow state, schedule health (live + history + backfill), Projects v2 (Kanban), PRs
Progress morning brief (snapshot + changes since last-seen)
AI issue summary, progress read, account read, insight extraction (all regenerable)
Insights GitHub-API sync, capture, draft lifecycle, publish-PR
Accounts list / detail / AI read + mini-CRM (create, PATCH profile, JSON + CSV ingest)
PM actions quick-action endpoints surfaced in the UI
Repo files read-only viewer for repo files referenced in issues
Data full-workspace export / import (backup + restore)
Auth Google OAuth login / callback / logout / session; GitHub link / callback / unlink (write identity)
Webhook HMAC-verified GitHub receiver

Rate-limit defenses

  • GraphQL bulk fetch (50 issues + 50 comments per call) instead of a REST loop
  • Webhook-driven inbound; reconcile only on boot + nightly
  • Rate-limit headers captured on every response, exposed at /api/meta; logger warns under 10%
  • Per-issue write debounce helper in sync.ts

Out of scope (next phases)

See CLAUDE.md §8 for the full candidate-work list. Notable open items:

  • Conditional ETag caching layer
  • Source connectors (Slack / gdoc / Jira) feeding the capture API
  • Tests

About

Internal PM tool: GitHub-issue roadmap with two-way sync, planning layer, AI reads, insights pipeline, and an accounts mini-CRM

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages