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>
Connect Echodo to itself: Cursor sessions can now call this repo's MCP
server (`claim_task`, `complete_task`) over stdio, and operators can
bootstrap the DB from `plans/` without leaving a long-running file
watcher in place.
- `.cursor/mcp.json`: register `echodo` MCP server. Spawns
`pnpm -s --filter @tasks/mcp-server mcp`. The `mcp` script invokes tsx
with `--env-file=../../.env` so DATABASE_URL is picked up at the
per-session process boundary without leaking into committed config.
- `import:markdown-backlog`: new one-shot importer (sibling of the
existing watch script). Until the DB → markdown export side lands
(see follow-up task), running the watcher continuously would clobber
agent-driven status flips on every sweep. The one-shot variant runs a
single `syncMarkdownBacklogScan` pass and exits.
- File `Task-export-db-to-markdown-frontmatter.md` documenting the
remaining direction of the sync loop.
Smoke-tested end-to-end against the homelab DB: full `claim_task` →
`complete_task` round-trip via real MCP stdio protocol, with agent_runs
+ audit_log rows landing as expected and the backlog item status
restored via `finalStatus`.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Adds the data layer for per-item agent prompts. Markdown frontmatter
gets an `agent_prompt:` block scalar that survives the importer
round-trip (newlines preserved), and `resolveWorkflowPrompt()` walks
task → epic → plan → built-in default returning both the resolved
string and the source level. Walk is slug-based, not parent_id-based,
because the importer leaves parent_id briefly null mid-transaction.
tRPC `backlog.getWorkflowPrompt` returns ownOverride + effectivePrompt
so future UI can render the override box + preview without two
queries. `backlog.updateWorkflowPrompt` is owner/admin-gated (prompts
change downstream Cursor/Claude behavior) and audit-logged on every
write.
UI deferred — apps/web doesn't have a backlog-item detail panel yet;
the existing object-detail panel is for the objects table. Follow-up
filed at Task-workflow-prompt-task-detail-ui.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
Three new plan trees that fill in the gaps surfaced during repo review.
Together they map out what remains between the current scaffold-with-stubs
state and a daily-usable, multitenant, agent-coordinated app.
* Plan-daily-driver-finish (P0): turn stubs into real data. Five tasks
covering the lint/shared-types breakage, hardcoded dashboard mocks,
AI-page setTimeout placeholder, post-signin landing decision, and a
cross-browser collab smoke test against the deployed Hocuspocus
instance.
* Plan-multitenant-saas-hardening (P1): everything multitenant needs
beyond what Plan-multitenant-cursor-sync already covers. Invites and
role management, soft-delete + append-only audit log, rate limits on
the auth + mutation hot paths, and a Vitest + GitHub Actions test
foundation so PRs can't ship red.
* Plan-agent-coordination (P2): the layer that makes a Task-*.md
runnable, not just readable. Adds workflow_prompt with task -> epic
-> plan inheritance, an agent_runs table for auditable sessions, and
two new MCP tools (claim_task / complete_task) that replace the
freeform update_object composition agents do today. Includes an
intentionally-deferred Epic-optional-orchestrator that captures the
Symphony-shaped runner as a decision point rather than an immediate
build.
Each task is bead-scale (one focused Cursor session) with explicit
in-scope, out-of-scope, and anti-goal sections so a future agent can
pick up a single Task-*.md and start without scrollback context.
Co-authored-by: Cursor <cursoragent@cursor.com>