feat(sync): implement kanban-project-sync script with concurrency control and background execution
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-19
|
||||
@@ -0,0 +1,86 @@
|
||||
## Context
|
||||
|
||||
The kanban sync workflow is currently a 280-line markdown document (`.claude/commands/kanban-sync.md`) that the LLM reads and interprets each time sync is triggered. The sync logic is deterministic — it reads OpenSpec state, compares it with Planka board state, and reconciles. No LLM judgment is needed. The overhead is significant: each sync consumes tokens for reasoning through ~10 phases and executing ~15-30 shell commands.
|
||||
|
||||
There is also no concurrency protection. If two opsx workflows complete in quick succession, two syncs could run simultaneously and produce conflicting Planka API calls.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Replace LLM-interpreted sync with a standalone bash script
|
||||
- Implement concurrency control so only one sync runs at a time
|
||||
- Support coalescing: if sync is requested while one runs, queue exactly one follow-up
|
||||
- Support foreground (default, for humans) and background (for LLM/skill invocation) modes
|
||||
- Keep the existing sync logic and board structure unchanged
|
||||
|
||||
**Non-Goals:**
|
||||
- Changing the sync algorithm or board layout
|
||||
- Adding bidirectional sync (Planka → OpenSpec)
|
||||
- Supporting multiple simultaneous boards
|
||||
- Adding a daemon/service mode — this remains an on-demand script
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Externally deployed bash script (`kanban-project-sync`)
|
||||
|
||||
**Rationale**: The sync logic uses `pcli`, `jq`, `yq`, and `openspec` CLI tools — all shell-native. A bash script is the natural fit. The script is deployed to developer instances via existing tooling and placed on PATH, like other shared scripts. It does not live in the project repo.
|
||||
|
||||
**Convention**: The script assumes it is run from the project root directory (uses `pwd` to locate `openspec/` directory). This is consistent with how `openspec` and `pcli` already work.
|
||||
|
||||
**Alternative considered**: A Go subcommand in pcli (`pcli sync`). Rejected because the sync orchestrates `openspec` (a separate tool) and would require shelling out anyway. The script approach keeps concerns separated.
|
||||
|
||||
**Alternative considered**: Placing the script in the project root alongside `build.sh`. Rejected — the script is a shared developer tool, not project-specific. External deployment keeps it consistent with other tooling.
|
||||
|
||||
### 2. `flock` for locking with pending file for coalescing
|
||||
|
||||
**Rationale**: `flock` is the standard POSIX advisory locking mechanism. It's atomic, handles process crashes (lock auto-releases), and avoids manual PID-file management. The pending flag file is a simple touch/rm mechanism that coalesces multiple queued requests into one re-run.
|
||||
|
||||
**Lock file**: `/tmp/kanban-project-sync-<project>-<board>.lock` — scoped per project-board to allow independent syncs for different boards.
|
||||
|
||||
**Pending file**: `/tmp/kanban-project-sync-<project>-<board>.pending` — touched by callers that can't acquire the lock.
|
||||
|
||||
**Flow**:
|
||||
```
|
||||
Caller:
|
||||
Try flock (non-blocking)
|
||||
├── Got lock:
|
||||
│ loop:
|
||||
│ run_sync()
|
||||
│ if pending file exists: rm pending, continue loop
|
||||
│ else: break
|
||||
│ release lock (fd close)
|
||||
│
|
||||
└── Lock held:
|
||||
touch pending file
|
||||
exit 0
|
||||
```
|
||||
|
||||
**Alternative considered**: PID file with `kill -0` checks. Rejected — racy, doesn't handle crashes cleanly, more complex than `flock`.
|
||||
|
||||
### 3. Foreground default, `--background` flag for detached mode
|
||||
|
||||
**Rationale**: Humans running the script manually want to see output. The LLM/skill always passes `--background` to avoid blocking. Background mode uses `nohup` + `&` with output redirected to a log file at `/tmp/kanban-project-sync-<project>-<board>.log`.
|
||||
|
||||
### 4. Script reads `project.yaml` for defaults but CLI args override
|
||||
|
||||
**Rationale**: `--project` and `--board` are required arguments. The script does NOT read `project.yaml` itself — that's the caller's concern. This keeps the script generic. The kanban-sync command/skill reads `project.yaml` and passes the values. A human can pass whatever project/board they want.
|
||||
|
||||
### 5. Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success (sync completed) or queued (pending flag set) |
|
||||
| 1 | Error (sync failed) |
|
||||
| 2 | Skipped (Planka offline / connectivity check failed) |
|
||||
|
||||
When a caller can't acquire the lock and sets the pending flag, it exits 0 — from the caller's perspective, the sync will happen.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[`flock` availability on macOS]** → macOS doesn't ship `flock` by default. Mitigation: document that `brew install util-linux` is needed. This is a dev tooling script, not a production binary — acceptable friction.
|
||||
|
||||
**[Background mode log management]** → Log files in `/tmp` accumulate. Mitigation: Each run overwrites the log file (not appends), so only the last run's output is kept per project-board pair.
|
||||
|
||||
**[Pending flag race window]** → Tiny window between "check pending" and "release lock" where a new pending could be missed. Mitigation: The check-and-release happens while still holding the flock, so no other process can set pending between check and release. The lock holder is the only one that reads/clears the pending file.
|
||||
|
||||
**[Script drift from sync logic]** → If the sync algorithm changes, the script must be updated manually (no longer auto-follows the markdown doc). Mitigation: The sync logic has been stable. The kanban-sync.md doc will reference the script, making it clear where changes go.
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
The kanban sync is currently implemented as a detailed workflow document that the LLM interprets and executes command-by-command. Each sync burns significant tokens as the LLM reasons through ~10 phases and executes ~15-30 `pcli` commands. The sync logic is entirely deterministic — there are no decisions that require LLM judgment. Moving this to a standalone bash script eliminates the overhead and makes sync near-instant. Additionally, there is no concurrency protection — two syncs could run simultaneously and clash on Planka API state.
|
||||
|
||||
## What Changes
|
||||
|
||||
- New `kanban-project-sync` bash script (deployed externally, on PATH) that performs the full 10-phase Planka board reconciliation
|
||||
- Script accepts `--project` and `--board` as required inputs, with optional `--background` flag
|
||||
- Script assumes it is run from the project root (uses `pwd` to locate `openspec/` directory)
|
||||
- Implements `flock`-based concurrency control with a coalescing pending flag (depth-1 queue)
|
||||
- Default mode is foreground (human-friendly); `--background` detaches for fire-and-forget use
|
||||
- `.claude/commands/kanban-sync.md` simplified to just invoke the script with `--background`
|
||||
- `CLAUDE.md` sync instructions updated to reference the script
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `kanban-sync-concurrency`: Concurrency control for sync execution — flock-based locking with a pending flag that coalesces multiple requests, and automatic re-run after completion if a sync was requested during execution
|
||||
|
||||
### Modified Capabilities
|
||||
_(none — no existing spec-level requirements change)_
|
||||
|
||||
## Impact
|
||||
|
||||
- **New external script**: `kanban-project-sync` — deployed to developer instances via existing tooling, placed on PATH
|
||||
- **Modified**: `.claude/commands/kanban-sync.md` — reduced from full workflow to script invocation
|
||||
- **Modified**: `CLAUDE.md` — updated Planka Sync section
|
||||
- **Dependencies**: Requires `flock` (standard on Linux, available on macOS via `brew install util-linux`), `jq`, `yq`, `pcli`, `openspec` on PATH
|
||||
- **No Go code changes** — this is entirely in the tooling/workflow layer
|
||||
- **Convention**: Script must be run from the project root directory (same as `openspec` and `pcli`)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Exclusive sync execution
|
||||
The sync script SHALL use `flock` advisory locking to ensure only one sync instance runs at a time per project-board pair. The lock file SHALL be located at `/tmp/kanban-project-sync-<project>-<board>.lock`.
|
||||
|
||||
#### Scenario: First sync acquires lock
|
||||
- **WHEN** kanban-project-sync is invoked and no other sync is running for the same project-board
|
||||
- **THEN** the script acquires the flock and executes the full sync
|
||||
|
||||
#### Scenario: Second sync finds lock held
|
||||
- **WHEN** kanban-project-sync is invoked while another sync is running for the same project-board
|
||||
- **THEN** the script SHALL NOT execute the sync, SHALL touch the pending flag file, and SHALL exit with code 0
|
||||
|
||||
### Requirement: Coalescing pending queue
|
||||
The sync script SHALL implement a depth-1 coalescing queue using a pending flag file at `/tmp/kanban-project-sync-<project>-<board>.pending`. Multiple pending requests SHALL coalesce into a single re-run.
|
||||
|
||||
#### Scenario: Pending flag set during sync
|
||||
- **WHEN** a sync completes and the pending flag file exists
|
||||
- **THEN** the script SHALL clear the pending flag and re-run the sync before releasing the lock
|
||||
|
||||
#### Scenario: No pending flag after sync
|
||||
- **WHEN** a sync completes and no pending flag file exists
|
||||
- **THEN** the script SHALL release the lock and exit
|
||||
|
||||
#### Scenario: Multiple pending requests coalesce
|
||||
- **WHEN** three sync requests arrive while a sync is running
|
||||
- **THEN** only one additional sync SHALL run after the current one completes (not three)
|
||||
|
||||
### Requirement: Foreground and background modes
|
||||
The sync script SHALL default to foreground execution (output to stdout/stderr). When `--background` is passed, the script SHALL detach and redirect output to a log file at `/tmp/kanban-project-sync-<project>-<board>.log`.
|
||||
|
||||
#### Scenario: Foreground execution
|
||||
- **WHEN** kanban-project-sync is invoked without `--background`
|
||||
- **THEN** sync output SHALL be written to stdout/stderr and the script SHALL block until complete
|
||||
|
||||
#### Scenario: Background execution
|
||||
- **WHEN** kanban-project-sync is invoked with `--background`
|
||||
- **THEN** the script SHALL detach from the terminal, redirect output to the log file, and return immediately
|
||||
|
||||
### Requirement: Required arguments
|
||||
The sync script SHALL require `--project <name>` and `--board <name>` arguments. The script SHALL NOT read `project.yaml` directly.
|
||||
|
||||
#### Scenario: Missing arguments
|
||||
- **WHEN** kanban-project-sync is invoked without `--project` or `--board`
|
||||
- **THEN** the script SHALL print usage information and exit with code 1
|
||||
|
||||
### Requirement: Connectivity check
|
||||
The sync script SHALL run `pcli status` before syncing. If the check fails, the script SHALL exit with code 2.
|
||||
|
||||
#### Scenario: Planka offline
|
||||
- **WHEN** `pcli status` returns a non-zero exit code
|
||||
- **THEN** the script SHALL print a message indicating Planka is unreachable and exit with code 2
|
||||
|
||||
### Requirement: Idempotent sync phases
|
||||
The sync script SHALL implement all 10 phases of the existing kanban-sync workflow: connectivity check, bootstrap infrastructure (find-or-create project, board, lists, label), gather OpenSpec state, determine target lists, gather Planka agent cards, create missing cards, move cards to correct lists, sync task checklists, handle archived changes, and report. Each phase SHALL be idempotent — running the script twice with no state changes SHALL produce no Planka API mutations on the second run.
|
||||
|
||||
#### Scenario: Clean re-run with no changes
|
||||
- **WHEN** the sync script runs twice in succession with no OpenSpec or Planka state changes between runs
|
||||
- **THEN** the second run SHALL make no create/update/move API calls and SHALL report zero changes
|
||||
@@ -0,0 +1,32 @@
|
||||
## 1. Script Skeleton and Concurrency
|
||||
|
||||
- [x] 1.1 Create `kanban-project-sync` script with argument parsing (`--project`, `--board`, `--background`), usage help, and exit code constants
|
||||
- [x] 1.2 Implement `flock`-based locking with pending flag file and re-run loop
|
||||
- [x] 1.3 Implement `--background` mode (re-exec self detached with output to log file)
|
||||
- [x] 1.4 Add connectivity check (`pcli status`) with exit code 2 on failure
|
||||
|
||||
## 2. Bootstrap Phase
|
||||
|
||||
- [x] 2.1 Implement find-or-create project by name
|
||||
- [x] 2.2 Implement find-or-create board by name within project
|
||||
- [x] 2.3 Implement find-or-create lists (Backlog, To Do, Planning, In Progress, Review, Done) with correct positions
|
||||
- [x] 2.4 Implement find-or-create `agent` label on the board
|
||||
|
||||
## 3. Reconciliation Phases
|
||||
|
||||
- [x] 3.1 Gather OpenSpec state — list active changes, parse artifact completion and task checkbox state
|
||||
- [x] 3.2 Determine target list for each change based on OpenSpec state (Planning / In Progress / Review)
|
||||
- [x] 3.3 Gather existing Planka agent-labelled cards and build name→card map
|
||||
- [x] 3.4 Create missing cards (new OpenSpec changes without a Planka card) with agent label
|
||||
- [x] 3.5 Move cards to correct list when current list doesn't match target
|
||||
- [x] 3.6 Sync task checklists — create/update task lists and tasks on cards from `tasks.md`
|
||||
- [x] 3.7 Move orphaned agent cards (no matching active change) to Done
|
||||
|
||||
## 4. Reporting and Summary
|
||||
|
||||
- [x] 4.1 Add summary output — infrastructure created, cards created/moved, tasks synced, errors
|
||||
|
||||
## 5. Skill and Documentation Updates
|
||||
|
||||
- [x] 5.1 Simplify `.claude/commands/kanban-sync.md` to invoke `kanban-project-sync --background`
|
||||
- [x] 5.2 Update `CLAUDE.md` Planka Sync section to reference the script
|
||||
Reference in New Issue
Block a user