kanban-operations
Skill215 starsChanged 2 months agoMITView on GitHub
Move kanban cards and query kanban state via scripts — requires a running Switchboard host (LocalApiServer).
In this repository19 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- improve-featureSkill
- improve-planSkill
- external-team-leadSkill
- improve-featureSkill
- manage-featuresSkill
- query-kanbanSkill
- switchboard-orchestrationSkill
- worktree-cleanupSkill
- kanban-operationsSkill
- manage-featuresSkill
- query-kanbanSkill
- switchboard-cloudSkill
- switchboard-memoSkill
- switchboard-remoteSkill
- switchboardSkill
- worktree-cleanupSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 4 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 16
Every op below is keyed on a planId (a UUID), but you should not need UUIDs for most ops.
- Very specific: line 66
"Planned" →
PLAN REVIEWED: No column is stored as PLANNED. - Distinctive: line 79
Features: When the card is a feature, all of its subtasks cascade to the same column automatically.
- Distinctive: line 152
⚠ Cross-column warning: If the plan being assigned is in a different kanban column than the feature (e.g.
- Very specific: line 154
To fix: after assignment, select the feature card on the kanban board and press the Replan button (the re-plan icon in the PLAN REVIEWED …
- Distinctive: line 155
Only once all subtasks are in PLAN REVIEWED should the feature be dragged to a coder column.
- Distinctive: line 234
When running from a different directory than the target workspace:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
⚠️ MANUAL FALLBACK ONLY — The
move-card.jsscript is an override/recovery mechanism. Do NOT run it automatically during standard workflow routing. Use it ONLY when the user has explicitly requested a card move. Theget-state.jsscript is read-only and may be used freely.
Kanban Operations
Move cards and query kanban state by running the provided scripts.
Resolving Plan IDs (do this FIRST — offline, no script)
Every op below is keyed on a planId (a UUID), but you should not need UUIDs for most ops. Resolve a plan the cheap way when needed, or use the path/slug-addressed APIs below so the server resolves it:
- Per-column index (fastest, offline):
.switchboard/kanban-state-<column>.md. Every plan line ends with<!-- planId:<uuid> … -->; subtasks also carrysubtask-of:"<feature>"and feature cards carryfeature. Onegrepgives you the ID and its feature membership:
Columns:grep -i "my-plan-slug-or-title" .switchboard/kanban-state-*.md # → …plans/my-plan.md](…) — My Plan Title <!-- planId:eb75281d-… subtask-of:"Some Feature" -->created,backlog,plan-reviewed,lead-coded,coder-coded,intern-coded,code-reviewed,acceptance-tested,coded,completed, plus custom columns. - Whole board via CLI (clean JSON):
switchboard api GET /kanban/board→{ success, data: [{ planId, planFile, kanbanColumn, isFeature, featureId, … }] }.
The real fix is to not need IDs at all: the path/slug-addressed feature API (
POST /kanban/features/reconcile, Feature A · A3 — landed) lets you reference plans by file path or slug and reconcile the whole feature structure in one idempotent call. Use it (see "Reorganize Features" below) instead of the per-verb UUID choreography. The two lookups above remain useful for one-off card moves.
Move a Card
node .agents/skills/kanban_operations/move-card.js <session_or_plan_file> <target_column>Examples:
node .agents/skills/kanban_operations/move-card.js sess_1777206335666 CODER_CODED
node .agents/skills/kanban_operations/move-card.js .switchboard/plans/my-plan.md CODER_CODED
node .agents/skills/kanban_operations/move-card.js my-plan.md CODER_CODED<session_or_plan_file>can be a legacysession_id, or a plan file path (relative or absolute), or a plan basename. The script resolves it to the DBplanId.
Valid columns: Sourced from VALID_KANBAN_COLUMNS export in KanbanDatabase.ts. Includes all built-in columns (CREATED, BACKLOG, PLAN REVIEWED, STAGING, INTERN CODED, LEAD CODED, CODER CODED, CODE REVIEWED, CODED, COMPLETED) plus any custom agent columns matching the safe-name regex.
⚠️ The user names the BOARD LABEL — translate it before you move anything
Those are storage ids, not what the board shows and not what the user will say. Translate the label to the id and proceed. Never tell the user their column "doesn't exist" or recite the storage ids back at them.
| Board label (what the user says) | Column id (what move-card.js takes) |
| New | CREATED |
| Backlog | BACKLOG |
| Planned | PLAN REVIEWED |
| Staging | STAGING |
| Lead Coder | LEAD CODED |
| Coder | CODER CODED |
| Intern | INTERN CODED |
| Reviewed | CODE REVIEWED |
| Completed | COMPLETED |
This is a correctness hazard on a write path, not a naming nicety. Two labels resolve to the opposite of the obvious guess:
- "Planned" →
PLAN REVIEWED. No column is stored asPLANNED. - "Reviewed" →
CODE REVIEWED. Reading "Reviewed" asPLAN REVIEWEDmoves the card backwards past every coding column — silently, because both ids are valid and the move succeeds.
Confirm the resolved id in your reply when you move a card ("Moved to Planned (PLAN REVIEWED)")
so a mis-resolution is visible immediately.
Custom columns carry user-chosen labels this table cannot cover. The live mapping is
GET /kanban/columns (the full catalogue tagged with enabled and enabledSource for built-in and custom alike; filter destinations to enabled !== false, though a disabled column may still hold historical cards). Code source of truth:
DEFAULT_KANBAN_COLUMNS / DISPLAY_MODE_COLUMNS in src/services/agentConfig.ts — that file wins
over this table.
Features: When the card is a feature, all of its subtasks cascade to the same column automatically.
How it routes (and why it matters for Linear/ClickUp sync):
The move is routed through the running Switchboard host's local API server (POST /kanban/move). The host performs the move, cascades subtasks, and pushes the feature + every subtask status to Linear/ClickUp — keeping external trackers in exact sync. A running host is required; there is no direct-DB write fallback.
Dispatching Cards & Features
move-card.js / POST /kanban/move only modify card placement in the database — no terminal boots, no prompt is delivered. The primitive that advances a card AND boots a terminal AND delivers the role prompt is POST /kanban/dispatch. It is the canonical one-call advance-and-dispatch in both hosted and standalone modes (POST /kanban/move is unavailable — 503 — on the standalone host).
switchboard api POST /kanban/dispatch '{
"plan": "<planId | plan-file path>",
"targetColumn": "<optional — omitted|\"auto\" routes by complexity>",
"workspaceRoot": "<optional — defaults to primary root>",
"from": "<optional — caller'\''s own terminal name for team routing>"
}'plan— the field name may beplan,planId,sessionIdorplanFile(_handleKanbanDispatch, LocalApiServer.ts:1870 readsbody?.plan || body?.planId || body?.sessionId || body?.planFile), but the value is resolved bygetPlanByPlanIdthen by plan-file path only — asessionIdthat differs from itsplan_id404s, so pass the planId. It does not acceptfeatureId— dispatching a feature as a whole is not supported; dispatch each subtask.targetColumnomitted or"auto"→ routed by plan complexity through the board's own rule (default bands 1–4 → INTERN CODED, 5–6 → CODER CODED, 7+/unknown → LEAD CODED; custom routing maps + pair-mode bypass honored; decision returned inrouting). Supply an explicit column only when you want to override.from— your own terminal name. Supply it and a role dispatch (e.g. CODE REVIEWED → reviewer) is routed to the member of your team rather than the first matching terminal on the board. The response echoesteamRoutingnaming the decision, including when it fell back. Extension host only: the standalone host does not wireresolveKanbanDispatch, sogate.roleis absent there and team routing short-circuits —teamRoutingreadsdispatch role unavailable on this host — fell back to workspace-wideandfromhas no effect.- Response:
{ success, planId, sessionId, topic, role, mode, column, moved, dispatched, dispatchedAgent, dispatchedAt, error?, routing?, teamRouting? }.successmeans "the card is in the target column AND a dispatch was observed" — never just "the request parsed". A502withmoved: true, dispatched: falsemeans the move persisted but no terminal agent picked up — investigate, do not retry blindly.
Physical board dispatch vs. database-only move:
POST /kanban/dispatch— persists the move, fires the column's role prompt, verifies against DB. The execution primitive.move-card.js/POST /kanban/move— modifies placement only, no terminal boot, no prompt delivery. Recovery/manual override.
Edge cases:
- A dispatch to a column with no configured role returns a 400 error (not a silent no-op). Do not retry blindly — the column has no role seat configured. This pre-flight is extension-host only: the standalone host leaves
resolveKanbanDispatchunwired, the check is skipped, and a role-less column falls through to the move without the loud 400. - With no terminal agent registered the call returns
409before moving anything — the dispatch would have fallen back to the clipboard and nothing would run. Open your agent terminal(s) so they re-register, then retry. POST /kanban/dispatchdoes not gate on in-flight status — it moves and dispatches unconditionally. If you dispatch a subtask a seat already holds (nocompleted_atand noreleased_at), the move persists and a new dispatch is attempted, overwriting the prior seat's context. To avoid a double-dispatch, checkGET /kanban/plan?planId=<id>→dispatchedAt,completedAt, andreleasedAtfirst: ifdispatchedAtis non-null and bothcompletedAtandreleasedAtare null, the card is in-flight — either postPOST /kanban/task/complete(with a non-emptyoutcome) when the work is done, orPOST /kanban/card/releaseto free the team without finishing, before re-dispatching. (The in-flight 409 refusal is aPOST /kanban/queue/nextbehavior, not a dispatch behavior — see section 4's queue/next row.)- See
.agents/skills/switchboard-orchestration/SKILL.mdsection 4 for the full HTTP surface authority, including thefromteam-routing contract and therouting/teamRoutingresponse fields.
Create a Feature
node .agents/skills/kanban_operations/create-feature.js <feature_name> <plan_ids_json> [workspace_root] [description]Example:
node .agents/skills/kanban_operations/create-feature.js "Onboarding revamp" '["a1b2-...","c3d4-..."]' /Users/me/repoplan_ids_jsonis a JSON array ofplanIdvalues (theplanIdfield fromget-state.jsoutput) — NOTsessionId. File-watcher-imported plans have an emptysession_id, soplanIdis the only reliable key.- Output (stdout):
{"ok":true,"featurePlanId":"...","featureSessionId":"..."}on success, or{"ok":false,"error":"..."}on failure. Exit code 0/1 matches. - The feature inherits its project/column from the subtasks and appears on the board immediately.
- External sync: feature creation and assignment sync the feature as a parent issue (Linear) or parent task (ClickUp) and link subtasks as children, IF real-time sync is enabled for that tracker. Subtasks without an existing external issue/task are skipped — they will be linked on a future feature-sync trigger once their individual sync creates an external issue. Sync is best-effort: failures are logged but do not roll back the local feature creation.
- Requires the running extension — there is no direct-DB fallback (unlike
move-card.js). Feature creation spans project inheritance, column resolution, a file write, and subtask linking; replicating that in raw DB calls would risk an orphaned feature. If the extension isn't reachable the script fails with a clear message.
Assign Plans to a Feature
node .agents/skills/kanban_operations/assign-to-feature.js <feature> <plan_or_plan_ids_json> [workspace_root]Examples:
# Add a single plan by path (no UUID)
node .agents/skills/kanban_operations/assign-to-feature.js "Agent skills improvements" .switchboard/plans/my-plan.md /Users/me/repo
# Add a single plan by slug
node .agents/skills/kanban_operations/assign-to-feature.js "Agent skills improvements" my-plan-slug
# Add a batch by UUIDs (legacy shape still works)
node .agents/skills/kanban_operations/assign-to-feature.js <featurePlanId-from-create> '["e5f6-..."]' /Users/me/repo<feature>can be a featureplanId, a feature file path, or a feature name/slug.<plan_or_plan_ids_json>is either a single plan ref (file path, slug, orplanId) or a JSON array of plan refs.- Output:
{"ok":true,"assigned":["..."],"skipped":["..."]}. A plan already on another feature (or that is itself a feature / missing) is reported inskippedand left untouched — it does not abort the batch. - Same constraints as
create-feature.js: requires the running extension (no direct-DB fallback). Feature assignment syncs the newly assigned subtasks as children of the feature's external issue/task IF real-time sync is enabled. - ⚠ Cross-column warning: If the plan being assigned is in a different kanban column than the feature (e.g. the plan is in CREATED but the feature is in PLAN REVIEWED), the agent MUST warn the user:
- The plan will NOT go through PLAN REVIEW if the feature is dragged to a coder column — it will skip straight to coding.
- To fix: after assignment, select the feature card on the kanban board and press the Replan button (the re-plan icon in the PLAN REVIEWED column header). This sends the CREATED subtasks to the planner for
improve-planrefinement. - Only once all subtasks are in PLAN REVIEWED should the feature be dragged to a coder column.
- The agent should also add a ⚠ Cross-Column Review Note section to the feature file (see
group-into-features/SKILL.mdfor the template).
Remove a Subtask from a Feature
node .agents/skills/kanban_operations/remove-from-feature.js <subtask_plan_id> [workspace_root]Example:
node .agents/skills/kanban_operations/remove-from-feature.js "e5f6-..." /Users/me/reposubtask_plan_idis theplanIdof the subtask to detach from its parent feature.- Output:
{"ok":true}on success, or{"ok":false,"error":"..."}on failure. - Detaches the subtask, abandons its per-subtask worktree, regenerates the feature file, refreshes the board, and unlinks the subtask from external trackers (best-effort).
- Requires the running extension — no direct-DB fallback.
Delete a Feature
node .agents/skills/kanban_operations/delete-feature.js <feature_plan_id> [delete_subtasks] [workspace_root]Example:
node .agents/skills/kanban_operations/delete-feature.js "a1b2-..." true /Users/me/repofeature_plan_idis thefeaturePlanIdof the feature to delete.delete_subtasks:trueto tombstone all subtasks,false(default) to detach them and leave them on the board.- Output:
{"ok":true}on success, or{"ok":false,"error":"..."}on failure. - Abandons all child worktrees, either tombstones or detaches subtasks, tombstones the feature, refreshes the board, and unlinks subtasks from external trackers (best-effort).
- Requires the running extension — no direct-DB fallback.
Split a Feature
node .agents/skills/kanban_operations/split-feature.js <feature_plan_id> <kept_plan_ids_json> <first_feature_name> <second_feature_name> [workspace_root]Example:
node .agents/skills/kanban_operations/split-feature.js "a1b2-..." '["c3d4-...","e5f6-..."]' "Backend refactor" "Frontend polish" /Users/me/repofeature_plan_idis thefeaturePlanIdof the feature to split.kept_plan_ids_jsonis a JSON array ofplanIdvalues that go to the first new feature. All other subtasks go to the second new feature.first_feature_nameandsecond_feature_nameare the names for the two new features.- Output:
{"ok":true,"firstFeaturePlanId":"...","secondFeaturePlanId":"..."}on success, or{"ok":false,"error":"..."}on failure. - The original feature is deleted (subtasks detached, not tombstoned). Two new features are created with their respective subtask sets.
- Requires the running extension — no direct-DB fallback.
Suggest Features Workflow (scan → propose → confirm → execute)
Triggered by the SUGGEST FEATURES board button, which copies a prompt to the clipboard. The agent must follow this flow:
- Scan — read the board with
get-state.jsand look only at pre-coding columns: CREATED, BACKLOG, PLAN REVIEWED. Ignore cards that are already features or already assigned (they carry anfeatureId). - Propose — in a SINGLE chat message, propose every grouping at once, listing each member plan with its
planIdand current kanban column. Leave standalone plans ungrouped. Flag any cross-column groupings (plans from different columns in the same feature) with a ⚠ CROSS-COLUMN warning — seegroup-into-features/SKILL.mdfor the warning text and replan-button guidance. Then stop. - Confirm — wait for the user's one approval (or edits). Create nothing before approval.
- Execute — run
create-feature.jsonce per approved group, no further confirmation. Useassign-to-feature.jsto add more plans later. For any cross-column feature, write the ⚠ Cross-Column Review Note into the feature file (seegroup-into-features/SKILL.mdfor the template).
Get Kanban State
node .agents/skills/kanban_operations/get-state.js <workspace_root>Example:
node .agents/skills/kanban_operations/get-state.js /Users/me/repo
node .agents/skills/kanban_operations/get-state.js /Users/me/repo | jq '.columns["CREATED"] | length'Outputs parseable JSON on stdout with columns as keys and arrays of plans as values. Diagnostic logs are routed to stderr, so piping to jq works.
Usage with Explicit Workspace
When running from a different directory than the target workspace:
# Get state from specific workspace
node .agents/skills/kanban_operations/get-state.js /Users/patrickvuleta/Documents/Gitlab
# Move card in specific workspace
node .agents/skills/kanban_operations/move-card.js <session_id> <column> "" /Users/patrickvuleta/Documents/GitlabReorganize Features (declarative — preferred over the per-verb scripts)
reconcile-features.js converges the whole feature structure to a desired end state in one idempotent call. Plans are addressed by file path / slug / topic / planId — never a raw UUID the agent must discover. New plans can be defined inline ({slug,title,body}) and reconcile writes + imports + links them. Re-running the same input is a no-op, so retry is safe.
node .agents/skills/kanban_operations/reconcile-features.js <workspace_root> '<reconcile_json>'reconcile_json:
{
"removeUnmentionedFeatures": false,
"features": [
{
"name": "My Feature",
"description": "optional feature description",
"subtasks": [
".switchboard/plans/my-plan.md",
"my-plan-slug",
"eb75281d-...",
{ "slug": "new-plan", "title": "New Plan", "body": "## Goal\n..." }
]
}
]
}- Create a feature: list it with its subtasks — the feature is created if no active feature has that name.
- Add subtasks to an existing feature: include the existing feature name + the full desired subtask set — new entries are assigned, the rest are left in place.
- Self-linking via file frontmatter: a plan file can carry
**Feature:** <feature-plan-id>or**Feature:** <feature-name>. The watcher links it to the feature on import (apply-if-empty — it never overwrites an existing link). - Remove a subtask from a feature: omit it from the desired subtask set — it's detached (not tombstoned).
- Delete unmentioned features: set
"removeUnmentionedFeatures": true— every active feature NOT named in the input is deleted (subtasks detached, not tombstoned). Defaultfalse(safe — never deletes by accident). - Inline new plan: a subtask entry of the form
{slug,title,body}writes a new plan file, imports it, and links it in one step. - Cross-column warning: assigning a
CREATEDplan to a feature in a later column produces awarnings[]entry (not a failure).
Output: { ok, features: [{name, featurePlanId, subtasks:[{planId,planFile,topic}]}], mutations: [{action,detail}], warnings: [] }.
The equivalent HTTP endpoint (for non-shell hosts) is POST /kanban/features/reconcile (or switchboard api POST /kanban/features/reconcile).
Single-add endpoint:
POST /kanban/features/assignwith{ feature, plan }(or{ feature, plans }for a batch) is the additive, path/slug-addressed primitive. It resolves both operands server-side and never detaches existing subtasks — use it for "add one plan" instead of the converge-to-setreconcileor UUID-onlyassignToFeature.
get-state.js | jqnow works: all diagnostic logging is routed to stderr, sonode get-state.js <root> | jq .emits parseable JSON on stdout.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

