Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/commands/pm.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ When creating issues, set the project Status field to "Todo" unless told otherwi
Keep issue titles concise and actionable.
When showing the backlog, group by project Status and sort by priority.

**The board's Next — `task-monitor-queue`.** When you and the user agree on what comes next, record it in one call, in order: `task-monitor-queue set 267 266 --note 267="kiro hooks first"`. Refs are issue numbers. This queue is the "Next" section of the user's task-monitor board. `add <ref>…` appends, `rm <ref>…` drops, `clear` empties, and each answers with one line. That call is your whole board cost: do not read the queue back, re-run it only when the agreed order changes, and do not `rm` what you dispatch — `task-work` takes it off the queue itself.

**Prose you author never goes through a double-quoted shell string.** An issue body, a review comment, a merge note, a spec — anything you compose that carries backticked code — goes in on stdin: `gh` takes `--body-file -`, `radio send` takes its body on stdin whenever `--body` is omitted, and both read from a quote-delimited heredoc (`<<'BODY'` … `BODY`, never `<<BODY`). Inside a double-quoted string the shell command-substitutes every backticked token: `pwd -P` posts its own output, and a backticked identifier that names no command posts as **nothing at all** — the word is deleted with no sign it was ever there. Single-quoting is not the fix either; prose contains apostrophes, and the first one ends the string. Fixed literals like `--body "merged"` can stay as they are — the rule is about prose you write.

