agentleFS
sindre-aisindre-ai/maskin/CLAUDE.md

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

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
  1. Very specific:

    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.

    line 17
  2. Very specific:

    All validation via Zod schemas in packages/shared/src/schemas/

    line 92
  3. Very specific:

    All write endpoints accept Idempotency-Key header

    line 96
  4. Very specific:

    Frontend responsiveness is non-negotiable: every UI surface must work at 375px (mobile), 768px (iPad portrait), and 1024px (iPad landscape).

    line 104
  5. Very specific:

    All external inputs validated at system boundaries — see .claude/rules/input-validation.md for specifics

    line 105
  6. Distinctive:

    Test pyramid for frontend: lib utilities → hooks → components

    line 212
  7. Very specific:

    Important: All env vars used at runtime must be listed in turbo.json globalPassThroughEnv.

    line 228
  8. Very specific:

    Read it with useFeatureFlag(id) at one boundary, as high in the tree as possible (shell or route layout) — never scattered across …

    line 247

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

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.

  1. Install deps + start the stack. Run pnpm install, then start the dev stack in the background with pnpm dev:win on Windows or pnpm dev on macOS/Linux. Use a background shell so the logs keep streaming. The dev script auto-seeds missing INTEGRATION_ENCRYPTION_KEY and DATABASE_URL in .env — do NOT write or overwrite those yourself.
  2. Wait for the startup banner. Poll the shell output (BashOutput / similar) until you see the 🚀 Maskin is running banner printed by apps/dev. It appears after PG NOTIFY bridge started and the S3/container init lines. Don't tell the user anything is ready before you see this banner.
  3. 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 start line with real credentials (the dev bootstrap auto-provisions a dev@local actor + My Workspace on 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.
  4. Run that exact claude mcp add command for the user so the MCP server gets wired into Claude Code.
  5. 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 (including get_started) available immediately without closing and reopening Claude Code.
  6. Call get_started with no args to see the loop menu. Do NOT wait for the user to paste a starter prompt. Call the get_started MCP tool with no arguments (or just workspace_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 call get_started again with { loop_id: "<id from the list>", confirm: true, workspace_name: "<name if they want to rename, else omit>" } to install.
  7. Relay the "Connect your Claude subscription" prompt. When get_started installs a loop, its response includes a block directing the user to <frontendUrl>/<workspaceId>/settings/keys to 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 touching apps/web or apps/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 UI
  • packages/db/MIGRATIONS.md — migration conventions for hot tables (CONCURRENTLY indexes, chunked backfills); read before editing a .sql file produced by pnpm 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 /api calls 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 on import.meta.env.PROD plus a VITE_*_FORCE_ENABLE escape hatch, and both are tree-shaken out entirely when their VITE_ config var is absent at build time — so a VITE_ var missing from apps/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, /metrics scrape, stalled-session alerts
  • observability/coolify-host/README.md — for a host running docker-compose.prod.yml under 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 session
  • docs/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 /api to backend
  • Auth: API keys (plain text, ank_ prefix). Bearer token in Authorization header. authMiddleware in packages/auth also enforces workspace membership when X-Workspace-Id is present — header-based routes don't need their own isWorkspaceMember() check; only by-ID routes (where the workspace is derived from the resource) do.
  • Reviewer pitfall — do not flag missing isWorkspaceMember on header-scoped routes. A route that reads its workspace from X-Workspace-Id is already protected: authMiddleware queries workspaceMembers and 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/:id where the workspace comes from the object row, not the header). Adding a redundant isWorkspaceMember call 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.ts wraps dockerode
  • Agent file storage: packages/storage provides abstract StorageProvider interface with S3 implementation (@aws-sdk/client-s3). apps/dev/src/services/agent-storage.ts manages pull/push of agent files.
  • MCP server: packages/mcp wraps the API as 90 tools for external agents (stdio + HTTP transport)
  • Workspace context passed via X-Workspace-Id header 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:win start PostgreSQL + SeaweedFS via docker-compose, the app cannot run without it)

