Agent skill

Kanban Heartbeat

by cyanluna-git in cyanluna-git/cyanluna.skills

Scan kanban boards for stagnant tasks and optionally mark them.

MITAuto-check passedProductivity & Automation

Install Kanban Heartbeat

skills CLI
$ npx skills add cyanluna-git/cyanluna.skills --skill kanban-heartbeat -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install cyanluna-git/cyanluna.skills kanban-heartbeat --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/cyanluna-git/cyanluna.skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/kanban-heartbeat .claude/skills/kanban-heartbeat && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
kanban-heartbeat
GitHub stars
183
Token cost
~2.7k tokens
SKILL.md length
111 words
Files
1
Skills in repo
16
Repo updated
First seen
Licence
MIT

At a glance

Scan kanban boards for stagnant tasks and optionally mark them.

  • Tasks that involve Task management
  • Calls python3 and sqlite3
  • Tasks that involve Sprint planning and agile

What it does

Kanban Heartbeat is an agent skill from cyanluna-git/cyanluna.skills. Scan kanban boards for stagnant tasks and optionally mark them. Detects tasks with no agent activity for N days (default 3), outputs a markdown report table, and appends Heartbeat entries to agentlog unless --dry-run.

Its SKILL.md is about 2.7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Productivity & Automation, covering Task management and Sprint planning and agile. The licence is MIT.

When your agent uses it

  • Tasks that involve Task management
  • Tasks that involve Sprint planning and agile

Example prompts

  • “/kanban-heartbeat”

Requirements

  • Python 3

What it can do on your machine

Read from SKILL.md and the folder at commit df5be37. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • python3
    • sqlite3

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Kanban Heartbeat loads about 2.7k tokens when it runs. Until then it costs about 59 tokens; SKILL.md has 111 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~59
When it runs · the whole SKILL.md, loaded when a task matches
~2.7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from cyanluna-git/cyanluna.skills at commit df5be37, republished under its MIT licence (© cyanluna-git). 111 words, ~2,738 tokens.

