{"id":51140784,"url":"https://github.com/dustinspace217/project-board","last_synced_at":"2026-06-25T22:30:39.350Z","repository":{"id":366126378,"uuid":"1274421148","full_name":"dustinspace217/project-board","owner":"dustinspace217","description":"A self-maintaining Kanban board of your Claude Code projects as a KDE Plasma 6 widget — a local LLM classifies each project's state from its session transcripts","archived":false,"fork":false,"pushed_at":"2026-06-20T11:52:41.000Z","size":556,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T12:21:03.616Z","etag":null,"topics":["claude-code","kanban","kde-plasma","local-llm","ollama","plasmoid"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dustinspace217.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T13:49:05.000Z","updated_at":"2026-06-20T11:52:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dustinspace217/project-board","commit_stats":null,"previous_names":["dustinspace217/project-board"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dustinspace217/project-board","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dustinspace217%2Fproject-board","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dustinspace217%2Fproject-board/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dustinspace217%2Fproject-board/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dustinspace217%2Fproject-board/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dustinspace217","download_url":"https://codeload.github.com/dustinspace217/project-board/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dustinspace217%2Fproject-board/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34795436,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","kanban","kde-plasma","local-llm","ollama","plasmoid"],"created_at":"2026-06-25T22:30:38.325Z","updated_at":"2026-06-25T22:30:39.342Z","avatar_url":"https://github.com/dustinspace217.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Project Board\n\n[![ci](https://github.com/dustinspace217/project-board/actions/workflows/ci.yml/badge.svg)](https://github.com/dustinspace217/project-board/actions/workflows/ci.yml)\n\nA self-maintaining Kanban board of your [Claude Code](https://claude.ai/code)\nprojects, rendered as a KDE Plasma 6 desktop widget.\n\nA small Python scanner looks at the projects under your projects root (`~/Claude`\nby default) and the Claude Code session transcripts under `~/.claude/projects`,\nworks out what state each project is in, and writes a single `board.json`. A thin\nPlasma plasmoid reads that file and draws a five-column board — Planning, Writing,\nQA, Testing, Finished — so you can see every project's status at a glance without\nopening anything.\n\nThere is no server and no daemon. A `systemd --user` timer re-runs the scan every\n15 minutes; the scanner itself runs in 1–2 seconds and exits.\n\n## What it looks like\n\n![The Project Board widget showing projects sorted across the five status columns](assets/screenshot.png)\n\nEach card shows the project name, the last completed work item, how long since it\nwas last touched, the immediate next step, and an \"owner\" chip — whose move is next\n(`claude`, `you`, or `done`). The name is prefixed with 📌 when you've pinned the card\nand ⚠ when its classification is stale (the model was tried but failed). Cards that\nhave gone quiet past a staleness threshold get an amber edge. Clicking a card copies a `claude --resume \u003csession\u003e` command to\nyour clipboard so you can jump straight back into that project's conversation.\n\n## How it works\n\nThe scanner is pure Python standard library — no third-party packages — so it runs\nanywhere Python 3 is installed.\n\n1. **Project detection** (`board/enumerate.py`). Each immediate subdirectory of the\n   projects root counts as a project if it has any project marker: a `CLAUDE.md`, a\n   `.git/`, a plan document, a root-level `PLAN.md`/`README.md`, a `.board-status`\n   pin, or a source/content file. Loose root-level `*.md` plan files (a plan that\n   lives as a single file, not yet in its own directory) are surfaced as lightweight\n   cards too, so nothing in progress is invisible. A directory can opt out with a\n   `.board-ignore` file.\n\n2. **Session attribution** (`board/attribution.py`). Most work happens from the\n   projects-root session rather than a per-project one, so a project's own session\n   folder is often just tangential command spawns. The scanner instead reads the\n   transcripts and attributes each session to the project it mentions most\n   (`/\u003croot-name\u003e/\u003cproject\u003e` path counts). The result is cached in\n   `session_index.json` and rebuilt incrementally — only sessions whose mtime changed\n   are re-read — so a scan over hundreds of transcripts stays cheap.\n\n3. **Classification** (`board/llm_classify.py`). For each project, the recent,\n   human-readable turns of its session (plus the plan doc's `## Status` block, if\n   there is one) are handed to a **local** language model running under\n   [Ollama](https://ollama.com) (`qwen2.5:7b`). The model returns the project's\n   current bucket, whose move is next, the next step, and any blocker. Everything\n   stays on your machine — no transcript text leaves it.\n\n4. **GPU gating** (`board/gpu_gate.py`). Before loading the model, the scanner\n   checks the GPU once with `nvidia-smi`. If the GPU is busy (a game, or any heavy\n   CUDA workload), it skips all model calls for that scan and carries the previous\n   cards forward, so the board never contends with what you're doing. On a machine\n   with no NVIDIA GPU this check is a no-op and classification proceeds normally.\n\n5. **Heuristic fallback** (`board/classify.py`). When there is no transcript to read,\n   or the model is unavailable, or the GPU is busy, the scanner falls back to a\n   deterministic keyword heuristic over the project's `## Status` block. Every card\n   records *how* it was classified (`llm`, `carried`, `gated`, `heuristic`, `pinned`)\n   so a model outage is visible rather than silent.\n\n`board.json` is written atomically (to a temp file, then renamed) so the widget\nnever reads a half-written file.\n\n## Features\n\n- **Manual pins.** Drop a `.board-status` file in a project to override what the\n  model infers — for example to mark a project finished when the transcript still\n  reads as active. A pinned project skips classification entirely; delete the file\n  to return to automatic state.\n- **Drag to reclassify.** Drag a card to another column in the widget to pin its\n  bucket. The widget writes the project's `.board-status` for you, so the change\n  survives the next scan.\n- **Broader project detection.** Beyond git repos and `CLAUDE.md`, the scanner now\n  picks up plan-only projects, source-only directories, and loose root-level plan\n  files (see \"How it works\" above).\n- **\"Show all\" toggle.** Finished projects drop off the board after a few days to\n  keep it focused on active work. They are flagged rather than deleted — a \"Show all\"\n  checkbox in the widget reveals the dropped (finished) cards.\n\n## Requirements\n\n- Python 3 (the scanner targets 3.12).\n- [Ollama](https://ollama.com) with the `qwen2.5:7b` model pulled\n  (`ollama pull qwen2.5:7b`) for live classification. Without it, the scanner still\n  runs and falls back to the heuristic.\n- An NVIDIA GPU is **optional** — it is only used by the GPU-busy gate. Without one,\n  classification simply always proceeds.\n- KDE Plasma 6 to display the widget. The scanner alone (writing `board.json`) needs\n  none of KDE.\n\n## Install and run\n\nRun a scan by hand at any time:\n\n```sh\npython3 scan.py\n```\n\nThis writes `~/.local/share/project-board/board.json`.\n\nTo set it up to refresh automatically and install the widget:\n\n```sh\nscripts/install.sh\n```\n\nThat installs and enables the `systemd --user` timer (every 15 minutes), seeds\n`board.json` once so the widget has data immediately, and installs the Plasma\nplasmoid. Afterwards, add the widget from your desktop's \"Add Widgets\" panel\n(search for \"Project Board\").\n\nIf you edit the widget's QML, reload it with:\n\n```sh\nscripts/reload-widget.sh\n```\n\nThis upgrades the package, clears the QML cache, and restarts `plasmashell` (your\ndesktop will flicker for a couple of seconds — that is expected). The board's *data*\nauto-refreshes on its own; only *code* changes to the widget need this.\n\n\u003e The widget reads `board.json` from `$HOME/.local/share/project-board/board.json` —\n\u003e the same path `scan.py` writes. The `$HOME` is expanded at read time, so no per-user\n\u003e editing is needed.\n\n## Configuration\n\n- `PROJECT_BOARD_ROOT` — the directory whose subdirectories are scanned as projects.\n  Defaults to `~/Claude`. Set it to point the board at a different layout:\n\n  ```sh\n  PROJECT_BOARD_ROOT=\"$HOME/code\" python3 scan.py\n  ```\n\n  That variable also drives the 15-minute timer **if you set it when running\n  `scripts/install.sh`** — the installer bakes it into the systemd unit. A plain shell\n  export won't reach the timer on its own, because `systemd --user` ignores your shell\n  environment.\n\n## Tests\n\nThe test suite is hermetic — it uses synthetic fixtures and never touches your real\n`~/.claude` or the GPU, and runs with classification disabled (`allow_llm=False`),\nso no Ollama or NVIDIA hardware is needed:\n\n```sh\npython3 -m pytest -q\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdustinspace217%2Fproject-board","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdustinspace217%2Fproject-board","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdustinspace217%2Fproject-board/lists"}