Skip to main content
Glama
README.md
# aiboard

A Jira/Trello-style issue tracker where **the filesystem is the database**.
Built for AI agents, readable by humans.

- Every **task** is a folder holding `brief.md` (what to do), `worklog.md` (what was done) and, once someone talks about it, `comments.md` (the discussion).
- Every **sprint** is a folder holding `sprint.md` (goal, dates, attached tasks).
- **Status is location.** A task in `tasks/done/` is done. Changing status moves the folder.
- No server state, no database. Agents can work with plain file operations; the CLI and web board are conveniences that read the same folders.

```
<board root>/
  tasks/
    backlog/
    in-progress/
      T-002-build-cli/
        brief.md
        worklog.md
    done/
    cancelled/
  sprints/
    backlog/
    in-progress/
      S-001-sprint-1/
        sprint.md
    done/
    cancelled/
```

## Installation

Requires Python 3.9+ and nothing else.

```bash
# recommended: an isolated install that puts `aiboard` on your PATH
pipx install git+https://github.com/flashist/aiboard.git     # or: pipx install /path/to/aiboard

# or plain pip
pip install git+https://github.com/flashist/aiboard.git      # or: pip install /path/to/aiboard

# or zero-install from a checkout
git clone https://github.com/flashist/aiboard.git ~/aiboard
ln -s ~/aiboard/bin/aiboard ~/.local/bin/aiboard          # any directory on your PATH
```

`aiboard --version` confirms it works. `python3 -m aiboard` is always available
as a fallback when running from the source tree.

## Quick start

```bash
cd my-product
aiboard init board --name "My product"   # creates board/ and a pointer file aiboard.json
aiboard agents-md --claude               # adds instructions to AGENTS.md and CLAUDE.md

aiboard sprint new "Sprint 1" --goal "Ship v1" --start 2026-09-14 --end 2026-09-25 --status in-progress
aiboard task new "Write the spec" --sprint S-001 --priority high --assignee claude
aiboard task start T-001 --as claude     # atomic claim + in-progress
aiboard task log  T-001 "Drafted section 1, open question about X" --by claude
aiboard task change-status T-001 done --note "Merged in PR #12"
aiboard sprint show S-001                # progress + task list
aiboard board                            # kanban in the terminal
aiboard serve                            # web board at http://127.0.0.1:8484
```

`aiboard init` with no directory creates the board in the current directory
instead. Either way the CLI finds the board from anywhere inside the project,
like git finds its repository: it walks up looking for `aiboard.json` (a board
or a pointer `{"board": "board"}`) or a `tasks/` + `sprints/` pair. `--root PATH`
or `AIBOARD_ROOT` override discovery.

Every command accepts `--json` for machine-readable output. With `--json`, every
failure, including bad arguments, prints `{"error": "..."}` to stdout and exits 1.
`task list` returns metadata only; `task show` adds the brief and worklog.
`AIBOARD_AUTHOR` sets the default author for worklog entries.

## Using it from AI agents

aiboard is not tied to any model or vendor. There are three ways in, from
simplest to most integrated:

**1. Instructions file.** `aiboard agents-md` inserts a marked, idempotent
block into `AGENTS.md` (read by Codex, Cursor, Copilot, Gemini CLI and
others); `--claude` also writes it into `CLAUDE.md` for Claude Code. The block
explains the folder layout and the CLI loop. Re-run it after upgrading aiboard.

**2. CLI with `--json`.** Any agent that can run shell commands can drive the
board. Set `AIBOARD_AUTHOR` to the agent's name so worklogs show who did what.

**3. MCP server.** `aiboard mcp` speaks the Model Context Protocol over stdio,
so agents call typed tools (`list_tasks`, `create_task`, `change_task_status`,
`log_work`, `get_sprint`, ...) instead of shelling out. Zero dependencies.

```bash
# Claude Code (project scope, from inside the project)
claude mcp add aiboard -- aiboard mcp --author claude

# Codex CLI: ~/.codex/config.toml
[mcp_servers.aiboard]
command = "aiboard"
args = ["mcp", "--author", "codex"]

# Cursor / Windsurf / generic: .cursor/mcp.json or equivalent
{ "mcpServers": { "aiboard": { "command": "aiboard", "args": ["mcp", "--author", "cursor"] } } }
```

The server resolves the board from its working directory (or `--root`), and
reports its name and location in the MCP `instructions` field on connect.
Verified end to end with Claude Code and Codex CLI; note that Codex asks for
approval before MCP tool calls unless its approval policy allows them.

## Commands

