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
24 changes: 24 additions & 0 deletions .claude/gh-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,30 @@ gh run list -c "$(git rev-parse HEAD)" --limit 1 --json databaseId --jq 'length'
# 0 => no run for this commit; "green" is not a claim you can make
```

`task-monitor-snapshot` — the board as JSON: in flight, next, done, and whether this repo needs you (#273)

It prints one JSON document (`"schema": 1`, documented in task-force's
`docs/task-monitor-schema.md`) and is the data layer of task-monitor. Every
value comes from disk, the tracker or the forge, and no model is asked to write
or summarise any of it.

- **In flight** is every worktree whose `.<slug>.info` sidecar exists, joined to
its radio session, its PR and the latest review intent.
- **Next** is the PM's queue, else the tracker's backlog (gh project items in
none of the three statuses, or the local `tasks/` files). There is none on
jira or notion.
- **Done** is the merged PRs that delivered an item in the last 24 hours, or
the last 10, whichever is more.
- **Attention** fires on exactly three signals: an unread PM inbox, a finished
review on a still-open PR that the PM has not answered, and a worker in
`awaiting`. The `awaiting` signal is claude-only for now.

```bash
task-monitor-snapshot --json # this repo
task-monitor-snapshot --all # every repo radio knows about
task-monitor-snapshot --local # no forge calls: disk + the last full run's cache
```

### PM ↔ worker messaging (radio)

Radio is the **canonical** coordination channel between the PM and workers — every
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- **`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.
- **Next** reads the PM queue file. Its writer is sub-issue 3, so the read uses the format that sub-issue is expected to keep.
- **Done** is the merged PRs that deliver an item over the last 24 hours, or the last 10, whichever is more. Each merged PR is fetched once and cached under `$TASK_FORCE_HOME/monitor/<repo>/prs/`.
- **Attention** has exactly three signals: an unread PM inbox, where `--also` aliases count toward the primary; a `review-complete-*` on a PR the forge lists as open, with no later `changes-requested` / `approved-and-merged`, rebuilt from message frontmatter in every mailbox; and a worker in `awaiting` whose heartbeat is younger than `radio orphans`' one hour, so a crashed tab cannot keep a repo lit. The third is claude-only for now. Messages from the same second are ordered by the radio log's `send` lines: a radio id cannot break that tie, because after the timestamp it holds the sender and a random suffix.
- **`--local`** makes no forge call at all. It shadows `gh` with a failing function and merges with the last full run's cache, so a filesystem-triggered refresh never waits on GitHub. A full run whose forge call fails keeps the previous result and marks it `stale`. A repo whose forge has never answered, such as one with no GitHub remote, reports `forge.available: false` instead of a permanent `stale`.

