docs: add AGENTS.md and Cursor rules
Repo-wide agent guide at the root plus path-scoped Cursor rules for the web app, database/migrations, and the plans/ markdown backlog. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b992db5b3e
commit
456fdd4595
5 changed files with 320 additions and 0 deletions
40
.cursor/rules/database.mdc
Normal file
40
.cursor/rules/database.mdc
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
description: Drizzle schema, migrations, and multitenancy rules
|
||||
globs:
|
||||
- packages/database/**/*.ts
|
||||
- apps/**/server/**/*.ts
|
||||
- apps/collab-server/src/**/*.ts
|
||||
- apps/mcp-server/src/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Database — Drizzle + Postgres
|
||||
|
||||
Schema source of truth: [packages/database/src/schema](mdc:packages/database/src/schema), re-exported from [packages/database/src/schema/index.ts](mdc:packages/database/src/schema/index.ts). Drizzle config: [packages/database/drizzle.config.ts](mdc:packages/database/drizzle.config.ts).
|
||||
|
||||
## Multitenancy is non-negotiable
|
||||
|
||||
- Every tenant-scoped table has a `workspace_id` (UUID, not null) column with an index.
|
||||
- Every query against such tables **must** filter by `workspace_id`. The id comes from the authenticated session — never trust a client-provided value without checking it against the session's workspace memberships.
|
||||
- New tables holding user data: include `workspace_id`, add the index, add it to relations.
|
||||
|
||||
## Schema authoring
|
||||
|
||||
- Keep one table per file in `src/schema/`. Re-export from `src/schema/index.ts`.
|
||||
- Define relations in [packages/database/src/schema/relations.ts](mdc:packages/database/src/schema/relations.ts).
|
||||
- Use `uuid().defaultRandom()` for primary keys, `timestamp({ withTimezone: true })` for time columns, and `.notNull()` aggressively.
|
||||
|
||||
## Migrations
|
||||
|
||||
- After any schema change, run `pnpm db:generate` from the repo root and commit the new SQL file in [packages/database/migrations](mdc:packages/database/migrations).
|
||||
- **Never edit existing migrations.** Write a new one to fix or evolve.
|
||||
- `pnpm db:push` is for local prototyping only. Production goes through `pnpm db:migrate`.
|
||||
|
||||
## Client usage
|
||||
|
||||
- Server-only. Never import the client into a Client Component, the browser bundle, or `packages/shared`.
|
||||
- Import from `@tasks/database` (or `@tasks/database/schema`, `@tasks/database/client`, `@tasks/database/markdown-backlog`) — these are the published export paths.
|
||||
|
||||
## Markdown backlog tables
|
||||
|
||||
- `markdown_backlog_items` mirrors files under `plans/`, scoped by `workspace_id`. `cursor_sync_mappings` holds the Cursor-side ids. See [config/CursorSync.md](mdc:config/CursorSync.md) and [docs/Glossary.md](mdc:docs/Glossary.md) before changing either.
|
||||
47
.cursor/rules/markdown-backlog.mdc
Normal file
47
.cursor/rules/markdown-backlog.mdc
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
description: Authoring rules for the plans/ markdown backlog
|
||||
globs:
|
||||
- plans/**/*.md
|
||||
- docs/templates/*.md
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Markdown backlog (`plans/`)
|
||||
|
||||
This tree is the **source of truth for product work** and is parsed into `markdown_backlog_items` by [packages/database/src/markdown-backlog](mdc:packages/database/src/markdown-backlog). The parser depends on the layout — don't improvise.
|
||||
|
||||
## Layout (strict)
|
||||
|
||||
```
|
||||
plans/
|
||||
Plan-<slug>/
|
||||
Plan-<slug>.md
|
||||
Epic-<slug>/
|
||||
Epic-<slug>.md
|
||||
Task-<slug>.md
|
||||
```
|
||||
|
||||
- Slugs are lowercase-hyphenated and stable. Renaming a slug breaks Cursor sync — only do it deliberately.
|
||||
- One Task per file. Tasks should be **bead-scale**: a single Cursor agent session can complete or materially advance the work.
|
||||
- Use the templates in [docs/templates](mdc:docs/templates) ([plan](mdc:docs/templates/plan-template.md), [epic](mdc:docs/templates/epic-template.md), [task](mdc:docs/templates/task-template.md)).
|
||||
|
||||
## Frontmatter
|
||||
|
||||
Every Plan/Epic/Task file starts with YAML frontmatter. Required across all kinds:
|
||||
|
||||
- `kind` — `plan` | `epic` | `task`
|
||||
- `slug` — matches the filename slug
|
||||
- `title`
|
||||
- `status` — `draft` | `ready` | `in_progress` | `blocked` | `done` | `cancelled`
|
||||
- `priority` — `P0` | `P1` | `P2` | `P3`
|
||||
- `tenant_id` — UUID or placeholder; scopes the row
|
||||
- `updated_at` — ISO-8601
|
||||
|
||||
Plan adds `cursor_plan_id`. Epic adds `plan_slug`, `cursor_epic_id`. Task adds `plan_slug`, `epic_slug`, `owner`, `cursor_todo_id`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Don't reach across tenants. `tenant_id` scopes every record and every Cursor mapping.
|
||||
- Don't put secrets, IPs, or homelab credentials in plans — these files are committed and synced.
|
||||
- Cross-link with relative paths so links work both in the repo and in Cursor: tasks link to their epic and plan via `./Epic-<slug>.md` and `../Plan-<slug>.md`.
|
||||
- Reference: [docs/Glossary.md](mdc:docs/Glossary.md) and [config/CursorSync.md](mdc:config/CursorSync.md).
|
||||
35
.cursor/rules/repo-overview.mdc
Normal file
35
.cursor/rules/repo-overview.mdc
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
description: Repo-wide conventions for the Echodo monorepo
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Echodo monorepo (always-on)
|
||||
|
||||
The authoritative agent guide for this repo is [AGENTS.md](mdc:AGENTS.md). Read it before doing anything non-trivial. This rule only restates the load-bearing constraints.
|
||||
|
||||
## Stack at a glance
|
||||
|
||||
- pnpm 9 + Turborepo + TypeScript 5.7, Node >= 20. ESM only.
|
||||
- `apps/web` — Next.js 15 App Router, React 19, tRPC, NextAuth v5, Drizzle, TipTap + Yjs + Hocuspocus, Tailwind + Radix.
|
||||
- `apps/collab-server` — Hocuspocus (Yjs) over Postgres + Redis.
|
||||
- `apps/mcp-server` — Model Context Protocol server.
|
||||
- `packages/database` — Drizzle schema, client, migrations, markdown-backlog importer.
|
||||
- `packages/shared` — Cross-app types and zod schemas. No React, no Node-only APIs, no DB.
|
||||
- `packages/ai` — Vercel AI SDK wrappers and prompts.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- Use `pnpm` (never `npm`/`yarn`) and filter by workspace: `pnpm --filter @tasks/<name> <script>`.
|
||||
- Run `pnpm lint && pnpm type-check` before declaring a task done. There is no test runner yet — don't pretend there is.
|
||||
- Validate every external input (HTTP, env, frontmatter, MCP args) with **zod**. Cross-package schemas live in `packages/shared`.
|
||||
- `strict: true` is on. No `any`, no `// @ts-ignore`. Use `// @ts-expect-error <reason>` only when truly necessary.
|
||||
- Multitenancy is enforced by `workspace_id` on every tenant-scoped DB row. **Every** query against such tables must filter by `workspace_id`.
|
||||
- Never read `AGENT-DEPLOY.md`, `.env`, or any `*-credentials.*` content into committed files, logs, or PR descriptions. They contain homelab credentials and are gitignored.
|
||||
- Don't hand-edit existing migrations under [packages/database/migrations](mdc:packages/database/migrations). Generate a new one with `pnpm db:generate`.
|
||||
- `NEXT_PUBLIC_*` is the only browser-exposed env prefix. Never put secrets behind it.
|
||||
|
||||
## Style
|
||||
|
||||
- Comments explain *why*, not *what*. No narration comments (`// import x`, `// loop over items`) and no "I just changed this" breadcrumbs.
|
||||
- Prefer named exports and `import type { ... }` for type-only imports.
|
||||
- Don't commit, push, or amend without explicit user instruction.
|
||||
43
.cursor/rules/web-app.mdc
Normal file
43
.cursor/rules/web-app.mdc
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
description: Conventions for the Next.js web app
|
||||
globs:
|
||||
- apps/web/**/*.{ts,tsx}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# `apps/web` — Next.js 15 + React 19
|
||||
|
||||
## Routing
|
||||
|
||||
- App Router only. Pages live under [apps/web/app](mdc:apps/web/app).
|
||||
- `(app)` is the authenticated app; `(auth)` is sign-in/up. Workspace-scoped pages live at `app/(app)/[workspaceSlug]/...`.
|
||||
- Server Components are the default. Add `"use client"` only when you need state, refs, effects, or browser APIs.
|
||||
|
||||
## Data layer
|
||||
|
||||
- Use **tRPC** for all client ↔ server data flow. Routers live in [apps/web/server/routers](mdc:apps/web/server/routers); the root router is [apps/web/server/root.ts](mdc:apps/web/server/root.ts).
|
||||
- Call procedures via `@trpc/react-query` in client components and via direct procedure invocation in server components / actions. **Don't** `fetch` your own `/api/trpc` routes.
|
||||
- Auth is **NextAuth v5**, configured in [apps/web/lib/auth.ts](mdc:apps/web/lib/auth.ts). Every protected procedure must check the session and resolve a `workspace_id` before touching the DB.
|
||||
|
||||
## UI
|
||||
|
||||
- Tailwind + Radix primitives. Compose with `cva`, `clsx`, and `tailwind-merge`.
|
||||
- Reuse components from `apps/web/components/ui/`. Don't reinvent buttons, dialogs, dropdowns, popovers, etc.
|
||||
- Icons: `lucide-react` (preferred) or `@radix-ui/react-icons`.
|
||||
- Theming via `next-themes`.
|
||||
|
||||
## Editor / collab
|
||||
|
||||
- The document editor is **TipTap 3** with **Yjs** docs synced through a Hocuspocus provider ([apps/web/lib/yjs-provider.ts](mdc:apps/web/lib/yjs-provider.ts)).
|
||||
- When a `Y.Doc` is available, mutate through Yjs / TipTap commands. Don't replace doc state by overwriting JSON unless you know what you're breaking.
|
||||
|
||||
## State
|
||||
|
||||
- Server state → React Query (via tRPC).
|
||||
- Local UI state → component state or **Zustand** stores in `apps/web/lib/stores/`. Don't add Redux, Jotai, or another store library.
|
||||
|
||||
## Don't
|
||||
|
||||
- Don't import from `@tasks/database/client` in client components — DB access is server-only.
|
||||
- Don't add `pages/` directory routes. App Router only.
|
||||
- Don't bypass NextAuth by reading cookies directly.
|
||||
155
AGENTS.md
Normal file
155
AGENTS.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# 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`. Package scope is `@tasks/*`.
|
||||
|
||||
---
|
||||
|
||||
## 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`) |
|
||||
|
||||
Before opening a PR or finishing a task, run **`pnpm lint && pnpm type-check`** at minimum. Run `pnpm build` if you touched build config, server entry points, or cross-package exports.
|
||||
|
||||
There is currently **no test runner configured**. Don't fabricate one or add green-checkmark "tests" in a description. If you add tests, add a real runner (Vitest preferred) and wire it through `turbo.json`.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
- `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.
|
||||
Loading…
Reference in a new issue