### Radio handoffs (canonical)
Expand Down
24 changes: 23 additions & 1 deletion .claude/gh-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,29 @@ session, and goes to a tab only while the tab at the recorded id still bears the
item's name, the check `task-done` makes before closing one (#260). It holds no
task-force logic: every value is a snapshot field. Build it from the task-force
checkout with `cargo build --release --manifest-path monitor/Cargo.toml`
(`install.sh` does not build it yet; #277).
(`install.sh` does not build it yet; #277). Its Next section is the queue the
PM writes with `task-monitor-queue`, below, else the tracker's backlog.

`task-monitor-queue` — the PM's short-term queue, the board's **Next** (#275)

The PM writes it when it and the user agree on what comes next. Each call
answers with one line, and the PM never reads it back: that is its whole board
cost. `task-work` takes the ref it dispatches off the queue itself, so nobody
has to `rm` a dispatched item.

```bash
task-monitor-queue set <ref>... [--note <ref>=<text>] # replace, in order
task-monitor-queue add <ref>... # append
task-monitor-queue rm <ref>... # drop
task-monitor-queue clear # empty
task-monitor-queue # show it
```

Refs are compared by their last path segment, ignoring a URL's query string
and fragment, so `267`, `#267` and an issue URL ending in `/issues/267` are one
item. `set` keeps the note of a ref that stays queued; `--note <ref>=` clears
it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a
note.

### PM ↔ worker messaging (radio)

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

Build it with `cargo build --release --manifest-path monitor/Cargo.toml` and run `monitor/target/release/task-monitor`, which finds the checkout's `bin/task-monitor-snapshot` (`$TASK_MONITOR_SNAPSHOT` overrides). `install.sh` does not build or link it yet (#277). **CI gains a Rust job** on ubuntu-latest and macos-latest, run from `monitor/` on stable: `cargo fmt --check`, `cargo build --locked`, `cargo test --locked` and `cargo clippy --all-targets --locked -- -D warnings`, cached with `Swatinem/rust-cache` (keyed on `monitor/Cargo.lock`). A second job builds and tests on Rust 1.88, the `rust-version` in `monitor/Cargo.toml` and ratatui 0.30's floor, so that pin stays true. Neither job is a required check on `main` yet; branch protection names only the four existing ones. Docs: a README section, and a `task-monitor` paragraph in `.claude/gh-workflow.md` and all seven steering templates. **Upgrading: re-run `task-init <loadout>`** only to refresh the workflow doc; nothing else installer-written changed.

- **`task-monitor-queue`: the PM's short-term queue, the board's "Next" (#275, epic #272 3/6).** A new canonical root binary the PM writes when it and the user agree on what comes next: `set <ref>... [--note <ref>=<text>]` replaces the queue in order, `add` appends, `rm` drops, `clear` empties, and no subcommand shows it. Each write answers with one line (`queue: 267 266 180`), so keeping the board costs the PM one short call and nothing read back. The file is `$TASK_FORCE_HOME/monitor/<repo>/queue`, the format `task-monitor-snapshot` already reads (one ref per line, optional tab-separated note), and this command now owns it. Refs are compared by their last path segment, ignoring leading `#`s and a URL's query string and fragment, so `267`, `#267`, `…/issues/267` and a Notion URL with or without `?pvs=4` are one item; `set` keeps the note of a ref that stays queued. A ref containing `=` cannot take a note, since `--note` splits on the first `=`. `task-work` now takes the ref it dispatches off the queue (best effort, silent), so a dispatched item never reappears as "next" once it lands and leaves In flight. Every PM prompt — the four claude `commands/pm.md`, this repo's own copy and the three kiro `agents/pm.json` — gains one paragraph saying when to call it and what not to spend on it. Not in v1: PM-written descriptions (`describe`).

New `tests/task_monitor_queue.bats` (24 tests, run under `/bin/bash`) covers each subcommand, key matching, note handling, the refusals, worktree and `--repo` resolution, a round trip through the snapshot, `task-work`'s drop, and the PM prompts; each assertion was mutation-checked. `tests/install_symlinks.bats` checks the new link. **Upgrading: re-run the loadout's `install.sh`** for the `~/.local/bin/task-monitor-queue` link (the shared install stanza creates it on all seven loadouts), **and re-run `task-init <loadout>`** to refresh the PM prompt (`commands/pm.md` / `agents/pm.json`) and the workflow doc, which now describes the command.

- **`task-monitor-snapshot`: the board as one JSON document — in flight, next, done, and whether a repo needs you (#273, epic #272 1/6).** A new canonical root binary, the data layer the task-monitor front end will render. It prints `"schema": 1` for the current repo, or with `--all` for every repo a radio session file names, and every value in it is read deterministically from radio's state, task-work's `.<slug>.info` sidecars, the tracker and the forge. No model is asked to write or summarise anything. The schema is documented in `docs/task-monitor-schema.md`, with an example taken from this repo.
- **In flight** is every worktree sidecar, joined to its radio session (`STATE=`, heartbeat), its PR (found by the `task/<slug>` head branch on every tracker) and a status word derived from the latest review intent about that PR. Reviewers attach to their PR's item rather than being items.
- **Titles and descriptions** come from three new tracker hooks with `_default.sh` bodies: `aw_tracker_item_describe` (gh: the issue's title and first paragraph; local: the task file; jira / notion: `ref · slug`, since the shell cannot read them), `aw_tracker_pr_item` (gh: `Closes #N`, else the head branch) and `aw_tracker_next_fallback` (gh: open issues on the configured project in none of the three configured statuses, leaving out drafts, archived items and issues with sub-issues, ordered by the project's `Priority` field when there is one; local: the `tasks/` backlog; none on jira / notion). The gh fallback is one paginated GraphQL query, because `gh project item-list` carries neither sub-issue counts nor issue state. Once a PR exists, its title and `## Summary` take over, falling back to the first paragraph after `Closes`. `tests/loadout_modules.bats` checks that all three resolve on every loadout.
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,26 @@ task-monitor-snapshot --local # disk only: no forge calls, merged with

**No model writes any of it.** Every value is read from radio's state, task-work's sidecars, the tracker or the forge. A gh or local item is described by its issue or task-file title and the first paragraph of its body; a jira or notion item is `PROJ-42 · <slug>` until its PR exists, because the shell cannot read those trackers. Once a PR exists, its title and `## Summary` take over on every tracker.

**In flight** is every worktree whose `.<slug>.info` sidecar exists — not every fresh heartbeat, so a resumed or roleless worker still shows — with its session state, its PR and a status word (`implementing`, `pr-open`, `under-review`, `changes-requested`, `re-review`, `waiting-merge`, `merged`) derived from the latest review intent. **Next** is the PM's queue, else the tracker's backlog. **Done** is the merged PRs that delivered an item in the last 24 hours, or the last 10, whichever is more; merged PRs are fetched once and cached for good.
**In flight** is every worktree whose `.<slug>.info` sidecar exists — not every fresh heartbeat, so a resumed or roleless worker still shows — with its session state, its PR and a status word (`implementing`, `pr-open`, `under-review`, `changes-requested`, `re-review`, `waiting-merge`, `merged`) derived from the latest review intent. **Next** is the PM's queue, written with [`task-monitor-queue`](#task-monitor-queue--the-pms-queue), else the tracker's backlog. **Done** is the merged PRs that delivered an item in the last 24 hours, or the last 10, whichever is more; merged PRs are fetched once and cached for good.

**Attention** fires on exactly three signals and nothing else: an unread PM inbox (`--also` aliases count toward the primary), a finished review on a still-open PR the PM has not answered, and a worker in `awaiting`. The third is claude-only for now: only the claude loadouts wire `radio awaiting`.

`--local` exists so a front end can refresh on every filesystem event without waiting on GitHub: it shadows `gh` with a function that fails, so every forge read falls back to the cache under `$TASK_FORCE_HOME/monitor/<repo>/`. The schema (`"schema": 1`) is documented in [`docs/task-monitor-schema.md`](docs/task-monitor-schema.md), with a full example beside it.

### `task-monitor-queue` — the PM's queue

**Next** is a short ordered list the PM writes when it and the user agree on what comes next (#275). Writing it is the PM's only board cost, so each call answers with one line and the PM never reads it back:

```bash
task-monitor-queue set 267 266 180 --note 267="kiro hooks first" # replace, in order
task-monitor-queue add 290 # append
task-monitor-queue rm 266 # drop
task-monitor-queue clear # empty
task-monitor-queue # show it
```

A ref is whatever the tracker uses: an issue number, a Jira key, a Notion page URL, a task file name. Refs are compared by their last path segment, ignoring a URL's query string and fragment, so `267`, `#267` and `…/issues/267` are one item, and so are a Notion page URL with and without `?pvs=4`. That is what lets `task-work` drop the ref it dispatches from the queue whatever form the PM queued it in, so a dispatched item never comes back as "next" after it lands. `set` keeps the note of a ref that stays queued; `--note <ref>=` clears it. `--note` splits on the first `=`, so a ref containing `=` cannot take a note, and `--` goes before a ref that starts with `-`. The file is `$TASK_FORCE_HOME/monitor/<repo>/queue`, one ref per line with an optional tab-separated note, and `task-monitor-queue` owns that format.

## `task-monitor` — the board, live

`task-monitor` is the terminal front end over that JSON (Rust + ratatui, in [`monitor/`](monitor/)). Inside a repo it opens on that repo's view; outside one, or with `--all`, on the overview of every repo radio knows about.
Expand All @@ -430,7 +444,7 @@ task-monitor-snapshot --local # disk only: no forge calls, merged with
↑↓ move ⏎ expand o open PR/issue t jump to tab a all repos q quit
```

- **Real time without waiting on GitHub.** A change under `$TASK_FORCE_HOME/radio` (sessions, mailboxes), `$TASK_FORCE_HOME/monitor` (the PM queue, the forge cache) or a repo's worktree base (a `.<slug>.info` sidecar appearing or going) triggers a `--local` snapshot, debounced. Forge state comes from a full snapshot every 45 seconds (`--poll SECS`, 30-600).
- **Real time without waiting on GitHub.** A change under `$TASK_FORCE_HOME/radio` (sessions, mailboxes), `$TASK_FORCE_HOME/monitor` (the PM queue that `task-monitor-queue` writes, the forge cache) or a repo's worktree base (a `.<slug>.info` sidecar appearing or going) triggers a `--local` snapshot, debounced. Forge state comes from a full snapshot every 45 seconds (`--poll SECS`, 30-600).
- **Never blank.** A snapshot that fails or times out leaves the last good one on screen; the header says `stale <age>` and the footer says why. `forge <age>` means disk state is current but PR state is from the last full run.
- **Keys.** `↑↓` move, `⏎` expand a done item to its full PR body (scrollable) or enter a repo from the overview, `o` open the PR or issue, `t` jump to the item's zellij tab (the worker's; the PM's for a queued or done item, or for an overview row), `a` toggle the overview, `q` quit.
- **`t` stays in this zellij session.** Each overview row names its repo's zellij session; `zellij action` cannot switch sessions, so a tab elsewhere is named, not attempted. Within the session, `t` goes to the recorded `TAB_ID` only while that tab still bears the item's name, the check `task-done` makes before it closes a tab (#260).
Expand Down
Loading
Loading