| Command | What it does |
| --- | --- |
| `aiboard init [DIR] [--name N]` | Create a board (status folders + `aiboard.json`); with DIR, also a pointer file in the current directory |
| `aiboard agents-md [--claude] [--print]` | Insert/refresh the agent instructions block in `AGENTS.md` (and `CLAUDE.md`) |
| `aiboard mcp [--author A]` | MCP server over stdio |
| `aiboard task new TITLE [--body ...] [--sprint S-001] [--priority low\|medium\|high\|critical] [--assignee X] [--label L]` | Create a task in `backlog` |
| `aiboard task list [--status S] [--sprint S-001] [--assignee X] [--unblocked\|--blocked\|--stale]` | List tasks in queue order, top first |
| `aiboard task rank T-001 (--top\|--bottom\|--before T-002\|--after T-002)` | Reorder the queue (Jira: Rank) |
| `aiboard task show T-001` | Brief + worklog |
| `aiboard task change-status T-001 STATUS [--note ...] [--force]` | Move the folder, append a worklog entry; refuses to start a blocked task unless forced |
| `aiboard task block T-002 T-001` / `unblock ...` | Record that T-002 is blocked by T-001 (Jira: "is blocked by"); logged in the worklog |
| `aiboard task start T-001 [--as NAME]` | Claim a backlog task and move it to in-progress in one atomic step (Jira: Start progress); fails if someone else holds it |
| `aiboard task assign T-001 NAME [--force]` | Set (or, with no name, clear) the assignee; refuses to take a task from someone else unless forced |
| `aiboard task log T-001 "text"` | Append a worklog entry (`-` reads stdin) |
| `aiboard task comment T-001 "text"` | Add to the task's discussion in `comments.md`: questions for humans, review notes, answers |
| `aiboard task edit T-001 [--title] [--priority] [--assignee] [--sprint] [--label]` | Change metadata; keeps sprint files in sync |
| `aiboard sprint new TITLE [--goal ...] [--start D] [--end D]` | Create a sprint in `backlog` |
| `aiboard sprint list` / `show S-001` / `move S-001 STATUS` | Inspect and move sprints |
| `aiboard sprint add S-001 T-001 T-002` / `remove ...` | Attach or detach tasks |
| `aiboard sprint refresh S-001` | Re-render the `## Tasks` checklist in `sprint.md` after hand edits |
| `aiboard board [--sprint S-001]` | Terminal kanban |
| `aiboard info` | Which board the CLI resolves to from here, with a summary |
| `aiboard check [--fix]` | Report inconsistencies (exit 1 if any); `--fix` repairs stale sprint checklists |
| `aiboard serve [--port 8484]` | Web board (auto-refreshes) |

IDs are forgiving: `T-001`, `T-1`, `1`, or the full folder name all work.
Statuses accept aliases such as `todo`, `wip`, `in progress`, `closed`, `canceled`.
`change-status` may be abbreviated to `status` or `move`.

## File formats

### `tasks/<status>/T-001-<slug>/brief.md`

```markdown
---
id: T-001
title: Write the spec
sprint: S-001          # or empty
priority: high         # low | medium | high | critical
rank: 3000             # place in the queue; lower is nearer the top
assignee: claude
labels:
  - docs
blocked_by:            # tasks that must be done or cancelled first
  - T-003
created: "2026-09-12T19:29:51Z"
updated: "2026-09-12T19:31:02Z"
---

# Write the spec

Free-form Markdown. Acceptance criteria, links, context, whatever the
worker needs. Agents may edit this file directly.
```

**Priority** is `low`, `medium` (the default), `high` or `critical`. Use
`critical` for drop-everything work that arrives suddenly and needs attention
now, such as production being broken; `urgent` is accepted as another name
for it. Critical cards are highlighted on the web board.

**Order.** Every task has a place in one queue, stored as `rank` in its front
matter (lower is nearer the top). Board columns, `task list`, sprint views and
the MCP `list_tasks` tool all sort by it, and agents are told to take the first
task in the list. New tasks join the bottom; `critical` tasks go to the top when
they are created or raised to critical, and can still be moved after that.
Reorder with `aiboard task rank`, the `rank_task` MCP tool, or by dragging cards
on the web board. Moving a task usually rewrites only that task's `brief.md`;
ranks are spaced 1000 apart and respaced only when a gap runs out. Tasks from
before ranking existed have no `rank` and keep their id order.

A task is **blocked** while any id in `blocked_by` is still in `backlog` or
`in-progress`. `task list --unblocked` (or the MCP `list_tasks` with
`unblocked: true`) returns only work that can start now, and `change-status
... in-progress` refuses a blocked task unless `--force` is given. `check`
reports missing blockers and dependency cycles.

The front matter is a deliberately small YAML subset: scalars, `key: [a, b]`
inline lists, and `- item` block lists. `status` is **not** stored here; the
folder is the single source of truth.

