@detergent-software/conductor 1.5.0-dev.4 → 2.0.0-dev.2

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 (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +67 -31
  3. package/build/brief-cli.js +28 -13
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/brief.js +8 -2
  6. package/build/brief.js.map +1 -1
  7. package/build/broker-cli.js +41 -2
  8. package/build/broker-cli.js.map +1 -1
  9. package/build/checkout-sync.js +2 -2
  10. package/build/checkout-sync.js.map +1 -1
  11. package/build/cli.js +40 -1
  12. package/build/cli.js.map +1 -1
  13. package/build/code-host-cli.js +32 -15
  14. package/build/code-host-cli.js.map +1 -1
  15. package/build/conditions.js +4 -3
  16. package/build/conditions.js.map +1 -1
  17. package/build/config.js +34 -9
  18. package/build/config.js.map +1 -1
  19. package/build/credential.js +13 -2
  20. package/build/credential.js.map +1 -1
  21. package/build/credentials.js +240 -104
  22. package/build/credentials.js.map +1 -1
  23. package/build/daemon.js +3 -0
  24. package/build/daemon.js.map +1 -1
  25. package/build/file-lock.js +106 -0
  26. package/build/file-lock.js.map +1 -0
  27. package/build/help.js +83 -3
  28. package/build/help.js.map +1 -1
  29. package/build/history.js +1 -1
  30. package/build/history.js.map +1 -1
  31. package/build/in-process.js +15 -4
  32. package/build/in-process.js.map +1 -1
  33. package/build/inbox.js +21 -9
  34. package/build/inbox.js.map +1 -1
  35. package/build/lib/providers/ado/http.js +6 -2
  36. package/build/lib/providers/ado/http.js.map +1 -1
  37. package/build/lib/providers/index.js +1 -1
  38. package/build/lib/providers/index.js.map +1 -1
  39. package/build/manager.js +5 -4
  40. package/build/manager.js.map +1 -1
  41. package/build/note.js +18 -8
  42. package/build/note.js.map +1 -1
  43. package/build/notes.js +16 -31
  44. package/build/notes.js.map +1 -1
  45. package/build/profile.js +14 -7
  46. package/build/profile.js.map +1 -1
  47. package/build/project-check-cli.js +1 -1
  48. package/build/project-check-cli.js.map +1 -1
  49. package/build/project-check.js +90 -10
  50. package/build/project-check.js.map +1 -1
  51. package/build/protocol.js +174 -0
  52. package/build/protocol.js.map +1 -0
  53. package/build/queue.js +1 -1
  54. package/build/queue.js.map +1 -1
  55. package/build/rule-actions.js +3 -2
  56. package/build/rule-actions.js.map +1 -1
  57. package/build/rules.js +14 -16
  58. package/build/rules.js.map +1 -1
  59. package/build/search.js +130 -0
  60. package/build/search.js.map +1 -0
  61. package/build/store.js +15 -8
  62. package/build/store.js.map +1 -1
  63. package/build/tracker-reads-cli.js +89 -0
  64. package/build/tracker-reads-cli.js.map +1 -0
  65. package/build/train.js +3 -3
  66. package/build/train.js.map +1 -1
  67. package/build/wait.js +9 -3
  68. package/build/wait.js.map +1 -1
  69. package/build/watchers/boards.js +8 -0
  70. package/build/watchers/boards.js.map +1 -1
  71. package/package.json +2 -1
  72. package/protocol/PROTOCOL.md +118 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  Releases are tagged `conductor-v<version>` and staged on npm by `atk-conductor-release` (`pipelines/release-conductor.yml`), which writes each section below. `scripts/check-changelog.mjs` fails when a tag has no section here.
4
4
 
5
+ ## [2.0.0-dev.2](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v2.0.0-dev.1&targetVersion=GTconductor-v2.0.0-dev.2) (2026-10-16)
6
+
7
+ ### Features
8
+
9
+ * **conductor:** conduct lock takes a file lock with no conductor (ATK-524) ([97ae248](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/97ae248eaac5b0d1134befe9957c2bdef793498b))
10
+ * **conductor:** conduct notes, comments, search and mode (ATK-524) ([b6c983e](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b6c983eaca8ea2f115500fd4c1bc0f5e0667c2aa))
11
+ * **conductor:** take each provider's credential least privilege first (ATK-524) ([b3facc6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b3facc66cb1c9580120d17339fafc924ddb75e44))
12
+
13
+ ### Bug Fixes
14
+
15
+ * **conductor:** close the credential and search gaps from review (ATK-524) ([a4bf41c](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/a4bf41c5562fb74e5fe198ce23fb87e209f88dd5))
16
+
17
+ ### Documentation
18
+
19
+ * name the conduct verbs, not the work-item helper (ATK-524) ([8af44ed](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8af44ed5bf69d22d990e5da36bdb87cd3ff06c1f))
20
+
21
+ ## [2.0.0-dev.1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.5.0-dev.4&targetVersion=GTconductor-v2.0.0-dev.1) (2026-10-16)
22
+
23
+ ### ⚠ BREAKING CHANGES
24
+
25
+ * **conductor:** worker protocol 2 replaces protocol 1, with no
26
+ support for it. A profile must say "protocol": 2, its work skill must
27
+ declare protocol: 2 in its frontmatter (work-item 2.0.0), and notes,
28
+ rules and config must use operator and release in place of jason,
29
+ prod-approval and ready-to-release.
30
+
31
+ Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
32
+
33
+ ### Features
34
+
35
+ * **conductor:** worker protocol 2, owned by the package (ATK-523) ([bc46f57](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/bc46f575e31a0583f7c9a6647027c4ff926853ec))
36
+
37
+ ### Bug Fixes
38
+
39
+ * **conductor:** close protocol 2's validation gaps from review (ATK-523) ([5f8e192](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/5f8e1925b8641e339b6fc65f6044c6ceda51ac1a))
40
+
5
41
  ## [1.5.0-dev.4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.5.0-dev.3&targetVersion=GTconductor-v1.5.0-dev.4) (2026-10-16)
6
42
 
7
43
  ### Features
package/README.md CHANGED
@@ -53,6 +53,13 @@ conduct note <item> <file> post a phase note on the item and record its p
53
53
  [--worker <worker>]
54
54
  conduct brief <item> [--json] [--fresh]
55
55
  the item's status, links, phase notes and approval, from the tracker (see Briefs)
56
+ conduct notes <item> [--phase <phase>] [--kind <kind>] [--last] [--json]
57
+ the item's phase notes, whole (see Tracker reads)
58
+ conduct comments <item> [--since <time>] [--json]
59
+ every comment on the item, whole
60
+ conduct search <terms…> [--json] the tracker's open items that match every term
61
+ conduct mode --tier S|M|L --files <a,b>
62
+ the landing mode a plan states before its PR (see Worker verbs)
56
63
  conduct wait <condition> wait for an outside event (see Waits)
57
64
  conduct state [--repo <repo>] the worktree's branch, changes and PR, and its other repos' (see Worker verbs)
58
65
  conduct branch <type> <slug> [--item <item>]
@@ -100,7 +107,7 @@ The session manager owns the `claude --bg` sessions it launches, and only those:
100
107
  - **restarts:** a launch the daemon was stopped in adopts its session if one was started, and otherwise fails and removes its clean worktree.
101
108
  - **attach:** a parked worker is resumed with no event text, so it wakes idle and keeps running when its tab closes. An attached worker isn't parked until it registers a new wait, or until its turn has been over `sessions.idleMinutes` (`worker.detached`). The daemon can't see the tab, so this also delivers a wait that resolved while it was attached. A wait that resolves after the attach, on a worker whose transcript shows no turn since then, is delivered at once: the worker is detached (`reason: wait resolved`), parked, which closes its tab, and resumed with the event. A worker someone woke outside the daemon counts as attached since its stop, so the turn that woke it counts. One that took a turn since the attach (or has no transcript to tell), or whose wait resolved before the attach, keeps it until the idle release, logs `worker.resume-held` (`reason: attached`) once, and `conduct status` shows it as `idle, holding resolved waits for the attached session`.
102
109
  - **park failures:** a session whose process outlives `claude stop` by 30 s logs one `worker.park-failed` per spell, which raises a `stall` in the inbox (see Inbox), and the park is tried again every 5 minutes.
103
- - **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell, and `worker.stall-cleared` when that spell ends. `claude agents` names no command, so a permission stall reads the session's transcripts (its own and its subagents') for the call still open: its tool, the command or path, the subagent that made it, and the auto-mode classifier denials just before it. The inbox item and its push say them, for example "ATK-297 has waited 5 min on a permission prompt: Bash `git -C … diff …` in subagent general-purpose (Review ATK-297 diff vs plan), after 2 auto-mode denials". Auto mode asks a person after 3 classifier denials in a row (20 in a session), even in a background subagent, so that count is usually why the prompt came. An open item of a Handoff kind with nothing to watch (`abandoned`, `blocked`, `failed`, `human`, `jason`, `scope`) on the worker, or on its item with no worker, counts as a wait: its idle clock starts once that item resolves. A `merge`, `npm-otp` or `plan` item doesn't: those come with a wait, so a worker idle with only the item still stalls.
110
+ - **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell, and `worker.stall-cleared` when that spell ends. `claude agents` names no command, so a permission stall reads the session's transcripts (its own and its subagents') for the call still open: its tool, the command or path, the subagent that made it, and the auto-mode classifier denials just before it. The inbox item and its push say them, for example "ATK-297 has waited 5 min on a permission prompt: Bash `git -C … diff …` in subagent general-purpose (Review ATK-297 diff vs plan), after 2 auto-mode denials". Auto mode asks a person after 3 classifier denials in a row (20 in a session), even in a background subagent, so that count is usually why the prompt came. An open item of a Handoff kind with nothing to watch (`abandoned`, `blocked`, `failed`, `human`, `operator`, `scope`) on the worker, or on its item with no worker, counts as a wait: its idle clock starts once that item resolves. A `merge`, `npm-otp` or `plan` item doesn't: those come with a wait, so a worker idle with only the item still stalls.
104
111
  - **spend:** tokens from the session's transcript and its subagents' (`~/.claude/projects/*/<session>.jsonl`), each message counted once, go into `spend` per worker and local day. It keeps input, output, cache read and cache write; `tokens` is their sum. A worker whose tokens in all, not counting cache reads, pass `inbox.runawayTokens` logs `worker.runaway` once per daemon run. Cache reads are left out because they are nearly all of a session's tokens (97% on ATK-312) at a tenth of the price. The spend `conduct status`, `GET /fleet` and the console's fleet pane show (`spend.today`, `spend.total`) and the `spend.*` rule fields count the same tokens: input, output and cache writes. Status shows the worker's cache reads beside them, labelled (`spend 417k today, 1.0M total, 25M cache reads`; `spend.cacheRead` in `GET /fleet`).
