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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .claude/gh-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ side and committed once. Which one runs is then a local choice, not a commit:
task-config add assistant kiro # install the other one beside this, settings carried
task-config use kiro # the active assistant: local git config, no tracked file
task-config sync # make the two docs' tracker settings agree again
task-config remove assistant kiro # drop one again: the mirror of add, the kept one goes active
```

`add` keeps the assistant you are on active, so nothing changes until you `use`
Expand All @@ -249,8 +250,13 @@ none active, every dispatcher refuses and names `task-config use`. `show` marks
active loadout and flags **drift**, a tracker setting the two workflow docs disagree
on. `sync` resolves it: the active assistant's doc wins, or `--from <assistant>`
picks the other one, and only the other doc is re-rendered. On a dual repo,
`set assistant A` keeps A and removes the other, `set loadout` on another tracker
replaces both, and `set tracker` is refused with the two steps that do it.
`set assistant A` is refused and changes nothing, `--yes` or not (#303): A is
already installed, so the switch is `use A` and dropping the other is
`remove assistant <other>`, which previews its deletions and confirms on a TTY.
`set loadout` onto the same tracker is refused the same way; onto another tracker
it replaces both, and `set tracker` is refused with the two steps that do it.
`show` prints the switch in one line: `task-config use <other>`, or
`task-work … --agent <other>` for one launch.
`task-remove` removes both and clears the setting. jira cannot be dual until a
kiro-jira loadout exists (#92).

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **`task-monitor-snapshot` works on jq 1.6 again: `$label` is a keyword there (#296).** On a machine with jq 1.6, `task-monitor` showed `snapshot failed` in every jira or notion repo. `_aw_tracker_item_describe_default` in `lib/trackers/_default.sh` bound `--arg label` and read `$label`, and in jq 1.6 `label` is a keyword, so the program failed to compile (`unexpected label, expecting IDENT`). jq 1.7 relaxed this. The empty output then reached the next `--argjson` and failed the whole snapshot. gh and local repos took the same path whenever the forge did not answer, including every `--local` run: 16 of the 51 snapshot tests failed under jq 1.6. The variable is now `$lbl`, and the output is byte-identical. A new test pins the describer's JSON for a key, a URL and a bare slug. With the old name it fails under jq 1.6 and passes under 1.8, so a new CI job, `Bats tests (jq 1.6)`, runs the whole bats suite with the official jq-1.6 binary first on `PATH`. The download is checked against jqlang's sha256 from `sig/v1.6/sha256sum.txt` in the jq repo. The first 1.6 run of the whole suite found a second difference. `jq -e` on empty input exits 0 on 1.6 and 4 on 1.7+, so an empty `zellij action list-tabs --json` answer passed as an array. `task-done` then said `no tab has id=N any more (zellij restarted?)` where it should have said the id could not be verified. It still fails closed, so no tab was ever closed wrongly. The same check is in `aw_zellij_session_running`, where an empty answer read as a running session, and in the snapshot's per-repo check, where an empty repo file passed. All three now go through a slurped check, and both jq versions agree on empty input, `[]`, non-JSON and `{}`. The zellij checks use a new `aw_jq_is_array` in `lib/zellij-tab.sh`, which is stricter than before: it requires exactly one value, where the old check passed a stream whose last value was an array. The snapshot uses `jq -se 'length > 0 and all(type == "object")'`, so a `null` or stray-string repo document is skipped too. New tests cover `aw_jq_is_array` on seven inputs, a live zellij session whose `list-tabs` prints nothing (not running), and `--all` with one repo whose document is empty or a string (skipped, with the warning). Under jq 1.6, putting back the old check turned each of them red. jq 1.6 is now the stated floor, in the README's requirements and in `lib/require-jq.sh`. Not done: the optional per-item degradation, where one item's describe failure blanks only that item. With 1.6 in CI, it would only cover a class of bug CI now catches. **Upgrading:** nothing to run, since `lib/` and `bin/` are read from the checkout. The workaround for anyone not on this version is `brew upgrade jq`.

### Changed
- **`task-config set assistant <one already installed>` in a dual repo is refused, and `task-config remove assistant` drops one explicitly (#303).** Behaviour change from #293. There, `set assistant A` in a repo with both assistants meant "keep A, remove the other". A user testing the dual setup ran `task-config set assistant claude` expecting a switch, confirmed the prompt, and lost `.kiro/agents/*.json` and the kiro workflow doc, so `task-work … --agent kiro` could no longer launch. `--yes` skipped even that prompt. Now, in a dual repo, `set assistant A` (both are always installed there) exits non-zero and changes nothing, `--yes` or not, and the message names both loadouts, `task-config use A` to switch while keeping the other, and `task-config remove assistant <other>` to drop it. `set loadout <A>-<same tracker>` is refused the same way; `set loadout` onto another tracker still replaces both, and `set tracker` stays refused. The explicit path is the new `task-config remove assistant <B>`, the mirror of `add`: it previews what it deletes (with `--dry-run` it stops there), confirms on a TTY, removes only B's artifacts (on the local tracker the shared `tasks/` scaffolding stays, as before), and makes the kept assistant active. It refuses the only assistant installed, an assistant that is not installed, and a repo whose two loadouts are on different trackers. Single-assistant repos are unchanged: `set assistant` still swaps. `task-config show` on a dual repo with an active assistant adds one line, `switch: task-config use <other> · launch one: task-work … --agent <other>`. Tests in `tests/dual_assistant.bats` cover both `set assistant` directions and `set loadout claude-gh` refused with a clean `git status` and the active setting unchanged, the refusal with no `--yes` and no prompt, `remove assistant` touching only `.kiro/` and setting the active assistant, its `--dry-run`, its refusals, a single-assistant swap after a remove, and the `show` hint both ways; the two former "keep A, remove the other" tests became `remove assistant` tests. Skipping the refusal, dropping its `exit`, and dropping `remove`'s active-assistant write each turned tests red. Docs: the README's dual-repo section, `.claude/gh-workflow.md`, all seven steering templates, and `task-config --help`. **Upgrading:** nothing is required, since `bin/` is read from the checkout; re-run `task-init <loadout>` only for the new wording in your workflow doc.
- **The local-loadout PM reads `tasks/_board.md` for the backlog, and the board's Done section is bounded (#294).** The PM's "Showing the backlog" instruction said to read the frontmatter of every `tasks/*.md`, so showing the backlog cost context in proportion to the whole backlog, done tasks included. Both local PM prompts (`claude-local/commands/pm.md`, the kiro-local `agents/pm.json`) now run `task-board` and read the one file it writes, present it as the board has it (grouped by status, priority on each line), and open a task file only when the work is about that task. Staleness is handled by always running `task-board` first rather than by comparing timestamps: the render answers in one line, and an mtime check would miss a change to the `state.json` overlay. `task-board` itself still lists every open task (todo and in-progress) in full, but Done now lists only the 10 newest, highest id first since ids are allocated max+1, followed by one `+ N older done tasks` line. A done task whose id is not a number sorts after all the numbered ones, so it is the first the cap trims. The cap is set by `--done-limit N` or `TASK_BOARD_DONE_LIMIT=N`, the flag winning; `0` lists none and still counts them, and a value that is not a non-negative integer is refused before the board is touched. Todo and In Progress keep their ascending-id order. The planner prompts read only their own task file, so they needed no change. New tests in `tests/task_board.bats` cover all open tasks listed past the cap, the default cap with newest-first order and the count line, the flag, the variable and their precedence, a leading-zero cap read as decimal, a non-numeric id sorting last in Done, a cap of 0, no count line below the cap, and a refused cap; raising the default, sorting ascending, dropping the count line, dropping the base-10 read, keying a non-numeric done id high and ignoring the flag's value each turned at least one of them red. Docs: the README's claude-local section and both local steering templates. **Upgrading: re-run `task-init <loadout>`** on claude-local and kiro-local, since the copied `commands/pm.md` / `agents/pm.json` changed; `task-board` is picked up through the existing symlink, and the next run trims an existing board. The other loadouts need nothing.

- **task-monitor's overview lists every agent under its repo and marks the one that needs you (#287, epic #272).** The overview showed one row per repo, so seeing who was doing what meant pressing ⏎ into each repo in turn. Now each repo row has one line per agent under it: the PM, each in-flight worker, and each reviewer nested under the item whose PR it reviews. Every line gives the agent (`claude` / `kiro`), the state icon (`▶` / `⏸` / `❓`), the state or status words, and the title. `← you` marks the agent behind each attention signal rather than the repo: the PM for `unread-inbox` and `review-waiting-merge`, and the worker that the `worker-awaiting` signal names (by role or slug) for `awaiting`. The signal decides, not the session's `STATE=`, so an `awaiting` left behind by a crashed tab marks nobody. `!` filters the overview to the marked agents under their repo rows, and says `nothing needs you` when nothing does. `t` on an agent line goes to that agent's tab: the worker's sidecar id and slug as in the repo view, and a reviewer's session id with task-reviewer's `review-pr<N>` slug, under the same session and #260 name checks as before. ⏎ on an agent line opens the repo view on its item. **`t` now crosses zellij sessions** (the user's scope addition to #287), reversing the epic's own-session-only rule, in the overview and the repo view alike. For a tab in another session, `goto_tab` lists that session's tabs with `zellij -s <session> action list-tabs --json`, applies the #260 name check, and runs `zellij action switch-session <session> --tab-position <n>`. That moves the client, and the monitor stays in its own session. These were probed on zellij 0.44.2 using throwaway sessions and a pty-attached client. `go-to-tab-by-id` sent to a session with no client attached is ignored: the client that attaches afterwards lands on that session's previous tab. So the switch carries the tab itself. `--tab-position` is 1-based: 1, 2 and 3 focused list positions 0, 1 and 2. The v0.44.2 source agrees: `Screen::go_to_tab` calls `switch_active_tab(tab_index.saturating_sub(1), …)` on the field list-tabs reports as `position`. `switch-session` to a missing session creates it and exits 0, while `list-tabs` against one prints plain text and exits 0. So a `list-tabs` answer that does not parse is refused as `session '<s>' is not running`, and no failed check ever reaches `switch-session`. Tests pin the exact command sequence and that a stale id or a dead session issues no switch. Breaking the 1-based offset, the session comparison, the dead-session refusal and the name check each turned a test red. A narrow terminal cuts the title and keeps `← you`. The overview's selection now follows its row across refreshes and filter toggles. It is keyed by repo name, worker slug and reviewer role, and falls back to the row's index when one is missing, so two nameless rows never share a key. Front end only: `snapshot.rs` now reads the fields the collector already emits for each signal (`count`, `prs`, `workers`). The `multi.json` fixture gains the `worker-awaiting` workers the collector emits, a kiro PM and task-force's own `worker-awaiting` signal. New Rust tests cover the row order, which agent each signal marks, the filter, `t` and ⏎ on agent rows, selection across a reorder, and rendering at 120 and 60 columns. Each of the nine mutations tried (dropping a PM signal, the role match or the slug match, the empty-repo skip, the reviewer slug, the reviewer rows, `← you` on narrow rows, the awaiting words, and ⏎'s item selection) turned a test red. Docs: the README `task-monitor` section (an overview mockup, its keys and the cross-session `t`), the `task-monitor` paragraph in `.claude/gh-workflow.md` and all seven steering templates, and `task-monitor --help`. **Upgrading:** re-run your loadout's `install.sh` to rebuild the binary. Re-run `task-init <loadout>` only if you want the refreshed wording in your workflow doc.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ task-config add assistant kiro # install kiro-<tracker> beside claude-<tracker
task-config use kiro # git config task-force.assistant kiro: no tracked file changes
task-config use claude # and back
task-config sync # make the two docs' tracker settings agree again
task-config remove assistant kiro # drop one again: the mirror of add, the kept one goes active
```

`add` keeps the assistant you are on active, so nothing changes until you `use` the other. Both change only the main checkout. A live worktree keeps the files it was forked with, so `add` and `use` end by listing every worker worktree that has no radio hooks for the new assistant, each with the `git -C <worktree> merge <base>` it needs, and say so when the new loadout is not committed yet, since a merge brings only what is (#304). It is a warning, not a refusal. `task-recreate-worker` refuses to launch there, and `task-work --from` refuses to fork from such a ref. A clone that has both committed but no local choice yet is refused by every dispatcher, with a pointer to `task-config use`. Linked worktrees share the repo's git config, so a worker's worktree resolves the same way its main checkout does.
Expand All @@ -334,7 +335,7 @@ Two loadouts on different trackers stay refused, whatever the active assistant s

**Drift.** The tracker settings are written into both workflow docs. `add` and `set` keep them identical, but a hand edit to one leaves the two assistants pointed at different boards. `task-config show` compares them on a dual repo, names every field that differs with both values, and exits non-zero. There is one way to resolve it: `task-config sync`. The active assistant's doc wins, or `sync --from <assistant>` picks the other one. Only the other doc is re-rendered, through its own `task-init --force --workflow` with the settings as flags.

**`set` and `task-remove` on a dual repo.** `set assistant A` keeps A and removes the other. `set loadout` on another tracker removes both and installs the one named. `set tracker` is refused with the two commands that do it (`set loadout <a>-<t>`, then `add assistant`), because it would have to reinstall both. `task-remove` removes both, as it removes every detected loadout, and clears the active-assistant setting. On the local tracker the pair shares the `tasks/` scaffolding and the `.gitignore` entry, so those stay while either loadout does and go with the last.
**`set`, `remove` and `task-remove` on a dual repo.** `set assistant A` is refused and changes nothing, `--yes` or not (#303). A is already installed, so the command can only mean one of two things, and #293 guessed the destructive one: a user testing the dual setup ran `set assistant claude` expecting a switch and lost every kiro file. The refusal names both: `task-config use A` switches and keeps the other, and `task-config remove assistant <other>` drops the other. `remove assistant` is the mirror of `add`: it previews what it deletes, confirms on a TTY, and makes the kept assistant active. It refuses the only assistant installed, since that would leave no loadout (use `set assistant` or `task-remove`). `set loadout` onto the same tracker is refused like `set assistant`; onto another tracker it removes both and installs the one named. `task-config show` on a dual repo prints the switch in one line: `switch: task-config use <other> · launch one: task-work … --agent <other>`. In a single-assistant repo `set assistant` still swaps the one for the other. `set tracker` is refused with the two commands that do it (`set loadout <a>-<t>`, then `add assistant`), because it would have to reinstall both. `task-remove` removes both, as it removes every detected loadout, and clears the active-assistant setting. On the local tracker the pair shares the `tasks/` scaffolding and the `.gitignore` entry, so those stay while either loadout does and go with the last.

jira cannot be dual until a kiro-jira loadout exists (#92): `add assistant kiro` on a claude-jira repo is refused with the same message as `set`.

Expand Down
Loading
Loading