@detergent-software/conductor 1.3.1-dev.1 → 1.4.0-dev.10

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 (110) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +92 -19
  3. package/build/brief-cli.js +105 -23
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/brief.js +107 -9
  6. package/build/brief.js.map +1 -1
  7. package/build/broker.js +40 -14
  8. package/build/broker.js.map +1 -1
  9. package/build/cli.js +56 -18
  10. package/build/cli.js.map +1 -1
  11. package/build/code-host-cli.js +101 -2
  12. package/build/code-host-cli.js.map +1 -1
  13. package/build/code-host.js +18 -5
  14. package/build/code-host.js.map +1 -1
  15. package/build/conditions.js +104 -30
  16. package/build/conditions.js.map +1 -1
  17. package/build/daemon.js +64 -16
  18. package/build/daemon.js.map +1 -1
  19. package/build/follow-ups.js +72 -0
  20. package/build/follow-ups.js.map +1 -0
  21. package/build/help.js +721 -192
  22. package/build/help.js.map +1 -1
  23. package/build/in-process.js +30 -0
  24. package/build/in-process.js.map +1 -0
  25. package/build/inbox.js +113 -11
  26. package/build/inbox.js.map +1 -1
  27. package/build/landing.js +2 -11
  28. package/build/landing.js.map +1 -1
  29. package/build/lib/providers/ado/code-host.js +8 -5
  30. package/build/lib/providers/ado/code-host.js.map +1 -1
  31. package/build/lib/providers/ado/pipelines.js +16 -10
  32. package/build/lib/providers/ado/pipelines.js.map +1 -1
  33. package/build/lib/providers/github/actions.js +11 -0
  34. package/build/lib/providers/github/actions.js.map +1 -1
  35. package/build/lib/providers/github/code-host.js +17 -0
  36. package/build/lib/providers/github/code-host.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/lib/providers/npm/stage.js +33 -2
  40. package/build/lib/providers/npm/stage.js.map +1 -1
  41. package/build/lib/providers/verdicts/pr.js +14 -12
  42. package/build/lib/providers/verdicts/pr.js.map +1 -1
  43. package/build/lib/providers/verdicts/runs.js +4 -4
  44. package/build/lib/providers/verdicts/runs.js.map +1 -1
  45. package/build/manager.js +8 -3
  46. package/build/manager.js.map +1 -1
  47. package/build/note.js +95 -14
  48. package/build/note.js.map +1 -1
  49. package/build/notes.js +104 -16
  50. package/build/notes.js.map +1 -1
  51. package/build/notify.js +2 -1
  52. package/build/notify.js.map +1 -1
  53. package/build/package-info.js +12 -0
  54. package/build/package-info.js.map +1 -0
  55. package/build/profile.js +88 -14
  56. package/build/profile.js.map +1 -1
  57. package/build/project-check-cli.js +85 -0
  58. package/build/project-check-cli.js.map +1 -0
  59. package/build/project-check.js +362 -0
  60. package/build/project-check.js.map +1 -0
  61. package/build/resolve.js +11 -3
  62. package/build/resolve.js.map +1 -1
  63. package/build/rule-actions.js +9 -0
  64. package/build/rule-actions.js.map +1 -1
  65. package/build/rules-service.js +28 -19
  66. package/build/rules-service.js.map +1 -1
  67. package/build/runs.js +208 -0
  68. package/build/runs.js.map +1 -0
  69. package/build/scheduler.js +43 -1
  70. package/build/scheduler.js.map +1 -1
  71. package/build/server.js +76 -7
  72. package/build/server.js.map +1 -1
  73. package/build/store.js +27 -4
  74. package/build/store.js.map +1 -1
  75. package/build/tracker-writes.js +74 -0
  76. package/build/tracker-writes.js.map +1 -0
  77. package/build/trackers.js +20 -0
  78. package/build/trackers.js.map +1 -1
  79. package/build/wait.js +1 -0
  80. package/build/wait.js.map +1 -1
  81. package/build/watchers/ado.js +41 -12
  82. package/build/watchers/ado.js.map +1 -1
  83. package/build/watchers/boards-writes.js +182 -34
  84. package/build/watchers/boards-writes.js.map +1 -1
  85. package/build/watchers/boards.js +7 -2
  86. package/build/watchers/boards.js.map +1 -1
  87. package/build/watchers/github.js +45 -10
  88. package/build/watchers/github.js.map +1 -1
  89. package/build/watchers/index.js +40 -5
  90. package/build/watchers/index.js.map +1 -1
  91. package/build/watchers/issues-brief.js +48 -0
  92. package/build/watchers/issues-brief.js.map +1 -0
  93. package/build/watchers/issues-queue.js +85 -0
  94. package/build/watchers/issues-queue.js.map +1 -0
  95. package/build/watchers/issues-writes.js +233 -0
  96. package/build/watchers/issues-writes.js.map +1 -0
  97. package/build/watchers/issues.js +264 -0
  98. package/build/watchers/issues.js.map +1 -0
  99. package/build/watchers/linear-writes.js +198 -0
  100. package/build/watchers/linear-writes.js.map +1 -0
  101. package/build/watchers/merge.js +53 -0
  102. package/build/watchers/merge.js.map +1 -0
  103. package/build/watchers/npm.js +7 -16
  104. package/build/watchers/npm.js.map +1 -1
  105. package/build/watchers/source.js.map +1 -1
  106. package/build/worktrees.js +1 -0
  107. package/build/worktrees.js.map +1 -1
  108. package/mod/hooks/model.ts +11 -0
  109. package/mod/hooks/register.tsx +25 -3
  110. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,107 @@
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
+ ## [1.4.0-dev.10](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.9&targetVersion=GTconductor-v1.4.0-dev.10) (2026-10-15)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **conductor:** back off a partial retire and raise it once (ATK-496) ([7fe082a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7fe082a0b211b95eddc5e7fd93ced4bd3ff0a925))
10
+
11
+ ## [1.4.0-dev.9](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.8&targetVersion=GTconductor-v1.4.0-dev.9) (2026-10-15)
12
+
13
+ ### Features
14
+
15
+ * **conductor:** `conduct project check <name>` (ATK-490) ([393868c](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/393868c9d346f661c1fddb55788551cf98d66faf))
16
+ * **conductor:** read a branch's tip and a repo's pipelines (ATK-490) ([40af3c1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/40af3c1362b16b07ea3c135a69880adcbdb36626))
17
+
18
+ ### Bug Fixes
19
+
20
+ * **conductor:** keep a failed atk audit on one check line (ATK-490) ([ae78d30](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/ae78d3012bb68d178f85772f6303f5c1b97d4e32))
21
+ * **conductor:** project check review fixes (ATK-490) ([7518d91](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7518d9155066c4e7f7b301b4bee703b3d3a4a0e8))
22
+
23
+ ## [1.4.0-dev.8](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.7&targetVersion=GTconductor-v1.4.0-dev.8) (2026-10-15)
24
+
25
+ ### Bug Fixes
26
+
27
+ * **conductor:** open an inbox link once from the terminal pane (ATK-494) ([1634517](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/16345173815330b2cea5361e964ebdf4f2921c15))
28
+ * **conductor:** open only web links, on Windows only (ATK-494) ([8d2f608](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8d2f6084cc99900b2c012cddbb5e287b488f1213))
29
+
30
+ ## [1.4.0-dev.7](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.6&targetVersion=GTconductor-v1.4.0-dev.7) (2026-10-15)
31
+
32
+ ### Features
33
+
34
+ * **conductor:** take the whole profile from disk, not only its rules ([b22705a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b22705ad2adb2bb6e886e5f99e011ccc0767b5c0))
35
+
36
+ ### Bug Fixes
37
+
38
+ * **conductor:** keep a project's name, and say only what is in force ([51efeb0](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/51efeb0977e005d0daa84c67f6590c7817339b87))
39
+ * **conductor:** refuse an unreadable stored profile's bad file once ([4e100f1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/4e100f14fb2a9ed36b701cdb8f2a36d91bacacb7))
40
+
41
+ ## [1.4.0-dev.6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.5&targetVersion=GTconductor-v1.4.0-dev.6) (2026-10-15)
42
+
43
+ ### Bug Fixes
44
+
45
+ * **conductor:** guard the red-checks inbox paths found in review (ATK-468) ([c567a4d](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c567a4d63192c1e7b6c630e4292846b4a78166e8))
46
+ * **conductor:** hold merges on red checks by item, not by worker (ATK-468) ([78c16ea](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/78c16ea33c78e6b8dd1d94b5b2f5d58a3c950b5a))
47
+ * **conductor:** raise red PR checks as a failure and hold the merge (ATK-468) ([5897adb](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/5897adb9033b89d575fcd26f391c012637dd602b))
48
+
49
+ ## [1.4.0-dev.5](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.4&targetVersion=GTconductor-v1.4.0-dev.5) (2026-10-15)
50
+
51
+ ### Features
52
+
53
+ * **conductor:** easier-to-scan conduct help (ATK-478) ([8c34660](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8c34660ba7ecbac439312e317eb359767efedb21))
54
+
55
+ ### Refactoring
56
+
57
+ * **conductor:** help examples a shell can run, review fixes (ATK-478) ([f1be136](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/f1be1361a1874a26a91dcfafd42626e25c307943)), closes [#12](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/12) [#-word](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/-word)
58
+
59
+ ## [1.4.0-dev.4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.3&targetVersion=GTconductor-v1.4.0-dev.4) (2026-10-15)
60
+
61
+ ### Features
62
+
63
+ * **conductor:** a GitHub Issues tracker kind (ATK-458) ([6028a13](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6028a136cc39e1ed51072571629e68dead466d35))
64
+
65
+ ### Bug Fixes
66
+
67
+ * **conductor:** review fixes for the github-issues tracker (ATK-458) ([79abe60](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/79abe602f80138714098a61821d16dfc015787c0))
68
+
69
+ ## [1.4.0-dev.3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.2&targetVersion=GTconductor-v1.4.0-dev.3) (2026-10-15)
70
+
71
+ ### Features
72
+
73
+ * **conductor:** a brief with the phase, the PR and the comments (ATK-456) ([cfd0f11](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/cfd0f11e8881f43eb0d4d4d4f93800738f81d57f))
74
+ * **conductor:** conduct file, in-process notes and the PR reference (ATK-456) ([0b3311b](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/0b3311b7ec877cc7846d1c5607c35c653c43b58d))
75
+ * **conductor:** phase notes move the tracker state (ATK-456) ([6c93b91](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6c93b91fc7f0487c41ddfe357248a3bfdc97a463))
76
+
77
+ ### Bug Fixes
78
+
79
+ * **conductor:** guard the brief found in review (ATK-456) ([5d7debe](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/5d7debe7cd670bc20798f7d55438bfc24d1095be))
80
+ * **conductor:** guard the note and follow-up paths found in review (ATK-456) ([6d881da](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6d881daca5d5c5103943fba6a1560f7ae4db36de)), closes [AB#12](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/12)
81
+
82
+ ### Refactoring
83
+
84
+ * **conductor:** the brief names its PR with the notes' helper (ATK-456) ([7b3f009](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7b3f009715cce46a4ac02697bdca3680ab9f78f5))
85
+
86
+ ## [1.4.0-dev.2](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.1&targetVersion=GTconductor-v1.4.0-dev.2) (2026-10-15)
87
+
88
+ ### Features
89
+
90
+ * **conductor:** tracker writes for Linear and ADO Boards (ATK-456) ([b86911e](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b86911ea478c8c4da90b6b5f8cf8586094b1fc4a))
91
+
92
+ ### Bug Fixes
93
+
94
+ * **conductor:** guard the tracker writes found in review (ATK-456) ([50c9987](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/50c998778997f5eb2b914339ed3c58f726fde83c))
95
+
96
+ ## [1.4.0-dev.1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.3.1-dev.1&targetVersion=GTconductor-v1.4.0-dev.1) (2026-10-15)
97
+
98
+ ### Features
99
+
100
+ * **conductor:** conduct runs and conduct wait merge (ATK-455) ([e46a331](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/e46a331491ec40d01da860306f910e62b59bdf67))
101
+
102
+ ### Bug Fixes
103
+
104
+ * **conductor:** review fixes for conduct runs and merge waits (ATK-455) ([b1ff160](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b1ff1602962f3bc902f33af57a23b06df081c5df))
105
+
5
106
  ## [1.3.1-dev.1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.3.0&targetVersion=GTconductor-v1.3.1-dev.1) (2026-10-14)
6
107
 
7
108
  ### Bug Fixes
package/README.md CHANGED
@@ -12,6 +12,8 @@ conduct supervise [--fail-fast] run the daemon and restart it when it dies (se
12
12
  conduct status [--json] the daemon's pid, port, projects, workers and held locks
13
13
  conduct project add <path> register the project whose checkout holds .claude/conductor.json
14
14
  conduct project list [--json] the registered projects
15
+ conduct project check <name> [--no-worktree] [--json]
16
+ whether the project can be conducted, before a launch finds out
15
17
  conduct launch <item> start a worker: worktree, background session, tab
16
18
  [--project <name>] [--probe] [--prompt <text>] [--conductor <name>]
17
19
  conduct retire <worker> [--force] stop a worker, remove its worktree and session
@@ -64,6 +66,8 @@ conduct land <pr> --reason <why> [--mode auto|review] [--rearm] [--tier S|M|L] [
64
66
  arm the worker's PR to land and give its first verdict (see Worker verbs)
65
67
  conduct reply <pr> [<thread>] --body-file <file> [--repo <repo>]
66
68
  answer on the worker's PR, headed as a note
69
+ conduct runs <sha> [--for <pipeline>[,<pipeline>…]] [--repo <repo>]
70
+ a merge's runs, their verdict, failed logs and npm stages (see Worker verbs)
67
71
  conduct train [<project>] [--dry-run]
68
72
  merge the waiting skill publishes into one registry PR (see Skill trains)
69
73
  conduct ports [<path>] [--prefer <slot>] [--json]
@@ -102,18 +106,28 @@ Worker statuses: `launching`, `running`, `parked`, `stopped-unexpectedly`, `fail
102
106
 
103
107
  ## Notes
104
108
 
105
- A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one line>]`, such as `## work-issue · PR opened`) with `conduct note <item> <file>`. The daemon posts the file as a comment on the item in the tracker, then records the phase at once: on the worker (`phase` in `GET /fleet` and `conduct status`), on the item, and as a `note.posted` event. A PR opened note also records the worker's PR (`pr`, such as `ado:Emergent.AgenticToolkit#1850` or `github:<owner>/<repo>#458`) from its first PR link, replacing an earlier one; until then, the worker's first `pr:` wait records it. `conduct status` and the console's fleet pane show it. The worker is `--worker <worker>`, else the one whose worktree is the current checkout, else the project's live worker on that item. The project is the worker's, else the one whose tracker key (the Linear team, or the Boards `key`) is the item's key prefix.
109
+ A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one line>]`, such as `## work-issue · PR opened`) with `conduct note <item> <file>`. The daemon posts the file as a comment on the item in the tracker, then records the phase at once: on the worker (`phase` in `GET /fleet` and `conduct status`), on the item, and as a `note.posted` event. A PR opened note also records the worker's PR (`pr`, such as `ado:Emergent.AgenticToolkit#1850` or `github:<owner>/<repo>#458`) from its first PR link, replacing an earlier one; until then, the worker's first `pr:` wait records it. `conduct status` and the console's fleet pane show it. The worker is `--worker <worker>`, else the one whose worktree is the current checkout, else the project's live worker on that item. The project is the worker's, else the one whose tracker key (the Linear team, or the Boards or GitHub Issues `key`) is the item's key prefix.
110
+
111
+ **State follows the notes** (providers design note, section 5). A `Started`, `PR opened` or `Landed` note (not a Handoff) also moves the item, to the state its phase maps to in the profile's `tracker.transitions`:
112
+
113
+ | Phase | Linear default | ADO Boards default (Scrum) | GitHub Issues default |
114
+ |---|---|---|---|
115
+ | `started` | In Progress, assigned to the key's owner | Committed, assigned to the `az` identity | the label `in-progress`, assigned to the gh account |
116
+ | `pr-opened` | In Review, the PR attached | no change, the PR linked (an `ArtifactLink` for an ADO PR, a `Hyperlink` for a GitHub one) | the label `in-review` in place of `in-progress`; a GitHub PR is linked by the `Refs <owner>/<repo>#<n>` its body carries, any other by a comment with its URL |
117
+ | `landed` | Done | Done | closed as completed, the phase label taken off |
118
+
119
+ 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.
106
120
 
107
121
  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.
108
122
 
109
123
  | Exit | Means | The worker then |
110
124
  |---|---|---|
111
125
  | 0 | posted | goes on |
112
- | 3 | nobody posted it: no daemon, one without notes, or the tracker refused it (such as a read-only key) | posts the note to the tracker itself, as before protocol v1 |
126
+ | 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 |
113
127
  | 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 |
114
128
  | 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 |
115
129
 
116
- 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.
130
+ 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.
117
131
 
118
132
  ## Briefs
119
133
 
@@ -121,20 +135,23 @@ On Linear the daemon uses the key the watchers read (below), so that key needs *
121
135
  - the title, URL, status (name and type), the tracker's project, Area and labels;
122
136
  - the blockers, the items it blocks and the related items, each with its status and title;
123
137
  - its phase notes (time, phase, kind and line), the newest Plan, and the `approve` / `revise:` after it with its words;
138
+ - the work skill's `phase`: `orient`, `plan`, `awaiting-approval`, `implement`, `wait`, `land`, `done`, `abandoned` or `canceled`, from the status, the notes and the PR;
139
+ - the `pr`: its worker's, else the newest `PR opened` note's link, read on the project's code host, with its state and verdict (or why it couldn't be read);
124
140
  - the description.
125
141
 
126
- `--json` prints it as one object.
142
+ `--json` prints it as one object. `--comments` adds every comment's words, time, and whether the key's owner wrote it. With no daemon it is read here, from the item's project checkout as its account.
127
143
 
128
144
  The queue's snapshot has no descriptions, relations or comments, so the daemon reads the item itself:
129
145
  - **Linear:** one GraphQL request with the watchers' key, and more only for comments past the first 100.
130
146
  - **ADO Boards:** the work item with its links, its linked items' titles and states, its discussion comments and the `az login` identity. Predecessor is a blocker, Successor an item it blocks.
147
+ - **GitHub Issues:** the issue, its comments, the issues it is blocked by and blocks (GitHub's issue dependencies; one in another repo is `<owner>/<repo>#<n>`) and the gh account's login. GitHub has no related relation, so the brief lists none.
131
148
 
132
149
  A brief is kept for 30 seconds. Anything the daemon sees happen to the item drops it: a note it posted, an `approve` or `revise` it posted, a wait resolved. `--fresh` reads the tracker again, as a worker checking for an approval after a wake should.
133
150
 
134
151
  | Exit | Means | The worker then |
135
152
  |---|---|---|
136
153
  | 0 | the brief | uses it |
137
- | 3 | no daemon, one without the brief API, or one that couldn't read the tracker (no key, the tracker down) | reads the item from the tracker itself (the Linear MCP, or the work skill's helper) |
154
+ | 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) |
138
155
  | 1 | refused: no project tracks the item, or the tracker has no such item | reads the message |
