Instructions for AI coding agents working on Coop. README.md is for humans; this file is for machines. The nearest AGENTS.md to the edited file wins; explicit user prompts override everything.
This file inherits from the ROOST community policy — read it once:
- ROOST community
AGENTS.md— pan-org agent rules (dependency approval, CI/CD approval, small diffs, PR standards). - ROOST
CONTRIBUTING.md— contribution standards (explainable, reviewable, digestible).
Four independent packages, not an npm workspace — each has its own package.json and lockfile:
/— root scripts, graphql-codegen, docker compose orchestration/server— Express + Apollo GraphQL API (ESM,"type": "module")/client— React + Vite + Apollo Client frontend (Ant Design, TailwindCSS)/db— migration runner for Postgres, ClickHouse, Scylla/migrator— package and CLI tool for database migrations
Node 24 (.nvmrc). Running on Node 20 produces EBADENGINE warnings and can fail native builds.
Reference files: README.md (getting started), server/bin/README.md (utility scripts), docs/ (architecture, ADRs).
-
API: REST + GraphQL (Apollo Server); client uses Apollo Client with InMemoryCache; server resolvers are aggregated in
server/graphql/resolvers.ts, with the SDL and per-domain resolvers inserver/graphql/modules/. -
GraphQL authoring: Inline in resolver files with
/* GraphQL */comment markers — codegen discovers queries this way. Searching forgqlorgraphqlalone misses most of it. -
GraphQL codegen:
npm run generate(from root) regeneratesclient/src/graphql/generated.tsandserver/graphql/generated.ts. Never hand-edit eithergenerated.ts. Never hand-merge eithergenerated.tsduring a rebase/merge — pick one side withgit checkout --ours|--theirs <file>, then runnpm run generate. Hand-merging produces output that parses but drifts from the schema. -
Adding a new built-in
SignalType: the type list is hand-mirrored in four files; missing any one ships a signal that's invisible to the dashboard. Update all of:server/services/signalsService/types/SignalType.ts— the canonical TS enum-like object (BuiltInExternalSignalTypeorBuiltInThirdPartySignalType) and theintegrationForSignalTypeswitch.server/services/signalsService/types/SignalArgsByType.ts— bothSignalArgsByTypeandRuntimeSignalArgsByType(theSatisfies<>will fail compile until you do).server/graphql/modules/signal.ts— theenum SignalType { ... }block inside the SDL string. Thesignal.test.tscoverage test fails if you miss this.client/src/models/signal.ts— theintegrationForSignalTypeswitch (the server's switch is the source of truth for whichIntegrationa type belongs to).
After step 3, run
npm run generatefrom the repo root to refresh the codegen output. -
Data model: Use Kysely query builder for Postgres; ClickHouse via raw SQL in
server/storage/dataWarehouse/ClickhouseAdapter.ts; Scylla via Cassandra driver. -
Dependency injection: Server uses BottleJS DI (wired in
server/iocContainer/). Register services iniocContainer, don't export singletons from service files. Consumers receive dependencies via DI rather than importing directly. BypassingiocContainerwill work at runtime but breaks test mocking patterns.
Prerequisites: Node 24 (.nvmrc), Docker + Docker Compose v2, 8 GiB RAM recommended (running an instance requires 4 GiB, the rest will be used by development tools).
# Start backing services (Postgres, ClickHouse, Scylla, Redis)
npm run up
# Install dependencies in all packages
npm install
(cd client && npm install)
(cd server && npm install)
(cd db && npm install)
# Copy .env files for /server, /db, and /client (defaults work for local dev)
cp server/.env.example server/.env
cp db/.env.example db/.env
cp client/.env.example client/.env
# Create databases, then run migrations.
npm run db:create -- --env staging --db api-server-pg
npm run db:create -- --env staging --db scylla
npm run db:create -- --env staging --db clickhouse
npm run db:update -- --env staging --db api-server-pg
npm run db:update -- --env staging --db scylla
npm run db:update -- --env staging --db clickhouse
# Create organization and admin user (all flags required)
npm run create-org -- \
--name "Test Org" \
--email "admin@example.com" \
--website "https://example.com" \
--firstName "Admin" \
--lastName "User" \
--password "your-password"
# Start dev servers (separate terminals recommended)
npm run client:start # React dev server
npm run server:start # Express + GraphQL API
npm run generate:watch # (optional) watch GraphQL changesClient: http://localhost:3000 · Server: http://localhost:8080
Both packages use Vitest. Server tests need the local backing services and migrations; client tests run in-process with jsdom.
# Run all tests (via docker compose)
docker compose run --rm test
# Server unit tests (backing services must already be running)
(cd server && npm test)
# Client unit tests (no Docker)
(cd client && npm test)Lint / format / type-check (no Docker needed):
npm run lint # lint all packages
npm run prettier:fix # format all packages (alias: npm run format)
(cd server && npm run lint)
(cd client && npm run lint)If tests fail with database errors, check migration logs via docker compose logs migrations.
CI runs entirely via GitHub Actions (.github/workflows/apply_pr_checks.yaml). Most PR checks are defined as docker compose services so you can reproduce any CI job locally; formatting and GraphQL codegen run directly via actions/setup-node. Run them in your shell (paste-as-is — each command's exit code matches the corresponding CI step's exit code):
npm ci && npm run prettier
npm ci && npm run generate && test -z "$(git status --porcelain)"
docker compose run --rm backend npm run lint
docker compose run --rm backend npm run build
docker compose run --rm client npm run lint
docker compose run --rm client npm run build
docker compose run --rm testIndividual checks:
| CI job | Local command |
|---|---|
check_formatting |
npm ci && npm run prettier |
check_generated_graphql |
npm ci && npm run generate && test -z "$(git status --porcelain)" |
check_api_server (lint) |
docker compose run --rm backend npm run lint |
check_api_server (build) |
docker compose run --rm backend npm run build |
run_frontend_checks_if_changed (lint) |
docker compose run --rm client npm run lint |
run_frontend_checks_if_changed (build) |
docker compose run --rm client npm run build |
check_api_server (test) |
docker compose run --rm test |
Tear down:
docker compose down # stop containers, keep DB volumes
docker compose down -v # also drop DB volumes (fresh DBs next run)Note: check_migration_order runs only in GitHub Actions — it's GitHub-specific and not needed locally. When adding a migration, use date -u +"%Y.%m.%dT%H.%M.%S" for the filename prefix.
- No secrets in code or committed files. Use environment variables via
.env(gitignored). - Do not disable lint or type rules to silence errors. Fix the underlying issue, or use a narrowly-scoped
// eslint-disable-next-line <rule>/// @ts-expect-errorwith a comment explaining why. - Before adding a new dependency, check it for known CVEs and confirm the license is compatible with
LICENSE(Apache 2.0). - Default Docker bindings are
127.0.0.1; do not change bind addresses without explicit instruction.
- Keep diffs small and focused; split unrelated changes into separate PRs.
- PR titles are descriptive and imperative ("Add X", "Fix Y").
- When opening a GitHub PR, use the template at
.github/PULL_REQUEST_TEMPLATE.mdbut do not actually write anything in the PR description. Let your human operator do that. - New behavior requires a test. Bug fixes require a regression test.
- All CI checks (above) must pass before requesting review.
CHANGELOG.md follows Keep a Changelog; Coop versions follow SemVer. Release process: docs/development/releases.md.
- Update
## [Unreleased]in the same PR as the change, not in a later cleanup PR. - Only notable changes get an entry: what someone deploying Coop needs to know. Internal refactors, test and CI plumbing, lint fixes, repo hygiene, and dependency bumps with no user-visible impact do not.
- Use only the six Keep a Changelog headings —
### Added,### Changed,### Deprecated,### Removed,### Fixed,### Security— adding the heading under## [Unreleased]if it's missing. Don't invent others. Fixedis for behavior that was wrong and is now correct;Changedis for intentionally altering behavior that was already correct.- Keep each entry to a single concise line, essentially a title: no reasoning, mechanism, or caveats. Anyone who needs the detail follows the PR link.
- Format:
- Description ([#123](https://github.com/roostorg/coop/pull/123) by [@user](https://github.com/user)), adding, closes [#456](...)where it applies. - Removing a GraphQL enum value, type, or field, or removing or renaming an environment variable, always earns an entry.
- Never edit a released version's section; it's a historical record. Corrections go under
## [Unreleased].
- TypeScript: ESLint + Prettier (Prettier config at root
.prettierrc; ESLint configs per package inserver/andclient/). Runnpm run lintandnpm run prettier:fixfrom root. - Naming: Use camelCase for variables/functions; PascalCase for components/classes; SCREAMING_SNAKE_CASE for constants.
- GraphQL: Type-safe resolvers and queries via codegen; never hand-edit
generated.ts. - Imports: Absolute imports configured via
tsconfig.jsonpaths; prefer@/prefix over relative paths where configured.
- Dependencies are declared in each package's
package.jsonand locked inpackage-lock.json. Add withnpm install --save <pkg>and commit the updated lockfile. - Every new or upgraded package including transitive dependencies requires human approval. Confirm the license is compatible with
LICENSE(Apache 2.0) and that there are no known CVEs. - Same conflict-resolution rule applies to any
package-lock.json: take one side withgit checkout --ours|--theirs <file>, then runnpm installin that package to reconcile.
Install gotchas:
CI runs npm ci from root. If npm ci hits ERESOLVE in any package, the lockfile has drifted from package.json — regenerate it against a known-good base:
git checkout main -- <pkg>/package-lock.json
(cd <pkg> && npm install)Do not reach for --legacy-peer-deps as a fix — it papers over real peer violations and CI's npm ci will fail on the next agent's machine.
Two things differ from a local dev setup:
- Use the production client build, not the vite dev server:
(cd client && npm run build), thennpm run server:startserves the built assets. Vite's HMR websocket does not reliably traverse the Codespace port proxy. - Apollo's GraphQL URI must be relative (
/api/v1/graphql). Hard-codedhttp://localhost:3000/...breaks because the Codespace proxies to a different host. Source of truth is theHttpLinkinclient/src/index.tsx.
- Commands over prose. Prefer
docker compose run --rm testover descriptive paragraphs. - Same review bar. PRs authored with agent assistance are held to the same standards as any other PR.
- Boundaries with alternatives. When stating a restriction, provide the alternative path (e.g. don't edit
generated.ts— regenerate vianpm run generate). - Iterate over time. Start minimal. When you give an agent the same instruction twice, add it to this file.
- Contributors update
AGENTS.md. When you find a gap, update this file as part of your PR.
Routine local setup and verification commands, including npm ci and existing build/test/lint/format/check scripts, do not require approval; the gates below apply to the changes being made, not merely to running commands.
Stop and get explicit human approval before:
- Changing license headers, copyright notices, or any legal text (including
LICENSE). - Modifying release, signing, or deploy workflows:
.github/workflows/publish-*.yaml, production Dockerfiles (Dockerfile,client/Dockerfile),docker-compose.yaml, orpackage.json"scripts"that affect deployment. - Database migrations — anything added under
db/src/scripts/<service>/runs against real data. Confirm schema design and rollback story with a maintainer ensure to use CURRENT_USER to support any user on postgres. - Deleting or renaming an existing GraphQL type or field — this breaks cached Apollo client state and any downstream consumer. Additive changes are usually safe; removals need a migration plan.
- Rewiring
server/iocContainerin a way that changes service lifecycles or startup order — cascading effects on tests and boot. - Auth, session, or request middleware (under
server/api.ts) — security-sensitive; prefer a small, reviewable PR with explicit callouts. - Adding, removing, or upgrading any dependency (including transitive dependencies in
package-lock.json) — confirm licenses are compatible with Apache 2.0 and that there are no known CVEs. - Multi-thousand-line diffs — ROOST policy is that reviewers can digest the change. Split into reviewable PRs; regenerated codegen and lockfile bumps are the only exceptions.
Agent-authored commits should include a Co-Authored-By trailer naming the agent, e.g.:
Co-Authored-By: <agent-name>
Coop is open source and contributions flow upstream; attribution matters for maintainer trust.
- Hand-merge
generated.tsor lockfiles. - Install with
--legacy-peer-depsas a workaround. - Commit
.env, credentials, or API keys. - Bypass
iocContainerby importing server singletons directly. - Silently modify a migration file that has already been applied to a shared environment — add a new forward migration instead.
- Edit a released version's section in
CHANGELOG.md— add to## [Unreleased]instead.