105
112
  - **context:** the session's context is its main transcript's newest model call: input, cache read, cache write and output. Subagents have contexts of their own, so theirs don't count. After a compaction it is unknown until the next call: the boundary counts only the conversation it kept, and the call adds the system prompt and tools back (a probe that kept 2.8k next read 68k). Status shows it as `context 312k` (`context` in `GET /fleet`), and the rules read it as `worker.context`.
106
113
  - **probes:** `--probe` names the worktree and session `probe-<item>` and gives it a prompt that works no issue, so tests stay out of the live fleet.
@@ -121,14 +128,16 @@ A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one li
121
128
 
122
129
  A phase mapped to `null` moves nothing (`started` still assigns). A `Started` never reopens a closed item (`kept`); a `PR opened` on one does, for a follow-up PR. The PR linked is the worker's, else the note's first PR link. A write the tracker refuses is a warning on the note's answer and stderr, and a `tracker.transition-failed` event; the note stands, and the verb still exits 0. A write that took is `tracker.transitioned`. With no daemon, `conduct note` posts from the item's project checkout as its account, and moves the item the same way; nothing is recorded then.
123
130
 
131
+ **Worker protocol 2.** The notes, their phases and kinds, the waits and the wake text are the package's spec, [`protocol/PROTOCOL.md`](protocol/PROTOCOL.md), held as data in `src/protocol.ts`. `conduct note` checks each note against it before anything is posted, on the daemon and with no daemon alike. It refuses one with an unknown phase, a Handoff without a kind and its line, a kind its phase doesn't carry (`jason` is `operator` since protocol 2), or a Plan without its `**Size tier**` and `**Landing:**` lines, and the error names the rule. The readers (watchers, briefs, history) still read older notes and people's comments.
132
+
124
133
  The work skill is the profile's `skills.work`. A note headed with one of `skills.aliases` counts the same, there and wherever the daemon reads notes (approvals, `conduct wait item … note`, the review-thread checks of `conduct wait pr … comment`). So when a project renames its work skill (ATK's `work-issue` became `work-item`), the old name goes in `aliases` and items in flight keep their phases.
125
134
 
126
135
  | Exit | Means | The worker then |
127
136
  |---|---|---|
128
137
  | 0 | posted | goes on |
129
- | 3 | nobody posted it: the tracker refused it (such as a read-only key), or no daemon and this isn't the item's project checkout | posts the note to the tracker itself, as before protocol v1 |
130
- | 4 | not posted: Jason wrote `revise: …` after the item's newest Plan was approved, by him (a comment, `conduct approve`, the worker's tab) or by the conductor (`auto-approve now`, see Rules); stdout has his words | stops, re-plans with them, posts a new Plan and waits for approval, which the gate now holds |
131
- | 1 | refused (no heading, no project) or unclear (the daemon or Linear didn't answer the write) | reads the message; when it says the note may be there, looks before posting again |
138
+ | 3 | nobody posted it: the tracker refused it (such as a read-only key), or no daemon and this isn't the item's project checkout | posts the note to the tracker itself |
139
+ | 4 | not posted: an approver wrote `revise: …` after the item's newest Plan was approved, by them (a comment, `conduct approve`, the worker's tab) or by the conductor (`auto-approve now`, see Rules); stdout has their words, and the message names config.json's `operator.name` | stops, re-plans with them, posts a new Plan and waits for approval, which the gate now holds |
140
+ | 1 | refused (it breaks worker protocol 2, no project) or unclear (the daemon or Linear didn't answer the write) | reads the message: a refusal names the rule; when it says the note may be there, looks before posting again |
132
141
 
133
142
  On Linear the daemon uses the key the watchers read (below), so that key needs **Create comments** as well as Read. Comments it writes carry the key owner's name, as the Linear MCP's do. On ADO Boards it posts a markdown discussion comment as the `az login` identity, and on GitHub Issues an issue comment as the project's gh account.
134
143
 
@@ -154,19 +163,31 @@ A brief is kept for 30 seconds. Anything the daemon sees happen to the item drop
154
163
  | Exit | Means | The worker then |
155
164
  |---|---|---|
156
165
  | 0 | the brief | uses it |
157
- | 3 | no daemon and this isn't the item's project checkout, or the tracker couldn't be read (no key, the tracker down) | reads the item from the tracker itself (the Linear MCP, or the work skill's helper) |
166
+ | 3 | no daemon and this isn't the item's project checkout, or the tracker couldn't be read (no key, the tracker down) | says why, and runs it again from the item's checkout or once the login is back |
158
167
  | 1 | refused: no project tracks the item, or the tracker has no such item | reads the message |
