@open-tomato/rafa 0.39.0 → 0.40.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.
@@ -14,7 +14,7 @@ import {
14
14
  trackerPathFor,
15
15
  updateTrackerLine,
16
16
  writeTrackerBlocker
17
- } from "./index-g4116t9m.js";
17
+ } from "./index-krpmxpkh.js";
18
18
  import {
19
19
  ACROSS_STORES_ORDER,
20
20
  CORE_ADAPTER_REGISTRY,
@@ -129,7 +129,7 @@ import {
129
129
  writeNames,
130
130
  writePositionFile,
131
131
  writeSqliteStore
132
- } from "./index-1r2b1052.js";
132
+ } from "./index-8veaj0t9.js";
133
133
  import {
134
134
  CHANGE_LEVELS,
135
135
  CONFIG_DEFAULTS,
@@ -158,7 +158,7 @@ import {
158
158
  requiredPrerequisite,
159
159
  resolveTiers,
160
160
  stampPrompt
161
- } from "./index-m0wt4nr1.js";
161
+ } from "./index-334y39tn.js";
162
162
  import {
163
163
  promotable
164
164
  } from "./index-d6d0942q.js";
@@ -11056,6 +11056,149 @@ function createGhIssueBoard(options) {
11056
11056
  return Object.freeze(board);
11057
11057
  }
11058
11058
 
