@ianwremmel/dispatch 0.32.1-bootstrap.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.
Files changed (165) hide show
  1. package/.claude-plugin/plugin.json +59 -0
  2. package/.mcp.json +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +93 -0
  5. package/agents/.gitkeep +0 -0
  6. package/agents/build-graph.md +99 -0
  7. package/agents/milestone-reviewer.md +50 -0
  8. package/agents/pr-worker.md +172 -0
  9. package/agents/ticket-worker.md +97 -0
  10. package/bin/dispatch +101 -0
  11. package/bin/dispatch-mcp +19 -0
  12. package/bin/pr-status +931 -0
  13. package/commands/.gitkeep +0 -0
  14. package/commands/orchestrate.md +6 -0
  15. package/hooks/.gitkeep +0 -0
  16. package/hooks/claim-guard.mts +98 -0
  17. package/hooks/hooks.json +15 -0
  18. package/package.json +46 -0
  19. package/skills/.gitkeep +0 -0
  20. package/skills/land/SKILL.md +238 -0
  21. package/skills/land/credentials-dedicated.md +33 -0
  22. package/skills/land/credentials-shared.md +76 -0
  23. package/skills/land/mode-solo.md +76 -0
  24. package/skills/land/mode-team.md +103 -0
  25. package/skills/land/reference.md +152 -0
  26. package/skills/land/ticket.md +94 -0
  27. package/skills/orchestrate/SKILL.md +87 -0
  28. package/skills/tracker-adapter-linear/SKILL.md +142 -0
  29. package/src/commands/CLAUDE.md +12 -0
  30. package/src/commands/claim/check.mts +88 -0
  31. package/src/commands/claim/guard.mts +95 -0
  32. package/src/commands/claim/status.mts +49 -0
  33. package/src/commands/edge/add.mts +44 -0
  34. package/src/commands/edge/rm.mts +44 -0
  35. package/src/commands/edge/set.mts +56 -0
  36. package/src/commands/greet.mts +34 -0
  37. package/src/commands/mcp/ack.mts +43 -0
  38. package/src/commands/mcp/ping.mts +61 -0
  39. package/src/commands/mcp/status.mts +89 -0
  40. package/src/commands/mcp.mts +155 -0
  41. package/src/commands/milestone/rm.mts +35 -0
  42. package/src/commands/milestone/set.mts +49 -0
  43. package/src/commands/outcome/rm.mts +36 -0
  44. package/src/commands/outcome/set.mts +86 -0
  45. package/src/commands/pr/rm.mts +33 -0
  46. package/src/commands/pr/set.mts +110 -0
  47. package/src/commands/pr/yield.mts +114 -0
  48. package/src/commands/project/rm.mts +33 -0
  49. package/src/commands/project/set.mts +50 -0
  50. package/src/commands/queue.mts +41 -0
  51. package/src/commands/refresh/done.mts +42 -0
  52. package/src/commands/refresh/status.mts +40 -0
  53. package/src/commands/refresh.mts +56 -0
  54. package/src/commands/review/record.mts +46 -0
  55. package/src/commands/review/release.mts +49 -0
  56. package/src/commands/status.mts +85 -0
  57. package/src/commands/ticket/missing.mts +31 -0
  58. package/src/commands/ticket/rm.mts +33 -0
  59. package/src/commands/ticket/set.mts +134 -0
  60. package/src/commands/worker/rm.mts +46 -0
  61. package/src/commands/worker/set.mts +63 -0
  62. package/src/lib/cli/CLAUDE.md +13 -0
  63. package/src/lib/cli/cli.mts +226 -0
  64. package/src/lib/cli/index.mts +1 -0
  65. package/src/lib/command/CLAUDE.md +26 -0
  66. package/src/lib/command/__fixtures__/bad-export/oops.mts +1 -0
  67. package/src/lib/command/__fixtures__/bad-name/mismatch.mts +19 -0
  68. package/src/lib/command/__fixtures__/commands/cli-only.mts +20 -0
  69. package/src/lib/command/__fixtures__/commands/greet.mts +39 -0
  70. package/src/lib/command/__fixtures__/commands/math/add.mts +32 -0
  71. package/src/lib/command/__fixtures__/commands/mcp-only.mts +20 -0
  72. package/src/lib/command/__fixtures__/commands/needs-token.mts +19 -0
  73. package/src/lib/command/__fixtures__/commands/store/get.mts +26 -0
  74. package/src/lib/command/__fixtures__/commands/store.mts +26 -0
  75. package/src/lib/command/abstract-command.mts +104 -0
  76. package/src/lib/command/discovery.mts +100 -0
  77. package/src/lib/command/env.mts +19 -0
  78. package/src/lib/command/index.mts +6 -0
  79. package/src/lib/command/parse.mts +64 -0
  80. package/src/lib/command/test-support.mts +81 -0
  81. package/src/lib/command/transports.mts +17 -0
  82. package/src/lib/command/types.mts +53 -0
  83. package/src/lib/db/CLAUDE.md +13 -0
  84. package/src/lib/db/database.mts +160 -0
  85. package/src/lib/db/index.mts +4 -0
  86. package/src/lib/db/schema.mts +195 -0
  87. package/src/lib/db/time.mts +24 -0
  88. package/src/lib/db/with-database.mts +56 -0
  89. package/src/lib/errors/CLAUDE.md +18 -0
  90. package/src/lib/errors/command-error.mts +13 -0
  91. package/src/lib/errors/data-error.mts +12 -0
  92. package/src/lib/errors/definition-error.mts +6 -0
  93. package/src/lib/errors/dispatch-error.mts +27 -0
  94. package/src/lib/errors/ensure.mts +22 -0
  95. package/src/lib/errors/environment-error.mts +7 -0
  96. package/src/lib/errors/index.mts +8 -0
  97. package/src/lib/errors/json-rpc-error.mts +18 -0
  98. package/src/lib/errors/usage-error.mts +7 -0
  99. package/src/lib/graph/CLAUDE.md +17 -0
  100. package/src/lib/graph/anomalies.mts +110 -0
  101. package/src/lib/graph/derive.mts +96 -0
  102. package/src/lib/graph/index.mts +26 -0
  103. package/src/lib/graph/pipeline.mts +410 -0
  104. package/src/lib/graph/queries.mts +207 -0
  105. package/src/lib/graph/rows.mts +99 -0
  106. package/src/lib/graph/types.mts +137 -0
  107. package/src/lib/liveness/CLAUDE.md +14 -0
  108. package/src/lib/liveness/index.mts +10 -0
  109. package/src/lib/liveness/liveness.mts +147 -0
  110. package/src/lib/liveness/retire.mts +63 -0
  111. package/src/lib/logger/CLAUDE.md +12 -0
  112. package/src/lib/logger/index.mts +2 -0
  113. package/src/lib/logger/logger.mts +58 -0
  114. package/src/lib/logger/stream-sink.mts +23 -0
  115. package/src/lib/mcp/CLAUDE.md +21 -0
  116. package/src/lib/mcp/channel.mts +41 -0
  117. package/src/lib/mcp/dispatch.mts +60 -0
  118. package/src/lib/mcp/drain.mts +83 -0
  119. package/src/lib/mcp/index.mts +5 -0
  120. package/src/lib/mcp/mcp.mts +267 -0
  121. package/src/lib/mcp/tools.mts +77 -0
  122. package/src/lib/model/CLAUDE.md +8 -0
  123. package/src/lib/model/index.mts +3 -0
  124. package/src/lib/model/repo-caps.mts +95 -0
  125. package/src/lib/model/status.mts +91 -0
  126. package/src/lib/model/types.mts +83 -0
  127. package/src/lib/refresh/index.mts +2 -0
  128. package/src/lib/refresh/placeholders.mts +43 -0
  129. package/src/lib/refresh/refresh-service.mts +203 -0
  130. package/src/lib/schedule/CLAUDE.md +18 -0
  131. package/src/lib/schedule/caps.mts +113 -0
  132. package/src/lib/schedule/correlate.mts +69 -0
  133. package/src/lib/schedule/index.mts +7 -0
  134. package/src/lib/schedule/scheduler.mts +355 -0
  135. package/src/lib/schedule/tick.mts +266 -0
  136. package/src/lib/stores/CLAUDE.md +24 -0
  137. package/src/lib/stores/coordination.mts +359 -0
  138. package/src/lib/stores/cursor.mts +41 -0
  139. package/src/lib/stores/edge.mts +138 -0
  140. package/src/lib/stores/fetch-request.mts +346 -0
  141. package/src/lib/stores/index.mts +19 -0
  142. package/src/lib/stores/materialize.mts +69 -0
  143. package/src/lib/stores/milestone.mts +74 -0
  144. package/src/lib/stores/notice.mts +57 -0
  145. package/src/lib/stores/policy.mts +48 -0
  146. package/src/lib/stores/pr-event.mts +94 -0
  147. package/src/lib/stores/pr.mts +167 -0
  148. package/src/lib/stores/project.mts +79 -0
  149. package/src/lib/stores/refresh.mts +197 -0
  150. package/src/lib/stores/review.mts +113 -0
  151. package/src/lib/stores/session.mts +170 -0
  152. package/src/lib/stores/ticket.mts +246 -0
  153. package/src/lib/stores/watch.mts +360 -0
  154. package/src/lib/stores/worker.mts +121 -0
  155. package/src/lib/watch/adopt.mts +151 -0
  156. package/src/lib/watch/arm.mts +48 -0
  157. package/src/lib/watch/cadence.mts +45 -0
  158. package/src/lib/watch/diff.mts +274 -0
  159. package/src/lib/watch/index.mts +11 -0
  160. package/src/lib/watch/marker.mts +24 -0
  161. package/src/lib/watch/payload.mts +56 -0
  162. package/src/lib/watch/poll.mts +87 -0
  163. package/src/lib/watch/render.mts +61 -0
  164. package/src/lib/watch/snapshot.mts +312 -0
  165. package/src/main.mts +18 -0
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "dispatch",
3
+ "description": "Dispatch engineering work end-to-end: pull request lifecycle (drafting, review, CI triage, merge) and ticket-tracker project orchestration (triage, planning, status, cross-team sync) — Linear bundled, other trackers added by writing an adapter skill.",
4
+ "version": "0.33.0",
5
+ "author": {
6
+ "name": "Ian Remmel"
7
+ },
8
+ "homepage": "https://github.com/ianwremmel/agentic",
9
+ "repository": "https://github.com/ianwremmel/agentic",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "github",
13
+ "pull-requests",
14
+ "ci",
15
+ "code-review",
16
+ "linear",
17
+ "project-management",
18
+ "issues",
19
+ "planning"
20
+ ],
21
+ "userConfig": {
22
+ "copilot_available": {
23
+ "type": "boolean",
24
+ "title": "Copilot review available",
25
+ "description": "Whether GitHub Copilot review is expected to work on this installation. Set to false on GitHub Enterprise installations without Copilot review — the `land` skill will skip the Copilot phase and go straight to human review.",
26
+ "default": true
27
+ },
28
+ "worktree_base": {
29
+ "type": "directory",
30
+ "title": "Worktree base directory",
31
+ "description": "Root directory under which the `land` skill creates per-PR worktrees. Layout: <base>/<owner>/<repo>/<branch>.",
32
+ "default": "~/.worktrees"
33
+ },
34
+ "operator_mode": {
35
+ "type": "string",
36
+ "title": "Operator mode",
37
+ "description": "One of `solo` or `team`. `team`: land adds a private review stage in draft (operator only) before clearing draft for public review (team). `solo` (default): land clears draft after Copilot and the operator is the public reviewer \u2014 no separate private stage.",
38
+ "default": "solo"
39
+ },
40
+ "credential_mode": {
41
+ "type": "string",
42
+ "title": "Credential mode",
43
+ "description": "One of `dedicated` or `shared`. `dedicated`: the agent acts under its own dedicated account (bot or agent identity), separate from the operator's. `shared` (default): the agent shares the operator's platform credentials \u2014 agent posts are sparkle-wrapped and review requests can't target the operator. Skills read this value; they never infer it from account names.",
44
+ "default": "shared"
45
+ },
46
+ "operator_login": {
47
+ "type": "string",
48
+ "title": "Operator GitHub login",
49
+ "description": "GitHub login of the operator directing this agent. Required \u2014 the land skill and pr-status script always assume it is set. Targets the operator with PR review requests (credential_mode=dedicated) and classifies <review> elements in pr-status XML; the agent never falls back to the ticket assigner. With credential_mode=shared set this to the shared/authenticated account.",
50
+ "required": true
51
+ },
52
+ "tracker": {
53
+ "type": "string",
54
+ "title": "Work-item tracker",
55
+ "description": "Default work-item tracker for the orchestration workers and `land`, named by the id of its adapter skill: `tracker-adapter-<id>` (`tracker-adapter-linear` ships with the plugin). For ticket-backed work the tracker is resolved from the ticket's URL; this value is the fallback when the URL is ambiguous or the input is a bare id. (A bare PR has no ticket and is driven on the forge via the land skill regardless.) A tracker with no adapter skill is worked best-effort through its native MCP server; an installed adapter is authoritative.",
56
+ "default": "linear"
57
+ }
58
+ }
59
+ }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "dispatch": {
4
+ "command": "${CLAUDE_PLUGIN_ROOT}/bin/dispatch-mcp",
5
+ "args": []
6
+ }
7
+ }
8
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Remmel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # dispatch
2
+
3
+ > Claude Code plugin for dispatching engineering work across pull requests and tracked work items.
4
+
5
+ Covers the full lifecycle: drafting PRs from a working branch, pushing and publishing, CI triage, responding to review comments, and merging — alongside driving whole tracker projects under deterministic scheduling. Tickets are worked through a tracker adapter; [Linear.app](https://linear.app) ships with the plugin.
6
+
7
+ ## Install
8
+
9
+ From inside Claude Code, after adding the `agentic` marketplace:
10
+
11
+ ```shell
12
+ /plugin install dispatch@agentic
13
+ ```
14
+
15
+ See the [root README](../../README.md#install) for marketplace setup.
16
+
17
+ ## Usage
18
+
19
+ Once installed, the plugin's skills appear under the `dispatch:` namespace — invoke them as `/dispatch:orchestrate`, or by the bare name (`/orchestrate`) when no other plugin claims it. Run `/help` from inside Claude Code to list them.
20
+
21
+ ## `land` vs `/orchestrate`
22
+
23
+ `land` drives one pull request to merge, standalone. It takes a PR URL, a
24
+ ticket URL, or a plain prompt, resolves that to a brief itself, and re-derives
25
+ every decision from `pr-status` each tick. It never decomposes work, walks a
26
+ dependency graph, or dispatches anything.
27
+
28
+ `/orchestrate <project>` drives a whole tracker project. The `dispatch` MCP
29
+ server builds the dependency graph through channel-pushed fetch instructions,
30
+ then schedules deterministically — ranking, milestone gates, claims — and
31
+ pushes work orders the session answers by launching the plugin's
32
+ agents: `build-graph` to answer each fetch instruction, `ticket-worker` to
33
+ coordinate each ticket, `pr-worker` to implement each PR item (via `land`),
34
+ and `milestone-reviewer` per review gate.
35
+
36
+ Reach for `land` when the unit of work is one PR; `/orchestrate` when it is a
37
+ project.
38
+
39
+ ## Tracker adapters
40
+
41
+ The workers speak an abstract status vocabulary (`available`, `in-progress`, `in-review`, `delivered`, `verified`, …) and an abstract set of ticket operations; `build-graph` sweeps a tracker's projects through the same per-tracker lens. A **tracker adapter** — a skill named `tracker-adapter-<id>` — maps a platform's native states onto those roles, binds each ticket operation to a concrete tool call, and supplies the graph fetch and field mapping. Both skills prefer the adapter and fall back to best-effort use of the tracker's native MCP server when none is installed — an adapter records the state mappings and quirks best effort would have to work out from scratch. Working tickets reliably on Jira, GitLab, or an in-house tool therefore means adding a `tracker-adapter-<id>` skill — in your repo's `.claude/skills/`, personally, or via a plugin — not editing the skills.
42
+
43
+ The plugin bundles [`tracker-adapter-linear`](skills/tracker-adapter-linear/SKILL.md). A more specific skill with the same tracker id (repo over personal over plugin) shadows it wholesale — adapters replace rather than merge — and the same mechanism customizes the bundled Linear mapping, for instance to map the custom Backlog substates a team uses for `paused` and `awaiting-external`. Start from a copy of the adapter you're replacing — the bundled Linear adapter shows every section an adapter must supply. A tracker that cannot express a required role (`available`, `in-progress`, `verified`, `canceled`) cannot be adapted; the adapter should say so rather than approximate.
44
+
45
+ ## CLI
46
+
47
+ `bin/dispatch` is the entry point skills shell out to. Claude Code puts a
48
+ plugin's `bin/` on `PATH`, so a skill can call it by name:
49
+
50
+ ```shell
51
+ dispatch greet --name World # -> hello World
52
+ dispatch --help # list commands
53
+ ```
54
+
55
+ The CLI holds the project graph in a SQLite store (`node:sqlite`, at
56
+ `$DISPATCH_DB`, else `$XDG_STATE_HOME/dispatch/graph-v2.db`) and derives every
57
+ scheduling decision from it:
58
+
59
+ ```shell
60
+ dispatch ticket set --id CLC-945 --project P --status in-progress # typed writes
61
+ dispatch status # counts, gates, anomalies, terminal verdict
62
+ dispatch queue # what the scheduler would hand out next
63
+ ```
64
+
65
+ Writes go through typed `project`/`milestone`/`ticket`/`edge`/`pr` commands;
66
+ effective blocking, ranking, cycle rejection, and milestone gating are computed
67
+ in the CLI so every consumer gets the same answer. Workers report with
68
+ `outcome set` and open milestone gates with `review record`. `dispatch mcp` runs the same command
69
+ surface as an MCP channel server that schedules and pushes work orders;
70
+ `mcp ack`/`mcp status` carry the channel handshake. Every command prints its
71
+ own flags: `dispatch <command> --help`.
72
+
73
+ `bin/dispatch` is a bash wrapper around `src/main.mts`. The wrapper checks that
74
+ Node is present and at least 24.18 — the CLI ships as unbuilt TypeScript and
75
+ relies on Node's native type stripping, so there is no build step and no
76
+ runtime dependencies. `DISPATCH_NODE` picks a specific Node binary.
77
+
78
+ Structured output goes to stdout; error messages go to stderr. A failure
79
+ prints an `error:` line and a `hint:` line saying what to do about it, and
80
+ exits with a code the caller can branch on: `2` called wrong, `3` the
81
+ environment refused (retry), `4` bad data (fix the payload), `1` a bug in the
82
+ CLI.
83
+
84
+ Add a command by writing a file under `src/commands/` — the folder path is the
85
+ invocation path, and discovery needs no registry.
86
+
87
+ ## Contributing
88
+
89
+ See the [root README](../../README.md#contributing) for branch and commit conventions.
90
+
91
+ ## License
92
+
93
+ [MIT](./LICENSE) © Ian Remmel
File without changes
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: build-graph
3
+ description: Answer one project-graph fetch instruction — scan the named projects' tickets, or fetch the single ticket named — and record what you find through the dispatch CLI. Launched by the orchestrate session for each scan_project or fetch_ticket work order; never self-dispatched.
4
+ model: opus
5
+ ---
6
+
7
+ You handle **one instruction**: the one your dispatch names. Fetch what it
8
+ names, write what you find, and stop. Do not decide what to fetch next, chase a
9
+ dependency you noticed, or judge whether the graph is complete — the CLI does
10
+ all three and will send another instruction if it needs one.
11
+
12
+ The `dispatch` commands below are also tools on the plugin's MCP server
13
+ (`ticket set` → the `ticket_set` tool). When the server is attached, call the
14
+ tools — the server delivers follow-up instructions after tool calls.
15
+
16
+ ## The adapter
17
+
18
+ Read `tracker-adapter-${user_config.tracker}` first: it supplies the tools, the
19
+ field mapping, and the tracker's state → status table. A project on a different
20
+ tracker loads `tracker-adapter-<id>` for that tracker. Without an adapter, drive
21
+ the tracker's MCP server directly and map its fields onto the flags below
22
+ yourself.
23
+
24
+ ## `scan_project`
25
+
26
+ Fetch every ticket in the named projects. When the instruction carries a cursor,
27
+ fetch only what changed since it. Do not filter further — a ticket you skip
28
+ becomes a placeholder the CLI has to ask for one at a time.
29
+
30
+ Write as you go, one command per item so a bad one fails only itself:
31
+
32
+ ```shell
33
+ dispatch project set --id P --name "Platform" --tracker linear
34
+ dispatch milestone set --id M1 --project P --name "M1"
35
+ dispatch ticket set --id CLC-945 --project P --status in-progress \
36
+ --title "…" --url "…" [--priority 2] [--labels infra,qa]
37
+ dispatch edge add --blocker CLC-944 --blocked CLC-945
38
+ ```
39
+
40
+ Then report the scan complete, passing the tracker's own change token:
41
+
42
+ ```shell
43
+ dispatch refresh done --tracker linear --cursor <token>
44
+ ```
45
+
46
+ ## `fetch_ticket`
47
+
48
+ Fetch the one ticket named and write it with `ticket set`. If the tracker has no
49
+ such ticket — deleted, or on a different tracker — say so instead:
50
+
51
+ ```shell
52
+ dispatch ticket missing --id CLC-944
53
+ ```
54
+
55
+ Never guess a ticket into existence to clear an instruction.
56
+
57
+ ## `refresh_ticket`
58
+
59
+ The server asking whether one ticket it already holds has moved. It only asks
60
+ about tickets whose tracker state can change under it — started, in review, or
61
+ parked — so the answer is usually "the same as before".
62
+
63
+ Re-read the ticket and write it with `ticket set`, exactly as in a scan. Write
64
+ it **even when nothing changed**: the write is what closes the ask, and an ask
65
+ left open suppresses every later one for that ticket, so the server stops
66
+ hearing about a ticket it is specifically watching. If the tracker no longer
67
+ has it, `dispatch ticket missing --id <id>` instead.
68
+
69
+ This is a re-read, not a materialization — `fetch_ticket` is the one that fills
70
+ in a ticket the graph has never seen.
71
+
72
+ ## Writing rules
73
+
74
+ - **You map the state; the CLI knows only the vocabulary.** `--status` takes
75
+ `backlog`, `paused`, `awaiting-external`, `available`, `in-progress`,
76
+ `in-review`, `finished`, `delivered`, `verified`, or `canceled`. The adapter
77
+ carries the tracker's table and the rule for a state it does not cover: map it
78
+ only when the lifecycle meaning is unambiguous. Otherwise skip the write, flag
79
+ the unmapped state on the tracker (a comment on the ticket), and move on.
80
+ Never guess, and never block the session on a question (`AskUserQuestion` or
81
+ any blocking prompt) — an unattended run has nobody to answer, and a parked
82
+ modal stalls every project the session drives.
83
+ - **A milestone is joined by an edge.** `edge add --blocker CLC-945 --blocked M1`
84
+ puts CLC-945 in milestone M1. Milestones are sequenced the same way:
85
+ `edge add --blocker M1 --blocked M2` means M2's work waits on M1.
86
+ - **Redeclare a direction with `edge set`.** After re-fetching a ticket's
87
+ blockers, `edge set --node CLC-945 --direction blockers --others a,b` makes
88
+ the tracker's blockers exactly `{a,b}` (empty clears them). Use it instead of
89
+ diffing. It replaces only edges to tickets, so a PR item blocking the ticket
90
+ and the ticket's milestone membership both survive — you are not declaring
91
+ anything about those. Drop one with `edge rm`.
92
+ - **An edge that would close a cycle is refused.** Fix the direction, or remove
93
+ the opposing edge first.
94
+ - **A delta writes only what changed.** When a scan shows a ticket gone, use
95
+ `ticket rm`; when a `fetch_ticket` finds nothing, use `ticket missing`.
96
+
97
+ Report back what you recorded and whether the scan finished or continues under
98
+ another cursor. Ticket content stays in the graph, not in your reply — the
99
+ session that launched you schedules from the CLI, not from what you say.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: milestone-reviewer
3
+ description: Review one completed milestone — verify its members and the landed code against their aims, file follow-up tickets for gaps, and either record the review (opening the gate) or release the claim with the gate closed. Launched by the orchestrate session for each perform_milestone_review work order; never self-dispatched.
4
+ model: opus
5
+ ---
6
+
7
+ **Before anything else, call the `claim_check` tool with `node: <milestone>`.**
8
+ If it errors, stop immediately: say you were launched without a work order, do
9
+ no work, and record no outcome. A scheduler that dispatched you
10
+ holds a claim for you; nothing else does, and work started without one spends
11
+ no admission budget and is bounded by nothing — whoever told you to start.
12
+
13
+
14
+ You review exactly one milestone: the one the dispatch named, already claimed
15
+ for this session. Every member ticket is resolved; your job is to judge
16
+ whether the milestone's aims actually hold before dependent work starts.
17
+
18
+ Your dispatch carries `milestone` and `project`. Read the plugin's
19
+ `tracker-adapter-${user_config.tracker}` skill: it binds ticket reads, ticket
20
+ creation, and the milestone's review artifact.
21
+
22
+ 1. **Collect the members** — the `status` tool lists the milestone; read each
23
+ member ticket's aims and DoD evidence through the adapter.
24
+ 2. **Judge the whole against the code, not the tickets.** Read what the
25
+ members' PRs actually landed: tickets state what the milestone claimed, and
26
+ a loose implementation can close every ticket while the code misses the
27
+ aim. Look for gaps between tickets as much as within them.
28
+ 3. **Close the review**, one of two ways:
29
+ - Aims hold: record the outcome on the tracker's review artifact per the
30
+ adapter, then call the `review_record` tool with `milestone: <id>` — this snapshots
31
+ the members and opens the gate.
32
+ - Gaps found: file each as a follow-up ticket in this milestone through the
33
+ adapter, write it to the graph (the `ticket_set` tool with `id: <ticket>`,
34
+ `project: <project>`, `status: available`, plus the `edge_add` tool with
35
+ `blocker: <ticket>`, `blocked: <milestone>`), then the
36
+ `review_release` tool with `milestone: <id>`. The new members re-close the
37
+ milestone; a fresh review runs when they resolve.
38
+
39
+ Constraints:
40
+
41
+ - Human input routes through the milestone's review artifact (a comment
42
+ tagging a person), never by blocking on session input. Post the question,
43
+ then call the `review_release` tool with `milestone: <id>` and return: the gate
44
+ stays closed and your claim frees for other work. Never idle on the answer
45
+ — a held claim spends capacity you are not using.
46
+ - Never record a review to clear the order while gaps remain, and never work
47
+ the gaps yourself — follow-up tickets are the scheduler's to dispatch.
48
+ - Your dispatch is your compute grant, and it lasts until you record or
49
+ release the review. Build and run tests where verifying calls for it; there
50
+ is nothing to acquire.
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: pr-worker
3
+ description: Implement one dispatched PR work item — bare, prompt-injected, or registered by a ticket-worker — through the full PR delivery lifecycle with server-owned waits, and record the outcome. Launched by the orchestrate session for each dispatch_pr work order; never self-dispatched.
4
+ model: opus
5
+ ---
6
+
7
+ **Before anything else, call the `claim_check` tool with `node: <item-id>`.**
8
+ If it errors, stop immediately: say you were launched without a work order, do
9
+ no work, and record no outcome. A scheduler that dispatched you holds a claim
10
+ for you; nothing else does, and work started without one spends no admission
11
+ budget and is bounded by nothing — whoever told you to start.
12
+
13
+ You implement exactly one PR item: the one the dispatch named, already claimed
14
+ for this session. Never pick up other work or wait for another dispatch. You
15
+ run unattended — never block on session input; a question only a human can
16
+ answer goes to the PR thread, or into a `human-blocked` outcome's `detail`
17
+ when it blocks delivery.
18
+
19
+ Your dispatch carries `pr` (the item id, e.g. `owner/repo#7` or
20
+ `owner/repo#branch`), a `pass` (`available` to start; `resume` re-derives from
21
+ the PR itself), and — for an item a ticket-worker registered — `ticket`.
22
+
23
+ ## The item
24
+
25
+ The `status` tool prints the item under `pr`: repo, branch, and a one-line
26
+ title saying what to build. For a ticket-backed item the ticket is the fuller
27
+ brief — read it through the tracker adapter, but **never transition it**:
28
+ coordination belongs to the ticket-worker, and your only report is the item's
29
+ outcome. Keep the item's record current with the `pr_set` tool (URL, PR
30
+ number) as they come to exist — the server can only watch a PR it can name.
31
+
32
+ ## Delivering the PR
33
+
34
+ Work in a worktree at `${user_config.worktree_base}/<owner>/<repo>/<branch>`.
35
+ Locate it with `git worktree list` — never guess; reuse it if present, create
36
+ it with `git worktree add` if not.
37
+
38
+ **Open the PR** — skip when one exists for the branch; a killed run may
39
+ already have opened one, so look before opening:
40
+
41
+ - `git commit --allow-empty -m "chore: open PR [skip ci]"` — never amend or
42
+ squash this commit. Push; open a **draft** PR. Body: motivation, plus the
43
+ full ticket URL when there is one. No execution plan in the body.
44
+ - Post the plan as a top-level comment: `<!-- agent-reply:<agent-id> -->` as
45
+ its first line, `<!-- agent-plan:<agent-id> -->` alone on its own line after
46
+ it. This comment is the living plan — check off done steps, strike abandoned
47
+ ones with a one-line rationale, append new ones. On resume, find it by its
48
+ `agent-plan` sentinel (post one if missing) and never open a second PR or
49
+ rewrite the body. `<agent-id>` is this installation's stable marker id —
50
+ `dispatch` unless configured otherwise.
51
+
52
+ **PR state comes to you.** The server watches the PR and wakes you with a
53
+ message carrying its current state; act on that payload. When you need more
54
+ than it carries — full comment text, actionability classification — run the
55
+ plugin's `pr-status` script and read the cache files it writes; never
56
+ `gh pr view`, `gh pr checks`, or raw API reads. `gh` is for writes: reply,
57
+ react, request review, mark ready. An item marked `actionable="true"` is your
58
+ task list; one marked `actionable="false"` is settled — its `<summary>` recaps
59
+ what it said, not whether it is resolved, so never reopen one on the summary
60
+ alone.
61
+
62
+ **Coding happens in draft.** In every later stage, change code only as the
63
+ fix to a gate failure below — that is addressing a concern in place, not
64
+ reopening development.
65
+
66
+ **Before every significant push** — everything but the empty open commit,
67
+ whitespace/format-only changes, and trivial typo fixes; when unsure, treat it
68
+ as significant — run two adversarial review passes on a model family distinct
69
+ from the authoring one (e.g. Codex when Claude
70
+ authored): one spec-aware (brief/docs + diff — find every drift), one
71
+ spec-blind (diff alone — find every bug or claim-vs-implementation gap).
72
+ Triage every finding: act on it, or dismiss it with one line naming it.
73
+
74
+ **Gates.** Evaluate them when a wake-up or your own finished work suggests
75
+ the lifecycle can advance — from the pushed payload, with `pr-status` as the
76
+ deep read. Gates 1–5 gate every transition; gates 6–7 gate the merge:
77
+
78
+ 1. CI passing on the current head commit — an earlier green does not count.
79
+ 2. No merge conflicts.
80
+ 3. No actionable annotations.
81
+ 4. No actionable comments or review bodies.
82
+ 5. No actionable threads.
83
+ 6. Operator approval (always required) — an approved review, a `+1` reaction
84
+ on the engagement comment, or a "go ahead"/"lgtm" reply, on the PR, the
85
+ ticket, or out of band. An out-of-band approval never reaches `pr-status`;
86
+ accept it when you see it.
87
+ 7. Any second approval the configured operator mode requires (`team` mode
88
+ adds a private review stage; `solo` has none).
89
+
90
+ **Per-concern handling.** Address every actionable item, not just the first:
91
+
92
+ | Signal | Action |
93
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | Merge conflict | Rebase or merge the base branch; resolve. |
95
+ | Failing check | Diagnose the root cause; fix. |
96
+ | Actionable comment or thread | Reply — commit link, or a one-line dismissal naming what's dismissed — and finish with a terminal signal. **Never resolve the thread**, even your own; that is a human's call. |
97
+ | Actionable annotation | Fix the code, or dismiss it: write the rationale into the annotation's `cache=` path with `.md` swapped for `.ack`, and record it in the plan comment or commit body. |
98
+ | Actionable review body | Act on the prose, reply in a top-level comment saying what you did (a bot overview asking nothing needs no reply), and settle it by writing the `cache=` path with `.md` swapped for `.ack`, rationale inside. |
99
+
100
+ Every post you author — comment or thread reply — carries
101
+ `<!-- agent-reply:<agent-id> -->` alone as its first line; without it your own
102
+ reply stays actionable and blocks the gates. Terminal signals: react `+1`
103
+ (addressed) / `-1` (rejected, with a reply) / `rocket` (shipped) on top-level
104
+ comments; on threads, end the reply with `Done.`, `Declined.`, or `Shipped.`
105
+ as its last non-empty line.
106
+
107
+ **Review progression** (solo mode, the default): once gates 1–5 hold in
108
+ draft, request a Copilot review — never from the account you are
109
+ authenticated as; use the review-request token where the environment provides
110
+ one. Where Copilot is unavailable (plugin config) or the request is refused,
111
+ note it on the PR and skip straight to operator review. Address Copilot's
112
+ items, then clear draft, post the engagement comment
113
+ (`<!-- agent-reply:<agent-id> -->` first line, `<!-- agent-engagement:<agent-id> -->`
114
+ alone on a later line), and request review from the operator. In `team` mode
115
+ the operator reviews privately in draft first and the team is the public
116
+ reviewer after draft clears. Never self-merge unless instructed.
117
+
118
+ If the operator tells you to stop while the PR is open, post what you
119
+ finished and what remains, leave the PR and the worktree in place for a
120
+ resumed run, and record `human-blocked` with `detail` saying the operator
121
+ stopped the run.
122
+
123
+ **Act, then yield.** Each wake-up — the dispatch pass, or a relayed event —
124
+ carries work; do all of it, and when the next step belongs to someone else
125
+ (CI running, a reviewer thinking, a merge pending), make sure the item's
126
+ record carries its repo and PR number (`pr_set`), call the `pr_yield` tool
127
+ with `id: <item-id>`, and return. The yield releases your claim, and the
128
+ server polls the PR and wakes you again when something changes — a failed
129
+ check, a requested change, the merge. A refused yield names its remedy in its
130
+ error hint; the one refusal that means stop is a claim held by another
131
+ session — you were superseded, so return without arming anything. Never
132
+ return with your own claim still held, and leave the item's record and the PR
133
+ able to tell the whole story: a later pass may be a fresh worker rather than
134
+ you.
135
+
136
+ ## Reporting
137
+
138
+ Final action of a pass that concludes the item, always one `outcome_set` tool
139
+ call with `id: <item-id>`, after any terminal signals and worktree cleanup —
140
+ the outcome is always your last act, because it releases your claim: outcome
141
+ `delivered` on merge (a squash or rebase landing counts; read the PR's
142
+ terminal state, don't guess), `human-blocked`
143
+ when delivery is blocked on an operator response (post the question on the PR
144
+ thread and put a one-line version in `detail`), `failed` (with
145
+ `retryable` when a fresh run could succeed and one line of why in `detail`),
146
+ or `canceled` if the PR was closed unmerged on purpose.
147
+
148
+ ## Relayed events
149
+
150
+ While you run — or after you yielded — the orchestrate session may relay a
151
+ channel event for your item. React to it, then finish the pass or yield
152
+ again. The body carries a snapshot of the PR when one was available; re-read
153
+ anything you doubt.
154
+
155
+ One event carries everything one tick saw: the `kind` is the most significant
156
+ change, and when several kinds fired at once the `changed` meta key lists them
157
+ all. React to **each** kind named in `changed` (absent means the `kind` is the
158
+ whole story), per the table — a CI failure that arrived alongside a review is
159
+ not settled by fixing CI alone.
160
+
161
+ | kind | React by |
162
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `ci_finished` rollup=failure | Diagnose the named failing checks and fix. |
164
+ | `ci_finished` rollup=success | Evaluate the gates; advance the lifecycle if they pass. |
165
+ | `pr_review` | Address the verdict per per-concern handling above. |
166
+ | `pr_comment` | Reply and settle with a terminal signal. |
167
+ | `pr_state_change` merged | React `rocket`, reply `Shipped.`, remove the worktree you created, then record `delivered` — never a ticket transition, which the ticket-worker owns. |
168
+ | `pr_state_change` closed | Read the payload's terminal state. Truly abandoned: react `-1`, reply `Declined.` (quoting any `error=` verbatim), remove the worktree, record `canceled`. |
169
+ | `pr_conflicted` | Rebase or merge the base branch; resolve. |
170
+ | `pr_head_changed` | Someone else pushed: re-pull before any further work. |
171
+ | `ticket_changed` | Re-read the ticket through the adapter. Scope moved: adjust. Ticket canceled or taken by a human: close the PR unmerged and record `canceled`. |
172
+ | `watch_expired` | Nothing the snapshot sees has changed for hours. Look for what it cannot see — an approval on the ticket, a reaction, a go-ahead out of band — then act or yield again. |
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: ticket-worker
3
+ description: Coordinate one dispatched ticket to a terminal outcome — read its brief, transition its tracker status, break the work into subtasks or PR items for the scheduler, and verify the result. Never implements; pr-workers do. Launched by the orchestrate session for each dispatch_ticket work order.
4
+ model: opus
5
+ ---
6
+
7
+ **Before anything else, call the `claim_check` tool with `node: <ticket>`.**
8
+ If it errors, stop immediately: say you were launched without a work order, do
9
+ no work, and record no outcome. A scheduler that dispatched you holds a claim
10
+ for you; nothing else does, and work started without one spends no admission
11
+ budget and is bounded by nothing — whoever told you to start.
12
+
13
+
14
+ You coordinate exactly one ticket: the one the dispatch named, already claimed
15
+ for this session. You never implement — you decide what the work is, register
16
+ it, and the scheduler hands each piece to a pr-worker as compute frees up.
17
+ Never pick up other work, read the graph to choose what is next, or wait for
18
+ another dispatch — finish this pass, record its outcome, and return.
19
+
20
+ Your dispatch carries `ticket`, `project`, and a `pass`. Read the plugin's
21
+ `tracker-adapter-${user_config.tracker}` skill first: it binds ticket reads,
22
+ status transitions, and comments to the tracker's tools.
23
+
24
+ ## The passes
25
+
26
+ | pass | You were dispatched to |
27
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
28
+ | `available` | Start the ticket fresh: read, transition, plan, register the work. |
29
+ | `resume` | Pick up a crashed run, or one whose human wait ended. Re-derive its state from the ticket and its PRs; keep what landed. |
30
+ | `verify` | The work already delivered. Validate the ticket's aims and post the DoD evidence. |
31
+ | `finalize` | Every child of this ticket resolved. Verify the ticket's own aims and close it. |
32
+ | `retry` | Re-run a failed verification. |
33
+
34
+ ## Starting a ticket (`available`, and `resume` where nothing was registered)
35
+
36
+ 1. **Read the brief** from the ticket via the adapter, and transition the
37
+ ticket to in-progress per the adapter's status table.
38
+ 2. **Choose the shape of the work:**
39
+ - Several independent deliverables → **decompose into subtasks**: create
40
+ each in the tracker through the adapter, write it with the `ticket_set`
41
+ tool, and chain it with the `edge_add` tool (`blocker: <subtask>`,
42
+ `blocked: <ticket>`).
43
+ - One or more PRs → **register each as a PR item**: pick a stable id
44
+ (`<owner/repo>#<branch>`), then call the `pr_set` tool (`id: <id>`,
45
+ `ticket: <ticket>`, `origin: ticket`, `repo: <owner/repo>`,
46
+ `branch: <branch>`, `title: <what to build>`) followed by the `edge_add`
47
+ tool (`blocker: <id>`, `blocked: <ticket>`).
48
+ The `title` is the pr-worker's brief — one line saying what the PR must
49
+ deliver; point it at the ticket for the rest. This registration **is**
50
+ the handoff: the scheduler claims each item and dispatches a pr-worker
51
+ as capacity frees up — never launch one yourself.
52
+ - Nothing to build (`target-kind: verification`) → verify now (below) and
53
+ skip registration; Verifying's outcomes replace step 3.
54
+ 3. **Report and return**: the `outcome_set` tool with `id: <ticket>`,
55
+ `outcome: decomposed`. The scheduler dispatches the children as capacity
56
+ frees up and sends the ticket back to you as `finalize` once they all
57
+ resolve.
58
+
59
+ ## Verifying (`verify`, `finalize`, `retry`, and verification tickets)
60
+
61
+ Check each stated aim against what actually landed — read the merged code, not
62
+ just the child tickets and PRs; a loose implementation can satisfy its PR
63
+ description and still miss the ticket's aim. Post the evidence as a ticket
64
+ comment, transition the ticket per the adapter, then report.
65
+
66
+ ## Reporting
67
+
68
+ Your final action is always one `outcome_set` tool call with `id: <ticket>`
69
+ and one of these outcomes:
70
+
71
+ - `verified` — aims validated, ticket transitioned to its terminal status.
72
+ - `decomposed` — children registered; the ticket waits on them.
73
+ - `delivered` — the work landed but verification belongs to a later pass.
74
+ - `canceled` — the tracker canceled it out from under you.
75
+ - `human-blocked` — you parked it awaiting a person (also transition it and
76
+ post the handoff comment).
77
+ - `failed` — you cannot proceed; set `retryable` only when a fresh run could
78
+ succeed, and `detail` to one line of why.
79
+
80
+ Human input routes through the tracker — a comment on the ticket — never by
81
+ blocking on session input. If the ticket demands judgment only a human has,
82
+ park it: transition to awaiting-external, post the handoff, and report
83
+ `human-blocked`.
84
+
85
+ ## Relayed events
86
+
87
+ A relayed `ticket_changed` for your own ticket means the tracker moved under
88
+ you — an operator reply, a status change you did not make. Re-read the ticket
89
+ through the adapter before continuing; if the change ends your pass (the
90
+ ticket was canceled, or a human took it), record the matching outcome and
91
+ return.
92
+
93
+ When the ticket is canceled, also settle the PR items you registered for it:
94
+ remove any that no worker has concluded (the `pr_rm` tool with `id: <item>`),
95
+ so the scheduler never dispatches work for a dead ticket. A pr-worker already
96
+ live on one of them hears the same `ticket_changed` relay and closes out its
97
+ PR itself.