### `tasks/<status>/T-001-<slug>/worklog.md`

Append-only. One `##` heading per entry: ISO timestamp, an em dash, author.

```markdown
# Worklog

## 2026-09-12T19:29:51Z — claude

Status changed `backlog` → `in-progress`.

## 2026-09-12T19:40:10Z — claude

Implemented the parser. Tests green. Blocked on: decision about sprint refs.
```

### `tasks/<status>/T-001-<slug>/comments.md`

Same entry format as the worklog, but a different purpose: the worklog is the
worker's own record, the comments are the conversation *about* the task. A
reviewer's note, an agent's question for the product owner, and the answer
all go here. The web board shows a comment count on each card, and agents
are told to check comments on their task before finishing.

### Stale tasks

A task that is in progress with no worklog entry or comment for longer than
`stale_after_hours` (default 24, set in `aiboard.json`) is **stale**: it
shows up in `aiboard check`, `task list --stale`, and as a chip on the board.
This is how an abandoned task, for example one whose agent crashed, becomes
visible.

### `sprints/<status>/S-001-<slug>/sprint.md`

```markdown
---
id: S-001
title: Sprint 1
goal: Ship the prototype
start: 2026-09-14
end: 2026-09-25
tasks:
  - T-001
  - T-002
created: "…"
updated: "…"
---

# Sprint 1

**Goal:** Ship the prototype

## Tasks

- [x] T-001 — Write the spec (done)
- [ ] T-002 — Build CLI (backlog)
```

The `tasks:` list in the front matter is the membership record. The
`## Tasks` section is a human-readable rendering that the CLI regenerates
whenever membership or a member's status changes. A task also carries a
`sprint:` field pointing back; `aiboard check` reports any mismatch.

## Working without the CLI

Everything the CLI does can be done by hand, which is the point:

- **Create a task:** make `tasks/backlog/T-00N-my-slug/brief.md` with the front matter above. `worklog.md` is optional but recommended.
- **Change status:** `mv tasks/backlog/T-00N-* tasks/in-progress/` and append a line to the worklog.
- **Attach to a sprint:** add the id under `tasks:` in `sprint.md` and set `sprint:` in the brief.

Ids are allocated as *highest existing number + 1* across all status
folders, so a hand-made `T-100` makes the next CLI task `T-101`. Pick the next
free number when creating by hand. After moving folders by hand, run
`aiboard sprint refresh S-00N` (or `aiboard check --fix`) so the checklist in
`sprint.md` matches reality.

Run `aiboard check` afterwards to confirm the board is consistent. See
`AGENTS.md` for the recommended agent workflow.

## Web board

`aiboard serve` runs a small local server with no state of its own. Every
request re-reads the folders, so the page is always in sync with what agents
write, and it refreshes itself every few seconds.

For humans it is a Trello-style board: drag cards up and down to reorder the
queue, or into another column to change status (a line shows where the card
will land). Open a card to change sprint, priority or assignee, move it to
the top or bottom of the queue, add worklog
entries, and create tasks and sprints from forms. Type your name in the header
once; it is remembered in the browser and used as the author of everything you
do. `aiboard serve --read-only` turns editing off for display-only screens.

Writes go through the same store code as the CLI and the MCP server, so the
files stay consistent no matter who edits.

API: `GET /api/board`, `GET /api/tasks/T-001`, `GET /api/sprints/S-001`,
`GET /api/check`; `POST /api/tasks`, `POST /api/tasks/T-001/{status,assign,log,edit}`,
`POST /api/sprints`, `POST /api/sprints/S-001/status`, `POST /api/check/fix`.
POST bodies are JSON and may carry an `author`.

## Development

```bash
python3 -m unittest discover -s tests -v
```

## Design decisions

- **Folder = status.** No status field to drift out of sync. `ls tasks/in-progress` is the query.
- **Zero dependencies.** Agents can shell out to `python3 -m aiboard` anywhere Python exists.
- **Stable ids with a slug suffix.** `T-001-fix-login` sorts, is unique, and is readable. References use the id only, so renaming a task does not break sprints.
- **One store, three doors.** CLI, MCP server and web board all call the same store module, so every writer keeps the files consistent the same way.
- **Safe for parallel agents.** Every write takes an advisory lock on `.aiboard.lock` in the board root, so two agents creating tasks at once never get the same id and folder moves never interleave with sprint-file rewrites. `task start` claims work atomically, so two agents can never both believe they own a task.
- **Git-friendly.** Everything is text; the whole board can live in the repo it tracks, and history comes for free.

Maintenance

ActivityMaintained
ResponsivenessNo issues