Added list management commands, board filtering by project name, and enhanced skill documentation with bootstrap workflow and error handling patterns. Also added plumbing in to "pcli" binary for status syncing with Planka
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
## Planka Sync
|
||||
|
||||
After completing any opsx workflow (/opsx:new, /opsx:ff, /opsx:continue, /opsx:apply, /opsx:verify, /opsx:archive, /opsx:bulk-archive), automatically reconcile Planka board state with OpenSpec by:
|
||||
|
||||
1. Reading the kanban-sync workflow documentation at `.windsurf/workflows/kanban-sync.md`
|
||||
2. Implementing the reconciliation steps programmatically using `pcli` commands
|
||||
3. Following the workflow logic to sync OpenSpec changes to Planka cards
|
||||
|
||||
This ensures the Planka dashboard stays current without modifying the opsx workflows themselves.
|
||||
|
||||
If the sync fails (pcli unavailable, network issues), log a brief warning and continue. Never block or retry - Planka sync is best-effort. Do NOT attempt to execute the markdown file as a bash script - it is documentation that must be implemented programmatically.
|
||||
@@ -103,6 +103,15 @@ pcli task update <task-id> [--name "..."] [--position N] [--completed]
|
||||
pcli task delete <task-id>
|
||||
```
|
||||
|
||||
### Lists
|
||||
|
||||
```bash
|
||||
pcli list create --board <board-id> --name "List Name" --position 65536 [--type active|closed]
|
||||
pcli list get <list-id>
|
||||
pcli list update <list-id> [--name "..."] [--position N] [--type active|closed] [--color "..."] [--board <board-id>]
|
||||
pcli list delete <list-id>
|
||||
```
|
||||
|
||||
### Labels
|
||||
|
||||
```bash
|
||||
@@ -111,6 +120,31 @@ pcli label update <label-id> [--name "..."] [--color "..."] [--position N]
|
||||
pcli label delete <label-id>
|
||||
```
|
||||
|
||||
## API Response Structure
|
||||
|
||||
### Board Get Response
|
||||
Board details include lists directly in `.data.lists[]`, not in an `included` section:
|
||||
```bash
|
||||
pcli board get <board-id> | jq '.data.lists[] | {id, name, position}'
|
||||
```
|
||||
|
||||
### Project Get Response
|
||||
Project details include boards in `.data.included.boards[]`:
|
||||
```bash
|
||||
pcli project get <project-id> | jq '.data.included.boards[] | {id, name}'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Project Configuration
|
||||
- Always strip quotes from yq output: `yq '.planka.project' project.yaml | tr -d '"'`
|
||||
- Exit with error if configured project cannot be found or created
|
||||
- The project name in project.yaml is the authoritative source
|
||||
|
||||
### Idempotent Operations
|
||||
- Use `2>/dev/null || true` for create operations that should not fail if resources already exist
|
||||
- Check for empty results before attempting operations: `if [ -z "$PROJECT_ID" ]; then`
|
||||
|
||||
## Extracting IDs from Output
|
||||
|
||||
All responses use `{"data": ..., "error": null}`. Extract IDs with jq:
|
||||
@@ -121,10 +155,50 @@ pcli card create --list <id> --name "X" | jq -r '.data.id'
|
||||
|
||||
# Array
|
||||
pcli card list --board <id> | jq -r '.data[].id'
|
||||
|
||||
# With error checking
|
||||
RESULT=$(pcli project list | jq -r --arg name "$PROJECT_NAME" '.data[] | select(.name == $name) | .id')
|
||||
if [ -z "$RESULT" ]; then
|
||||
echo "Not found"
|
||||
else
|
||||
echo "Found: $RESULT"
|
||||
fi
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Bootstrap Project Infrastructure
|
||||
|
||||
```bash
|
||||
# Read project configuration
|
||||
PROJECT_NAME=$(yq '.planka.project' project.yaml | tr -d '"')
|
||||
BOARD_NAME=$(yq '.planka.board' project.yaml | tr -d '"')
|
||||
|
||||
# Find or create project (exit with error if fails)
|
||||
PROJECT_ID=$(pcli project list | jq -r --arg name "$PROJECT_NAME" '.data[] | select(.name == $name) | .id')
|
||||
if [ -z "$PROJECT_ID" ]; then
|
||||
echo "Error: Project '$PROJECT_NAME' not found and creation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find or create board
|
||||
BOARD_ID=$(pcli project get $PROJECT_ID | jq -r --arg name "$BOARD_NAME" '.data.included.boards[] | select(.name == $name) | .id')
|
||||
if [ -z "$BOARD_ID" ]; then
|
||||
BOARD_ID=$(pcli board create --project $PROJECT_ID --name "$BOARD_NAME" | jq -r '.data.id')
|
||||
fi
|
||||
|
||||
# Create required lists (skip if exists)
|
||||
pcli list create --board $BOARD_ID --name "Backlog" --position 65536 2>/dev/null || true
|
||||
pcli list create --board $BOARD_ID --name "To Do" --position 131072 2>/dev/null || true
|
||||
pcli list create --board $BOARD_ID --name "Planning" --position 196608 2>/dev/null || true
|
||||
pcli list create --board $BOARD_ID --name "In Progress" --position 262144 2>/dev/null || true
|
||||
pcli list create --board $BOARD_ID --name "Review" --position 327680 2>/dev/null || true
|
||||
pcli list create --board $BOARD_ID --name "Done" --position 393216 2>/dev/null || true
|
||||
|
||||
# Create agent label
|
||||
pcli label create --board $BOARD_ID --name "agent" --color "berry-red" 2>/dev/null || true
|
||||
```
|
||||
|
||||
### Create a card with a checklist
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
name: "Kanban Sync"
|
||||
description: Reconcile Planka board state with OpenSpec changes. Planka is a read-only dashboard - OpenSpec is the source of truth.
|
||||
category: Workflow
|
||||
tags: [workflow, kanban, planka, tasks, project-management, sync]
|
||||
---
|
||||
|
||||
# Planka ↔ OpenSpec Reconciliation Sync
|
||||
|
||||
**OpenSpec is the source of truth for all agentic work.** Planka provides a read-only view so humans and other agents can see what's happening. This workflow reads OpenSpec state and reconciles Planka to match.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before syncing, verify connectivity:
|
||||
|
||||
```bash
|
||||
pcli status
|
||||
```
|
||||
|
||||
If this fails, skip the Planka sync silently. Planka sync is best-effort - never block agentic work because Planka is unavailable.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **OpenSpec owns the state** - `tasks.md`, artifacts, and change status live in OpenSpec
|
||||
2. **Planka is a projection** - it reflects OpenSpec state, never the other way around
|
||||
3. **Sync is one-directional** - OpenSpec → Planka, never Planka → OpenSpec
|
||||
4. **Sync is idempotent** - running it twice produces the same result
|
||||
5. **Sync is best-effort** - if Planka is down, work continues uninterrupted
|
||||
6. **Non-agentic work** is managed directly in Planka (no OpenSpec involvement)
|
||||
|
||||
---
|
||||
|
||||
## When This Runs
|
||||
|
||||
This workflow is triggered automatically after any opsx workflow completes (via project-level instruction in `.windsurf/rules/kanban-update.md`). It can also be invoked manually via `/kanban-tasks`.
|
||||
|
||||
---
|
||||
|
||||
## Board Structure
|
||||
|
||||
The board lists map to OpenSpec lifecycle stages:
|
||||
|
||||
| List | Position | Purpose |
|
||||
|------|----------|---------|
|
||||
| **Backlog** | 1 | Non-agentic work items (human-managed, read-write) |
|
||||
| **To Do** | 2 | Non-agentic work items ready to start (human-managed, read-write) |
|
||||
| **Planning** | 3 | OpenSpec changes with artifacts still being created (`opsx:new`, `opsx:continue`, `opsx:ff`) |
|
||||
| **In Progress** | 4 | Active implementation — tasks being worked (`opsx:apply`) |
|
||||
| **Review** | 5 | All tasks complete, awaiting verification (`opsx:verify`) |
|
||||
| **Done** | 6 | Completed and archived (`opsx:archive`) |
|
||||
|
||||
---
|
||||
|
||||
## Bootstrap: Ensure Project, Board, Lists, and Label Exist
|
||||
|
||||
Before reconciling, ensure all required Planka infrastructure exists. This makes the sync self-bootstrapping — running it on a fresh Planka instance will create everything needed.
|
||||
|
||||
### 1. Read project config
|
||||
|
||||
```bash
|
||||
PROJECT_NAME=$(yq '.planka.project' project.yaml)
|
||||
BOARD_NAME=$(yq '.planka.board' project.yaml)
|
||||
```
|
||||
|
||||
If `project.yaml` doesn't exist or has no `planka` section, ask the user for the project and board name, then offer to create the file.
|
||||
|
||||
### 2. Find or create the project
|
||||
|
||||
```bash
|
||||
PROJECT_ID=$(pcli project list | jq -r --arg name "$PROJECT_NAME" '.data[] | select(.name == $name) | .id')
|
||||
```
|
||||
|
||||
If no project found:
|
||||
```bash
|
||||
PROJECT_ID=$(pcli project create --name "$PROJECT_NAME" --type "public" | jq -r '.data.id')
|
||||
```
|
||||
|
||||
### 3. Find or create the board
|
||||
|
||||
```bash
|
||||
# Get project details to find boards
|
||||
BOARD_ID=$(pcli project get $PROJECT_ID | jq -r --arg name "$BOARD_NAME" '.data.included.boards[] | select(.name == $name) | .id')
|
||||
```
|
||||
|
||||
If no board found:
|
||||
```bash
|
||||
BOARD_ID=$(pcli board create --project $PROJECT_ID --name "$BOARD_NAME" | jq -r '.data.id')
|
||||
```
|
||||
|
||||
### 4. Find or create the lists
|
||||
|
||||
After obtaining the board, get its current lists:
|
||||
```bash
|
||||
EXISTING_LISTS=$(pcli board get $BOARD_ID | jq -r '.data.included.lists[]? | .name')
|
||||
```
|
||||
|
||||
Create any missing lists with explicit positions to maintain correct ordering:
|
||||
```bash
|
||||
# Only create lists that don't already exist
|
||||
pcli list create --board $BOARD_ID --name "Backlog" --position 65536
|
||||
pcli list create --board $BOARD_ID --name "To Do" --position 131072
|
||||
pcli list create --board $BOARD_ID --name "Planning" --position 196608
|
||||
pcli list create --board $BOARD_ID --name "In Progress" --position 262144
|
||||
pcli list create --board $BOARD_ID --name "Review" --position 327680
|
||||
pcli list create --board $BOARD_ID --name "Done" --position 393216
|
||||
```
|
||||
|
||||
Skip any list that already exists (match by name).
|
||||
|
||||
### 5. Find or create the `agent` label
|
||||
|
||||
```bash
|
||||
LABEL_ID=$(pcli board get $BOARD_ID | jq -r '.data.included.labels[]? | select(.name == "agent") | .id')
|
||||
```
|
||||
|
||||
If no `agent` label found:
|
||||
```bash
|
||||
LABEL_ID=$(pcli label create --board $BOARD_ID --name "agent" --color "berry-red" | jq -r '.data.id')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reconciliation Steps
|
||||
|
||||
### 1. Gather OpenSpec state
|
||||
|
||||
```bash
|
||||
openspec list --json
|
||||
```
|
||||
|
||||
This returns all active changes with their names, schemas, and status.
|
||||
|
||||
For each active change:
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
|
||||
Parse to get:
|
||||
- Change name
|
||||
- Schema name
|
||||
- Artifact completion status (how many artifacts complete vs total)
|
||||
- Whether all `applyRequires` artifacts are done
|
||||
- Whether tasks exist and their completion state
|
||||
|
||||
If a `tasks.md` exists, read it and parse checkbox state (`- [ ]` vs `- [x]`).
|
||||
|
||||
### 2. Determine target list for each change
|
||||
|
||||
Map each change to the correct board list based on its OpenSpec state:
|
||||
|
||||
| Condition | Target List |
|
||||
|-----------|-------------|
|
||||
| Artifacts incomplete (not all `applyRequires` done) | **Planning** |
|
||||
| Artifacts complete, tasks exist with incomplete items | **In Progress** |
|
||||
| All tasks complete (all `[x]`) | **Review** |
|
||||
| Change archived (not in active list) | **Done** |
|
||||
|
||||
### 3. Gather Planka state
|
||||
|
||||
```bash
|
||||
pcli card list --board $BOARD_ID | jq '.data[] | select(.labels[]?.name == "agent")'
|
||||
```
|
||||
|
||||
Build a map of existing agent-labelled cards by name, including which list they're currently in.
|
||||
|
||||
### 4. Reconcile: create missing cards
|
||||
|
||||
For each active OpenSpec change that has no matching Planka card:
|
||||
|
||||
```bash
|
||||
# Determine the correct list based on change state (see step 2)
|
||||
LIST_ID=$(pcli board get $BOARD_ID | jq -r --arg list "<target-list>" '.data.included.lists[] | select(.name == $list) | .id')
|
||||
CARD_ID=$(pcli card create --list $LIST_ID --name "<change-name>" --description "<schema: schema-name>" | jq -r '.data.id')
|
||||
|
||||
# Add agent label
|
||||
pcli card add-label $CARD_ID --label $LABEL_ID
|
||||
```
|
||||
|
||||
### 5. Reconcile: move cards to correct list
|
||||
|
||||
For each agent-labelled card that exists but is in the wrong list (based on current OpenSpec state):
|
||||
|
||||
```bash
|
||||
TARGET_LIST_ID=$(pcli board get $BOARD_ID | jq -r --arg list "<target-list>" '.data.included.lists[] | select(.name == $list) | .id')
|
||||
pcli card move $CARD_ID --list $TARGET_LIST_ID
|
||||
```
|
||||
|
||||
This ensures cards move through the board as work progresses:
|
||||
- `Planning` → `In Progress` when artifacts are complete and apply begins
|
||||
- `In Progress` → `Review` when all tasks are marked done
|
||||
- `Review` → `Done` when the change is archived
|
||||
|
||||
### 6. Reconcile: sync task lists
|
||||
|
||||
For each OpenSpec change that has a `tasks.md`:
|
||||
|
||||
- If the Planka card has no task list → create one and add all tasks
|
||||
- If the Planka card has a task list → compare task names and completion state, update as needed
|
||||
|
||||
```bash
|
||||
# Create task list if missing
|
||||
TL_ID=$(pcli task-list create --card $CARD_ID --name "Implementation" --show-on-front | jq -r '.data.id')
|
||||
|
||||
# For each task in tasks.md:
|
||||
pcli task create --task-list $TL_ID --name "<task description>"
|
||||
|
||||
# For tasks already in Planka, update completion state to match tasks.md:
|
||||
pcli task update <task-id> --completed # if tasks.md shows [x]
|
||||
```
|
||||
|
||||
### 7. Reconcile: move completed/archived changes
|
||||
|
||||
For each agent-labelled Planka card that has no matching active OpenSpec change:
|
||||
- The change was likely archived → move the card to "Done"
|
||||
|
||||
```bash
|
||||
DONE_LIST_ID=$(pcli board get $BOARD_ID | jq -r '.data.included.lists[] | select(.name == "Done") | .id')
|
||||
pcli card move $CARD_ID --list $DONE_LIST_ID
|
||||
```
|
||||
|
||||
### 8. Report
|
||||
|
||||
After reconciliation, briefly summarise what changed:
|
||||
- Infrastructure created (project/board/lists/label): yes/no
|
||||
- Cards created: N
|
||||
- Cards moved: N (list details)
|
||||
- Tasks synced: N
|
||||
- Cards moved to Done: N
|
||||
- Errors (if any): list them
|
||||
|
||||
---
|
||||
|
||||
## Non-Agentic Work
|
||||
|
||||
Cards **without** the `agent` label are human-managed and fully read-write. The kanban skill (`/kanban`) handles these directly - creating cards, moving them, adding checklists, etc.
|
||||
|
||||
The distinction:
|
||||
- **Has `agent` label** → read-only projection, managed by this sync workflow
|
||||
- **No `agent` label** → regular Planka card, managed directly by humans
|
||||
|
||||
---
|
||||
|
||||
## ID Discovery
|
||||
|
||||
Planka IDs cannot be cached across sessions. Each sync run must discover IDs dynamically:
|
||||
|
||||
1. Read project and board name from `project.yaml`
|
||||
2. Find the project by name: `pcli project list | jq ...`
|
||||
3. Find the board by name within the project
|
||||
4. Find lists on the board: `pcli board get <board-id> | jq ...`
|
||||
5. Find agent cards: `pcli card list --board <board-id> | jq '.data[] | select(.labels[]?.name == "agent")'`
|
||||
6. Match cards to changes by name
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Never modify an `agent`-labelled card outside of this sync workflow**
|
||||
- **Never read Planka to determine what work to do** - query OpenSpec instead
|
||||
- **Always discover IDs dynamically** - never hardcode or cache across sessions
|
||||
- **Sync failures are silent** - log a warning but never block opsx workflows
|
||||
- **One board per project** - if multiple boards exist, ask the user which to sync to
|
||||
- **Idempotent** - safe to run multiple times, will not create duplicates
|
||||
- **Bootstrap is safe** - creating project/board/lists is idempotent; existing resources are reused
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
name: "Kanban"
|
||||
description: "Manage Planka project boards using the pcli CLI"
|
||||
category: Workflow
|
||||
tags: [workflow, kanban, planka, project-management]
|
||||
---
|
||||
|
||||
Manage Planka project boards using the `pcli` CLI. Use the kanban skill for detailed command reference.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running any commands, verify the environment is ready:
|
||||
|
||||
```bash
|
||||
// turbo
|
||||
pcli status
|
||||
```
|
||||
|
||||
If this fails, ensure `PLANKA_URL` and `PLANKA_API_KEY` environment variables are set and `pcli` is in PATH.
|
||||
|
||||
---
|
||||
|
||||
## How to Use
|
||||
|
||||
**Input**: The argument after `/kanban` is what the user wants to do. Could be:
|
||||
- A status check: "show me all boards" or "what's on my board"
|
||||
- A card operation: "create a card for fixing the login bug"
|
||||
- A board query: "list all cards in the backlog"
|
||||
- A move: "move card X to done"
|
||||
- A bulk operation: "move all cards from In Progress to Done"
|
||||
- Nothing (show overall status)
|
||||
|
||||
---
|
||||
|
||||
## Responding to Requests
|
||||
|
||||
### 1. Understand the request
|
||||
|
||||
Map the user's intent to `pcli` commands. Use `pcli status` for overview requests. For specific operations, identify the resource (project, board, card, list, label, task, comment) and action (list, get, create, update, delete, move).
|
||||
|
||||
### 2. Discover IDs when needed
|
||||
|
||||
Most commands require IDs. Discover them by querying first:
|
||||
|
||||
```bash
|
||||
# Find boards
|
||||
pcli board list | jq '.data[] | {id, title}'
|
||||
|
||||
# Find lists on a board
|
||||
pcli board get <board-id> | jq '.data.included.lists[] | {id, title}'
|
||||
|
||||
# Find cards on a list or board
|
||||
pcli card list --board <board-id> | jq '.data[] | {id, name}'
|
||||
pcli card list --list <list-id> | jq '.data[] | {id, name}'
|
||||
```
|
||||
|
||||
Always use `jq` to extract and format output for readability.
|
||||
|
||||
### 3. Execute the operation
|
||||
|
||||
Run the appropriate `pcli` command. For create/update/delete operations, confirm with the user before executing unless the intent is unambiguous.
|
||||
|
||||
### 4. Report results
|
||||
|
||||
Show the user a concise summary of what happened. Use tables or formatted output when listing multiple items.
|
||||
|
||||
---
|
||||
|
||||
## Command Quick Reference
|
||||
|
||||
| Resource | Commands |
|
||||
|----------|----------|
|
||||
| **Status** | `pcli status` |
|
||||
| **Projects** | `list`, `get` |
|
||||
| **Boards** | `list`, `get`, `actions` |
|
||||
| **Cards** | `list`, `get`, `create`, `update`, `delete`, `duplicate`, `move`, `assign`, `unassign`, `add-label`, `remove-label`, `actions` |
|
||||
| **Comments** | `list`, `create`, `update`, `delete` |
|
||||
| **Task Lists** | `create`, `get`, `update`, `delete` |
|
||||
| **Tasks** | `create`, `update`, `delete` |
|
||||
| **Labels** | `create`, `update`, `delete` |
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Create a card with a checklist
|
||||
|
||||
```bash
|
||||
CARD_ID=$(pcli card create --list <list-id> --name "Task" | jq -r '.data.id')
|
||||
TL_ID=$(pcli task-list create --card $CARD_ID --name "Steps" | jq -r '.data.id')
|
||||
pcli task create --task-list $TL_ID --name "Step 1"
|
||||
pcli task create --task-list $TL_ID --name "Step 2"
|
||||
```
|
||||
|
||||
### Move all cards between lists
|
||||
|
||||
```bash
|
||||
pcli card list --list <source-list-id> | jq -r '.data[].id' | while read id; do
|
||||
pcli card move $id --list <target-list-id>
|
||||
done
|
||||
```
|
||||
|
||||
### Extract IDs from output
|
||||
|
||||
```bash
|
||||
# Single object
|
||||
pcli card create --list <id> --name "X" | jq -r '.data.id'
|
||||
|
||||
# Array
|
||||
pcli card list --board <id> | jq -r '.data[].id'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Discover before acting** - Always query for IDs rather than guessing
|
||||
- **Confirm destructive actions** - Ask before delete or bulk move operations
|
||||
- **Use jq for output** - Parse JSON responses with `jq` for clean, readable results
|
||||
- **Show context** - When listing cards, include the list/board name for context
|
||||
- **Global flags** - All commands accept `--format json|table` for output format
|
||||
Reference in New Issue
Block a user