Commands

  • pnpm install — install all dependencies
  • pnpm dev — start Docker (postgres + seaweedfs), run migrations, then start all services via Turborepo
  • pnpm dev:win — same as above but for cmd/PowerShell on Windows (uses Node script instead of bash)
  • pnpm build — build all packages
  • pnpm --filter=@maskin/mcp build — build a specific package (filter goes BEFORE build)
  • pnpm --filter=@maskin/web build — build web app only
  • pnpm test — run all tests (Vitest)
  • pnpm test -- --filter=@maskin/dev — run tests for a specific package
  • cd apps/dev && pnpm vitest run src/__tests__/auth.test.ts — run a single test file
  • pnpm test:integration — run integration tests (requires real database)
  • pnpm type-check — TypeScript type checking across all packages
  • pnpm lint — lint with Biome (biome check .)
  • pnpm lint:fix — auto-fix lint issues
  • pnpm format — format with Biome
  • A pre-commit hook (.githooks/pre-commit, installed by pnpm install) runs the Biome auto-fix on staged files; skip it with git commit --no-verify or SKIP_BIOME_HOOK=1
  • docker-compose up postgres -d — start PostgreSQL only (not needed if using pnpm dev)
  • docker-compose up — start PostgreSQL + all services
  • pnpm db:generate — generate Drizzle migration from schema changes
  • pnpm db:migrate — run Drizzle migrations (automatically run by pnpm 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-Key header
  • Frontend uses @ path alias mapped to /src in apps/web
  • Frontend routing: file-based with TanStack Router (apps/web/src/routes/), routeTree.gen.ts is auto-generated
  • Frontend data fetching: TanStack Query hooks in apps/web/src/hooks/, API client in apps/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/updatedAt or 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/web is not done until the equivalent change is made in apps/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 in apps/web/CLAUDE.md under "Responsive (mobile + iPad)".
  • All external inputs validated at system boundaries — see .claude/rules/input-validation.md for specifics
  • PG NOTIFY payloads must stay under 8KB — truncate large fields in DB triggers

Data Model

  • Unified objects table with type field: 'insight' | 'bet' | 'task'
  • actors table: humans and agents share the same identity model
  • workspaces with configurable settings (statuses, field definitions, display names)
  • workspace_members — joins actors to workspaces with roles
  • events table = 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 sessions
  • sessions = 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 streaming
  • agent_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 spec
  • POST /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 code
  • POST /api/device-auth/approve / POST /api/device-auth/deny — approve or refuse a code

Actors (/api/actors)

  • GET /api/actors — list actors
  • GET /api/actors/:id — get actor by ID
  • PATCH /api/actors/:id — update actor
  • POST /api/actors/:id/api-keys — regenerate API key

Objects (/api/objects) — unified CRUD for insight/bet/task

  • POST /api/objects — create object
  • GET /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 takes group_by/group_value, starred, working, attention_ids and q
  • GET /api/objects/search — search objects by text in title/content
  • GET /api/objects/:id — get object by ID
  • GET /api/objects/:id/graph — get object with all relationships and connected objects
  • PATCH /api/objects/:id — update object
  • DELETE /api/objects/:id — delete object

Workspaces (/api/workspaces)

  • POST /api/workspaces — create workspace
  • GET /api/workspaces — list workspaces for current actor
  • PATCH /api/workspaces/:id — update workspace
  • POST /api/workspaces/:id/members — add member
  • GET /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; returns results (capped at limit, max 150), total, capped, facets and relaxed counts over the full set, and partial for any source that failed

Relationships (/api/relationships)

  • POST /api/relationships — create relationship
  • GET /api/relationships — list relationships (filter by source_id, target_id, type)
  • DELETE /api/relationships/:id — delete relationship

Triggers (/api/triggers)

  • POST /api/triggers — create trigger
  • GET /api/triggers — list triggers
  • PATCH /api/triggers/:id — update trigger
  • DELETE /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 session
  • GET /api/sessions — list sessions
  • GET /api/sessions/:id — get session details
  • POST /api/sessions/:id/stop — stop session
  • POST /api/sessions/:id/pause — pause & snapshot session
  • POST /api/sessions/:id/resume — resume paused session
  • GET /api/sessions/:id/logs — paginated log history
  • GET /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 workspace
  • GET /api/integrations/providers — list available providers
  • POST /api/integrations/:provider/connect — start OAuth/connection flow
  • GET /api/integrations/:provider/callback — OAuth callback (no auth)
  • DELETE /api/integrations/:id — disconnect integration
  • GET|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 (see docs/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 is DELETE /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.md for 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 tests
  • pnpm 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:8333 for SeaweedFS)
  • S3_BUCKET — storage bucket name (default: agent-files)
  • S3_ACCESS_KEY, S3_SECRET_KEY — S3 credentials (default: admin/admin for 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 see

A 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 FLAGS in apps/dev/src/lib/feature-flags.ts; unregistered ids always resolve to false.
  • 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

  1. Simple & intuitive
  2. Speed — blazingly fast
  3. Everything's an API
  4. Transparent — live feedback on everything
  5. Agents-first — agents run autonomously
  6. Idempotency — all write endpoints accept Idempotency-Key
  7. Observability — structured logging from day one

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.