11059
+ // src/board/project/retry.ts
11060
+ var NEVER_RETRIED = Object.freeze([
11061
+ /RATE_LIMITED|(?:secondary )?rate limit/iu,
11062
+ /INSUFFICIENT_SCOPES|not been granted the required scopes|needs the "[^"]*" scope/u,
11063
+ /NOT_FOUND|Could not resolve to [^\n]*/u
11064
+ ]);
11065
+ var RETRIED = Object.freeze([
11066
+ /operation timed out|i\/o timeout|TLS handshake timeout/u,
11067
+ /connection reset|\bEOF\b/u,
11068
+ /\bHTTP 50[234]\b/u
11069
+ ]);
11070
+ var UNKNOWN_REASON = "unknown error";
11071
+ function firstMatch(patterns, text) {
11072
+ for (const pattern of patterns) {
11073
+ const match = pattern.exec(text);
11074
+ if (match !== null)
11075
+ return match[0];
11076
+ }
11077
+ return;
11078
+ }
11079
+ function lastLine(text) {
11080
+ const lines = text.split(`
11081
+ `).map((line) => line.trim()).filter((line) => line !== "");
11082
+ return lines.at(-1) ?? UNKNOWN_REASON;
11083
+ }
11084
+ function classifyFailure(stderr) {
11085
+ const permanent = firstMatch(NEVER_RETRIED, stderr);
11086
+ if (permanent !== undefined)
11087
+ return { kind: "permanent", reason: permanent };
11088
+ const retried = firstMatch(RETRIED, stderr);
11089
+ if (retried !== undefined)
11090
+ return { kind: "retried", reason: retried };
11091
+ return { kind: "permanent", reason: lastLine(stderr) };
11092
+ }
11093
+ function callNumber(args) {
11094
+ for (let index = 0;index + 1 < args.length; index += 1) {
11095
+ const value = args[index + 1] ?? "";
11096
+ if (args[index] !== "-F" || !value.startsWith("number="))
11097
+ continue;
11098
+ const number = Number(value.slice("number=".length));
11099
+ if (Number.isSafeInteger(number))
11100
+ return number;
11101
+ }
11102
+ return;
11103
+ }
11104
+ var FIRST_FIELD = /\{\s*([A-Za-z_]\w*)/u;
11105
+ function callSubject(args) {
11106
+ const number = callNumber(args);
11107
+ if (number !== undefined)
11108
+ return `#${String(number)}`;
11109
+ const query = args.find((arg) => arg.startsWith("query="));
11110
+ const field3 = query === undefined ? undefined : FIRST_FIELD.exec(query)?.[1];
11111
+ return field3 ?? args.slice(0, 2).join(" ");
11112
+ }
11113
+ var MS_PER_SECOND = 1000;
11114
+ function isWholeFrom(value, min) {
11115
+ return Number.isSafeInteger(value) && value >= min;
11116
+ }
11117
+ function checkOptions(options) {
11118
+ if (options.retries !== false && !isWholeFrom(options.retries, 1)) {
11119
+ throw new RangeError(`board.project.retries must be false or a whole number from 1, not ${String(options.retries)}`);
11120
+ }
11121
+ if (!isWholeFrom(options.retryWaitSeconds, 1)) {
11122
+ throw new RangeError(`board.project.retryWaitSeconds must be a whole number from 1, not ${String(options.retryWaitSeconds)}`);
11123
+ }
11124
+ }
11125
+ function retryingGhRunner(gh, options) {
11126
+ checkOptions(options);
11127
+ const { retries, retryWaitSeconds, onRetry } = options;
11128
+ if (retries === false)
11129
+ return gh;
11130
+ const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
11131
+ return async (args, stdin) => {
11132
+ let result = await gh(args, stdin);
11133
+ for (let attempt = 1;attempt <= retries && !result.ok; attempt += 1) {
11134
+ const reading2 = classifyFailure(result.stderr);
11135
+ if (reading2.kind === "permanent")
11136
+ return result;
11137
+ const waitMs = retryWaitSeconds * MS_PER_SECOND * 2 ** (attempt - 1);
11138
+ onRetry?.({
11139
+ attempt,
11140
+ of: retries,
11141
+ reason: reading2.reason,
11142
+ subject: callSubject(args),
11143
+ number: callNumber(args),
11144
+ waitMs,
11145
+ args
11146
+ });
11147
+ await sleep(waitMs);
11148
+ result = await gh(args, stdin);
11149
+ }
11150
+ return result;
11151
+ };
11152
+ }
11153
+
11154
+ // src/board/project/project-runner.ts
11155
+ var RETRY_EVENT = "retry";
11156
+ var OPENED = new WeakSet;
11157
+ function retryLine(notice) {
11158
+ return `retrying ${notice.subject} (${String(notice.attempt)} of ${String(notice.of)}): ${notice.reason}`;
11159
+ }
11160
+ function retryEvent(notice, now) {
11161
+ return {
11162
+ type: "event",
11163
+ name: RETRY_EVENT,
11164
+ summary: retryLine(notice),
11165
+ data: {
11166
+ attempt: notice.attempt,
11167
+ of: notice.of,
11168
+ reason: notice.reason,
11169
+ subject: notice.subject,
11170
+ number: notice.number ?? null,
11171
+ waitMs: notice.waitMs
11172
+ },
11173
+ ts: now.toISOString()
11174
+ };
11175
+ }
11176
+ function retryReporter(output, mode, now = () => new Date) {
11177
+ return (notice) => {
11178
+ if (mode === "json")
11179
+ output.emit(retryEvent(notice, now()));
11180
+ else
11181
+ output.info(retryLine(notice));
11182
+ };
11183
+ }
11184
+ function commandRetrySeams(output, mode, sleep) {
11185
+ const onRetry = retryReporter(output, mode);
11186
+ return sleep === undefined ? { onRetry } : { onRetry, sleep };
11187
+ }
11188
+ var activeRetryReport = (notice) => {
11189
+ retryReporter(activeOutput(), activeOutputMode())(notice);
11190
+ };
11191
+ function openProjectRunner(gh, config, seams = {}) {
11192
+ if (OPENED.has(gh))
11193
+ return gh;
11194
+ const onRetry = seams.onRetry ?? activeRetryReport;
11195
+ const base = { retries: config.boardProjectRetries, retryWaitSeconds: config.boardProjectRetryWaitSeconds, onRetry };
11196
+ const opened = retryingGhRunner(gh, seams.sleep === undefined ? base : { ...base, sleep: seams.sleep });
11197
+ if (opened !== gh)
11198
+ OPENED.add(opened);
11199
+ return opened;
11200
+ }
11201
+
11059
11202
  // src/board/project/options.ts
11060
11203
  var STAGE_OPTIONS = Object.freeze([
11061
11204
  "Backlog",
@@ -11080,8 +11223,6 @@ var HORIZON_OPTIONS = Object.freeze([
11080
11223
 
11081
11224
  // src/board/project/port.ts
11082
11225
  var PROJECT_PAGE_SIZE = 100;
11083
- var PROJECT_WRITE_BATCH_SIZE = 20;
11084
- var PROJECT_WRITE_PAUSE_MS = 1000;
11085
11226
  var PROJECT_PORT_PREFIX = "board project";
11086
11227
  var FIELD_DATA_TYPES = Object.freeze({
11087
11228
  singleSelect: "SINGLE_SELECT",
@@ -11167,6 +11308,9 @@ function notFoundWarning(ref) {
11167
11308
  function skippedFieldWarning(mismatch) {
11168
11309
  return `The project's field "${mismatch.template.name}" was skipped and the rest were written: ${mismatch.sentence} Run \`${DOCTOR_FIX}\`.`;
11169
11310
  }
11311
+ function notRefreshedWarning(refusal2) {
11312
+ return `#${String(refusal2.number)} not refreshed: ${refusal2.reason}`;
11313
+ }
11170
11314
 
11171
11315
  // src/release/fragment-tree.ts
11172
11316
  import { posix } from "path";
@@ -11423,18 +11567,31 @@ function readFragmentTree(git, tree, directory) {
11423
11567
  // src/board/project/facts-query.ts
11424
11568
  var PAGE_SIZE = 100;
11425
11569
  var ISSUE_BATCH = 20;
11570
+ var LIST_BATCH = 20;
11426
11571
  var PULL_BATCH = 20;
11427
11572
  var FRAGMENT_BATCH = 20;
11428
11573
  var PULL_FIELDS = "number state baseRefName headRefOid";
11429
- var ISSUE_SELECTION = [
11430
- "number state stateReason",
11431
- ` labels(first: ${String(PAGE_SIZE)}) { pageInfo { hasNextPage } nodes { name } }`,
11432
- ` closedByPullRequestsReferences(first: ${String(PAGE_SIZE)}, includeClosedPrs: true)`,
11433
- " { pageInfo { hasNextPage } nodes { ...pull } }",
11434
- ` timelineItems(first: ${String(PAGE_SIZE)}, itemTypes: [CROSS_REFERENCED_EVENT])`,
11435
- " { pageInfo { hasNextPage } nodes { ... on CrossReferencedEvent",
11436
- " { isCrossRepository source { ... on PullRequest { ...pull body } } } } }"
11437
- ].join("");
11574
+ var ISSUE_LISTS = Object.freeze(["labels", "closedByPullRequestsReferences", "timelineItems"]);
11575
+ var LIST_CODES = Object.freeze({
11576
+ labels: "la",
11577
+ closedByPullRequestsReferences: "cb",
11578
+ timelineItems: "tl"
11579
+ });
11580
+ var LIST_ARGUMENTS = Object.freeze({
11581
+ labels: "",
11582
+ closedByPullRequestsReferences: ", includeClosedPrs: true",
11583
+ timelineItems: ", itemTypes: [CROSS_REFERENCED_EVENT]"
11584
+ });
11585
+ var LIST_NODES = Object.freeze({
11586
+ labels: "name",
11587
+ closedByPullRequestsReferences: "...pull",
11588
+ timelineItems: "... on CrossReferencedEvent { isCrossRepository source { ... on PullRequest { ...pull body } } }"
11589
+ });
11590
+ function listSelection(list, after) {
11591
+ const from = after === null ? "" : `, after: $${after}`;
11592
+ return `${list}(first: ${String(PAGE_SIZE)}${from}${LIST_ARGUMENTS[list]})` + ` { pageInfo { hasNextPage endCursor } nodes { ${LIST_NODES[list]} } }`;
11593
+ }
11594
+ var ISSUE_SELECTION = ["number state stateReason", ...ISSUE_LISTS.map((list) => listSelection(list, null))].join(" ");
11438
11595
  function checkNumber(number) {
11439
11596
  if (!Number.isSafeInteger(number) || number < 1)
11440
11597
  throw new RangeError(`not an issue number: ${String(number)}`);
@@ -11456,6 +11613,9 @@ function graphqlArgs(query, variables) {
11456
11613
  function issueAlias(number) {
11457
11614
  return `i${checkNumber(number)}`;
11458
11615
  }
11616
+ function listAlias(number, list) {
11617
+ return `${LIST_CODES[list]}${checkNumber(number)}`;
11618
+ }
11459
11619
  function pullAlias(number) {
11460
11620
  return `p${checkNumber(number)}`;
11461
11621
  }
@@ -11467,6 +11627,18 @@ function issuesArgs(numbers) {
11467
11627
  const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { nameWithOwner${issues} } }` + ` fragment facts on Issue { ${ISSUE_SELECTION} } fragment pull on PullRequest { ${PULL_FIELDS} }`;
11468
11628
  return graphqlArgs(query, []);
11469
11629
  }
11630
+ function listPagesArgs(pages) {
11631
+ const aliases = pages.map(({ number, list }) => listAlias(number, list));
11632
+ const declared = aliases.map((alias) => `, $${alias}: String!`).join("");
11633
+ const issues = pages.map(({ number, list }, index) => {
11634
+ const alias = aliases[index] ?? listAlias(number, list);
11635
+ return ` ${alias}: issue(number: ${String(number)}) { ${listSelection(list, alias)} }`;
11636
+ }).join("");
11637
+ const usesPull = pages.some(({ list }) => list !== "labels");
11638
+ const fragment = usesPull ? ` fragment pull on PullRequest { ${PULL_FIELDS} }` : "";
11639
+ const query = `query($owner: String!, $repo: String!${declared}) { repository(owner: $owner, name: $repo) {${issues} } }${fragment}`;
11640
+ return graphqlArgs(query, pages.map(({ cursor }, index) => [aliases[index] ?? "", cursor]));
11641
+ }
11470
11642
  function filesArgs(pages) {
11471
11643
  const cursors = pages.map(({ number }) => `, $c${checkNumber(number)}: String`).join("");
11472
11644
  const pulls = pages.map(({ number }) => ` ${pullAlias(number)}: pullRequest(number: ${String(number)})` + ` { files(first: ${String(PAGE_SIZE)}, after: $c${String(number)})` + " { pageInfo { hasNextPage endCursor } nodes { path changeType } } }").join("");
@@ -13660,7 +13832,7 @@ var CHECKOUT_ROSTER_TIMEOUT_MS = 30000;
13660
13832
  var DESCRIBE_LINE = `bun ${RAFA_ENTRY} describe --output=json`;
13661
13833
  async function coreRoster() {
13662
13834
  const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
13663
- import("./index-psv1sxkv.js"),
13835
+ import("./index-j7kjj0dd.js"),
13664
13836
  import("./describe-pwhh0vws.js")
13665
13837
  ]);
13666
13838
  return describeRegistry(CORE_REGISTRY, version);
@@ -15241,6 +15413,17 @@ var CLOSING_REFERENCE = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?\s+(?:([\w
15241
15413
  function refuse8(problem) {
15242
15414
  throw new Error(`${PREFIX19}: ${problem}`);
15243
15415
  }
15416
+ function reasonOf2(error) {
15417
+ const message = error instanceof Error ? error.message : String(error);
15418
+ return message.startsWith(`${PREFIX19}: `) ? message.slice(PREFIX19.length + 2) : message;
15419
+ }
15420
+ function settle(item, read) {
15421
+ try {
15422
+ return { item, ok: true, value: read() };
15423
+ } catch (error) {
15424
+ return { item, ok: false, reason: reasonOf2(error) };
15425
+ }
15426
+ }
15244
15427
  function readMapping3(value, where) {
15245
15428
  if (!isMapping(value))
15246
15429
  refuse8(`${where} is ${describeValue(value)}, expected a mapping`);
@@ -15262,12 +15445,16 @@ function readList2(value, where) {
15262
15445
  refuse8(`${where} is ${describeValue(value)}, expected a list`);
15263
15446
  return value;
15264
15447
  }
15265
- function readOnePage(value, where) {
15448
+ function readPage(value, where) {
15266
15449
  const connection = readMapping3(value, where);
15267
15450
  const pageInfo = readMapping3(connection["pageInfo"], `${where}.pageInfo`);
15268
- if (pageInfo["hasNextPage"] !== false)
15269
- refuse8(`${where} holds more than ${String(PAGE_SIZE)} entries, one page`);
15270
- return readList2(connection["nodes"], `${where}.nodes`);
15451
+ const nodes = readList2(connection["nodes"], `${where}.nodes`);
15452
+ const more = pageInfo["hasNextPage"];
15453
+ if (more === false)
15454
+ return { nodes, next: null };
15455
+ if (more !== true)
15456
+ refuse8(`${where}.pageInfo.hasNextPage is ${describeValue(more)}, expected true or false`);
15457
+ return { nodes, next: readString4(pageInfo["endCursor"], `${where}.pageInfo.endCursor`) };
15271
15458
  }
15272
15459
  async function readRepository(gh, args) {
15273
15460
  const result = await gh(args);
@@ -15282,6 +15469,21 @@ async function readRepository(gh, args) {
15282
15469
  const data = readMapping3(readMapping3(parsed, "the answer")["data"], "data");
15283
15470
  return readMapping3(data["repository"], "data.repository");
15284
15471
  }
15472
+ async function readEach(gh, items, how) {
15473
+ const read = [];
15474
+ for (const batch of batchesOf(items, how.size)) {
15475
+ let answer3;
15476
+ try {
15477
+ answer3 = await readRepository(gh, how.argsOf(batch));
15478
+ } catch (error) {
15479
+ const alone = batch.length === 1 ? batch.map((item) => ({ item, ok: false, reason: reasonOf2(error) })) : await readEach(gh, batch, { ...how, size: 1 });
15480
+ read.push(...alone);
15481
+ continue;
15482
+ }
15483
+ read.push(...batch.map((item, index) => settle(item, () => how.readOne(answer3, item, index))));
15484
+ }
15485
+ return read;
15486
+ }
15285
15487
  function readPull(value, where) {
15286
15488
  const pull = readMapping3(value, where);
15287
15489
  const state = pull["state"];
@@ -15311,35 +15513,102 @@ function crossReferencedPull(value, where, issue3, repository) {
15311
15513
  refuse8(`${where}.source.body is ${describeValue(body)}, expected a string`);
15312
15514
  return bodyCloses(body, issue3, repository) ? readPull(source, `${where}.source`) : null;
15313
15515
  }
15314
- function readIssue(repositoryAnswer, number, repository) {
15516
+ function listWhere(number, list) {
15517
+ return `issue #${String(number)}.${list}`;
15518
+ }
15519
+ function readIssueFirstPages(answer3, number, repository) {
15315
15520
  const where = `issue #${String(number)}`;
15316
- const issue3 = readMapping3(repositoryAnswer[issueAlias(number)], where);
15521
+ const issue3 = readMapping3(answer3[issueAlias(number)], where);
15317
15522
  const state = issue3["state"];
15318
15523
  if (state !== "OPEN" && state !== "CLOSED")
15319
15524
  refuse8(`${where}.state is ${describeValue(state)}, expected OPEN or CLOSED`);
15320
15525
  const reason = issue3["stateReason"];
15321
15526
  if (reason !== null && typeof reason !== "string")
15322
15527
  refuse8(`${where}.stateReason is ${describeValue(reason)}, expected a string or null`);
15323
- const labels = readOnePage(issue3["labels"], `${where}.labels`).map((label, index) => readString4(readMapping3(label, `${where}.labels[${String(index)}]`)["name"], `${where}.labels[${String(index)}].name`));
15324
- const linked = readOnePage(issue3["closedByPullRequestsReferences"], `${where}.closedByPullRequestsReferences`).map((pull, index) => readPull(pull, `${where}.closedByPullRequestsReferences[${String(index)}]`));
15325
- const mentioned = readOnePage(issue3["timelineItems"], `${where}.timelineItems`).map((event, index) => crossReferencedPull(event, `${where}.timelineItems[${String(index)}]`, number, repository));
15528
+ const pages = Object.fromEntries(ISSUE_LISTS.map((list) => [list, readPage(issue3[list], listWhere(number, list))]));
15529
+ return { head: { number, state, stateReason: reason }, repository, pages };
15530
+ }
15531
+ function listKey(number, list) {
15532
+ return `${String(number)}:${list}`;
15533
+ }
15534
+ function readListPage(answer3, { number, list }) {
15535
+ return readPage(readMapping3(answer3[listAlias(number, list)], `issue #${String(number)}`)[list], listWhere(number, list));
15536
+ }
15537
+ var LIST_PAGES = { size: LIST_BATCH, argsOf: listPagesArgs, readOne: readListPage };
15538
+ async function readRemainingPages(gh, firsts, refused) {
15539
+ const nodes = new Map;
15540
+ const seen = new Map;
15541
+ let pending = firsts.flatMap(({ head: { number }, pages }) => ISSUE_LISTS.flatMap((list) => {
15542
+ const page = pages[list];
15543
+ nodes.set(listKey(number, list), [...page.nodes]);
15544
+ seen.set(listKey(number, list), new Set(page.next === null ? [] : [page.next]));
15545
+ return page.next === null ? [] : [{ number, list, cursor: page.next }];
15546
+ }));
15547
+ while (pending.length > 0) {
15548
+ const next = [];
15549
+ for (const settled of await readEach(gh, pending, LIST_PAGES)) {
15550
+ const { number, list } = settled.item;
15551
+ if (refused.has(number))
15552
+ continue;
15553
+ if (!settled.ok) {
15554
+ refused.set(number, settled.reason);
15555
+ continue;
15556
+ }
15557
+ nodes.get(listKey(number, list))?.push(...settled.value.nodes);
15558
+ const cursor = settled.value.next;
15559
+ if (cursor === null)
15560
+ continue;
15561
+ const cursors = seen.get(listKey(number, list));
15562
+ if (cursors?.has(cursor) === true) {
15563
+ refused.set(number, `${listWhere(number, list)} answered the cursor ${JSON.stringify(cursor)} a second time, so its next page would repeat one read before`);
15564
+ continue;
15565
+ }
15566
+ cursors?.add(cursor);
15567
+ next.push({ number, list, cursor });
15568
+ }
15569
+ pending = next.filter(({ number }) => !refused.has(number));
15570
+ }
15571
+ return nodes;
15572
+ }
15573
+ function readIssue(head, lists, repository) {
15574
+ const where = `issue #${String(head.number)}`;
15575
+ const labels = lists.labels.map((label, index) => readString4(readMapping3(label, `${where}.labels[${String(index)}]`)["name"], `${where}.labels[${String(index)}].name`));
15576
+ const linked = lists.closedByPullRequestsReferences.map((pull, index) => readPull(pull, `${where}.closedByPullRequestsReferences[${String(index)}]`));
15577
+ const mentioned = lists.timelineItems.map((event, index) => crossReferencedPull(event, `${where}.timelineItems[${String(index)}]`, head.number, repository));
15326
15578
  const pulls = new Map([...linked, ...mentioned].flatMap((pull) => pull === null ? [] : [[pull.number, pull]]));
15327
15579
  return {
15328
- number,
15329
- state,
15330
- stateReason: reason,
15580
+ ...head,
15331
15581
  labels,
15332
15582
  pulls: [...pulls.values()].sort((left, right) => left.number - right.number)
15333
15583
  };
15334
15584
  }
15335
- async function readIssues(gh, numbers) {
15336
- const read = [];
15337
- for (const batch of batchesOf(numbers, ISSUE_BATCH)) {
15338
- const answer3 = await readRepository(gh, issuesArgs(batch));
15339
- const repository = readString4(answer3["nameWithOwner"], "data.repository.nameWithOwner");
15340
- read.push(...batch.map((number) => readIssue(answer3, number, repository)));
15585
+ function readFirstPagesOf(answer3, number) {
15586
+ return readIssueFirstPages(answer3, number, readString4(answer3["nameWithOwner"], "data.repository.nameWithOwner"));
15587
+ }
15588
+ var ISSUES = { size: ISSUE_BATCH, argsOf: issuesArgs, readOne: readFirstPagesOf };
15589
+ async function readIssues(gh, numbers, refused, onRead) {
15590
+ const settledFirsts = [];
15591
+ for (const batch of batchesOf(numbers, ISSUES.size)) {
15592
+ settledFirsts.push(...await readEach(gh, batch, ISSUES));
15593
+ onRead(settledFirsts.length);
15341
15594
  }
15342
- return read;
15595
+ const firsts = settledFirsts.flatMap((settled) => {
15596
+ if (settled.ok)
15597
+ return [settled.value];
15598
+ refused.set(settled.item, settled.reason);
15599
+ return [];
15600
+ });
15601
+ const nodes = await readRemainingPages(gh, firsts, refused);
15602
+ return firsts.flatMap(({ head, repository }) => {
15603
+ if (refused.has(head.number))
15604
+ return [];
15605
+ const lists = Object.fromEntries(ISSUE_LISTS.map((list) => [list, nodes.get(listKey(head.number, list)) ?? []]));
15606
+ const issue3 = settle(head.number, () => readIssue(head, lists, repository));
15607
+ if (issue3.ok)
15608
+ return [issue3.value];
15609
+ refused.set(head.number, issue3.reason);
15610
+ return [];
15611
+ });
15343
15612
  }
15344
15613
  function readFilesPage(value, where, prefix) {
15345
15614
  const files = readMapping3(readMapping3(value, where)["files"], `${where}.files`);
@@ -15356,41 +15625,41 @@ function readFilesPage(value, where, prefix) {
15356
15625
  next: pageInfo["hasNextPage"] === true ? readString4(pageInfo["endCursor"], `${where}.files.pageInfo.endCursor`) : null
15357
15626
  };
15358
15627
  }
15359
- async function readAddedFragments(gh, numbers, prefix) {
15628
+ async function readAddedFragments(gh, numbers, prefix, failed2) {
15360
15629
  const added = new Map(numbers.map((number) => [number, []]));
15630
+ const how = {
15631
+ size: PULL_BATCH,
15632
+ argsOf: filesArgs,
15633
+ readOne: (answer3, { number }) => readFilesPage(answer3[pullAlias(number)], `pull request #${String(number)}`, prefix)
15634
+ };
15361
15635
  let pending = numbers.map((number) => ({ number, cursor: null }));
15362
15636
  while (pending.length > 0) {
15363
15637
  const next = [];
15364
- for (const batch of batchesOf(pending, PULL_BATCH)) {
15365
- const answer3 = await readRepository(gh, filesArgs(batch));
15366
- for (const { number } of batch) {
15367
- const page = readFilesPage(answer3[pullAlias(number)], `pull request #${String(number)}`, prefix);
15368
- added.get(number)?.push(...page.paths);
15369
- if (page.next !== null)
15370
- next.push({ number, cursor: page.next });
15638
+ for (const settled of await readEach(gh, pending, how)) {
15639
+ const { number } = settled.item;
15640
+ if (!settled.ok) {
15641
+ failed2.set(number, settled.reason);
15642
+ continue;
15371
15643
  }
15644
+ added.get(number)?.push(...settled.value.paths);
15645
+ if (settled.value.next !== null)
15646
+ next.push({ number, cursor: settled.value.next });
15372
15647
  }
15373
15648
  pending = next;
15374
15649
  }
15375
15650
  return added;
15376
15651
  }
15377
- async function readBlobs(gh, lookups) {
15378
- const read = [];
15379
- for (const batch of batchesOf(lookups, FRAGMENT_BATCH)) {
15380
- const answer3 = await readRepository(gh, blobsArgs(batch));
15381
- read.push(...batch.map(({ path: path3 }, index) => {
15382
- const { head, base } = blobAliases(index);
15383
- if (!(head in answer3) || !(base in answer3))
15384
- refuse8(`the blob read answered no ${head} or ${base} for ${path3}`);
15385
- const headBlob = answer3[head];
15386
- const text = headBlob === null ? null : readMapping3(headBlob, `${path3} at the head`)["text"];
15387
- if (text !== null && typeof text !== "string")
15388
- refuse8(`${path3} at the head has text ${describeValue(text)}, expected a string`);
15389
- return { text, onBase: answer3[base] !== null };
15390
- }));
15391
- }
15392
- return read;
15652
+ function readBlobsOf(answer3, { path: path3 }, index) {
15653
+ const { head, base } = blobAliases(index);
15654
+ if (!(head in answer3) || !(base in answer3))
15655
+ refuse8(`the blob read answered no ${head} or ${base} for ${path3}`);
15656
+ const headBlob = answer3[head];
15657
+ const text = headBlob === null ? null : readMapping3(headBlob, `${path3} at the head`)["text"];
15658
+ if (text !== null && typeof text !== "string")
15659
+ refuse8(`${path3} at the head has text ${describeValue(text)}, expected a string`);
15660
+ return { text, onBase: answer3[base] !== null };
15393
15661
  }
15662
+ var BLOBS = { size: FRAGMENT_BATCH, argsOf: blobsArgs, readOne: readBlobsOf };
15394
15663
  function fragmentOf(path3, blobs) {
15395
15664
  if (blobs.text === null) {
15396
15665
  return { path: path3, level: UNREAD_LEVEL, onBase: blobs.onBase, problem: "The fragment could not be read at the head of its pull request." };
@@ -15404,20 +15673,45 @@ function decidingFragment(fragments) {
15404
15673
  function blobKey(pull, path3) {
15405
15674
  return `${String(pull)}:${path3}`;
15406
15675
  }
15676
+ async function readFragments(gh, pulls, prefix, failed2) {
15677
+ const added = await readAddedFragments(gh, pulls.map(({ number }) => number), prefix, failed2);
15678
+ const lookups = pulls.filter(({ number }) => !failed2.has(number)).flatMap((pull) => (added.get(pull.number) ?? []).map((path3) => ({ pull: pull.number, path: path3, headRefOid: pull.headRefOid, baseRefName: pull.baseRefName })));
15679
+ const fragments = new Map((await readEach(gh, lookups, BLOBS)).flatMap((settled) => {
15680
+ if (settled.ok)
15681
+ return [[blobKey(settled.item.pull, settled.item.path), fragmentOf(settled.item.path, settled.value)]];
15682
+ failed2.set(settled.item.pull, settled.reason);
15683
+ return [];
15684
+ }));
15685
+ return { added, fragments };
15686
+ }
15407
15687
  async function readIssueFacts(options, numbers) {
15408
15688
  const { gh } = options;
15409
- const prefix = directoryPrefix(options.fragments);
15410
- const issues = await readIssues(gh, [...new Set(numbers)]);
15689
+ const asked = [...new Set(numbers)];
15690
+ const refused = new Map;
15691
+ const failed2 = new Map;
15692
+ const issues = await readIssues(gh, asked, refused, options.onRead ?? (() => {
15693
+ return;
15694
+ }));
15411
15695
  const pulls = [...new Map(issues.flatMap(({ pulls: read }) => read.map((pull) => [pull.number, pull]))).values()];
15412
- const added = await readAddedFragments(gh, pulls.map(({ number }) => number), prefix);
15413
- const lookups = pulls.flatMap((pull) => (added.get(pull.number) ?? []).map((path3) => ({ pull: pull.number, path: path3, headRefOid: pull.headRefOid, baseRefName: pull.baseRefName })));
15414
- const blobs = await readBlobs(gh, lookups);
15415
- const fragments = new Map(lookups.map(({ pull, path: path3 }, index) => [blobKey(pull, path3), fragmentOf(path3, blobs[index] ?? { text: null, onBase: false })]));
15696
+ const { added, fragments } = await readFragments(gh, pulls, directoryPrefix(options.fragments), failed2);
15416
15697
  const factsOf = (pull) => {
15417
15698
  const own = [...added.get(pull.number) ?? []].sort().flatMap((path3) => fragments.get(blobKey(pull.number, path3)) ?? []);
15418
15699
  return { number: pull.number, state: pull.state, baseRefName: pull.baseRefName, fragments: own, fragment: decidingFragment(own) };
15419
15700
  };
15420
- return new Map(issues.map(({ pulls: read, ...issue3 }) => [issue3.number, { ...issue3, pullRequests: read.map(factsOf) }]));
15701
+ const facts = new Map(issues.flatMap(({ pulls: read, ...issue3 }) => {
15702
+ const unread3 = read.find(({ number }) => failed2.has(number));
15703
+ if (unread3 === undefined)
15704
+ return [[issue3.number, { ...issue3, pullRequests: read.map(factsOf) }]];
15705
+ refused.set(issue3.number, `pull request #${String(unread3.number)} could not be read: ${failed2.get(unread3.number) ?? ""}`);
15706
+ return [];
15707
+ }));
15708
+ return {
15709
+ facts,
15710
+ refused: asked.flatMap((number) => {
15711
+ const reason = refused.get(number);
15712
+ return reason === undefined ? [] : [{ number, reason }];
15713
+ })
15714
+ };
15421
15715
  }
15422
15716
 
15423
15717
  // src/board/project/gh.ts
@@ -15469,7 +15763,7 @@ function readList3(value, where) {
15469
15763
  refuse9(`${where} is ${describeValue(value)}, expected a list`);
15470
15764
  return value;
15471
15765
  }
15472
- function readOnePage2(value, where) {
15766
+ function readOnePage(value, where) {
15473
15767
  const connection = readMapping4(value, where);
15474
15768
  if (readMapping4(connection["pageInfo"], `${where}.pageInfo`)["hasNextPage"] !== false) {
15475
15769
  refuse9(`${where} holds more than ${String(PROJECT_PAGE_SIZE)} entries, one page`);
@@ -15528,7 +15822,7 @@ function readProject(owner, value, where) {
15528
15822
  url: readString5(project["url"], `${where}.url`),
15529
15823
  closed: readBoolean2(project["closed"], `${where}.closed`),
15530
15824
  public: readBoolean2(project["public"], `${where}.public`),
15531
- fields: readOnePage2(project["fields"], `${where}.fields`).map((field4, index) => readField(field4, `${where}.fields.nodes[${String(index)}]`))
15825
+ fields: readOnePage(project["fields"], `${where}.fields`).map((field4, index) => readField(field4, `${where}.fields.nodes[${String(index)}]`))
15532
15826
  };
15533
15827
  }
15534
15828
  function findArgs(ref3) {
@@ -15626,7 +15920,7 @@ function readContent(item, where) {
15626
15920
  }
15627
15921
  function readItem(value, where) {
15628
15922
  const item = readMapping4(value, where);
15629
- const values = readOnePage2(item["fieldValues"], `${where}.fieldValues`).map((node, index) => readValue(node, `${where}.fieldValues.nodes[${String(index)}]`));
15923
+ const values = readOnePage(item["fieldValues"], `${where}.fieldValues`).map((node, index) => readValue(node, `${where}.fieldValues.nodes[${String(index)}]`));
15630
15924
  return {
15631
15925
  id: readString5(item["id"], `${where}.id`),
15632
15926
  archived: readBoolean2(item["isArchived"], `${where}.isArchived`),
@@ -15705,6 +15999,129 @@ function createGhProjectPort(gh) {
15705
15999
  };
15706
16000
  }
15707
16001
 
16002
+ // src/board/project/progress.ts
16003
+ var PROGRESS_EVENT = "progress";
16004
+ var PROGRESS_PHASES = Object.freeze({
16005
+ adds: "adding issues",
16006
+ facts: "reading facts",
16007
+ writes: "writing fields"
16008
+ });
16009
+ var SILENT_PHASE = Object.freeze({
16010
+ advance: () => {
16011
+ return;
16012
+ },
16013
+ wait: () => {
16014
+ return;
16015
+ },
16016
+ end: () => {
16017
+ return;
16018
+ }
16019
+ });
16020
+ var MS_PER_SECOND2 = 1000;
16021
+ var SECONDS_PER_MINUTE = 60;
16022
+ var SECONDS_PER_HOUR = 3600;
16023
+ function formatElapsed(elapsedMs) {
16024
+ const seconds = Math.floor(Math.max(0, elapsedMs) / MS_PER_SECOND2);
16025
+ const hours = Math.floor(seconds / SECONDS_PER_HOUR);
16026
+ const minutes = Math.floor(seconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
16027
+ const rest = seconds % SECONDS_PER_MINUTE;
16028
+ if (hours > 0)
16029
+ return `${String(hours)}h ${String(minutes)}m ${String(rest)}s`;
16030
+ if (minutes > 0)
16031
+ return `${String(minutes)}m ${String(rest)}s`;
16032
+ return `${String(rest)}s`;
16033
+ }
16034
+ function waitLine(waitMs) {
16035
+ return `waiting ${String(Math.ceil(Math.max(0, waitMs) / MS_PER_SECOND2))} s for GitHub's write limit`;
16036
+ }
16037
+ function progressEvent(line, now) {
16038
+ return {
16039
+ type: "event",
16040
+ name: PROGRESS_EVENT,
16041
+ summary: line.text,
16042
+ data: { ...line.data },
16043
+ ts: now.toISOString()
16044
+ };
16045
+ }
16046
+ function phaseReporter(options) {
16047
+ const label = PROGRESS_PHASES[options.phase];
16048
+ const total2 = String(options.total);
16049
+ const gapMs = options.progressSeconds === false ? null : options.progressSeconds * MS_PER_SECOND2;
16050
+ let startedAt = options.now();
16051
+ let lastLineAt = startedAt;
16052
+ let doneSoFar = 0;
16053
+ const base = (step, done, at) => ({
16054
+ phase: options.phase,
16055
+ step,
16056
+ done,
16057
+ total: options.total,
16058
+ elapsedMs: at - startedAt
16059
+ });
16060
+ return {
16061
+ start: () => {
16062
+ startedAt = options.now();
16063
+ lastLineAt = startedAt;
16064
+ return { text: `${label}: ${total2}`, data: base("start", 0, startedAt) };
16065
+ },
16066
+ advance: (done) => {
16067
+ doneSoFar = done;
16068
+ if (gapMs === null)
16069
+ return;
16070
+ const at = options.now();
16071
+ if (at - lastLineAt < gapMs)
16072
+ return;
16073
+ lastLineAt = at;
16074
+ const data = base("progress", done, at);
16075
+ return { text: `${label}: ${String(done)}/${total2}, ${formatElapsed(data.elapsedMs)}`, data };
16076
+ },
16077
+ end: ({ done, refused }) => {
16078
+ const data = { ...base("end", done, options.now()), refused };
16079
+ return {
16080
+ text: `${label}: ${String(done)}/${total2}, ${String(refused)} refused, ${formatElapsed(data.elapsedMs)}`,
16081
+ data
16082
+ };
16083
+ },
16084
+ wait: (waitMs) => {
16085
+ const data = { ...base("wait", doneSoFar, options.now()), waitMs };
16086
+ return { text: waitLine(waitMs), data };
16087
+ }
16088
+ };
16089
+ }
16090
+ function openPhase(feed, phase, total2) {
16091
+ if (feed === undefined)
16092
+ return SILENT_PHASE;
16093
+ const reporter = phaseReporter({ phase, total: total2, now: feed.now, progressSeconds: feed.progressSeconds });
16094
+ feed.sink(reporter.start());
16095
+ return {
16096
+ advance: (done) => {
16097
+ const line = reporter.advance(done);
16098
+ if (line !== undefined)
16099
+ feed.sink(line);
16100
+ },
16101
+ wait: (waitMs) => {
16102
+ feed.sink(reporter.wait(waitMs));
16103
+ },
16104
+ end: (result) => {
16105
+ feed.sink(reporter.end(result));
16106
+ }
16107
+ };
16108
+ }
16109
+ function progressSink(output, mode, stamp = () => new Date) {
16110
+ return (line) => {
16111
+ if (mode === "json")
16112
+ output.emit(progressEvent(line, stamp()));
16113
+ else
16114
+ output.info(line.text);
16115
+ };
16116
+ }
16117
+ function commandProgressFeed(output, mode, progressSeconds, clocks = {}) {
16118
+ return {
16119
+ sink: progressSink(output, mode, clocks.stamp),
16120
+ now: clocks.now ?? (() => Date.now()),
16121
+ progressSeconds
16122
+ };
16123
+ }
16124
+
15708
16125
  // src/board/project/refresh-values.ts
15709
16126
  function projectValuesOf(facts, board) {
15710
16127
  return {
@@ -15814,8 +16231,16 @@ function writeBatchArgs(projectId, batch) {
15814
16231
  const query = `mutation(${declarations}) { ${parts.map((part) => part.mutation).join(" ")} }`;
15815
16232
  return graphqlArgs2(query, [["-f", "project", projectId], ...parts.flatMap((part) => part.fields)]);
15816
16233
  }
15817
- function batchesOf2(writes) {
15818
- return Array.from({ length: Math.ceil(writes.length / PROJECT_WRITE_BATCH_SIZE) }, (_, index) => writes.slice(index * PROJECT_WRITE_BATCH_SIZE, (index + 1) * PROJECT_WRITE_BATCH_SIZE));
16234
+ function requirePace(batchSize, pauseMs) {
16235
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1) {
16236
+ throw new RangeError(`not a batch size of writes: ${String(batchSize)}, expected a whole number from 1`);
16237
+ }
16238
+ if (!Number.isSafeInteger(pauseMs) || pauseMs < 0) {
16239
+ throw new RangeError(`not a pause between write requests: ${String(pauseMs)}, expected a whole number from 0`);
16240
+ }
16241
+ }
16242
+ function batchesOf2(writes, batchSize) {
16243
+ return Array.from({ length: Math.ceil(writes.length / batchSize) }, (_, index) => writes.slice(index * batchSize, (index + 1) * batchSize));
15819
16244
  }
15820
16245
  function isRateLimited(stdout, stderr) {
15821
16246
  const answer3 = parsed(stdout);
@@ -15852,26 +16277,46 @@ async function sendBatch(gh, args, size) {
15852
16277
  function itemCount(writes) {
15853
16278
  return new Set(writes.map((write) => write.itemId)).size;
15854
16279
  }
15855
- async function writeProjectFields(gh, projectId, writes, seams = {}) {
15856
- const sleep = seams.sleep ?? ((ms) => Bun.sleep(ms));
15857
- const batches = batchesOf2(writes);
16280
+ async function writeProjectFields(gh, projectId, writes, options) {
16281
+ const { batchSize, pauseMs } = options;
16282
+ requirePace(batchSize, pauseMs);
16283
+ const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
16284
+ const batches = batchesOf2(writes, batchSize);
15858
16285
  const requests = batches.map((batch) => writeBatchArgs(projectId, batch));
16286
+ const phase = openPhase(options.progress, "writes", writes.length);
15859
16287
  let sent = 0;
15860
16288
  for (const [index, batch] of batches.entries()) {
15861
- if (index > 0)
15862
- await sleep(PROJECT_WRITE_PAUSE_MS);
16289
+ if (index > 0) {
16290
+ if (pauseMs > 0)
16291
+ phase.wait(pauseMs);
16292
+ await sleep(pauseMs);
16293
+ }
15863
16294
  const outcome = await sendBatch(gh, requests[index] ?? [], batch.length);
15864
16295
  if (outcome.rateLimited) {
15865
16296
  const unwritten = [...batch.filter((_, at) => !outcome.landed.has(at)), ...batches.slice(index + 1).flat()];
15866
- return { written: sent + outcome.landed.size, notUpdated: itemCount(unwritten), rateLimited: true, detail: outcome.detail };
16297
+ const written2 = sent + outcome.landed.size;
16298
+ phase.end({ done: written2, refused: writes.length - written2 });
16299
+ return { written: written2, notUpdated: itemCount(unwritten), rateLimited: true, detail: outcome.detail };
15867
16300
  }
15868
16301
  sent += batch.length;
16302
+ phase.advance(sent);
15869
16303
  }
16304
+ phase.end({ done: sent, refused: 0 });
15870
16305
  return { written: sent, notUpdated: 0, rateLimited: false, detail: "" };
15871
16306
  }
15872
16307
 
15873
16308
  // src/board/project/refresh.ts
15874
16309
  var PREFIX20 = "board project refresh";
16310
+ function writesOptionsOf(options) {
16311
+ const { sleep, progress } = options;
16312
+ return {
16313
+ batchSize: options.config.boardProjectWriteBatchSize,
16314
+ pauseMs: options.config.boardProjectWritePauseMs,
16315
+ ...sleep === undefined ? {} : { sleep },
16316
+ ...progress === undefined ? {} : { progress }
16317
+ };
16318
+ }
16319
+ var NOTHING_READ = Object.freeze({ changes: [], refused: [] });
15875
16320
  var NOTHING_WRITTEN = Object.freeze({ written: 0, notUpdated: 0, rateLimited: false, detail: "" });
15876
16321
  var NO_UNLABELLED_SEARCH = () => Promise.resolve([]);
15877
16322
  function issueNumbers(issues) {
@@ -15921,16 +16366,17 @@ async function readRefreshBoard(config, gh, repository) {
15921
16366
  async function changesFor(options, repository, project, present, read) {
15922
16367
  const { config, gh } = options;
15923
16368
  const numbers = [...present.keys()];
15924
- const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, numbers);
16369
+ const phase = openPhase(options.progress, "facts", numbers.length);
16370
+ const { facts, refused } = await readIssueFacts({ gh, fragments: config.releaseFragments, onRead: phase.advance }, numbers);
16371
+ phase.end({ done: facts.size, refused: refused.length });
15925
16372
  const board = read ?? await readRefreshBoard(config, gh, repository);
15926
16373
  const { matched } = matchProjectFields(project);
15927
- return numbers.flatMap((issue3) => {
15928
- const read2 = facts.get(issue3);
16374
+ const changes = numbers.flatMap((issue3) => {
16375
+ const issueFacts = facts.get(issue3);
15929
16376
  const item = present.get(issue3);
15930
- if (read2 === undefined || item === undefined)
15931
- throw new Error(`${PREFIX20}: no facts were read for #${String(issue3)}`);
15932
- return projectChangesOf(issue3, item, projectValuesOf(read2, board), matched);
16377
+ return issueFacts === undefined || item === undefined ? [] : projectChangesOf(issue3, item, projectValuesOf(issueFacts, board), matched);
15933
16378
  });
16379
+ return { changes, refused };
15934
16380
  }
15935
16381
  function rankShifted(issue3, item, rankField, rank) {
15936
16382
  const values = { stage: null, horizon: null, rank, blockedBy: null, progress: null };
@@ -15962,13 +16408,14 @@ async function refreshOn(options, repository, ref3, asked, widening) {
15962
16408
  }));
15963
16409
  const missing = numbers.filter((issue3) => !present.has(issue3));
15964
16410
  const { mismatched: skipped2 } = matchProjectFields(project);
15965
- const changes = present.size === 0 ? [] : await changesFor(options, repository, project, present, board);
15966
- const writes = changes.length === 0 || options.dryRun === true ? NOTHING_WRITTEN : await writeProjectFields(gh, project.id, changes.map(({ write }) => write), options.sleep === undefined ? {} : { sleep: options.sleep });
16411
+ const { changes, refused } = present.size === 0 ? NOTHING_READ : await changesFor(options, repository, project, present, board);
16412
+ const writes = changes.length === 0 || options.dryRun === true ? NOTHING_WRITTEN : await writeProjectFields(gh, project.id, changes.map(({ write }) => write), writesOptionsOf(options));
15967
16413
  const warnings = [
16414
+ ...refused.map(notRefreshedWarning),
15968
16415
  ...writes.rateLimited ? [rateLimitWarning(writes.notUpdated)] : [],
15969
16416
  ...skipped2.map(skippedFieldWarning)
15970
16417
  ];
15971
- return { kind: "refreshed", project: ref3, changes, writes, missing, skipped: skipped2, warnings };
16418
+ return { kind: "refreshed", project: ref3, changes, writes, missing, skipped: skipped2, refused, warnings };
15972
16419
  }
15973
16420
  async function refreshProjectItems(options, issues, widening = {}) {
15974
16421
  const { config, gh } = options;
@@ -15997,7 +16444,13 @@ async function refreshProjectItems(options, issues, widening = {}) {
15997
16444
 
15998
16445
  // src/board/project/issue-board-refresh.ts
15999
16446
  function refreshIssueItems(options, issues, widening = {}) {
16000
- return refreshProjectItems(options, issues, widening);
16447
+ return refreshProjectItems(retrying(options), issues, widening);
16448
+ }
16449
+ function retrying(options) {
16450
+ if (options.config.boardProjectNumber === null)
16451
+ return options;
16452
+ const seams = options.sleep === undefined ? {} : { sleep: options.sleep };
16453
+ return { ...options, gh: openProjectRunner(options.gh, options.config, seams) };
16001
16454
  }
16002
16455
  function refreshFailedWarning(issue3, error) {
16003
16456
  return `The project was not updated for #${String(issue3)}: ${messageOf(error)}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
@@ -16037,7 +16490,7 @@ function createRefreshingGhIssueBoard(options) {
16037
16490
  });
16038
16491
  const refreshOptions = sleep === undefined ? { config, gh } : { config, gh, sleep };
16039
16492
  return withProjectRefresh(createGhIssueBoard({ gh }), {
16040
- refresh: (issues) => refreshProjectItems(refreshOptions, issues),
16493
+ refresh: (issues) => refreshProjectItems(retrying(refreshOptions), issues),
16041
16494
  warn
16042
16495
  });
16043
16496
  }
@@ -16073,8 +16526,8 @@ function createLinePrompter(input, output) {
16073
16526
  return next;
16074
16527
  if (ended)
16075
16528
  return null;
16076
- return new Promise((settle) => {
16077
- waiting.push(settle);
16529
+ return new Promise((settle2) => {
16530
+ waiting.push(settle2);
16078
16531
  });
16079
16532
  },
16080
16533
  close: () => {
@@ -21211,7 +21664,7 @@ function settleWhy(waiting) {
21211
21664
  return `${count} ${wait} on ${waiting.base} and ${fold} into ${waiting.version}`;
21212
21665
  }
21213
21666
  function readFollowUps(reading2) {
21214
- const { liveLoopBranches, rafaCheckout, runtimeInstalled, settle, version: version2 } = reading2;
21667
+ const { liveLoopBranches, rafaCheckout, runtimeInstalled, settle: settle2, version: version2 } = reading2;
21215
21668
  const followUps = [];
21216
21669
  if (version2 !== null && rafaCheckout && !runtimeInstalled) {
21217
21670
  followUps.push({
@@ -21220,8 +21673,8 @@ function readFollowUps(reading2) {
21220
21673
  why: selfUpdateWhy(version2, liveLoopBranches)
21221
21674
  });
21222
21675
  }
21223
- if (settle !== null) {
21224
- followUps.push({ id: "release-settle", command: RELEASE_SETTLE_COMMAND, why: settleWhy(settle) });
21676
+ if (settle2 !== null) {
21677
+ followUps.push({ id: "release-settle", command: RELEASE_SETTLE_COMMAND, why: settleWhy(settle2) });
21225
21678
  }
21226
21679
  return Object.freeze(followUps.map((followUp) => Object.freeze(followUp)));
21227
21680
  }
@@ -24702,7 +25155,7 @@ function matchIn(shape, field4) {
24702
25155
  return null;
24703
25156
  return { kind: shape.kind, shape: shape.name, evidence: found[0] };
24704
25157
  }
24705
- function firstMatch(fields) {
25158
+ function firstMatch2(fields) {
24706
25159
  for (const shape of SHAPES2) {
24707
25160
  for (const field4 of fields) {
24708
25161
  const found = matchIn(shape, field4);
@@ -24713,7 +25166,7 @@ function firstMatch(fields) {
24713
25166
  return null;
24714
25167
  }
24715
25168
  function readMachineFault(bug) {
24716
- return firstMatch(fieldsOf(bug));
25169
+ return firstMatch2(fieldsOf(bug));
24717
25170
  }
24718
25171
  function isMachineFault(bug) {
24719
25172
  return readMachineFault(bug) !== null;
@@ -30475,7 +30928,7 @@ function resultOf(run, action, fields = {}) {
30475
30928
  ...fields
30476
30929
  };
30477
30930
  }
30478
- async function settle(run, action, ref8, foundBy, supersedes) {
30931
+ async function settle2(run, action, ref8, foundBy, supersedes) {
30479
30932
  const { store } = run.route;
30480
30933
  const { key } = run.filing;
30481
30934
  if (store === null || key === null)
@@ -30487,13 +30940,13 @@ async function fileNew(run, extras = {}) {
30487
30940
  const { filing } = run;
30488
30941
  const draft = extras.supersedes === undefined && extras.duplicates === undefined ? filing.draft : { ...filing.draft, body: issueText(ISSUE_OPENING, { ...filing.values, ...extras }, run.redact) };
30489
30942
  const created = await step(run, `the ${run.route.channel} tracker create`, () => run.route.tracker.create(draft));
30490
- return created.ok ? settle(run, "filed", created.value, null, extras.supersedes) : resultOf(run, "failed", { problem: created.problem });
30943
+ return created.ok ? settle2(run, "filed", created.value, null, extras.supersedes) : resultOf(run, "failed", { problem: created.problem });
30491
30944
  }
30492
30945
  async function commentOn(run, ref8, foundBy, action = "commented", comment = run.filing.comment) {
30493
30946
  const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(ref8, comment));
30494
30947
  if (!commented.ok)
30495
30948
  return resultOf(run, "failed", { ref: ref8, foundBy, problem: commented.problem });
30496
- return foundBy === "store" ? resultOf(run, action, { ref: ref8, foundBy }) : settle(run, action, ref8, foundBy);
30949
+ return foundBy === "store" ? resultOf(run, action, { ref: ref8, foundBy }) : settle2(run, action, ref8, foundBy);
30497
30950
  }
30498
30951
  async function lookUp2(run, key, searchText) {
30499
30952
  const { route, filing } = run;
@@ -30833,7 +31286,7 @@ var BRANCH_FORMAT = [
30833
31286
  ].join("%09");
30834
31287
  var REMOTE3 = "origin";
30835
31288
  var FIELD_COUNT = 5;
30836
- var MS_PER_SECOND = 1000;
31289
+ var MS_PER_SECOND3 = 1000;
30837
31290
  var GONE_TRACK = "gone";
30838
31291
  function readRemoteHead(git) {
30839
31292
  const result = git(["symbolic-ref", "--quiet", "--short", `refs/remotes/${REMOTE3}/HEAD`]);
@@ -30891,7 +31344,7 @@ function parseBranchLine(line) {
30891
31344
  if (unix === "" || !Number.isInteger(seconds)) {
30892
31345
  return `git for-each-ref wrote no commit date for ${name2}: ${JSON.stringify(unix)}`;
30893
31346
  }
30894
- const lastCommit = new Date(seconds * MS_PER_SECOND);
31347
+ const lastCommit = new Date(seconds * MS_PER_SECOND3);
30895
31348
  const current = head === "*";
30896
31349
  if (upstreamField === "") {
30897
31350
  return { current, branch: { name: name2, upstream: null, gone: false, ahead: null, lastCommit } };
@@ -34248,4 +34701,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
34248
34701
  }
34249
34702
  }
34250
34703
 
34251
- export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseTreePaths, untrackedLeftLine, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, PROJECT_WRITE_PAUSE_MS, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, CHANGELOG_TITLE, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, readRemoteHead, eventsFilePath, WORKTREES_SUBDIR, resolveWorktreePath, crossRepositoryRefusal, addResolveWorktree, removeResolveWorktree, pushResolved, worktreeAt, worktreeDirAt, WORKTREE_STATUS, countChanges, cleanupSteps, dryRunLines, runCleanupSteps, defaultCleanupSeams, readCleanup, cleanupCounts, createLinePrompter, openNextRelations, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, LABELS_ONLY_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter2 as lazyPrompter, ready_default, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, readBlockedIssues, renderBlockedIssues, blockerToken, addBlockerToBody, resolveCreateSpec, TRIAGE_MODULE, namedSecrets, triageReport, unresolvedTracker, DISK_ROOTS_FILE_SYSTEM, gitToplevel, rootRefusal, rootCandidates, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, retargetedLine, MECHANICAL_CONFLICT_SENTENCE, start, createPlanRefsVerifier, readDoctorRefs, renderDoctorRefs, createPlanDirNames, hasPlanFor, HORIZONS, horizonOf, readListedEpics, stageOf, rankOf, readIssueFacts, readRefreshBoard, refreshProjectItems, refreshIssueItems, refreshFailedWarning, unblock_default, unblockAfterMerge, labelsLineBlockersOf, LABELS_READS, createLabelsRelations, EPIC_TYPE_LABEL, plan };
34704
+ export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseTreePaths, untrackedLeftLine, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, commandRetrySeams, openProjectRunner, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, skippedFieldWarning, notRefreshedWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, openPhase, commandProgressFeed, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, CHANGELOG_TITLE, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, readRemoteHead, eventsFilePath, WORKTREES_SUBDIR, resolveWorktreePath, crossRepositoryRefusal, addResolveWorktree, removeResolveWorktree, pushResolved, worktreeAt, worktreeDirAt, WORKTREE_STATUS, countChanges, cleanupSteps, dryRunLines, runCleanupSteps, defaultCleanupSeams, readCleanup, cleanupCounts, createLinePrompter, openNextRelations, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, LABELS_ONLY_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter2 as lazyPrompter, ready_default, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, readBlockedIssues, renderBlockedIssues, blockerToken, addBlockerToBody, resolveCreateSpec, TRIAGE_MODULE, namedSecrets, triageReport, unresolvedTracker, DISK_ROOTS_FILE_SYSTEM, gitToplevel, rootRefusal, rootCandidates, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, retargetedLine, MECHANICAL_CONFLICT_SENTENCE, start, createPlanRefsVerifier, readDoctorRefs, renderDoctorRefs, createPlanDirNames, hasPlanFor, HORIZONS, horizonOf, readListedEpics, stageOf, rankOf, readIssueFacts, readRefreshBoard, refreshProjectItems, refreshIssueItems, refreshFailedWarning, unblock_default, unblockAfterMerge, labelsLineBlockersOf, LABELS_READS, createLabelsRelations, EPIC_TYPE_LABEL, plan };