The Create Workspace button in the top-header dropdown was still calling
`window.alert("Create workspace (placeholder)")` instead of opening the
real CreateWorkspaceDialog. Wires it to the same dialog the sidebar
workspace switcher uses.
Also clarifies in .env.example and AGENTS.md that the canonical Postgres
database name is `echodo` everywhere (local, Coolify, drizzle ledger,
docker-compose POSTGRES_DB). A stale `tasks` database on CT 102 from the
pre-rename era was the root cause of the 2026-06 Authentik "Configuration"
SSO outage: Coolify correctly pointed at `echodo` (which was empty), while
the only initialized schema lived in the relic `tasks` DB.
Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
10 KiB
Markdown
161 lines
10 KiB
Markdown
# AGENTS.md
|
|
|
|
Operating manual for AI coding agents (Cursor, Codex, Copilot, etc.) working in this repo. Humans should read it too. Keep it short and accurate — if you change conventions in code, update this file in the same change.
|
|
|
|
> Product: **Echodo** — a multitenant task app whose backlog is mirrored as markdown under `plans/` and synced to Cursor.
|
|
> Repo name on disk is `tasks` and the package scope is `@tasks/*` (historical, pre-rename). Everything else — the database, the deploy, the public domain — is `echodo`. If you see a `tasks` database anywhere, it's an artifact of the old name; the source of truth is `echodo`.
|
|
|
|
---
|
|
|
|
## 1. Repo layout
|
|
|
|
```
|
|
apps/
|
|
web/ Next.js 15 (App Router, React 19) — tRPC, NextAuth, Drizzle, TipTap, Tailwind
|
|
collab-server/ Hocuspocus (Yjs) realtime server, Redis-backed
|
|
mcp-server/ Model Context Protocol server exposing tasks/objects to agents
|
|
packages/
|
|
database/ Drizzle ORM schema, client, migrations, markdown-backlog importer
|
|
shared/ Shared TS types and utils (zod schemas live here)
|
|
ai/ AI SDK wrappers, prompts, provider config (OpenAI / Anthropic / Ollama)
|
|
config/
|
|
CursorSync.md Contract for app ↔ Cursor sync (read before touching sync code)
|
|
docs/
|
|
Glossary.md Vocabulary for plans/epics/tasks/tenants/sync
|
|
templates/ Plan / Epic / Task markdown templates
|
|
plans/ The markdown backlog itself (see §6)
|
|
docker/ Local + Coolify compose files, Dockerfile, prod next.config
|
|
```
|
|
|
|
Workspace tooling: **pnpm 9** + **Turborepo** + **TypeScript 5.7**. Node `>=20`.
|
|
|
|
---
|
|
|
|
## 2. Commands
|
|
|
|
Run from the repo root unless noted. Always use `pnpm`, never `npm` or `yarn`.
|
|
|
|
| Task | Command |
|
|
|------|---------|
|
|
| Install | `pnpm install` |
|
|
| Dev (all apps) | `pnpm dev` |
|
|
| Dev one app | `pnpm --filter @tasks/web dev` (or `@tasks/collab-server`, `@tasks/mcp-server`) |
|
|
| Build | `pnpm build` |
|
|
| Lint | `pnpm lint` |
|
|
| Type-check | `pnpm type-check` |
|
|
| Format | `pnpm format` (Prettier) |
|
|
| Drizzle: generate migration | `pnpm db:generate` |
|
|
| Drizzle: apply migrations | `pnpm db:migrate` |
|
|
| Drizzle: push (dev only) | `pnpm db:push` |
|
|
| Drizzle Studio | `pnpm --filter @tasks/database db:studio` |
|
|
| Import `plans/` into DB | `pnpm --filter @tasks/database watch:markdown-backlog` (needs `MARKDOWN_BACKLOG_WORKSPACE_ID`) |
|
|
| Test | `pnpm test` (turbo runs vitest in every package that has a `test` script) |
|
|
| Test one package | `pnpm --filter @tasks/<name> test` |
|
|
| Test in watch mode | `pnpm --filter @tasks/<name> test:watch` |
|
|
|
|
Before opening a PR or finishing a task, run **`pnpm lint && pnpm type-check && pnpm test`** at minimum. Run `pnpm build` if you touched build config, server entry points, or cross-package exports.
|
|
|
|
If you edit anything under `apps/mcp-server/` or any library it imports, your changes won't be visible to MCP tool calls in the **current** Cursor chat — the MCP server is a long-lived stdio process per session and doesn't auto-reload. See the **MCP server lifecycle** section in [config/CursorSync.md](./config/CursorSync.md) for how to find and kill stale servers.
|
|
|
|
Tests live next to source as `*.test.ts` files. Vitest is configured per package (see `vitest.config.ts` in `packages/shared`, `packages/database`, `packages/ai`). When you add a new package that has logic worth verifying, copy one of those configs and add `test` / `test:watch` scripts to the package's `package.json`. CI gates merges on lint + type-check + test — see [.github/workflows/ci.yml](./.github/workflows/ci.yml).
|
|
|
|
---
|
|
|
|
## 3. Environment & secrets
|
|
|
|
- Local dev reads `.env` at the repo root. `.env.example` documents every variable; keep it in sync when you add a new one.
|
|
- `.env`, `*.secrets`, `*-credentials.*`, `id_rsa`, `*.key`, and `AGENT-DEPLOY.md` are gitignored. **Never** read `AGENT-DEPLOY.md` contents into a committed file, log, or PR description — it contains homelab IPs and credentials.
|
|
- Production deploys run on Coolify (CT 107) against shared Postgres/Redis on CT 102. Don't change deploy assumptions (Docker compose files, `next.config.docker.ts`) without flagging it explicitly.
|
|
- **The Postgres database name is `echodo`**, full stop — local dev, Coolify, the drizzle migration ledger, and the docker-compose `POSTGRES_DB` all need to agree. An older `tasks` database may still exist on CT 102 as a relic of the pre-rename era; treat it as a dev sandbox at best and never point Coolify at it. The 2026-06 "Authentik SSO returns `Configuration`" incident was caused by Coolify pointing at `echodo` (correct) while a stale `tasks` database had the only initialized schema.
|
|
- `NEXT_PUBLIC_*` is the only browser-exposed prefix. Never put secrets behind it.
|
|
|
|
---
|
|
|
|
## 4. Code conventions
|
|
|
|
### TypeScript
|
|
- `strict: true` is on. Don't add `// @ts-ignore` or `any` to silence errors — fix the type. If you truly need to escape, use `// @ts-expect-error <reason>`.
|
|
- ESM only (`"type": "module"` in packages). Use named exports.
|
|
- Validate every external input (HTTP body, env var, markdown frontmatter, MCP tool args) with **zod**. Shared schemas live in `packages/shared`.
|
|
- Prefer `import type { … }` for type-only imports.
|
|
|
|
### Web app (`apps/web`)
|
|
- Next.js **App Router**. Server Components by default; add `"use client"` only when you need state, refs, or browser APIs.
|
|
- Data flow is **tRPC** (`apps/web/server/`) called via `@trpc/react-query` in client components and direct procedure calls in server components / actions.
|
|
- Auth is **NextAuth v5** (`apps/web/lib/auth.ts`). Always check session in protected procedures and route handlers.
|
|
- UI: **Tailwind + Radix primitives + `cva` + `clsx` + `tailwind-merge`**. Reuse components from `components/ui/`; don't reinvent buttons, dialogs, dropdowns.
|
|
- Editor stack is **TipTap 3 + Yjs + Hocuspocus**. Document state is collaborative — don't mutate doc JSON directly when a Y.Doc is available.
|
|
- Routing groups: `(app)` is authenticated app, `(auth)` is sign-in/up. Workspace pages live under `app/(app)/[workspaceSlug]/`.
|
|
|
|
### Database (`packages/database`)
|
|
- Schema lives in `src/schema/*.ts` and is re-exported from `src/schema/index.ts`. Drizzle config points there.
|
|
- **Multitenancy is enforced by `workspace_id`** on every tenant-scoped table. Any new table that holds user data must include `workspace_id` (UUID, not null) and an index on it. Every query must filter by `workspace_id`.
|
|
- Generate migrations with `pnpm db:generate` after schema changes. Commit the generated SQL in `packages/database/migrations/`. Don't hand-edit existing migrations — write a new one.
|
|
- `db:push` is for local prototyping only. Production uses `db:migrate`.
|
|
|
|
### Collab server (`apps/collab-server`)
|
|
- Persistence is via Hocuspocus database extension into Postgres; presence/awareness via Redis. Keep auth checks in `src/index.ts` aligned with the web app's session model.
|
|
|
|
### MCP server (`apps/mcp-server`)
|
|
- Tools live in `src/tools/`, resources in `src/resources/`. Every tool must have a zod schema for arguments and must respect tenant scoping (`workspace_id`).
|
|
|
|
### Shared (`packages/shared`)
|
|
- Cross-app types and zod schemas only. No React, no Node-specific APIs, no DB imports.
|
|
|
|
### AI (`packages/ai`)
|
|
- Use the **Vercel AI SDK** (`ai`, `@ai-sdk/openai`, `@ai-sdk/anthropic`). Provider selection is driven by env (`OPENAI_BASE_URL` can route through local Ollama on CT 108).
|
|
- Keep prompts in `src/prompts/`; no inline prompt strings scattered in components.
|
|
|
|
### Style
|
|
- Prettier formats everything. Don't fight it.
|
|
- Comments explain **why**, not **what**. Don't add narration comments like `// import the module` or `// loop over items`. Don't leave breadcrumbs describing the change you just made.
|
|
- Prefer small, named functions over deeply nested callbacks.
|
|
- File names: kebab-case for routes/components, camelCase for hooks/utilities matching the symbol they export.
|
|
|
|
---
|
|
|
|
## 5. Workflow
|
|
|
|
1. **Read before writing.** Skim `docs/Glossary.md` and (when relevant) `config/CursorSync.md` and the touched package's `src/index.ts` before editing.
|
|
2. Make a plan if the task touches >2 files or crosses package boundaries. Update or add a `Task-*.md` under the right `plans/Plan-*/Epic-*/` if the work is non-trivial product change.
|
|
3. Implement, then run `pnpm lint && pnpm type-check`. Fix every error you introduced.
|
|
4. Don't commit unless the user asks. When you do commit, write a short, focused message — *why*, not a diff narration.
|
|
5. Don't push, force-push, or amend without explicit instruction.
|
|
|
|
---
|
|
|
|
## 6. The markdown backlog (`plans/`)
|
|
|
|
This tree is **the source of truth for product work**. The importer in `packages/database/src/markdown-backlog/` parses every file under `plans/` into `markdown_backlog_items`, and `cursor_sync_mappings` stores the Cursor-side ids.
|
|
|
|
Layout is strict (the parser depends on it):
|
|
|
|
```
|
|
plans/
|
|
Plan-<slug>/
|
|
Plan-<slug>.md
|
|
Epic-<slug>/
|
|
Epic-<slug>.md
|
|
Task-<slug>.md
|
|
```
|
|
|
|
Rules:
|
|
- Slugs are lowercase-hyphenated and stable. Renaming the display title is fine; renaming a slug breaks sync — only do it deliberately.
|
|
- Every Plan/Epic/Task file must start with the YAML frontmatter from `docs/templates/`. Required fields: `kind`, `slug`, `title`, `status`, `priority`, `tenant_id`, `updated_at`. Plan/Epic/Task add their own (`plan_slug`, `epic_slug`, `cursor_*_id`).
|
|
- `status` ∈ `draft | ready | in_progress | blocked | done | cancelled`. `priority` ∈ `P0 | P1 | P2 | P3`.
|
|
- One Task per file. Keep tasks **bead-scale**: a single Cursor agent session can complete or materially advance the work.
|
|
- Don't reach across tenants — `tenant_id` scopes every record and every Cursor mapping.
|
|
|
|
When in doubt, copy from `docs/templates/` and look at `plans/Plan-multitenant-cursor-sync/` for a worked example.
|
|
|
|
---
|
|
|
|
## 7. Things to avoid
|
|
|
|
- Adding a new package manager, monorepo tool, or framework without a discussion.
|
|
- Bypassing tRPC by writing raw `fetch` calls to internal API routes.
|
|
- Querying the DB without a `workspace_id` filter on tenant-scoped tables.
|
|
- Inlining secrets, IPs, or hostnames from `AGENT-DEPLOY.md` into source or docs.
|
|
- Editing existing migrations in `packages/database/migrations/`.
|
|
- Adding files outside the directories listed in §1 without a reason.
|
|
- Generating large binary blobs, hashes, or lockfile diffs by hand.
|