Jira-style Kanban boards for triaging the issues and PRs of several GitHub
repos, rebuilt every ~15 minutes by GitHub Actions (using the repo's stored
PROJECTS_TOKEN) and published on GitHub Pages. Read-only: refreshing the
page always serves the latest snapshot — no viewer tokens, no editing.
Boards index: https://danbar2.github.io/triage-boards/
| Board | Watched repo |
|---|---|
| Grove | ai-dynamo/grove |
| KAI Scheduler | kai-scheduler/KAI-Scheduler |
| Karta | run-ai/karta |
For every open issue/PR it computes the last meaningful activity — comments, reviews, commits, force-pushes, review-thread replies, ready-for-review. Label, milestone, assignment, and project churn is ignored, as is bot activity.
Each stage is a column; cards are sorted by last meaningful activity, newest first:
| Column | Meaning |
|---|---|
| Needs first response | No maintainer has engaged yet |
| Awaiting maintainer | Author/community acted last (replied, pushed, resolved threads) — the working queue |
| Awaiting author | A maintainer responded last; ball is with the author |
| In progress | Dealt with, not waiting on triage — any of: an open PR closes the issue, its project Status is one of in_progress_statuses, or the triaged_when_set fields (issue type + Priority) are all set |
| Stale | No meaningful activity for 80+ days (either direction) |
Classification rule: if the last non-bot actor is a maintainer (and not the item's own author), the item is awaiting author; otherwise it's awaiting maintainer (or needs first response if no maintainer has ever engaged). An item that is already being worked on is not waiting on anyone, so in progress takes over from needs first response and awaiting maintainer — it never hides an item from awaiting author, and stale still wins over everything.
Inline (diff) comments count as engagement in full: only the review that opens a review thread appears in the PR timeline, so every comment of every thread is read as well — otherwise a maintainer who answered inside a thread looks like they never engaged.
The header has a PRs/Issues toggle and a user filter — a dropdown of every
person actually involved with an item (author, assignee, or anyone who
commented, reviewed, or pushed), most-active first. Requested reviewers are
excluded on purpose: they're auto-populated from CODEOWNERS and mostly haven't
engaged, so the filter reflects who has actually touched the item. Both filters
are kept in the URL hash, so a view like …/grove/#pr&user=alice can be
bookmarked or shared.
Everything lives in config.yaml: shared defaults (ignored
bots, stale_days: 80, the triage-field rule) and a projects map with one
entry per board. The key is the URL slug; each project sets its watched repo,
its maintainers list, and may override any default.
To add a project, add an entry under projects: and push — the next workflow
run publishes it at /<slug>/ and lists it on the index page.
Every board is a static snapshot regenerated every ~15 minutes by the
scheduled workflow (GitHub Actions cron is best-effort, so expect 15–25
minutes in practice). The header shows the snapshot's age. The token used for
fetching lives only in the repo's Actions secrets — the pages themselves
contain no credentials and perform no GitHub operations. For an immediate
rebuild, run the workflow manually (Actions tab → "Run workflow", or
gh workflow run dashboard.yml -R danbar2/triage-boards).
The type comes from GitHub's native issue type; Status, Priority and Severity
are read from the issue's GitHub Project (v2) single-select fields of those
names (case-insensitive, any linked project — e.g. Grove's roadmap board and
run-ai/projects/7 for Karta).
Status drives the In progress column via in_progress_statuses, matched
ignoring case and punctuation so one entry covers every board's spelling
(In-Progress, In Progress, In progress).
Reading Projects v2 requires a token with the read:project scope, which
neither the default Actions token nor a plain repo PAT has. To enable the
check, add a repo secret named PROJECTS_TOKEN containing a PAT with
repo + read:project scopes (Settings → Secrets and variables → Actions),
authorized for every org whose projects it must read. Note: the NVIDIA
enterprise rejects classic PATs with a lifetime over 366 days, so set the
token's expiration to a year or less. Without a working token the boards still
work — they just can't see those fields, log a warning, and treat no issue as
triaged; In progress then falls back to the linked-PR signal alone, which
needs no extra scope.
pip install requests pyyaml
GITHUB_TOKEN=$(gh auth token) python generate.py # all boards
GITHUB_TOKEN=$(gh auth token) python generate.py karta # just one
open dist/index.htmlThe workflow also runs on every push to main and can be triggered manually:
gh workflow run dashboard.yml -R danbar2/triage-boardsEach board publishes a data.json next to its page (e.g.
grove/data.json)
for building other views.