139
156
 
140
157
  ## Worker verbs
@@ -145,7 +162,15 @@ Protocol 2's verbs on git and the code host (providers design note, section 4),
145
162
  - `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.
146
163
  - `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.
147
164
  - `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.
165
+ - `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.
166
+ - `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.
148
167
  - `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.
168
+ - `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.
169
+ - **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.
170
+ - **The verdict.** It is `runsVerdict`: `succeeded`, `failed`, `cancelled`, `waiting-for-approval`, `missing`, `none` or `waiting`. On Azure Pipelines `none` comes at once when nothing is triggered. On GitHub it comes after 5 minutes with no run, and on Azure Pipelines a triggered pipeline gets 15 minutes to queue before it is `missing`.
171
+ - **`--for`.** It settles on the named pipelines alone, by YAML file name, path or name, and lists the rest under `others`. Azure Pipelines refuses a name no pipeline answers to.
172
+ - **Logs and stages.** Each failed run comes with its failed tasks and log tail (`logs`). For the project's repo, each release (`releases`, else `work.releases`) whose run succeeded gives what it staged (`stages.<key>`): package, version, `stageId`, the `npm stage approve` line, and `published`.
173
+ - `conduct wait merge <sha> [--for …] [--repo …]` parks the worker until that verdict isn't `waiting` (see Waits), on either CI, so no worker polls with `wait time`. It absorbs ATK-430, ATK-246 and ATK-182.
149
174
 
150
175
  ## Writes
151
176
 
@@ -153,7 +178,9 @@ Every write the conductor makes outside its own store, with what triggers it and
153
178
 
154
179
  | Write | Trigger | Identity |
155
180
  |---|---|---|
156
- | A phase note on the item | `conduct note` (a worker) | the Linear key's owner; on Boards, the project's `az` identity |
181
+ | A phase note on the item | `conduct note` (a worker) | the Linear key's owner; on Boards, the project's `az` identity; on GitHub Issues, its gh account |
182
+ | The item's state, its assignee on `started`, and the PR's link (see Notes) | a `Started`, `PR opened` or `Landed` note | the Linear key's owner, the `az` identity, or the gh account |
183
+ | A follow-up item and its relations | `conduct file` → `POST /workers/:id/follow-ups` | the Linear key's owner, the `az` identity, or the gh account |
157
184
  | `approve` / `revise:` on the item | `conduct approve` / `revise`, the console | the Linear key's owner, or the `az` identity |
158
185
  | An auto-approval comment | a rule's `auto-approve now` / `after` | the Linear key's owner, or the `az` identity |
159
186
  | A label added or removed (`human`, `triage`, `shipped-open`) | a rule's `label add` / `remove` | the Linear key's owner, or the `az` identity (a tag) |
@@ -164,7 +191,7 @@ Every write the conductor makes outside its own store, with what triggers it and
164
191
  | A reply on the worker's PR | `conduct reply` → `POST /workers/:id/replies` | the project's `az` identity, or its gh account |
165
192
  | A train: its branch, the members' merges into it, its PR, a note on each member | `conduct train` (you) | the project's gh account |
166
193
 
167
- Accounts are per project (see Accounts). Each code-host write is an event: `code-host.pr-created`, `code-host.landing-armed`, `code-host.kept-armed`, `code-host.replied`.
194
+ Accounts are per project (see Accounts). Each code-host write is an event: `code-host.pr-created`, `code-host.landing-armed`, `code-host.kept-armed`, `code-host.replied`. So is each tracker write a worker causes: `tracker.transitioned` (or `tracker.transition-failed`) and `tracker.filed`. Rules still never change an item's state: only its phase notes do.
168
195
 
169
196
  ## Inbox
170
197
 
@@ -181,12 +208,13 @@ An item resolves itself when its condition is seen, and records what resolved it
181
208
  | Kind | Raised by | Resolved by |
182
209
  |---|---|---|
183
210
  | `plan` | a `Plan` note (`conduct note`), or an `approval` wait; not a Plan an `auto-approve now` rule approves (unless its comment fails) | the approval wait resolving (an `approve` or `revise:` comment), a `Plan approved` note, a newer Plan, or `conduct approve` / `revise` |
184
- | `merge` | a `Handoff · merge: <PR>` note; a `pr … merged` wait attaches to it | its PR wait resolving as merged, abandoned or closed, or a `Landed` note. A PR wait without the note raises nothing, because auto landing needs nobody |
211
+ | `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 |
185
212
  | `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 |
186
213
  | `jason`, `human`, `scope`, `blocked`, `abandoned`, `failed` | a Handoff note of that kind | any later note on the item (such as `Resumed`), or `conduct done` |
187
214
  | `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) |
