cyberfleet 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberfleet",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Harness-agnostic, MCP-free inter-agent sessions and messaging over the filesystem",
5
5
  "keywords": [
6
6
  "agent",
@@ -38,6 +38,7 @@
38
38
  "devDependencies": {
39
39
  "@types/node": "^24.10.1",
40
40
  "commander": "^15.0.0",
41
+ "cyber-mux": "^0.12.0",
41
42
  "cyberlegion": "^1.7.0",
42
43
  "tsdown": "^0.23.0",
43
44
  "tsx": "^4.22.3",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "cyberfleet",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "description": "Fleet & crew personas over cyberlegion — Pod (ship's bridge), Operator (command center), Crimp (crew recruitment), and Mechanic (automaton building).",
6
6
  "author": {
7
7
  "name": "unional"
package/readme.md CHANGED
@@ -26,8 +26,8 @@ npx cyberfleet@0.0.4 <command>
26
26
  npx cyberfleet missions
27
27
  npx cyberfleet missions --agents-root <path>
28
28
 
29
- # Select/focus a ship's session (tmux pane), or print its worktree path to cd into
30
- npx cyberfleet jump <peer>
29
+ # Focus the one live unit a repo, branch, unit, or task names, or go back along the last jump
30
+ npx cyberfleet jump <target> [--repo <name>] [--branch <name>]... [--mentions <n>]
31
31
 
32
32
  # Pause a ship's mission — a cyberfleet-level status marker only (NOT the SDD pause-mission checkpoint)
33
33
  npx cyberfleet pause <peer>
@@ -112,7 +112,8 @@ Under the automatons sit two CLIs — cold, deterministic mechanism with no judg
112
112
  **`cyberfleet`** is the thin fleet layer on top, and carries only what is fleet-specific:
113
113
 
114
114
  - `missions` — the Council view: ships × mission × gate × leash, derived from SDD state
115
- - `jump` — focus a ship's pane, or print its worktree path to `cd` into
115
+ - `jump` — focus the one live unit a repo, branch, unit, or task names, or go `back`; on several
116
+ matches, a task ref, or free text it focuses nothing and returns what the `/jump` skill decides
116
117
  - `pause` — mark a ship's mission paused (a status marker only, not an SDD mission checkpoint)
117
118
  - `gate approve` — deliberately stubbed: a Council ratification can't be safely relayed through a
118
119
  CLI, so it refuses and tells you to ratify in-session
@@ -12,15 +12,20 @@ for retiring a pod's record: that belongs to the pod's Captain (`cyberfleet pod
12
12
 
13
13
  ## What it does
14
14
 
15
- - Reads every unit with `cyberlegion unit who --all --format json`.
15
+ - Reads every unit with `cyberlegion unit who --all --format json`, and acts only on units in the
16
+ caller's current multiplexer session (records do not yet store the multiplexer session, cyberuni/cyberlegion#241, so
17
+ units whose pane `mux` matches the caller's). Other units are kept, since another session may resume them.
18
+ - Fetches pull requests once per repository and matches branches locally, instead of one `gh` call per unit.
16
19
  - Classifies each unit by its worktree and its branch's pull requests. A unit is closable only when
17
20
  every pull request from its branch is merged or closed and its `HEAD` is contained in a pull
18
- request's head commit. A unit whose worktree was deleted is closed only once its session has exited.
21
+ request's head commit. Mechanical evidence decides first. A unit whose worktree was deleted is
22
+ closed only once its session has exited.
19
23
  - Lists a landed unit with leftover uncommitted files separately, with the files, and closes it
20
24
  (discarding them) only when the Council names it.
21
- - Reads each close candidate's Claude Code transcript. A unit active in the last hour is kept. A unit
22
- the Council typed into is listed with its last exchange and closed only when the Council names it,
23
- so a conversation still in progress is never cut off.
25
+ - Asks each live unit that passes the mechanical close one question by mail: "Do you have unfinished
26
+ work? Reply DONE or UNFINISHED: <reason>". A unit the multiplexer shows working is kept without
27
+ asking. UNFINISHED or no reply keeps the unit, with the reason listed. A reply never closes a unit
28
+ the evidence keeps. It never types into a pane or reads its screen, and needs no transcript or hook.
24
29
  - Keeps this session, standing owners, units with no worktree, and every session in a main
25
30
  checkout (Captain, Coordinator, Operator, the Council's desk).
26
31
  - Shows the Council the close, ask, and keep groups, and asks about the close and ask groups
@@ -7,16 +7,21 @@ model: haiku
7
7
  # Close sessions
8
8
 
9
9
  Close every agent session whose work has landed, and keep every session that might still have work.
10
- This is a mechanical sweep: each step below is a command and a rule, with no judgment call in it.
10
+ This is a mechanical sweep: each step below is a command and a rule. The one question to a unit is
11
+ mail, and its answer can only keep the unit.
11
12
 
12
13
  A unit is closable only when **all** of its work is visible on the host as finished: every pull
13
14
  request from its branch is merged or closed, and none of its commits are missing from those pull
14
- requests. A unit whose work landed but whose worktree still has uncommitted files is shown to the
15
- Council with those files, and closed only if the Council says so. So is a unit the Council was talking
16
- with, since that conversation may not be finished. When a check cannot be run or reads
17
- unclear, keep the unit. Closing destroys the session, its inbox,
15
+ requests. Mechanical evidence always decides first; nothing a unit says can close one the evidence keeps.
16
+ A live unit that passes that evidence is then asked one question (step 4), because the unit is the best
17
+ judge of whether it has unfinished work. A unit whose work landed but whose worktree still has
18
+ uncommitted files is shown to the Council with those files, and closed only if the Council says so.
19
+ When a check cannot be run or reads unclear, keep the unit. Closing destroys the session, its inbox,
18
20
  and its worktree, and cannot be undone. Keeping a finished unit only costs a pane.
19
21
 
22
+ Act only on units in the caller's current multiplexer session. Another session or multiplexer may
23
+ resume those agents, and this skill cannot know.
24
+
20
25
  ## 1. Resolve `cyberlegion`
21
26
 
22
27
  Take the first rung that resolves and whose `cyberlegion --version` is `1.6.0` or later:
@@ -31,9 +36,20 @@ When no rung resolves, stop and tell the Council to install the `cyberlegion` pl
31
36
  ## 2. Read the fleet
32
37
 
33
38
  1. `cyberlegion unit prune` marks units whose pane is gone as exited.
34
- 2. `cyberlegion unit whoami` gives this session's own id.
35
- 3. `cyberlegion unit who --all --format json` lists every unit. Each record has `id`, `handle`,
36
- `harness`, `status`, `pane`, `cwd`, `conversation`, and `worktree` (`root` and `branch`).
39
+ 2. `cyberlegion unit whoami` gives this session's own id and handle.
40
+ 3. Name the caller's multiplexer: `herdr` when `HERDR_ENV` is set, `tmux` when `TMUX` is set.
41
+ When neither is set, stop and tell the Council.
42
+ 4. `cyberlegion unit who --all --format json` lists every unit. Each record has `id`, `handle`,
43
+ `harness`, `status`, `cwd`, a pane, and `worktree` (`root` and `branch`). The pane is under `pane`
44
+ (`mux` and `id`), or, in records written by older versions, under the legacy `tmux` key. Read it from
45
+ whichever is present. The `conversation` field is optional, and this skill does not use it.
46
+ 5. Fetch pull requests once per repository, not once per unit. For each distinct repository among the
47
+ in-scope units, run inside one of its worktrees:
48
+ `gh pr list --state all --limit 1000 --json number,state,headRefOid,headRefName`.
49
+ Match each unit's branch to `headRefName` locally. Per-unit `gh` calls made a 400-unit sweep take
50
+ about 7 minutes.
51
+ 6. When the caller's multiplexer is `herdr`, read each live in-scope pane's state once, with
52
+ `herdr pane list` (or `herdr pane get <pane id>`). It reports `agent_status` from metadata. Never read a pane's screen.
37
53
 
38
54
  ## 3. Classify each unit
39
55
 
@@ -42,88 +58,81 @@ Apply these rules in order to each unit. The first rule that matches decides it.
42
58
  | # | Check | Result |
43
59
  | - | ----- | ------ |
44
60
  | 1 | `id` is this session's own id | keep: this session |
45
- | 2 | It is a standing owner (no `pane`, and no `harness` field or `harness` is `-`) | keep: standing owner |
46
- | 3 | It has no `worktree` | keep: no worktree |
47
- | 4 | `worktree.root` does not exist on disk, and `status` is not `exited` | keep: worktree gone, session live |
48
- | 5 | `worktree.root` does not exist on disk, and `status` is `exited` | close: worktree gone, session ended |
49
- | 6 | Either `git -C <root> rev-parse --git-dir` or `git -C <root> rev-parse --git-common-dir` fails | keep: cannot read worktree |
50
- | 7 | Those two commands print the same path | keep: primary checkout |
51
- | 8 | `gh pr list --head <branch> --state all --json number,state,headRefOid` fails, run inside `<root>` | keep: cannot read pull requests |
52
- | 9 | That list is empty | keep: no pull request yet |
53
- | 10 | Any pull request in it has state `OPEN` | keep: pull request open |
54
- | 11 | `git -C <root> merge-base --is-ancestor HEAD <headRefOid>` exits non-zero for every pull request in the list | keep: commits after the last pull request |
55
- | 12 | `git -C <root> status --porcelain --untracked-files=all` prints any line other than `?? .agents/cyberlegion/config.json` | ask: work landed, leftover files |
56
- | 13 | Anything else (every pull request is `MERGED` or `CLOSED`, `HEAD` is inside one of them, and the worktree is clean) | close: pull requests merged or closed |
57
-
58
- Rules 4 and 5 handle a worktree that was deleted from disk. Nothing is left on disk to check, so
61
+ | 2 | It is a standing owner (no pane, and no `harness` field or `harness` is `-`) | keep: standing owner |
62
+ | 3 | Its pane is absent, or its `mux` (or legacy `tmux` key) is not the caller's multiplexer | keep: out of scope |
63
+ | 4 | It has no `worktree` | keep: no worktree |
64
+ | 5 | `worktree.root` does not exist on disk, and `status` is not `exited` | keep: worktree gone, session live |
65
+ | 6 | `worktree.root` does not exist on disk, and `status` is `exited` | close: worktree gone, session ended |
66
+ | 7 | Either `git -C <root> rev-parse --git-dir` or `git -C <root> rev-parse --git-common-dir` fails | keep: cannot read worktree |
67
+ | 8 | Those two commands print the same path | keep: primary checkout |
68
+ | 9 | The repository's pull request list (step 2.5) failed to load | keep: cannot read pull requests |
69
+ | 10 | No pull request in that list has `headRefName` equal to the branch | keep: no pull request yet |
70
+ | 11 | Any matching pull request has state `OPEN` | keep: pull request open |
71
+ | 12 | `git -C <root> merge-base --is-ancestor HEAD <headRefOid>` exits non-zero for every matching pull request | keep: commits after the last pull request |
72
+ | 13 | `git -C <root> status --porcelain --untracked-files=all` prints any line other than `?? .agents/cyberlegion/config.json` | ask: work landed, leftover files |
73
+ | 14 | Anything else (every pull request is `MERGED` or `CLOSED`, `HEAD` is inside one of them, and the worktree is clean) | close: pull requests merged or closed |
74
+
75
+ Rules 5 and 6 handle a worktree that was deleted from disk. Nothing is left on disk to check, so
59
76
  the session's own state decides: a live session may still be in use, and an exited one has nothing
60
77
  left to lose.
61
78
 
62
- Rule 7 keeps every session in a project's main checkout: a Captain, a Coordinator, the Operator, or
79
+ Rule 3 is the scope rule. Records do not yet store the multiplexer session (cyberuni/cyberlegion#241),
80
+ so scope is the multiplexer kind. A record with no pane, or with only the legacy `tmux` key while the caller is
81
+ on herdr, is out of scope. herdr restores panes with the same ids and resumes Claude itself, so a
82
+ unit on another herdr session may come back.
83
+
84
+ Rule 8 keeps every session in a project's main checkout: a Captain, a Coordinator, the Operator, or
63
85
  the Council's own desk. Those are standing seats, not spawned units, so this skill never closes one.
64
86
 
65
- Rule 12 catches a unit whose work landed but left files behind: build output, scratch files, an edit it
87
+ Rule 13 catches a unit whose work landed but left files behind: build output, scratch files, an edit it
66
88
  never committed. Those are often safe to drop, but only the Council can say, so the unit is listed
67
89
  with its files and closed only on the Council's word. The one line it skips is the marker cyberlegion
68
90
  stamps into every worktree it spawns; `cyberlegion unit close` sets that file aside too, so it never
69
91
  counts as leftover work.
70
92
 
71
- Rule 11 is the unpushed-work check. It compares the local `HEAD` with each pull request's head commit
93
+ Rule 12 is the unpushed-work check. It compares the local `HEAD` with each pull request's head commit
72
94
  rather than with the remote branch, because the host usually deletes a branch once its pull request
73
95
  merges. One pull request containing `HEAD` is enough. A head commit missing locally makes that
74
96
  command exit non-zero; it counts like any other non-zero exit.
75
97
 
76
98
  Do not retry a failed command. A failure keeps the unit, and the next sweep checks it again.
77
99
 
78
- A held merge always keeps its unit, because a held pull request is still open (rule 10).
100
+ A held merge always keeps its unit, because a held pull request is still open (rule 11).
79
101
 
80
- ## 4. Check the conversation
102
+ `stopped` is not `exited`. A stopped unit was kept on purpose so it can be restarted.
81
103
 
82
- A unit's pull requests can land while the Council is still talking with it: a question the unit asked
83
- is unanswered, or a decision lives only in its conversation. Closing ends that conversation. Run these
84
- checks on every unit that step 3 put in close or ask. The first rule that matches decides it.
104
+ ## 4. Ask the live units
85
105
 
86
- | # | Check | Result |
87
- | - | ----- | ------ |
88
- | C1 | `harness` is not `claude` | ask: conversation not checked |
89
- | C2 | No transcript is found (below), and `status` is `exited` | no change |
90
- | C3 | No transcript is found, and `status` is not `exited` | ask: conversation not found |
91
- | C4 | The transcript was modified in the last 60 minutes | keep: conversation active |
92
- | C5 | The Council-turns command (below) prints anything | ask: the Council talked to it |
93
- | C6 | Anything else | no change |
94
-
95
- **Find the transcript.** It is `~/.claude/projects/*/<conversation>.jsonl`, where `<conversation>` is
96
- the unit's `conversation` field. When the unit has no `conversation`, or that matches no file, no
97
- transcript is found. Do not substitute another file from the same folder: a worktree is reused by
98
- many units, so the newest transcript there often belongs to a different one.
106
+ An **exited** unit's step-3 result stands: its session ended, so there is nobody to ask.
99
107
 
100
- `stopped` is not `exited`. A stopped unit was kept on purpose so it can be restarted.
108
+ For each **live** unit (`status` is not `exited`) that step 3 put in **close**:
109
+
110
+ 1. When the multiplexer shows it working, keep it: **keep: working**. On herdr that is `agent_status`
111
+ `working` or `blocked`. Skip the question.
112
+ 2. Otherwise mail it one structured question on a fresh thread:
101
113
 
102
- **Council turns** are the turns a person typed into the session, apart from the spawn brief and the
103
- mail doorbell. A slash command the Council ran counts. Print them with:
114
+ ```sh
115
+ cyberlegion mail send --to <id> --thread close-<id>-<timestamp> --subject "Closing?" \
116
+ --body "Do you have unfinished work? Reply with: cyberlegion mail send --to <caller handle> --thread <thread> --body 'DONE' (or --body 'UNFINISHED: <reason>')"
117
+ ```
104
118
 
105
- ```sh
106
- jq -rR 'fromjson? | select(.type=="user" and .origin.kind=="human" and (.isMeta|not))
107
- | .message.content
108
- | if type=="string" then . else ([.[]? | select(.type=="text") | .text] | join(" ")) end
109
- | select(length>0)
110
- | select(startswith("Read your brief at")|not)
111
- | select(startswith("You have unread mail")|not)
112
- | .[0:200]' <transcript>
113
- ```
119
+ `<caller handle>` is the handle from `unit whoami`, and `<thread>` is the same thread id. A reply
120
+ sent without that thread never matches the wait.
114
121
 
115
- Print the unit's last reply with:
122
+ Send the questions to all such units first. Then wait on each thread with
123
+ `cyberlegion mail await --thread <thread> --timeout <ms>`, giving each the remaining share of a
124
+ total budget of about 2 minutes (120000 ms), not 2 minutes per unit. `await` prints the reply and acks
125
+ it; it exits non-zero on timeout.
126
+ 3. Read the reply:
127
+ - `DONE` → the unit stays in **close**.
128
+ - `UNFINISHED: <reason>` → **keep: unit reports unfinished work**, and show the reason.
129
+ - No reply in time, or a reply that is neither → **keep: no answer**.
116
130
 
117
- ```sh
118
- jq -rR 'fromjson? | select(.type=="assistant")
119
- | [.message.content[]? | select(.type=="text") | .text] | join(" ")
120
- | select(length>0)' <transcript> | tail -n 3
121
- ```
131
+ A reply can only keep a unit. It never moves a unit from keep to close, and never overrides
132
+ rule 13: a live unit that passed the mechanical close but has leftover files stays in **ask**, and is
133
+ not mailed.
122
134
 
123
- C2 lets an exited unit with no transcript keep its step-3 result: its session ended and no
124
- conversation is left on disk, so closing it loses nothing more. C4 keeps a unit that is working or
125
- being talked to right now. C5 sends every unit the Council spoke to back to the Council, because only
126
- the Council knows whether that conversation finished.
135
+ Never type into a pane or read its screen. The mail is the only way this skill talks to a unit.
127
136
 
128
137
  ## 5. Confirm with the Council
129
138
 
@@ -131,15 +140,13 @@ Show the units in three groups: **close**, **ask**, then **keep**. Each row give
131
140
  handle, the branch, the result, and the pull request numbers. Handles are not unique, so the id is what
132
141
  identifies a unit. Under each ask row, show what the Council needs to decide:
133
142
 
134
- - for leftover files (rule 12): the lines rule 12 counted, the first 10, then how many more;
135
- - for the Council talked to it (C5): its last 3 Council turns and the last 3 lines of its last reply.
143
+ - for leftover files (rule 13): the lines rule 13 counted, the first 10, then how many more;
136
144
 
137
- A unit can have both. Then ask two questions, in this order:
145
+ Then ask two questions, in this order:
138
146
 
139
147
  1. "Close these N units?" for the close group.
140
- 2. "These M units need your look before closing: their leftover files or their last exchange with
141
- you are listed above. Closing one ends its conversation and deletes its worktree, with any
142
- leftover files. Which should I close?"
148
+ 2. "These M units have leftover files, listed above. Closing one deletes its worktree with those
149
+ files. Which should I close?"
143
150
 
144
151
  The Council may drop units from either list. A yes to the first question never covers the ask group.
145
152
  Close only the units the Council's answer names or covers. If an answer names a handle that more than
@@ -147,20 +154,19 @@ one candidate shares, ask which id it means. If an answer is not a yes, close no
147
154
 
148
155
  ## 6. Close
149
156
 
150
- For each confirmed unit, run `cyberlegion unit close <id>`. Add `--force` only for a unit that rule 12
157
+ For each confirmed unit, run `cyberlegion unit close <id>`. Add `--force` only for a unit that rule 13
151
158
  put in ask, which discards its leftover files.
152
159
 
153
- - Pass `--force` only for a rule-12 unit the Council confirmed in answer to the second question.
160
+ - Pass `--force` only for a rule-13 unit the Council confirmed in answer to the second question.
154
161
  Never pass `--keep-worktree`.
155
162
  - If a close refuses (for example, a close-group worktree that became dirty since step 3), report the
156
163
  refusal and move on. Do not retry it, add `--force`, or reach around it.
157
164
  - Never run `cyberfleet pod retire`. A pod's record belongs to its Captain, who retires it.
158
- - Never nudge, mail, or read the screen of a unit to decide whether it is finished.
165
+ - Mail a unit only with the step-4 question. Never type into a pane or read its screen.
159
166
 
160
167
  ## 7. Report
161
168
 
162
169
  Lead with the count closed and the count kept (an ask-group unit the Council did not confirm counts as
163
- kept). List the closed handles, each with its transcript path when step 4 found one: closing does not
164
- delete the transcript, so the conversation can still be read. Then list the kept units
165
- grouped by result, so the Council can see which ones are waiting on a pull request and which ones are
166
- waiting on them.
170
+ kept). List the closed handles. Then list the kept units grouped by result, so the Council can see which
171
+ ones are waiting on a pull request, which reported unfinished work (with the reason), and which are
172
+ out of scope.
@@ -1,144 +1,97 @@
1
1
  ---
2
2
  name: jump
3
- description: "Use this skill when the Council runs /jump, or asks to go to, switch to, or show the pane working on a repo, a branch, a pod, an issue or pull request (`#123`, `color-map#123`), or a task described in words — and to go back to the pane the last jump came from (`/jump back`). Not for opening a new pane or spawning a pod, and not for messaging a unit."
3
+ description: "Moves the attached view to the one pane working on a repo, a branch, a pod, a GitHub issue or pull request (`#123`, `color-map#123`), or a task described in words, and back along the last jump. Use when the Council runs /jump, or asks to go to, switch to, or show the pane working on one of these, or to go back to the pane the last jump came from (`/jump back`). Not for opening a new pane or spawning a pod, and not for messaging a unit."
4
4
  argument-hint: "[repo|branch|pod|task|back|<prose>]"
5
5
  ---
6
6
 
7
7
  # Jump
8
8
 
9
9
  Move the attached view to the one pane working on what the argument names, or back along the last
10
- jump. Every jump goes through the `cyberlegion` CLI, so cyber-mux records where the view came from
11
- and `back` can return there.
10
+ jump. `cyberfleet jump` does the matching and the focusing. This skill does the two steps the
11
+ command leaves to it: the GitHub lookup for a task ref, and matching free text to a unit.
12
12
 
13
13
  The one rule above all others: **more than one match lists the candidates and stops.** Never pick
14
- one, not even the newest or the most likely. A jump to the wrong pane interrupts the Council's
15
- attention in the wrong place, and the Council can always narrow the argument.
14
+ one, not even the newest or the most likely.
16
15
 
17
- ## Resolving `cyberlegion`
16
+ ## Running `cyberfleet`
18
17
 
19
- Every `cyberlegion …` command below runs whichever CLI resolves here, resolved afresh in each
20
- session and again after any plugin reload. Never hardcode a versioned
21
- `~/.claude/plugins/cache/…/<version>/` path: a reload installs a new version beside the old one, and a
22
- remembered path keeps running the old CLI. The pin is `1.7.0`, the first release with
23
- `unit focus --repo` and `--branch`. Take the first rung that resolves and whose
24
- `cyberlegion --version` is at or above it; skip a rung that reports an older version:
18
+ This plugin ships the `cyberfleet` CLI, so its version always matches this skill. Run it from this
19
+ skill's directory, with no install:
25
20
 
26
- 1. `cyberlegion` on `PATH`.
27
- 2. The installed cyberlegion plugin: read `~/.claude/plugins/installed_plugins.json` now, take the
28
- `installPath` of its `cyberlegion@<marketplace>` entry, and run `node
29
- <installPath>/bin/cyberlegion.mjs`.
30
- 3. `npx -y cyberlegion@1.7.0`.
21
+ ```bash
22
+ node <this skill's directory>/../../bin/cyberfleet.mjs jump <argument>
23
+ ```
31
24
 
32
- When no rung resolves, stop and report it to the Council with the install hint: install the
33
- `cyberlegion` plugin beside this one, or `npm install -g cyberlegion@1.7.0`.
25
+ Every `cyberfleet jump …` below means that command. Add `--format json` to read the result as JSON;
26
+ its `result` field names the kind.
34
27
 
35
- ## Resolving `cyber-mux`
28
+ ## 1. Run `cyberfleet jump <argument>`
36
29
 
37
- Only `back` needs `cyber-mux` directly; every other form reaches it through `cyberlegion`. Resolve it
38
- the same way, afresh each time. The pin is `0.11.0`, the first release with `focus --back`. Take the
39
- first rung that resolves and whose `cyber-mux --version` is at or above it:
30
+ Claude Code appends the argument as `ARGUMENTS: <value>`; other runtimes pass the text after the
31
+ skill name. With no argument, ask the Council where to jump and stop.
40
32
 
41
- 1. `cyber-mux` on `PATH`.
42
- 2. The installed cyber-mux plugin: read `~/.claude/plugins/installed_plugins.json` now, take the
43
- `installPath` of its `cyber-mux@<marketplace>` entry, and run `node <installPath>/bin/cyber-mux.mjs`.
44
- 3. `npx -y cyber-mux@0.11.0`.
33
+ Pass the argument as it came, quoted as one value. The command prints one result:
45
34
 
46
- When no rung resolves, stop and report it with the install hint: install the `cyber-mux` plugin, or
47
- `npm install -g cyber-mux@0.11.0`.
35
+ | `result` | What it means | Next |
36
+ | -------- | ------------- | ---- |
37
+ | `jumped` | It focused one unit: its `id`, `handle`, and `pane` (`back` also gives `from`). | Step 4 |
38
+ | `candidates` | More than one unit matched. Nothing was focused. | Step 4 |
39
+ | `task` | A task ref: its `repo` name, `slug` (`<owner>/<repo>` from that checkout's `origin`), and `number`. Nothing was focused. | Step 2 |
40
+ | `prose` | No exact match: the `words`, and the live units with `id`, `handle`, `branch`, `repo`, and `brief` (a path). Nothing was focused. | Step 3 |
48
41
 
49
- ## 1. Read the argument
42
+ A non-zero exit is an error. Go to step 4 with it.
50
43
 
51
- Claude Code appends it as `ARGUMENTS: <value>`; other runtimes pass the text after the skill name.
52
- With no argument, ask the Council where to jump and stop.
44
+ The command already applies these rules, so do not repeat them by hand: it never focuses this
45
+ session or lists it, it jumps past this session when this session was one of the matches, a bare
46
+ word must exactly equal a unit's id, handle, branch, or repo name, and `back` follows the jump
47
+ history for this pane.
53
48
 
54
- The argument may start with a form word that forces the form: `repo <name>`, `branch <name>`,
55
- `pod <handle>`, or `task <ref>`. Use one when a bare argument was ambiguous across forms. Without
56
- one, decide the form in this order:
49
+ ## 2. On `task`, look up the branches with `gh`
57
50
 
58
- | # | Argument | Form |
59
- | - | -------- | ---- |
60
- | 1 | exactly `back` | Back |
61
- | 2 | `#123`, `123`, or `<repo>#123` | Task |
62
- | 3 | one word that exactly equals a live unit's id, handle, branch, or repo name | Repo, branch, or pod |
63
- | 4 | anything else | Prose |
51
+ A task is a GitHub issue or pull request; one number names exactly one of them. This step needs an
52
+ authenticated `gh`. When `gh` is missing or not logged in, quote its error and stop.
64
53
 
65
- ## 2. Read the fleet
66
-
67
- Every form except Back starts from the same list:
68
-
69
- - `cyberlegion unit who --format json` lists the live units. Each record has `id`, `handle`,
70
- `status`, `pane`, `cwd`, `brief` (a path to the unit's brief file, when it was spawned with one),
71
- and `worktree` (`root`, `branch`, and `primaryRoot`).
72
- - A unit's **repo name** is the folder name of `worktree.primaryRoot`, or of `cwd` when it has no
73
- `primaryRoot`. This is the rule `unit focus --repo` uses, so the skill and the CLI agree.
74
- - `cyberlegion unit whoami` gives this session's own id, when it has one. Leave this session out of
75
- every candidate list the skill builds itself: a jump to the pane you are in does nothing.
76
-
77
- ## 3. Jump
78
-
79
- ### Back
80
-
81
- Run `cyber-mux focus --back` from this session's shell. It follows cyber-mux's record of the pane the
82
- last jump into this pane came from. Report its result. If it fails (for example, `no jump into <pane>`),
83
- quote the error and its next-step hint verbatim and stop; never jump somewhere else instead.
84
-
85
- `/jump back` waits for this session's turn, so in a busy agent's pane it is slow. The tmux key that
86
- cyber-mux's `setup-tmux` binds to `focus --back` is the fast path; mention it when the Council uses
87
- `back` often.
88
-
89
- ### Repo, branch, or pod
90
-
91
- Collect the live units the word matches exactly, and note which field each matched:
92
-
93
- - repo name → `cyberlegion unit focus --repo <name>`
94
- - branch → `cyberlegion unit focus --branch <name>`
95
- - id or handle → the pod form: `cyberlegion unit focus <id>`, using the matched unit's id. Handles
96
- are not unique, so never pass the handle itself.
97
-
98
- If every match is in one field, run that field's command. `unit focus --repo` and `--branch` fail on
99
- their own when more than one unit matches, listing each candidate. If the word matches in more than
100
- one field, or names more than one unit by handle, list the candidates and stop.
101
-
102
- ### Task
103
-
104
- A task is an issue or pull request ref. GitHub numbers issues and pull requests in one sequence, so a
105
- ref names one of them.
106
-
107
- 1. **Find the repo.**
108
- - `#123` or `123`: the caller's own repo. Its name is the folder name of the primary checkout
109
- (`dirname "$(git rev-parse --path-format=absolute --git-common-dir)"`), and its `<owner>/<repo>`
110
- comes from `git remote get-url origin`.
111
- - `color-map#123`: the live units whose repo name is `color-map`. Read `<owner>/<repo>` from
112
- `git -C <primaryRoot or cwd> remote get-url origin` of those checkouts. No unit with that repo
113
- name: fail naming it. Checkouts whose `origin` remotes disagree: list them and stop.
114
- The org is needed only for the `gh` lookup.
115
- 2. **Read what the number is.** `gh api repos/<owner>/<repo>/issues/<n> --jq '.pull_request != null'`
54
+ 1. **Read what the number is.** `gh api repos/<slug>/issues/<number> --jq '.pull_request != null'`
116
55
  prints `true` for a pull request and `false` for an issue. If it fails, quote the error and stop.
117
- 3. **Find the branches.**
118
- - A pull request: `gh pr view <n> -R <owner>/<repo> --json headRefName --jq .headRefName`.
119
- - An issue: `gh issue view <n> -R <owner>/<repo> --json closedByPullRequestsReferences` lists the
120
- linked pull requests, each with its `number` and `repository`. Read each one's `headRefName` as
121
- above, in that pull request's own repository.
122
- 4. **Focus the branch.** Keep the branches a live unit is on. Exactly one: run
123
- `cyberlegion unit focus --repo <name> --branch <branch>`, where `<name>` is that pull request's
124
- repository name. More than one: list them and stop.
125
- 5. **Fall back.** When no branch has a live unit, look for a live unit in that repo whose handle or
126
- brief names the number: a handle that ends in `-123`, or a brief file that mentions `#123`,
127
- `<repo>#123`, or `/issues/123` or `/pull/123` on that repo. Exactly one: run
128
- `cyberlegion unit focus <id>`. More than one: list them and stop. None: fail naming the ref and
129
- the branches tried.
130
-
131
- ### Prose
132
-
133
- Match the words against each live unit's handle, branch, repo name, and brief (read the brief file
134
- when the record has one). A match is a unit the words plainly describe, not one they could be
135
- stretched to fit. Exactly one: run `cyberlegion unit focus <id>`. More than one: list them and stop.
136
- None: say no live unit matches, and list the units so the Council can name one.
137
-
138
- ## 4. Report
139
-
140
- - A jump: one line naming the unit's handle and id and the pane, then that `/jump back` returns.
141
- - Candidates: one row per unit with its id, handle, branch, and repo name, then a line saying the
142
- Council can narrow the argument (a form word, a repo, or an id). Do not jump.
143
- - A failure: quote the CLI's error verbatim with its exit code. Do not retry with other flags, prune,
144
- or reach the pane any other way.
56
+ 2. **Find the branches.**
57
+ - A pull request: `gh pr view <number> -R <slug> --json headRefName --jq .headRefName`.
58
+ - An issue: `gh issue view <number> -R <slug> --json closedByPullRequestsReferences` lists the
59
+ linked pull requests. Read each one's `headRefName` as above, in that pull request's own
60
+ repository. An issue with no linked pull request has no branches.
61
+ 3. **Focus.** Run `cyberfleet jump --repo <repo> --branch <b>... --mentions <number>`, one `--branch`
62
+ per branch found, with the `repo` the `task` result gave. When no branch has a live unit,
63
+ `--mentions` falls back to a unit whose handle or brief names the number. Go to step 4 with what
64
+ it prints.
65
+
66
+ ## 3. On `prose`, match the words to one unit
67
+
68
+ Match the `words` against each listed unit's handle, branch, repo name, and brief (read the brief
69
+ file when the unit has one). A match is a unit the words plainly describe, not one they could be
70
+ stretched to fit.
71
+
72
+ - Exactly one: run `cyberfleet jump <id>` with that unit's id, and go to step 4 with what it prints.
73
+ - More than one plausible unit: list them and stop.
74
+ - None: say no live unit matches, and list the units so the Council can name one.
75
+
76
+ ## 4. Report the result
77
+
78
+ - `jumped`: one line naming the unit's handle and id and the pane, then that `/jump back` returns.
79
+ After a `back` with no unit at the pane, name the pane alone.
80
+ - `candidates`: one row per unit with its id, handle, branch, and repo name, then a line saying the
81
+ Council can narrow the argument (a repo, a branch, or an id). Do not jump.
82
+ - An error: quote the command's error verbatim with its exit code. Do not retry with other flags,
83
+ prune, or reach the pane any other way.
84
+
85
+ `/jump back` waits for this session's turn. When the Council uses `back` often, mention the tmux key
86
+ (prefix+B) that cyber-mux's `setup-tmux` binds to `focus --back`.
87
+
88
+ ## Validate
89
+
90
+ Before reporting, check the result against what the commands printed:
91
+
92
+ - A reported jump comes from a `jumped` result, and names the `id`, `handle`, and `pane` it gave.
93
+ - Every `cyberfleet jump` run after the first took an id from a result, or `--repo` with the
94
+ branches `gh` returned. None took a handle, and none was guessed.
95
+ - A candidates report has at least two rows, each a unit from the `candidates` or `prose` result,
96
+ and no `cyberfleet jump` ran after the candidates were collected.
97
+ - An error report quotes the command's error and exit code, and no other command ran after it.