Submatter/.agents/skills/backlog-sync/SKILL.md

298 lines
10 KiB
Markdown
Raw Permalink Normal View History

---
name: backlog-sync
description: Work with the plans/ markdown backlog - understand the strict layout (Plan/Epic/Task hierarchy), parse frontmatter, sync to markdown_backlog_items, and maintain the DB ↔ .md contract. Use when creating or modifying backlog items, understanding the import process, or working with the markdown-backlog package.
---
# Backlog Sync
The `plans/` directory is the **source of truth** for product work. It's a strict markdown hierarchy parsed into the `markdown_backlog_items` database table. This skill teaches you the layout rules, frontmatter contract, and how the markdown ↔ DB sync works.
## When to Activate
Use this skill when:
- Creating or editing files under `plans/`
- Working with `markdown_backlog_items` or `cursor_sync_mappings` tables
- Running or debugging the markdown import (`pnpm --filter @tasks/database watch:markdown-backlog`)
- Understanding how `agent_prompt` inheritance works (plan → epic → task)
- Troubleshooting backlog sync issues
- Implementing features that read/write backlog data
## Core Rules
### 1. Directory Structure is Strict
The parser depends on this **exact** layout. Do not improvise.
```
plans/
Plan-<slug>/
Plan-<slug>.md ← Plan overview
Epic-<slug>/
Epic-<slug>.md ← Epic definition
Task-<slug>.md ← Executable work item
Task-<another>.md
```
**Key constraints:**
- Slugs are lowercase-hyphenated (`feature-x`, not `Feature_X` or `featureX`)
- Slugs are stable — renaming breaks Cursor sync mappings
- One task per file
- File names must match the pattern: `Plan-<slug>.md`, `Epic-<slug>.md`, `Task-<slug>.md`
### 2. Frontmatter Contract
Every Plan/Epic/Task markdown file must start with YAML frontmatter. The parser extracts these fields:
**Required across all kinds:**
```yaml
---
kind: plan | epic | task
slug: matches-the-filename-slug
title: Human-readable title
status: draft | ready | in_progress | blocked | done | cancelled
priority: P0 | P1 | P2 | P3
tenant_id: UUID or "global"
updated_at: "YYYY-MM-DD"
---
```
**Plan-specific additions:**
```yaml
cursor_plan_id: null # populated by Cursor sync
```
**Epic-specific additions:**
```yaml
plan_slug: parent-plan-slug
cursor_epic_id: null
```
**Task-specific additions:**
```yaml
plan_slug: parent-plan-slug
epic_slug: parent-epic-slug
owner: unassigned | github-username
cursor_todo_id: null
agent_prompt: |
Optional multi-line prompt for agents working on this task.
Inherits from epic → plan if not set here.
```
**What the parser does:**
- Extracts frontmatter via regex: `/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/`
- Parses YAML (tolerates syntax errors → empty object)
- Infers `kind` from filename if frontmatter omits it (`Task-*.md` → `task`)
- Infers `slug` from filename if frontmatter omits it (strips `Plan-`/`Epic-`/`Task-` prefix)
- Infers `plan_slug` from path if frontmatter omits it (`plans/Plan-foo/...` → `foo`)
- Infers `epic_slug` from path for tasks if frontmatter omits it (`Epic-bar/Task-x.md` → `bar`)
- Computes `contentHash` (SHA-256 of full file) for idempotency
- Stores `workflowPrompt` from `agent_prompt:` frontmatter field
### 3. Agent Prompt Inheritance
Tasks can define `agent_prompt:` in frontmatter. If missing, the system inherits:
```
Task (no agent_prompt) → Epic (check agent_prompt) → Plan (check agent_prompt) → Default
```
The `resolveWorkflowPrompt` function in `packages/database/src/markdown-backlog/resolve-prompt.ts` implements this cascade. When an agent claims a task via MCP `claim_task`, the resolved prompt is returned as context.
**Default prompt** (when nothing is set):
```
You are an AI assistant helping implement a software task.
Follow the acceptance criteria and constraints in the task description.
Run pnpm lint && pnpm type-check after code changes.
```
### 4. Database Sync Flow
**Import (Markdown → DB):**
1. Run `pnpm --filter @tasks/database watch:markdown-backlog`
2. Scanner walks `plans/` for `*.md` files matching the pattern
3. Parser extracts frontmatter + body + contentHash
4. Syncer upserts rows to `markdown_backlog_items` by `(workspace_id, repo_path)` unique key
5. Hierarchical `parent_id` links are computed (task → epic → plan)
**Key columns in `markdown_backlog_items`:**
| Column | Source |
|--------|--------|
| `workspace_id` | Env var `MARKDOWN_BACKLOG_WORKSPACE_ID` |
| `repo_path` | Relative posix path: `plans/Plan-foo/Epic-bar/Task-baz.md` |
| `kind` | Frontmatter `kind:` or inferred from filename |
| `slug` | Frontmatter `slug:` or inferred from filename |
| `title` | Frontmatter `title:` or first `# Heading` or filename |
| `body` | Markdown body after frontmatter |
| `content_hash` | SHA-256 of full file |
| `parent_id` | Computed by matching `plan_slug`/`epic_slug` to parent rows |
| `workflow_prompt` | Frontmatter `agent_prompt:` (null if not set) |
**Export (DB → Markdown):**
Not yet fully automated. The `exportBacklogItemToMarkdown` function can generate markdown from a DB row, but writes are manual or triggered via tRPC `backlog.createPlan/createEpic/createTask`.
### 5. Don't Break the Contract
**What NOT to do:**
- Don't nest tasks under other tasks (only Plan → Epic → Task)
- Don't put secrets or API keys in frontmatter (files are committed)
- Don't hand-edit `cursor_plan_id`, `cursor_epic_id`, `cursor_todo_id` (Cursor owns these)
- Don't rename slugs without understanding the Cursor sync impact (breaks mappings)
- Don't mix `snake_case` and `camelCase` in frontmatter keys (parser expects `plan_slug`, `epic_slug`, `agent_prompt`)
- Don't create files outside the `plans/Plan-*/` structure (parser ignores them)
**What TO do:**
- Match existing style (see `docs/templates/plan-template.md`, `epic-template.md`, `task-template.md`)
- Keep tasks **bead-scale** (one agent session can complete or materially advance)
- Cross-link with relative paths: tasks link to epic via `./Epic-<slug>.md`, to plan via `../Plan-<slug>.md`
- Use `tenant_id: global` for repo-wide work that's not tenant-specific
- Run `pnpm --filter @tasks/database watch:markdown-backlog` after adding/editing files to sync
## Common Patterns
### Creating a New Task
1. Navigate to the Epic folder: `plans/Plan-<plan>/Epic-<epic>/`
2. Create `Task-<slug>.md` (lowercase-hyphenated slug)
3. Copy frontmatter template from `docs/templates/task-template.md`
4. Fill required fields: `kind: task`, `slug`, `title`, `status: draft`, `priority`, `plan_slug`, `epic_slug`, `tenant_id`, `updated_at`
5. Write task body (## Description, ## Acceptance criteria, etc.)
6. Run `pnpm --filter @tasks/database watch:markdown-backlog` to import
7. Verify row exists: `SELECT * FROM markdown_backlog_items WHERE slug = '<slug>'`
### Adding an Agent Prompt to a Task
If the task needs custom context for agents:
```yaml
---
kind: task
slug: implement-feature-x
# ... other required fields ...
agent_prompt: |
This task extends the existing authentication system.
Key constraints:
- Use NextAuth v5 (not v4)
- Follow patterns in apps/web/lib/auth.ts
- Add tests for new session logic
---
```
When an agent calls MCP `claim_task` with this task's `backlogItemId`, the prompt is returned and injected into the session context.
### Checking Sync Status
```bash
# Watch mode (auto-imports on file change)
pnpm --filter @tasks/database watch:markdown-backlog
# One-shot import
MARKDOWN_BACKLOG_WORKSPACE_ID=<uuid> node packages/database/dist/markdown-backlog-cli.js
```
**What it logs:**
- Created: new rows inserted
- Updated: contentHash changed
- Skipped: contentHash matches (idempotent)
- Errors: parse failures, missing parent refs
### Understanding the Hierarchy via SQL
```sql
-- Get full tree for a plan
WITH RECURSIVE tree AS (
SELECT id, kind, slug, title, parent_id, 0 AS depth
FROM markdown_backlog_items
WHERE kind = 'plan' AND slug = 'automation-foundation'
UNION ALL
SELECT c.id, c.kind, c.slug, c.title, c.parent_id, p.depth + 1
FROM markdown_backlog_items c
JOIN tree p ON c.parent_id = p.id
)
SELECT kind, slug, title, depth FROM tree ORDER BY depth, slug;
```
## Examples
### Example 1: Valid Task Frontmatter
```yaml
---
kind: task
slug: db-convoy-events-migration
title: Drizzle convoy_events table and migration
plan_slug: automation-foundation
epic_slug: convoy-lifecycle-mcp
status: done
priority: P1
tenant_id: global
owner: unassigned
cursor_todo_id: null
updated_at: "2026-08-12"
---
# Task summary
Add Drizzle schema + migration for convoy_events...
```
### Example 2: Task with Agent Prompt
```yaml
---
kind: task
slug: trpc-backlog-tree
title: tRPC backlog.list / tree / get for markdown_backlog_items
plan_slug: automation-foundation
epic_slug: plans-roadmap-ui
status: ready
priority: P1
tenant_id: global
owner: unassigned
cursor_todo_id: null
updated_at: "2026-08-12"
agent_prompt: |
Extend apps/web backlog router: list/tree/get for markdown_backlog_items.
Always filter by workspace_id. Zod inputs. No Plans UI yet — API only.
Reuse existing schema. claim_task / complete_task; lint + type-check + test.
---
```
### Example 3: Debugging Import Issues
**Problem:** Task not appearing in DB after markdown edit.
**Diagnosis:**
1. Check file structure: `ls plans/Plan-<plan>/Epic-<epic>/Task-<slug>.md`
2. Validate frontmatter YAML syntax (common issue: unbalanced quotes, missing colons)
3. Check watcher logs: `pnpm --filter @tasks/database watch:markdown-backlog` (look for parse errors)
4. Query DB: `SELECT * FROM markdown_backlog_items WHERE slug = '<slug>' AND workspace_id = '<uuid>'`
5. Check `content_hash`: if it matches, sync is idempotent (no change detected)
**Fix:** Correct frontmatter YAML, re-run import, verify row updated.
## References
- **Parser source:** `packages/database/src/markdown-backlog/parse.ts`
- **Sync logic:** `packages/database/src/markdown-backlog/sync.ts`
- **Prompt resolution:** `packages/database/src/markdown-backlog/resolve-prompt.ts`
- **Templates:** `docs/templates/plan-template.md`, `epic-template.md`, `task-template.md`
- **Glossary:** `docs/Glossary.md` (backlog hierarchy, sync vocabulary)
- **Cursor sync notes:** `config/CursorSync.md` (push/pull behavior, mappings)
- **Schema:** `packages/database/src/schema/markdown_backlog_items.ts`
## Related Skills
- `convoy-lifecycle` — when backlog tasks map to convoy briefs
- `model-routing` — how agent_prompt + model_policy interact
- `code-review-two-axis` — reviewing changes to backlog sync code