Download SKILL.mdSave it as .claude/skills/kanban-heartbeat/SKILL.md (or your agent's skills folder).
name
kanban-heartbeat
description
Scan kanban boards for stagnant tasks and optionally mark them. Detects tasks with no agent activity for N days (default 3), outputs a markdown report table, and appends Heartbeat entries to agent_log unless --dry-run.
license
MIT

Shared context: read ../kanban/shared.md for DB path, pipeline levels, status transitions, DB operations, error handling, and agent context flow. Schema: read ../kanban/schema.md for full DB schema, column descriptions, and JSON field formats.

/kanban-heartbeat [--project X] [--days N] [--dry-run] -- Stagnant Task Detection

Scan all active projects (or a single project) for tasks that have had no agent activity for N days. Output a markdown table of stagnant tasks and optionally append a Heartbeat warning entry to each task's agent_log.

Defaults: --days 3, all active projects, writes to agent_log. --dry-run: report only, no agent_log modifications.

Procedure
① DB Setup & Argument Parsing

   Read project config and DB path:
   CONFIG=$(cat .claude/kanban.json 2>/dev/null || cat .codex/kanban.json 2>/dev/null)
   PROJECT=$(echo "$CONFIG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['project'])" 2>/dev/null || basename "$(pwd)")
   DB="$HOME/.claude/kanban-dbs/${PROJECT}.db"

   Parse CLI arguments:
   - --project X  → scan only project X (override PROJECT)
   - --days N     → stagnation threshold in days (default: 3)
   - --dry-run    → report only, do not write agent_log entries

② Fetch Projects

   If --project X specified:
     Validate project exists:
     sqlite3 "$DB" "SELECT id FROM projects WHERE id='$X'"
     If empty → print error "Project '$X' not found." and exit.
     PROJECTS=("$X")

   Else:
     PROJECTS = sqlite3 "$DB" "SELECT id FROM projects WHERE status='active'"
     If no active projects → scan using PROJECT from config

③ Fetch Tasks per Project (active columns only)

   For each project P in PROJECTS:
     sqlite3 -json "$DB" "SELECT id, title, status, agent_log, created_at FROM tasks
       WHERE project='$P' AND status IN ('todo','plan','plan_review','impl','impl_review','test')
       ORDER BY id"

     If project has 0 tasks → skip silently, continue.

④ Extract Last Activity Timestamp per Task

   For each task, use Python for safe JSON parsing of agent_log:

   python3 -c "
   import json, sys
   task = json.loads(sys.stdin.read())
   agent_log_raw = task.get('agent_log') or '[]'
   try:
       log = json.loads(agent_log_raw)
       if isinstance(log, list) and len(log) > 0:
           timestamps = [e.get('timestamp', '') for e in log if isinstance(e, dict)]
           timestamps = [t for t in timestamps if t]
           if timestamps:
               print(max(timestamps))
               sys.exit(0)
   except (json.JSONDecodeError, TypeError):
       print('PARSE_ERROR', file=sys.stderr)
   # Fallback to created_at
   print(task.get('created_at', ''))
   "

   Store: task ID, project, status, title, last_activity_ts

⑤ Compute Stagnation

   NOW = current UTC timestamp
   THRESHOLD = NOW - N days

   For each task:
     Parse last_activity_ts as datetime
     days_stagnant = (NOW - last_activity_ts).days
     If days_stagnant >= N → mark as stagnant

   If no stagnant tasks across all projects:
     Print "No stagnant tasks found."
     Exit.

⑥ Output Markdown Table

   Sort stagnant tasks by days_stagnant descending.

   Print:

   | ID | Project | Status | Days | Title |
   |----|---------|--------|------|-------|
   | 2100 | cpet.db | impl | 12 | Add export feature |
   | 2055 | today.bike | plan | 5 | Refactor route module |

   Print summary line:
   "**Heartbeat: X stagnant tasks found across Y projects.**"
   If --dry-run: append " (dry-run, no agent_log entries written)"

⑦ Write agent_log Entries (skip if --dry-run)

   For each stagnant task, append a Heartbeat entry using sqlite3 json_insert:

   NEW_ENTRY=$(python3 -c "
   import json, datetime
   entry = {
     'agent': 'Heartbeat',
     'model': 'system',
     'message': f'⚠️ Stagnant {DAYS} days in {STATUS}. Last activity: {LAST_TS}',
     'timestamp': datetime.datetime.utcnow().isoformat() + 'Z'
   }
   print(json.dumps(entry))
   ")
   sqlite3 "$DB" "UPDATE tasks SET agent_log=json_insert(COALESCE(agent_log,'[]'), '\$[#]', json('$NEW_ENTRY')), updated_at=datetime('now') WHERE id=$TASK_ID AND project='$PROJECT'"
   echo "  Heartbeat written to task #$TASK_ID"

   Print: "agent_log entries written for X tasks."
Full Implementation (Copy-Paste Script)

The executing agent should run this as a single Python script for reliability:

bash
python3 - "$@" <<'PYEOF'
import sqlite3 as sq, json, sys, datetime, re, os, pathlib

# ── Parse arguments ──────────────────────────────────────────────
args = sys.argv[1:]
project_filter = None
days_threshold = 3
dry_run = False

i = 0
while i < len(args):
    if args[i] == "--project" and i + 1 < len(args):
        project_filter = args[i + 1]; i += 2
    elif args[i] == "--days" and i + 1 < len(args):
        days_threshold = int(args[i + 1]); i += 2
    elif args[i] == "--dry-run":
        dry_run = True; i += 1
    else:
        i += 1

# ── DB Setup ─────────────────────────────────────────────────────
import subprocess
config_paths = [".claude/kanban.json", ".codex/kanban.json"]
project_name = None
for p in config_paths:
    if os.path.exists(p):
        try:
            d = json.loads(open(p).read())
            project_name = d.get("project")
            break
        except Exception:
            pass
if not project_name:
    project_name = os.path.basename(os.getcwd())

db_path = str(pathlib.Path.home() / ".claude" / "kanban-dbs" / f"{project_name}.db")
if not os.path.exists(db_path):
    print(f"Error: DB not found at {db_path}. Run /kanban-init first.")
    sys.exit(1)

conn = sq.connect(db_path)
conn.row_factory = sq.Row

# ── Fetch projects ───────────────────────────────────────────────
if project_filter:
    row = conn.execute("SELECT id FROM projects WHERE id=?", (project_filter,)).fetchone()
    if not row:
        # Fall back: check if project has tasks even without projects table entry
        count = conn.execute("SELECT count(*) FROM tasks WHERE project=?", (project_filter,)).fetchone()[0]
        if count == 0:
            print(f"Error: Project '{project_filter}' not found.")
            sys.exit(1)
    projects = [project_filter]
else:
    rows = conn.execute("SELECT id FROM projects WHERE status='active'").fetchall()
    projects = [r["id"] for r in rows]
    if not projects:
        # Fall back to the project from config
        projects = [project_name]

if not projects:
    print("No active projects found.")
    sys.exit(0)

# ── Scan tasks ───────────────────────────────────────────────────
now = datetime.datetime.utcnow()
active_columns = ("todo", "plan", "plan_review", "impl", "impl_review", "test")
stagnant_tasks = []

for proj in projects:
    rows = conn.execute(
        "SELECT id, title, status, agent_log, created_at FROM tasks WHERE project=? AND status IN ({})".format(
            ",".join("?" * len(active_columns))
        ),
        (proj, *active_columns)
    ).fetchall()

    for task in rows:
        task_id = task["id"]
        title = task["title"] or "(untitled)"
        status = task["status"]
        created_at = task["created_at"] or ""
        agent_log_raw = task["agent_log"]

        # Extract last activity timestamp
        last_ts = None
        parse_error = False
        if agent_log_raw:
            try:
                log = json.loads(agent_log_raw) if isinstance(agent_log_raw, str) else agent_log_raw
                if isinstance(log, list) and len(log) > 0:
                    timestamps = [e.get("timestamp", "") for e in log if isinstance(e, dict)]
                    timestamps = [t for t in timestamps if t]
                    if timestamps:
                        last_ts = max(timestamps)
            except (json.JSONDecodeError, TypeError):
                parse_error = True

        if last_ts is None:
            last_ts = created_at
            if parse_error:
                print(f"Warning: task #{task_id} has malformed agent_log, falling back to created_at", file=sys.stderr)

        if not last_ts:
            print(f"Warning: task #{task_id} has no timestamp, skipping", file=sys.stderr)
            continue

        # Parse timestamp
        try:
            clean_ts = re.sub(r"\.\d+", "", last_ts.replace("Z", "").replace("+00:00", ""))
            ts_dt = datetime.datetime.fromisoformat(clean_ts)
        except (ValueError, AttributeError):
            print(f"Warning: task #{task_id} has unparseable timestamp '{last_ts}', skipping", file=sys.stderr)
            continue

        days_stagnant = (now - ts_dt).days
        if days_stagnant >= days_threshold:
            stagnant_tasks.append({
                "id": task_id,
                "project": proj,
                "status": status,
                "days": days_stagnant,
                "title": title,
                "last_ts": last_ts,
            })

# ── Output ───────────────────────────────────────────────────────
if not stagnant_tasks:
    print("No stagnant tasks found.")
    sys.exit(0)

stagnant_tasks.sort(key=lambda t: t["days"], reverse=True)

print("")
print("| ID | Project | Status | Days | Title |")
print("|----|---------|--------|------|-------|")
for t in stagnant_tasks:
    print(f"| {t['id']} | {t['project']} | {t['status']} | {t['days']} | {t['title']} |")
print("")

project_set = set(t["project"] for t in stagnant_tasks)
summary = f"**Heartbeat: {len(stagnant_tasks)} stagnant tasks found across {len(project_set)} projects.**"
if dry_run:
    summary += " (dry-run, no agent_log entries written)"
print(summary)

# ── Write agent_log entries ──────────────────────────────────────
if dry_run:
    sys.exit(0)

print("")
written = 0
for t in stagnant_tasks:
    try:
        row = conn.execute("SELECT agent_log FROM tasks WHERE id=? AND project=?", (t["id"], t["project"])).fetchone()
        try:
            log = json.loads(row["agent_log"] or "[]")
        except (json.JSONDecodeError, TypeError):
            log = []

        log.append({
            "agent": "Heartbeat",
            "model": "system",
            "message": f"⚠️ Stagnant {t['days']} days in {t['status']}. Last activity: {t['last_ts']}",
            "timestamp": now.isoformat() + "Z",
        })

        conn.execute(
            "UPDATE tasks SET agent_log=?, updated_at=datetime('now') WHERE id=? AND project=?",
            (json.dumps(log), t["id"], t["project"])
        )
        conn.commit()
        print(f"  Heartbeat written to task #{t['id']}")
        written += 1
    except Exception as e:
        print(f"  Error writing to task #{t['id']}: {e}", file=sys.stderr)

print(f"\nagent_log entries written for {written} tasks.")
conn.close()
PYEOF

© cyanluna-git, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in kanban-heartbeat of cyanluna-git/cyanluna.skills.

Open the folder on GitHubat commit df5be37

Compare with similar skills

Kanban Heartbeat next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Kanban Heartbeat compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Kanban Heartbeat this skillcyanluna-git/cyanluna.skills183—~2.7kAutomated safety check: PassMIT
Kanbaniii-hq/workers113—~1.4kAutomated safety check: PassApache-2.0
Frappe Impl UI ComponentsImpertio-Studio/Frappe_Claude_Skill_Package189—~4.2kAutomated safety check: PassMIT
Wrongstack KanbanWrongStack/WrongStack371—~2.5kAutomated safety check: PassMIT
Kanban Mdantopolskiy/kanban-md223—~4kAutomated safety check: PassMIT
Herdr BoardnelsonPires5/herdr-board173—~4.6kAutomated safety check: PassNone

Similar skills

  • Kanban

    iii-hq/workers

    File-backed kanban board: create, read, move and comment on tickets by key or uuid, assign agent profiles, and wake on comments through the kanban:comment trigger instead of polling.

    113 GitHub stars~1.4k tokensUpdated yesterday
    Productivity & AutomationAuto-check passed
  • Frappe Impl UI Components

    Impertio-Studio/Frappe_Claude_Skill_Package

    A skill your agent uses when building custom dialogs, extending List View, creating Page controllers, or adding Kanban/Calendar views and realtime updates.

    189 GitHub stars~4.2k tokensUpdated 24 days ago
    Productivity & AutomationAuto-check passed
  • Wrongstack Kanban

    WrongStack/WrongStack

    Record substantial project work on WrongStack's IPC-backed Kanban board so it survives the session and other agents can see it.

    371 GitHub stars~2.5k tokensUpdated yesterday
    Productivity & AutomationAuto-check passed
  • Kanban Md

    antopolskiy/kanban-md

    Manage project tasks using kanban-md, a file-based kanban board CLI.

    223 GitHub stars~4k tokensUpdated 5 days ago
    Productivity & AutomationAuto-check passed
  • Herdr Board

    nelsonPires5/herdr-board

    Interact with the herdr-board kanban from inside an agent run or an interactive session.

    173 GitHub stars~4.6k tokensUpdated yesterday
    Productivity & AutomationAuto-check passed
  • Team Workflow

    Owl-Listener/designer-skills

    Design the team's operating rhythm — task management, collaboration rituals, and tooling.

    2.9k GitHub starsUsed in 1 repo~553 tokens
    Productivity & AutomationAuto-check passed

More from cyanluna-git/cyanluna.skills

All 16 skills in this repo
  • Kanban Batch Run

    cyanluna-git/cyanluna.skills

    Run multiple kanban tasks end-to-end in Rolling Wave order — refine each task based on the prior card's actual implementation, then implement, then verify, then refine the next.

    183 GitHub stars~2.7k tokensUpdated 3 mo ago
    Auto-check passed
  • Kanban

    cyanluna-git/cyanluna.skills

    Manage project tasks in a local SQLite DB (~/.claude/kanban-dbs/{project}.db).

    183 GitHub stars~3.6k tokensUpdated 3 mo ago
    Auto-check passed
  • Kanban Explore

    cyanluna-git/cyanluna.skills

    Codebase exploration skill for uncertain implementation direction.

    183 GitHub stars~3k tokensUpdated 3 mo ago
    Auto-check passed
  • Kanban Local

    cyanluna-git/cyanluna.skills

    Local markdown-file kanban for toy and personal projects. An agent skill from cyanluna-git/cyanluna.skills.

    183 GitHub stars~1.9k tokensUpdated 3 mo ago
    Auto-check passed
  • Kanban Run

    cyanluna-git/cyanluna.skills

    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.

    183 GitHub stars~4.3k tokensUpdated 3 mo ago
    Auto-check passed
  • Kanban Spec

    cyanluna-git/cyanluna.skills

    Turn vague intent into a precise, executable spec through 5 structured phases, then optionally create a kanban task.

    183 GitHub stars~1.4k tokensUpdated 3 mo ago
    Auto-check passed

Questions about Kanban Heartbeat

What does Kanban Heartbeat do?

Scan kanban boards for stagnant tasks and optionally mark them. skills. Scan kanban boards for stagnant tasks and optionally mark them.

When should I use Kanban Heartbeat?

Kanban Heartbeat fits situations like: tasks that involve Task management; tasks that involve Sprint planning and agile.

How do I install Kanban Heartbeat in Claude Code?

Run `npx skills add cyanluna-git/cyanluna.skills --skill kanban-heartbeat -a claude-code`. Or copy the skill folder (kanban-heartbeat in cyanluna-git/cyanluna.skills) into .claude/skills/kanban-heartbeat in your project. Claude Code loads it when a task matches its description.

How do I install Kanban Heartbeat in Codex?

Run `npx skills add cyanluna-git/cyanluna.skills --skill kanban-heartbeat -a codex`. Or copy the skill folder (kanban-heartbeat in cyanluna-git/cyanluna.skills) into .agents/skills/kanban-heartbeat in your project. Codex loads it when a task matches its description.

Can I use Kanban Heartbeat in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add cyanluna-git/cyanluna.skills --skill kanban-heartbeat -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/kanban-heartbeat, .gemini/skills/kanban-heartbeat, .github/skills/kanban-heartbeat and .opencode/skills/kanban-heartbeat in your project.

What does Kanban Heartbeat need to run?

Going by SKILL.md and its folder, Kanban Heartbeat needs the command-line tools its instructions call (python3 and sqlite3). Our summary lists: Python 3.

Does Kanban Heartbeat access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Kanban Heartbeat safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Kanban Heartbeat use?

Kanban Heartbeat is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Kanban Heartbeat use?

About 2.7k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Kanban Heartbeat?

Skills that share tags, products or a category with Kanban Heartbeat: Kanban (iii-hq/workers, 113 stars), Frappe Impl UI Components (Impertio-Studio/Frappe_Claude_Skill_Package, 189 stars), Wrongstack Kanban (WrongStack/WrongStack, 371 stars) and Kanban Md (antopolskiy/kanban-md, 223 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Kanban Heartbeat?

cyanluna-git (a GitHub user) maintains it in cyanluna-git/cyanluna.skills, which has 183 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on June 22, 2026.

Source: cyanluna-git/cyanluna.skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.