Quick reference for AI assistants working on the Metaboost repo (HTTP API + Next.js app).
Authoritative AI rules: .cursor/, .cursorrules. Machine-generated exports: .llm/exports/ (produced and published by llm-exports-sync / llm-exports-full; not by default local npm runs). LLM_EXPORT_ALLOW_LOCAL=1 is only for scripts/llm/ development; see docs/development/llm/DOCS-DEVELOPMENT-LLM.md and llm-cursor-source. Non-Cursor: use exports first, then docs/development/llm/LLM-EDITOR-ALIGNMENT-PROMPT.md if needed. Policy and gh setup: docs/development/llm/DOCS-DEVELOPMENT-LLM.md, docs/development/llm/GH-EXPORTS-SETUP.md.
- Node.js 24+ (see
.nvmrc) - npm workspaces:
apps/api,apps/web,apps/web/sidecar - TypeScript strict, ESM
npm install
npm run build
npm run lint
npm run dev:api # API only (default port 4000)
npm run dev:web # Next.js only
npm run dev:web-sidecar # Build sidecar + run web with sidecarNode and npm are provided by the repo's Nix flake, not a global install. When running terminal commands (e.g. in Cursor's agent), use the wrapper so the correct environment is available:
- Wrapper:
./scripts/nix/with-env <command> [args...] - Examples:
./scripts/nix/with-env npm run build,./scripts/nix/with-env npm run dev:api - Run from repo root. Full explanation and setup-in-other-repos: docs/CURSOR-NIX-WITH-ENV.md.
- Agent runs that use the wrapper may need full permissions so Nix can write to its cache.
apps/api/– Standalone Express HTTP APIapps/web/– Next.js app (fetches runtime config from sidecar whenRUNTIME_CONFIG_URLis set; optionalnpm run validate-env -w @metaboost/web/-w @metaboost/management-webfor a Podverse-style env check)apps/web/sidecar/– Runtime-config sidecar (serves env-derived config for the Next.js app)packages/ui/– Shared UI: design tokens and mixins inpackages/ui/src/styles/(variables, mixins); component groups (form, layout, modal, navigation, table, feedback, bucket) and hooks (useDeleteModal, useTableFilterState, useAuthValidation) documented in packages/ui/PACKAGES-UI.md. Form controls live underpackages/ui/src/components/form/(see that directory’s PACKAGES-UI-SRC-COMPONENTS-FORM.md).- Validation schemas: Joi validation lives in
apps/api/src/schemas/andapps/management-api/src/schemas/. Controllers and routes import from these directories only; no ad-hoc schema definitions in controllers or routes. Shared request/response types may live inpackages/helpers-requestsorpackages/helpers. See each app’sschemas/APPS-API-SRC-SCHEMAS.mdandschemas/APPS-MANAGEMENT-API-SRC-SCHEMAS.mdfor the file layout.
Secrets (JWT, DB, Valkey, etc.) are auto-generated by make local_env_setup via
scripts/env-setup-secrets.sh. Canonical contributor-facing env templates/examples are apps/*/.env.example, apps/*/sidecar/.env.example, and infra/config/env-templates/*.env.example (see docs/development/env/ENV-REFERENCE.md). scripts/local-env/setup.sh generates app and infra/config/local/*.env files from those templates when missing. Optional override files under dev/env-overrides/local/*.env (symlinked from ~/.config/metaboost/local-env-overrides/ via prepare/link) are merged by setup.sh when present — info.env, user-agent.env, mailer.env, auth.env, locale.env (see docs/development/env/LOCAL-ENV-OVERRIDES.md). API_CORS_ORIGINS and MANAGEMENT_API_CORS_ORIGINS should match local web base URLs for local dev. WEB_BASE_URL / WEB_URL stay as local dev defaults in generated app env (not in home override stubs).
- Prepare:
make local_env_prepare— ensures~/.config/metaboost/local-env-overrides/exists and creates missing override.envfiles with keys/defaults seeded from canonical.env.exampletemplates (write-home-override-stubs.rb; never overwrites existingKEY=lines; appends missing keys with defaults); edit for non-default values - Link:
make local_env_link— symlinksdev/env-overrides/local/*.envto existing files in the home overrides directory - Clean:
make local_env_clean— removes generated repo env anddev/env-overrides/local/*.envsymlinks; does not remove home files under~/.config/metaboost/local-env-overrides/. Runlocal_env_linkbeforelocal_env_setupafter a clean if you use home overrides. - Setup:
make local_env_setup— seed env files from canonical templates/examples, then apply auto-generated secrets and overrides (info, user-agent, mailer, auth, locale) when those override files exist (via repo paths underdev/env-overrides/local/after link). - One-shot:
make local_setup—local_env_setup+local_infra_up
See docs/development/env/LOCAL-ENV-OVERRIDES.md.
See .llm/LLM.md for full guidelines. Use the llm-history skill when updating history or starting feature work. The llm-history-tracking rule (always applied) requires updating .llm/history/active/[feature]/ on any file-modifying response.
- Follow
.cursor/skills/llm-history/SKILL.mdfor history timing, format, and 10-session limit.
For PRs touching auth, CORS, outbound HTTP, redirects, SQL query builders, or proxy headers, use docs/development/security/SECURITY-REVIEW-CHECKLIST.md. Finding-level traceability lives in docs/development/security/SECURITY-FINDINGS-CLOSURE-MATRIX.md. Run npm run security:check locally (also part of make validate_ci).
When implementing features or executing plans that touch api or management-api, include integration tests (see api-testing). When they touch web or management-web, include E2E tests (see e2e-page-tests). If an API change affects UI in web or management-web, add or update the relevant E2E specs as well.
| Script | What it runs | Services needed |
|---|---|---|
npm run test:unit |
Vitest in @metaboost/helpers, @metaboost/helpers-currency, @metaboost/helpers-valkey, metaboost-signing, @metaboost/rss-parser |
None (pure Node) |
npm run test:e2e:api |
check-test-requirements then Vitest in apps/api + apps/management-api |
Postgres 5632, Valkey 6579 (make test_deps) |
npm run test:e2e:web |
make e2e_test (Playwright: web + management-web) |
Full E2E stack (make test_deps + seeds) |
npm run test:e2e:web:reports |
make e2e_test_report (HTML step-screenshot reports) |
Same as test:e2e:web |
npm run test:reports |
test:unit → test:e2e:api → test:e2e:web:reports |
All tiers |
npm test |
test:unit → test:e2e:api → test:e2e:web |
All tiers (slow — runs locally, not in CI) |
Full npm test including Playwright is intentionally slow and meant for developer-local runs. CI skips the full suite; maintainers run make test_deps and npm test targets locally before merge.
One setup file (apps/api/src/test/setup.ts) provides smart-default env for all tests. Tests that need different env (e.g. signup/mailer) override only those vars at the top of the file and load app/config via dynamic import in beforeAll so overrides apply. The first step is a requirements check: Postgres and Valkey must be reachable at the test ports (defaults 5632, 6579 — distinct from Metaboost dev Docker on 5532/6479). If not, the script exits with instructions (e.g. make test_deps). In Nix/agent environments use ./scripts/nix/with-env npm run test:e2e:api.
- Test requirements (Makefile): Test-related commands live in
makefiles/local/Makefile.local.test.mk. From repo root:make test_depsstarts Postgres on 5632 and Valkey on 6579, creates two test databases:metaboost_app_test(main app;infra/k8s/base/ops/source/database/linear-migrations/app/0001_app_schema.sql) andmetaboost_management_test(management-api;infra/k8s/base/ops/source/database/linear-migrations/management/0001_management_schema.sql), and creates app/management read and read_write roles (metaboost_app_read/metaboost_app_read_write,metaboost_management_read/metaboost_management_read_write).make help_testprints instructions. - Test databases: Tests use dedicated DBs on the same Postgres instance. Main:
metaboost_app_test(api and management-api main-user CRUD). Management:metaboost_management_test(management identities, permissions, events). Default test ports are 5632 (Postgres) and 6579 (Valkey). Each test run starts with a clean slate: globalSetup truncates main and management tables once before any test file runs (api:apps/api/src/test/global-setup.mjs; management-api:apps/management-api/src/test/global-setup.mjs). - Database naming (dev/Docker/K8s): Two databases in one Postgres instance: app DB
metaboost_app, management DBmetaboost_management. Canonical env templates defineDB_APP_NAMEandDB_MANAGEMENT_NAME; owner credentials areDB_APP_OWNER_USER(defaultmetaboost_app_owner) andDB_APP_OWNER_PASSWORDindb.env(withDB_HOST/DB_PORTfor clients). Migrator credentials areDB_APP_MIGRATOR_*andDB_MANAGEMENT_MIGRATOR_*. The official Postgres Docker image still readsPOSTGRES_USER,POSTGRES_PASSWORD, andPOSTGRES_DB; local Compose maps those fromDB_APP_OWNER_USER,DB_APP_OWNER_PASSWORD, andDB_APP_NAME. Apps useDB_APP_NAME(synced bylocal_env_setup). Management-api uses the sameDB_HOST/DB_PORTplusDB_MANAGEMENT_NAMEandDB_MANAGEMENT_READ_WRITE_*(inherited from env groupdb; no separateMANAGEMENT_DB_*vars). Role names:metaboost_app_read/metaboost_app_read_write,metaboost_management_read/metaboost_management_read_write; keysDB_APP_READ_*andDB_MANAGEMENT_READ_*. - Mailer: No local mailer service is required. Tests that cover verification flows use a Vitest mock of the
mailer module to capture tokens and call verification endpoints; see
apps/api/src/test/*.test.ts.
- Credentials: The system stores only
passwordHash(no plaintext password). Never returnpasswordHashor any credential field in API responses. UseuserToJson(or a similar safe serializer) for user data; never serializereq.useroruser.credentialsdirectly. - Verification tokens: Token hashes and raw tokens are never returned or listed by the API; they are only consumed server-side (verify-email, reset-password, confirm-email-change). Do not add endpoints that expose verification token entities.
- User in responses: For the authenticated user only (login, me, signup, refresh, confirm-email-change, update-profile), use
PublicUser(id, idText, email, username, displayName) viauserToJson. Email is PII and must not be returned for other users. For any response that describes another user (e.g. bucket admins list/detail), usePublicUserSummary(id, idText, username, displayName) viauserToPublicSummaryfromapps/api/src/lib/userToJson.ts. - API messages and locale: API structural messages (e.g. "Invalid or expired link", "Authentication required") are American English. Password validation text and email content (verification, password reset, email-change) are localized: resolved from
Accept-Languagewhen the value is supported (en-US, es), else app default fromDEFAULT_LOCALEenv. Do not localize themessagefield for structural errors; user-facing content such as email subject/body and password validation messages is localized.
- Upgrade policy: Apply patch and minor updates routinely (e.g.
npm outdatedthen bump versions andnpm install). Major upgrades (e.g. @faker-js/faker, dotenv, eslint-plugin-perfectionist, express-rate-limit, lint-staged, nanoid, @types/node) are done separately with migration and testing. After any dependency change, runnpm run buildandnpm run lint; commit the updatedpackage-lock.jsonso CI and Docker use the same versions. - Linux-canonical lockfile: CI runs on Linux and needs Linux optional deps (e.g.
@parcel/watcher,@next/swc-linux-x64-gnu, next-intl's@swc/core) in the lockfile. Generate or refresh the lockfile under Linux x64 so it stays correct for CI: run./scripts/development/update-lockfile-linux.sh(requires Docker). The bump-version script runs this automatically before committing. When you add or update dependencies from a Mac, run that script and commit the updatedpackage-lock.json.
- Strict equality (
===/!==only) - Avoid type assertions (
as); prefer types and narrowing - Semicolons in JS/TS
Imports must follow a consistent hierarchy and be alphabetized within groups: (1) Node built-ins, (2) external packages, (3) workspace packages (@metaboost/*), (4) relative imports (parent, sibling, index), (5) style imports (SCSS/CSS) last. Type-only imports use a separate line (import type { X } from '...') and are ordered with their group. Enforced by ESLint rule perfectionist/sort-imports; run npm run lint:fix to auto-fix.
- .cursor/skills/ – Project skills (one SKILL.md per directory). Use when the task matches the skill’s “when to use” so the agent follows the right patterns.
- .cursor/rules/ – Glob-triggered or always-applied rules (e.g. eqeqeq, avoid-type-assertions, llm-history-tracking, path-casing). Rules apply automatically when editing matching files.
All configuration is project-scoped (no home-directory skills or rules).
| Task or area | Skill(s) to use |
|---|---|
| LLM / exports (tooling) | llm-exports-scripts when changing scripts/llm/** (export sync, adapters, vendor selector). llm-cursor-source for what to commit and .llm/exports automation policy. |
| History / plans | llm-history when updating history or starting feature work. plan-files-convention when creating, saving, or completing multi-step plans (e.g. COPY-PASTA execution). single-readme when adding index/overview docs (repo has only one README at root). Plans live in .llm/plans/active/; split if over 300 lines (see plan-creation rule). |
| API | api for Express API patterns. api-testing when adding or changing API routes, auth, or env-dependent behavior (integration tests). swagger-openapi when changing API surface. api-no-pii-credentials rule applies to responses. Run: npm run test:e2e:api (integration), npm run test:unit (packages only). |
| Web / management-web | web for Next.js app patterns. e2e-page-tests when layout or behavior changes in web/management-web. response-ending-make-verify (and end-with-targeted-make-report-verify rule) for E2E verification commands. Use make E2E targets only (e2e-run-with-make-only). Run: make e2e_test_web, make e2e_test_report. |
| Testing (general) | feature-implementation-testing — tests are required for features touching api/management-api/web/management-web. unit-tests-risk-first for prioritizing unit tests. unit-tests-confident-granularity for test depth. unit-tests-security-authz-template for auth/permission tests. Run: npm test (full), npm run test:unit (no DB), npm run test:e2e:api (DB needed), npm run test:reports (full with HTML). |
| i18n | i18n when adding/editing translation keys, locales, or generating translations. |
| Forms / UI | use-form-component, button-loading-async, password-strength-on-set-update. avoid-type-assertions, eqeqeq-strict-equality rules apply. |
| DB / ORM | database-schema-naming, typeorm-orderby-property-names, generate-data-sync when schema or entities change. nested-resource-prefix-naming for nested resources. |
| K8s / Argo CD | Remote cluster runbook: docs/development/k8s/REMOTE-K8S-GITOPS.md. argocd-gitops-push when changing files under infra/k8s/ or sync targets for k8s (add push-to-Git reminder in response so Argo CD can sync). |
| Env / templates | Keep env work anchored to canonical templates/examples (scripts/local-env/setup.sh, scripts/env-overrides/prepare-home-env-overrides.sh). For remote K8s, maintain env/manifests directly in your GitOps repository. |
| Imports / paths | path-casing-imports when adding or changing relative imports (rule also applies on .ts/.tsx). type-imports-separate-line rule for type-only imports. |
For file-modifying work: update .llm/history/active/[feature]/ per llm-history and the llm-history-tracking rule. For large or multi-step plans: save under .llm/plans/active/ and use plan-files-convention; execute via COPY-PASTA prompts one plan at a time.