YUIBoard:本地只读的 AI 辅助开发进度看板
funnysubaru/yuiboard
YUIBoard 是一个本地只读的 AI 辅助开发进度看板,通过读取仓库中的 spec tasks.md、ADR、计划文档、GitHub PR 以及本地 Claude Code / Codex 会话日志,自动推断每个任务处于未开始、进行中、待评审、待合并还是已完成。
A local, read-only progress board for AI-assisted development.
YUIBoard reads the planning docs in your repository (spec tasks.md files, ADRs, plans, TODO lists), your GitHub pull requests, and your local Claude Code / Codex session logs. From these it works out where every task stands: not started, in progress, awaiting review, awaiting merge, or done. It shows the result in your browser as six views: an overview, a Gantt chart, a dependency graph, what to do next, what needs your attention, and diagnostics. It can also show a pane right inside Claude Code that follows the task you're working on (see Watch it inside Claude Code).
Why
When AI agents write most of the code, the hard part is keeping track of what has happened. One task is half-done in a session, another has a draft PR, and a third was merged but never checked off in the plan. YUIBoard puts that picture back together from material you already have:
- No new tracker. The task docs your AI already writes are the source of truth. There are no tickets to file and nothing to keep in sync by hand.
- Evidence over claims. A task can count as done only once it is checked in the doc, and a linked PR that is still open or merged but not yet pulled locally keeps it from being done. A checked task with no valid PR, or whose local sync can't be verified, still counts as done but is flagged "Checked in doc only" or "Local sync not verified". Every mismatch shows up under "Needs attention", and every status links back to the line, PR or session it came from.
- Sees AI work as it happens. A Claude Code or Codex session that is working on a task shows up on that task within seconds.
- Local and read-only. It never writes to your repository, your GitHub account or your session logs. It sends no telemetry and calls no AI APIs.
- Zero dependencies. It runs on Node 24's standard library and a native ES-module frontend, with no build step and no
npm install.
The UI is available in English, 中文 and 日本語. It follows your browser language, and you can switch from the top bar.
Quick start
Requirements: Node 24.x and git. To see pull requests you also need the GitHub CLI, signed in with gh auth login; read-only access is enough.
Try it first with a fictional project (made-up docs, pull requests and an AI session). No config or GitHub sign-in needed:
git clone https://github.com/funnysubaru/yuiboard.git
cd yuiboard
npm run demoThe demo runs on the first free port from 4318, writes only to your system's temp directory, and deletes its files when you press Ctrl+C.
To watch your own repository:
cp devboard.config.example.json devboard.config.json # point it at your repository (see Configuration) node src/server.mjs # or: npm start
Open the URL printed at startup (default http://127.0.0.1:4317/). To keep the config file elsewhere:
node src/server.mjs --config /path/to/devboard.config.json
- If the port is taken, the server exits with an error. A config mistake affects only the project it belongs to: that project shows an error page naming the exact field, and the other projects keep working.
- Server logs and
devboard-ctloutput are in English by default. SetDEVBOARD_LANG=zhorja(or run with a Chinese or JapaneseLANG) to switch. Whendevboard-ctl upcreates the LaunchAgent or systemd service, it saves the language of the terminal you ran it from asDEVBOARD_LANG, because services usually start withoutLANG. - Caches go to
cache/<project id>/and manual links todata/<project id>/; both are git-ignored. Deletingcache/only makes the next start slower. - Write your docs and PR titles the way CONVENTION.md describes. Most existing
tasks.mdfiles already work as they are.
Add a project with AI
Rather than editing the config by hand, you can ask your AI agent: "add this project to devboard". The bundled skill skills/devboard-add-project finds the spec tasks.md files, ADRs, plans and TODO docs, confirms the list with you, writes the config through ops/devboard-ctl.mjs, and restarts the service. Saying "remove this project from devboard" removes it.
To install the skill, run this in the YUIBoard directory. It must be a symlink, because the skill uses it to find its way back to this directory.
mkdir -p ~/.claude/skills && ln -s "$PWD/skills/devboard-add-project" ~/.claude/skills/devboard-add-project mkdir -p ~/.agents/skills && ln -s "$PWD/skills/devboard-add-project" ~/.agents/skills/devboard-add-project
The AI changes devboard.config.json only through devboard-ctl save / remove, which back up the file to devboard.config.json.bak first. Repositories you add are only ever read.
Watch it inside Claude Code (mod)
This repository is also a Claude Code plugin. It opens a YUIBoard pane next to the conversation, in both the terminal and the desktop app. The pane shows the project the session belongs to: the overall numbers, a card with the task this session is working on, the task tree, Needs attention and Diagnostics. It reads from your running YUIBoard service and changes nothing. Tested with Claude Code 2.1.293–2.1.296.
Both screenshots show the fictional demo project. The session asked about "sync-engine task 2.3", so the card at the top shows that task and why it has its status, and the tree opens to it.
Install it in a Claude Code session:
/plugin marketplace add funnysubaru/yuiboard
/plugin install yuiboard@yuiboard
Or from a shell in one step (Claude Code 2.1.275 or later):
claude plugin install yuiboard --marketplace funnysubaru/yuiboard
The plugin and your YUIBoard service must be the same version. When you update the service (git pull, then restart it), update the plugin too: claude plugin update yuiboard@yuiboard.
To try changes from a checkout instead, load the plugin straight from it (the repository root is the plugin): claude --plugin-dir /path/to/yuiboard for one terminal session, or add "CLAUDE_CODE_PLUGIN_DIRS": "/path/to/yuiboard" to env in ~/.claude/settings.json and start a new session. Loaded this way, the plugin's id is yuiboard@inline instead of yuiboard@yuiboard.
Options:
| Option | Default | Meaning |
|---|---|---|
url |
http://127.0.0.1:4317 |
Address of the YUIBoard service. It must run the same version as the plugin. |
lang |
auto |
Pane language: auto (follows Claude Code), zh, ja, en |
echo '{"url":"http://127.0.0.1:4317"}' | claude plugin configure yuiboard@yuiboard --values-stdin
- The pane opens by itself once per session, as soon as the session can be matched to a project: the session is linked to one of its tasks, or its working directory is inside a watched workspace. After you close it, it stays closed. Type
/yuiboardto open it again. - The card shows the tasks the session is linked to. Name the task the way you would in a PR title, such as "
<doc>task 3.2"; a bare number isn't enough. When several tasks are active, the one with the latest activity comes first. Subagents count separately, so each dispatched task stays "running" while its own subagent works. - In the terminal, use the keyboard: press Ctrl+X then Tab to enter the pane, Tab or the arrow keys to move, Enter to press, and Esc to go back to the prompt. In fullscreen mode with at least 110 columns, the pane docks on the right and takes mouse clicks. Task numbers and "Open in browser" open the web page. Terminals without OSC 8 links print the address after the label.
- Option changes reach running sessions. Changes to the plugin's files don't; start a new session.
- The plugin also brings the skill
skills/devboard-add-project. An installed plugin runs from a copy, so this copy of the skill asks where your YUIBoard checkout is before it writes anything. If you also symlinked the skill as described above, Claude Code lists it twice; either one works. The~/.agents/skillslink for Codex is not affected.
Run as a service (macOS / Linux)
Run devboard-ctl to install the service or restart it. You don't need to edit any plist or unit file by hand.
node ops/devboard-ctl.mjs up # macOS: restart the LaunchAgent for this directory, or create one from ops/devboard.plist.template # Linux: same, with a systemd user service (ops/devboard.service.template) node ops/devboard-ctl.mjs up --no-service # just start a background process (PID in cache/devboard.pid) node ops/devboard-ctl.mjs check # validate the config only node ops/devboard-ctl.mjs find <repo path> # find the project registered for a repository (read-only) node ops/devboard-ctl.mjs save --new < project.json # add a project; refused if the id or repository is already registered node ops/devboard-ctl.mjs save --update <id> < patch.json # patch only name / docSources / repo.github node ops/devboard-ctl.mjs remove <id> # remove a project (cache/<id> and data/<id> are kept) node ops/devboard-ctl.mjs revert <revision> # undo the save / remove that returned this revision
The service is reachable at http://devboard.localhost:4317/ as well as 127.0.0.1. It still listens only on the loopback address. Browsers that support .localhost resolve the name to your machine, so you don't need to edit hosts files.
Details: up, save / remove, launchd, systemd, logs
-
upfirst checks whether the configured port is served by a devboard this directory manages. If it is,uprestarts it. If another program holds the port,upmoves to the next free port and writes it back toserver.port. If the devboard on the port was started by hand,upreports its PID and leaves it alone.upprints one line of JSON; onok:falseit exits with code 1 and gives a machine-readablereason(unmanaged,bad-port,not-ready, …). -
save/removetake a lock (devboard.config.json.lock; they wait up to 10 s, then reportconfig-busy, orstale-lockif the lock was left by a process that has exited). They copy the config todevboard.config.json.bak, write atomically, and runcheck. If the global section or the touched project fails validation, they restore the original file (reason: "check-failed"). Errors that other projects already had don't block the save. On success they return arevision; to undo, userevert <revision>rather than copying.bakback. They don't restart the service, so runupafterwards. -
macOS: the LaunchAgent label is
com.devboard.agent(with a hash suffix if another directory already uses it). The plist goes to~/Library/LaunchAgents/, and the service runs on the Node binary that ranup. It starts at login and launchd restarts it if it exits. Logs:~/Library/Logs/devboard.loganddevboard.error.log. To manage it by hand:launchctl print gui/$(id -u)/<label> # status launchctl kickstart -k gui/$(id -u)/<label> # restart after changing the backend launchctl bootout gui/$(id -u)/<label> # stop; delete ~/Library/LaunchAgents/<label>.plist to disable autostart
Logs are not rotated automatically. To rotate them with macOS's
newsyslog(each log rotates at 10 MiB and 3 archives are kept), run this from the YUIBoard directory:sed "s|/Users/YOU|$HOME|" ops/devboard.newsyslog.conf > cache/devboard.newsyslog.conf launchctl bootout gui/$(id -u)/<label> && /usr/sbin/newsyslog -r -f "$PWD/cache/devboard.newsyslog.conf" launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/<label>.plist # run this even if rotation failed
Don't just rename a log while the service is running, because the process keeps writing to the old file handle.
-
Linux: the unit is
devboard.service(with a hash suffix if taken). It starts at login and restarts 10 s after it exits, and it appends logs tocache/devboard.log. To run it without logging in, useloginctl enable-linger. Where no systemd user instance is available (containers, some WSL setups),upfalls back to a background process and says so inwarnings.systemctl --user status devboard.service systemctl --user restart devboard.service systemctl --user disable --now devboard.service # stop and disable autostart -
The service runs the code currently in this directory. After you switch branches or change
src/, restart it. If you change onlyweb/, a hard refresh in the browser is enough. -
To update to a new release: run
git pullin this directory, restart the service withnode ops/devboard-ctl.mjs up(orlaunchctl kickstart -k/systemctl --user restartas above), then, if you use the Claude Code pane, runclaude plugin update yuiboard@yuiboardso the plugin matches the service.
Configuration
Copy devboard.config.example.json to devboard.config.json. Relative paths are resolved against the config file's directory, and ~ expands to your home directory.
{
"server": { "host": "127.0.0.1", "port": 4317 },
"defaults": { "hoursPerDay": 8, "defaultDurationDays": 1, "sessionRunningMinutes": 10, "sessionStaleDays": 3,
"githubRefreshMinutes": 5, "recentClosedPrDays": 14, "recentChangesDays": 7 },
"projects": [{
"id": "my-app", "name": "My App",
"repo": { "path": "~/Projects/my-app", "github": "acme/my-app" },
"docSources": [
{ "type": "spec-dir", "label": "Claude Code spec", "path": ".claude/specs" },
{ "type": "adr-dir", "label": "ADR", "path": "docs/adr" },
{ "type": "task-list", "label": "Task list", "files": [] }
],
"archived": [], "excluded": [],
"branchLines": [
{ "id": "main", "name": "Main", "base": "main", "docFilter": "^(?!v2-)" },
{ "id": "v2", "name": "v2", "base": "release/v2",
"titlePrefix": "[V2]", "labels": ["v2"], "docFilter": "^v2-" }
],
"github": {},
"sessions": [{ "source": "claude-code" }, { "source": "codex" }]
}]
}| Field | Meaning |
|---|---|
server.host / port |
Listens on loopback by default. A non-loopback host triggers a warning in the log and on the page. |
defaults |
Working hours per day; default task duration in days; minutes of inactivity before a session stops counting as "running"; days before it counts as "stalled"; GitHub refresh interval in minutes (minimum 2); and how many days back to list unlinked PRs and recent changes. |
projects[].id |
Letters, digits, - and _ only. It is used in cache paths and page URLs. |
repo.path / repo.github |
The local working copy (must be a git repository) and the GitHub owner/name. repo.github is optional; without it, status comes from docs and AI sessions only. |
docSources |
Where the docs live. spec-dir: one spec per subdirectory; subdirectories without tasks.md are listed as "Planning". adr-dir: an ADR directory. md-dir: a plan or TODO directory, where every .md at that level is one doc and docs without task lines are "Planning". task-list: individual task-list files. |
archived / excluded |
docIds to archive (hidden by default, can be filtered in) or to exclude entirely. * and ? wildcards are supported. |
milestoneCodePattern / prerequisiteIdPattern |
Optional regexes for milestone codes and prerequisite IDs. By default Milestone M1 / 里程碑 M1 / マイルストーン M1 and P1 are recognized. |
branchLines |
Release lines. base is the target branch. PRs are assigned to a line by titlePrefix / labels / base branch, and docs by docFilter, a regex matched against the docId. Add workspace to read a separate checkout. Alternatively, use docRef (e.g. origin/next/v3) plus docFiles to read specific files from that ref of the main repository without a separate checkout. Those docs get an <line id>: prefix, so set this line's docFilter to ^<id>: and exclude that prefix from the other lines. YUIBoard never fetches, so the content is whatever your last fetch brought in. |
github.ghPath |
Optional path to the gh executable, if it isn't on PATH. |
sessions |
AI session sources. Each source is scanned on its own and degrades on its own. claude-code reads projects/ under $CLAUDE_CONFIG_DIR or ~/.claude; codex reads sessions/ under $CODEX_HOME or ~/.codex. Override either with dir. extraCwds also counts sessions started in other directories (such as the repository's parent) toward this project. A Codex session belongs to a project only if it started there, and sub-agents are merged into their parent session. |
Docs, sessions and manual links refresh every 5 seconds; GitHub every githubRefreshMinutes. Pages update live over SSE.
Read-only guarantees
- Repositories: YUIBoard runs only allow-listed read-only git commands (
rev-parse,merge-base --is-ancestor,for-each-ref,log,worktree list) withGIT_OPTIONAL_LOCKS=0, ignoring any inheritedGIT_*variables such asGIT_DIR. It never fetches or checks out, never writes.gitor the index, never competes with you or your AI for git locks, and never changes any file in the repository. - GitHub: YUIBoard only reads, through
gh. Your token stays withghand never appears in the config, caches, logs or pages. If the token has more than read access, the page tells you. - AI session logs: files are opened read-only and read up to the last complete line, and nothing is created in session directories. Caches and pages keep only metadata and short excerpts, with anything that looks like a key or token masked. Full conversations are never stored or uploaded.
- The server itself writes only to
cache/anddata/in its own directory. Pages accept only same-origin requests, and the write endpoints (manual links, refresh now) checkOriginandContent-Type. There is no telemetry. test/readonly.test.mjsruns a full refresh against a temporary repository and session directory. It checks thatgit status,.gitand the session files (including their mtimes) are unchanged afterwards.
Platforms
- Tested: macOS 15 with Node 24. The full test suite also runs in CI on Ubuntu and macOS.
- Linux: the systemd user service is tested only against a stand-in, not yet on a real machine. Directories whose file names are NFD-normalized may fail to open from "View source".
- Windows: handled by design but untested. Known risks are the encoding of Claude Code session directory names, finding
gh.exe(setgithub.ghPathif needed), shared reads of session files that are being written, andrenamefailing withEPERMwhile antivirus or indexing holds the cache file. - Everywhere: paths are normalized to NFC and shown with
/, CRLF and LF are both accepted, and git / gh are called throughexecFile, never a shell.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md for the dev setup, tests and the hard constraints (zero dependencies, read-only).
License
Apache License 2.0. Copyright 2026 JimmyFu.
来源:GitHub · github.com



