Loopworks is an agentic software factory portal for planning, executing, validating, and improving software delivery loops. GitHub Issues are the source of truth for roadmap, planning, milestones, decisions, and execution state. Vercel is the application visibility surface for previews, deployments, and build status.
- Next.js App Router, TypeScript, Bun
- ShadCN/UI and Tailwind CSS
- Auth.js GitHub SSO
- Postgres and Drizzle
- Biome, Vitest, Playwright, Storybook
- Markdownlint for Markdown documentation
- Pino structured logging
- Eve, Vercel Workflows, Vercel Sandbox, Vercel AI Gateway integration points
bun install
bun run devFor local UI work without GitHub OAuth credentials:
bun run dev:fixtureTo inspect a signed GitHub issue webhook fixture without sending it:
bun run github:webhook-fixture -- --kind agent-ready
bun run github:webhook-fixture -- --kind spike-agent-readyThe fixture defaults to http://127.0.0.1:3000/api/github/webhooks, uses
GITHUB_WEBHOOK_SECRET, and only posts to the local webhook route when
--send is provided. Sending is restricted to loopback URLs.
Copy .env.example to .env.local for local development. The fixture server only needs the defaults from .env.example; real GitHub SSO, webhooks, database persistence, and Vercel deployment visibility use these variables:
AUTH_SECRETAUTH_GITHUB_IDAUTH_GITHUB_SECRETLOOPWORKS_AUTH_BYPASSLOOPWORKS_ALLOWED_GITHUB_USERSLOOPWORKS_ALLOWED_GITHUB_ORGSLOOPWORKS_PUBLIC_URLLOOPWORKS_AGENT_READY_LOOP_ENABLEDLOOPWORKS_DEVELOPMENT_LOOP_ENABLEDLOOPWORKS_RESEARCH_LOOP_ENABLEDLOOPWORKS_PORTAL_DATA_MODELOOPWORKS_EVE_TEST_RECEIPT_SECRETLOOPWORKS_EVE_TEST_WRITER_FIXTURE_MODELOOPWORKS_EVE_IMPLEMENTER_FIXTURE_MODELOG_LEVELDATABASE_URLDATABASE_URL_UNPOOLEDOTEL_EXPORTER_OTLP_PROTOCOLOTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_TRACES_HEADERSOTEL_EXPORTER_OTLP_METRICS_HEADERSOTEL_SERVICE_NAMEOTEL_RESOURCE_ATTRIBUTESGITHUB_APP_IDGITHUB_APP_PRIVATE_KEYGITHUB_WEBHOOK_SECRETVERCEL_ACCESS_TOKENVERCEL_TEAM_IDVERCEL_TEAM_SLUG
The Vercel-managed Neon resource owns hosted Postgres for Production and
Preview only. Runtime queries use the pooled DATABASE_URL; Drizzle migrations
use the direct DATABASE_URL_UNPOOLED. src/db/client.ts keeps prepared
statements disabled for compatibility with the pooled runtime connection.
Vercel runs bun run vercel-build, which applies migrations before the Next.js
build. The ordinary bun run build command remains migration-free for local
development. Hosted builds fail before connecting if
either database URL is missing, if either URL is malformed, if their Neon
branch or database differs, or if their pooled/direct roles are reversed.
bun run db:migrate holds a Postgres advisory lock while Drizzle applies
pending migrations, serializing overlapping builds that target the same
database.
The required Neon deployment actions create an isolated branch for each Preview before the build and clean it up with the Preview lifecycle. Neon and Vercel own branch creation, connection injection, and cleanup; the application does not create or select hosted branches itself.
After provisioning or changing project environment variables, refresh the untracked local file with:
vercel env pull .env.local --yesThe command overwrites .env.local, so preserve any local-only values before
running it and review the result afterward. Development is intentionally not
connected to the hosted Neon resource, so a Development pull does not select a
Production or Preview database. Continue to use local Postgres for development.
Never run bun run db:seed or bun run db:seed:reset against a hosted Neon
database.
OpenTelemetry is registered through @vercel/otel. Local development is safe
by default: leave the OTLP exporter variables blank unless you intentionally want
to ship telemetry. For the ADR 0012 Axiom preview proof, use OTLP/HTTP protobuf,
send traces to an Axiom Events dataset with
OTEL_EXPORTER_OTLP_TRACES_HEADERS, and send metrics to a dedicated Axiom
Metrics dataset with OTEL_EXPORTER_OTLP_METRICS_HEADERS. Pino stdout logs stay
attached to Vercel runtime logs with the active traceId; direct Pino-to-Axiom
log shipping is tracked separately by issue #65.
bun run check
bun run agent-docs:check
bun run markdownlint
bun run typecheck
bun run test
bun run storybook:build
bun run test:e2ebun run test:e2e owns a fresh development server with explicit non-production
fixture mode. It deterministically verifies the Fixture fallback path and
does not attach to an existing server. LOOPWORKS_PORTAL_DATA_MODE=fixtures
is ignored in production, where database failures continue to fail closed.
The seeded Postgres browser lane is separate. It requires a local
loopworks_e2e database owned by the loopworks role:
createdb --host 127.0.0.1 --username loopworks loopworks_e2e
DATABASE_URL="postgres://loopworks:loopworks@127.0.0.1:5432/loopworks_e2e" \
bun run test:e2e:seededLoopworks may squash its migration history before the first production
release. A database created from an older journal cannot apply a replacement
baseline in place; recreate it before running migrations.
Issue #113 replaced the
original 0000-0007 journal, so existing local and preview databases must be
reset. To recreate the explicitly local test database:
dropdb --host 127.0.0.1 --username loopworks loopworks_e2e
createdb --host 127.0.0.1 --username loopworks loopworks_e2eRecreate any other non-production database through its provider rather than pointing these local commands at a remote host.
The seeded command refuses production runtimes, non-Postgres URLs,
non-loopback hosts, and database names other than loopworks_e2e before it
runs migrations. It then runs migrations, resets only the fixed-id demo rows,
and requires Live database browser evidence. Migration or seed failures name
the failed stage; confirm Postgres is running and that the local role and
database exist.
The native Postgres admission lane uses the same database. It proves that competing dispatch transactions on two independent sessions serialize on the durable group guard, which the embedded PGlite suite cannot demonstrate:
DATABASE_URL="postgres://loopworks:loopworks@127.0.0.1:5432/loopworks_e2e" \
bun run test:integration:postgresIt applies any pending migrations itself and enforces the same local-database
guard. Without a safe DATABASE_URL it fails with a non-zero exit rather than
skipping or falling back to PGlite, so a missing database can never be mistaken
for passing concurrency evidence. Each test truncates every table in the
public schema of loopworks_e2e, so run the seeded lane afterwards if you
need the demo rows back.
The aggregate command is:
bun run validateLoopworks uses pre-k through uvx prek.
bun run precommit:install
bun run precommit:runThe pre-commit hook runs bun run precommit, which mirrors CI validation: Biome format check, Biome lint, agent docs sync, Markdownlint, TypeScript, Vitest, Storybook build, and Playwright.
- Agent workflow:
AGENTS.md - Claude Code shim:
CLAUDE.mdimportsAGENTS.md; runbun run agent-docs:syncafter changing agent guides - Contributing guide:
CONTRIBUTING.MD - Product requirements:
docs/prd.md - Architecture:
docs/architecture.md - ADR index:
docs/adr/README.md - Loop manifest:
docs/loop-manifest.md - Design-system planning:
docs/design-system-planning.md - Observability:
docs/observability.md - Personas and test scenarios:
docs/personas-and-test-scenarios.md - MVP security review:
docs/security-review.md
After the database bootstrap (bun run db:migrate), seed a demo dataset
covering repos, loops, runs, run steps, artifacts, approvals, and Vercel
deployment states in every status. DATABASE_URL must explicitly identify a
local Postgres database:
bun run db:seedSeeding is idempotent (upsert by fixed id), so running it again does not duplicate rows. To clear the fixed-id demo rows and reseed from scratch:
bun run db:seed:resetReset only deletes the exact rows this script owns, not the whole table, so any other data in those tables is left untouched.
Add -- --dry-run to either command to print the planned row counts without
writing. Per
ADR 0007, both
commands refuse to run when DATABASE_URL is missing or malformed, when
NODE_ENV or VERCEL_ENV is production, or when the URL is not Postgres on
a loopback host (localhost/127.0.0.1/::1) — Loopworks demo data must never
write into a database that isn't explicitly and obviously local.