Skip to content

Repository files navigation

Plans

A multi-tenant web app on Cloudflare that reads the plans/ directories across your GitHub repos (the ones that use the plans skill) and lets you browse them by state — backlog, ready, in-progress, done — in one place. GitHub is the source of truth; the app only ever reads and (in later phases) writes plans through the GitHub API.

The plans skill lives in this repo too — it's the spec this app reads, so the two share one source of truth for the lifecycle. See The plans skill below.

This repo currently implements Phase 0 (foundations), Phase 1 (the read-only reader), Phase 2 (hand editing), and Phase 3 (AI-assisted moves) — plans can be edited and committed back as bot-authored commits, and moved between lifecycle states with Claude (via Cloudflare AI Gateway) drafting the rewrite, shown as a diff to approve before an atomic move-and-update commit. AI-drafted new backlog items and Flue chat are later phases — see the plan.

What works today

  • Sign in with GitHub (GitHub App user-OAuth), signed HttpOnly session cookies.
  • Installation linking — at login the app snapshots which App installations you can access and stores the user→installation mapping in D1.
  • Automatic repo discovery — scans each installation's repos for a top-level plans/ folder (Git Trees API), caching results in D1.
  • Dashboard — installations, and under each, the repos that have plans.
  • Repo view — the four states as lists, populated from the plans.
  • Plan view — rendered markdown body + parsed frontmatter (title, status, dates).
  • Freshness — incremental cache keyed by git blob sha, a manual "Rescan"/ "Refresh", and push / installation webhook handling.
  • One-command bootstrapcurl -fsSL plans.superhighfives.com/start | sh turns any repo into a plans-enabled repo (see Bootstrapping a repo).