188
215
  | `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 |
189
216
  | `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 |
217
+ | `failed` (red checks) | `pr.checks-failed`: a `pr … checks` wait resolved as failed, with each failed run's name, id and link, the PR's link and `conduct attach`; one per PR | a checks wait on the PR resolving passed, the PR merged, abandoned or closed, a `Landed` or `Done` note, a newer `Handoff · failed` note, its retirement, or `conduct done`. Not the worker waking for it, or another note: the checks are still red |
190
218
  | `conflict` | `pr.conflict-stuck`: a watched PR has conflicted with its base for `watchers.conflictMinutes` (30) since its worker was woken for it, and the wake never reached the worker or a pass still sees the conflict; with `conduct attach` and the PR link | the PR merging cleanly again, merged or abandoned (`pr.conflict-cleared`), its retirement, or a daemon restart (the spell starts over) |
191
219
  | `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) |
192
220
  | `runaway` | a worker passing `inbox.runawayTokens` (1.5M) input, output and cache-write tokens in all, once per worker | `conduct done`, or its retirement |
@@ -262,10 +290,10 @@ Rules say what the conductor does when something happens (design note, section 9
262
290
  ```
263
291
 
264
292
  - **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`, `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.
265
- - **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`, `worker.idle`, `worker.exited`, `worker.permission`, `worker.waiting`, `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`, `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`.
293
+ - **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`, `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`, `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`.
266
294
  - **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.
267
295
  - **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`, `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).
268
- - **Reload:** saving `rules.json` or a project's profile reloads its rules. A profile save takes only the `rules`; the rest of a profile changes with `conduct project add`. A file the conductor refuses leaves its last good rules 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.
296
+ - **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. 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.
269
297
  - **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.
270
298
 
271
299
  - **Why:** every action the conductor takes is an event that names its rule, directly (`rule`) or through the event that caused it (`data.cause`). `conduct why <event id>` follows that chain back and shows the rule as it fired; `conduct why ATK-315` does it for the item's newest actions.
@@ -306,7 +334,7 @@ The scheduler moves conduct-atk's tick into the daemon (design note, section 8).
306
334
  - **Release waits:** a Linear profile's `tracker.releaseWait: { "label": "waits-release", "shipped": "release-atk · Shipped" }` marks work that needs a dependency released, not only merged (a CLI change on npm `latest`, a prod deploy). An item with that label waits, once its open blockers have closed, until every Done blocker has a comment whose first line starts with the `shipped` text, under any heading or bold (`waits for ATK-391 to ship`), and starts on the first tick after. A comment that only quotes the text doesn't count. A blocker still open in another team waits the same way; a canceled one holds nothing. A blocker that shipped before release-atk wrote its notes (2026-10-06) holds until you add the note or remove the label. With the label and no blocker at all it waits until you act (`labelled waits-release with no blocker to wait on`), for something outside the tracker such as a Claude Code release. Such an item doesn't keep its tracker project from `queue.ready-to-release`, since the release is what it waits for. The read asks about the Done blockers in one more request, only when a labelled item has one. Without the setting, a Done blocker never holds anything.
307
335
  - **Slots:** `scheduler.slots` (4) across projects and the profile's `slots` per project (ATK's is 4), or `conduct focus --slots`, never more than the global ones. Slots count running workers only: a parked worker keeps its Area and frees its slot.
308
336
  - **Holds:** quiet hours and `conduct pause` turn picks into waits that say why.
309
- 3. **Act.** It runs the rules on what it found: `queue.pick` → `launch` (default `launch`), `item.closed` → `retire` (default `retire-closed`; a refused retire raises an inbox item once, except one refused while the worker's turn runs or its session starts, which is tried again each tick and raised only after 15 minutes), `queue.drained` and `queue.ready-to-release` → an inbox item once a day, `queue.triage` → a `triage` inbox item per item (default `triage`), resolved once the label goes. Each action is a `rule.fired` event, then `rule.done` or `rule.failed`. A launch that failed isn't tried again while its inbox item is open.
337
+ 3. **Act.** It runs the rules on what it found: `queue.pick` → `launch` (default `launch`), `item.closed` → `retire` (default `retire-closed`; a refused retire raises an inbox item once, except one refused while the worker's turn runs or its session starts, which is tried again each tick and raised only after 15 minutes; a partial retire is tried again after 15 minutes, and after 3 in a row it raises one inbox item that names the failed steps and any worktree left on disk, which the conductor never deletes, and is tried once a day), `queue.drained` and `queue.ready-to-release` → an inbox item once a day, `queue.triage` → a `triage` inbox item per item (default `triage`), resolved once the label goes. Each action is a `rule.fired` event, then `rule.done` or `rule.failed`. A launch that failed isn't tried again while its inbox item is open.
310
338
 
311
339
  `scheduler.mode` decides how far it goes:
312
340
  - **`on`** (the default): it acts.
@@ -350,20 +378,22 @@ A worker waits by posting its note, running `conduct wait …` and ending the tu
350
378
  | `pr 1850 checks` | `…#1850:checks` | no blocking build of the PR's current commit is queued or running (passed or failed; a path-filtered build it doesn't touch counts as passed) |
351
379
  | `pr 1850 comment` | `…#1850:comment` | a thread is open whose last word isn't a work-skill note |
352
380
  | `pr 458 merged --repo <owner>/<repo>` | `pr:github:<owner>/<repo>#458:merged` | the same on GitHub; `comment` there is a person's comment or review after the wait was registered. Without `--repo`, a project whose `codeHost` is `{ "kind": "github", "owner", "repo" }` means its own repo |
381
+ | `merge dda5b26c` / `--for deploy-api` | `merge:ado:Emergent.AgenticToolkit@dda5b26c:for=deploy-api` | the merge's runs settled as `conduct runs` reads them (its verdict isn't `waiting`). The wake names the outcome, the failed pipelines and each release's staged version with its approve line. The ADO watcher reads the merge's git in the worker's worktree, once per wait |
382
+ | `merge 5426434c --repo <owner>/<repo>` | `merge:github:<owner>/<repo>@5426434c` | the same on GitHub Actions: the runs the merge's push started settled |
353
383
  | `run atk-api-deploy 12345 done` | `run:ado:atk-api-deploy#12345:done` | the run completed, with its result |
354
- | `npm 8.5.0-dev.2 --tag dev` | `npm:@detergent-software/atk@8.5.0-dev.2:dev` | the dist-tag points at the version |
384
+ | `npm 8.5.0-dev.2 --tag dev` | `npm:@detergent-software/atk@8.5.0-dev.2:dev` | the dist-tag points at the version, read through the `npm-stage` release adapter |
355
385
  | `health dev --version 1.2.3` / `--commit <sha>` | `health:dev:version=1.2.3` | the profile's `health.dev` URL answers with that version or commit |
356
- | `approval` | `approval:linear:ATK-310` (`approval:ado-boards:CAD-12` on Boards) | an `approve` / `revise:` comment by the Linear key's owner (on Boards, the `az login` identity) after the item's newest `<work skill> · Plan` note |
357
- | `item ATK-271 done` / `label human` / `note Landed` | `item:linear:ATK-271:state=done` | the state (name or type), a label (a tag on Boards), or a phase note |
386
+ | `approval` | `approval:linear:ATK-310` (`approval:ado-boards:CAD-12` on Boards, `approval:github-issues:PAR-12` on GitHub Issues) | an `approve` / `revise:` comment by the Linear key's owner (on Boards, the `az login` identity; on GitHub Issues, the gh account) after the item's newest `<work skill> · Plan` note |
387
+ | `item ATK-271 done` / `label human` / `note Landed` | `item:linear:ATK-271:state=done` | the state (name or type), a label (a tag on Boards), or a phase note; on GitHub Issues the state is `open`, `closed`, a phase label or its type |
358
388
  | `login ado` / `github` / `linear` | `login:ado:atk` | the project's login to the provider works again: a probe as its account (see Accounts) succeeds |
359
389
  | `time 2026-10-10T09:00` | `time:2026-10-10T14:00:00.000Z` | the time has passed (no zone means local time) |
360
390
 
361
- - **With the daemon**, in a worker's worktree (or with `--worker <worker>`), `conduct wait` registers the wait and returns at once. The daemon parks the session when its turn ends and resumes it with `[conductor] event: <resolution> HH:MM` once the condition holds. The text is a fact, never an approval: the worker reads the source again. The daemon checks waits against the project's profile as registered (`conduct project add`), and refuses one it can't check, such as a `health` environment the profile has no URL for.
391
+ - **With the daemon**, in a worker's worktree (or with `--worker <worker>`), `conduct wait` registers the wait and returns at once. The daemon parks the session when its turn ends and resumes it with `[conductor] event: <resolution> HH:MM` once the condition holds. The text is a fact, never an approval: the worker reads the source again. The daemon checks waits against the project's stored profile (the last good one read from disk), and refuses one it can't check, such as a `health` environment the profile has no URL for.
362
392
  - **Without a daemon**, with `--local`, or when the daemon doesn't answer, it checks the condition itself and blocks. It prints `[conductor] event: …` and exits 0 when the condition holds, or exits 2 after `--timeout` minutes (29: Monitor ends a command at 30) so the caller can run it again. A wait that can't be checked (a PR that doesn't exist, no Linear key, `az` not signed in) exits 1, with the command that signs in again for a lost login. A `login` wait instead waits through a failing probe, after saying which account it waits for and the command. `--timeout` applies only here.
363
393
 
364
394
  **Watchers.** One per API: Linear (GraphQL), Azure DevOps (REST with an `az` token), GitHub (`gh api`), npm (the registry's dist-tags, no cache), `/health`, and `login` for login waits. Each provider call uses the waiting project's account (see Accounts). Time waits are the session manager's sweep. An API is asked nothing while no wait needs it. Otherwise it's asked once per `watchers.<api>.intervalSeconds`, within `perMinute` requests in any rolling minute shared by every project. Each pass covers all of that API's waits in as few requests as it can; Linear's is one request, filtered on `updatedAt` (with a 2-minute overlap) after the first. A pass that runs out of budget stops, and the next one starts a wait further on. A failing API waits twice as long each time (up to 10 minutes) and logs `watcher.failed` once, then `watcher.recovered`. A wait that can't be checked logs `wait.check-failed` once and stays open. Every `watchers.statsMinutes` (10) in which anything waited, a `watcher.stats` event records each API's passes, requests, budget skips, failures and waits.
365
395
 
366
- **Providers.** The Azure DevOps and GitHub watchers read PRs and runs through `@atk/providers` (`packages/providers`): one adapter per code host, CI and release, which turns each host's answers into the same snapshots, and the verdicts that read them (`checksSettled` for a `checks` wait, `needsAnswer` for a `comment` wait, and `prVerdict`, `runsVerdict` and `stageVerdict` for the verbs to come). A note is told from a person's comment by its heading (`noteHeading`) everywhere. The package is private, so the conductor vendors it into `src/lib/providers/` (`pnpm --filter @atk/providers sync-conductor`; `check` fails on drift, and `atk-conductor-ci` runs it). Change it in `packages/providers`, never in the copy.
396
+ **Providers.** The Azure DevOps and GitHub watchers read PRs and runs through `@atk/providers` (`packages/providers`): one adapter per code host, CI and release, which turns each host's answers into the same snapshots, and the verdicts that read them (`checksSettled` for a `checks` wait, `needsAnswer` for a `comment` wait, `prVerdict` for a verdict wait, and `runsVerdict` for a merge wait and `conduct runs`). A note is told from a person's comment by its heading (`noteHeading`) everywhere. The package is private, so the conductor vendors it into `src/lib/providers/` (`pnpm --filter @atk/providers sync-conductor`; `check` fails on drift, and `atk-conductor-ci` runs it). Change it in `packages/providers`, never in the copy.
367
397
 
368
398
  **PR conflicts.** Azure DevOps runs no build on a PR that conflicts with its base branch, so its `checks` never settle and it can't merge. The ADO watcher reads the PR's `mergeStatus` and target branch with the PR itself, at no extra request, and doesn't ask a conflicted PR's build evaluations, which can only be stale. The first pass that sees `conflicts` starts a spell for each worker waiting on the PR: it logs `pr.conflicted` and resolves the worker's waits on the PR with `pr <n> conflicts with <base>: merge origin/<base> into the branch (no rebase), resolve, rerun the checks, push and wait on the PR again` (after a wait's own line when it resolved on its own, such as a comment), which wakes the worker. Later passes in the spell wake nobody, unless the PR conflicts on a new source commit: the worker pushed and the base moved again, which starts a new spell. A pass that sees a clean merge, or the PR merged or abandoned, ends it (`pr.conflict-cleared`). A spell older than `watchers.conflictMinutes` (30) whose wake hasn't reached the worker, or whose conflict a pass still sees, logs `pr.conflict-stuck` once and raises a `conflict` inbox item. Spells are kept in memory, so after a restart a PR still in conflict wakes its worker once more. `conduct wait` without a daemon returns the same line while the PR conflicts.
369
399
 
@@ -423,6 +453,37 @@ A project whose work items live in Azure DevOps Boards (Cadence) sets `tracker.k
423
453
 
424
454
  Everything goes through the project's `az` token (see Accounts). The reads (the scheduler's queue and the watchers' waits) take from the `ado` watcher's budget, as PR and pipeline waits do. A wait pass reads every waited item of a board in one batch, and an item's comments only when its rev or comment count moved. Notes and the rules' `label` edits (a patch of `System.Tags` alone, refused if the item changed since it was read) are writes, outside the budget, as on Linear.
425
455
 
456
+ ## GitHub Issues
457
+
458
+ A project whose items are a GitHub repo's issues sets `tracker.kind` to `github-issues` (providers design note, section 5; ATK-458):
459
+
460
+ ```jsonc
461
+ "tracker": {
462
+ "kind": "github-issues",
463
+ "key": "PAR", // items are PAR-<issue number>: issue numbers are numbers alone
464
+ "humanLabel": "human", // a label
465
+ "triageLabel": "triage", // optional, a label too: new items Jason hasn't triaged (see Scheduler)
466
+ "queueLabel": "conduct", // the default: the open issues carrying it are the queue
467
+ "areaPrefix": "area:", // the default: area:docs is the Area docs
468
+ "priority": 3, // the queue's priority against other projects' (1 urgent … 4 low)
469
+ "transitions": { "started": "in-progress", "pr-opened": "in-review" } // the defaults
470
+ // "owner", "repo": the GitHub code host's by default; required beside an Azure DevOps one
471
+ }
472
+ ```
473
+
474
+ | Tracker concept | On GitHub Issues |
475
+ |---|---|
476
+ | the queue | the open issues labelled `queueLabel`, oldest first, one tracker project named after the repo with the profile's `priority`. PRs, which the issues API lists too, are left out. In-flight issues are read as well, so a closed one retires its worker. |
477
+ | Area | the name after `areaPrefix` on the issue's first such label |
478
+ | blockers | GitHub's issue dependencies (`blocked by`) still open, inside the queue or not; one in another repo is listed as `<owner>/<repo>#<n>` |
479
+ | human, triage, labels | labels, matched in any case |
480
+ | states | open → unstarted; open with a `transitions` label → started (named after the label); closed as completed → completed; closed as not planned or a duplicate → canceled |
481
+ | phase notes, `approve` / `revise:` | issue comments; the gh account's own comments count as Jason's, so the note heading tells them apart |
482
+
483
+ `Closes #n` would close an issue only on a merge to the default branch, so the conductor closes it on the `Landed` note instead, and a project on a `develop` branch works the same. Phase labels are added and taken off one at a time, never replaced as a set, so a label someone else added meanwhile stays. GitHub makes a label it doesn't have yet when a phase or rule first adds it; `conduct file` refuses an Area with no label.
484
+
485
+ Everything goes through the project's gh account (`projects.<name>.github.user`, else gh's active account; see Accounts). The reads (the scheduler's queue and the watchers' waits) take from the `github` watcher's budget, as PR and merge waits on GitHub do. A wait pass reads each waited issue once, and its comments only when its update time or comment count moved. Notes, label edits and the tracker writes are outside the budget, as on Linear. That account's rate limit is shared with anything else it runs (ATK's prod API reads with Jason's), so the `github` watcher's `perMinute` bounds it.
486
+
426
487
  ## Broker
427
488
 
428
489
  The broker shares what every project on the machine uses (ATK-318).
@@ -491,7 +552,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
491
552
 
492
553
  ## API
493
554
 
494
- - `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one.
555
+ - `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one. `POST /projects/<name>/check` with `{ "worktree": false }` or `{}` runs `conduct project check` as the daemon's accounts: `{ project, results: [{ check, status, summary, details, fix }] }`, or 404 for a name no project is registered under.
495
556
  - `GET /events`: the event log as server-sent events. Add `?since=<id>` (or send `Last-Event-ID`) to replay from an id. Without either, the stream starts at the live end. `conduct events` prints it one JSON event per line: the events after `--since` (all of them without it), or with `--follow` everything from there on (live without `--since`). `--follow` reconnects with `Last-Event-ID` when the daemon goes away, so a restart loses nothing, and prints the stream's state as lines without an id: `{"kind":"conduct.connected"}` and, once per outage, `{"kind":"conduct.disconnected","error":…}`. `?console=1` (`conduct events --follow --console`, which the conductor mod runs, or `conduct events --follow` with `CONDUCTOR_CONSOLE=1`, as a mod from before `--console` runs it until `/reload-plugins`) marks the console's stream: while one is open, escalations have a console to wake (see Console).
496
557
  - `POST /shutdown`: a clean stop (`conduct stop`).
497
558
  - `GET /console/escalations`: the open escalations, oldest first (see Console).
@@ -546,7 +607,19 @@ conduct install-startup
546
607
  conduct project add <path to the project's main checkout>
547
608
  ```
548
609
 
549
- `conduct project add` walks up from the path it is given to the directory that holds `.claude/conductor.json`, and maps a worktree to its main checkout. The daemon reloads a profile's `rules` when the file changes; every other change, such as a new `skills.work`, takes effect at the next `conduct project add`. An older daemon refuses a profile with fields it doesn't know, so upgrade the daemon before a profile that uses new ones lands. The same holds for the copy the daemon stored at `conduct project add`: after an `--allow-downgrade` to a version without those fields, the project shows `the stored profile no longer validates` and isn't conducted until a profile without them is added again.
610
+ `conduct project add` walks up from the path it is given to the directory that holds `.claude/conductor.json`, and maps a worktree to its main checkout. After that the daemon takes the whole profile from disk when it starts and each time the file is saved, so a `git pull` that changes `slots` or `skills.work` needs no `conduct project add`. An older daemon refuses a profile with fields it doesn't know, so upgrade the daemon before a profile that uses new ones lands. After an `--allow-downgrade` to a version without those fields, the stored copy no longer validates either: the project shows `the stored profile no longer validates` and isn't conducted until its file, saved without them, is read again.
611
+
612
+ 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):
613
+
614
+ - **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`.
615
+ - **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).
616
+ - **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.
617
+ - **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`.
618
+ - **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`.
619
+ - **ports:** the broker has a free slot for a new worktree, up to `ports.maxSlot`, saving nothing.
620
+ - **health:** each `health` URL answers; a `warn` at most, since an environment can be down.
621
+
622
+ With the daemon running it checks through the API, as the daemon's own accounts; without it, here, as the machine's.
550
623
 
551
624
  The profile's optional `work` section is for the project's work skill; the daemon only checks it:
552
625
 
@@ -605,7 +678,7 @@ It runs `claude -n conductor --permission-mode auto -- /conductor-console` with
605
678
  - **Other sessions** get nothing from it: no status line, and no calls to the daemon.
606
679
  - **The console**, the session started with `CONDUCTOR_CONSOLE=1` (`conduct console` starts it; see Console), reads `GET /fleet`, `GET /inbox` and `GET /notify` every 30 s and gets:
607
680
  - **the band** above the prompt while the inbox has items: one hotkey per item (a plan approves after a second press, `y`, since a digit typed into an empty prompt presses a band button; an npm OTP copies its `npm stage approve` line; the rest open the pane), and `o` for the pane;
608
- - **the pane** (`/conductor`, `o`, or opened at the start, docked as a sidebar on a fullscreen terminal of 144 columns or more): the notification profile (a picker; buttons on mobile) and Remote Control's state, the inbox with each item's actions (approve, revise, copy a command, open a link, done), the workers by project (each project's rule has [pause] and [focus]; a row expands to attach and why), and today's spend;
681
+ - **the pane** (`/conductor`, `o`, or opened at the start, docked as a sidebar on a fullscreen terminal of 144 columns or more): the notification profile (a picker; buttons on mobile) and Remote Control's state, the inbox with each item's actions (approve, revise, copy a command, open a link, done; on a Windows terminal, open is a button that opens a web link in the default browser once, since a ctrl+click on a link in a pane opened it twice, ATK-494), the workers by project (each project's rule has [pause] and [focus]; a row expands to attach and why), and today's spend;
609
682
  - **commands answered in code**, never by the model: `/conductor`, `/inbox`, `/approve <item | #id>`, `/revise <item | #id> <text>`, `/attach <item | worker>`, `/notify [<profile> [--until <when>]]`, `/pause [<project>] [--until <when>]`, `/unpause [<project>]`, `/focus-project <project> [--until <when>]` (puts its items first, until midnight without `--until`; not `/focus`, which is Claude Code's own);
610
683
  - **a check that `/conductor-console` ran**: a console started without it (by hand, as `claude -n conductor`) shows `console without its skill: run /conductor-console` in the band, and each prompt, Jason's or an escalation's, carries a line telling the model to load the skill before it answers. Running the skill (`/conductor-console`, or the model's Skill tool) clears both, until a `/clear` or a resume starts a conversation without it;
611
684
  - **a warm prompt cache while it idles** (ATK-386). The console's cache lasts an hour after its last use, and a wake after that writes the whole context again. So 55 minutes after the console's last turn, the mod sends one ping (`$.model.fork`, the console's own last request plus a one-word ask, no tools, no turn in the transcript). The API serves the ping from the cache for a tenth of a write's price, and the hour starts over. It pings again every 55 minutes until a turn comes, and each turn starts the timer over. At an idle point it runs `/clear` instead when the context is 150k or more, or after 12 pings in a row (the 13th idle point, about 12 hours after the last turn), and the band says `fresh console (cleared while idle: …)` until the next prompt loads the skill. A ping that read less than half the context from the cache (the cache was gone anyway, as under a 5-minute TTL) stops the pings until the next turn. Each ping and clear goes to `POST /console/cache`, which logs a `console.cache` event with the context size and what the ping cost. A fork isn't in the transcript, so that event is the only place its cost shows;
@@ -1,70 +1,152 @@
1
1
  import { z } from 'zod';
2
- import { briefLines } from "./brief.js";
2
+ import { BriefError, briefLines, briefOf, briefPrs, linearBriefs, notedPr, } from "./brief.js";
3
3
  import { callApi, NotRunningError } from "./client.js";
4
+ import { projectHosts } from "./code-host.js";
5
+ import {} from "./credentials.js";
4
6
  import { ConductorError } from "./errors.js";
5
- // Exit code for a brief no conductor gave (none running, one without the brief API, or one that
6
- // couldn't read the tracker): the worker reads the item from the tracker itself. A refusal (no
7
- // project tracks the item, the tracker has no such item) exits 1.
7
+ import { checkoutProject, localCredentials } from "./in-process.js";
8
+ import { ITEM } from "./notes.js";
9
+ import { notePrefixes } from "./profile.js";
10
+ import { routedBriefs } from "./trackers.js";
11
+ import { boardsBriefs } from "./watchers/boards-brief.js";
12
+ import { issuesBriefs } from "./watchers/issues-brief.js";
13
+ // Exit code for a brief nobody gave (no conductor answered, and this isn't the item's project
14
+ // checkout to read it from; or the conductor couldn't read the tracker): the worker reads the item
15
+ // from the tracker itself. A refusal (no project tracks the item, the tracker has no such item)
16
+ // exits 1.
8
17
  export const NOT_BRIEFED = 3;
9
- // The daemon asks the tracker once or a few times, each within 20 s.
10
- const BRIEF_TIMEOUT_MS = 60_000;
18
+ // The daemon asks the tracker once or a few times, and the code host for the PR, each within 20 s.
19
+ const BRIEF_TIMEOUT_MS = 90_000;
11
20
  const link = z.object({ id: z.string(), status: z.string().nullable(), title: z.string().nullable() });
12
21
  export const briefSchema = z.object({
13
22
  approval: z.object({ at: z.string(), kind: z.enum(['approve', 'revise']), text: z.string() }).nullable(),
14
23
  area: z.string().nullable(),
15
24
  blockedBy: z.array(link),
16
25
  blocks: z.array(link),
26
+ comments: z.array(z.object({ at: z.string(), body: z.string(), byViewer: z.boolean() })),
17
27
  description: z.string(),
18
28
  item: z.string(),
19
29
  labels: z.array(z.string()),
20
30
  notes: z.array(z.object({ at: z.string(), kind: z.string().nullable(), line: z.string().nullable(), phase: z.string() })),
31
+ phase: z.enum(['abandoned', 'awaiting-approval', 'canceled', 'done', 'implement', 'land', 'orient', 'plan', 'wait']),
21
32
  plan: z.string().nullable(),
33
+ pr: z
34
+ .object({
35
+ error: z.string().optional(),
36
+ outcome: z
37
+ .enum([
38
+ 'abandoned',
39
+ 'auto-merge-cancelled',
40
+ 'build-failed',
41
+ 'changes-requested',
42
+ 'conflicts',
43
+ 'merge-failed',
44
+ 'merged',
45
+ 'review-comment',
46
+ 'waiting',
47
+ ])
48
+ .nullable(),
49
+ ref: z.string(),
50
+ state: z.enum(['closed', 'merged', 'open']).nullable(),
51
+ title: z.string().nullable(),
52
+ url: z.string().nullable(),
53
+ })
54
+ .nullable(),
22
55
  project: z.string(),
23
56
  readAt: z.string(),
24
57
  related: z.array(link),
25
- // A daemon from before it gives none.
26
- revise: z.object({ at: z.string(), text: z.string() }).nullable().default(null),
58
+ revise: z.object({ at: z.string(), text: z.string() }).nullable(),
27
59
  status: z.object({ name: z.string(), type: z.string().nullable() }),
28
60
  title: z.string(),
29
61
  trackerProject: z.string().nullable(),
30
62
  url: z.string().nullable(),
31
63
  });
32
64
  const refused = z.object({ details: z.array(z.string()).optional(), error: z.string() });
33
- // `conduct brief <item>`: the item's context from the daemon, as text or JSON. Returns the exit
34
- // code: 0, NOT_BRIEFED, or 1 (thrown) when the conductor refused it.
65
+ // `conduct brief <item>`: the item's context, as text or JSON; its comments with --comments. From
66
+ // the daemon, else read here from this checkout's project. Returns the exit code: 0, NOT_BRIEFED,
67
+ // or 1 (thrown) when it was refused.
35
68
  export async function briefCommand(paths, item, options, io) {
36
- const notBriefed = (why) => {
37
- io.err(`no brief: ${why}; read the item from the tracker yourself`);
38
- return NOT_BRIEFED;
39
- };
40
69
  let answer;
41
70
  try {
42
71
  answer = await callApi(paths, {
43
72
  method: 'GET',
44
- path: `/items/${encodeURIComponent(item)}/brief${options.fresh ? '?fresh=true' : ''}`,
73
+ path: `/items/${encodeURIComponent(item)}/brief?${new URLSearchParams({
74
+ ...(options.fresh ? { fresh: 'true' } : {}),
75
+ ...(options.comments ? { comments: 'true' } : {}),
76
+ }).toString()}`,
45
77
  timeoutMs: BRIEF_TIMEOUT_MS,
46
78
  });
47
79
  }
48
80
  catch (error) {
49
81
  if (error instanceof NotRunningError)
50
- return notBriefed('the conductor is not running');
82
+ return briefHere(paths, item, options, io, 'the conductor is not running');
51
83
  throw error;
52
84
  }
53
85
  if (answer.status === 200) {
54
- const { brief } = z.object({ brief: briefSchema }).parse(answer.body);
55
- if (options.json)
56
- io.out(JSON.stringify(brief, null, 2));
57
- else
58
- briefLines(brief).forEach((line) => io.out(line));
86
+ const given = z.object({ brief: briefSchema }).safeParse(answer.body);
87
+ // A conductor of another version gives a brief of another shape: read it here instead.
88
+ 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);
59
91
  return 0;
60
92
  }
61
93
  const error = refused.safeParse(answer.body).data;
62
94
  // A daemon from before the brief API answers its route's `not_found`; a missing item says which.
63
95
  if (answer.status === 404 && error?.error === 'not_found')
64
- return notBriefed('this conductor has no brief API (upgrade it)');
96
+ return briefHere(paths, item, options, io, 'this conductor has no brief API');
65
97
  // The conductor couldn't read the tracker (no key, the tracker down): the worker still may.
66
98
  if (answer.status === 502)
67
- return notBriefed(`${error?.error ?? 'the conductor could not read the tracker'}${error?.details?.length ? ` (${error.details.join('; ')})` : ''}`);
99
+ return notBriefed(io, `${error?.error ?? 'the conductor could not read the tracker'}${error?.details?.length ? ` (${error.details.join('; ')})` : ''}`);
68
100
  throw new ConductorError(error?.error ?? `the API answered ${answer.status}`, error?.details ?? []);
69
101
  }
102
+ // The brief read here, with no conductor: the tracker and the PR as this checkout's project's
103
+ // 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) {
105
+ if (!ITEM.test(item))
106
+ throw new ConductorError(`"${item}" is not a tracker item, such as ATK-311`);
107
+ const key = item.toUpperCase();
108
+ const here = await checkoutProject(io.cwd, key, why, 'read it from');
109
+ if ('refusal' in here)
110
+ return notBriefed(io, here.refusal);
111
+ const { profile } = here;
112
+ // The machine's accounts, built once, and only where a seam doesn't stand in for them. A config
113
+ // that can't be read is a brief that couldn't be read here (exit 3), as a tracker that is down.
114
+ let credentials;
115
+ const accounts = () => (credentials ??= localCredentials(paths));
116
+ // A PR it can't read (the accounts included) is an `error` on the brief, never a throw.
117
+ const prs = io.prs ?? briefPrs((project) => projectHosts(accounts(), project));
118
+ let read;
119
+ try {
120
+ const reader = io.reader ??
121
+ routedBriefs({
122
+ 'ado-boards': boardsBriefs({ credentials: accounts() }),
123
+ 'github-issues': issuesBriefs({ credentials: accounts() }),
124
+ linear: linearBriefs({ credentials: accounts() }),
125
+ });
126
+ read = await reader.read(key, profile);
127
+ }
128
+ catch (error) {
129
+ if (error instanceof BriefError && error.reason === 'missing')
130
+ throw new ConductorError(error.message);
131
+ return notBriefed(io, error instanceof Error ? error.message : String(error));
132
+ }
133
+ const prefixes = notePrefixes(profile);
134
+ const ref = notedPr(read.comments, prefixes);
135
+ const brief = briefOf(key, profile.name, read, prefixes, new Date(), ref === null ? null : await prs(profile, ref));
136
+ io.err(`read here (${why})`);
137
+ print(brief, options, io);
138
+ return 0;
139
+ }
140
+ function notBriefed(io, why) {
141
+ io.err(`no brief: ${why}; read the item from the tracker yourself`);
142
+ return NOT_BRIEFED;
143
+ }
144
+ function print(brief, options, io) {
145
+ if (options.json) {
146
+ const { comments: _comments, ...rest } = brief;
147
+ io.out(JSON.stringify(options.comments ? brief : rest, null, 2));
148
+ }
149
+ else
150
+ briefLines(brief, { comments: options.comments === true }).forEach((line) => io.out(line));
151
+ }
70
152
  //# sourceMappingURL=brief-cli.js.map