From 0d0b28f7be40589ca1a0302f7015c8347ff86814 Mon Sep 17 00:00:00 2001 From: Martin Contreras Date: Tue, 20 Oct 2026 15:25:10 -0300 Subject: [PATCH 1/3] =?UTF-8?q?task-monitor=203/6:=20PM=20short-term=20que?= =?UTF-8?q?ue=20(task-monitor=20next=20=E2=80=A6):=20add=20task-monitor-qu?= =?UTF-8?q?eue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New canonical root binary `bin/task-monitor-queue` (named by the PM: the user-facing `task-monitor` is the #276 front end). The PM writes the board's Next section with `set ... [--note =]`, `add`, `rm`, `clear`, or no subcommand to show it. Each write answers with one line, so the PM's board cost is one short call. It owns the queue file format that `task-monitor-snapshot` already reads: `$TASK_FORCE_HOME/monitor//queue`, one ref per line with an optional tab-separated note. Refs are compared by their last path segment, so `267`, `#267` and an issue URL are one item. `task-work` uses that to drop the ref it dispatches, best effort, so a dispatched item never returns as next once it leaves In flight. Every PM prompt (four claude `commands/pm.md`, this repo's copy, three kiro `agents/pm.json`) gains one paragraph on when to call it. All seven installers link it; README, workflow templates, schema doc and CHANGELOG updated. New tests/task_monitor_queue.bats (21 tests under /bin/bash, mutation-checked) and a symlink test. Closes #275 Co-Authored-By: Claude Opus 5.5 --- .claude/commands/pm.md | 2 + .claude/gh-workflow.md | 19 ++ CHANGELOG.md | 4 + README.md | 14 + bin/task-monitor-queue | 275 ++++++++++++++++++ bin/task-monitor-snapshot | 6 +- bin/task-work | 8 + claude-gh/commands/pm.md | 2 + claude-gh/install.sh | 6 +- claude-gh/steering/gh-workflow.example.md | 19 ++ claude-jira/commands/pm.md | 2 + claude-jira/install.sh | 6 +- claude-jira/steering/jira-workflow.example.md | 19 ++ claude-local/commands/pm.md | 2 + claude-local/install.sh | 6 +- .../steering/local-workflow.example.md | 19 ++ claude-notion/commands/pm.md | 2 + claude-notion/install.sh | 6 +- .../steering/notion-workflow.example.md | 19 ++ docs/task-monitor-schema.md | 4 +- kiro-gh/agents/pm.json | 2 +- kiro-gh/install.sh | 6 +- kiro-gh/steering/gh-workflow.example.md | 19 ++ kiro-local/agents/pm.json | 2 +- kiro-local/install.sh | 6 +- kiro-local/steering/local-workflow.example.md | 19 ++ kiro-notion/agents/pm.json | 2 +- kiro-notion/install.sh | 6 +- .../steering/notion-workflow.example.md | 19 ++ tests/install_symlinks.bats | 11 + tests/task_monitor_queue.bats | 209 +++++++++++++ 31 files changed, 719 insertions(+), 22 deletions(-) create mode 100755 bin/task-monitor-queue create mode 100644 tests/task_monitor_queue.bats diff --git a/.claude/commands/pm.md b/.claude/commands/pm.md index 6a32889..c9d41b8 100644 --- a/.claude/commands/pm.md +++ b/.claude/commands/pm.md @@ -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 …` appends, `rm …` 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 `<... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/CHANGELOG.md b/CHANGELOG.md index f25a11a..756565d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `** 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 ... [--note =]` 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//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, so `267`, `#267` and `…/issues/267` are one item; `set` keeps the note of a ref that stays queued. `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` (21 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 `** 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 `..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/` 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. diff --git a/README.md b/README.md index d24d0fb..ec39a5e 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,20 @@ task-monitor-snapshot --local # disk only: no forge calls, merged with `--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//`. 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, so `267`, `#267` and `…/issues/267` are one item. 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 =` clears it. The file is `$TASK_FORCE_HOME/monitor//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. diff --git a/bin/task-monitor-queue b/bin/task-monitor-queue new file mode 100755 index 0000000..577713b --- /dev/null +++ b/bin/task-monitor-queue @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +set -euo pipefail + +# task-monitor-queue — the PM's short-term queue: what comes next in this repo +# (#275, epic #272 3/6). +# +# The PM writes it when the user and the PM agree on what comes next, and +# task-monitor-snapshot reads it as the board's "Next" section. Writing it is +# the PM's only board cost, so every write answers with one short line and the +# PM never has to read the file back. +# +# This command owns the queue file's format. The file is +# ${TASK_FORCE_HOME:-~/.task-force}/monitor//queue, being the +# sanitized main-repo name (the same directory the snapshot caches in). One +# tracker ref per line, in order, with an optional one-line note after a tab. +# Blank lines and `#` comments are ignored on read and never written. +# +# Refs are compared by key, not by spelling: a leading `#` and any URL or path +# prefix are dropped, so `267`, `#267` and .../issues/267 are one item, as are +# tasks/012-x.md and 012-x.md. That is what lets task-work drop the ref it +# dispatches (`rm --quiet `) whatever form the PM queued it in. +# +# Must run on /bin/bash 3.2: no associative arrays, no mapfile. + +# Resolve own path through symlinks -> AW_ROOT. +src="${BASH_SOURCE[0]}" +while [ -L "$src" ]; do + dir=$(cd -P "$(dirname "$src")" && pwd) + src=$(readlink "$src") + [[ $src != /* ]] && src="$dir/$src" +done +BIN_DIR=$(cd -P "$(dirname "$src")" && pwd) +AW_ROOT=$(dirname "$BIN_DIR") + +# shellcheck source=lib/repo-name.sh +source "$AW_ROOT/lib/repo-name.sh" + +usage() { + cat <<'EOF' +Usage: task-monitor-queue [show] + task-monitor-queue set ... [--note =]... + task-monitor-queue add ... [--note =]... + task-monitor-queue rm ... [--quiet] + task-monitor-queue clear + (every form also takes --repo DIR) + +The PM's short-term queue for this repo: the "Next" section of task-monitor. + + show print the queue, one item per line (the default) + set replace the queue with ... in that order. A ref that was + already queued keeps its note unless --note gives a new one + add append ... to the end; a ref already queued keeps its place + and only takes the new note, if one is given + rm drop ...; a ref that is not queued is ignored + clear empty the queue + + --note = a one-line note for , which must be one of the + refs given or already queued. An empty text clears it + --quiet print nothing on success + --repo DIR the repo containing DIR instead of $PWD + +A ref is a tracker ref: an issue number, a URL, a Jira key, a task file. +`267`, `#267` and .../issues/267 name the same item. + +Writes ${TASK_FORCE_HOME:-~/.task-force}/monitor//queue. +EOF + exit "${1:-0}" +} + +CMD=show +case "${1:-}" in + show|set|add|rm|clear) CMD="$1"; shift ;; + -h|--help|help) usage 0 ;; +esac + +ARGS=() +NOTE_ARGS=() +QUIET=0 +REPO_ARG="" +while [[ $# -gt 0 ]]; do + case "$1" in + --note) + [[ $# -ge 2 ]] || { echo "Error: --note requires =" >&2; exit 2; } + [[ "$2" == ?*=* ]] || { echo "Error: --note takes =, got: $2" >&2; exit 2; } + NOTE_ARGS+=("$2"); shift 2 ;; + --quiet) QUIET=1; shift ;; + --repo) + [[ $# -ge 2 ]] || { echo "Error: --repo requires a value" >&2; exit 2; } + REPO_ARG="$2"; shift 2 ;; + -h|--help) usage 0 ;; + -*) echo "Error: unknown option: $1" >&2; usage 2 >&2 ;; + *) ARGS+=("$1"); shift ;; + esac +done + +case "$CMD" in + show|clear) + [[ ${#ARGS[@]} -eq 0 ]] || { echo "Error: $CMD takes no refs" >&2; exit 2; } ;; + set|add|rm) + [[ ${#ARGS[@]} -gt 0 ]] || { echo "Error: $CMD needs at least one ref" >&2; exit 2; } ;; +esac +if [[ ${#NOTE_ARGS[@]} -gt 0 && "$CMD" != set && "$CMD" != add ]]; then + echo "Error: --note goes with set or add" >&2 + exit 2 +fi + +ROOT=$(aw_main_repo_root "${REPO_ARG:-$PWD}") \ + || { echo "Error: not in a git repo: ${REPO_ARG:-$PWD}" >&2; exit 1; } +QUEUE="${TASK_FORCE_HOME:-$HOME/.task-force}/monitor/$(aw_main_repo_name "$ROOT")/queue" + +# ----- helpers -------------------------------------------------------------- + +# The identity a ref is compared by: no leading `#`, no trailing `/`, and only +# the last path segment of a URL or a path. +_key() { + local k="${1#\#}" + k="${k%/}" + printf '%s' "${k##*/}" +} + +# A ref goes on one line before a tab, so it may hold neither whitespace nor +# be empty. +_check_ref() { + case "$1" in + ''|'#') echo "Error: empty ref" >&2; exit 2 ;; + *[[:space:]]*) echo "Error: a ref cannot contain whitespace: $1" >&2; exit 2 ;; + esac +} + +# A note is one line: tabs and newlines become spaces. +_clean_note() { + printf '%s' "$1" | tr '\t\n\r' ' ' +} + +# The current queue, as parallel arrays. +REFS=() +NOTES=() +if [[ -f "$QUEUE" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + case "$line" in ''|'#'*) continue ;; esac + REFS+=("${line%%$'\t'*}") + if [[ "$line" == *$'\t'* ]]; then NOTES+=("${line#*$'\t'}"); else NOTES+=(""); fi + done < "$QUEUE" +fi + +# Index of in REFS by key, or nothing. +_index_of() { + local k i + k=$(_key "$1") + for ((i = 0; i < ${#REFS[@]}; i++)); do + if [[ "$(_key "${REFS[$i]}")" == "$k" ]]; then + printf '%s' "$i" + return 0 + fi + done + return 1 +} + +_write() { + local i dir tmp + if [[ ${#REFS[@]} -eq 0 ]]; then + rm -f "$QUEUE" + return 0 + fi + dir=$(dirname "$QUEUE") + mkdir -p "$dir" + tmp="$QUEUE.tmp.$$" + : > "$tmp" + for ((i = 0; i < ${#REFS[@]}; i++)); do + if [[ -n "${NOTES[$i]}" ]]; then + printf '%s\t%s\n' "${REFS[$i]}" "${NOTES[$i]}" >> "$tmp" + else + printf '%s\n' "${REFS[$i]}" >> "$tmp" + fi + done + mv -f "$tmp" "$QUEUE" +} + +# The one line a write answers with: the new order, refs only. +_summary() { + [[ "$QUIET" == 1 ]] && return 0 + if [[ ${#REFS[@]} -eq 0 ]]; then + echo "queue: (empty)" + else + echo "queue: ${REFS[*]}" + fi +} + +# Apply every --note to REFS / NOTES. Each must name a queued ref. +_apply_notes() { + local n ref i + [[ ${#NOTE_ARGS[@]} -gt 0 ]] || return 0 + for n in "${NOTE_ARGS[@]}"; do + ref="${n%%=*}" + if ! i=$(_index_of "$ref"); then + echo "Error: --note names $ref, which is not in the queue" >&2 + exit 2 + fi + NOTES[i]=$(_clean_note "${n#*=}") + done +} + +# ----- commands ------------------------------------------------------------- + +case "$CMD" in + show) + if [[ ${#REFS[@]} -eq 0 ]]; then + [[ "$QUIET" == 1 ]] || echo "queue: (empty)" + exit 0 + fi + for ((i = 0; i < ${#REFS[@]}; i++)); do + if [[ -n "${NOTES[$i]}" ]]; then + printf '%d %s %s\n' "$((i + 1))" "${REFS[$i]}" "${NOTES[$i]}" + else + printf '%d %s\n' "$((i + 1))" "${REFS[$i]}" + fi + done + ;; + set) + OLD_REFS=("${REFS[@]+"${REFS[@]}"}") + OLD_NOTES=("${NOTES[@]+"${NOTES[@]}"}") + REFS=() + NOTES=() + for ref in "${ARGS[@]}"; do + _check_ref "$ref" + _index_of "$ref" >/dev/null && continue + note="" + for ((i = 0; i < ${#OLD_REFS[@]}; i++)); do + if [[ "$(_key "${OLD_REFS[$i]}")" == "$(_key "$ref")" ]]; then + note="${OLD_NOTES[$i]}" + break + fi + done + REFS+=("${ref#\#}") + NOTES+=("$note") + done + _apply_notes + _write + _summary + ;; + add) + for ref in "${ARGS[@]}"; do + _check_ref "$ref" + _index_of "$ref" >/dev/null && continue + REFS+=("${ref#\#}") + NOTES+=("") + done + _apply_notes + _write + _summary + ;; + rm) + for ref in "${ARGS[@]}"; do + [[ -n "$ref" ]] || continue + i=$(_index_of "$ref") || continue + KEEP_REFS=() + KEEP_NOTES=() + for ((j = 0; j < ${#REFS[@]}; j++)); do + [[ "$j" -eq "$i" ]] && continue + KEEP_REFS+=("${REFS[$j]}") + KEEP_NOTES+=("${NOTES[$j]}") + done + REFS=("${KEEP_REFS[@]+"${KEEP_REFS[@]}"}") + NOTES=("${KEEP_NOTES[@]+"${KEEP_NOTES[@]}"}") + done + _write + _summary + ;; + clear) + REFS=() + NOTES=() + _write + _summary + ;; +esac diff --git a/bin/task-monitor-snapshot b/bin/task-monitor-snapshot index 03ef08d..b19aa1c 100755 --- a/bin/task-monitor-snapshot +++ b/bin/task-monitor-snapshot @@ -374,9 +374,9 @@ _snapshot_repo() { # --- next: the PM's queue, else the tracker's fallback. # - # The queue file's format is owned by the `task-monitor next` command (#272 - # sub-issue 3). Until that lands this reads the shape it is expected to keep: - # one ref per line, an optional note after a tab, `#` comments ignored. + # The queue file's format is owned by bin/task-monitor-queue (#275), which + # the PM writes it through: one ref per line, an optional note after a tab, + # `#` comments ignored. local queue="$cache/queue" next_source=none line note fallback fb_ok=1 : > "$w/next.ndjson" if [[ -s "$queue" ]]; then diff --git a/bin/task-work b/bin/task-work index ad7964b..45882d4 100755 --- a/bin/task-work +++ b/bin/task-work @@ -217,6 +217,14 @@ printf 'BASE_BRANCH=%s\nSLUG=%s\n%s=%s\n' "$BASE_BRANCH" "$SLUG" "$INFO_KEY" "$T aw_tracker_post_worktree "$REPO_ROOT" "$SLUG" "$BRANCH" "$WORKTREE_DIR" "$TASK_REF" +# A dispatched item is no longer next: take it off the PM's queue (#275), so +# the PM never spends a call keeping the board's Next section honest. Refs are +# compared by key, so a queued `267` matches the URL dispatched here. Best +# effort: a queue that cannot be written never stops a launch. +if [[ -n "$TASK_REF" ]]; then + "$AW_ROOT/bin/task-monitor-queue" rm --quiet --repo "$REPO_ROOT" "$TASK_REF" 2>/dev/null || true +fi + # Install the commit-msg guard before the worker gets a shell (#194). GitHub # honours a CI-skip marker anywhere in the head commit's message, so an agent # that merely *quotes* one while describing another commit silently gets zero diff --git a/claude-gh/commands/pm.md b/claude-gh/commands/pm.md index 6a32889..c9d41b8 100644 --- a/claude-gh/commands/pm.md +++ b/claude-gh/commands/pm.md @@ -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 …` appends, `rm …` 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 `<... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/claude-jira/commands/pm.md b/claude-jira/commands/pm.md index 1045c16..4ed2821 100644 --- a/claude-jira/commands/pm.md +++ b/claude-jira/commands/pm.md @@ -18,6 +18,8 @@ When creating issues, set status to the project's "to do" equivalent unless told Keep summaries concise and actionable. When showing the backlog, group by epic (or project) 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 PROJ-42 PROJ-40 --note PROJ-42="after the release"`. Refs are issue keys. This queue is the "Next" section of the user's task-monitor board. `add …` appends, `rm …` 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 `<... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/claude-local/commands/pm.md b/claude-local/commands/pm.md index eeaee03..adc653d 100644 --- a/claude-local/commands/pm.md +++ b/claude-local/commands/pm.md @@ -63,6 +63,8 @@ When asked to "show backlog" or "list tasks": When creating tasks, default `status: todo` and `priority: P2` unless told otherwise. Keep titles concise and actionable. +**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 012-fix-auth.md 013-cache.md --note 012-fix-auth.md="blocks 013"`. Refs are task file names, as you pass them to `task-work`. This queue is the "Next" section of the user's task-monitor board. `add …` appends, `rm …` 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 `<... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/claude-notion/commands/pm.md b/claude-notion/commands/pm.md index ab99f39..46ce754 100644 --- a/claude-notion/commands/pm.md +++ b/claude-notion/commands/pm.md @@ -18,6 +18,8 @@ When creating tasks, set Status to "Not Started" unless told otherwise. Keep task names concise and actionable. When showing the backlog, group by project 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 --note ="after the release"`. Refs are task page URLs, as you pass them to `task-work`. This queue is the "Next" section of the user's task-monitor board. `add …` appends, `rm …` 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 `<... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/docs/task-monitor-schema.md b/docs/task-monitor-schema.md index 6ebd321..ae69d5f 100644 --- a/docs/task-monitor-schema.md +++ b/docs/task-monitor-schema.md @@ -135,8 +135,8 @@ that PR: - `source: "queue"`: the PM's queue, in its order. It is read from `${TASK_FORCE_HOME}/monitor//queue`, one ref per line, with an optional - tab-separated note and `#` comments. The command that writes this file is - #272's sub-issue 3, which owns the format. + tab-separated note and `#` comments. `task-monitor-queue` (#275) writes this + file and owns the format; `task-work` drops the ref it dispatches from it. - `source: "tracker"`: the queue is empty, so the tracker's fallback is used. - **gh**: open issues on the configured project whose status is none of the three in the workflow doc (starting work, in review, done), compared diff --git a/kiro-gh/agents/pm.json b/kiro-gh/agents/pm.json index af7fdf1..c845b72 100644 --- a/kiro-gh/agents/pm.json +++ b/kiro-gh/agents/pm.json @@ -1,7 +1,7 @@ { "name": "pm", "description": "Product Manager — backlog grooming, task creation, prioritization via GitHub Projects", - "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog in GitHub Projects.\n\nRefer to the project's .kiro/steering/gh-workflow.md for GitHub owner/repo, project number, and conventions. Use the `gh` CLI for issue and project I/O (gh issue list / view / create / edit / comment, gh search issues, gh project view / item-list / field-list / item-edit, gh label list).\n\nYour responsibilities:\n- Show backlog status when asked (list open issues in the project, grouped by status and priority)\n- Create new issues with a clear title and brief body description, then add them to the project\n- Update issue labels, priorities, and project field values\n- Groom the backlog — identify stale issues, reprioritize, suggest what to work on next\n- When asked to start work on an issue, run: task-work \"\"\n Pass --trust-all for autonomous runs, -m to pick a specific kiro model,\n or --no-launch to open the worktree tab and let the user drive.\n\nWhen creating issues, set the project Status field to \"Todo\" unless told otherwise.\nKeep issue titles concise and actionable.\nWhen showing the backlog, group by project Status and sort by priority.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to set Status=Done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.\n\n- **Delegating review to a reviewer worker** (optional, cost-saving). Why this exists: the reviewer runs on a cheaper model than you do — for non-trivial PRs, offloading the read shifts cost without losing quality. Skip it and review inline if the PR is small enough that the dispatch overhead isn't worth it.\n\n On `review-requested` from a worker, run `task-reviewer []` in any spare tab. The reviewer runs with `--trust-all-tools` by default — the reviewer prompt's authority list rules out anything destructive (no merge, no push, no Status edits), so hands-off dispatch is safe. Pass `--no-trust-all` to babysit a specific review.\n\n Positional args:\n - PR (required): URL or bare number.\n - Issue (optional): URL or bare number for the spec issue. If omitted, auto-detected from the PR body's first `Closes #N` / `Fixes #N` / `Resolves #N` line (case-insensitive). Pass it explicitly only when the PR body doesn't link a spec, or when you want to override the auto-detected target.\n\n The reviewer spawns a fresh tab + worktree on the PR's head ref, cross-checks the diff against the spec, posts a single thorough PR comment, and radios you back with one of:\n - `review-complete-clean` → no findings. Proceed to merge: `gh pr merge --squash --delete-branch`, then `radio send --to --intent approved-and-merged --pr --body \"merged\"`.\n - `review-complete-with-findings` → reviewer posted blockers/nits to the PR. Forward to the worker: `radio send --to --intent changes-requested --pr --body \"see PR comments (reviewer flagged findings)\"`. The review content lives in the PR comment; radio just carries the routing ping.\n\n You still own the merge decision — the reviewer never approves, merges, closes, or mutates Status. The reviewer tab stays open showing the analysis; `task-done --remove-worktree` from inside that worktree cleans it up.", + "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog in GitHub Projects.\n\nRefer to the project's .kiro/steering/gh-workflow.md for GitHub owner/repo, project number, and conventions. Use the `gh` CLI for issue and project I/O (gh issue list / view / create / edit / comment, gh search issues, gh project view / item-list / field-list / item-edit, gh label list).\n\nYour responsibilities:\n- Show backlog status when asked (list open issues in the project, grouped by status and priority)\n- Create new issues with a clear title and brief body description, then add them to the project\n- Update issue labels, priorities, and project field values\n- Groom the backlog — identify stale issues, reprioritize, suggest what to work on next\n- When asked to start work on an issue, run: task-work \"\"\n Pass --trust-all for autonomous runs, -m to pick a specific kiro model,\n or --no-launch to open the worktree tab and let the user drive.\n\nWhen creating issues, set the project Status field to \"Todo\" unless told otherwise.\nKeep issue titles concise and actionable.\nWhen showing the backlog, group by project Status and sort by priority.\n\n**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 …` appends, `rm …` 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.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to set Status=Done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.\n\n- **Delegating review to a reviewer worker** (optional, cost-saving). Why this exists: the reviewer runs on a cheaper model than you do — for non-trivial PRs, offloading the read shifts cost without losing quality. Skip it and review inline if the PR is small enough that the dispatch overhead isn't worth it.\n\n On `review-requested` from a worker, run `task-reviewer []` in any spare tab. The reviewer runs with `--trust-all-tools` by default — the reviewer prompt's authority list rules out anything destructive (no merge, no push, no Status edits), so hands-off dispatch is safe. Pass `--no-trust-all` to babysit a specific review.\n\n Positional args:\n - PR (required): URL or bare number.\n - Issue (optional): URL or bare number for the spec issue. If omitted, auto-detected from the PR body's first `Closes #N` / `Fixes #N` / `Resolves #N` line (case-insensitive). Pass it explicitly only when the PR body doesn't link a spec, or when you want to override the auto-detected target.\n\n The reviewer spawns a fresh tab + worktree on the PR's head ref, cross-checks the diff against the spec, posts a single thorough PR comment, and radios you back with one of:\n - `review-complete-clean` → no findings. Proceed to merge: `gh pr merge --squash --delete-branch`, then `radio send --to --intent approved-and-merged --pr --body \"merged\"`.\n - `review-complete-with-findings` → reviewer posted blockers/nits to the PR. Forward to the worker: `radio send --to --intent changes-requested --pr --body \"see PR comments (reviewer flagged findings)\"`. The review content lives in the PR comment; radio just carries the routing ping.\n\n You still own the merge decision — the reviewer never approves, merges, closes, or mutates Status. The reviewer tab stays open showing the analysis; `task-done --remove-worktree` from inside that worktree cleans it up.", "tools": [ "fs_read", "grep", diff --git a/kiro-gh/install.sh b/kiro-gh/install.sh index f052e46..dbd0ea4 100755 --- a/kiro-gh/install.sh +++ b/kiro-gh/install.sh @@ -12,8 +12,8 @@ echo "Installing kiro-gh scripts..." # lib/trackers/ (and, for task-work, lib/agents/) instead of exec'ing a # per-loadout copy (#236, #237); # task-board, task-pm, radio, task-config, task-remove, -# task-recreate-worker and task-monitor-snapshot are canonical single copies -# (#170, #219, #220, #230, #238, #273 — +# task-recreate-worker, task-monitor-snapshot and task-monitor-queue are +# canonical single copies (#170, #219, #220, #230, #238, #273, #275 — # task-config and task-remove act *across* loadouts, so a per-loadout copy would # make no sense; task-pm and task-recreate-worker differ by agent only in their # final launch line). @@ -45,6 +45,8 @@ ln -sf "$SCRIPT_DIR/../bin/task-recreate-worker" ~/.local/bin/task-recreate-work echo " ✓ Script: task-recreate-worker (canonical)"; sleep 0.05 ln -sf "$SCRIPT_DIR/../bin/task-monitor-snapshot" ~/.local/bin/task-monitor-snapshot echo " ✓ Script: task-monitor-snapshot (canonical)"; sleep 0.05 +ln -sf "$SCRIPT_DIR/../bin/task-monitor-queue" ~/.local/bin/task-monitor-queue +echo " ✓ Script: task-monitor-queue (canonical)"; sleep 0.05 # endregion:install-shared-symlinks ln -sf "$SCRIPT_DIR/../bin/task-reviewer" ~/.local/bin/task-reviewer echo " ✓ Script: task-reviewer (canonical + kiro routing)"; sleep 0.05 diff --git a/kiro-gh/steering/gh-workflow.example.md b/kiro-gh/steering/gh-workflow.example.md index 933cff7..66c8148 100644 --- a/kiro-gh/steering/gh-workflow.example.md +++ b/kiro-gh/steering/gh-workflow.example.md @@ -305,6 +305,25 @@ 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). +`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 ... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/kiro-local/agents/pm.json b/kiro-local/agents/pm.json index 19b1ce1..3ac9853 100644 --- a/kiro-local/agents/pm.json +++ b/kiro-local/agents/pm.json @@ -1,7 +1,7 @@ { "name": "pm", "description": "Product Manager — backlog grooming, task creation, prioritization via local markdown files", - "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog as local markdown files in tasks/.\n\nRefer to the project's .kiro/steering/local-workflow.md for conventions. Tasks live as markdown files in tasks/ — there is no external tracker and no GitHub MCP in this workflow.\n\nYour responsibilities:\n- Show backlog status when asked (read tasks/*.md frontmatter, group by status and priority)\n- Create new task files with a clear title and the standard frontmatter + body sections\n- Update frontmatter fields (status, priority, tags) on existing task files\n- Groom the backlog — flag stale tasks, reprioritize, suggest what to work on next\n- When asked to start work on a task, run: task-work tasks/NNN-slug.md\n\nTask file conventions:\n- One .md per task at tasks/NNN-slug.md.\n- NNN is a zero-padded 3-digit id. When creating a task, allocate id = max-existing + 1 (scan tasks/[0-9][0-9][0-9]-*.md); start at 001 if none exist.\n- slug is a kebab-case short title (lowercase, hyphens, no special chars).\n- Required frontmatter:\n ---\n id: NNN\n title: \n status: todo # todo | in-progress | done\n priority: P1 # P0 | P1 | P2 | P3 — default P2 unless told otherwise\n tags: []\n created: YYYY-MM-DD # today's date\n branch: \"\"\n pr: \"\"\n ---\n- Body sections (leave bodies blank for the planner to fill in):\n ## Problem\n ## Solution\n ## Files to Create/Modify\n ## Verification\n\nAfter every mutation:\nAfter creating, editing, or deleting any task file, run task-board (in the repo root) to regenerate tasks/_board.md. Never edit _board.md by hand — it's the autogenerated overview.\n\nShowing the backlog:\nWhen asked to show backlog or list tasks:\n- Read tasks/*.md frontmatter for id / title / status / priority / tags.\n- Overlay .git/task-force/state.json (one JSON object per line) for what's in flight right now — any slug there is in progress regardless of frontmatter.\n- Group by status (Todo / In Progress / Done) and sort by priority (P0 → P3) then by id.\n\nWhen creating tasks, default status: todo and priority: P2 unless told otherwise.\nKeep titles concise and actionable.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to bump the task file's status to done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.", + "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog as local markdown files in tasks/.\n\nRefer to the project's .kiro/steering/local-workflow.md for conventions. Tasks live as markdown files in tasks/ — there is no external tracker and no GitHub MCP in this workflow.\n\nYour responsibilities:\n- Show backlog status when asked (read tasks/*.md frontmatter, group by status and priority)\n- Create new task files with a clear title and the standard frontmatter + body sections\n- Update frontmatter fields (status, priority, tags) on existing task files\n- Groom the backlog — flag stale tasks, reprioritize, suggest what to work on next\n- When asked to start work on a task, run: task-work tasks/NNN-slug.md\n\nTask file conventions:\n- One .md per task at tasks/NNN-slug.md.\n- NNN is a zero-padded 3-digit id. When creating a task, allocate id = max-existing + 1 (scan tasks/[0-9][0-9][0-9]-*.md); start at 001 if none exist.\n- slug is a kebab-case short title (lowercase, hyphens, no special chars).\n- Required frontmatter:\n ---\n id: NNN\n title: \n status: todo # todo | in-progress | done\n priority: P1 # P0 | P1 | P2 | P3 — default P2 unless told otherwise\n tags: []\n created: YYYY-MM-DD # today's date\n branch: \"\"\n pr: \"\"\n ---\n- Body sections (leave bodies blank for the planner to fill in):\n ## Problem\n ## Solution\n ## Files to Create/Modify\n ## Verification\n\nAfter every mutation:\nAfter creating, editing, or deleting any task file, run task-board (in the repo root) to regenerate tasks/_board.md. Never edit _board.md by hand — it's the autogenerated overview.\n\nShowing the backlog:\nWhen asked to show backlog or list tasks:\n- Read tasks/*.md frontmatter for id / title / status / priority / tags.\n- Overlay .git/task-force/state.json (one JSON object per line) for what's in flight right now — any slug there is in progress regardless of frontmatter.\n- Group by status (Todo / In Progress / Done) and sort by priority (P0 → P3) then by id.\n\nWhen creating tasks, default status: todo and priority: P2 unless told otherwise.\nKeep titles concise and actionable.\n\n**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 012-fix-auth.md 013-cache.md --note 012-fix-auth.md=\"blocks 013\"`. Refs are task file names, as you pass them to `task-work`. This queue is the \"Next\" section of the user's task-monitor board. `add …` appends, `rm …` 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.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to bump the task file's status to done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.", "tools": [ "fs_read", "fs_write", diff --git a/kiro-local/install.sh b/kiro-local/install.sh index 7cc0dff..f57ef1b 100755 --- a/kiro-local/install.sh +++ b/kiro-local/install.sh @@ -12,8 +12,8 @@ echo "Installing kiro-local scripts..." # lib/trackers/ (and, for task-work, lib/agents/) instead of exec'ing a # per-loadout copy (#236, #237); # task-board, task-pm, radio, task-config, task-remove, -# task-recreate-worker and task-monitor-snapshot are canonical single copies -# (#170, #219, #220, #230, #238, #273 — +# task-recreate-worker, task-monitor-snapshot and task-monitor-queue are +# canonical single copies (#170, #219, #220, #230, #238, #273, #275 — # task-config and task-remove act *across* loadouts, so a per-loadout copy would # make no sense; task-pm and task-recreate-worker differ by agent only in their # final launch line). @@ -45,6 +45,8 @@ ln -sf "$SCRIPT_DIR/../bin/task-recreate-worker" ~/.local/bin/task-recreate-work echo " ✓ Script: task-recreate-worker (canonical)"; sleep 0.05 ln -sf "$SCRIPT_DIR/../bin/task-monitor-snapshot" ~/.local/bin/task-monitor-snapshot echo " ✓ Script: task-monitor-snapshot (canonical)"; sleep 0.05 +ln -sf "$SCRIPT_DIR/../bin/task-monitor-queue" ~/.local/bin/task-monitor-queue +echo " ✓ Script: task-monitor-queue (canonical)"; sleep 0.05 # endregion:install-shared-symlinks # Ensure ~/.local/bin is on PATH diff --git a/kiro-local/steering/local-workflow.example.md b/kiro-local/steering/local-workflow.example.md index dd40b12..706972c 100644 --- a/kiro-local/steering/local-workflow.example.md +++ b/kiro-local/steering/local-workflow.example.md @@ -327,6 +327,25 @@ 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). +`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 ... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/kiro-notion/agents/pm.json b/kiro-notion/agents/pm.json index 62d442d..30fd908 100644 --- a/kiro-notion/agents/pm.json +++ b/kiro-notion/agents/pm.json @@ -1,7 +1,7 @@ { "name": "pm", "description": "Product Manager — backlog grooming, task creation, prioritization via Notion", - "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog in Notion.\n\nRefer to the project's .kiro/steering/notion-workflow.md for Notion database IDs, task statuses, and conventions.\n\nYour responsibilities:\n- Show backlog status when asked (query Notion tasks)\n- Create new tasks with proper properties (title, priority, status, project relation, tags)\n- Update task priorities and statuses\n- Groom the backlog — identify stale tasks, reprioritize, suggest what to work on next\n- When asked to start work on a task, run: task-work \"\"\n Pass --trust-all for autonomous runs, -m to pick a specific kiro model,\n or --no-launch to open the worktree tab and let the user drive.\n\nWhen creating tasks, always set Status to \"Not Started\" unless told otherwise.\nKeep task names concise and actionable.\nWhen showing the backlog, group by project and sort by priority.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to set the Notion task Status to Done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.", + "prompt": "## Radio inbox — poll it yourself, every turn\n\nOnce you are mid-session, nothing will hand you an incoming radio message. Your hooks register this role and flip its state, but the only one that prints anything to your context is the register at startup — after that, radio's push path here is a best-effort keystroke written into your pane, and once you go idle no stop-hook drain will catch whatever arrives. Treat radio as a pull channel: run `radio check` at the start of every turn, `radio read ` (which acknowledges it) for anything it lists, and handle those messages before continuing with whatever else you were asked. An empty inbox prints nothing and costs you a second.\n\nYou are a PM agent. Your job is to manage the project backlog in Notion.\n\nRefer to the project's .kiro/steering/notion-workflow.md for Notion database IDs, task statuses, and conventions.\n\nYour responsibilities:\n- Show backlog status when asked (query Notion tasks)\n- Create new tasks with proper properties (title, priority, status, project relation, tags)\n- Update task priorities and statuses\n- Groom the backlog — identify stale tasks, reprioritize, suggest what to work on next\n- When asked to start work on a task, run: task-work \"\"\n Pass --trust-all for autonomous runs, -m to pick a specific kiro model,\n or --no-launch to open the worktree tab and let the user drive.\n\nWhen creating tasks, always set Status to \"Not Started\" unless told otherwise.\nKeep task names concise and actionable.\nWhen showing the backlog, group by project and sort by priority.\n\n**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 --note =\"after the release\"`. Refs are task page URLs, as you pass them to `task-work`. This queue is the \"Next\" section of the user's task-monitor board. `add …` appends, `rm …` 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.\n\n**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 `<-`; discover the live one via `ls ~/.task-force/radio/sessions/`.\n\n- **After merging a PR**: run `gh pr merge --squash --delete-branch` (or `--merge` / `--rebase` per project convention), then:\n radio send --to --intent approved-and-merged --pr --body \"merged\"\n The worker treats this as the signal to set the Notion task Status to Done and run `task-done --remove-worktree`. Without this beat, the worker sits idle and the worktree leaks.\n\n- **After requesting changes**: post the substantive review via `gh pr comment --body-file -` or `gh pr review --request-changes --body-file -`, with the body on a quote-delimited heredoc as above, then:\n radio send --to --intent changes-requested --pr --body \"see PR comments\"\n `radio` only carries the routing ping — the review content stays in the PR.\n\n- **After approving without merging** (e.g., waiting on CI): `gh pr review --approve` is enough; no radio beat needed until you actually merge.", "tools": [ "fs_read", "grep", diff --git a/kiro-notion/install.sh b/kiro-notion/install.sh index bcfa222..035b499 100755 --- a/kiro-notion/install.sh +++ b/kiro-notion/install.sh @@ -12,8 +12,8 @@ echo "Installing kiro-notion scripts..." # lib/trackers/ (and, for task-work, lib/agents/) instead of exec'ing a # per-loadout copy (#236, #237); # task-board, task-pm, radio, task-config, task-remove, -# task-recreate-worker and task-monitor-snapshot are canonical single copies -# (#170, #219, #220, #230, #238, #273 — +# task-recreate-worker, task-monitor-snapshot and task-monitor-queue are +# canonical single copies (#170, #219, #220, #230, #238, #273, #275 — # task-config and task-remove act *across* loadouts, so a per-loadout copy would # make no sense; task-pm and task-recreate-worker differ by agent only in their # final launch line). @@ -45,6 +45,8 @@ ln -sf "$SCRIPT_DIR/../bin/task-recreate-worker" ~/.local/bin/task-recreate-work echo " ✓ Script: task-recreate-worker (canonical)"; sleep 0.05 ln -sf "$SCRIPT_DIR/../bin/task-monitor-snapshot" ~/.local/bin/task-monitor-snapshot echo " ✓ Script: task-monitor-snapshot (canonical)"; sleep 0.05 +ln -sf "$SCRIPT_DIR/../bin/task-monitor-queue" ~/.local/bin/task-monitor-queue +echo " ✓ Script: task-monitor-queue (canonical)"; sleep 0.05 # endregion:install-shared-symlinks # Ensure ~/.local/bin is on PATH diff --git a/kiro-notion/steering/notion-workflow.example.md b/kiro-notion/steering/notion-workflow.example.md index abe2b5f..866bf68 100644 --- a/kiro-notion/steering/notion-workflow.example.md +++ b/kiro-notion/steering/notion-workflow.example.md @@ -302,6 +302,25 @@ 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). +`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 ... [--note =] # replace, in order +task-monitor-queue add ... # append +task-monitor-queue rm ... # drop +task-monitor-queue clear # empty +task-monitor-queue # show it +``` + +Refs are compared by their last path segment, 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 =` clears it. + ### PM ↔ worker messaging (radio) Radio is the **canonical** coordination channel between the PM and workers — every diff --git a/tests/install_symlinks.bats b/tests/install_symlinks.bats index f34edf7..9772544 100644 --- a/tests/install_symlinks.bats +++ b/tests/install_symlinks.bats @@ -38,6 +38,17 @@ teardown() { done } +@test "install symlinks task-monitor-queue to the canonical root bin (#275)" { + local impl target + for impl in claude-gh kiro-local; do + bash "$REPO_ROOT_REAL/$impl/install.sh" >/dev/null + assert [ -L "$HOME/.local/bin/task-monitor-queue" ] + target=$(cd "$(dirname "$(readlink "$HOME/.local/bin/task-monitor-queue")")" && pwd -P) + assert_equal "$target" "$(cd "$REPO_ROOT_REAL/bin" && pwd -P)" + rm -f "$HOME/.local/bin/task-monitor-queue" + done +} + @test "kiro-gh install symlinks task-pm and radio into ~/.local/bin" { bash "$REPO_ROOT_REAL/kiro-gh/install.sh" >/dev/null assert [ -L "$HOME/.local/bin/task-pm" ] diff --git a/tests/task_monitor_queue.bats b/tests/task_monitor_queue.bats new file mode 100644 index 0000000..09be4db --- /dev/null +++ b/tests/task_monitor_queue.bats @@ -0,0 +1,209 @@ +#!/usr/bin/env bats +# bin/task-monitor-queue (#275): the PM's short-term queue, the "Next" section of +# task-monitor. Every test runs against a scratch repo and a scratch +# $TASK_FORCE_HOME, and under /bin/bash, which is 3.2 on macOS. + +bats_load_library bats-support +bats_load_library bats-assert + +load helpers/common + +NEXT="$REPO_ROOT_REAL/bin/task-monitor-queue" +SNAP="$REPO_ROOT_REAL/bin/task-monitor-snapshot" + +setup() { + setup_repo + setup_task_force_home + unset TASK_FORCE_ROLE + NAME=$(basename "$MAIN_REPO" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g') + QUEUE="$TASK_FORCE_HOME/monitor/$NAME/queue" + cd "$MAIN_REPO" +} + +teardown() { + teardown_all +} + +nx() { /bin/bash "$NEXT" "$@"; } + +# The queue file with tabs made visible. +queue_file() { sed 's/\t//' "$QUEUE"; } + +@test "show: an empty queue says so and writes nothing" { + run nx + assert_success + assert_output "queue: (empty)" + assert [ ! -e "$TASK_FORCE_HOME/monitor" ] +} + +@test "set: writes refs in order, one per line, notes after a tab, and answers with one line" { + run nx set 267 '#266' 180 --note 267='kiro hooks first' + assert_success + assert_output "queue: 267 266 180" + run queue_file + assert_output "$(printf '267kiro hooks first\n266\n180')" +} + +@test "show: numbered, with notes" { + nx set 8 5 --note 5='after the release' >/dev/null + run nx show + assert_success + assert_output "$(printf '1 8\n2 5 after the release')" +} + +@test "set: replaces the queue, keeps a surviving ref's note, an empty --note clears it" { + nx set 1 2 3 --note 2=two --note 3=three >/dev/null + run nx set 3 2 + assert_output "queue: 3 2" + run queue_file + assert_output "$(printf '3three\n2two')" + nx set 3 2 --note 3= >/dev/null + run queue_file + assert_output "$(printf '3\n2two')" +} + +@test "set: a ref given twice, in any spelling, is queued once" { + run nx set 7 '#7' https://github.com/acme/widgets/issues/7 9 + assert_output "queue: 7 9" +} + +@test "add: appends new refs; a queued one keeps its place and only takes the note" { + nx set 1 2 >/dev/null + run nx add 3 '#1' --note 1='first, still' + assert_success + assert_output "queue: 1 2 3" + run queue_file + assert_output "$(printf '1first, still\n2\n3')" +} + +@test "add: onto an empty queue creates the file" { + run nx add PROJ-42 + assert_success + assert_output "queue: PROJ-42" + assert [ -f "$QUEUE" ] +} + +@test "rm: drops by key, so a URL removes a bare number; unknown refs are ignored" { + nx set 267 266 180 >/dev/null + run nx rm https://github.com/acme/widgets/issues/267/ nope + assert_success + assert_output "queue: 266 180" +} + +@test "rm: a task file path matches the file name it was queued by" { + nx set 012-fix-it.md 013-other.md >/dev/null + run nx rm /abs/repo/tasks/012-fix-it.md + assert_output "queue: 013-other.md" +} + +@test "rm --quiet prints nothing; emptying the queue removes the file" { + nx set 5 >/dev/null + run nx rm --quiet 5 + assert_success + assert_output "" + assert [ ! -e "$QUEUE" ] +} + +@test "rm: with no queue file at all, creates nothing" { + run nx rm 5 + assert_success + assert_output "queue: (empty)" + assert [ ! -e "$TASK_FORCE_HOME/monitor" ] +} + +@test "clear: empties the queue and removes the file" { + nx set 1 2 >/dev/null + run nx clear + assert_success + assert_output "queue: (empty)" + assert [ ! -e "$QUEUE" ] +} + +@test "a note is one line: tabs and newlines become spaces" { + nx set 4 --note "4=$(printf 'a\tb\nc')" >/dev/null + run queue_file + assert_output "4a b c" +} + +@test "refused: a ref with whitespace, a note for an unqueued ref, refs on clear, nothing on set" { + run nx set 'a b' + assert_failure 2 + assert_output --partial "cannot contain whitespace" + run nx set 1 --note 9=x + assert_failure 2 + assert_output --partial "9, which is not in the queue" + run nx clear 1 + assert_failure 2 + run nx set + assert_failure 2 + run nx rm 1 --note 1=x + assert_failure 2 + assert [ ! -e "$QUEUE" ] +} + +@test "refused: outside a git repo" { + local d + d=$(mktemp -d) + run nx --repo "$d" + rm -rf "$d" + assert_failure 1 + assert_output --partial "not in a git repo" +} + +@test "from a linked worktree, it writes the main repo's queue" { + setup_worktree wt-a + cd "$WORKTREE_BASE/wt-a" + nx set 11 >/dev/null + run cat "$QUEUE" + assert_output "11" +} + +@test "--repo names the repo from anywhere" { + cd / + nx set 3 --repo "$MAIN_REPO" >/dev/null + run cat "$QUEUE" + assert_output "3" +} + +@test "the snapshot reads what this command writes, in order, with notes" { + mkdir -p .claude + touch .claude/notion-workflow.md + nx set PROJ-2 PROJ-1 --note PROJ-1='after the release' >/dev/null + run /bin/bash "$SNAP" --local + assert_success + assert_equal "$(jq -r '.repos[0].next.source' <<<"$output")" queue + assert_equal "$(jq -r '[.repos[0].next.items[].ref] | join(" ")' <<<"$output")" "PROJ-2 PROJ-1" + assert_equal "$(jq -r '.repos[0].next.items[1].note' <<<"$output")" "after the release" +} + +@test "task-work takes the ref it dispatches off the queue, whatever form it was queued in" { + setup_stubs + export AW_IMPL=claude-gh + nx set 41 42 43 --note 43=later >/dev/null + run "$TASK_WORK" "https://github.com/owner/repo/issues/42" + assert_success + run queue_file + assert_output "$(printf '41\n43later')" +} + +@test "task-work with no ref leaves the queue alone" { + setup_stubs + export AW_IMPL=claude-gh + nx set 41 >/dev/null + run "$TASK_WORK" "41" + assert_success + run cat "$QUEUE" + assert_output "41" +} + +@test "every PM prompt teaches task-monitor-queue set, and that task-work drops what it dispatches" { + local f + for f in "$REPO_ROOT_REAL"/claude-*/commands/pm.md "$REPO_ROOT_REAL"/.claude/commands/pm.md; do + run grep -c 'task-monitor-queue set .*`task-work` takes it off the queue itself' "$f" + assert_output 1 + done + for f in "$REPO_ROOT_REAL"/kiro-*/agents/pm.json; do + run jq -r '.prompt | test("task-monitor-queue set .*`task-work` takes it off the queue itself")' "$f" + assert_output true + done +} From 41e1e8f78b2b816d3e9351b02dcab9e42c3c0f27 Mon Sep 17 00:00:00 2001 From: Martin Contreras Date: Tue, 20 Oct 2026 15:46:42 -0300 Subject: [PATCH 2/3] =?UTF-8?q?task-monitor=203/6:=20PM=20short-term=20que?= =?UTF-8?q?ue=20(task-monitor=20next=20=E2=80=A6):=20address=20review=20ro?= =?UTF-8?q?und=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Every leading `#` of a ref is stripped, so `##5` is stored as `5` instead of `#5`, which the reader skipped as a comment. - `--` ends the options, so a ref starting with `-` can be passed. - A URL ref's query string and fragment are dropped from its key: a Notion page URL with and without `?pvs=4` is one item. - Documented that `--note` splits on the first `=`, so a ref containing `=` cannot take a note. - One comment naming the accepted race between a PM `set` and the task-work `rm`. Three new tests, each mutation-checked. Co-Authored-By: Claude Opus 5.5 --- .claude/gh-workflow.md | 8 ++-- CHANGELOG.md | 4 +- README.md | 2 +- bin/task-monitor-queue | 42 ++++++++++++++----- claude-gh/steering/gh-workflow.example.md | 8 ++-- claude-jira/steering/jira-workflow.example.md | 8 ++-- .../steering/local-workflow.example.md | 8 ++-- .../steering/notion-workflow.example.md | 8 ++-- kiro-gh/steering/gh-workflow.example.md | 8 ++-- kiro-local/steering/local-workflow.example.md | 8 ++-- .../steering/notion-workflow.example.md | 8 ++-- tests/task_monitor_queue.bats | 27 ++++++++++++ 12 files changed, 101 insertions(+), 38 deletions(-) diff --git a/.claude/gh-workflow.md b/.claude/gh-workflow.md index c6fb08e..e98fb4e 100644 --- a/.claude/gh-workflow.md +++ b/.claude/gh-workflow.md @@ -326,9 +326,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/CHANGELOG.md b/CHANGELOG.md index 756565d..c4ea404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -206,9 +206,9 @@ 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 `** 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 ... [--note =]` 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//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, so `267`, `#267` and `…/issues/267` are one item; `set` keeps the note of a ref that stays queued. `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`). +- **`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 ... [--note =]` 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//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` (21 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 `** to refresh the PM prompt (`commands/pm.md` / `agents/pm.json`) and the workflow doc, which now describes the command. + 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 `** 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 `..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/` 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. diff --git a/README.md b/README.md index ec39a5e..69d9580 100644 --- a/README.md +++ b/README.md @@ -428,7 +428,7 @@ 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, so `267`, `#267` and `…/issues/267` are one item. 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 =` clears it. The file is `$TASK_FORCE_HOME/monitor//queue`, one ref per line with an optional tab-separated note, and `task-monitor-queue` owns that format. +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 =` 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//queue`, one ref per line with an optional tab-separated note, and `task-monitor-queue` owns that format. ## `task-monitor` — the board, live diff --git a/bin/task-monitor-queue b/bin/task-monitor-queue index 577713b..a198af0 100755 --- a/bin/task-monitor-queue +++ b/bin/task-monitor-queue @@ -15,9 +15,9 @@ set -euo pipefail # tracker ref per line, in order, with an optional one-line note after a tab. # Blank lines and `#` comments are ignored on read and never written. # -# Refs are compared by key, not by spelling: a leading `#` and any URL or path -# prefix are dropped, so `267`, `#267` and .../issues/267 are one item, as are -# tasks/012-x.md and 012-x.md. That is what lets task-work drop the ref it +# Refs are compared by key, not by spelling: leading `#`s, a URL's query +# string and fragment, and any URL or path prefix are dropped, so `267`, +# `#267` and .../issues/267 are one item, as are tasks/012-x.md and 012-x.md. That is what lets task-work drop the ref it # dispatches (`rm --quiet `) whatever form the PM queued it in. # # Must run on /bin/bash 3.2: no associative arrays, no mapfile. @@ -55,12 +55,15 @@ The PM's short-term queue for this repo: the "Next" section of task-monitor. clear empty the queue --note = a one-line note for , which must be one of the - refs given or already queued. An empty text clears it + refs given or already queued. An empty text clears it. + It splits on the first `=`, so a ref that itself + contains `=` cannot take a note --quiet print nothing on success --repo DIR the repo containing DIR instead of $PWD A ref is a tracker ref: an issue number, a URL, a Jira key, a task file. -`267`, `#267` and .../issues/267 name the same item. +`267`, `#267` and .../issues/267 name the same item; a URL's query string +and fragment are ignored. Put `--` before a ref that starts with `-`. Writes ${TASK_FORCE_HOME:-~/.task-force}/monitor//queue. EOF @@ -88,6 +91,7 @@ while [[ $# -gt 0 ]]; do [[ $# -ge 2 ]] || { echo "Error: --repo requires a value" >&2; exit 2; } REPO_ARG="$2"; shift 2 ;; -h|--help) usage 0 ;; + --) shift; ARGS+=("$@"); break ;; -*) echo "Error: unknown option: $1" >&2; usage 2 >&2 ;; *) ARGS+=("$1"); shift ;; esac @@ -110,10 +114,23 @@ QUEUE="${TASK_FORCE_HOME:-$HOME/.task-force}/monitor/$(aw_main_repo_name "$ROOT" # ----- helpers -------------------------------------------------------------- -# The identity a ref is compared by: no leading `#`, no trailing `/`, and only -# the last path segment of a URL or a path. +# A ref with every leading `#` gone. One would be enough for `#267`, but the +# stored form must never start with `#`: the reader skips such a line as a +# comment, so `##5` kept as `#5` would vanish from the queue. +_strip_hashes() { + local r="$1" + while [[ "$r" == '#'* ]]; do r="${r#\#}"; done + printf '%s' "$r" +} + +# The identity a ref is compared by: no leading `#`, no query string or +# fragment, no trailing `/`, and only the last path segment of a URL or a path. +# So a Notion page URL and the same URL with `?pvs=4` are one item. _key() { - local k="${1#\#}" + local k + k=$(_strip_hashes "$1") + k="${k%%\?*}" + k="${k%%#*}" k="${k%/}" printf '%s' "${k##*/}" } @@ -121,8 +138,8 @@ _key() { # A ref goes on one line before a tab, so it may hold neither whitespace nor # be empty. _check_ref() { + [[ -n "$(_strip_hashes "$1")" ]] || { echo "Error: empty ref" >&2; exit 2; } case "$1" in - ''|'#') echo "Error: empty ref" >&2; exit 2 ;; *[[:space:]]*) echo "Error: a ref cannot contain whitespace: $1" >&2; exit 2 ;; esac } @@ -156,6 +173,9 @@ _index_of() { return 1 } +# Read-modify-write with no lock: a PM `set` racing task-work's `rm` can lose +# one of the two updates. Accepted: the window is milliseconds, and the PM, the +# only real writer, re-runs `set` whenever the agreed order changes anyway. _write() { local i dir tmp if [[ ${#REFS[@]} -eq 0 ]]; then @@ -231,7 +251,7 @@ case "$CMD" in break fi done - REFS+=("${ref#\#}") + REFS+=("$(_strip_hashes "$ref")") NOTES+=("$note") done _apply_notes @@ -242,7 +262,7 @@ case "$CMD" in for ref in "${ARGS[@]}"; do _check_ref "$ref" _index_of "$ref" >/dev/null && continue - REFS+=("${ref#\#}") + REFS+=("$(_strip_hashes "$ref")") NOTES+=("") done _apply_notes diff --git a/claude-gh/steering/gh-workflow.example.md b/claude-gh/steering/gh-workflow.example.md index 5a0cf9b..0931474 100644 --- a/claude-gh/steering/gh-workflow.example.md +++ b/claude-gh/steering/gh-workflow.example.md @@ -322,9 +322,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/claude-jira/steering/jira-workflow.example.md b/claude-jira/steering/jira-workflow.example.md index 526e7ff..ee03fae 100644 --- a/claude-jira/steering/jira-workflow.example.md +++ b/claude-jira/steering/jira-workflow.example.md @@ -322,9 +322,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/claude-local/steering/local-workflow.example.md b/claude-local/steering/local-workflow.example.md index 6510a88..cb43e35 100644 --- a/claude-local/steering/local-workflow.example.md +++ b/claude-local/steering/local-workflow.example.md @@ -344,9 +344,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/claude-notion/steering/notion-workflow.example.md b/claude-notion/steering/notion-workflow.example.md index ab74850..c1b0379 100644 --- a/claude-notion/steering/notion-workflow.example.md +++ b/claude-notion/steering/notion-workflow.example.md @@ -339,9 +339,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/kiro-gh/steering/gh-workflow.example.md b/kiro-gh/steering/gh-workflow.example.md index 66c8148..dfbe115 100644 --- a/kiro-gh/steering/gh-workflow.example.md +++ b/kiro-gh/steering/gh-workflow.example.md @@ -320,9 +320,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/kiro-local/steering/local-workflow.example.md b/kiro-local/steering/local-workflow.example.md index 706972c..1f33ba1 100644 --- a/kiro-local/steering/local-workflow.example.md +++ b/kiro-local/steering/local-workflow.example.md @@ -342,9 +342,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/kiro-notion/steering/notion-workflow.example.md b/kiro-notion/steering/notion-workflow.example.md index 866bf68..5e12790 100644 --- a/kiro-notion/steering/notion-workflow.example.md +++ b/kiro-notion/steering/notion-workflow.example.md @@ -317,9 +317,11 @@ task-monitor-queue clear # empty task-monitor-queue # show it ``` -Refs are compared by their last path segment, 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 =` clears 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 =` clears +it. `--note` splits on the first `=`, so a ref that contains `=` cannot take a +note. ### PM ↔ worker messaging (radio) diff --git a/tests/task_monitor_queue.bats b/tests/task_monitor_queue.bats index 09be4db..5aafc21 100644 --- a/tests/task_monitor_queue.bats +++ b/tests/task_monitor_queue.bats @@ -207,3 +207,30 @@ queue_file() { sed 's/\t//' "$QUEUE"; } assert_output true done } + +@test "a ref's leading #s are all stripped, so ##5 is stored as 5 and not read back as a comment" { + run nx set '##5' 6 + assert_output "queue: 5 6" + run nx show + assert_output "$(printf '1 5\n2 6')" + run nx set '##' + assert_failure 2 + assert_output --partial "empty ref" +} + +@test "-- ends the options, so a ref may start with -" { + run nx add -- -9 10 + assert_success + assert_output "queue: -9 10" + run nx rm -- -9 + assert_output "queue: 10" +} + +@test "a URL's query string and fragment do not change its key" { + nx set 'https://www.notion.so/acme/Fix-auth-abc123?pvs=4' 7 >/dev/null + run nx rm 'https://www.notion.so/acme/Fix-auth-abc123' + assert_output "queue: 7" + nx set 'https://www.notion.so/acme/Fix-auth-abc123' 7 >/dev/null + run nx add 'https://www.notion.so/acme/Fix-auth-abc123#section' 'https://github.com/acme/widgets/issues/7?x=1' + assert_output "queue: https://www.notion.so/acme/Fix-auth-abc123 7" +} From 334b8bb0151310caced1bbb27f0851f77ca0c126 Mon Sep 17 00:00:00 2001 From: Martin Contreras Date: Tue, 20 Oct 2026 16:14:46 -0300 Subject: [PATCH 3/3] =?UTF-8?q?task-monitor=203/6:=20PM=20short-term=20que?= =?UTF-8?q?ue=20(task-monitor=20next=20=E2=80=A6):=20name=20task-monitor-q?= =?UTF-8?q?ueue=20in=20the=20task-monitor=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After rebasing onto #282: the README's snapshot and `task-monitor` sections and the `task-monitor` block in all seven workflow templates and this repo's workflow doc now say the Next section is the queue the PM writes with `task-monitor-queue`. Co-Authored-By: Claude Opus 5.5 --- .claude/gh-workflow.md | 3 ++- README.md | 4 ++-- claude-gh/steering/gh-workflow.example.md | 3 ++- claude-jira/steering/jira-workflow.example.md | 3 ++- claude-local/steering/local-workflow.example.md | 3 ++- claude-notion/steering/notion-workflow.example.md | 3 ++- kiro-gh/steering/gh-workflow.example.md | 3 ++- kiro-local/steering/local-workflow.example.md | 3 ++- kiro-notion/steering/notion-workflow.example.md | 3 ++- 9 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.claude/gh-workflow.md b/.claude/gh-workflow.md index e98fb4e..ce41d5b 100644 --- a/.claude/gh-workflow.md +++ b/.claude/gh-workflow.md @@ -309,7 +309,8 @@ 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) diff --git a/README.md b/README.md index 69d9580..0226f6d 100644 --- a/README.md +++ b/README.md @@ -410,7 +410,7 @@ 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 · ` 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 `..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 `..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`. @@ -444,7 +444,7 @@ A ref is whatever the tracker uses: an issue number, a Jira key, a Notion page U ↑↓ 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 `..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 `..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 ` and the footer says why. `forge ` 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). diff --git a/claude-gh/steering/gh-workflow.example.md b/claude-gh/steering/gh-workflow.example.md index 0931474..f7103a7 100644 --- a/claude-gh/steering/gh-workflow.example.md +++ b/claude-gh/steering/gh-workflow.example.md @@ -305,7 +305,8 @@ 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) diff --git a/claude-jira/steering/jira-workflow.example.md b/claude-jira/steering/jira-workflow.example.md index ee03fae..88ee962 100644 --- a/claude-jira/steering/jira-workflow.example.md +++ b/claude-jira/steering/jira-workflow.example.md @@ -305,7 +305,8 @@ 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) diff --git a/claude-local/steering/local-workflow.example.md b/claude-local/steering/local-workflow.example.md index cb43e35..333ed72 100644 --- a/claude-local/steering/local-workflow.example.md +++ b/claude-local/steering/local-workflow.example.md @@ -327,7 +327,8 @@ 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) diff --git a/claude-notion/steering/notion-workflow.example.md b/claude-notion/steering/notion-workflow.example.md index c1b0379..e0182f0 100644 --- a/claude-notion/steering/notion-workflow.example.md +++ b/claude-notion/steering/notion-workflow.example.md @@ -322,7 +322,8 @@ 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) diff --git a/kiro-gh/steering/gh-workflow.example.md b/kiro-gh/steering/gh-workflow.example.md index dfbe115..f350ecf 100644 --- a/kiro-gh/steering/gh-workflow.example.md +++ b/kiro-gh/steering/gh-workflow.example.md @@ -303,7 +303,8 @@ 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) diff --git a/kiro-local/steering/local-workflow.example.md b/kiro-local/steering/local-workflow.example.md index 1f33ba1..21b4452 100644 --- a/kiro-local/steering/local-workflow.example.md +++ b/kiro-local/steering/local-workflow.example.md @@ -325,7 +325,8 @@ 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) diff --git a/kiro-notion/steering/notion-workflow.example.md b/kiro-notion/steering/notion-workflow.example.md index 5e12790..cdeb478 100644 --- a/kiro-notion/steering/notion-workflow.example.md +++ b/kiro-notion/steering/notion-workflow.example.md @@ -300,7 +300,8 @@ 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)