@detergent-software/conductor 1.3.0-dev.5 → 1.3.0-dev.6

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,17 @@
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.3.0-dev.6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.3.0-dev.5&targetVersion=GTconductor-v1.3.0-dev.6) (2026-10-14)
6
+
7
+ ### Features
8
+
9
+ * **conductor:** conduct state, branch, checkout and pr create (ATK-454) ([626f9eb](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/626f9eb0fb74f454190888c9e4fb420a642a8766))
10
+
11
+ ### Bug Fixes
12
+
13
+ * **conductor:** pr help names every check before the push (ATK-454) ([b866334](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b8663346be93057a499634c35a429b809a2de523))
14
+ * **conductor:** review fixes for the worker verbs (ATK-454) ([6806665](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/68066658c029fab55bdbef56721beb1cb66af810))
15
+
5
16
  ## [1.3.0-dev.5](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.3.0-dev.4&targetVersion=GTconductor-v1.3.0-dev.5) (2026-10-14)
6
17
 
7
18
  ### Features
package/README.md CHANGED
@@ -51,6 +51,13 @@ conduct note <item> <file> post a phase note on the item and record its p
51
51
  conduct brief <item> [--json] [--fresh]
52
52
  the item's status, links, phase notes and approval, from the tracker (see Briefs)
53
53
  conduct wait <condition> wait for an outside event (see Waits)
54
+ conduct state [--repo <repo>] the worktree's branch, changes and PR, and its other repos' (see Worker verbs)
55
+ conduct branch <type> <slug> [--item <item>]
56
+ rename worktree-ATK-n to <type>/atk-n-<slug>
57
+ conduct checkout <owner>/<repo> <type> <slug> [--item <item>]
58
+ clone another GitHub repo into .scratch/repos/<repo> for the item
59
+ conduct pr create --title <title> --body-file <file> [--repo <repo>] [--item <item>] [--dry-run]
60
+ check, push and open the worker's PR (see Worker verbs)
54
61
  conduct train [<project>] [--dry-run]
55
62
  merge the waiting skill publishes into one registry PR (see Skill trains)
56
63
  conduct ports [<path>] [--prefer <slot>] [--json]
@@ -124,6 +131,13 @@ A brief is kept for 30 seconds. Anything the daemon sees happen to the item drop
124
131
  | 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) |
125
132
  | 1 | refused: no project tracks the item, or the tracker has no such item | reads the message |
126
133
 
134
+ ## Worker verbs
135
+
136
+ Protocol 2's verbs on git and the code host (providers design note, section 4), run in the item's worktree. They come beside the work-item helper's `state`, `rename`, `checkout` and `pr-create`, which stay until the cutover (ATK-457). Each prints one JSON object with `via`: `daemon` when the daemon wrote for the worker that owns the checkout, `local` when it ran in the `conduct` process (no daemon, or one without the route). Exit codes: 0 done, 1 refused or failed (the message says why), 5 a lost login (run `conduct wait login <provider>` and end the turn).
137
+
138
+ - `conduct state`, `conduct branch` and `conduct checkout` are git in the worktree, so they always run here. `state` reads the branch's PR as the project's account (see Accounts). `checkout` clones another GitHub repo into `.scratch/repos/<repo>`, keeps it out of the worktree's git status, and retire checks it for unpushed or uncommitted work (ATK-429).
139
+ - `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. `--repo` takes `github:<owner>/<repo>` (from its `.scratch/repos` checkout, into its default branch) or `ado:<repository>`; the profile's `codeHost` is the default.
140
+
127
141
  ## Inbox
128
142
 
129
143
  The `inbox` table holds everything that waits on Jason, across projects. Each item records:
@@ -456,8 +470,9 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
456
470
  - `POST /console/cache` with `{ "action": "warm" | "cold" | "failed", "contextTokens", "usage": { "cacheRead", "cacheWrite", "input", "output" } | null, "warms" }` or `{ "action": "clear", "contextTokens", "reason" }`: the mod's report on the idle console's prompt cache (see Mod); 201 with the `console.cache` event's id.
457
471
  - `GET /fleet`: the workers in flight, with session activity, phase, PR, waits and spend (today, total).
458
472
  - `POST /workers` with `{ "item": "ATK-309", "project"?, "probe"?, "prompt"?, "conductor"? }`: launch; answers 202 with the `launching` worker. `GET /workers/:id` shows it with its latest events.
459
- - `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work when it refuses.
473
+ - `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work (the worktree's, and each `.scratch/repos` checkout's under `checkouts`) when it refuses.
460
474
  - `POST /workers/:id/attach`: wake if parked, mark attached, open the tab.
475
+ - `POST /workers/:id/prs` with `{ "title", "body", "repo"? }`: open the worker's PR after the ownership checks (see Worker verbs); 201 with the PR, 409 refused, 503 a lost login (with `login.provider` and `login.command`), 502 the host failed.
461
476
  - `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"`.
