# AGENTS.md — Instructions for AI coding agents

This file tells AI coding agents (Claude, Cursor, Copilot, Devin, Codex, Aider) how to work in the HeyCMO repo. It complements `.cursorrules` and is referenced from `llms.txt`.

## Project shape

- **Monorepo.** Mix of TypeScript apps + a small set of Node ES module scripts.
- **Marketing site:** `apps/web` (Vite + React 19 + Tailwind 4). Built into `apps/web/dist`.
- **API + MCP server:** `apps/api/infra/server.ts` (single Hono server, ~5700 lines, hosts both `/api/*` and `/mcp/sse`). Entry: `apps/api/infra/index.ts`.
- **Agent runtime:** `apps/api/agent/` (Mastra). 17 agents in `agents/`, 18 workflows in `workflows/`, 70+ tools in `tools/`. MCP-exposed tool surface in `agent.config.ts` + `mcp-server.ts`.
- **Docs site:** `apps/docs/` (Next.js + Fumadocs static export, deployed to docs.heycmo.ai).
- **SDKs / packages:** `apps/sdk/` (embeddable editor SDK), `packages/managed-agent/`, `packages/cli/` (CLI).
- **Schema:** `prisma/`. Run `prisma generate` after pulling.

## Common tasks

| Task | Command |
|------|---------|
| Start API server (watch mode) | `npm run dev` |
| Build TypeScript | `npm run build` |
| Vitest unit tests | `npm test` |
| Playwright e2e | `npm run test:e2e` |
| Lint | `npm run lint` |
| Build marketing site | `cd apps/web && npm install && npm run build` |
| Build docs site | `cd apps/docs && npm install && npm run build` |
| Generate MCP server-card.json | `node apps/web/scripts/build-well-known.mjs` |
| Generate llms-full.txt | `node apps/web/scripts/build-llms-full.mjs` |
| Prerender per-route HTML | `node apps/web/scripts/prerender.mjs` |
| Generate sitemap.xml | `node apps/web/scripts/build-sitemap.mjs` |
| Prisma migrate dev | `npm run db:migrate` |

## When making changes

1. **Read `voice.md`** before changing user-facing copy. The brand voice is intentional.
2. **Read `docs/00-heycmo-master.md`** for product overview if you're new to the codebase.
3. **Validate everything with zod.** All HTTP route bodies, all MCP tool inputs, all message payloads. Type-only validation is not enough.
4. **Add types to all new public functions.**
5. **For new HTTP routes:** add to `apps/api/infra/server.ts`, validate the body with `zod` via `@hono/zod-validator`, add a Vitest test under `apps/api/infra/__tests__/`. If the route is public, add CORS in mind (the global `app.use('*', cors(...))` covers most cases).
6. **For new MCP tools:** register in `apps/api/agent/mcp-server.ts` AND add the name to `agentConfig.mcpTools` in `apps/api/agent/agent.config.ts` AND add a one-line description to the `TOOL_DESCRIPTIONS` map in `apps/web/scripts/build-well-known.mjs` (so the public MCP server-card.json reflects the new tool).
7. **For new SPA routes:** add to:
   - `apps/web/src/main.tsx` (router)
   - `spaRoutes` array in `apps/api/infra/server.ts`
   - `routeMeta` table in `apps/api/infra/server.ts`
   - `apps/web/scripts/prerender.mjs` (so build-time prerender picks it up for SEO)
   - `apps/web/public/sitemap.xml` AND `apps/web/scripts/build-sitemap.mjs`
8. **Test your changes end-to-end** — don't ship code without running it. The repo has no tolerance for "compiles, looks right, never executed".
9. **🔒 Never push to `main` without `tsc` clean.** A `.githooks/pre-push` hook runs `npx tsc --noEmit` (root) and `apps/web && tsc -b --noEmit` before any push to `main`. The hook is auto-installed by `npm install` (via `scripts/install-hooks.sh`). Run `npm run preflight` to invoke the same gate manually. **Why:** vitest passes on type-broken code (esbuild/ts-loader is more permissive than `tsc`). The v211 incident (2026-05-08) — 17 commits in a row that each broke `tsc --noEmit` and froze prod for 2 days — is exactly what this gate prevents. Never use `git push --no-verify` to land code with type errors. The override is for emergency revert paths only.

## Don't

- **Don't commit secrets.** Use `.env` (excluded by `.gitignore`). Reference: `.env.example`.
- **Don't bypass the approval queue logic** in workflows. Every customer-facing send goes through human review by default. Per-skill autonomy levels (L1–L5) gate which actions auto-execute vs queue for review — change those, never bypass them.
- **Don't add new top-level dependencies without `npm install`.** The `package-lock.json` is canonical. Manual edits invalidate the lockfile.
- **Don't use `pkill -f` or any name-based process kill.** Use specific PIDs.
- **Don't kill the dev server without good reason** — leave it running so the next invocation is fast.
- **Don't write multi-thousand-line "rewrite the architecture" PRs** without an explicit go-ahead. Prefer small, reviewable changes.

## Architecture notes that matter

- **Auth is per-customer Bearer API key** (`Authorization: Bearer hcmo_live_*`). Validation is timing-safe (`crypto.timingSafeEqual`). Customer status (`active`, `onboarding`, `suspended`, `cancelled`) is checked on every request. Suspended/cancelled accounts return 403.
- **MCP auth uses `?token=` query param** instead of Authorization header (limitation of MCP SSE transport in many clients).
- **Memory is per-customer.** Every Mastra Memory entry is tagged with `customerId`. Cross-tenant retrieval is not allowed. Don't add code that could leak this.
- **Rate limiting is enforced at the `rateLimit()` middleware level** with named scopes. New routes must pick a scope and pass through `rateLimit(...)`. 429 responses already include `Retry-After`.
- **Request IDs are added by the request-ID middleware.** Quote `X-Request-ID` in error logs and customer-facing error messages.

## See also

- `SKILL.md` — exposed MCP tool catalog (skills.sh format)
- `llms.txt` — agent-facing product description
- `openapi.json` — REST API specification (OpenAPI 3.1)
- `.cursorrules` — Cursor-specific shorthand
- `voice.md` — brand voice rules
- `CHANGELOG.md` — recent product changes
- `docs/runbooks/` — operational runbooks

## Repository facts

- Repo URL: <https://github.com/ravidsrk/heycmo>
- Default branch: `main`
- Node version: 22 (see `.nvmrc` if present, otherwise honour `package.json` engines)
- Package manager: `npm` (lockfile is the source of truth)
- TypeScript: strict mode
- Linter: ESLint via `eslint.config.js`
- Test runner: Vitest (`vitest.config.ts`) + Playwright (`tests-e2e/playwright.config.ts`)
- Deployment: Fly.io (`fly.toml` for API; `apps/docs/fly.toml` for docs)