159
168
 
169
+ ## Tracker reads
170
+
171
+ The rest of what a work skill and the console read from the tracker, so neither needs a tracker's MCP tools (design note v2, section 4). Each works for Linear, ADO Boards and GitHub Issues.
172
+
173
+ - `conduct notes <item>` prints the item's phase notes with their whole text, oldest first, each with when and who. `--phase` and `--kind` keep the ones that match (case aside), and `--last` the newest of those: `conduct notes ATK-312 --phase Handoff --kind conductor --last` is the Handoff a console escalation is about.
174
+ - `conduct comments <item>` prints every comment the same way, and whether an approver wrote it; `--since <time>` keeps the newer ones.
175
+
176
+ Both read the item's brief (the comments now carry their author and their note heading), so they read as the daemon reads, else here, and exit 3 as `conduct brief` does. `--json` prints them as one object.
177
+
178
+ - `conduct search <terms…>` lists the open items (not started or started) of the checkout's project's tracker that match every term, newest first, with status and Area: at most 25. Linear matches a term in the title or description, GitHub Issues as its search does (title or body), Boards in the title. It is how a worker looks for a duplicate before `conduct file`. A read, so it runs here as the project's account.
179
+
160
180
  ## Worker verbs
161
181
 
162
- Protocol 2's verbs on git and the code host (providers design note, section 4), run in the item's worktree. They come beside the work-item helper's `state`, `rename`, `checkout` and `pr-create`, which stay until the cutover (ATK-457). Each prints one JSON object with `via`: `daemon` when the daemon wrote for the worker that owns the checkout, `local` when it ran in the `conduct` process (no daemon, or one without the route). Exit codes: 0 done, 1 refused or failed (the message says why), 5 a lost login (run `conduct wait login <provider>` and end the turn).
182
+ Protocol 2's verbs on git and the code host (providers design note, section 4), run in the item's worktree. They replace the work-item helper, which work-item 3.0 no longer ships (ATK-524). Each prints one JSON object with `via`: `daemon` when the daemon wrote for the worker that owns the checkout, `local` when it ran in the `conduct` process (no daemon, or one without the route). Exit codes: 0 done, 1 refused or failed (the message says why), 5 a lost login (run `conduct wait login <provider>` and end the turn).
163
183
 
164
184
  - `conduct state`, `conduct branch` and `conduct checkout` are git in the worktree, so they always run here. `state` reads the branch's PR as the project's account (see Accounts). `checkout` clones another GitHub repo into `.scratch/repos/<repo>`, keeps it out of the worktree's git status, and retire checks it for unpushed or uncommitted work (ATK-429).
165
185
  - `conduct pr create` checks everything before it pushes, `--dry-run` included: the title carries the item key, the branch carries it too and isn't the base, the description fits the host (ADO 4,000 characters, GitHub 65,536), no PR is open from the branch yet, nothing is uncommitted, the branch is ahead of its base, and commitlint passes (skipped in a clone without it installed, whose own CI lints the PR). Then it pushes from the checkout and has the daemon open the PR (`POST /workers/:id/prs`). The daemon checks ownership again from its own records and the worktree, never from the request: the worker is live, the branch the worktree is on now (or the `--repo` checkout's) carries the worker's item, and the title carries it. It records the PR on the worker (`pr`; another repo's only while the worker has none) and logs `code-host.pr-created`. `conduct branch` renames only the item's own branch, never the base, `main` or another item's.
166
186
  - `conduct land <pr>` sets the worker's PR up to land, as the helper's `land` did, and is safe to repeat. The PR must be the worker's (the same checks, and from the branch the checkout is on now, into its base). A brake already on it (a comment, a failed build, conflicts) is reported, and nothing is armed. A Landing note whose landing armed something, with that gone now, means someone cancelled it: `auto-merge-cancelled`, until `--rearm`. `--mode` is held to the profile's landing rules (`work.landing`) for the PR's files as pushed (`origin/<base>...origin/<head>`, fetched first) and the plan's tier. The daemon reads the tier from the item's Plan note; in-process it takes `--tier`, and auto is refused without one. Without `--mode` the rules pick it. Then it arms the host's own landing behind its capability check (`landingCheck`). On ADO that is a +10 vote in auto mode, with auto-complete and noFastForward. On GitHub it is auto-merge with a merge commit in auto mode, where the repo allows it; in review mode nothing is armed. It posts the Landing note last, and a new one whenever the mode changes, then prints the verdict. A failed build comes with its log, and a merge with whether it has two parents. The daemon logs `code-host.landing-armed` with the mode.
167
187
  - `conduct wait pr <pr>` (no event word) is the verdict wait, `pr:<host>:<repo>#<n>:verdict`: it holds on the first verdict that isn't `waiting`, with a line such as `pr 2030 build-failed: <pipeline>`. While it waits on a PR whose landing the daemon recorded, the ADO watcher keeps it armed. It queues an expired build again (once per wait) and, in auto mode, restores a cleared vote (never over a negative one), logged as `code-host.kept-armed`. A local wait (no daemon) gives the same verdicts but keeps nothing armed.
168
188
  - `conduct pr create` ends the description with the tracker's reference when it has one, before the length check: `AB#<id>` for an ADO Boards item on a GitHub PR, so the Boards GitHub app links it, and `Refs <owner>/<repo>#<n>` for a GitHub Issues item on a GitHub PR. Linear has none.
169
- - `conduct file --title … --body-file … --area … [--related …] [--blocked-by …] [--human]` files a follow-up of the checkout's item: in Backlog, in the item's project, with one Area (a label of the profile's Area group, a path below a board's `areaRoot`, or the name after a GitHub Issues `areaPrefix` label), the profile's `triageLabel`, and its `humanLabel` with `--human`. It is related to the item and each `--related`, and waits on each `--blocked-by` (keys, repeated or comma-separated). A key of another project, or an Area outside the group, is refused before anything is written. On Linear a relation that fails after the issue exists is a warning; on Boards the item and its links are one request. On GitHub Issues the follow-up also carries the `queueLabel`, the related items are a `Related: #n` line in its body (GitHub has no related relation), and a dependency that fails after the issue exists is a warning. The daemon files it for the worker (`POST /workers/:id/follow-ups`) and logs `tracker.filed`. The worker searches for a duplicate first, with the tracker's own tools: this only files.
189
+ - `conduct file --title … --body-file … --area … [--related …] [--blocked-by …] [--human]` files a follow-up of the checkout's item: in Backlog, in the item's project, with one Area (a label of the profile's Area group, a path below a board's `areaRoot`, or the name after a GitHub Issues `areaPrefix` label), the profile's `triageLabel`, and its `humanLabel` with `--human`. It is related to the item and each `--related`, and waits on each `--blocked-by` (keys, repeated or comma-separated). A key of another project, or an Area outside the group, is refused before anything is written. On Linear a relation that fails after the issue exists is a warning; on Boards the item and its links are one request. On GitHub Issues the follow-up also carries the `queueLabel`, the related items are a `Related: #n` line in its body (GitHub has no related relation), and a dependency that fails after the issue exists is a warning. The daemon files it for the worker (`POST /workers/:id/follow-ups`) and logs `tracker.filed`. The worker searches for a duplicate first, with `conduct search`: this only files.
190
+ - `conduct mode --tier S|M|L --files <a,b>` gives the landing mode a plan states before its PR exists, with the reasons, by the rule `conduct land` holds the PR to (`work.landing`). It reads only the profile, so it runs here.
170
191
  - `conduct pr threads <pr>` lists the PR's threads (on GitHub each comment and review) with `needsAnswer` (ATK-384). `conduct reply <pr> [<thread>]` answers on the worker's own PR: in the ADO thread named, or as a note on the PR. The text is headed `## <work skill> · Reply` unless it starts with a note heading. The daemon logs `code-host.replied`. `--repo` takes `github:<owner>/<repo>` (from its `.scratch/repos` checkout, into its default branch) or `ado:<repository>`; the profile's `codeHost` is the default.
