kanban/SKILL.md
Manage project tasks in a local SQLite DB (~/.claude/kanban-dbs/{project}.db). Supports task CRUD (add, edit, move, remove), board viewing, session context persistence, and statistics. For pipeline orchestration use /kanban-run, for requirements refinement use /kanban-refine. Run /kanban-init first to create the local DB.
npx skillsauth add cyanluna-git/cyanluna.skills kanbanInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Shared context: read
shared.mdfor DB path, pipeline levels, status transitions, DB operations, error handling, and agent context flow. Safety principles: readprinciples.md— mandatory, not optional.
/kanban or /kanban list — View BoardBOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level, current_agent FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
Python으로 column별 그룹화 후 markdown table 출력 (ID, Status, Priority, Title).
/kanban context — Session HandoffRun first when starting a new session. Fetch board and output pipeline state: Implementing / Plan Review / Impl Review / Testing / Recently Done / Next Todo.
BOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level, current_agent FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
/kanban context save — Save Session StateCaptures current board state + git branch + decisions made this session to .claude/kanban-context.md.
Use before ending a session so the next session can resume without context loss.
BOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
DIRTY=$(git diff --stat 2>/dev/null | tail -1 || echo "")
Write .claude/kanban-context.md with:
impl / impl_review / test columns (ID, title, status)plan_review or impl_review (needs human decision)todo column$DIRTY)Add .claude/kanban-context.md to .gitignore if not already present.
/kanban context restore — Restore Session StateLoads .claude/kanban-context.md if it exists, then fetches live board to show what changed since save.
Use at session start instead of /kanban context when you were mid-task last session.
SAVED=$(cat .claude/kanban-context.md 2>/dev/null || echo "")
BOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
Output:
/kanban add <title> — Add Task/kanban move <ID> <status> — Move Task반드시
shared.md→ Move Protocol 순서를 따를 것. Step 1(현재 status+level 확인) → Step 2(매트릭스 조회) → Step 3(유효성 검사 후 이동 실행). 유효하지 않은 전환 시 오류 출력, 사용자에게 올바른 다음 상태 안내.
/kanban edit <ID> — Edit TaskAsk user which fields to modify, then update via sqlite3 CLI.
/kanban remove <ID> — Delete Tasksqlite3 "$DB" "DELETE FROM tasks WHERE id=$ID AND project='$PROJECT'"
/kanban stats — StatisticsBOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level, agent_log FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
python3 << 'PY'
import json, sys
from collections import defaultdict
rows = json.loads(sys.stdin.read())
board = {}
for row in rows:
board.setdefault(row['status'], []).append(row)
columns = ['todo', 'plan', 'plan_review', 'impl', 'impl_review', 'test', 'done']
# Column counts
counts = {col: len(board.get(col, [])) for col in columns}
counts['total'] = sum(counts.values())
print("## Column Counts\n")
print("| Status | Count |")
print("|--------|-------|")
for col in columns:
print(f"| {col} | {counts[col]} |")
print(f"| **total** | **{counts['total']}** |")
# Token stats per agent
agent_stats = defaultdict(lambda: {'entries': 0, 'tokens': 0})
for col in columns:
for task in board.get(col, []):
raw = task.get('agent_log')
if not raw:
continue
try:
logs = json.loads(raw) if isinstance(raw, str) else raw
except (json.JSONDecodeError, TypeError):
continue
for entry in logs:
agent = entry.get('agent', 'unknown')
agent_stats[agent]['entries'] += 1
agent_stats[agent]['tokens'] += entry.get('tokens', 0)
total_tokens = sum(v['tokens'] for v in agent_stats.values())
total_entries = sum(v['entries'] for v in agent_stats.values())
print("\n## Agent Token Usage\n")
if total_tokens == 0:
print("No token data")
else:
print("| Agent | Entries | Tokens (est.) |")
print("|-------|---------|---------------|")
for agent in sorted(agent_stats):
s = agent_stats[agent]
print(f"| {agent} | {s['entries']} | {s['tokens']:,} |")
print(f"| **Total** | **{total_entries}** | **{total_tokens:,}** |")
PY
Pass BOARD_JSON to stdin: echo "$BOARD_JSON" | python3 - <<'PY' ...
/kanban stats health — Code Health ScoreAuto-detects available tools and computes a 0–10 composite code health score. Use when: "health check", "코드 품질 확인", "how healthy is this codebase".
python3 - <<'PY'
import subprocess, json, sys
checks = []
def run(cmd, label, parse=None):
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
ok = r.returncode == 0
detail = parse(r) if parse else ""
checks.append({"label": label, "ok": ok, "detail": detail})
except FileNotFoundError:
pass # tool not installed — skip silently
except subprocess.TimeoutExpired:
checks.append({"label": label, "ok": False, "detail": "timeout"})
# TypeScript
run(["npx", "--no", "tsc", "--noEmit", "--pretty", "false"],
"TypeScript",
lambda r: f"{r.stdout.count('error TS')} errors" if r.returncode != 0 else "")
# Python type check (pyright preferred, mypy fallback)
if subprocess.run(["which", "pyright"], capture_output=True).returncode == 0:
run(["pyright", "--outputjson"], "Pyright",
lambda r: f"{json.loads(r.stdout).get('summary',{}).get('errorCount',0)} errors" if r.stdout else "")
elif subprocess.run(["which", "mypy"], capture_output=True).returncode == 0:
run(["mypy", ".", "--ignore-missing-imports"], "mypy",
lambda r: r.stdout.strip().split('\n')[-1] if r.stdout else "")
# Linter
if subprocess.run(["which", "ruff"], capture_output=True).returncode == 0:
run(["ruff", "check", "--statistics"], "ruff",
lambda r: r.stdout.strip().split('\n')[0] if r.stdout else "")
elif subprocess.run(["npx", "--no", "eslint", "--version"], capture_output=True).returncode == 0:
run(["npx", "--no", "eslint", ".", "--max-warnings=0"], "ESLint",
lambda r: f"{r.stdout.count('warning') + r.stdout.count('error')} issues" if r.returncode != 0 else "")
# Tests
if subprocess.run(["which", "pytest"], capture_output=True).returncode == 0:
run(["pytest", "--tb=no", "-q"], "pytest",
lambda r: r.stdout.strip().split('\n')[-1] if r.stdout else "")
elif subprocess.run(["npx", "--no", "jest", "--version"], capture_output=True).returncode == 0:
run(["npx", "--no", "jest", "--passWithNoTests", "--silent"], "Jest",
lambda r: r.stderr.strip().split('\n')[-1] if r.stderr else "")
# Rust
run(["cargo", "check", "--quiet"], "cargo check")
# Shell lint
if subprocess.run(["which", "shellcheck"], capture_output=True).returncode == 0:
sh_files = subprocess.run(["find", ".", "-name", "*.sh", "-not", "-path", "*/.git/*"],
capture_output=True, text=True).stdout.strip().split()
if sh_files:
run(["shellcheck"] + sh_files[:20], "shellcheck",
lambda r: f"{r.stdout.count('SC')} warnings" if r.returncode != 0 else "")
# Score
if not checks:
print("## Code Health\nNo supported tools found (tsc/pyright/ruff/pytest/jest/cargo/shellcheck).")
sys.exit(0)
passed = sum(1 for c in checks if c["ok"])
score = round(passed / len(checks) * 10, 1)
grade = "🟢" if score >= 8 else "🟡" if score >= 5 else "🔴"
print(f"## Code Health: {grade} {score}/10 ({passed}/{len(checks)} checks passed)\n")
print("| Check | Status | Detail |")
print("|-------|--------|--------|")
for c in checks:
icon = "✅" if c["ok"] else "❌"
print(f"| {c['label']} | {icon} | {c['detail'] or ''} |")
PY
/kanban retro — Retrospective AnalysisAnalyzes completed tasks + git history to produce a sprint retrospective report. Use at end of week/sprint: "kanban retro", "주간 회고", "what did we ship this week".
BOARD_JSON=$(sqlite3 -json "$DB" \
"SELECT id, title, status, priority, level, agent_log, impl_review_count FROM tasks WHERE project='$PROJECT' ORDER BY status, rank, id")
python3 - <<'PY'
import json, sys, subprocess
from collections import defaultdict
rows = json.loads(sys.stdin.read())
board = {}
for row in rows:
board.setdefault(row['status'], []).append(row)
columns = ['todo', 'plan', 'plan_review', 'impl', 'impl_review', 'test', 'done']
done_tasks = board.get('done', [])
print("## Retrospective\n")
# --- Completed tasks ---
print(f"### Completed: {len(done_tasks)} tasks\n")
if done_tasks:
print("| ID | Title | Level | Rework |")
print("|----|-------|-------|--------|")
for t in done_tasks[-10:]:
rework = t.get('impl_review_count', 0) or 0
flag = f"⚠️ {rework}x" if rework > 1 else "✅"
print(f"| {t.get('id','')} | {t.get('title','')[:45]} | L{t.get('level',1)} | {flag} |")
# --- Rework rate ---
rework_tasks = [t for t in done_tasks if (t.get('impl_review_count') or 0) > 1]
rate = len(rework_tasks) / len(done_tasks) * 100 if done_tasks else 0
print(f"\n**Rework rate**: {rate:.0f}% ({len(rework_tasks)}/{len(done_tasks)} tasks needed re-impl)")
# --- Pipeline snapshot (non-done) ---
snapshot = {col: len(board.get(col, [])) for col in columns[:-1] if board.get(col)}
if snapshot:
print("\n### Pipeline Snapshot\n")
print("| Column | Count |")
print("|--------|-------|")
for col, count in snapshot.items():
print(f"| {col} | {count} |")
# --- Agent token spend (done tasks) ---
agent_stats = defaultdict(lambda: {'entries': 0, 'tokens': 0})
for t in done_tasks:
raw = t.get('agent_log')
if not raw:
continue
try:
logs = json.loads(raw) if isinstance(raw, str) else raw
except (json.JSONDecodeError, TypeError):
continue
for entry in logs:
a = entry.get('agent', 'unknown')
agent_stats[a]['entries'] += 1
agent_stats[a]['tokens'] += entry.get('tokens', 0)
total = sum(v['tokens'] for v in agent_stats.values())
if total > 0:
print(f"\n### Token Spend (completed): {total:,} est.\n")
print("| Agent | Tokens |")
print("|-------|--------|")
for a in sorted(agent_stats, key=lambda x: -agent_stats[x]['tokens']):
print(f"| {a} | {agent_stats[a]['tokens']:,} |")
# --- Git commits ---
git = subprocess.run(
['git', 'log', '--oneline', '--since=7 days ago'],
capture_output=True, text=True
)
commits = [l for l in git.stdout.strip().split('\n') if l]
if commits:
print(f"\n### Git Activity: {len(commits)} commits (last 7 days)")
PY
Pass BOARD_JSON to stdin. To scope to a custom period (e.g. 14 days), adjust --since=14 days ago in the git subprocess call.
/kanban project — Current Project ContextFetch the current project's context from the projects table.
sqlite3 -json "$DB" "SELECT * FROM projects WHERE id='$PROJECT'"
Output formatted project context: Purpose, Stack, Brief, Category, task counts by status, linked projects.
If the project is not registered, suggest running /kanban-init to register it.
/kanban project all — Full Project Mapsqlite3 -json "$DB" "SELECT id, name, purpose, status, category FROM projects ORDER BY category, name"
Output: projects grouped by category with names and purposes.
/kanban project brief — View/Update Project BriefThe brief is a compressed context summary (200–500 chars) that agents consume at low token cost.
View current brief:
sqlite3 "$DB" "SELECT brief FROM projects WHERE id='$PROJECT'"
Set brief directly:
sqlite3 "$DB" "UPDATE projects SET brief='...' WHERE id='$PROJECT'"
AI-assisted update (/kanban project brief update):
/kanban project update <field> <value> — Edit Project Metadatasqlite3 "$DB" "UPDATE projects SET purpose='...' WHERE id='$PROJECT'"
sqlite3 "$DB" "UPDATE projects SET status='archived' WHERE id='$PROJECT'"
Supported fields: name, purpose, stack, brief, status, category, repo_url.
/kanban project link — Manage Project Relationships# Add relationship
sqlite3 "$DB" "INSERT OR IGNORE INTO project_links (source_id, target_id, relation) VALUES ('$PROJECT', 'other-project', 'depends_on')"
# Remove relationship
sqlite3 "$DB" "DELETE FROM project_links WHERE source_id='$PROJECT' AND target_id='other-project' AND relation='depends_on'"
# List links
sqlite3 -json "$DB" "SELECT * FROM project_links WHERE source_id='$PROJECT'"
Relations: extends, serves, depends_on, shares_data.
Run /kanban-init first to create the local SQLite DB and register the project.
Add to .gitignore:
.claude/kanban.json
.codex/kanban.json
.claude/kanban-context.md
DB is stored at ~/.claude/kanban-dbs/<PROJECT>.db — not in the project directory.
testing
Turn vague intent into a precise, executable spec through 5 structured phases, then optionally create a kanban task. Use when starting something new that needs proper scoping before refinement. Complements /kanban-refine (which refines existing tasks).
databases
--- name: kanban-local description: Local markdown-file kanban for toy and personal projects. No server, no PostgreSQL, no auth — all state lives in KANBAN.md in the project root. Use for solo/small projects where the remote kanban board is overkill. Auto-trigger when KANBAN.md exists and user says "태스크 추가", "add task", "칸반 보여줘", "다음 할 일", "task list", or similar task-management phrases. Sub-commands: init, list, add, move, done, show, edit, refine, run, rm, stats. --- # kanban-local 로컬 `KANBA
documentation
프로젝트의 전체 아키텍처, 목표, 주요 결정사항을 wiki/ 디렉토리에 합성하여 정리합니다. 첫 실행 시 전체 생성, update로 변경분 반영. 매 카드가 아닌 프로젝트 수준의 지식을 정리합니다.
development
--- name: kanban-run description: Run the AI team pipeline for kanban tasks — orchestration loop with 6 agents (Planner, Critic, Builder, Shield, Inspector, Ranger), single-step execution, and code review. Use /kanban-run to execute tasks through the 7-column pipeline. AUTO-TRIGGER when: user says "implement task NNN" or any task ID + implement/build/do combination; or user confirms with "yes/ok/go/do it" after Claude proposes implementing a specific kanban task. license: MIT --- ## Auto-Trigge