Phase 1: Add 5 SKILL.md files to .agents/skills/ - backlog-sync: plans/ markdown ↔ DB contract - convoy-lifecycle: status machine, gates, MCP tools, checkpoints - model-routing: role→model via OmniRoute profiles + registry - code-review-two-axis: Standards vs Spec parallel sub-agent review - tdd-red-green: TDD loop with seams Phase 2: OmniRoute operational docs - docs/omniroute-role-mapping.md: profile→role matrix, convoy examples - docs/omniroute-setup.md: step-by-step operator guide Phase 3: Worker OpenHands integration - apps/worker/src/session/load-skills.ts: loads .agents/skills/ - apps/worker/src/session/openhands-session.ts: agent-server REST adapter - apps/worker/src/executor/index.ts: createOpenHandsExecutor() factory - apps/worker/src/env.ts: WORKER_SESSION_TYPE, AGENT_SERVER_URL, SESSION_API_KEY - apps/worker/src/handle-job.ts: routes to OpenHands when configured - apps/worker/src/session/minimal-session.ts: injects skills into system prompt - packages/shared/src/worker/index.ts: re-export WorkerExecutorOptions
297 lines
10 KiB
Markdown
297 lines
10 KiB
Markdown
---
|
|
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
|