Read before working on this repo. Project-specific; inherits from
~/AGENTS.md.
A Linear-style project management platform. Fast, minimalist, keyboard-driven. Extensible via a plugin/skill/MCP system. Built for humans and agents equally.
Next.js 15 · TypeScript · Prisma 6 / Postgres · tRPC 11 / Zod · NextAuth v5 · Redis (pub/sub + rate limit + BullMQ) · Tailwind (warm-earthy tokens) · Vitest + Playwright.
| What | Where |
|---|---|
| DB schema | prisma/schema.prisma |
| tRPC routers | src/server/routers/ |
| Plugin runtime + MCP | src/server/services/ |
| API route handlers | src/app/api/ |
| App pages (RSC + client) | src/app/(app)/ |
| Design tokens | src/app/globals.css, tailwind.config.ts |
| Sample plugin | plugins/issue-triage/ |
| DevLog | DEVLOG.md — update at session end |
All tenant-scoped on workspaceId.
- Workspace — tenant. Short
key(e.g.AXI,PER,WRK) is the issue-id prefix (AXI-42) and is immutable after create — changing it means a data migration (see Phase 1 of 2026-04-20 inDEVLOG.mdfor the pattern). Slug/name can change freely. - Project — groups issues. Optionally belongs to an Initiative.
- Issue — the unit of work. Optional
projectId, optionalcycleId. - Cycle — time-boxed iteration (default length from
Workspace.cycleLengthDays). Issues move in/out of cycles. Surfaced as "Sprint" in the UI (since 2026-04-24); the data model, tRPC router (cycle.*), routes (/cycles), folder names, and MCP namespace (cycles.*) all staycycle*. Only display strings were renamed. If you need to rename Cycle in copy you've added, write "Sprint" in the UI. - Initiative — higher-level bucket above projects (quarterly bets,
themes). Projects nest under initiatives via nullable
initiativeId. - IssueRelation — directed, typed link between issues (blocks / duplicates / related / …). Cascade-deleted from either end.
- TimeEntry — per-user duration rows against issues. Only active when
Workspace.timeTrackingEnabled. - Attachment — polymorphic via
targetType+targetId. Can hang off any entity (issue/comment/project/initiative/…). Backed by MinIO. - Agent — first-class non-human actor.
profileKeyis the stable cross-system handle (e.g.victor,mizu) and matches the Hermes profile directory name. Hascapabilities[],webhookUrl+webhookSecretfor push dispatch,status(ONLINE/OFFLINE/BUSY),lastHeartbeatAt,maxConcurrent. Issues point at an assigned agent viaassignedAgentId(independent of the humanclaimedById). ApiKeys can point at an agent vialinkedAgentId— soissues.assignedcan infer "my work" without the caller passingprofileKey.
Workspace.autoDispatch + autoDispatchMode drive automatic agent
selection when an issue hits the queue unassigned. Modes:
MANUAL_ONLY— dispatcher is a no-op; assignment is human-only.ROUND_ROBIN— pick the least-recently-dispatched eligible agent.PRIORITY_MATCH— prefer agents whosecapabilitiescontain the lowercase priority name (urgent,high, …); round-robin tie-break.CAPABILITY_MATCH— intersect the issue's label names with agent capabilities; most matches wins; round-robin tie-break; zero-match falls through to round-robin rather than stalling dispatch.maxConcurrentcaps active assignments per agent (0 = unlimited).requireApprovalBeforeStartgates push until a human approves.autoStartOnAssigndetermines whether AGENT_ASSIGNED fires a webhook on assignment (vs just filling the agent's queue).
Bailey's system-wide value: prefer settings-driven values over
hardcoded defaults. Every workspace-level knob should surface as a
column on Workspace (or a row in a settings table) — no magic numbers
baked into handlers. Current knobs: cycleLengthDays (7),
cycleCooldownDays (0), timeTrackingEnabled (false),
attachmentQuotaMb (1024).
ApiKey has the usual coarse PluginScope[] ceiling plus three
narrowing arrays: projectIds, labelIds, initiativeIds, plus an
optional linkedAgentId pointing at the Agent row the key belongs
to. Empty arrays = unrestricted within the declared scopes. Non-empty =
key only sees/acts on rows matching those ids. Currently Victor and
Mizu hold FULL scope and no narrowing; future sub-agents should be
narrowed where possible (e.g., a per-initiative bot gets only that
initiativeId). Set linkedAgentId on the key so issues.assigned
can infer the agent without the caller supplying profileKey.
- Every tenant-scoped row includes
workspaceId. Access gated byworkspaceProcedure/adminProcedureinsrc/server/trpc.ts. - Write audit + event together. Use
recordChange()fromsrc/server/audit.ts— it creates anAuditLogandActivityEventin the same transaction and publishes to Redis for SSE fan-out. - Scope-gated plugin access. Manifest
scopesis the ceiling for any API key issued. Enforce inapi-key-auth.ts. - No mocks in integration tests. Use the Postgres + Redis service
containers from
docker/docker-compose.yml. - Keep client components small. Data comes from tRPC queries; RSC is used for session resolution and the shell layouts.
Nothing-inspired minimalism + Anthropic warm earthy. Tokens in
globals.css — do not hardcode colors. Prefer bg-card/40 and
text-muted-foreground over ad-hoc tailwind classes. Mono for identifiers
(issue IDs, keys), sans for everything else.
Per-user Appearance prefs (User.density, User.textSize) cascade
onto <html data-density="…" data-textsize="…"> via AppearanceProvider.
Use these utility classes from globals.css instead of hardcoded
text-[10px]/text-[11px] when you're rendering primary content (the
ones the Appearance setting should actually move):
.text-id— issue IDs / keys / mono identifiers (includesfont-mono).text-meta— timestamps, secondary metadata (sans).text-filename— filename overlays on attachment thumbs.text-subtitle— topbar subtitles
Leave true labels (badges, kbd hints, count bubbles, uppercase eyebrows) hardcoded — those should stay small regardless of Appearance.
- No pure white or pure black — always warm paper or graphite (see tokens).
- No blocking pub/sub on mutation path. Publish is best-effort; durability
is in
WebhookDeliveryrows, processed bysrc/server/worker.ts. - No global
PrismaClientre-instantiation in dev (already guarded; don't add bypass). - No giant feature commits — DEVLOG each session, then commit.
pnpm lint && pnpm typecheck && pnpm testpnpm test:e2e(needs Postgres + Redis)- Append to
DEVLOG.md - Commit; optional push.
- Follow
RELEASE.mdfor release tagging and deploy.
- GitHub
originis the code source of truth; Forge is the ownership and delivery source of truth. Forge agents, Codex Desktop tasks, and contributors follow the same workflow. - One issue has one active work session, one isolated worktree/branch, and one primary implementation PR. Check and claim the issue's Delivery session before editing. Continue an existing session instead of creating competing work.
- Branch names include the issue key (
codex/axi-123-description). Never edit, commit, or test changes in the deployment/main checkout. - Heartbeat the work session during meaningful phases and after commits. Link
PRs through native GitHub
IMPLEMENTSrelations, never generic attachments. - GitHub owns PR/review/check/merge truth. Forge records the delivery sequence: Claimed → In progress → PR/review → Merged → Released → Deployed → Verified.
- Release/deploy/verification require explicit operator authority and are
serialized. See
docs/engineering/work-management.mdandRELEASE.md. - Use timestamped Prisma migration directories to prevent parallel numbering collisions.
- Forge's branch contract is trunk-based: integration branch
main, release branchmain, tag sourcemain, staging source an exactmainSHA, and production source an immutablev*tag. Hotfixes branch from and return tomain; there is no back-merge target. Other repositories may declare a different contract, such as Hermes Relay'sfeature → dev → main → tagrelease train.