ubiquitous-invention/AGENTS.md
Randall Stillwell 011d3eb710 fix(workspaces): wire top-header create-workspace button + clarify echodo db naming
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>
2026-06-05 16:33:41 -05:00

10 KiB

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


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).
  • statusdraft | ready | in_progress | blocked | done | cancelled. priorityP0 | 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.