@ianwremmel/dispatch 0.32.1-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +59 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/agents/.gitkeep +0 -0
- package/agents/build-graph.md +99 -0
- package/agents/milestone-reviewer.md +50 -0
- package/agents/pr-worker.md +172 -0
- package/agents/ticket-worker.md +97 -0
- package/bin/dispatch +101 -0
- package/bin/dispatch-mcp +19 -0
- package/bin/pr-status +931 -0
- package/commands/.gitkeep +0 -0
- package/commands/orchestrate.md +6 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/claim-guard.mts +98 -0
- package/hooks/hooks.json +15 -0
- package/package.json +46 -0
- package/skills/.gitkeep +0 -0
- package/skills/land/SKILL.md +238 -0
- package/skills/land/credentials-dedicated.md +33 -0
- package/skills/land/credentials-shared.md +76 -0
- package/skills/land/mode-solo.md +76 -0
- package/skills/land/mode-team.md +103 -0
- package/skills/land/reference.md +152 -0
- package/skills/land/ticket.md +94 -0
- package/skills/orchestrate/SKILL.md +87 -0
- package/skills/tracker-adapter-linear/SKILL.md +142 -0
- package/src/commands/CLAUDE.md +12 -0
- package/src/commands/claim/check.mts +88 -0
- package/src/commands/claim/guard.mts +95 -0
- package/src/commands/claim/status.mts +49 -0
- package/src/commands/edge/add.mts +44 -0
- package/src/commands/edge/rm.mts +44 -0
- package/src/commands/edge/set.mts +56 -0
- package/src/commands/greet.mts +34 -0
- package/src/commands/mcp/ack.mts +43 -0
- package/src/commands/mcp/ping.mts +61 -0
- package/src/commands/mcp/status.mts +89 -0
- package/src/commands/mcp.mts +155 -0
- package/src/commands/milestone/rm.mts +35 -0
- package/src/commands/milestone/set.mts +49 -0
- package/src/commands/outcome/rm.mts +36 -0
- package/src/commands/outcome/set.mts +86 -0
- package/src/commands/pr/rm.mts +33 -0
- package/src/commands/pr/set.mts +110 -0
- package/src/commands/pr/yield.mts +114 -0
- package/src/commands/project/rm.mts +33 -0
- package/src/commands/project/set.mts +50 -0
- package/src/commands/queue.mts +41 -0
- package/src/commands/refresh/done.mts +42 -0
- package/src/commands/refresh/status.mts +40 -0
- package/src/commands/refresh.mts +56 -0
- package/src/commands/review/record.mts +46 -0
- package/src/commands/review/release.mts +49 -0
- package/src/commands/status.mts +85 -0
- package/src/commands/ticket/missing.mts +31 -0
- package/src/commands/ticket/rm.mts +33 -0
- package/src/commands/ticket/set.mts +134 -0
- package/src/commands/worker/rm.mts +46 -0
- package/src/commands/worker/set.mts +63 -0
- package/src/lib/cli/CLAUDE.md +13 -0
- package/src/lib/cli/cli.mts +226 -0
- package/src/lib/cli/index.mts +1 -0
- package/src/lib/command/CLAUDE.md +26 -0
- package/src/lib/command/__fixtures__/bad-export/oops.mts +1 -0
- package/src/lib/command/__fixtures__/bad-name/mismatch.mts +19 -0
- package/src/lib/command/__fixtures__/commands/cli-only.mts +20 -0
- package/src/lib/command/__fixtures__/commands/greet.mts +39 -0
- package/src/lib/command/__fixtures__/commands/math/add.mts +32 -0
- package/src/lib/command/__fixtures__/commands/mcp-only.mts +20 -0
- package/src/lib/command/__fixtures__/commands/needs-token.mts +19 -0
- package/src/lib/command/__fixtures__/commands/store/get.mts +26 -0
- package/src/lib/command/__fixtures__/commands/store.mts +26 -0
- package/src/lib/command/abstract-command.mts +104 -0
- package/src/lib/command/discovery.mts +100 -0
- package/src/lib/command/env.mts +19 -0
- package/src/lib/command/index.mts +6 -0
- package/src/lib/command/parse.mts +64 -0
- package/src/lib/command/test-support.mts +81 -0
- package/src/lib/command/transports.mts +17 -0
- package/src/lib/command/types.mts +53 -0
- package/src/lib/db/CLAUDE.md +13 -0
- package/src/lib/db/database.mts +160 -0
- package/src/lib/db/index.mts +4 -0
- package/src/lib/db/schema.mts +195 -0
- package/src/lib/db/time.mts +24 -0
- package/src/lib/db/with-database.mts +56 -0
- package/src/lib/errors/CLAUDE.md +18 -0
- package/src/lib/errors/command-error.mts +13 -0
- package/src/lib/errors/data-error.mts +12 -0
- package/src/lib/errors/definition-error.mts +6 -0
- package/src/lib/errors/dispatch-error.mts +27 -0
- package/src/lib/errors/ensure.mts +22 -0
- package/src/lib/errors/environment-error.mts +7 -0
- package/src/lib/errors/index.mts +8 -0
- package/src/lib/errors/json-rpc-error.mts +18 -0
- package/src/lib/errors/usage-error.mts +7 -0
- package/src/lib/graph/CLAUDE.md +17 -0
- package/src/lib/graph/anomalies.mts +110 -0
- package/src/lib/graph/derive.mts +96 -0
- package/src/lib/graph/index.mts +26 -0
- package/src/lib/graph/pipeline.mts +410 -0
- package/src/lib/graph/queries.mts +207 -0
- package/src/lib/graph/rows.mts +99 -0
- package/src/lib/graph/types.mts +137 -0
- package/src/lib/liveness/CLAUDE.md +14 -0
- package/src/lib/liveness/index.mts +10 -0
- package/src/lib/liveness/liveness.mts +147 -0
- package/src/lib/liveness/retire.mts +63 -0
- package/src/lib/logger/CLAUDE.md +12 -0
- package/src/lib/logger/index.mts +2 -0
- package/src/lib/logger/logger.mts +58 -0
- package/src/lib/logger/stream-sink.mts +23 -0
- package/src/lib/mcp/CLAUDE.md +21 -0
- package/src/lib/mcp/channel.mts +41 -0
- package/src/lib/mcp/dispatch.mts +60 -0
- package/src/lib/mcp/drain.mts +83 -0
- package/src/lib/mcp/index.mts +5 -0
- package/src/lib/mcp/mcp.mts +267 -0
- package/src/lib/mcp/tools.mts +77 -0
- package/src/lib/model/CLAUDE.md +8 -0
- package/src/lib/model/index.mts +3 -0
- package/src/lib/model/repo-caps.mts +95 -0
- package/src/lib/model/status.mts +91 -0
- package/src/lib/model/types.mts +83 -0
- package/src/lib/refresh/index.mts +2 -0
- package/src/lib/refresh/placeholders.mts +43 -0
- package/src/lib/refresh/refresh-service.mts +203 -0
- package/src/lib/schedule/CLAUDE.md +18 -0
- package/src/lib/schedule/caps.mts +113 -0
- package/src/lib/schedule/correlate.mts +69 -0
- package/src/lib/schedule/index.mts +7 -0
- package/src/lib/schedule/scheduler.mts +355 -0
- package/src/lib/schedule/tick.mts +266 -0
- package/src/lib/stores/CLAUDE.md +24 -0
- package/src/lib/stores/coordination.mts +359 -0
- package/src/lib/stores/cursor.mts +41 -0
- package/src/lib/stores/edge.mts +138 -0
- package/src/lib/stores/fetch-request.mts +346 -0
- package/src/lib/stores/index.mts +19 -0
- package/src/lib/stores/materialize.mts +69 -0
- package/src/lib/stores/milestone.mts +74 -0
- package/src/lib/stores/notice.mts +57 -0
- package/src/lib/stores/policy.mts +48 -0
- package/src/lib/stores/pr-event.mts +94 -0
- package/src/lib/stores/pr.mts +167 -0
- package/src/lib/stores/project.mts +79 -0
- package/src/lib/stores/refresh.mts +197 -0
- package/src/lib/stores/review.mts +113 -0
- package/src/lib/stores/session.mts +170 -0
- package/src/lib/stores/ticket.mts +246 -0
- package/src/lib/stores/watch.mts +360 -0
- package/src/lib/stores/worker.mts +121 -0
- package/src/lib/watch/adopt.mts +151 -0
- package/src/lib/watch/arm.mts +48 -0
- package/src/lib/watch/cadence.mts +45 -0
- package/src/lib/watch/diff.mts +274 -0
- package/src/lib/watch/index.mts +11 -0
- package/src/lib/watch/marker.mts +24 -0
- package/src/lib/watch/payload.mts +56 -0
- package/src/lib/watch/poll.mts +87 -0
- package/src/lib/watch/render.mts +61 -0
- package/src/lib/watch/snapshot.mts +312 -0
- package/src/main.mts +18 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import {execFile} from 'node:child_process';
|
|
2
|
+
import {promisify} from 'node:util';
|
|
3
|
+
|
|
4
|
+
import {DataError, EnvironmentError, ensure} from '../errors/index.mts';
|
|
5
|
+
import {writtenByThisAgent} from './marker.mts';
|
|
6
|
+
|
|
7
|
+
const run = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Everything one poll needs to decide what changed and say so specifically.
|
|
11
|
+
*
|
|
12
|
+
* `updatedAt` is deliberately absent: it moves on every write, including the
|
|
13
|
+
* agent's own, so a snapshot carrying it would report a change every time the
|
|
14
|
+
* worker touched its own PR. Each field here is instead something a waiting
|
|
15
|
+
* worker would act on.
|
|
16
|
+
*/
|
|
17
|
+
export const SNAPSHOT_QUERY = `query($owner: String!, $name: String!, $number: Int!) {
|
|
18
|
+
repository(owner: $owner, name: $name) {
|
|
19
|
+
pullRequest(number: $number) {
|
|
20
|
+
headRefOid
|
|
21
|
+
state
|
|
22
|
+
isDraft
|
|
23
|
+
merged
|
|
24
|
+
mergeable
|
|
25
|
+
mergeStateStatus
|
|
26
|
+
reviewDecision
|
|
27
|
+
reviews(last: 20) {
|
|
28
|
+
totalCount
|
|
29
|
+
nodes { author { login } state submittedAt body }
|
|
30
|
+
}
|
|
31
|
+
reviewThreads(last: 50) {
|
|
32
|
+
totalCount
|
|
33
|
+
nodes {
|
|
34
|
+
id
|
|
35
|
+
isResolved
|
|
36
|
+
isOutdated
|
|
37
|
+
comments(last: 1) { nodes { author { login } createdAt body } }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
comments(last: 30) {
|
|
41
|
+
totalCount
|
|
42
|
+
nodes { id author { login } createdAt body }
|
|
43
|
+
}
|
|
44
|
+
commits(last: 1) {
|
|
45
|
+
nodes {
|
|
46
|
+
commit {
|
|
47
|
+
statusCheckRollup {
|
|
48
|
+
state
|
|
49
|
+
contexts(last: 50) {
|
|
50
|
+
nodes {
|
|
51
|
+
__typename
|
|
52
|
+
... on CheckRun {
|
|
53
|
+
name
|
|
54
|
+
conclusion
|
|
55
|
+
status
|
|
56
|
+
detailsUrl
|
|
57
|
+
}
|
|
58
|
+
... on StatusContext {
|
|
59
|
+
context
|
|
60
|
+
state
|
|
61
|
+
targetUrl
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}`;
|
|
72
|
+
|
|
73
|
+
export interface CheckState {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
/** `null` while the check is still running. */
|
|
76
|
+
readonly conclusion: string | null;
|
|
77
|
+
readonly url: string | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ReviewState {
|
|
81
|
+
readonly author: string;
|
|
82
|
+
readonly state: string;
|
|
83
|
+
readonly submittedAt: string | null;
|
|
84
|
+
/** Carries this agent's machine marker; see `marker.mts`. */
|
|
85
|
+
readonly mine: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ThreadState {
|
|
89
|
+
readonly id: string;
|
|
90
|
+
readonly resolved: boolean;
|
|
91
|
+
readonly outdated: boolean;
|
|
92
|
+
readonly lastAuthor: string | null;
|
|
93
|
+
readonly lastAt: string | null;
|
|
94
|
+
readonly mine: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CommentState {
|
|
98
|
+
readonly id: string;
|
|
99
|
+
readonly author: string;
|
|
100
|
+
readonly createdAt: string;
|
|
101
|
+
readonly mine: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface PrSnapshot {
|
|
105
|
+
readonly head: string | null;
|
|
106
|
+
readonly state: string | null;
|
|
107
|
+
readonly draft: boolean;
|
|
108
|
+
readonly merged: boolean;
|
|
109
|
+
readonly mergeable: string | null;
|
|
110
|
+
readonly mergeState: string | null;
|
|
111
|
+
readonly reviewDecision: string | null;
|
|
112
|
+
readonly rollup: string | null;
|
|
113
|
+
readonly checks: readonly CheckState[];
|
|
114
|
+
readonly reviews: readonly ReviewState[];
|
|
115
|
+
readonly threads: readonly ThreadState[];
|
|
116
|
+
readonly comments: readonly CommentState[];
|
|
117
|
+
/**
|
|
118
|
+
* Totals as the forge reports them, against the windows the query asks for.
|
|
119
|
+
* A total past its window means the snapshot is missing older entries, and
|
|
120
|
+
* the diff must not read "absent from my window" as "deleted".
|
|
121
|
+
*/
|
|
122
|
+
readonly totals: {
|
|
123
|
+
readonly reviews: number;
|
|
124
|
+
readonly threads: number;
|
|
125
|
+
readonly comments: number;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface RawContext {
|
|
130
|
+
__typename?: string;
|
|
131
|
+
name?: string;
|
|
132
|
+
conclusion?: string | null;
|
|
133
|
+
status?: string | null;
|
|
134
|
+
detailsUrl?: string | null;
|
|
135
|
+
context?: string;
|
|
136
|
+
state?: string | null;
|
|
137
|
+
targetUrl?: string | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface RawPr {
|
|
141
|
+
headRefOid?: string;
|
|
142
|
+
state?: string;
|
|
143
|
+
isDraft?: boolean;
|
|
144
|
+
merged?: boolean;
|
|
145
|
+
mergeable?: string | null;
|
|
146
|
+
mergeStateStatus?: string | null;
|
|
147
|
+
reviewDecision?: string | null;
|
|
148
|
+
reviews?: {
|
|
149
|
+
totalCount?: number;
|
|
150
|
+
nodes?: {
|
|
151
|
+
author?: {login?: string} | null;
|
|
152
|
+
state?: string;
|
|
153
|
+
submittedAt?: string | null;
|
|
154
|
+
body?: string;
|
|
155
|
+
}[];
|
|
156
|
+
};
|
|
157
|
+
reviewThreads?: {
|
|
158
|
+
totalCount?: number;
|
|
159
|
+
nodes?: {
|
|
160
|
+
id?: string;
|
|
161
|
+
isResolved?: boolean;
|
|
162
|
+
isOutdated?: boolean;
|
|
163
|
+
comments?: {
|
|
164
|
+
nodes?: {
|
|
165
|
+
author?: {login?: string} | null;
|
|
166
|
+
createdAt?: string;
|
|
167
|
+
body?: string;
|
|
168
|
+
}[];
|
|
169
|
+
};
|
|
170
|
+
}[];
|
|
171
|
+
};
|
|
172
|
+
comments?: {
|
|
173
|
+
totalCount?: number;
|
|
174
|
+
nodes?: {
|
|
175
|
+
id?: string;
|
|
176
|
+
author?: {login?: string} | null;
|
|
177
|
+
createdAt?: string;
|
|
178
|
+
body?: string;
|
|
179
|
+
}[];
|
|
180
|
+
};
|
|
181
|
+
commits?: {
|
|
182
|
+
nodes?: {
|
|
183
|
+
commit?: {
|
|
184
|
+
statusCheckRollup?: {
|
|
185
|
+
state?: string;
|
|
186
|
+
contexts?: {nodes?: RawContext[]};
|
|
187
|
+
} | null;
|
|
188
|
+
};
|
|
189
|
+
}[];
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A status context reports `state`; a check run reports `conclusion` once it
|
|
195
|
+
* finishes and `status` while it runs. Normalizing both onto "conclusion or
|
|
196
|
+
* null-while-running" is what lets the diff talk about checks without caring
|
|
197
|
+
* which kind produced them.
|
|
198
|
+
*/
|
|
199
|
+
function toCheck(context: RawContext): CheckState | null {
|
|
200
|
+
if (typeof context.name === 'string') {
|
|
201
|
+
const running = context.status !== 'COMPLETED';
|
|
202
|
+
return {
|
|
203
|
+
name: context.name,
|
|
204
|
+
conclusion: running ? null : (context.conclusion ?? null),
|
|
205
|
+
url: context.detailsUrl ?? null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (typeof context.context === 'string') {
|
|
209
|
+
const state = context.state ?? null;
|
|
210
|
+
return {
|
|
211
|
+
name: context.context,
|
|
212
|
+
conclusion: state === 'PENDING' || state === null ? null : state,
|
|
213
|
+
url: context.targetUrl ?? null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parse(pr: RawPr): PrSnapshot {
|
|
220
|
+
const rollupNode = pr.commits?.nodes?.[0]?.commit?.statusCheckRollup ?? null;
|
|
221
|
+
return {
|
|
222
|
+
head: pr.headRefOid ?? null,
|
|
223
|
+
state: pr.state ?? null,
|
|
224
|
+
draft: pr.isDraft === true,
|
|
225
|
+
merged: pr.merged === true,
|
|
226
|
+
mergeable: pr.mergeable ?? null,
|
|
227
|
+
mergeState: pr.mergeStateStatus ?? null,
|
|
228
|
+
reviewDecision: pr.reviewDecision ?? null,
|
|
229
|
+
rollup: rollupNode?.state ?? null,
|
|
230
|
+
checks: (rollupNode?.contexts?.nodes ?? [])
|
|
231
|
+
.map(toCheck)
|
|
232
|
+
.filter((check): check is CheckState => check !== null)
|
|
233
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
234
|
+
reviews: (pr.reviews?.nodes ?? []).map((review) => ({
|
|
235
|
+
author: review.author?.login ?? '',
|
|
236
|
+
state: review.state ?? '',
|
|
237
|
+
submittedAt: review.submittedAt ?? null,
|
|
238
|
+
mine: writtenByThisAgent(review.body),
|
|
239
|
+
})),
|
|
240
|
+
threads: (pr.reviewThreads?.nodes ?? []).map((thread) => {
|
|
241
|
+
const last = thread.comments?.nodes?.at(-1);
|
|
242
|
+
return {
|
|
243
|
+
id: thread.id ?? '',
|
|
244
|
+
resolved: thread.isResolved === true,
|
|
245
|
+
outdated: thread.isOutdated === true,
|
|
246
|
+
lastAuthor: last?.author?.login ?? null,
|
|
247
|
+
lastAt: last?.createdAt ?? null,
|
|
248
|
+
mine: writtenByThisAgent(last?.body),
|
|
249
|
+
};
|
|
250
|
+
}),
|
|
251
|
+
comments: (pr.comments?.nodes ?? []).map((comment) => ({
|
|
252
|
+
id: comment.id ?? '',
|
|
253
|
+
author: comment.author?.login ?? '',
|
|
254
|
+
createdAt: comment.createdAt ?? '',
|
|
255
|
+
mine: writtenByThisAgent(comment.body),
|
|
256
|
+
})),
|
|
257
|
+
totals: {
|
|
258
|
+
reviews: pr.reviews?.totalCount ?? 0,
|
|
259
|
+
threads: pr.reviewThreads?.totalCount ?? 0,
|
|
260
|
+
comments: pr.comments?.totalCount ?? 0,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export type Snapshotter = (
|
|
266
|
+
repo: string,
|
|
267
|
+
prNumber: number
|
|
268
|
+
) => Promise<PrSnapshot>;
|
|
269
|
+
|
|
270
|
+
/** Snapshot a PR through `gh api graphql`: one call per poll. */
|
|
271
|
+
export const githubSnapshot: Snapshotter = async (repo, prNumber) => {
|
|
272
|
+
ensure(
|
|
273
|
+
/^[^/\s]+\/[^/\s]+$/u.test(repo),
|
|
274
|
+
() =>
|
|
275
|
+
new DataError(`"${repo}" is not an owner/repo`, {
|
|
276
|
+
hint: 'record the PR item with --repo <owner>/<name> before watching it.',
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
const [owner, name] = repo.split('/') as [string, string];
|
|
280
|
+
let stdout: string;
|
|
281
|
+
try {
|
|
282
|
+
({stdout} = await run('gh', [
|
|
283
|
+
'api',
|
|
284
|
+
'graphql',
|
|
285
|
+
'-f',
|
|
286
|
+
`query=${SNAPSHOT_QUERY}`,
|
|
287
|
+
'-F',
|
|
288
|
+
`owner=${owner}`,
|
|
289
|
+
'-F',
|
|
290
|
+
`name=${name}`,
|
|
291
|
+
'-F',
|
|
292
|
+
`number=${String(prNumber)}`,
|
|
293
|
+
]));
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw new EnvironmentError(
|
|
296
|
+
`gh could not read ${repo}#${String(prNumber)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
297
|
+
{hint: 'check gh auth status and that the PR still exists.'}
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const parsed = JSON.parse(stdout) as {
|
|
301
|
+
data?: {repository?: {pullRequest?: RawPr | null} | null};
|
|
302
|
+
};
|
|
303
|
+
const pr = parsed.data?.repository?.pullRequest;
|
|
304
|
+
ensure(
|
|
305
|
+
pr !== undefined && pr !== null,
|
|
306
|
+
() =>
|
|
307
|
+
new DataError(`no pull request ${repo}#${String(prNumber)}`, {
|
|
308
|
+
hint: 'the PR item names a PR the forge does not have; fix its --repo/--pr-number.',
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
return parse(pr);
|
|
312
|
+
};
|
package/src/main.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {discover} from './lib/command/index.mts';
|
|
2
|
+
import {runCli} from './lib/cli/index.mts';
|
|
3
|
+
import {createLogger, streamSink} from './lib/logger/index.mts';
|
|
4
|
+
|
|
5
|
+
const tree = await discover(new URL('./commands/', import.meta.url));
|
|
6
|
+
|
|
7
|
+
// Diagnostics go to stderr on every path, because one of them is `dispatch mcp`:
|
|
8
|
+
// it serves JSON-RPC on stdout, and the default `console` sink would put
|
|
9
|
+
// `log`/`info`/`debug` into that stream. Command output rides `io`, not the
|
|
10
|
+
// logger, so nothing a caller reads moves.
|
|
11
|
+
process.exitCode = await runCli({
|
|
12
|
+
argv: process.argv.slice(2),
|
|
13
|
+
tree,
|
|
14
|
+
log: createLogger(streamSink(process.stderr)),
|
|
15
|
+
env: process.env,
|
|
16
|
+
stdout: process.stdout,
|
|
17
|
+
stderr: process.stderr,
|
|
18
|
+
});
|