It runs on `/bin/bash` 3.2, and its bodies go through awk and jq, never a large-string pattern substitution (#266). Repos in `--all` run in parallel. Measured on this machine's four repos: a full `--all` takes about 10s, mostly `gh`, and a single-repo `--local` 0.65s. Two small extractions came with it. `aw_task_fm`, the task-file frontmatter reader, moved from `bin/task-board` to `lib/task-file.sh` so the local tracker module can share it. `aw_main_repo_root` now carries the git-common-dir logic in `lib/repo-name.sh`, and `aw_main_repo_name` calls it.

New `tests/task_monitor_snapshot.bats` (45 tests, all run under `/bin/bash`) covers each source, each tracker's titles, each status, each signal and its negatives, `--local`, the forge-failure path, `--all` discovery and the text extraction. Each assertion was mutation-checked. **Upgrading: no `task-init` re-run needed for the command itself.** Re-run the loadout's `install.sh` to get the `~/.local/bin/task-monitor-snapshot` link, which the shared install stanza now creates on all seven loadouts. Re-run `task-init <loadout>` to refresh the workflow doc, which now describes the command.

- **Radio auto-submit is its own flag on claude, no longer a side effect of `--permission-mode auto` (#246).** `task-work --auto` set both, so a worker could not get its mail submitted without also auto-accepting edits. The reverse was worse: a worker kept off `--auto` because its task is destructive (a release cut that tags and publishes) lost auto-submit as a side effect, so the worker you were supervising most carefully was the one whose handoffs stalled. The coupling stays out of sight while workers are busy, because a busy recipient's mail is drained by the Stop hook with no keystroke. It shows up the moment they go idle waiting on a review, which is exactly the handoff that matters.

**The split, matching kiro (#206) and `task-pm` (#189):** every `task-work` copy now takes `--auto-submit` and `--no-auto-submit`. `--auto` keeps its meaning and becomes shorthand for auto permission mode plus `--auto-submit`, so existing invocations and docs behave as before. `--no-auto-submit` wins over `--auto` in either order, and `--auto-submit` composes with `--plan`. The flag arms live in a new `auto-flags` drift region shared by all seven copies, and the resolution sits in the existing `radio-env-injection` region, so the claude and kiro copies cannot drift apart again. On `task-reviewer`, `--no-auto` now drops **only** the permission mode: auto-submit stays on by default, with `--no-auto-submit` as its own opt-out. `task-recreate-worker` gains the same pair.
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,26 @@ It opens a fresh tab on the **existing** worktree and launches the agent with th

---

## `task-monitor-snapshot` — the board as JSON

With task-force a ticket takes minutes, and a session runs ten or more of them, all referred to by number. The PM and the workers keep up; the human driving the PM loses track of what `#262` was. `task-monitor-snapshot` is the data layer of task-monitor (epic #272): it prints one JSON document saying what is **in flight**, what is **next** and what is **done** in a repo, each as a title plus a plain one-paragraph description, and whether the repo **needs you**.

```bash
task-monitor-snapshot --json # this repo
task-monitor-snapshot --all # every repo radio knows about, one entry each
task-monitor-snapshot --local # disk only: no forge calls, merged with the last full run
```

**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.

**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.

---

## Per-combo setup

Pick the section that matches what you installed.
Expand Down
33 changes: 9 additions & 24 deletions bin/task-board
Original file line number Diff line number Diff line change
Expand Up @@ -148,25 +148,10 @@ is_in_flight() {
[[ -n "$IN_FLIGHT_SLUGS" ]] && printf '%s' "$IN_FLIGHT_SLUGS" | grep -qxF "$1"
}

# Read a single field from YAML frontmatter (very small grammar — keys are
# simple lowercase, values are scalar; strips surrounding quotes).
# Usage: read_fm <file> <key>
read_fm() {
local file="$1" key="$2"
awk -v key="$key" '
/^---[[:space:]]*$/ {
if (in_fm) { exit }
in_fm = 1; next
}
in_fm && $0 ~ "^" key "[[:space:]]*:" {
sub("^" key "[[:space:]]*:[[:space:]]*", "")
gsub(/^"|"$/, "")
gsub(/^'\''|'\''$/, "")
print
exit
}
' "$file"
}
# The frontmatter reader is shared with lib/trackers/local.sh's task-monitor
# hooks (#273), so it lives in a lib rather than here.
# shellcheck source=lib/task-file.sh
source "$AW_ROOT/lib/task-file.sh"

# Collect tasks into three temp files; each line is "<sort_id>\t<display>".
# We use temp files (instead of `declare -n` namerefs) because bash 3.2 has
Expand All @@ -186,11 +171,11 @@ for tf in "$TASKS_DIR"/*.md; do
# Skip the board itself and the README
[[ "$base" == "_board.md" || "$base" == "README.md" ]] && continue

fm_id=$(read_fm "$tf" "id")
fm_title=$(read_fm "$tf" "title")
fm_status=$(read_fm "$tf" "status")
fm_priority=$(read_fm "$tf" "priority")
fm_pr=$(read_fm "$tf" "pr")
fm_id=$(aw_task_fm "$tf" "id")
fm_title=$(aw_task_fm "$tf" "title")
fm_status=$(aw_task_fm "$tf" "status")
fm_priority=$(aw_task_fm "$tf" "priority")
fm_pr=$(aw_task_fm "$tf" "pr")

# Derive slug from filename: strip leading NNN- and trailing .md
slug="${base%.md}"
Expand Down
Loading
Loading