171
192
  - `conduct runs <sha>` is one look at a merge's runs, as the helper's `runs-watch --once` and `stage` were (ATK-455). It is a read, so it always runs here.
172
193
  - **Which runs.** On Azure Pipelines (the profile's `ci: ado-pipelines`, for its Azure DevOps repo), they are the pipelines whose YAML trigger the merge fires. The YAML and the merge's changed files are read in the worktree, which fetches the base when it hasn't got the commit. On GitHub Actions (`--repo github:<owner>/<repo>`, or `ci: github-actions` for a GitHub repo), they are the runs the merge's push started.
@@ -198,7 +219,7 @@ Accounts are per project (see Accounts). Each code-host write is an event: `code
198
219
 
199
220
  ## Inbox
200
221
 
201
- The `inbox` table holds everything that waits on Jason, across projects. Each item records:
222
+ The `inbox` table holds everything that waits on the operator, across projects. Each item records:
202
223
 
203
224
  - its kind;
204
225
  - the item and worker it is about;
@@ -222,7 +243,7 @@ An item resolves itself when its condition is seen, and records what resolved it
222
243
  | `plan` | a `Plan` note (`conduct note`), or an `approval` wait; not a Plan an `auto-approve now` rule approves (unless its comment fails). An answer by someone who isn't an approver adds `(ignored approve from <name>: not an approver)` to its text | the approval wait resolving (an approver's `approve` or `revise:` comment), a `Plan approved` note, a newer Plan, or `conduct approve` / `revise` |
223
244
  | `merge` | a `Handoff · merge: <PR>` note; a `pr … merged` wait attaches to it. Held while its PR's checks are red, and held at once (no push) when the note names a PR whose checks are red: raised again as it was when a checks wait on the PR resolves passed | its PR wait resolving as merged, abandoned or closed, a `Landed` note, or the PR's checks failing (`pr.checks-failed`, resolved as `held: …`). A PR wait without the note raises nothing, because auto landing needs nobody |
224
245
  | `npm-otp` | a `Handoff · npm-otp` note (the `npm stage approve …` on its heading is the command), or an `npm` wait | the version on its tag, or a `Resumed` or `Landed` note |
225
- | `jason`, `human`, `scope`, `blocked`, `abandoned`, `failed` | a Handoff note of that kind | any later note on the item (such as `Resumed`), or `conduct done` |
246
+ | `operator`, `release`, `human`, `scope`, `blocked`, `abandoned`, `failed` | a Handoff note of that kind | any later note on the item (such as `Resumed`), or `conduct done` |
226
247
  | `permission`, `stall` | `worker.stalled` (no idle `stall` while the worker's Handoff item with nothing to watch is open; see Workers) | `worker.stall-cleared`, a park or resume, a new wait or Handoff (stall only), or a daemon restart (it is raised again if it persists) |
227
248
  | `stall` (park failing) | `worker.park-failed`, with `conduct attach` and `conduct retire` | a park, the session stopping unexpectedly (which raises `failed`), its retirement, or a daemon restart (the park is tried again, and fails again if it still does). A new wait doesn't resolve it. A retire stops the session the same way, so when the pid outlives `claude stop`, end that process: the next sweep finds the session gone |
228
249
  | `failed` | a failed launch, or a session that stopped unexpectedly | a new launch, the worker running again (a retry's revive included), or its retirement |
@@ -231,12 +252,12 @@ An item resolves itself when its condition is seen, and records what resolved it
231
252
  | `login` | `provider.login-failed`: a project's provider login stopped working (see Accounts), once per provider and account however many projects and calls find it, with the command that signs in again | the login working again, found by the daemon's re-probe, any call, or a `login` wait (`provider.login-restored`), `conduct done`, or a daemon restart (the spell starts over) |
232
253
  | `runaway` | a worker passing `inbox.runawayTokens` (1.5M) input, output and cache-write tokens in all, once per worker | `conduct done`, or its retirement |
233
254
  | `context` | `worker.compact-failed`: the daemon couldn't compact a worker whose context passed the `compact-on-park` rule (see Workers), while one is open, with `conduct attach` (run `/compact` in it) and `conduct retire` | the session compacting (`worker.compacted`), `conduct done`, or its retirement |
234
- | `ready-to-release`, `queue-drained` | the scheduler's default rules (see Scheduler) | `conduct done` |
255
+ | `release`, `queue-drained` | the scheduler's default rules (see Scheduler): `ready-to-release` raises `release` with the profile's `release.handoff` text (ATK's says to run `/release-atk`) | `conduct done` |
235
256
  | `triage` | the scheduler's default rule `triage`: an item with the profile's triage label, once per item (see Scheduler) | a read of its project that no longer shows the label on the item (removed, or the item closed or left the queue), in `scheduler.mode` `on`; or `conduct done`, after which it is raised again the next day while the label stays. Once resolved, a label put back raises it again at once |
236
257
  | `console` | the console's finding on an escalation, with `conduct escalations done <id> --ask` (see Console) | `conduct done` |
237
258
  | `console` (no console open) | escalations wait and no console has followed the stream for 30 s, with `conduct console` and `conduct escalations` (see Console) | a console opening, the last escalation handled, or `conduct done` |
238
259
 
239
- Nothing raises `prod-approval` yet; `/release-atk` will. When a worker is retired, its open items resolve with it.
260
+ Worker protocol 2 renamed the kind `jason` to `operator` and merged `prod-approval` and `ready-to-release` into `release`; schema 10 moved the stored items. The daemon never approves prod itself. When a worker is retired, its open items resolve with it.
240
261
 
241
262
  For an item raised from a note:
242
263
 
@@ -261,13 +282,14 @@ The notifier routes each new inbox item to the phone, on two channels:
261
282
 
262
283
  ```jsonc
263
284
  {
285
+ "operator": { "name": "Jason" }, // the one person this daemon serves; messages name them ("the operator" by default)
264
286
  "quietHours": { "from": "22:00", "to": "07:00" }, // local; omit for none
265
287
  "notify": {
266
288
  "profile": "default", // the profile when none is switched to
267
289
  "profiles": {
268
290
  "default": [{ "to": ["remote-control", "pushover"] }], // built in
269
291
  "away": [{ "to": ["pushover"] }], // built in
270
- "focus": [{ "kinds": ["npm-otp", "permission", "failed", "conflict", "jason", "login"], "to": ["remote-control", "pushover"] }], // built in
292
+ "focus": [{ "kinds": ["npm-otp", "permission", "failed", "conflict", "operator", "login"], "to": ["remote-control", "pushover"] }], // built in
271
293
  "workday": [
272
294
  { "projects": ["cadence"], "days": ["sat", "sun"], "to": [] }, // keep these off every channel
273
295
  { "kinds": ["npm-otp"], "to": ["pushover", "remote-control"], "remind": { "afterMinutes": 10, "times": 5 } },
@@ -294,7 +316,7 @@ Rules say what the conductor does when something happens (design note, section 9
294
316
  ```jsonc
295
317
  // ~/.conductor/rules.json
296
318
  [
297
- { "when": "wait.kind == merge and wait.age > 2h", "do": "remind jason" },
319
+ { "when": "wait.kind == merge and wait.age > 2h", "do": "remind operator" },
298
320
  { "when": "plan.tier == S and plan.landing == auto and item.area in [docs, ops]", "do": "auto-approve after 15m" },
299
321
  { "id": "runaway", "when": "spend.worker > 5M", "do": "inbox runaway" }, // replaces the default
300
322
  { "id": "daily-cap", "do": "none" }, // switches a default off
@@ -302,9 +324,9 @@ Rules say what the conductor does when something happens (design note, section 9
302
324
  ]
303
325
  ```
304
326
 
305
- - **Sources, in order:** the item's project's `rules` (in its `.claude/conductor.json`, changed by PR), then `~/.conductor/rules.json`, then the built-in defaults: conduct-atk's behavior written as rules (`launch`, `retire-closed`, `park-on-wait`, `resume-on-wait`, `compact-on-park`, `runaway`, `daily-cap`, `ready-to-release`, `queue-drained`, `triage`, which raises a new item that waits on the profile's triage label in the inbox, `conductor-handoff`, which escalates a worker's `Handoff · conductor` note to the console, and `hold-pre-start-handoff`, which adds the human label when a worker hands off as `human`, `jason`, `blocked` or `scope` before its `Started` note, so the scheduler never launches work no worker can do again; remove the label if the item should be picked later). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences.
327
+ - **Sources, in order:** the item's project's `rules` (in its `.claude/conductor.json`, changed by PR), then `~/.conductor/rules.json`, then the built-in defaults: conduct-atk's behavior written as rules (`launch`, `retire-closed`, `park-on-wait`, `resume-on-wait`, `compact-on-park`, `runaway`, `daily-cap`, `ready-to-release`, `queue-drained`, `triage`, which raises a new item that waits on the profile's triage label in the inbox, `conductor-handoff`, which escalates a worker's `Handoff · conductor` note to the console, and `hold-pre-start-handoff`, which adds the human label when a worker hands off as `human`, `operator`, `blocked` or `scope` before its `Started` note, so the scheduler never launches work no worker can do again; remove the label if the item should be picked later). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences.
306
328
  - **when:** `and`, `or`, `not` and parentheses over comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`, `in [a, b]`, `<label> in item.labels`, `Started in item.phases`) and events. An event rule fires when one of its events happens: `note`, `plan`, `wait.resolved`, `run.failed` (a failed launch, a session that stopped unexpectedly, or a PR's red checks: `pr.checks-failed`), `worker.idle`, `worker.exited`, `worker.permission`, `worker.waiting`, `worker.parked` (a park; the session manager also asks it of each parked worker on a sweep, for `compact`), `inbox.raised`, `pr.comment`, `item.unblocked`, `item.closed`, `provider.login-failed` (a project's login lost, once per spell; see Accounts), `queue.pick`, `queue.drained`, `queue.ready-to-release`, `queue.triage`. A rule without one is a state rule, checked on each scheduler tick against each wait, inbox item, worker or project, or once for the machine, by the fields it reads. Fields: `project`, `item.id/area/labels/tier/priority`, `item.phases` (the phases of every note the item has had, the one being posted too), `plan.tier/landing`, `note.phase/kind`, `wait.kind/age`, `inbox.kind/age`, `worker.retries/phase`, `worker.context` (the session's context in tokens, see Workers), `spend.worker/item/project/today` (input, output and cache-write tokens, without cache reads: the worker's and the item's in all, the project's and the machine's today), `time`, `day`, `notify.profile`. Values carry units: `15m`, `2h`, `500k`, `2M`, `22:00`, `saturday`.
307
- - **do:** `launch`, `retire`, `park`, `resume worker`, `retry`, `pause launches [in <project>] [until <when> | for <duration>]`, `pause all launches …`, `unpause launches`, `focus [<project>] weight|slots <n> until <when>`, `auto-approve now`, `auto-approve after <duration>`, `inbox <kind> ["<text>"]`, `remind jason`, `notify <channel> ["<text>"]`, `notify profile <name> [until <when>]`, `label add|remove human|triage|shipped-open`, `flag shipped-open`, `escalate console "<ask>"`, `none`. One string, or a list.
329
+ - **do:** `launch`, `retire`, `park`, `resume worker`, `retry`, `pause launches [in <project>] [until <when> | for <duration>]`, `pause all launches …`, `unpause launches`, `focus [<project>] weight|slots <n> until <when>`, `auto-approve now`, `auto-approve after <duration>`, `inbox <kind> ["<text>"]`, `remind operator`, `notify <channel> ["<text>"]`, `notify profile <name> [until <when>]`, `label add|remove human|triage|shipped-open`, `flag shipped-open`, `escalate console "<ask>"`, `none`. One string, or a list.
308
330
  - **Guardrails:** rules act inside Area locks, slots, quiet hours and config.json's limits. A rule that would go past one is refused when it loads: `focus … slots` over `scheduler.slots`, an override with no end or one over 14 days, `retire` on anything but `item.closed`, `launch` outside `queue.pick` / `item.unblocked`, `park` outside `worker.waiting`, `resume` outside `wait.resolved` / `pr.comment`, `compact` outside `worker.parked`, `retry` outside `run.failed`. `merge`, `push`, `publish`, `approve`, `delete` and `run` aren't in the vocabulary: rules never merge, push, publish, approve prod, delete or run commands, and the daemon writes to a code host only when the owning worker asks (see Writes).
309
331
  - **Reload:** saving `rules.json` or a project's profile reloads its rules. A profile save, or a profile changed while the daemon was down, is taken whole: the daemon stores it and logs `project.updated` with the fields that `changed`, and the scheduler, watchers and the rest use it from then on. A profile that renames the project is refused: a project keeps the name it was registered under. To rename one, `conduct project remove <old name>`, then `conduct project add` it under the new name (in that order: with both registered, both would read the same queue and could launch the same item). The remove drops the old registration and its queue read, resolves its open inbox items (the refusal's among them) and logs `project.removed`, on which the daemon drops its rules and its watch of the file. It is refused while the project has workers in flight, and works without the daemon. A file the conductor refuses leaves its last good rules (for a profile, the whole last good profile) in force, logs `rules.rejected` and raises a `failed` inbox item until it loads again (`rules.loaded`), including after a restart. `conduct project add` refuses a profile whose rules don't load, with or without the daemon.
310
332
  - **Checking:** `conduct rules` lists the rules in force (`project.atk:#2`, `machine:runaway`, `default:launch`) and what replaces what. `conduct rules test <file>` dry-runs a sample, such as `{ "event": "pr.comment", "project": "atk", "fields": { "wait.kind": "merge", "wait.age": "3h" } }` (leave out `event` for a state rule), and shows what fires, each action's guardrail, and why every other rule of the trigger didn't. Both read the files directly when the daemon isn't running.
@@ -319,7 +341,7 @@ Rules say what the conductor does when something happens (design note, section 9
319
341
  - **State rules** are checked each minute against every wait, inbox item, worker, project and the machine. Each fires once while it holds and again after it stops holding; a restart remembers which hold. A `pause launches` with no end, from a state rule, lifts when the rule stops holding (`rule.released`), and only the pause that rule set.
320
342
 
321
343
  What each action does:
322
- - `remind jason` pushes the subject's inbox item again: the wait's gate, or the worker's item.
344
+ - `remind operator` pushes the subject's inbox item again: the wait's gate, or the worker's item.
323
345
  - `notify <channel>` pushes once. `notify profile` switches the profile.
324
346
  - `inbox <kind>` raises an item, once while it is open. `inbox runaway` is raised once per worker.
325
347
  - `pause` / `unpause` / `focus` set overrides that name the rule.
@@ -365,16 +387,16 @@ A worker's worktree branches from `origin/<base>` and installs its own pins. The
365
387
  1. It runs `git fetch origin <base>` in the main checkout and compares the pins (asset type, name and version) at `HEAD` with those at `origin/<base>`. When they match and nothing is left to install, nothing more happens.
366
388
  2. When they differ, it records the pins as pending (a `checkout.pending` event). With the main checkout on `<base>` and no local changes to `.atk-lock.json`, it then runs `git merge --ff-only origin/<base>`.
367
389
  3. Once `HEAD` has the pins, whether by this pull or yours, it runs `atk install` for the pending assets. A `checkout.updated` event lists each `name old → new`. An install that fails or leaves drift stays pending, and the next check tries it again, in this daemon or the next.
368
- 4. When `conductor-console` itself changed, a `jason` inbox item asks you once to restart the console (`/clear`, then `/conductor-console`). Other skills need no restart, because every worker starts fresh.
390
+ 4. When `conductor-console` itself changed, a `operator` inbox item asks you once to restart the console (`/clear`, then `/conductor-console`). Other skills need no restart, because every worker starts fresh.
369
391
 
370
- The checkout isn't touched in these cases: it is on another branch, its lockfile has local changes, or git refuses the fast-forward. Instead one `jason` item gives the commands (`git -C <root> pull --ff-only`, `atk install -P <root>`). The same item stays open while an install leaves drift, and it resolves itself once the pins are installed. A worktree create waits for a check that is running, and a check waits while one of the project's workers is launching, so a new worktree never copies a half-updated checkout. A failed fetch is only logged. `checkout.mode` is `update` (the default), `notify` (the item only, with no git or atk run) or `off`.
392
+ The checkout isn't touched in these cases: it is on another branch, its lockfile has local changes, or git refuses the fast-forward. Instead one `operator` item gives the commands (`git -C <root> pull --ff-only`, `atk install -P <root>`). The same item stays open while an install leaves drift, and it resolves itself once the pins are installed. A worktree create waits for a check that is running, and a check waits while one of the project's workers is launching, so a new worktree never copies a half-updated checkout. A failed fetch is only logged. `checkout.mode` is `update` (the default), `notify` (the item only, with no git or atk run) or `off`.
371
393
 
372
394
  ## Skill trains
373
395
 
374
396
  Each skill change ships as its own registry publish, and each publish is a GitHub PR that Jason merges. A train turns several of them into one merge (ATK-285). A profile turns it on with `"train": { "repo": "<owner>/<repo>", "minimum": 3, "maxHours": 24 }`.
375
397
 
376
398
  1. A worker publishes as before (`atk publish`, which opens registry PR #n). It then marks #n with a note headed `## <work skill> · Train`, or `## <work skill> · Train · urgent: <why>` to ask for a train now. It waits on #n's merge as before: parked, with its slot free.
377
- 2. Every 15 minutes, the first a minute after the start, the daemon reads the repo's open PRs and their comments. While the marked ones not yet on a train reach `minimum`, one is urgent, or the oldest has waited `maxHours`, one `jason` inbox item asks for `conduct train <project>`. While a train PR is open, a `merge` item asks you to merge it. Each resolves itself once it no longer holds.
399
+ 2. Every 15 minutes, the first a minute after the start, the daemon reads the repo's open PRs and their comments. While the marked ones not yet on a train reach `minimum`, one is urgent, or the oldest has waited `maxHours`, one `operator` inbox item asks for `conduct train <project>`. While a train PR is open, a `merge` item asks you to merge it. Each resolves itself once it no longer holds.
378
400
  3. `conduct train` takes each marked PR that targets the default branch, has passed its checks, and changes no file another member changes. It creates `train/<yyyymmdd-hhmm>` (UTC) from the default branch and merges each member's head into it with GitHub's merges API, so nothing is checked out. It then opens one PR, which lists the members as `- #n …` lines, and notes `<work skill> · Train opened · merge: <url>` on each member. A merge GitHub refuses deletes the branch, and nothing is opened. `--dry-run` only reads. It lists what waits, what a train would take, and why the rest stay out.
379
401
  4. Merge the train with a merge commit. Each member's head is then on the default branch, so GitHub shows the member merged and its worker wakes. A member pushed to after the train opened stays open, and rides the next train.
380
402
 
@@ -382,7 +404,7 @@ A train PR that is closed unmerged frees its members for the next train.
382
404
 
383
405
  ## Waits
384
406
 
385
- A worker waits by posting its note, running `conduct wait …` and ending the turn (worker protocol v1). The verbs take the repo, host and package from the worktree's `.claude/conductor.json`; the full form is accepted too, and is what the store keeps:
407
+ A worker waits by posting its note, running `conduct wait …` and ending the turn (worker protocol 2, [`protocol/PROTOCOL.md`](protocol/PROTOCOL.md)). A verb or full form that doesn't parse is refused with exit 1 and the form it expects. The verbs take the repo, host and package from the worktree's `.claude/conductor.json`; the full form is accepted too, and is what the store keeps:
386
408
 
387
409
  | Verb | Full form | Holds when |
388
410
  |---|---|---|
@@ -417,23 +439,36 @@ Accounts belong to the person running the daemon, not to the repo, so each proje
417
439
 
418
440
  ```jsonc
419
441
  "projects": {
420
- "cadence": { "ado": { "tenant": "<tenant id or domain>" }, "github": { "user": "<gh account>" } },
421
- "atk": { "linear": { "credential": "atk-conductor-linear" } }
442
+ "atk": { "ado": { "token": "conductor-atk-ado" }, "linear": { "credential": "conductor-atk-linear" } },
443
+ "cadence": { "ado": { "tenant": "<tenant id or domain>" }, "github": { "user": "<gh account>" } }
422
444
  }
423
445
  ```
424
446
 
425
- | Provider | With an entry | Without one |
426
- |---|---|---|
427
- | Azure DevOps (PRs, runs, Boards) | `az account get-access-token --resource 499b84ac-… --tenant <tenant>` | the same without `--tenant`: az's default account |
428
- | GitHub (reads, and writes such as `conduct train`'s) | `gh auth token --user <user>`, passed to `gh api` as `GH_TOKEN`, so gh's active account doesn't change | `gh auth token`: gh's active account |
429
- | Linear (waits, notes, labels, queue, briefs) | the password of the generic credential `<credential>` (Credential Manager) | `LINEAR_API_KEY`, else the credential `atk-conductor-linear` (`ATK_CONDUCT_LINEAR_CREDENTIAL` names another) |
447
+ **Least privilege first** (design note v2, section 8). Each provider takes the first credential it finds, in this order, so the operator can give one project exactly the permissions its work needs:
448
+
449
+ | Order | Source | Azure DevOps (PRs, runs, Boards) | GitHub (reads, and writes such as `conduct train`'s) | Linear (waits, notes, labels, queue, briefs) |
450
+ |---|---|---|---|---|
451
+ | 1 | **project token**: a generic credential (Credential Manager) the project names | `projects.<name>.ado.token`: a PAT, sent as Basic auth | `projects.<name>.github.token`: a token, as `GH_TOKEN` | `projects.<name>.linear.credential`: an API key |
452
+ | 2 | **machine token**: a conductor-only env var, else a generic credential | `CONDUCTOR_ADO_TOKEN`, else `conductor-ado` | `CONDUCTOR_GITHUB_TOKEN`, else `conductor-github` | `CONDUCTOR_LINEAR_KEY` or `LINEAR_API_KEY`, else `atk-conductor-linear` (`ATK_CONDUCT_LINEAR_CREDENTIAL` names another) |
453
+ | 3 | **CLI login**, last: it usually carries the widest permissions | `az account get-access-token --resource 499b84ac-…`, with `--tenant <tenant>` when `ado.tenant` names one | `gh auth token`, with `--user <user>` when `github.user` names one, so gh's active account doesn't change | none |
454
+
455
+ Only the conductor's own env names count: another tool's `AZURE_DEVOPS_PAT` or `GITHUB_TOKEN` never changes what the conductor signs in as. Save a token with `cmdkey /generic:<target> /user:pat /pass` (Linear's with `/user:linear`); a token scoped to one project wants:
456
+ - **Azure DevOps:** Code (read & write) and Build (read & execute), plus Work Items (read & write) for Boards;
457
+ - **GitHub:** a fine-grained token on the repo with Contents, Pull requests and Issues (read & write) and Actions (read);
458
+ - **Linear:** an API key limited to the project's team.
459
+
460
+ `conduct project check <name>` says which source each provider signed in with, and for each that isn't the project's own token, the line that would scope it (its `auth` check).
461
+
462
+ A project whose entry names its CLI account (`ado.tenant`, `github.user`) keeps that account ahead of the machine's token, which is another identity and may not reach the project's organization. One machine ADO PAT serves every other project, so a project in another organization than the PAT's gets a token of its own.
463
+
464
+ Found means there: a project token that is named but missing, or any token that is there but refused, is a lost login (below), never a quiet fall back to a wider credential. A refused PAT is read again once, so a replaced one is tried; the same one refused again is the lost login. The machine credential is optional: one that `cmdkey /list` says isn't saved (no PowerShell started then), or that can't be read, is passed over.
430
465
 
431
- Each token is kept per account: an az token 20 minutes, renewed when ADO answers 401 or 203; a gh token 20 minutes, asked again once on a 401; a Linear key until Linear answers 401, when it is read again once. Projects on the same account share it. The secrets are never logged, stored or put on a command line.
466
+ Each credential is kept per account: an az token 20 minutes, renewed when ADO answers 401 or 203; a token read again every 20 minutes, so a replaced or removed one is seen; a gh token asked again once on a 401; a Linear key until Linear answers 401, when it is read again once. Projects on the same account share it. The secrets are never logged, stored or put on a command line.
432
467
 
433
- **A lost login** is one of these: `az` gives no token; `gh auth token` fails, or GitHub refuses the token again after a fresh one; Linear has no key, or refuses it again after a fresh read. An ADO 401 that survives a fresh token isn't counted. It holds only the waits of the projects on that account (`wait.check-failed`), so other projects' waits on the same API carry on. Each account's loss is a spell:
468
+ **A lost login** is one of these: `az` gives no token; `gh auth token` fails, or GitHub refuses the token again after a fresh one; Linear has no key, or refuses it again after a fresh read; a project token is missing; ADO refuses a PAT again (its retry reads the same token). An ADO 401 that survives a fresh az token isn't counted. It holds only the waits of the projects on that account (`wait.check-failed`), so other projects' waits on the same API carry on. Each account's loss is a spell:
434
469
 
435
470
  1. The first failure logs `provider.login-failed` (the provider, the account, the command and the error, on the project whose call found it). The event raises a `login` inbox item, and rules can act on it.
436
- 2. While the spell lasts, the account's calls fail at once, asking nothing. Every `watchers.login.intervalSeconds` (60) the daemon probes each lost account again: a fresh token for ADO, the token and `gh api user` for GitHub, and the key and `{ viewer { id } }` for Linear. That is the spell's back-off.
471
+ 2. While the spell lasts, the account's calls fail at once, asking nothing. Every `watchers.login.intervalSeconds` (60) the daemon probes each lost account again: a fresh az token for ADO (a PAT is asked `_apis/connectionData` on the project's organization, since az can't vouch for it), the token and `gh api user` for GitHub, and the key and `{ viewer { id } }` for Linear. That is the spell's back-off.
437
472
  3. The first probe the provider answers (an answer other than a refusal; a timeout, a network error or a 5xx says nothing) logs `provider.login-restored`, which resolves the item, and calls go through again.
438
473
 
439
474
  `conduct wait login <provider>` waits on the same probe for the worktree's project (`login:<provider>:<project>`), within the `login` watcher's budget. A worker that can't go on without a login posts its note, waits on it and ends its turn; once Jason signs in, the daemon wakes it. **Exit code 5** ("login needed") is what the worker verbs (`state`, `pr`, `land`, `reply`) exit with when they hit a lost login, here or in the daemon.
@@ -626,10 +661,11 @@ conduct project add <path to the project's main checkout>
626
661
  Then `conduct project check <name>` says whether the project can be conducted, before its first launch finds out. Each check prints one line, `ok`, `warn`, `fail` or `skip`, with a fix under a warn or a fail, and the command exits 1 when any check fails (`--json` prints the same as data):
627
662
 
628
663
  - **profile:** the file reads with this build's schema and keeps the registered name; a `warn` lists the fields that differ from the daemon's stored copy, which it takes on the next save or start. The later checks use the file when it reads, else the stored copy; with neither they are `skip`.
664
+ - **auth:** which credential each provider the project uses signs in with (project token, machine token or CLI login; see Accounts), probed as the daemon would. For each that isn't the project's own token, a detail line gives the scopes, the `cmdkey` line and the config key that would scope it; that is advice, and the check passes. A token that is there but refused, or no credential at all, fails.
629
665
  - **tracker:** the tracker's login answers, and the queue reads (Linear: the team, and whether any of its projects has a priority; Boards: the saved query; GitHub Issues: the labelled issues).
630
666
  - **code-host** and **ci:** the code host's login answers and the base branch exists; each release's `pipeline` names exactly one of the repo's pipelines.
631
667
  - **worktree:** the launch's first steps on a throwaway `conduct-check-<time>`: the worktree hooks create it, its skills match `.atk-lock.json`, and it is removed, whatever happened. The one check that writes; `--no-worktree` skips it, since the create hook runs `pnpm install`.
632
- - **skills:** in the main checkout, `skills.work` is installed and declares `worker protocol v1`, `skills.verify` is installed, and `atk audit` finds both unchanged; a missing alias is a `warn`.
668
+ - **skills:** in the main checkout, `skills.work` is installed and declares `protocol: 2` in its SKILL.md frontmatter (worker protocol 2), `skills.verify` is installed, and `atk audit` finds both unchanged; a missing alias is a `warn`.
633
669
  - **ports:** the broker has a free slot for a new worktree, up to `ports.maxSlot`, saving nothing.
634
670
  - **health:** each `health` URL answers; a `warn` at most, since an environment can be down.
635
671
 
@@ -19,11 +19,19 @@ export const NOT_BRIEFED = 3;
19
19
  const BRIEF_TIMEOUT_MS = 90_000;
20
20
  const link = z.object({ id: z.string(), status: z.string().nullable(), title: z.string().nullable() });
21
21
  export const briefSchema = z.object({
22
- approval: z.object({ at: z.string(), kind: z.enum(['approve', 'revise']), text: z.string() }).nullable(),
22
+ approval: z
23
+ .object({ at: z.string(), by: z.string().nullish(), kind: z.enum(['approve', 'revise']), text: z.string() })
24
+ .nullable(),
23
25
  area: z.string().nullable(),
24
26
  blockedBy: z.array(link),
25
27
  blocks: z.array(link),
26
- comments: z.array(z.object({ at: z.string(), body: z.string(), byApprover: z.boolean() })),
28
+ comments: z.array(z.object({
29
+ at: z.string(),
30
+ body: z.string(),
31
+ by: z.string().nullable(),
32
+ byApprover: z.boolean(),
33
+ note: z.object({ kind: z.string().nullable(), line: z.string().nullable(), phase: z.string() }).nullable(),
34
+ })),
27
35
  description: z.string(),
28
36
  item: z.string(),
29
37
  labels: z.array(z.string()),
@@ -55,7 +63,7 @@ export const briefSchema = z.object({
55
63
  project: z.string(),
56
64
  readAt: z.string(),
57
65
  related: z.array(link),
58
- revise: z.object({ at: z.string(), text: z.string() }).nullable(),
66
+ revise: z.object({ at: z.string(), by: z.string().nullish(), text: z.string() }).nullable(),
59
67
  status: z.object({ name: z.string(), type: z.string().nullable() }),
60
68
  title: z.string(),
61
69
  trackerProject: z.string().nullable(),
@@ -66,6 +74,15 @@ const refused = z.object({ details: z.array(z.string()).optional(), error: z.str
66
74
  // the daemon, else read here from this checkout's project. Returns the exit code: 0, NOT_BRIEFED,
67
75
  // or 1 (thrown) when it was refused.
68
76
  export async function briefCommand(paths, item, options, io) {
77
+ const read = await readBrief(paths, item, options, io);
78
+ if ('exit' in read)
79
+ return read.exit;
80
+ print(read.brief, options, io);
81
+ return 0;
82
+ }
83
+ // The item's brief, from the daemon, else read here from this checkout's project; or the exit code
84
+ // when nobody gave it (NOT_BRIEFED, after saying why on stderr). Throws when it was refused.
85
+ export async function readBrief(paths, item, options, io) {
69
86
  let answer;
70
87
  try {
71
88
  answer = await callApi(paths, {
@@ -79,21 +96,20 @@ export async function briefCommand(paths, item, options, io) {
79
96
  }
80
97
  catch (error) {
81
98
  if (error instanceof NotRunningError)
82
- return briefHere(paths, item, options, io, 'the conductor is not running');
99
+ return briefHere(paths, item, io, 'the conductor is not running');
83
100
  throw error;
84
101
  }
85
102
  if (answer.status === 200) {
86
103
  const given = z.object({ brief: briefSchema }).safeParse(answer.body);
87
104
  // A conductor of another version gives a brief of another shape: read it here instead.
88
105
  if (!given.success)
89
- return briefHere(paths, item, options, io, "this conductor's brief is of another version");
90
- print(given.data.brief, options, io);
91
- return 0;
106
+ return briefHere(paths, item, io, "this conductor's brief is of another version");
107
+ return { brief: given.data.brief };
92
108
  }
93
109
  const error = refused.safeParse(answer.body).data;
94
110
  // A daemon from before the brief API answers its route's `not_found`; a missing item says which.
95
111
  if (answer.status === 404 && error?.error === 'not_found')
96
- return briefHere(paths, item, options, io, 'this conductor has no brief API');
112
+ return briefHere(paths, item, io, 'this conductor has no brief API');
97
113
  // The conductor couldn't read the tracker (no key, the tracker down): the worker still may.
98
114
  if (answer.status === 502)
99
115
  return notBriefed(io, `${error?.error ?? 'the conductor could not read the tracker'}${error?.details?.length ? ` (${error.details.join('; ')})` : ''}`);
@@ -101,7 +117,7 @@ export async function briefCommand(paths, item, options, io) {
101
117
  }
102
118
  // The brief read here, with no conductor: the tracker and the PR as this checkout's project's
103
119
  // account. Without a daemon there is no worker, so the PR is the newest PR opened note's.
104
- async function briefHere(paths, item, options, io, why) {
120
+ async function briefHere(paths, item, io, why) {
105
121
  if (!ITEM.test(item))
106
122
  throw new ConductorError(`"${item}" is not a tracker item, such as ATK-311`);
107
123
  const key = item.toUpperCase();
@@ -132,14 +148,13 @@ async function briefHere(paths, item, options, io, why) {
132
148
  }
133
149
  const prefixes = notePrefixes(profile);
134
150
  const ref = notedPr(read.comments, prefixes);
135
- const brief = briefOf(key, profile.name, read, prefixes, new Date(), ref === null ? null : await prs(profile, ref));
151
+ const brief = briefOf(key, profile.name, read, prefixes, new Date(), ref === null ? null : await prs(profile, ref), profile.approvers);
136
152
  io.err(`read here (${why})`);
137
- print(brief, options, io);
138
- return 0;
153
+ return { brief };
139
154
  }
140
155
  function notBriefed(io, why) {
141
156
  io.err(`no brief: ${why}; read the item from the tracker yourself`);
142
- return NOT_BRIEFED;
157
+ return { exit: NOT_BRIEFED };
143
158
  }
144
159
  function print(brief, options, io) {
145
160
  if (options.json) {
@@ -1 +1 @@
1
- {"version":3,"file":"brief-cli.js","sourceRoot":"","sources":["../src/brief-cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"brief-cli.js","sourceRoot":"","sources":["../src/brief-cli.ts"],"names":[],"mappings":"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"}
package/build/brief.js CHANGED
@@ -20,6 +20,10 @@ export class BriefError extends ConductorError {
20
20
  this.reason = reason;
21
21
  }
22
22
  }
23
+ // Who wrote a comment, as the tracker names them: their name, else their email or login.
24
+ export function authorOf(comment) {
25
+ return comment.authorName ?? comment.authorHandle ?? null;
26
+ }
23
27
  // The brief as a reader sees it, for `conduct brief` without --json; its comments with `comments`.
24
28
  export function briefLines(brief, { comments = false } = {}) {
25
29
  const links = (label, list) => list.length === 0
@@ -98,15 +102,17 @@ export function briefOf(item, project, read, prefixes, at, pr = null, approvers
98
102
  const revised = answers
99
103
  .slice(answers.findLastIndex((c) => /^approved?\b/i.test(c.body.trim())) + 1)
100
104
  .find((c) => /^revise\s*:/i.test(c.body.trim()));
101
- const approval = found ? { ...found, text: said ? said.body.trim() : '' } : null;
102
- const revise = revised ? { at: revised.createdAt, text: revised.body.trim() } : null;
105
+ const approval = found ? { ...found, by: said ? authorOf(said) : null, text: said ? said.body.trim() : '' } : null;
106
+ const revise = revised ? { at: revised.createdAt, by: authorOf(revised), text: revised.body.trim() } : null;
103
107
  return {
104
108
  ...fields,
105
109
  approval,
106
110
  comments: sorted.map((c) => ({
107
111
  at: c.createdAt,
108
112
  body: c.body,
113
+ by: authorOf(c),
109
114
  byApprover: isApprover(c, who),
115
+ note: noteHeading(c.body, prefixes),
110
116
  })),
111
117
  item,
112
118
  notes,