@pourkit/cli 0.0.0-next-20260529095319

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.
@@ -0,0 +1,685 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../common/logger/src/index.ts
13
+ import { createWriteStream } from "fs";
14
+ import { mkdirSync } from "fs";
15
+ import path from "path";
16
+ import { styleText } from "util";
17
+ function createLogger(name, filePath) {
18
+ let fileStream;
19
+ if (filePath) {
20
+ mkdirSync(path.dirname(filePath), { recursive: true });
21
+ fileStream = createWriteStream(filePath, { flags: "a" });
22
+ }
23
+ const write = (terminal, plain = terminal) => {
24
+ process.stdout.write(`${terminal}
25
+ `);
26
+ if (fileStream) {
27
+ fileStream.write(`${plain}
28
+ `);
29
+ }
30
+ };
31
+ return {
32
+ line(msg) {
33
+ const ts = timestamp();
34
+ write(`${ts.terminal} ${msg}`, `${ts.plain} ${msg}`);
35
+ },
36
+ raw(msg) {
37
+ write(msg);
38
+ },
39
+ step(step, msg) {
40
+ const ts = timestamp();
41
+ write(
42
+ `${ts.terminal} ${formatStep(step)} ${formatStepMessage(step, msg)}`,
43
+ `${ts.plain} [${step}] ${msg}`
44
+ );
45
+ },
46
+ status(status) {
47
+ const ts = timestamp();
48
+ write(
49
+ `${ts.terminal} ${color(["bold", "cyan"], "POURKIT")} ${color("cyan", status)}`,
50
+ `${ts.plain} POURKIT ${status}`
51
+ );
52
+ },
53
+ kv(key, value) {
54
+ const ts = timestamp();
55
+ write(
56
+ `${ts.terminal} ${color("dim", key)}=${formatValue(key, value)}`,
57
+ `${ts.plain} ${key}=${value}`
58
+ );
59
+ },
60
+ async close() {
61
+ await new Promise((resolve) => {
62
+ if (!fileStream) {
63
+ resolve();
64
+ return;
65
+ }
66
+ const timer = setTimeout(() => {
67
+ if (!fileStream.destroyed) {
68
+ fileStream.destroy();
69
+ }
70
+ resolve();
71
+ }, 2e3);
72
+ fileStream.end(() => {
73
+ clearTimeout(timer);
74
+ resolve();
75
+ });
76
+ });
77
+ }
78
+ };
79
+ }
80
+ function timestamp() {
81
+ const now = /* @__PURE__ */ new Date();
82
+ const time = now.toTimeString().slice(0, 8);
83
+ const ms = String(now.getMilliseconds()).padStart(3, "0");
84
+ const plain = `${time}.${ms}`;
85
+ return { terminal: color("dim", plain), plain };
86
+ }
87
+ function formatStep(step) {
88
+ return color(stepStyle(step), `[${step}]`);
89
+ }
90
+ function formatStepMessage(step, msg) {
91
+ if (step === "error") {
92
+ return color("red", msg);
93
+ }
94
+ if (step === "warn") {
95
+ return color("yellow", msg);
96
+ }
97
+ return msg;
98
+ }
99
+ function formatValue(key, value) {
100
+ if (/SUCCESS|CREATED|COMMITS/.test(key)) {
101
+ return color("green", value);
102
+ }
103
+ if (/BRANCH|PATH|FILE|URL/.test(key)) {
104
+ return color("cyan", value);
105
+ }
106
+ return color("bold", value);
107
+ }
108
+ function stepStyle(step) {
109
+ switch (step) {
110
+ case "sandcastle":
111
+ return ["bold", "cyan"];
112
+ case "git":
113
+ return ["bold", "magenta"];
114
+ case "review":
115
+ case "reviewer":
116
+ return ["bold", "blue"];
117
+ case "cleanup":
118
+ return ["bold", "yellow"];
119
+ case "error":
120
+ return ["bold", "red"];
121
+ case "warn":
122
+ return ["bold", "yellow"];
123
+ case "info":
124
+ return "cyan";
125
+ default:
126
+ return "green";
127
+ }
128
+ }
129
+ function color(format, text) {
130
+ if (process.env.NO_COLOR) {
131
+ return text;
132
+ }
133
+ try {
134
+ return styleText(format, text);
135
+ } catch {
136
+ return text;
137
+ }
138
+ }
139
+ var init_src = __esm({
140
+ "../common/logger/src/index.ts"() {
141
+ "use strict";
142
+ }
143
+ });
144
+
145
+ // shared/common.ts
146
+ var common_exports = {};
147
+ __export(common_exports, {
148
+ TYPE_LABELS: () => TYPE_LABELS,
149
+ createLogger: () => createLogger,
150
+ ensureDir: () => ensureDir,
151
+ execCapture: () => execCapture,
152
+ execCaptureWithRetry: () => execCaptureWithRetry,
153
+ execJson: () => execJson,
154
+ execJsonWithRetry: () => execJsonWithRetry,
155
+ parseWorktreeListPorcelain: () => parseWorktreeListPorcelain,
156
+ readMaybeEnvInt: () => readMaybeEnvInt,
157
+ repoRelative: () => repoRelative,
158
+ repoRoot: () => repoRoot,
159
+ sleep: () => sleep,
160
+ slugify: () => slugify
161
+ });
162
+ import { mkdir } from "fs/promises";
163
+ import path2 from "path";
164
+ import { execFile, spawnSync } from "child_process";
165
+ import { promisify } from "util";
166
+ async function ensureDir(dir) {
167
+ await mkdir(dir, { recursive: true });
168
+ }
169
+ function repoRoot(explicitRoot = process.env.POURKIT_ROOT) {
170
+ if (explicitRoot?.trim()) {
171
+ const root = explicitRoot.trim();
172
+ const insideResult = spawnSync(
173
+ "git",
174
+ ["-C", root, "rev-parse", "--is-inside-work-tree"],
175
+ { encoding: "utf8" }
176
+ );
177
+ if (insideResult.status !== 0 || insideResult.stdout.trim() !== "true") {
178
+ throw new Error(
179
+ `POURKIT_ROOT is not a valid Git worktree: ${root}
180
+ ${insideResult.stderr || insideResult.stdout}`
181
+ );
182
+ }
183
+ const topLevelResult = spawnSync(
184
+ "git",
185
+ ["-C", root, "rev-parse", "--show-toplevel"],
186
+ { encoding: "utf8" }
187
+ );
188
+ if (topLevelResult.status !== 0) {
189
+ throw new Error(
190
+ `Failed to validate POURKIT_ROOT as a Git worktree: ${root}
191
+ ${topLevelResult.stderr || topLevelResult.stdout}`
192
+ );
193
+ }
194
+ return topLevelResult.stdout.trim();
195
+ }
196
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
197
+ encoding: "utf8"
198
+ });
199
+ if (result.status !== 0) {
200
+ throw new Error(
201
+ `Failed to resolve repo root: ${result.stderr || result.stdout}`
202
+ );
203
+ }
204
+ return result.stdout.trim();
205
+ }
206
+ function repoRelative(root, ...segments) {
207
+ return path2.join(root, ...segments);
208
+ }
209
+ function slugify(value) {
210
+ const slug = value.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 50);
211
+ return slug || "issue";
212
+ }
213
+ function formatCommand(command, args) {
214
+ return [command, ...args].map((part) => {
215
+ if (/^[A-Za-z0-9_\/.=:,@+-]+$/.test(part)) {
216
+ return part;
217
+ }
218
+ return `'${part.replace(/'/g, "'\\''")}'`;
219
+ }).join(" ");
220
+ }
221
+ function readMaybeEnvInt(value, fallback) {
222
+ const parsed = Number(value ?? fallback);
223
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
224
+ }
225
+ async function execCapture(command, args, options = {}) {
226
+ if (options.logger && options.label) {
227
+ options.logger.step(
228
+ options.label,
229
+ `running ${formatCommand(command, args)}`
230
+ );
231
+ }
232
+ let stdout = "";
233
+ let stderr = "";
234
+ let code = 0;
235
+ try {
236
+ const result = await execFileAsync(command, args, {
237
+ cwd: options.cwd,
238
+ env: options.env,
239
+ encoding: "utf8",
240
+ maxBuffer: 20 * 1024 * 1024
241
+ });
242
+ stdout = typeof result.stdout === "string" ? result.stdout : String(result.stdout ?? "");
243
+ stderr = typeof result.stderr === "string" ? result.stderr : String(result.stderr ?? "");
244
+ } catch (error) {
245
+ const err = error;
246
+ stdout = typeof err.stdout === "string" ? err.stdout : "";
247
+ stderr = typeof err.stderr === "string" ? err.stderr : "";
248
+ code = typeof err.code === "number" ? err.code : 1;
249
+ }
250
+ if (code !== 0) {
251
+ throw new Error(
252
+ [
253
+ `command failed: ${formatCommand(command, args)}`,
254
+ `exit code: ${code}`,
255
+ stdout ? `stdout:
256
+ ${stdout}` : "",
257
+ stderr ? `stderr:
258
+ ${stderr}` : ""
259
+ ].filter(Boolean).join("\n")
260
+ );
261
+ }
262
+ return { code, stdout, stderr };
263
+ }
264
+ async function execJson(command, args, options = {}) {
265
+ const result = await execCapture(command, args, options);
266
+ return JSON.parse(result.stdout);
267
+ }
268
+ function sleep(ms) {
269
+ return new Promise((resolve) => setTimeout(resolve, ms));
270
+ }
271
+ async function execCaptureWithRetry(command, args, options = {}) {
272
+ const retries = options.retries ?? 3;
273
+ const backoffMs = options.backoffMs ?? 2e3;
274
+ let lastError = null;
275
+ for (let attempt = 1; attempt <= retries; attempt++) {
276
+ try {
277
+ return await execCapture(command, args, options);
278
+ } catch (error) {
279
+ lastError = error instanceof Error ? error : new Error(String(error));
280
+ if (!TRANSIENT_GH_ERROR.test(lastError.message)) {
281
+ throw lastError;
282
+ }
283
+ if (options.logger) {
284
+ options.logger.step(
285
+ options.label ?? command,
286
+ `transient failure (attempt ${attempt}/${retries}), retrying`
287
+ );
288
+ }
289
+ if (attempt < retries) {
290
+ await sleep(backoffMs * Math.pow(2, attempt - 1));
291
+ }
292
+ }
293
+ }
294
+ throw lastError;
295
+ }
296
+ async function execJsonWithRetry(command, args, options = {}) {
297
+ const result = await execCaptureWithRetry(command, args, options);
298
+ return JSON.parse(result.stdout);
299
+ }
300
+ function parseWorktreeListPorcelain(text, branch) {
301
+ const entries = text.trim().split("\n\n");
302
+ for (const entry of entries) {
303
+ const lines = entry.trim().split("\n");
304
+ let path3 = "";
305
+ let entryBranch = "";
306
+ for (const line of lines) {
307
+ if (line.startsWith("worktree ")) {
308
+ path3 = line.slice("worktree ".length);
309
+ } else if (line.startsWith("branch refs/heads/")) {
310
+ entryBranch = line.slice("branch refs/heads/".length);
311
+ }
312
+ }
313
+ if (entryBranch === branch && path3) {
314
+ return path3;
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+ var execFileAsync, TRANSIENT_GH_ERROR, TYPE_LABELS;
320
+ var init_common = __esm({
321
+ "shared/common.ts"() {
322
+ "use strict";
323
+ init_src();
324
+ execFileAsync = promisify(execFile);
325
+ TRANSIENT_GH_ERROR = /HTTP (502|503|504)\b|Could not close the issue|GraphQL:.*closeIssue/;
326
+ TYPE_LABELS = [
327
+ "type:bugfix",
328
+ "type:infra",
329
+ "type:feature",
330
+ "type:polish",
331
+ "type:refactor"
332
+ ];
333
+ }
334
+ });
335
+
336
+ // issues/unblock.ts
337
+ init_common();
338
+ init_common();
339
+ import { fileURLToPath } from "url";
340
+
341
+ // issues/blocked-issue.ts
342
+ function parseBlockedBy(body) {
343
+ if (!body) return [];
344
+ const bm = body.match(/## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i);
345
+ if (!bm) return [];
346
+ const refs = [];
347
+ const re = /#(\d+)/g;
348
+ let m;
349
+ while ((m = re.exec(bm[1])) !== null) {
350
+ refs.push(Number(m[1]));
351
+ }
352
+ return refs;
353
+ }
354
+ async function reconcileBlockedIssue(issue, deps) {
355
+ const blockers = parseBlockedBy(issue.body);
356
+ if (blockers.length === 0) {
357
+ await deps.transitions.moveToNeedsTriage(issue.number);
358
+ return "needs-triage";
359
+ }
360
+ const stillBlocked = await anyBlockerStillOpen(blockers, deps.getIssueState);
361
+ if (stillBlocked) {
362
+ return "still-blocked";
363
+ }
364
+ const labels = issue.labels ?? [];
365
+ const typeLabels = labels.filter((l) => deps.typeLabels.includes(l.name));
366
+ if (typeLabels.length === 1) {
367
+ await deps.transitions.removeBlocked(issue.number);
368
+ const alreadyReady = labels.some((l) => l.name === deps.readyLabel);
369
+ if (!alreadyReady) {
370
+ await deps.transitions.addReadyForAgent(issue.number);
371
+ }
372
+ return "unblocked";
373
+ }
374
+ await deps.transitions.moveToNeedsTriage(issue.number);
375
+ return "needs-triage";
376
+ }
377
+ async function reconcileBlockedIssues(issues, deps) {
378
+ const results = [];
379
+ for (const issue of issues) {
380
+ const result = await reconcileBlockedIssue(issue, deps);
381
+ results.push({ issueNumber: issue.number, result });
382
+ }
383
+ return results;
384
+ }
385
+ async function anyBlockerStillOpen(refs, getIssueState) {
386
+ for (const ref of refs) {
387
+ const state = await getIssueState(ref);
388
+ if (state !== "CLOSED") {
389
+ return true;
390
+ }
391
+ }
392
+ return false;
393
+ }
394
+
395
+ // issues/issue-transitions.ts
396
+ function createIssueTransitions(deps, labels) {
397
+ return {
398
+ async removeBlocked(issueNumber) {
399
+ await deps.removeLabel(issueNumber, labels.blocked);
400
+ },
401
+ async addReadyForAgent(issueNumber) {
402
+ const issue = await deps.fetchIssue(issueNumber);
403
+ if (!issue.labels.includes(labels.readyForAgent)) {
404
+ await deps.addLabels(issueNumber, [labels.readyForAgent]);
405
+ }
406
+ },
407
+ async moveToNeedsTriage(issueNumber) {
408
+ if (deps.updateLabels) {
409
+ await deps.updateLabels(
410
+ issueNumber,
411
+ [labels.blocked, labels.readyForAgent],
412
+ [labels.needsTriage]
413
+ );
414
+ } else {
415
+ await deps.removeLabel(issueNumber, labels.blocked);
416
+ const issue = await deps.fetchIssue(issueNumber);
417
+ if (issue.labels.includes(labels.readyForAgent)) {
418
+ await deps.removeLabel(issueNumber, labels.readyForAgent);
419
+ }
420
+ await deps.addLabels(issueNumber, [labels.needsTriage]);
421
+ }
422
+ },
423
+ async moveToReadyForHuman(issueNumber) {
424
+ try {
425
+ await deps.removeLabel(issueNumber, labels.agentInProgress);
426
+ } catch {
427
+ }
428
+ try {
429
+ await deps.removeLabel(issueNumber, labels.readyForAgent);
430
+ } catch {
431
+ }
432
+ await deps.addLabels(issueNumber, [labels.readyForHuman]);
433
+ },
434
+ async closeCompleted(issueNumber) {
435
+ try {
436
+ await deps.removeLabel(issueNumber, labels.agentInProgress);
437
+ } catch {
438
+ }
439
+ try {
440
+ await deps.removeLabel(issueNumber, labels.prOpenAwaitingMerge);
441
+ } catch {
442
+ }
443
+ if (!deps.closeIssue) {
444
+ throw new Error("closeIssue is required for closeCompleted");
445
+ }
446
+ await deps.closeIssue(issueNumber);
447
+ }
448
+ };
449
+ }
450
+
451
+ // providers/github-client.ts
452
+ import { Octokit } from "octokit";
453
+ var REMOTE_PATTERN = /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/;
454
+ function resolveGitHubToken(env) {
455
+ const token = env.POURKIT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN;
456
+ if (!token) {
457
+ throw new Error(
458
+ "GitHub token is required. Set POURKIT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN."
459
+ );
460
+ }
461
+ return token;
462
+ }
463
+ async function resolveGitHubRepository(options) {
464
+ if (options?.repository) {
465
+ const parts = options.repository.split("/");
466
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
467
+ throw new Error(
468
+ `Invalid repository format: "${options.repository}". Expected "owner/repo".`
469
+ );
470
+ }
471
+ return { owner: parts[0], repo: parts[1] };
472
+ }
473
+ const env = options?.env ?? process.env;
474
+ const envRepo = env.GITHUB_REPOSITORY;
475
+ if (envRepo) {
476
+ const parts = envRepo.split("/");
477
+ if (parts.length === 2 && parts[0] && parts[1]) {
478
+ return { owner: parts[0], repo: parts[1] };
479
+ }
480
+ throw new Error(
481
+ `Invalid repository format: "${envRepo}". Expected "owner/repo".`
482
+ );
483
+ }
484
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
485
+ const cwd = options?.cwd;
486
+ try {
487
+ const result = await execCapture2("git", ["remote", "get-url", "origin"], {
488
+ cwd
489
+ });
490
+ const remote = result.stdout.trim();
491
+ const match = remote.match(REMOTE_PATTERN);
492
+ if (match) {
493
+ return { owner: match[1], repo: match[2] };
494
+ }
495
+ } catch {
496
+ }
497
+ throw new Error(
498
+ "Could not resolve GitHub repository. Set GITHUB_REPOSITORY env var or ensure a valid 'origin' remote exists."
499
+ );
500
+ }
501
+ async function requireGitHubClient(options) {
502
+ const env = options?.env ?? process.env;
503
+ const token = resolveGitHubToken(env);
504
+ const repo = await resolveGitHubRepository(options);
505
+ const octokit = new Octokit({ auth: token });
506
+ return { octokit, ...repo };
507
+ }
508
+
509
+ // issues/unblock.ts
510
+ var ROOT = repoRoot();
511
+ process.chdir(ROOT);
512
+ var LOG_DIR = repoRelative(ROOT, "pourkit", "logs");
513
+ var RUN_ID = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
514
+ var LOG_PATH = repoRelative(LOG_DIR, `unblock-${RUN_ID}.log`);
515
+ var logger = createLogger("unblock", LOG_PATH);
516
+ var __filename = fileURLToPath(import.meta.url);
517
+ async function main() {
518
+ try {
519
+ logger.status("starting");
520
+ const client = await requireGitHubClient();
521
+ await unblock(client);
522
+ logger.status("completed");
523
+ } catch (error) {
524
+ logger.status("failed");
525
+ logger.line(
526
+ error instanceof Error ? error.stack ?? error.message : String(error)
527
+ );
528
+ process.exitCode = 1;
529
+ } finally {
530
+ await logger.close();
531
+ }
532
+ }
533
+ async function withRetryOnTransient(fn, maxAttempts = 2) {
534
+ let lastError;
535
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
536
+ try {
537
+ return await fn();
538
+ } catch (error) {
539
+ lastError = error;
540
+ const isTransient = error instanceof Error && /HTTP (502|503|504)\b/.test(error.message) || typeof error === "object" && error !== null && "status" in error && (error.status === 502 || error.status === 503 || error.status === 504);
541
+ if (!isTransient || attempt === maxAttempts) {
542
+ throw error;
543
+ }
544
+ await new Promise((r) => setTimeout(r, 2e3 * Math.pow(2, attempt - 1)));
545
+ }
546
+ }
547
+ throw lastError;
548
+ }
549
+ async function unblock(client) {
550
+ const issues = await listBlockedIssues(client);
551
+ logger.kv("POURKIT_BLOCKED_COUNT", String(issues.length));
552
+ const issueTitles = new Map(issues.map((i) => [i.number, i.title]));
553
+ const transitionDeps = {
554
+ fetchIssue: async (issueNumber) => {
555
+ const { data } = await client.octokit.rest.issues.get({
556
+ owner: client.owner,
557
+ repo: client.repo,
558
+ issue_number: issueNumber
559
+ });
560
+ return {
561
+ labels: data.labels.map(
562
+ (l) => typeof l === "string" ? l : l.name ?? ""
563
+ )
564
+ };
565
+ },
566
+ addLabels: async (issueNumber, labels) => {
567
+ await withRetryOnTransient(
568
+ () => client.octokit.rest.issues.addLabels({
569
+ owner: client.owner,
570
+ repo: client.repo,
571
+ issue_number: issueNumber,
572
+ labels
573
+ })
574
+ );
575
+ },
576
+ removeLabel: async (issueNumber, label) => {
577
+ try {
578
+ await withRetryOnTransient(
579
+ () => client.octokit.rest.issues.removeLabel({
580
+ owner: client.owner,
581
+ repo: client.repo,
582
+ issue_number: issueNumber,
583
+ name: label
584
+ })
585
+ );
586
+ } catch (error) {
587
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 404) {
588
+ } else {
589
+ throw error;
590
+ }
591
+ }
592
+ },
593
+ updateLabels: async (issueNumber, removes, adds) => {
594
+ for (const label of removes) {
595
+ try {
596
+ await withRetryOnTransient(
597
+ () => client.octokit.rest.issues.removeLabel({
598
+ owner: client.owner,
599
+ repo: client.repo,
600
+ issue_number: issueNumber,
601
+ name: label
602
+ })
603
+ );
604
+ } catch (error) {
605
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 404) {
606
+ } else {
607
+ throw error;
608
+ }
609
+ }
610
+ }
611
+ if (adds.length > 0) {
612
+ await withRetryOnTransient(
613
+ () => client.octokit.rest.issues.addLabels({
614
+ owner: client.owner,
615
+ repo: client.repo,
616
+ issue_number: issueNumber,
617
+ labels: adds
618
+ })
619
+ );
620
+ }
621
+ }
622
+ };
623
+ const transitions = createIssueTransitions(transitionDeps, {
624
+ blocked: "blocked",
625
+ readyForAgent: "ready-for-agent",
626
+ needsTriage: "needs-triage",
627
+ agentInProgress: "agent-in-progress",
628
+ readyForHuman: "ready-for-human",
629
+ prOpenAwaitingMerge: "pr-open-awaiting-merge"
630
+ });
631
+ const results = await reconcileBlockedIssues(issues, {
632
+ getIssueState: async (issueNumber) => {
633
+ const { data } = await client.octokit.rest.issues.get({
634
+ owner: client.owner,
635
+ repo: client.repo,
636
+ issue_number: issueNumber
637
+ });
638
+ return data.state.toUpperCase();
639
+ },
640
+ transitions,
641
+ typeLabels: TYPE_LABELS,
642
+ readyLabel: "ready-for-agent"
643
+ });
644
+ for (const { issueNumber, result } of results) {
645
+ const title = issueTitles.get(issueNumber) ?? `#${issueNumber}`;
646
+ logger.step("process", `#${issueNumber}: ${title}`);
647
+ if (result === "still-blocked") {
648
+ logger.step("skip", `#${issueNumber}: still blocked`);
649
+ continue;
650
+ }
651
+ logger.step("unblock", `#${issueNumber}: all blockers resolved`);
652
+ if (result === "unblocked") {
653
+ logger.step("done", `#${issueNumber}: unblocked and ready`);
654
+ continue;
655
+ }
656
+ logger.step(
657
+ "needs_triage",
658
+ `#${issueNumber}: moved to needs-triage (missing or conflicting type labels)`
659
+ );
660
+ }
661
+ }
662
+ async function listBlockedIssues(client) {
663
+ const { data } = await client.octokit.rest.issues.listForRepo({
664
+ owner: client.owner,
665
+ repo: client.repo,
666
+ state: "open",
667
+ per_page: 100,
668
+ labels: "blocked"
669
+ });
670
+ return data.filter((issue) => !issue.pull_request).map((issue) => ({
671
+ number: issue.number,
672
+ title: issue.title,
673
+ body: issue.body ?? null,
674
+ labels: issue.labels.map((l) => ({
675
+ name: typeof l === "string" ? l : l.name ?? ""
676
+ }))
677
+ }));
678
+ }
679
+ if (process.argv[1] === __filename) {
680
+ void main();
681
+ }
682
+ export {
683
+ main
684
+ };
685
+ //# sourceMappingURL=unblock.js.map