Only top-level plans/<state>/*.md files with valid frontmatter (a non-empty title) are recognized; anything else is silently skipped ("detect and skip").

Bootstrapping a repo

Turn any repo into a plans-enabled repo with one command — no npx, no auth, just this app:

curl -fsSL plans.superhighfives.com/start | sh

It installs the plans skill for your agents — both .agents/skills/plans/ (the shared convention opencode et al. read) and .claude/skills/plans/ (Claude Code) — adds a plans section to AGENTS.md, and creates plans/{backlog,ready,in-progress,done}/ with a seeded README.md. It's idempotent and never clobbers existing files, so re-running it is safe. Working with an agent, just say "run plans.superhighfives.com/start in this repo" — it runs the same curl.

Refresh the skill later without re-bootstrapping:

curl -fsSL plans.superhighfives.com/start | sh -s -- update

Visiting the URL in a browser (or an agent fetching it) shows a readable page instead of the script — /start content-negotiates on Accept. The app serves its own skill at /start/skill, so /start has no dependency beyond the app. See plans/done/start-endpoint.md for the design.

The plans skill

skills/plans/SKILL.md is the agent skill that writes the plans/ structure this app reads — the four states, the naming convention, the frontmatter schema, and the /plans slash-command modes. It lives here (rather than in a general skills collection) because the skill and the app are two halves of one contract: change the lifecycle in one and you change it in the other.

To keep that from drifting, the contract has a single source of truth in code — src/lib/plans/states.ts (PLAN_STATE_DEFS), from which the state ids, UI labels, frontmatter status values, and the plans/<state>/*.md path matcher are all derived. states.skill.test.ts reads SKILL.md and fails if its documented lifecycle or status enum no longer matches PLAN_STATE_DEFS.

Install the skill globally with the skills CLI:

npx skills add superhighfives/plans --skill plans -g

Stack

  • TanStack Start (React 19) on Cloudflare Workers — server functions are the API boundary; tokens never reach the browser.
  • Cloudflare D1 (SQLite) + Drizzle for app state and caches.
  • WebCrypto for the App JWT (RS256), installation-token encryption (AES-256-GCM), and cookie signing (HMAC).

Architecture

Browser (TanStack Start / React)
      │  route loaders → server functions (RPC)
      ▼
Cloudflare Worker
      ├── GitHub App      → installation tokens → repo contents + PRs (read)
      ├── D1 (Drizzle)    → users, installations, repos, plan cache, audit
      └── WebCrypto       → App JWT, token encryption, cookie signing

Source of truth is GitHub. D1 is a cache, rebuilt on demand and validated against git blob shas; push webhooks evict stale entries.

Project layout

src/
  env.ts                 Typed Worker bindings/secrets (per-request only)
  router.tsx             Router factory
  db/                    Drizzle schema + client
  lib/
    crypto.ts            WebCrypto: base64(url), HMAC, AES-GCM, ids
    github/              App JWT, REST client, installation tokens, OAuth, tree/blob reads
    plans/               Frontmatter parser, state/path rules, shared types
  server/                Server functions (RPC) + server-only helpers
    session.ts           Signed cookie sessions + OAuth state
    *.functions.ts       The RPC boundary (auth-guarded)
    *.server.ts          DB/GitHub logic
  routes/
    __root.tsx           Document shell + header
    index.tsx            Dashboard / landing
    repos/$owner/$repo/  Repo view + plan view
    api/                 OAuth login/callback, logout, GitHub webhook (server routes)
migrations/              D1 SQL migrations (drizzle-kit)
skills/
  plans/SKILL.md         The plans skill — the plans/ contract this app reads

Setup

1. Register a GitHub App

Create one App (Settings → Developer settings → GitHub Apps → New):

  • Callback URL: https://plans.superhighfives.com/api/auth/github/callback (and http://localhost:5173/api/auth/github/callback for local dev).
  • Webhook URL: https://plans.superhighfives.com/api/webhooks/github, with a webhook secret.
  • Permissions: Repository Contents: Read-only, Metadata: Read-only, Pull requests: Read-only (the last powers branch/PR plan activity on the board and the plan detail's branch tabs). (Contents becomes read-write in Phase 2.) Adding Pull requests to an existing App makes each installation owner re-approve; until they do, the board degrades to a "grant access" notice instead of erroring.
  • Subscribe to events: Push, Pull request, Installation, Installation repositories.
  • Enable "Request user authorization (OAuth) during installation" / generate a client secret so user login works.
  • Generate a private key (downloads a .pem).

Then Install the App on your account or an org.

2. Create the D1 database

npx wrangler d1 create plans
# Copy the printed database_id into wrangler.jsonc (replace REPLACE_WITH_D1_DATABASE_ID)
npm run db:migrate:remote   # apply migrations to the remote D1

3. Configure secrets

Local dev: copy .dev.vars.example to .dev.vars and fill it in. Production: set each as a Worker secret:

for s in GITHUB_APP_ID GITHUB_APP_CLIENT_ID GITHUB_APP_CLIENT_SECRET \
         GITHUB_APP_PRIVATE_KEY GITHUB_WEBHOOK_SECRET SESSION_SECRET \
         TOKEN_ENCRYPTION_KEY APP_URL; do npx wrangler secret put "$s"; done
  • SESSION_SECRETopenssl rand -base64 48
  • TOKEN_ENCRYPTION_KEYopenssl rand -base64 32 (must decode to 32 bytes)
  • GITHUB_APP_PRIVATE_KEY — paste the full .pem (PKCS#1 or PKCS#8 both work)
  • APP_URL — the public origin, no trailing slash (https://plans.superhighfives.com)

4. Run locally

npm install
npm run db:migrate:local   # apply migrations to local D1
npm run dev                # http://localhost:5173

Exercising the "move to done" verify flow locally requires a container runtime (Docker) — wrangler dev uses it to run the Sandbox container that clones, installs, and runs a repo's test/build scripts.

5. Deploy

Pushes to main deploy automatically via GitHub Actions (.github/workflows/deploy.yml). The workflow waits for CI to pass on the commit, then applies pending D1 migrations (--remote) and runs wrangler deploy.

For CI deploys to work, add two repository secrets (Settings → Secrets and variables → Actions):

  • CLOUDFLARE_API_TOKEN — scoped token with Workers Scripts: Edit and D1: Edit permissions.
  • CLOUDFLARE_ACCOUNT_ID — your Cloudflare account ID.

The Worker secrets from step 3 and the D1 database_id are set once (as above) and persist across deploys — CI never touches them.

Production is served at https://plans.superhighfives.com via the custom-domain route in wrangler.jsonc. Cloudflare provisions the DNS record and TLS cert on the first deploy, so the superhighfives.com zone must be on the same Cloudflare account.

To deploy by hand instead (e.g. the very first deploy, before the workflow is on main):

npm run deploy             # build + wrangler deploy

Scripts

Script Purpose
npm run dev Local dev server (Vite + Workers runtime)
npm run build Production build (also regenerates the route tree)
npm run deploy Build and deploy to Cloudflare (manual; CI deploys on push to main)
npm run typecheck tsc --noEmit
npm test Unit tests (frontmatter, plan paths, skill sync, crypto)
npm run db:generate Generate a D1 migration from the schema
npm run db:migrate:local / :remote Apply migrations

Security notes

  • Every server function that touches private data runs authMiddleware; the RPC endpoint — not the route — is the auth boundary.
  • Repo/plan access is re-checked against the user→installation mapping on every request, so one tenant can't read another's repos by guessing owner/name.
  • Installation tokens are cached encrypted (AES-256-GCM) in D1.
  • Session and OAuth-state cookies are HttpOnly, SameSite=Lax, and use the __Host- prefix + Secure in production; OAuth uses a signed state cookie (GitHub OAuth doesn't support PKCE).
  • The webhook verifies X-Hub-Signature-256 before trusting any payload.

Deviations from the plan (v1 scope)

These are deliberate simplifications for the first shippable slice; each has a clear upgrade path noted in the code:

  • Discovery runs inline, not on Queues. The plan calls for Cloudflare Queues fan-out. v1 runs the scan on-demand (first dashboard load + manual "Rescan"), with bounded concurrency. scanInstallation/scanRepo are factored so they can be lifted into a queue consumer without change.
  • Sessions are stateless signed cookies (no server-side session table). The layer is intentionally thin so an identity provider (or DB sessions) can be slotted in later.
  • The user OAuth token is not persisted. It's used only during the callback to identify the user and snapshot their installations. Installing the App somewhere new later is picked up by signing in again.
  • Discovery does a lightweight presence check (a plans/<state>/*.md file exists); full frontmatter validation happens when a repo/plan is opened, and invalid plans are skipped there.

License

Private / unreleased.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages