@detergent-software/conductor 1.0.0-dev.30 → 1.0.0-dev.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
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.0.0-dev.31](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.30&targetVersion=GTconductor-v1.0.0-dev.31) (2026-10-12)
6
+
7
+ ### Features
8
+
9
+ * **conductor:** conduct brief, an item's context for orient (ATK-371) ([441d47f](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/441d47fd735eb927690e22d5b30ab130fafd7d16))
10
+
11
+ ### Bug Fixes
12
+
13
+ * **conductor:** brief cache drops on any item event (ATK-371) ([59b9775](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/59b9775703f2296cdd44e8abc5bec2c9b5d9c71b))
14
+
5
15
  ## [1.0.0-dev.30](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.0.0-dev.29&targetVersion=GTconductor-v1.0.0-dev.30) (2026-10-12)
6
16
 
7
17
  ### Features
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Conductor
2
2
 
3
- `@detergent-software/conductor`: the local daemon that runs the work loop for every conducted project on this machine, and its `conduct` CLI. The design, decisions and build order are in the Linear document *Conductor: design note* (project *Conductor daemon*). This package has build step 1, the skeleton (ATK-308), step 2, the session manager (ATK-309), step 3, the watchers and `conduct wait` (ATK-310), the daemon's side of step 4, worker protocol v1's `conduct note` (ATK-311), step 5, the inbox with `conduct approve` / `revise` (ATK-312), step 6, the notifier (ATK-313), step 9, the conductor mod for Claude Code in `mod/` (ATK-265), step 8, the scheduler, settings and rules (ATK-315), the daemon's side of step 7, the console (ATK-314), step 12, the port broker (ATK-318), and step 13, the ADO Boards tracker (ATK-319).
3
+ `@detergent-software/conductor`: the local daemon that runs the work loop for every conducted project on this machine, and its `conduct` CLI. The design, decisions and build order are in the Linear document *Conductor: design note* (project *Conductor daemon*). This package has build step 1, the skeleton (ATK-308), step 2, the session manager (ATK-309), step 3, the watchers and `conduct wait` (ATK-310), the daemon's side of step 4, worker protocol v1's `conduct note` (ATK-311), step 5, the inbox with `conduct approve` / `revise` (ATK-312), step 6, the notifier (ATK-313), step 9, the conductor mod for Claude Code in `mod/` (ATK-265), step 8, the scheduler, settings and rules (ATK-315), the daemon's side of step 7, the console (ATK-314), step 12, the port broker (ATK-318), step 13, the ADO Boards tracker (ATK-319), and `conduct brief` for a worker's orient step (ATK-371).
4
4
 
5
5
  ## Commands
6
6
 
@@ -47,6 +47,8 @@ conduct rules test <file> [--json]
47
47
  dry-run the rules on a sample event; acts on nothing
48
48
  conduct note <item> <file> post a phase note on the item and record its phase (see Notes)
49
49
  [--worker <worker>]
50
+ conduct brief <item> [--json] [--fresh]
51
+ the item's status, links, phase notes and approval, from the tracker (see Briefs)
50
52
  conduct wait <condition> wait for an outside event (see Waits)
51
53
  conduct ports [<path>] [--prefer <slot>] [--json]
52
54
  a checkout's dev ports from the port broker, or every worktree's (see Broker)
@@ -96,6 +98,28 @@ The work skill is the profile's `skills.work`. A note headed with one of `skills
96
98
 
97
99
  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.
98
100
 
101
+ ## Briefs
102
+
103
+ `conduct brief <item>` gives a worker the item's context for its orient step, read by the daemon from the tracker, so the worker needs no MCP call. It gives:
104
+ - the title, URL, status (name and type), the tracker's project, Area and labels;
105
+ - the blockers, the items it blocks and the related items, each with its status and title;
106
+ - its phase notes (time, phase, kind and line), the newest Plan, and the `approve` / `revise:` after it with its words;
107
+ - the description.
108
+
109
+ `--json` prints it as one object.
110
+
111
+ The queue's snapshot has no descriptions, relations or comments, so the daemon reads the item itself:
112
+ - **Linear:** one GraphQL request with the watchers' key, and more only for comments past the first 100.
113
+ - **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.
114
+
115
+ 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.
116
+
117
+ | Exit | Means | The worker then |
118
+ |---|---|---|
119
+ | 0 | the brief | uses it |
120
+ | 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) |
121
+ | 1 | refused: no project tracks the item, or the tracker has no such item | reads the message |
122
+
99
123
  ## Inbox
100
124
 
101
125
  The `inbox` table holds everything that waits on Jason, across projects. Each item records:
@@ -367,6 +391,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
367
391
  - `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work when it refuses.
368
392
  - `POST /workers/:id/attach`: wake if parked, mark attached, open the tab.
369
393
  - `POST /items/:id/notes` with `{ "body": "## work-issue · …", "worker"?: 1 }`: post a phase note (see Notes); 201 with the note and the worker. 409 when it won't post it; 502 when the tracker didn't take it, with `posted: "no"` or `"unknown"`.
394
+ - `GET /items/:id/brief` (`?fresh=true`): the item's brief (see Briefs), as `{ "brief": … }`. 404 when the tracker has no such item, 409 when no project tracks it, 502 when the tracker couldn't be read.
370
395
  - `GET /inbox`: the open inbox items, oldest first.
371
396
  - `POST /inbox/:id/resolve`: Jason's answer (see Inbox). The body is `{ "action": "approve", "text"? }`, `{ "action": "revise", "text" }` or `{ "action": "done" }`.
372
397
  - 200 with the item, the comment and the waits it resolved.
@@ -0,0 +1,68 @@
1
+ import { z } from 'zod';
2
+ import { briefLines } from "./brief.js";
3
+ import { callApi, NotRunningError } from "./client.js";
4
+ 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.
8
+ 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;
11
+ const link = z.object({ id: z.string(), status: z.string().nullable(), title: z.string().nullable() });
12
+ export const briefSchema = z.object({
13
+ approval: z.object({ at: z.string(), kind: z.enum(['approve', 'revise']), text: z.string() }).nullable(),
14
+ area: z.string().nullable(),
15
+ blockedBy: z.array(link),
16
+ blocks: z.array(link),
17
+ description: z.string(),
18
+ item: z.string(),
19
+ labels: z.array(z.string()),
20
+ notes: z.array(z.object({ at: z.string(), kind: z.string().nullable(), line: z.string().nullable(), phase: z.string() })),
21
+ plan: z.string().nullable(),
22
+ project: z.string(),
23
+ readAt: z.string(),
24
+ related: z.array(link),
25
+ status: z.object({ name: z.string(), type: z.string().nullable() }),
26
+ title: z.string(),
27
+ trackerProject: z.string().nullable(),
28
+ url: z.string().nullable(),
29
+ });
30
+ const refused = z.object({ details: z.array(z.string()).optional(), error: z.string() });
31
+ // `conduct brief <item>`: the item's context from the daemon, as text or JSON. Returns the exit
32
+ // code: 0, NOT_BRIEFED, or 1 (thrown) when the conductor refused it.
33
+ export async function briefCommand(paths, item, options, io) {
34
+ const notBriefed = (why) => {
35
+ io.err(`no brief: ${why}; read the item from the tracker yourself`);
36
+ return NOT_BRIEFED;
37
+ };
38
+ let answer;
39
+ try {
40
+ answer = await callApi(paths, {
41
+ method: 'GET',
42
+ path: `/items/${encodeURIComponent(item)}/brief${options.fresh ? '?fresh=true' : ''}`,
43
+ timeoutMs: BRIEF_TIMEOUT_MS,
44
+ });
45
+ }
46
+ catch (error) {
47
+ if (error instanceof NotRunningError)
48
+ return notBriefed('the conductor is not running');
49
+ throw error;
50
+ }
51
+ if (answer.status === 200) {
52
+ const { brief } = z.object({ brief: briefSchema }).parse(answer.body);
53
+ if (options.json)
54
+ io.out(JSON.stringify(brief, null, 2));
55
+ else
56
+ briefLines(brief).forEach((line) => io.out(line));
57
+ return 0;
58
+ }
59
+ const error = refused.safeParse(answer.body).data;
60
+ // A daemon from before the brief API answers its route's `not_found`; a missing item says which.
61
+ if (answer.status === 404 && error?.error === 'not_found')
62
+ return notBriefed('this conductor has no brief API (upgrade it)');
63
+ // The conductor couldn't read the tracker (no key, the tracker down): the worker still may.
64
+ if (answer.status === 502)
65
+ return notBriefed(`${error?.error ?? 'the conductor could not read the tracker'}${error?.details?.length ? ` (${error.details.join('; ')})` : ''}`);
66
+ throw new ConductorError(error?.error ?? `the API answered ${answer.status}`, error?.details ?? []);
67
+ }
68
+ //# sourceMappingURL=brief-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief-cli.js","sourceRoot":"","sources":["../src/brief-cli.ts"],"names":[],"mappings":"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"}
package/build/brief.js ADDED
@@ -0,0 +1,211 @@
1
+ import { z } from 'zod';
2
+ import { ConductorError } from "./errors.js";
3
+ import { ITEM, projectFor } from "./notes.js";
4
+ import { notePrefixes } from "./profile.js";
5
+ import { linearApi, LinearRequestError } from "./watchers/linear-api.js";
6
+ import { approvalAfterPlan, noteHeading } from "./watchers/linear.js";
7
+ // `conduct brief` (design note, section 13): one item's context for a worker's orient step, read
8
+ // by the daemon from the tracker with its own key or az login, never through an MCP. The queue's
9
+ // snapshot holds no description, relations or comments, so the brief reads the item itself.
10
+ const TTL_MS = 30_000;
11
+ // A brief the tracker couldn't give: `missing` when it has no such item, `unreadable` otherwise.
12
+ export class BriefError extends ConductorError {
13
+ reason;
14
+ constructor(message, reason, details = []) {
15
+ super(message, details);
16
+ this.name = 'BriefError';
17
+ this.reason = reason;
18
+ }
19
+ }
20
+ // The brief as a reader sees it, for `conduct brief` without --json.
21
+ export function briefLines(brief) {
22
+ const links = (label, list) => list.length === 0 ? [] : [`${label}:`, ...list.map((l) => ` ${l.id} ${l.status ?? '?'} ${l.title ?? ''}`.trimEnd())];
23
+ const approval = brief.plan === null ? 'no Plan note yet'
24
+ : brief.approval === null ? `none after the Plan of ${brief.plan}`
25
+ : `${brief.approval.kind} ${brief.approval.at}: "${brief.approval.text}"`;
26
+ return [
27
+ `${brief.item} ${brief.title}`,
28
+ ...(brief.url ? [brief.url] : []),
29
+ `status: ${brief.status.name}${brief.status.type && brief.status.type !== brief.status.name.toLowerCase() ? ` (${brief.status.type})` : ''}`,
30
+ `project: ${brief.project}${brief.trackerProject ? ` · ${brief.trackerProject}` : ''}`,
31
+ `area: ${brief.area ?? 'none'}`,
32
+ `labels: ${brief.labels.length > 0 ? brief.labels.join(', ') : 'none'}`,
33
+ ...links('blocked by', brief.blockedBy),
34
+ ...links('blocks', brief.blocks),
35
+ ...links('related', brief.related),
36
+ ...(brief.notes.length === 0
37
+ ? ['notes: none']
38
+ : ['notes:', ...brief.notes.map((n) => ` ${n.at} ${n.phase}${n.kind ? ` · ${n.kind}${n.line ? `: ${n.line}` : ''}` : ''}`)]),
39
+ `approval: ${approval}`,
40
+ '',
41
+ brief.description.trim() || '(no description)',
42
+ ];
43
+ }
44
+ // The brief from what the tracker gave: the comments as phase notes, the newest Plan, and the
45
+ // approval after it.
46
+ export function briefOf(item, project, read, prefixes, at) {
47
+ const { comments, viewer, ...fields } = read;
48
+ const sorted = [...comments].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
49
+ const notes = sorted.flatMap((c) => {
50
+ const heading = noteHeading(c.body, prefixes);
51
+ return heading ? [{ at: c.createdAt, ...heading }] : [];
52
+ });
53
+ const found = approvalAfterPlan(sorted, viewer, prefixes);
54
+ // Notes carry the same author as the answer, so only a comment that isn't a note can be it.
55
+ const said = found && sorted.find((c) => c.createdAt === found.at && c.authorId === viewer && noteHeading(c.body, prefixes) === null);
56
+ return {
57
+ ...fields,
58
+ approval: found ? { ...found, text: said ? said.body.trim() : '' } : null,
59
+ item,
60
+ notes,
61
+ plan: notes.findLast((n) => n.phase === 'Plan')?.at ?? null,
62
+ project,
63
+ readAt: at.toISOString(),
64
+ };
65
+ }
66
+ export function briefService({ now = () => new Date(), reader, store, ttlMs = TTL_MS, }) {
67
+ const cache = new Map();
68
+ // Bumped by each event about an item, so a read that began before one isn't kept.
69
+ const changes = new Map();
70
+ // Whatever the daemon sees happen to an item (a note posted, an approve or revise it posted, a
71
+ // wait resolved) makes its brief stale at once.
72
+ store.onEvent((event) => {
73
+ if (!event.subject)
74
+ return;
75
+ const key = event.subject.toUpperCase();
76
+ cache.delete(key);
77
+ changes.set(key, (changes.get(key) ?? 0) + 1);
78
+ });
79
+ return {
80
+ read: async (item, { fresh = false } = {}) => {
81
+ if (!ITEM.test(item))
82
+ throw new ConductorError(`"${item}" is not a tracker item, such as ATK-311`);
83
+ const key = item.toUpperCase();
84
+ const at = now().getTime();
85
+ for (const [k, entry] of cache)
86
+ if (at - entry.at >= ttlMs)
87
+ cache.delete(k);
88
+ const hit = cache.get(key);
89
+ if (!fresh && hit)
90
+ return hit.brief;
91
+ let project;
92
+ try {
93
+ project = projectFor(store, key);
94
+ }
95
+ catch (error) {
96
+ // Its details are for a note (--worker), which a brief doesn't take.
97
+ throw error instanceof ConductorError ? new ConductorError(error.message) : error;
98
+ }
99
+ const seen = changes.get(key) ?? 0;
100
+ let read;
101
+ try {
102
+ read = await reader.read(key, project.profile);
103
+ }
104
+ catch (error) {
105
+ if (error instanceof BriefError)
106
+ throw error;
107
+ const details = error instanceof ConductorError ? error.details : [];
108
+ throw new BriefError(`couldn't read ${key}: ${error instanceof Error ? error.message : String(error)}`, 'unreadable', details);
109
+ }
110
+ const brief = briefOf(key, project.name, read, notePrefixes(project.profile), now());
111
+ if ((changes.get(key) ?? 0) === seen)
112
+ cache.set(key, { at: now().getTime(), brief });
113
+ return brief;
114
+ },
115
+ };
116
+ }
117
+ const STATE = 'state { name type }';
118
+ const LINKED = `identifier title ${STATE}`;
119
+ const COMMENTS = 'comments(first: 100, after: $after) { pageInfo { hasNextPage endCursor } nodes { body createdAt user { id } } }';
120
+ export const BRIEF_QUERY = `query Brief($id: String!, $after: String) {
121
+ viewer { id }
122
+ issue(id: $id) {
123
+ identifier title description url ${STATE}
124
+ project { name }
125
+ labels(first: 50) { nodes { name parent { name } } }
126
+ relations(first: 50) { nodes { type relatedIssue { ${LINKED} } } }
127
+ inverseRelations(first: 50) { nodes { type issue { ${LINKED} } } }
128
+ ${COMMENTS}
129
+ }
130
+ }`;
131
+ export const BRIEF_COMMENTS_QUERY = `query BriefComments($id: String!, $after: String) {
132
+ issue(id: $id) { ${COMMENTS} }
133
+ }`;
134
+ const state = z.object({ name: z.string(), type: z.string() });
135
+ const linked = z.object({ identifier: z.string(), state, title: z.string().nullable() });
136
+ const commentsPage = z.object({
137
+ nodes: z.array(z.object({ body: z.string(), createdAt: z.string(), user: z.object({ id: z.string() }).nullable() })),
138
+ pageInfo: z.object({ endCursor: z.string().nullable(), hasNextPage: z.boolean() }),
139
+ });
140
+ const briefAnswer = z.object({
141
+ issue: z
142
+ .object({
143
+ comments: commentsPage,
144
+ description: z.string().nullable(),
145
+ identifier: z.string(),
146
+ inverseRelations: z.object({ nodes: z.array(z.object({ issue: linked.nullable(), type: z.string() })) }),
147
+ labels: z.object({ nodes: z.array(z.object({ name: z.string(), parent: z.object({ name: z.string() }).nullable() })) }),
148
+ project: z.object({ name: z.string() }).nullable(),
149
+ relations: z.object({ nodes: z.array(z.object({ relatedIssue: linked.nullable(), type: z.string() })) }),
150
+ state,
151
+ title: z.string(),
152
+ url: z.string().nullable(),
153
+ })
154
+ .nullable(),
155
+ viewer: z.object({ id: z.string() }),
156
+ });
157
+ const commentsAnswer = z.object({ issue: z.object({ comments: commentsPage }).nullable() });
158
+ // Linear: the issue, its labels (the Area is the one under the profile's label group), relations
159
+ // both ways and every comment, in one request (more only for comments past the first 100).
160
+ export function linearBriefs(options = {}) {
161
+ const request = linearApi(options);
162
+ const ask = async (query, variables) => {
163
+ try {
164
+ return await request(query, variables);
165
+ }
166
+ catch (error) {
167
+ // Linear answers an identifier it doesn't have with an error, not a null issue.
168
+ if (error instanceof LinearRequestError && error.kind === 'refused' && /not found/i.test(error.message)) {
169
+ throw new BriefError(`Linear has no issue ${String(variables.id)}`, 'missing');
170
+ }
171
+ throw error;
172
+ }
173
+ };
174
+ return {
175
+ read: async (item, profile) => {
176
+ if (profile.tracker.kind !== 'linear')
177
+ throw new ConductorError(`project ${profile.name}'s tracker isn't Linear`);
178
+ const { issue, viewer } = briefAnswer.parse(await ask(BRIEF_QUERY, { after: null, id: item }));
179
+ if (!issue)
180
+ throw new BriefError(`Linear has no issue ${item}`, 'missing');
181
+ const comments = [...issue.comments.nodes];
182
+ for (let page = issue.comments.pageInfo; page.hasNextPage && page.endCursor !== null;) {
183
+ const more = commentsAnswer.parse(await ask(BRIEF_COMMENTS_QUERY, { after: page.endCursor, id: item })).issue?.comments;
184
+ if (!more)
185
+ break;
186
+ comments.push(...more.nodes);
187
+ page = more.pageInfo;
188
+ }
189
+ const link = (l) => ({ id: l.identifier, status: l.state.name, title: l.title });
190
+ // "A blocks B" is stored on A: B sees it as an inverse relation. `related` shows on either side.
191
+ const outward = issue.relations.nodes.flatMap((r) => (r.relatedIssue ? [{ issue: r.relatedIssue, type: r.type }] : []));
192
+ const inward = issue.inverseRelations.nodes.flatMap((r) => (r.issue ? [{ issue: r.issue, type: r.type }] : []));
193
+ const { areaLabelGroup } = profile.tracker;
194
+ return {
195
+ area: issue.labels.nodes.find((l) => l.parent?.name === areaLabelGroup)?.name ?? null,
196
+ blockedBy: inward.filter((r) => r.type === 'blocks').map((r) => link(r.issue)),
197
+ blocks: outward.filter((r) => r.type === 'blocks').map((r) => link(r.issue)),
198
+ comments: comments.map((c) => ({ authorId: c.user?.id ?? null, body: c.body, createdAt: c.createdAt })),
199
+ description: issue.description ?? '',
200
+ labels: issue.labels.nodes.map((l) => l.name),
201
+ related: [...outward, ...inward].filter((r) => r.type === 'related').map((r) => link(r.issue)),
202
+ status: { name: issue.state.name, type: issue.state.type },
203
+ title: issue.title,
204
+ trackerProject: issue.project?.name ?? null,
205
+ url: issue.url,
206
+ viewer: viewer.id,
207
+ };
208
+ },
209
+ };
210
+ }
211
+ //# sourceMappingURL=brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../src/brief.ts"],"names":[],"mappings":"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"}
package/build/cli.js CHANGED
@@ -5,6 +5,7 @@ import { resolve, sep } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { parseArgs } from 'node:util';
7
7
  import { z } from 'zod';
8
+ import { briefCommand } from "./brief-cli.js";
8
9
  import { heldLocks, lockCommand, lockLine, portsCommand } from "./broker-cli.js";
9
10
  import { callApi, NotRunningError, ping, readEndpoint } from "./client.js";
10
11
  import { loadConfig } from "./config.js";
@@ -418,6 +419,14 @@ async function main(argv) {
418
419
  if (sub === undefined)
419
420
  throw usageError('attach');
420
421
  return attach(paths, sub);
422
+ case 'brief':
423
+ if (sub === undefined)
424
+ throw usageError('brief');
425
+ process.exitCode = await briefCommand(paths, sub, { fresh: values.fresh === true, json: values.json === true }, {
426
+ err: (line) => process.stderr.write(`${line}\n`),
427
+ out,
428
+ });
429
+ return;
421
430
  case 'console':
422
431
  return consoleCommand(paths, { here: values.here === true, project: values.project });
423
432
  case 'done':