omp-conductor 0.3.17 → 0.3.18
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/package.json +1 -1
- package/src/tracker/github.ts +40 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
package/src/tracker/github.ts
CHANGED
|
@@ -53,6 +53,12 @@ const CLOSERS_QUERY = `query($owner:String!,$repo:String!,$n:Int!){
|
|
|
53
53
|
}
|
|
54
54
|
}`;
|
|
55
55
|
|
|
56
|
+
const PARENT_QUERY = `query($owner:String!,$repo:String!,$n:Int!){
|
|
57
|
+
repository(owner:$owner,name:$repo){
|
|
58
|
+
issue(number:$n){ parent{ number } }
|
|
59
|
+
}
|
|
60
|
+
}`;
|
|
61
|
+
|
|
56
62
|
/** The `gh api graphql` envelope for {@link CLOSERS_QUERY}. Every level is
|
|
57
63
|
* nullable: a deleted or wrong-numbered issue answers `null`, not an error. */
|
|
58
64
|
interface ClosersResponse {
|
|
@@ -190,27 +196,30 @@ export function prStateFrom(raw: string): PrState | undefined {
|
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
/**
|
|
193
|
-
* Parent number from a
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* "no parent" and skip the epic soft-cap.
|
|
199
|
+
* Parent number from a raw GraphQL response, or undefined when the issue has
|
|
200
|
+
* no parent. Throws when the issue or claimed parent is malformed — admission
|
|
201
|
+
* must not turn an unknown relationship into permission to run siblings.
|
|
197
202
|
*/
|
|
198
203
|
export function parentNumberFrom(raw: string): number | undefined {
|
|
199
|
-
const parsed = JSON.parse(raw) as {
|
|
200
|
-
|
|
201
|
-
|
|
204
|
+
const parsed = JSON.parse(raw) as {
|
|
205
|
+
data?: { repository?: { issue?: { parent?: { number?: unknown } | null } | null } | null };
|
|
206
|
+
};
|
|
207
|
+
const issue = parsed.data?.repository?.issue;
|
|
208
|
+
if (issue == null) throw new Error("unexpected missing issue in parent response");
|
|
209
|
+
if (issue.parent == null) return undefined;
|
|
210
|
+
const n = issue.parent.number;
|
|
202
211
|
if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
|
|
203
212
|
throw new Error(`unexpected parent number ${JSON.stringify(n)}`);
|
|
204
213
|
}
|
|
205
214
|
return n;
|
|
206
215
|
}
|
|
207
216
|
|
|
208
|
-
export function makeTracker(p: ProjectConfig): Tracker {
|
|
217
|
+
export function makeTracker(p: ProjectConfig, runGh: typeof gh = gh): Tracker {
|
|
209
218
|
const repo = p.tracker.repo;
|
|
210
219
|
|
|
211
220
|
return {
|
|
212
221
|
async listReady(): Promise<ReadyIssue[]> {
|
|
213
|
-
const raw = await
|
|
222
|
+
const raw = await runGh([
|
|
214
223
|
"issue",
|
|
215
224
|
"list",
|
|
216
225
|
"--repo",
|
|
@@ -246,7 +255,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
246
255
|
|
|
247
256
|
async addLabel(issue: number, label: string): Promise<void> {
|
|
248
257
|
try {
|
|
249
|
-
await
|
|
258
|
+
await runGh(["issue", "edit", String(issue), "--repo", repo, "--add-label", label]);
|
|
250
259
|
} catch (err) {
|
|
251
260
|
if (!isLabelNoop(err, "add")) throw err;
|
|
252
261
|
}
|
|
@@ -254,7 +263,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
254
263
|
|
|
255
264
|
async removeLabel(issue: number, label: string): Promise<void> {
|
|
256
265
|
try {
|
|
257
|
-
await
|
|
266
|
+
await runGh(["issue", "edit", String(issue), "--repo", repo, "--remove-label", label]);
|
|
258
267
|
} catch (err) {
|
|
259
268
|
if (!isLabelNoop(err, "remove")) throw err;
|
|
260
269
|
}
|
|
@@ -263,25 +272,37 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
263
272
|
async comment(issue: number, body: string): Promise<void> {
|
|
264
273
|
// `--body-file -` reads stdin, so the body is never shell- or argv-
|
|
265
274
|
// mangled and has no length limit worth worrying about.
|
|
266
|
-
await
|
|
275
|
+
await runGh(["issue", "comment", String(issue), "--repo", repo, "--body-file", "-"], body);
|
|
267
276
|
},
|
|
268
277
|
|
|
269
278
|
async close(issue: number): Promise<void> {
|
|
270
|
-
await
|
|
279
|
+
await runGh(["issue", "close", String(issue), "--repo", repo]);
|
|
271
280
|
},
|
|
272
281
|
|
|
273
282
|
async linkParent(child: number, parent: number): Promise<void> {
|
|
274
283
|
// Native sub-issue linkage rather than a body mention: it is what the
|
|
275
284
|
// repo's own epic rollups read, so a human sees the split without us
|
|
276
285
|
// maintaining a second index of it.
|
|
277
|
-
await
|
|
286
|
+
await runGh(["issue", "edit", String(parent), "--repo", repo, "--add-sub-issue", String(child)]);
|
|
278
287
|
},
|
|
279
288
|
|
|
280
289
|
async parentOf(issue: number): Promise<number | undefined> {
|
|
281
|
-
//
|
|
282
|
-
//
|
|
290
|
+
// gh 2.86 cannot expose `parent` through `issue view --json`; its raw
|
|
291
|
+
// GraphQL command can, and is already the adapter's path for PR closers.
|
|
292
|
+
const [owner = "", name = ""] = repo.split("/");
|
|
283
293
|
return parentNumberFrom(
|
|
284
|
-
await
|
|
294
|
+
await runGh([
|
|
295
|
+
"api",
|
|
296
|
+
"graphql",
|
|
297
|
+
"-f",
|
|
298
|
+
`query=${PARENT_QUERY}`,
|
|
299
|
+
"-F",
|
|
300
|
+
`owner=${owner}`,
|
|
301
|
+
"-F",
|
|
302
|
+
`repo=${name}`,
|
|
303
|
+
"-F",
|
|
304
|
+
`n=${issue}`,
|
|
305
|
+
]),
|
|
285
306
|
);
|
|
286
307
|
},
|
|
287
308
|
|
|
@@ -290,7 +311,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
290
311
|
// that spelling, so an empty half means a hand-edited config: `gh` then
|
|
291
312
|
// errors and the caller holds the candidate rather than guessing.
|
|
292
313
|
const [owner = "", name = ""] = repo.split("/");
|
|
293
|
-
const raw = await
|
|
314
|
+
const raw = await runGh([
|
|
294
315
|
"api",
|
|
295
316
|
"graphql",
|
|
296
317
|
"-f",
|
|
@@ -316,7 +337,7 @@ export function makeTracker(p: ProjectConfig): Tracker {
|
|
|
316
337
|
// what the URL already says.
|
|
317
338
|
if (!PR_URL.test(url)) return undefined;
|
|
318
339
|
try {
|
|
319
|
-
return prStateFrom(await
|
|
340
|
+
return prStateFrom(await runGh(["pr", "view", url, "--json", "state", "--jq", ".state"]));
|
|
320
341
|
} catch {
|
|
321
342
|
// Never throws, per the port's contract. A deleted PR, a revoked token
|
|
322
343
|
// and a flaky network all mean "could not tell", and the caller's whole
|