Skip to content

Latest commit

 

History

History
246 lines (177 loc) · 14.9 KB

File metadata and controls

246 lines (177 loc) · 14.9 KB

AGENTS.md

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:

Architecture

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).

Design

  • 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 in server/graphql/modules/.

  • GraphQL authoring: Inline in resolver files with /* GraphQL */ comment markers — codegen discovers queries this way. Searching for gql or graphql alone misses most of it.

  • GraphQL codegen: npm run generate (from root) regenerates client/src/graphql/generated.ts and server/graphql/generated.ts. Never hand-edit either generated.ts. Never hand-merge either generated.ts during a rebase/merge — pick one side with git checkout --ours|--theirs <file>, then run npm 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:

    1. server/services/signalsService/types/SignalType.ts — the canonical TS enum-like object (BuiltInExternalSignalType or BuiltInThirdPartySignalType) and the integrationForSignalType switch.
    2. server/services/signalsService/types/SignalArgsByType.ts — both SignalArgsByType and RuntimeSignalArgsByType (the Satisfies<> will fail compile until you do).
    3. server/graphql/modules/signal.ts — the enum SignalType { ... } block inside the SDL string. The signal.test.ts coverage test fails if you miss this.
    4. client/src/models/signal.ts — the integrationForSignalType switch (the server's switch is the source of truth for which Integration a type belongs to).

    After step 3, run npm run generate from 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 in iocContainer, don't export singletons from service files. Consumers receive dependencies via DI rather than importing directly. Bypassing iocContainer will work at runtime but breaks test mocking patterns.

Build and run

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 changes

Client: http://localhost:3000 · Server: http://localhost:8080

Testing

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

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 test

Individual 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.

Security

  • 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-error with 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.

Code review

  • 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.md but 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

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.
  • Fixed is for behavior that was wrong and is now correct; Changed is 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].

Code style

  • TypeScript: ESLint + Prettier (Prettier config at root .prettierrc; ESLint configs per package in server/ and client/). Run npm run lint and npm run prettier:fix from 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.json paths; prefer @/ prefix over relative paths where configured.

Dependencies

  • Dependencies are declared in each package's package.json and locked in package-lock.json. Add with npm 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 with git checkout --ours|--theirs <file>, then run npm install in 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.

Codespaces

Two things differ from a local dev setup:

  1. Use the production client build, not the vite dev server: (cd client && npm run build), then npm run server:start serves the built assets. Vite's HMR websocket does not reliably traverse the Codespace port proxy.
  2. Apollo's GraphQL URI must be relative (/api/v1/graphql). Hard-coded http://localhost:3000/... breaks because the Codespace proxies to a different host. Source of truth is the HttpLink in client/src/index.tsx.

ROOST guiding principles

  • Commands over prose. Prefer docker compose run --rm test over 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 via npm 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.

Human-approval-required actions

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, or package.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/iocContainer in 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.

Commit attribution

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.

Don't

  • Hand-merge generated.ts or lockfiles.
  • Install with --legacy-peer-deps as a workaround.
  • Commit .env, credentials, or API keys.
  • Bypass iocContainer by 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.