maskin
CLAUDE.md8 starsChanged 3 months agoApache-2.0View on GitHub
If the user asks how to start this repo, start it, set it up, or similar — follow this flow exactly. The goal is to get them from clone → running workspace → MCP-connected → get_started in as few steps as possible, inside a SINGLE Claude Code session. Do NOT guess commands or URLs; read the dev server's own banner output. Don't skip steps 2 or 5. The API key and workspace id only exist after the dev server boots…
- Reads credentials
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-shadcn-componentSkill
- frontend-taskSkill
- workspace-observer-onboardingSkill
- add-shadcn-componentSkill
- frontend-taskSkill
- maskin-designSkill
The file
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 1 is distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 17
Don't skip steps 2 or 5. The API key and workspace id only exist after the dev server boots and the auto-bootstrap runs.
- Very specific: line 92
All validation via Zod schemas in packages/shared/src/schemas/
- Very specific: line 96
All write endpoints accept Idempotency-Key header
- Very specific: line 104
Frontend responsiveness is non-negotiable: every UI surface must work at 375px (mobile), 768px (iPad portrait), and 1024px (iPad landscape).
- Very specific: line 105
All external inputs validated at system boundaries — see .claude/rules/input-validation.md for specifics
- Distinctive: line 212
Test pyramid for frontend: lib utilities → hooks → components
- Very specific: line 228
Important: All env vars used at runtime must be listed in turbo.json globalPassThroughEnv.
- Very specific: line 247
Read it with useFeatureFlag(id) at one boundary, as high in the tree as possible (shell or route layout) — never scattered across …
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Onboarding a new user from Claude Code
If the user asks how to start this repo, start it, set it up, or similar — follow this flow exactly. The goal is to get them from clone → running workspace → MCP-connected → get_started in as few steps as possible, inside a SINGLE Claude Code session. Do NOT guess commands or URLs; read the dev server's own banner output.
- Install deps + start the stack. Run
pnpm install, then start the dev stack in the background withpnpm dev:winon Windows orpnpm devon macOS/Linux. Use a background shell so the logs keep streaming. The dev script auto-seeds missingINTEGRATION_ENCRYPTION_KEYandDATABASE_URLin.env— do NOT write or overwrite those yourself. - Wait for the startup banner. Poll the shell output (BashOutput / similar) until you see the
🚀 Maskin is runningbanner printed byapps/dev. It appears afterPG NOTIFY bridge startedand the S3/container init lines. Don't tell the user anything is ready before you see this banner. - Extract the ready-made MCP command from the banner. The banner prints a copy-pasteable
claude mcp add maskin -e API_BASE_URL=... -e API_KEY=ank_... -e WORKSPACE_ID=... -- pnpm --filter @maskin/mcp startline with real credentials (the dev bootstrap auto-provisions adev@localactor +My Workspaceon a fresh DB, or looks up an existing actor if the DB already has one). Parse that exact line from the log output — do NOT reconstruct it by hand. - Run that exact
claude mcp addcommand for the user so the MCP server gets wired into Claude Code. - Reload plugins to pick up the new MCP server in this session by running
/reload-plugins. This is critical — it makes the Maskin MCP tools (includingget_started) available immediately without closing and reopening Claude Code. - Call
get_startedwith no args to see the loop menu. Do NOT wait for the user to paste a starter prompt. Call theget_startedMCP tool with no arguments (or justworkspace_id). It returns a numbered list of marketplace loops with ids and descriptions. Read that list back to the user and ask (a) which loop they want to install and (b) whether they want to rename the workspace. Then callget_startedagain with{ loop_id: "<id from the list>", confirm: true, workspace_name: "<name if they want to rename, else omit>" }to install. - Relay the "Connect your Claude subscription" prompt. When
get_startedinstalls a loop, its response includes a block directing the user to<frontendUrl>/<workspaceId>/settings/keysto import their Claude Pro/Max credentials — agent sessions cannot run without them. Render that prompt as written and wait for the user to confirm before kicking off the pipeline.
Don't skip steps 2 or 5. The API key and workspace id only exist after the dev server boots and the auto-bootstrap runs; and without /reload-plugins the MCP tools won't be callable in the current session.
Sub-project CLAUDE.md
apps/web/CLAUDE.md— detailed frontend guidance: product philosophy, design system, component conventions, routing, state management, SSE patterns, and styling rules
Project Rules
.claude/rules/frontend.md— frontend component reuse, DRY, and consistency rules (shadcn/ui, Radix UI).claude/rules/testing.md— testing conventions for all test types (unit, integration, E2E, frontend).claude/rules/pre-commit.md— pre-commit checklist (lint, type-check, tests).claude/rules/pr-merge.md— PR merge checklist (up to date with main, lint, type-check, tests).claude/rules/input-validation.md— input validation requirements at system boundaries (HTTP params, env vars, DB triggers).claude/rules/structural-verification.md— file placement and build configuration verification checklist.claude/rules/known-pitfalls.md— registry of recurring bugs to check against before submitting code.claude/rules/integrations-mcp.md— Integrations & MCP class rules (source of truth: workspace's Tech principles & best-practices doc).claude/rules/web-ios-parity.md— any user-visible change to the web app or the iOS app must be made in both, in the same PR; read before touchingapps/weborapps/apple.claude/rules/feature-flags.md— how to add, place, and retire a feature flag (one boundary per feature; no shared-state change).claude/rules/verification.md— mandatory runtime-verification gates: integration tests for DB/route changes, E2E specs for frontend changes.claude/rules/live-verification.md— live-environment evidence gates for integration/provider work: surface matrix, staging + production runs, no provider ids in the UIpackages/db/MIGRATIONS.md— migration conventions for hot tables (CONCURRENTLY indexes, chunked backfills); read before editing a.sqlfile produced bypnpm db:generate
Reference
docs/reference/README.md— Canonical documentation for Maskin's primitives — read the matching page before trusting model memory.
Observability
- Frontend (
apps/web) — Grafana Faro (src/lib/faro.ts) reports browser-side JS exceptions, unhandled rejections, Core Web Vitals, failed/apicalls and the TanStack route name into the same Grafana Cloud stack as the backend. It runs in parallel with Sentry (src/lib/sentry.ts) while we compare the two; do not assume Sentry has been retired. Both are gated onimport.meta.env.PRODplus aVITE_*_FORCE_ENABLEescape hatch, and both are tree-shaken out entirely when theirVITE_config var is absent at build time — so aVITE_var missing fromapps/dev/Dockerfile's ARG/ENV list means that SDK simply isn't in the production bundle, silently. apps/agent-server/observability/README.md— for a host running agent-server as a systemd unit: journald logs, host metrics,/metricsscrape, stalled-session alertsobservability/coolify-host/README.md— for a host runningdocker-compose.prod.ymlunder Coolify: Docker logs, host metrics, SeaweedFS metrics, disk-headroom alerts. Both configs read every deployment-specific value from the environment — keep it that way.
Runbooks
docs/runbooks/agent-base-image-rollback.md— retag the previous agent-base sha image as latest and restart maskin-agent-server; how far back it reaches and how to confirm it on a real sessiondocs/runbooks/github-agent-merge-reliability.md— diagnostic + recovery guide for GitHub write-path failures (approve, merge, push, PR open) in autonomous agent sessions; includes the first-move pattern for late-run 401s (token-mint delta, installation-ID churn) and the failure-tag glossary set by the tool-call layer
Architecture
- Modular monorepo managed by Turborepo + pnpm workspaces:
apps/(deployable services) +packages/(shared libs) - Backend (
apps/dev): Hono.js + OpenAPIHono + Drizzle ORM + PostgreSQL, runs on port 3000 - Frontend (
apps/web): Vite + React 19 + TanStack Router + TanStack Query + Tailwind CSS 4, runs on port 5173, proxies/apito backend - Auth: API keys (plain text,
ank_prefix). Bearer token in Authorization header.authMiddlewareinpackages/authalso enforces workspace membership whenX-Workspace-Idis present — header-based routes don't need their ownisWorkspaceMember()check; only by-ID routes (where the workspace is derived from the resource) do. - Reviewer pitfall — do not flag missing
isWorkspaceMemberon header-scoped routes. A route that reads its workspace fromX-Workspace-Idis already protected:authMiddlewarequeriesworkspaceMembersand returns 404 before the handler runs. Only flag a missing membership check when the workspace is derived from the resource ID (e.g.GET /objects/:idwhere the workspace comes from the object row, not the header). Adding a redundantisWorkspaceMembercall inside a header-scoped route is harmless defense-in-depth, but its absence is not a security hole. - Real-time: PG NOTIFY → SSE bridge (packages/realtime) — events table has a DB trigger that fires NOTIFY on insert
- Agent execution: Docker-based container sessions in
apps/dev/src/services/session-manager.ts— spins up ephemeral containers running Claude Code, Codex, or custom CLIs. Persistent agent files (skills, learnings, memory) stored in S3-compatible storage (SeaweedFS for dev). Sessions are trackable, streamable via SSE, pausable/resumable via snapshots. - Container management:
apps/dev/src/services/container-manager.tswraps dockerode - Agent file storage:
packages/storageprovides abstractStorageProviderinterface with S3 implementation (@aws-sdk/client-s3).apps/dev/src/services/agent-storage.tsmanages pull/push of agent files. - MCP server:
packages/mcpwraps the API as 90 tools for external agents (stdio + HTTP transport) - Workspace context passed via
X-Workspace-Idheader on all workspace-scoped routes
Prerequisites
- Node.js ≥ 20
- pnpm 9.15.0
- Docker + Docker Compose (must be installed and running —
pnpm dev/pnpm dev:winstart PostgreSQL + SeaweedFS viadocker-compose, the app cannot run without it)
Commands
pnpm install— install all dependenciespnpm dev— start Docker (postgres + seaweedfs), run migrations, then start all services via Turborepopnpm dev:win— same as above but for cmd/PowerShell on Windows (uses Node script instead of bash)pnpm build— build all packagespnpm --filter=@maskin/mcp build— build a specific package (filter goes BEFOREbuild)pnpm --filter=@maskin/web build— build web app onlypnpm test— run all tests (Vitest)pnpm test -- --filter=@maskin/dev— run tests for a specific packagecd apps/dev && pnpm vitest run src/__tests__/auth.test.ts— run a single test filepnpm test:integration— run integration tests (requires real database)pnpm type-check— TypeScript type checking across all packagespnpm lint— lint with Biome (biome check .)pnpm lint:fix— auto-fix lint issuespnpm format— format with Biome- A pre-commit hook (
.githooks/pre-commit, installed bypnpm install) runs the Biome auto-fix on staged files; skip it withgit commit --no-verifyorSKIP_BIOME_HOOK=1 docker-compose up postgres -d— start PostgreSQL only (not needed if usingpnpm dev)docker-compose up— start PostgreSQL + all servicespnpm db:generate— generate Drizzle migration from schema changespnpm db:migrate— run Drizzle migrations (automatically run bypnpm dev)pnpm db:seed— seed demo data
Code Conventions
- Biome for linting + formatting (not ESLint/Prettier) — ignores
node_modules,dist,drizzle,.turbo - Tab indentation, single quotes, semicolons as-needed, 100 char line width
- All validation via Zod schemas in
packages/shared/src/schemas/ - Drizzle for all DB access — SQL-like API, no magic
- Route files export a Hono app, mounted in
apps/dev/src/index.ts - Events logged on every mutation (create/update/delete) for audit + real-time
- All write endpoints accept
Idempotency-Keyheader - Frontend uses
@path alias mapped to/srcinapps/web - Frontend routing: file-based with TanStack Router (
apps/web/src/routes/),routeTree.gen.tsis auto-generated - Frontend data fetching: TanStack Query hooks in
apps/web/src/hooks/, API client inapps/web/src/lib/api.ts - Frontend SSE: real-time cache invalidation via
apps/web/src/lib/sse-invalidation.ts - UI components: Radix UI primitives + custom components in
apps/web/src/components/ui/ - Lists are alphabetical by default: every list, dropdown, picker and filter option list sorts A→Z by its visible label (locale-aware, case-insensitive, numeric-aware) — never by
createdAt/updatedAtor API order unless the user picks that order or the order is inherent to the data (statuses, timelines, chat/log feeds, wizard steps). Sort a copy with a stable tiebreaker, once in the shared hook/helper that feeds the list - Web and iOS stay aligned: a user-visible change in
apps/webis not done until the equivalent change is made inapps/apple, and vice versa — same task, same PR, same wording and behaviour. Native idiom may differ; capability may not. Rules and exemptions:.claude/rules/web-ios-parity.md - Frontend responsiveness is non-negotiable: every UI surface must work at 375px (mobile), 768px (iPad portrait), and 1024px (iPad landscape). Use Tailwind breakpoints (
md:,lg:) — never hardcode widths or skip mobile collapse. Full guidelines inapps/web/CLAUDE.mdunder "Responsive (mobile + iPad)". - All external inputs validated at system boundaries — see
.claude/rules/input-validation.mdfor specifics - PG NOTIFY payloads must stay under 8KB — truncate large fields in DB triggers
Data Model
- Unified
objectstable withtypefield: 'insight' | 'bet' | 'task' actorstable: humans and agents share the same identity modelworkspaceswith configurable settings (statuses, field definitions, display names)workspace_members— joins actors to workspaces with roleseventstable = audit log + real-time feed (PG NOTIFY trigger)relationships= universal edge table between objects (unique on source_id + target_id + type)triggers= cron or event-based automation, fires agents via container sessionssessions= container execution sessions, tracks lifecycle (pending → running → completed/paused/failed/timeout)session_logs= append-only log output from container sessions (stdout/stderr/system), used for SSE streamingagent_files= metadata index for agent files stored in S3 (skills, learnings, memory)
API Routes
Public (no auth)
GET /api/health— liveness, never touches the database (the container healthcheck);GET /api/health/db— database readiness, 503 on a stall (for monitoring, not the container healthcheck)GET /api/openapi.json— OpenAPI specPOST /api/actors— signup, returns API key (agents: caller API key + workspace_id, born as a member)POST /api/device-auth/start,POST /api/device-auth/token— device sign-in for a keyboard-less client (Apple TV): the device gets a short code, a signed-in person approves it at/tv, the device then receives the session once
Device sign-in approval (/api/device-auth, signed-in humans only)
GET /api/device-auth/preview— which device is asking, for a codePOST /api/device-auth/approve/POST /api/device-auth/deny— approve or refuse a code
Actors (/api/actors)
GET /api/actors— list actorsGET /api/actors/:id— get actor by IDPATCH /api/actors/:id— update actorPOST /api/actors/:id/api-keys— regenerate API key
Objects (/api/objects) — unified CRUD for insight/bet/task
POST /api/objects— create objectGET /api/objects— list objects (filter by type, status, owner)GET /api/objects/facets— exact counts (by type/status/driver, quick filters, group totals) under the same filters as the list; the Objects page reads every number from here, never from loaded rows. The list also takesgroup_by/group_value,starred,working,attention_idsandqGET /api/objects/search— search objects by text in title/contentGET /api/objects/:id— get object by IDGET /api/objects/:id/graph— get object with all relationships and connected objectsPATCH /api/objects/:id— update objectDELETE /api/objects/:id— delete object
Workspaces (/api/workspaces)
POST /api/workspaces— create workspaceGET /api/workspaces— list workspaces for current actorPATCH /api/workspaces/:id— update workspacePOST /api/workspaces/:id/members— add memberGET /api/workspaces/:id/members— list members
Search (/api/search)
GET /api/search— chats (the caller's own), loops, agents, objects and automations in one call; every typed word must match; returnsresults(capped atlimit, max 150),total,capped,facetsandrelaxedcounts over the full set, andpartialfor any source that failed
Relationships (/api/relationships)
POST /api/relationships— create relationshipGET /api/relationships— list relationships (filter by source_id, target_id, type)DELETE /api/relationships/:id— delete relationship
Triggers (/api/triggers)
POST /api/triggers— create triggerGET /api/triggers— list triggersPATCH /api/triggers/:id— update triggerDELETE /api/triggers/:id— delete trigger
Events (/api/events)
GET /api/events— SSE stream (supports Last-Event-ID)GET /api/events/history— paginated event history
Sessions (/api/sessions) — container-based agent execution
POST /api/sessions— create sessionGET /api/sessions— list sessionsGET /api/sessions/:id— get session detailsPOST /api/sessions/:id/stop— stop sessionPOST /api/sessions/:id/pause— pause & snapshot sessionPOST /api/sessions/:id/resume— resume paused sessionGET /api/sessions/:id/logs— paginated log historyGET /api/sessions/:id/logs/stream— SSE stream of live logs
Graph (/api/graph) — batch operations
POST /api/graph— atomic batch create (objects + relationships in one transaction)
Integrations (/api/integrations)
GET /api/integrations— list integrations for workspaceGET /api/integrations/providers— list available providersPOST /api/integrations/:provider/connect— start OAuth/connection flowGET /api/integrations/:provider/callback— OAuth callback (no auth)DELETE /api/integrations/:id— disconnect integrationGET|PUT /api/integrations/:id/grants,PUT|DELETE /api/integrations/:id/grants/:actorId,PUT /api/integrations/:id/default-access— which agents may use an integration, and how (seedocs/reference/agent-access.md)POST|GET|DELETE /api/integrations/:provider/gateway— gated gateway to a hosted MCP (Gmail, Google Calendar, Linear, PostHog)
Custom tool servers (/api/custom-mcp-servers) — a workspace's own MCP servers, shared with agents through grants
POST /api/custom-mcp-servers— add a server (humans only)GET|PUT /api/custom-mcp-servers/:id— read (secrets masked) or change one; removal isDELETE /api/integrations/:id
Webhooks (/api/webhooks) — no auth
POST /api/webhooks/:provider— incoming webhook handler
MCP HTTP Transport
POST /mcp— Streamable HTTP transport for MCP Apps
Testing
- Full conventions: see
.claude/rules/testing.mdfor patterns, file locations, and examples - Pre-commit: see
.claude/rules/pre-commit.md— always run lint, type-check, and tests before committing - Backend unit tests: Vitest with mock DB context (
apps/dev/src/__tests__/setup.ts) - Backend integration tests: Vitest with real PostgreSQL (
apps/dev/src/__tests__/integration/) - Frontend tests: Vitest + React Testing Library + jsdom (
apps/web/src/__tests__/) - E2E tests: Playwright (
apps/e2e/src/tests/) - Test pyramid for frontend: lib utilities → hooks → components
pnpm test -- --run— run all unit testspnpm test:integration -- --run— run integration tests (requires DATABASE_URL)pnpm test:e2e— run E2E tests (requires running dev server)
Environment Variables
DATABASE_URL— PostgreSQL connection string (required)PORT— server port (default 3000)S3_ENDPOINT— S3-compatible storage endpoint (default:http://localhost:8333for SeaweedFS)S3_BUCKET— storage bucket name (default:agent-files)S3_ACCESS_KEY,S3_SECRET_KEY— S3 credentials (default:admin/adminfor dev)S3_REGION— S3 region (default:us-east-1)CORS_ORIGIN— comma-separated allowed origins for CORS (default:http://localhost:5173)FF_TESTER_ACTOR_IDS— comma-separated actor UUIDs who get early access to flagged features (default: empty)FF_TESTER_FEATURES— comma-separated flag ids those testers see (default: empty = every flag off for everyone)
Important: All env vars used at runtime must be listed in turbo.json globalPassThroughEnv. Turbo filters env vars — unlisted ones are silently unavailable to dev/build tasks. When adding new env vars (e.g., for integrations), always add them there too.
Feature Flags
Ship a feature to a couple of named testers before everyone else. Config is read
at runtime from the apps/dev environment, so a rollout or rollback is an env
change plus a backend restart — never a frontend rebuild.
FF_TESTER_ACTOR_IDS=<uuid>,<uuid> # actors who get early access
FF_TESTER_FEATURES=some-flag # flag ids those actors seeA flag has two states: off, or on for the tester actors. There is no "on for everyone" setting — shipping to everyone means deleting the flag, not promoting it, so the config can't become a graveyard of undeleted code.
- Register every flag id in
FLAGSinapps/dev/src/lib/feature-flags.ts; unregistered ids always resolve tofalse. - Read it with
useFeatureFlag(id)at one boundary, as high in the tree as possible (shell or route layout) — never scattered across components. Third call site for one flag means the boundary is wrong. - Flags gate per-actor behaviour, never shared state. A flag may gate any behaviour whose off-state is the already-shipped state — visual (a UI branch) or behavioural (an action the system takes for the flagging actor, read per driver-actor). A flag must never make a data-layer, API, or migration change safe: those change shared state for everyone, and an actor-scoped flag read at runtime cannot isolate the un-flagged population, so such changes must be safe for all users on their own. If a design needs a breaking backend change, raise it rather than wrapping it in a flag.
- Resolution happens server-side (
GET /api/feature-flags); the tester actor ids never reach the browser.
Full guide, including the test-only ff:<flagId> localStorage override and the
retirement checklist: .claude/rules/feature-flags.md.
Principles
- Simple & intuitive
- Speed — blazingly fast
- Everything's an API
- Transparent — live feedback on everything
- Agents-first — agents run autonomously
- Idempotency — all write endpoints accept Idempotency-Key
- Observability — structured logging from day one
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