462
477
  - `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.
463
478
  - `GET /inbox`: the open inbox items, oldest first.
@@ -0,0 +1,139 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { stripVTControlCharacters } from 'node:util';
4
+ import { ConductorError } from "./errors.js";
5
+ import { githubRepoRef } from "./lib/providers/index.js";
6
+ import { outputOf, run } from "./run.js";
7
+ // Git in a worker's checkouts, for the worker verbs and retire: the item's worktree and the other
8
+ // repos it cloned under .scratch/repos/<repo> (`conduct checkout`).
9
+ // Other repos a worker clones into its worktree (`conduct checkout`), each at
10
+ // <worktree>/.scratch/repos/<repo>.
11
+ export const REPOS_DIR = join('.scratch', 'repos');
12
+ export const runGit = (args, cwd) => run('git', args, { cwd, timeoutMs: 120_000 });
13
+ // The commits on HEAD that aren't on origin/<base>, and the ones on origin/<base> that aren't on
14
+ // HEAD, after fetching the base.
15
+ export async function aheadBehind(git, dir, base) {
16
+ await git(['fetch', '--quiet', 'origin', base], dir);
17
+ const count = async (range) => Number((await out(git, ['rev-list', '--count', range], dir)) ?? 0);
18
+ return { ahead: await count(`origin/${base}..HEAD`), behind: await count(`HEAD..origin/${base}`) };
19
+ }
20
+ // The branch a checkout is on now, or null (detached, or not a checkout).
21
+ export async function branchOf(git, dir) {
22
+ return (await out(git, ['branch', '--show-current'], dir)) || null;
23
+ }
24
+ // A checkout's base: its origin's default branch (refs/remotes/origin/HEAD, which a clone sets).
25
+ export async function checkoutBase(git, dir) {
26
+ const head = await out(git, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], dir);
27
+ if (!head)
28
+ throw new ConductorError(`${dir} has no origin/HEAD`, ['run git remote set-head origin --auto there']);
29
+ return head.replace(/^origin\//, '');
30
+ }
31
+ // commitlint over every commit the PR will carry, from the checkout's root where its config lives:
32
+ // 'passed', or 'skipped' without a commitlint config. Throws with its output when it fails.
33
+ export async function commitlint(dir, base) {
34
+ if (!hasCommitlint(dir))
35
+ return 'skipped (no commitlint config)';
36
+ // npx is a .cmd on Windows; its arguments are constants and the base branch's name.
37
+ const result = await run('npx', ['--no-install', 'commitlint', '--from', `origin/${base}`, '--to', 'HEAD'], {
38
+ cwd: dir,
39
+ shell: process.platform === 'win32',
40
+ timeoutMs: 120_000,
41
+ });
42
+ if (result.code === 0)
43
+ return 'passed';
44
+ const text = stripVTControlCharacters(`${result.stdout}\n${result.stderr}`);
45
+ // A clone that was never installed (`conduct checkout` doesn't) has the config but not the tool:
46
+ // its own CI lints the PR.
47
+ if (/could not determine executable|commitlint: (?:command )?not found|is not recognized/i.test(text)) {
48
+ return 'skipped (commitlint is not installed here)';
49
+ }
50
+ const said = text
51
+ .split(/\r?\n/)
52
+ .filter((line) => line.trim() && !line.startsWith('npm notice'))
53
+ .slice(-30);
54
+ throw new ConductorError(`commitlint failed over origin/${base}..HEAD`, said);
55
+ }
56
+ // Whether HEAD has commits its upstream hasn't (or it has no upstream yet).
57
+ export async function needsPush(git, dir) {
58
+ const upstream = await out(git, ['rev-parse', '--abbrev-ref', '@{upstream}'], dir);
59
+ if (!upstream)
60
+ return true;
61
+ return (await out(git, ['rev-list', '--count', `${upstream}..HEAD`], dir)) !== '0';
62
+ }
63
+ // The other repos checked out under the worktree's .scratch/repos/, with the repo each origin names.
64
+ export async function otherCheckouts(git, worktree) {
65
+ return Promise.all(scratchClones(worktree).map(async (dir) => ({
66
+ dir,
67
+ repo: githubRepoRef(await out(git, ['remote', 'get-url', 'origin'], dir)),
68
+ })));
69
+ }
70
+ // `git status --porcelain=v1 -z` as { code, path }. A rename or copy carries its source path as the
71
+ // next NUL-separated field.
72
+ export function parsePorcelain(text) {
73
+ const fields = text.split('\0');
74
+ const entries = [];
75
+ for (let i = 0; i < fields.length; i++) {
76
+ const field = fields[i] ?? '';
77
+ if (field.length < 4)
78
+ continue;
79
+ const code = field.slice(0, 2);
80
+ entries.push({ code, path: field.slice(3) });
81
+ if (code[0] === 'R' || code[0] === 'C')
82
+ i++;
83
+ }
84
+ return entries;
85
+ }
86
+ // Pushes the branch with an upstream, once more after a failure: a push has failed on a connection
87
+ // reset before.
88
+ export async function push(git, dir, branch, sleep = (ms) => new Promise((done) => setTimeout(done, ms))) {
89
+ const args = ['push', '--quiet', '-u', 'origin', branch];
90
+ const first = await git(args, dir);
91
+ if (first.code === 0)
92
+ return { attempts: 1 };
93
+ await sleep(5_000);
94
+ const second = await git(args, dir);
95
+ if (second.code === 0)
96
+ return { attempts: 2, firstError: outputOf(first, 300) };
97
+ throw new ConductorError(`git push of ${branch} failed twice`, [outputOf(second, 500)]);
98
+ }
99
+ // Uncommitted changes, without files whose only difference from HEAD is their line endings (a
100
+ // checkout with core.autocrlf can show those as modified).
101
+ export async function realDirty(git, dir) {
102
+ const status = await git(['status', '--porcelain=v1', '-z'], dir);
103
+ if (status.code !== 0)
104
+ throw new ConductorError(`git status failed in ${dir}`, [outputOf(status, 300)]);
105
+ const dirty = [];
106
+ const lineEndingOnly = [];
107
+ for (const entry of parsePorcelain(status.stdout)) {
108
+ const same = /^[ M][ M]$/.test(entry.code) &&
109
+ (await git(['diff', '--quiet', '--ignore-cr-at-eol', 'HEAD', '--', entry.path], dir)).code === 0;
110
+ (same ? lineEndingOnly : dirty).push(entry.path);
111
+ }
112
+ return { dirty, lineEndingOnly };
113
+ }
114
+ // The clones under the worktree's .scratch/repos/, by directory.
115
+ export function scratchClones(worktree) {
116
+ const root = join(worktree, REPOS_DIR);
117
+ if (!existsSync(root))
118
+ return [];
119
+ return readdirSync(root, { withFileTypes: true })
120
+ .filter((entry) => entry.isDirectory() && existsSync(join(root, entry.name, '.git')))
121
+ .map((entry) => join(root, entry.name));
122
+ }
123
+ function hasCommitlint(dir) {
124
+ const configured = readdirSync(dir).some((name) => /^(?:commitlint\.config\.[cm]?[jt]s|\.commitlintrc(?:\.(?:json|ya?ml|[cm]?[jt]s))?)$/.test(name));
125
+ if (configured)
126
+ return true;
127
+ try {
128
+ return 'commitlint' in JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
134
+ // A git command's output, trimmed, or null when it failed.
135
+ async function out(git, args, dir) {
136
+ const result = await git(args, dir);
137
+ return result.code === 0 ? result.stdout.trim() : null;
138
+ }
139
+ //# sourceMappingURL=checkout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"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"}
package/build/cli.js CHANGED
@@ -8,6 +8,7 @@ import { z } from 'zod';
8
8
  import { briefCommand } from "./brief-cli.js";
9
9
  import { heldLocks, lockCommand, lockLine, portsCommand } from "./broker-cli.js";
10
10
  import { callApi, NotRunningError, ping, readEndpoint } from "./client.js";
11
+ import { branchCommand, checkoutCommand, prCreateCommand, stateCommand } from "./code-host-cli.js";
11
12
  import { configSchema, loadConfig } from "./config.js";
12
13
  import { claimConsole, CONSOLE_ARGS, CONSOLE_WINDOW, consoleProject, consoleTabArgs, escalationLines, escalationSchema, handledSchema, } from "./console.js";
13
14
  import { AlreadyRunningError, logCrashes, startDaemon } from "./daemon.js";
@@ -63,15 +64,18 @@ const workerDetailBody = z.object({
63
64
  events: z.array(z.object({ data: z.record(z.string(), z.unknown()), kind: z.string() })),
64
65
  worker: workerSchema,
65
66
  });
67
+ const workSchema = z.object({
68
+ dirty: z.array(z.string()).optional(),
69
+ unknown: z.string().optional(),
70
+ unpushed: z.number().optional(),
71
+ });
66
72
  const retireBody = z.object({
67
73
  outcome: z.enum(['partial', 'refused', 'retired']),
68
74
  reason: z.string().optional(),
69
75
  steps: z
70
76
  .array(z.object({ error: z.string().optional(), note: z.string().optional(), ok: z.boolean(), step: z.string() }))
71
77
  .optional(),
72
- work: z
73
- .object({ dirty: z.array(z.string()).optional(), unknown: z.string().optional(), unpushed: z.number().optional() })
74
- .optional(),
78
+ work: workSchema.extend({ checkouts: z.record(z.string(), workSchema).optional() }).optional(),
75
79
  worker: workerSchema,
76
80
  });
77
81
  const attachBody = z.object({ resumed: z.boolean(), tab: z.string().nullable(), worker: workerSchema });
@@ -421,6 +425,7 @@ async function main(argv) {
421
425
  args: argv,
422
426
  options: {
423
427
  'allow-downgrade': { type: 'boolean' },
428
+ 'body-file': { type: 'string' },
424
429
  channel: { type: 'string' },
425
430
  conductor: { type: 'string' },
426
431
  console: { type: 'boolean' },
@@ -431,6 +436,7 @@ async function main(argv) {
431
436
  fresh: { type: 'boolean' },
432
437
  help: { short: 'h', type: 'boolean' },
433
438
  here: { type: 'boolean' },
439
+ item: { type: 'string' },
434
440
  json: { type: 'boolean' },
435
441
  kind: { type: 'string' },
436
442
  owner: { type: 'string' },
@@ -440,10 +446,12 @@ async function main(argv) {
440
446
  prompt: { type: 'string' },
441
447
  raise: { type: 'boolean' },
442
448
  remove: { type: 'boolean' },
449
+ repo: { type: 'string' },
443
450
  since: { type: 'string' },
444
451
  slots: { type: 'string' },
445
452
  tag: { type: 'string' },
446
453
  text: { type: 'string' },
454
+ title: { type: 'string' },
447
455
  until: { type: 'string' },
448
456
  version: { short: 'v', type: 'boolean' },
449
457
  wait: { type: 'string' },
@@ -452,7 +460,9 @@ async function main(argv) {
452
460
  },
453
461
  }));
454
462
  const paths = conductorPaths();
455
- const [command, sub, arg] = positionals;
463
+ const [command, sub, arg, fourth] = positionals;
464
+ const item = values.item === undefined ? {} : { item: values.item };
465
+ const repo = values.repo === undefined ? {} : { repo: values.repo };
456
466
  if (values.version)
457
467
  return out(version());
458
468
  if (command === undefined || command === 'help')
@@ -470,6 +480,11 @@ async function main(argv) {
470
480
  if (sub === undefined)
471
481
  throw usageError('attach');
472
482
  return attach(paths, sub);
483
+ case 'branch':
484
+ if (sub === undefined || arg === undefined)
485
+ throw usageError('branch');
486
+ process.exitCode = await branchCommand(sub, arg, item, verbIo());
487
+ return;
473
488
  case 'brief':
474
489
  if (sub === undefined)
475
490
  throw usageError('brief');
@@ -478,6 +493,11 @@ async function main(argv) {
478
493
  out,
479
494
  });
480
495
  return;
496
+ case 'checkout':
497
+ if (sub === undefined || arg === undefined || fourth === undefined)
498
+ throw usageError('checkout');
499
+ process.exitCode = await checkoutCommand(sub, arg, fourth, item, verbIo());
500
+ return;
481
501
  case 'console':
482
502
  return consoleCommand(paths, {
483
503
  force: values.force === true,
@@ -496,6 +516,16 @@ async function main(argv) {
496
516
  // CONDUCTOR_CONSOLE as well: a console still running a mod from before --console (until
497
517
  // /reload-plugins after an upgrade) starts this package's new build, in the console's env.
498
518
  return events(paths, values.follow === true, values.since, values.console === true || process.env.CONDUCTOR_CONSOLE === '1');
519
+ case 'focus':
520
+ if (sub === undefined || values.until === undefined)
521
+ throw usageError('focus');
522
+ return override(paths, {
523
+ action: 'focus',
524
+ project: sub,
525
+ ...(values.slots === undefined ? {} : { slots: wholeNumber('--slots', values.slots) }),
526
+ until: untilOf(values.until),
527
+ ...(values.weight === undefined ? {} : { weight: wholeNumber('--weight', values.weight) }),
528
+ });
499
529
  case 'history':
500
530
  case 'retro':
501
531
  return history(paths, command, sub, {
@@ -547,22 +577,11 @@ async function main(argv) {
547
577
  if (sub === 'test')
548
578
  return notifyTest(paths, { channel: values.channel, kind: values.kind, raise: values.raise === true });
549
579
  throw usageError('notify');
550
- case 'project':
551
- if (sub === 'add' && arg !== undefined)
552
- return addProject(paths, arg);
553
- if (sub === 'list')
554
- return listProjects(paths, values.json === true);
555
- throw usageError('project');
556
- case 'focus':
557
- if (sub === undefined || values.until === undefined)
558
- throw usageError('focus');
559
- return override(paths, {
560
- action: 'focus',
561
- project: sub,
562
- ...(values.slots === undefined ? {} : { slots: wholeNumber('--slots', values.slots) }),
563
- until: untilOf(values.until),
564
- ...(values.weight === undefined ? {} : { weight: wholeNumber('--weight', values.weight) }),
565
- });
580
+ case 'pr':
581
+ if (sub !== 'create' || values.title === undefined || values['body-file'] === undefined)
582
+ throw usageError('pr');
583
+ process.exitCode = await prCreateCommand(paths, { bodyFile: values['body-file'], dryRun: values['dry-run'] === true, title: values.title, ...item, ...repo }, verbIo());
584
+ return;
566
585
  case 'pause':
567
586
  return override(paths, {
568
587
  action: 'pause',
@@ -575,6 +594,12 @@ async function main(argv) {
575
594
  ...(values.prefer === undefined ? {} : { prefer: wholeNumber('--prefer', values.prefer) }),
576
595
  }, brokerIo());
577
596
  return;
597
+ case 'project':
598
+ if (sub === 'add' && arg !== undefined)
599
+ return addProject(paths, arg);
600
+ if (sub === 'list')
601
+ return listProjects(paths, values.json === true);
602
+ throw usageError('project');
578
603
  case 'queue':
579
604
  return queue(paths, values.json === true, values.fresh === true);
580
605
  case 'rules':
@@ -591,6 +616,11 @@ async function main(argv) {
591
616
  return serve(paths);
592
617
  case 'start':
593
618
  return start(paths);
619
+ case 'state':
620
+ if (sub !== undefined)
621
+ throw usageError('state');
622
+ process.exitCode = await stateCommand(paths, repo, verbIo());
623
+ return;
594
624
  case 'status':
595
625
  return status(paths, values.json === true);
596
626
  case 'stop':
@@ -814,10 +844,14 @@ async function retire(paths, ref, force) {
814
844
  const result = parsed.data;
815
845
  const steps = (result.steps ?? []).map((s) => `${s.step}: ${s.ok ? `ok${s.note ? ` (${s.note})` : ''}` : `failed (${s.error ?? '?'})`}`);
816
846
  if (result.outcome === 'refused') {
847
+ const lines = (w, where = '') => [
848
+ ...(w?.unpushed ? [`${where}${w.unpushed} unpushed commit(s)`] : []),
849
+ ...(w?.dirty ? [`${where}uncommitted: ${w.dirty.join(', ')}`] : []),
850
+ ...(w?.unknown ? [`${where}not checked: ${w.unknown}`] : []),
851
+ ];
817
852
  const work = [
818
- ...(result.work?.unpushed ? [`${result.work.unpushed} unpushed commit(s)`] : []),
819
- ...(result.work?.dirty ? [`uncommitted: ${result.work.dirty.join(', ')}`] : []),
820
- ...(result.work?.unknown ? [`not checked: ${result.work.unknown}`] : []),
853
+ ...lines(result.work),
854
+ ...Object.entries(result.work?.checkouts ?? {}).flatMap(([name, w]) => lines(w, `.scratch/repos/${name}: `)),
821
855
  ];
822
856
  throw new ConductorError(`refused to retire ${worker.name}: ${result.reason ?? ''}`, work);
823
857
  }
@@ -1146,6 +1180,9 @@ async function upgradeCommand(paths, tag, allowDowngrade) {
1146
1180
  stopDaemon: () => stopDaemon(paths),
1147
1181
  });
1148
1182
  }
1183
+ function verbIo() {
1184
+ return { cwd: process.cwd(), err: (line) => process.stderr.write(`${line}\n`), out };
1185
+ }
1149
1186
  // Read from disk each time: upgrade asks again after npm has replaced the package.
1150
1187
  function version() {
1151
1188
  const file = fileURLToPath(new URL('../package.json', import.meta.url));