Commit graph

6 commits

Author SHA1 Message Date
Randall Stillwell
56b697b81c feat(markdown-backlog): close the sync loop with DB → frontmatter export
Until now the markdown importer was one-way (plans/*.md → DB). Any
agent-driven status flip via claim_task / complete_task would be
clobbered on the next importer sweep. This change closes the loop: the
DB now projects status, priority, agent_prompt, and updated_at back
into the file's frontmatter, preserving body bytes, key order, and
every other frontmatter key.

New: packages/database/src/markdown-backlog/export.ts
  - `rewriteFrontmatter()` — pure function, covered by 10 Vitest cases
    (round-trip identity, status flip, priority flip, agent_prompt
    null/block-scalar/single-line variants, body preservation,
    trailing-newline preservation, idempotent re-application).
  - `exportBacklogItemToMarkdown()` — DB-loading wrapper with atomic
    write (tmp + rename) and tenant fencing. Returns a structured
    result so callers can surface what happened in their response.

Wired into:
  - `claim_task` MCP tool — exports on the ready → in_progress flip.
  - `complete_task` MCP tool — exports on any finalStatus transition.
  - `backlog.updateWorkflowPrompt` tRPC mutation — exports on prompt
    edits made through the app UI.

Robust repo-root resolution (`apps/{mcp-server,web}/src/lib/repo-root.ts`,
plus a copy in `import-markdown-backlog.ts`): walk up from the source
file looking for `pnpm-workspace.yaml`, falling back to env var or cwd.
This fixes a class of bug where `pnpm --filter <pkg>` cd's into the
package directory and breaks naive cwd-based path resolution — the
importer was deleting all 44 rows during smoke testing before this fix
because it found zero files in `packages/database/plans/`.

`config/CursorSync.md`: documents the new two-way contract, the
DB-wins-on-allow-list conflict policy, and the
MARKDOWN_BACKLOG_REPO_ROOT=off escape hatch for production deployments
where `plans/` isn't checked out.

Smoke verified end-to-end against the homelab DB: claim flips file
status to in_progress, complete flips it back to ready, importer
round-trips with stable content_hash (true no-op), agent identity
preserved throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 10:21:22 -05:00
Randall Stillwell
fc2235a346 feat(agent-runs): capture client + model identity on claim/complete
Adds level-B agent attribution: every MCP tool call can identify itself
with a `client` (e.g. cursor-ide, codex, echodo-orchestrator) and `model`
(e.g. claude-4.6-sonnet) string. Both optional, both stored in
agent_runs.metadata as JSONB so the schema doesn't move.

- claim_task: new optional `client` + `model` args. Written into the
  inserted agent_runs row's metadata, and mirrored into the audit log
  entry. On idempotent re-claim, incoming values are merged into
  existing metadata (existing keys override only when explicit), so a
  mid-session model switch updates attribution without losing earlier
  context.
- complete_task: same optional args, with merge-on-close semantics —
  late-bound values override the earlier claim's values so the run row
  reflects whichever model actually closed the session. Audit row also
  carries the merged identity.
- /settings/runs UI: stack the client/model under the actor name in the
  table so attribution is visible at a glance without adding a column.

Smoke-tested: claim with {cursor-ide, claude-4.6-sonnet} then complete
with only model={claude-4.6-sonnet-medium-thinking} yields final
metadata {client: cursor-ide, model: claude-4.6-sonnet-medium-thinking}
on both the run row and the task.completed audit entry.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 10:11:35 -05:00
Randall Stillwell
b2aff2045b feat(mcp): claim_task tool + register claim/complete pair
Pairs with the parallel complete_task commit. claim_task opens an
agent_runs row, flips status to in_progress only when it's safe
(ready/draft → in_progress, never overwriting a deliberate
blocked/done/in_progress), and returns the resolved workflow prompt
+ source level. Idempotent re-claim by the same actor returns the
existing run with reused=true and refreshes notes only — started_at
is sacred. Different-actor re-claim errors with ALREADY_CLAIMED
naming the existing actor and run id.

Tenancy fence: if the backlog item exists but in a different workspace
than the resolved handle, we refuse with "doesn't belong to workspace"
rather than 404. Prevents cross-tenant existence fishing.

All three writes (run insert + status flip + audit insert) happen in
one db.transaction() so a partial claim is unreachable.

tools/index.ts now registers both claim_task and complete_task.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 22:23:05 -05:00
Randall Stillwell
93b6398c76 feat(mcp): complete_task tool — close agent_runs row + finalize status
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 22:21:26 -05:00
Randall Stillwell
c582d621ce multi-tenancy: promote workspaces to top-level table
Block A of the EchoDo plan. Workspaces used to live as `objects(type='workspace')`,
which made it impossible to put a real RLS-friendly tenant boundary on the schema
or to give each workspace a stable URL slug. This commit:

- Adds a top-level `workspaces` table (slug unique, owner FK, plan_tier hook).
- Migrates the 8 anchor tables (objects, workspace_members, object_type_defs,
  property_definitions, templates, forms, markdown_backlog_items,
  cursor_sync_mappings) to FK into `workspaces.id` instead of `objects.id`,
  with a hand-augmented data-copy migration that preserves IDs and slug-collision-
  proofs on backfill.
- Introduces a `workspaceProcedure` tRPC middleware + `resolveWorkspace` helper
  that take a UUID-or-slug `workspace` handle and expose `ctx.workspace`. All
  tenant-scoped routers (objects, types, properties, templates, forms, search,
  ai, relations, favorites) now flow through it.
- Updates the web app to pass `workspace` slugs from the URL (or store) instead
  of the old `workspaceId`, including a workspace-sync layer that rewrites
  /<UUID>/... links to /<slug>/...
- Updates the MCP tools (list_objects, create_object, search_objects) and the
  workspace://{handle}/tree resource to accept either a slug or UUID so existing
  agents keep working.
- Adds a Create Workspace dialog and a Workspace Settings page (rename + slug
  rename with redirect, owner-only archive).

Verified locally against a fresh Postgres: migration applies cleanly, slug
uniqueness holds, tenant data is isolated by workspace_id, slug↔UUID resolution
works in both directions, and ON DELETE CASCADE cleans up child rows in the
correct workspace only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 23:02:55 -05:00
Randall Stillwell
a508ece6e7 feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:

- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)

Made-with: Cursor
2026-03-26 22:39:16 -05:00