@open-tomato/rafa 0.39.0 → 0.41.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.
@@ -39,9 +39,9 @@ import {
39
39
  NO_NEXT_STEP,
40
40
  NoSessionRefusal,
41
41
  ORIGIN_FIELD,
42
+ PLACEHOLDER_REASON,
42
43
  PREVIOUS_COPY_WARN_ABOVE,
43
44
  PROJECT_SCOPE_FIX,
44
- PROJECT_WRITE_PAUSE_MS,
45
45
  PROVENANCE_FIELD,
46
46
  REASON_FLAG,
47
47
  RELEASE_BASE_BRANCH,
@@ -94,6 +94,7 @@ import {
94
94
  branchName,
95
95
  buildReport,
96
96
  buildSettle,
97
+ buildVerifyPrompt,
97
98
  bundledAgentsDirectory,
98
99
  byName,
99
100
  cancelMoveReason,
@@ -111,6 +112,8 @@ import {
111
112
  closedIssuesIn,
112
113
  collect,
113
114
  collectSessionRow,
115
+ commandProgressFeed,
116
+ commandRetrySeams,
114
117
  commandWords,
115
118
  compareVersions,
116
119
  configuredMoveRelations,
@@ -133,6 +136,7 @@ import {
133
136
  createPlanDirNames,
134
137
  createPlanRefsVerifier,
135
138
  createStartPreflightClaim,
139
+ criteriaToAsk,
136
140
  crossRepositoryRefusal,
137
141
  defaultCleanupSeams,
138
142
  developmentApplied,
@@ -149,9 +153,10 @@ import {
149
153
  epicTickSentence,
150
154
  estimateEta,
151
155
  etaLine,
152
- eventsFilePath,
156
+ eventsFileOf,
153
157
  expectTwoArguments,
154
158
  failingRows,
159
+ fenceFor,
155
160
  fetchClaimBranches,
156
161
  findReadinessGaps,
157
162
  fingerprintText,
@@ -220,17 +225,21 @@ import {
220
225
  noBoardListsLine,
221
226
  normaliseReason,
222
227
  notFoundWarning,
228
+ notRefreshedWarning,
223
229
  notesFileName,
224
230
  onTracker,
225
231
  openBoards,
226
232
  openNextRelations,
227
233
  openNextSources,
234
+ openPhase,
235
+ openProjectRunner,
228
236
  parseBaseStanding,
229
237
  parseClaimMessage,
230
238
  parseReportArgs,
231
239
  parseRoadmapBody,
232
240
  parseSemanticVersion,
233
241
  parseTreePaths,
242
+ parseVerifyPlan,
234
243
  parseWorkingTree,
235
244
  parseWorktrees,
236
245
  pathDirectories,
@@ -269,6 +278,7 @@ import {
269
278
  readEpicDependents,
270
279
  readEpicProblems,
271
280
  readEpics,
281
+ readEventsFrom,
272
282
  readGuard,
273
283
  readHomeAfterLoop,
274
284
  readIssueFacts,
@@ -299,6 +309,7 @@ import {
299
309
  readTextFlag,
300
310
  readTrees,
301
311
  readTrendInputs,
312
+ readVerifyPrompt,
302
313
  readWaiting,
303
314
  ready_default,
304
315
  receiptProblem,
@@ -316,6 +327,7 @@ import {
316
327
  renderDependentComment,
317
328
  renderDoctorEffortSync,
318
329
  renderDoctorRefs,
330
+ renderEpicBody,
319
331
  renderHorizonComment,
320
332
  renderMoveComment,
321
333
  renderParkedPullRequestComment,
@@ -361,7 +373,9 @@ import {
361
373
  shellQuote,
362
374
  show_default,
363
375
  skillItem,
376
+ skippedFieldWarning,
364
377
  sourceItem,
378
+ splitCriteria,
365
379
  stackPaths,
366
380
  stacksFromEccSkillName,
367
381
  stageOf,
@@ -395,7 +409,7 @@ import {
395
409
  worktreesHolding,
396
410
  writeSession,
397
411
  writeShippedFile
398
- } from "./index-nwgwxfkt.js";
412
+ } from "./index-wjsarzf5.js";
399
413
  import {
400
414
  PROBE_TIMEOUT_MS,
401
415
  loadPlanPrerequisites,
@@ -407,7 +421,7 @@ import {
407
421
  runPreflight,
408
422
  splitBlockerComment,
409
423
  trackerPathFor
410
- } from "./index-g4116t9m.js";
424
+ } from "./index-zg0qsb5b.js";
411
425
  import {
412
426
  ACTION_HEADING,
413
427
  CAUSE_HEADING,
@@ -518,7 +532,7 @@ import {
518
532
  versionLine,
519
533
  writeFrontmatter,
520
534
  writePositionFile
521
- } from "./index-1r2b1052.js";
535
+ } from "./index-cd0wzc12.js";
522
536
  import {
523
537
  CONFIG_DEFAULTS,
524
538
  CONFIG_FILE,
@@ -537,7 +551,7 @@ import {
537
551
  readItemBytes,
538
552
  readRafaBlocks,
539
553
  resolveTiers
540
- } from "./index-m0wt4nr1.js";
554
+ } from "./index-3jpz9nr3.js";
541
555
  import {
542
556
  promotable
543
557
  } from "./index-d6d0942q.js";
@@ -2163,7 +2177,7 @@ import { join as join4, relative as relative2 } from "path";
2163
2177
  var SEARCH_PROMPT_PREFIX = "# Inventory search instructions";
2164
2178
  var SEARCH_BLOCK_FENCE = "rafa:search";
2165
2179
  var MIN_FENCE_LENGTH = 3;
2166
- function fenceFor(text) {
2180
+ function fenceFor2(text) {
2167
2181
  const longestRun = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
2168
2182
  return "`".repeat(Math.max(MIN_FENCE_LENGTH, longestRun + 1));
2169
2183
  }
@@ -2179,7 +2193,7 @@ var EXAMPLE_BLOCK = [
2179
2193
  ];
2180
2194
  function renderSearchPrompt(input) {
2181
2195
  const plural2 = `${input.kind}s`;
2182
- const fence = fenceFor(input.question);
2196
+ const fence = fenceFor2(input.question);
2183
2197
  const candidates = input.candidates.map((candidate) => `- ${candidate.name}: \`${candidate.file}\``);
2184
2198
  return [
2185
2199
  SEARCH_PROMPT_PREFIX,
@@ -3309,8 +3323,17 @@ async function addIssues(options, ref, issues) {
3309
3323
  const project = await port.find(ref);
3310
3324
  if (project === null)
3311
3325
  return null;
3312
- for (const number of issues)
3313
- await port.addItem(project.id, { repository, number });
3326
+ const phase = openPhase(options.progress, "adds", issues.length);
3327
+ let added = 0;
3328
+ try {
3329
+ for (const number of issues) {
3330
+ await port.addItem(project.id, { repository, number });
3331
+ added += 1;
3332
+ phase.advance(added);
3333
+ }
3334
+ } finally {
3335
+ phase.end({ done: added, refused: issues.length - added });
3336
+ }
3314
3337
  return issues;
3315
3338
  }
3316
3339
  async function addMissing(options, first, synced) {
@@ -3327,6 +3350,7 @@ async function addMissing(options, first, synced) {
3327
3350
  ...synced,
3328
3351
  changes: [...first.changes, ...second.changes],
3329
3352
  added,
3353
+ refused: [...first.refused, ...second.refused],
3330
3354
  writes: addedWrites(first.writes, second.writes),
3331
3355
  warnings: [...first.warnings, ...second.warnings.filter((line) => !first.warnings.includes(line))]
3332
3356
  };
@@ -3347,6 +3371,7 @@ async function syncProject(options) {
3347
3371
  changes: first.changes,
3348
3372
  missing: first.missing,
3349
3373
  added: [],
3374
+ refused: first.refused,
3350
3375
  writes: first.writes,
3351
3376
  warnings: first.warnings
3352
3377
  });
@@ -3364,17 +3389,21 @@ function counted(count, noun) {
3364
3389
  function changeLine(change) {
3365
3390
  return `#${String(change.issue)} ${change.name}: ${change.from ?? EMPTY_VALUE} \u2192 ${change.to ?? EMPTY_VALUE}`;
3366
3391
  }
3392
+ function refusedPart(result) {
3393
+ return result.refused.length === 0 ? "" : `; ${counted(result.refused.length, "issue")} not refreshed`;
3394
+ }
3367
3395
  function closingLine(result) {
3368
3396
  const project = `project #${String(result.project.number)}`;
3369
3397
  const adds = result.dryRun ? result.missing : result.added;
3370
3398
  const changes = counted(result.changes.length, "change");
3371
3399
  const issues = counted(adds.length, "issue");
3400
+ const inStep = result.changes.length === 0 && adds.length === 0 && result.refused.length === 0;
3372
3401
  if (result.dryRun) {
3373
- return result.changes.length === 0 && adds.length === 0 ? `Dry run on ${project}: in step, nothing to change.` : `Dry run on ${project}: ${changes} and ${issues} to add; nothing written.`;
3402
+ return inStep ? `Dry run on ${project}: in step, nothing to change.` : `Dry run on ${project}: ${changes} and ${issues} to add${refusedPart(result)}; nothing written.`;
3374
3403
  }
3375
3404
  if (result.rateLimited)
3376
3405
  return `Stopped on ${project}: ${String(result.written)} of ${changes} written; the rate limit refused the rest.`;
3377
- return result.changes.length === 0 && adds.length === 0 ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added.`;
3406
+ return inStep ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added${refusedPart(result)}.`;
3378
3407
  }
3379
3408
  function syncLines(result) {
3380
3409
  const adds = result.dryRun ? result.missing.map((issue) => `#${String(issue)} is not on the project: would be added`) : result.added.map((issue) => `#${String(issue)} added to the project`);
@@ -3390,6 +3419,7 @@ function syncResultOf(synced) {
3390
3419
  changes: synced.changes.map(plainChange),
3391
3420
  missing: synced.missing,
3392
3421
  added: synced.added,
3422
+ refused: synced.refused.map(({ number, reason }) => ({ issue: number, reason })),
3393
3423
  written: synced.writes.written,
3394
3424
  rateLimited: synced.writes.rateLimited,
3395
3425
  notUpdated: synced.writes.notUpdated,
@@ -3407,9 +3437,10 @@ async function runBoardSync(context, seams) {
3407
3437
  if (config.boardProjectNumber === null) {
3408
3438
  throw new CommandExit(1, `\u274C board.project.number is not set, so there is no project to sync. Run \`${PROJECT_INIT_FIX}\` to create one.`);
3409
3439
  }
3410
- const gh = seams.gh ?? createGhRunner({ cwd: project.root });
3440
+ const gh = openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep));
3411
3441
  const dryRun = context.flags[DRY_RUN_FLAG] === true;
3412
- const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun } : { config, gh, dryRun, sleep: seams.sleep }).catch((error) => {
3442
+ const progress = commandProgressFeed(context.output, context.outputMode, config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now });
3443
+ const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun, progress } : { config, gh, dryRun, progress, sleep: seams.sleep }).catch((error) => {
3413
3444
  throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C Could not sync the project: ${messageOf(error)}`);
3414
3445
  });
3415
3446
  if (synced.kind !== "synced") {
@@ -3439,7 +3470,7 @@ function createBoardSyncCommand(seams = {}) {
3439
3470
  subject: "board",
3440
3471
  action: "sync",
3441
3472
  summary: "bring the repository's GitHub project in step with its issues",
3442
- description: "Refreshes every item of this repository on the GitHub project board.project.number names, writing" + " only the Stage, Horizon, Rank, Blocked by and Progress values that differ from what rafa reads of the" + " issues, then adds every open issue missing from the project and fills its fields. It repairs what changed" + " outside rafa: a label edited in the web UI, an issue closed by hand, the Roadmap issue's order edited in" + " its body. Prints one line per change and per issue added, then a closing count. With `--dry-run` it" + " prints the same lines and writes nothing. With `--output=json` the changes are the data of the terminal" + " result event. Refused with exit code 1 when board.project.number is unset, and with exit code 2 when the" + " token lacks the project scope, the number names no project, or the rate limit stopped the writes.",
3473
+ description: "Refreshes every item of this repository on the GitHub project board.project.number names, writing" + " only the Stage, Horizon, Rank, Blocked by and Progress values that differ from what rafa reads of the" + " issues, then adds every open issue missing from the project and fills its fields. It repairs what changed" + " outside rafa: a label edited in the web UI, an issue closed by hand, the Roadmap issue's order edited in" + " its body. Prints one line per change and per issue added, then a closing count. With `--dry-run` it" + " prints the same lines and writes nothing. With `--output=json` the changes are the data of the terminal" + " result event. An issue whose facts could not be read is named in a warning, `#<n> not refreshed:" + " <reason>`, while the others are synced; the exit code stays 0 and the next run reads it again. Adding" + " the issues, reading their facts and writing their fields each print a start line with the total, a" + " progress line at most every board.project.progressSeconds, as `reading facts: 146/283, 6m 40s`, and an" + " end line; with `--output=json` each is a `progress` event. Refused" + " with exit code 1 when board.project.number is unset, and with exit code 2 when the token lacks the" + " project scope, the number names no project, or the rate limit stopped the writes.",
3443
3474
  args: [],
3444
3475
  flags: [
3445
3476
  {
@@ -8274,41 +8305,76 @@ async function missingIssues(options, repository, project) {
8274
8305
  const held = heldIssues(await createGhProjectPort(gh).items(project.id), repository);
8275
8306
  const ranked = board.closed.filter((issue) => rankOf(board.ranks, issue) !== null);
8276
8307
  const unread = board.closed.filter((issue) => rankOf(board.ranks, issue) === null && !held.has(issue));
8277
- const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
8308
+ const { facts } = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
8278
8309
  const inReview = unread.filter((issue) => {
8279
8310
  const read = facts.get(issue);
8280
8311
  return read !== undefined && stageOf(read) === "In review";
8281
8312
  });
8282
- return [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
8313
+ const missing = [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
8314
+ return { held, missing };
8283
8315
  }
8284
8316
  async function itemsStep(options, repository, project) {
8285
8317
  const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
8286
- let missing;
8318
+ let wanted;
8287
8319
  try {
8288
- missing = await missingIssues(options, repository, project);
8320
+ wanted = await missingIssues(options, repository, project);
8289
8321
  } catch (error) {
8290
- return partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`);
8322
+ return { part: partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`), holds: null };
8291
8323
  }
8324
+ const { held, missing } = wanted;
8292
8325
  if (missing.length === 0)
8293
- return partOf("items", ITEMS_PART, "present", "the project holds every issue it shows");
8326
+ return { part: partOf("items", ITEMS_PART, "present", "the project holds every issue it shows"), holds: held.size };
8294
8327
  const port = createGhProjectPort(options.gh);
8328
+ const phase = openPhase(options.progress, "adds", missing.length);
8295
8329
  let added = 0;
8296
8330
  try {
8297
8331
  for (const number of missing) {
8298
8332
  if (added > 0)
8299
- await sleep(PROJECT_WRITE_PAUSE_MS);
8333
+ await sleep(options.config.boardProjectWritePauseMs);
8300
8334
  await port.addItem(project.id, { repository, number });
8301
8335
  added += 1;
8336
+ phase.advance(added);
8302
8337
  }
8303
8338
  } catch (error) {
8339
+ phase.end({ done: added, refused: missing.length - added });
8304
8340
  const went = `${issueCount(added)} of ${String(missing.length)} added`;
8305
- return partOf("items", ITEMS_PART, "refused", `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`);
8341
+ const why = `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`;
8342
+ return { part: partOf("items", ITEMS_PART, "refused", why), holds: held.size + added };
8306
8343
  }
8307
- return partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`);
8344
+ phase.end({ done: added, refused: 0 });
8345
+ return { part: partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`), holds: held.size + added };
8308
8346
  }
8309
- async function fieldsStep(options, project) {
8347
+ function withListed(part, listed2) {
8348
+ return listed2.length === 0 ? part : { ...part, listed: listed2 };
8349
+ }
8350
+ function refusedClause(refused) {
8351
+ return refused === 0 ? "" : `; ${issueCount(refused)} not refreshed`;
8352
+ }
8353
+ function refreshedPart(refreshed, holds) {
8354
+ const { writes, changes, skipped, refused } = refreshed;
8355
+ const listed2 = refused.map(notRefreshedWarning);
8356
+ if (writes.rateLimited)
8357
+ return withListed(partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), listed2);
8358
+ if (changes.length === 0 && refused.length > 0 && (holds === null || holds <= refused.length)) {
8359
+ return withListed(partOf("fields", FIELDS_PART, "refused", `no item was filled: ${issueCount(refused.length)} not refreshed`), listed2);
8360
+ }
8361
+ if (changes.length === 0) {
8362
+ const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
8363
+ return withListed(partOf("fields", FIELDS_PART, "present", `${held}${refusedClause(refused.length)}`), listed2);
8364
+ }
8365
+ const issues = new Set(changes.map(({ issue }) => issue)).size;
8366
+ const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
8367
+ return withListed(partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}${refusedClause(refused.length)}`), listed2);
8368
+ }
8369
+ async function fieldsStep(options, project, holds) {
8310
8370
  const config = { ...options.config, boardProjectNumber: project.number };
8311
- const refreshOptions = options.sleep === undefined ? { config, gh: options.gh } : { config, gh: options.gh, sleep: options.sleep };
8371
+ const { sleep, progress } = options;
8372
+ const refreshOptions = {
8373
+ config,
8374
+ gh: options.gh,
8375
+ ...sleep === undefined ? {} : { sleep },
8376
+ ...progress === undefined ? {} : { progress }
8377
+ };
8312
8378
  let refreshed;
8313
8379
  try {
8314
8380
  refreshed = await refreshProjectItems(refreshOptions, [], { everyItem: true });
@@ -8320,24 +8386,13 @@ async function fieldsStep(options, project) {
8320
8386
  if (refreshed.kind !== "refreshed") {
8321
8387
  return { part: partOf("fields", FIELDS_PART, "refused", refreshed.warnings.join(" ") || "the refresh sent no call"), problems: [] };
8322
8388
  }
8323
- const { writes, changes, skipped } = refreshed;
8324
- const problems = refreshed.warnings.slice(writes.rateLimited ? 1 : 0);
8325
- if (writes.rateLimited) {
8326
- return { part: partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), problems };
8327
- }
8328
- if (changes.length === 0) {
8329
- const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
8330
- return { part: partOf("fields", FIELDS_PART, "present", held), problems };
8331
- }
8332
- const issues = new Set(changes.map(({ issue }) => issue)).size;
8333
- const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
8334
- return { part: partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}`), problems };
8389
+ return { part: refreshedPart(refreshed, holds), problems: refreshed.skipped.map(skippedFieldWarning) };
8335
8390
  }
8336
8391
  async function onProject(options, repository, project) {
8337
8392
  const items = await itemsStep(options, repository, project);
8338
- const fields = await fieldsStep(options, project);
8393
+ const fields = await fieldsStep(options, project, items.holds);
8339
8394
  const setting = writeProjectNumber(options.root, project.number);
8340
- return { parts: [items, fields.part, setting], problems: fields.problems };
8395
+ return { parts: [items.part, fields.part, setting], problems: fields.problems };
8341
8396
  }
8342
8397
  async function setUpProject(options) {
8343
8398
  const { gh, config } = options;
@@ -8369,12 +8424,16 @@ function projectSetupChanged(report2) {
8369
8424
  }
8370
8425
  var PROJECT_HEADING = "GitHub project:";
8371
8426
  var OUTCOME_WIDTH3 = 7;
8427
+ var LISTED_INDENT = " ".repeat(2 + OUTCOME_WIDTH3 + 2);
8372
8428
  function projectPartLine(part) {
8373
8429
  const row = ` ${part.outcome.padEnd(OUTCOME_WIDTH3, " ")} ${part.name}`;
8374
8430
  return part.outcome === "present" ? row : `${row}: ${part.detail}`;
8375
8431
  }
8432
+ function projectPartLines(part) {
8433
+ return [projectPartLine(part), ...(part.listed ?? []).map((line) => `${LISTED_INDENT}${line}`)];
8434
+ }
8376
8435
  function renderProjectSetup(report2) {
8377
- return [PROJECT_HEADING, ...report2.parts.map((part) => projectPartLine(part)), ...report2.problems];
8436
+ return [PROJECT_HEADING, ...report2.parts.flatMap((part) => projectPartLines(part)), ...report2.problems];
8378
8437
  }
8379
8438
  var PROJECT_QUESTION = "Also create a GitHub project with roadmap and kanban views? [y/N] ";
8380
8439
  var PROJECT_NO_BOARD_WARNING = "--project creates the GitHub project with the GitHub board," + ` and the board step did not run, so no project was created; run ${PROJECT_STEP_FIX}.`;
@@ -8394,6 +8453,13 @@ function withWrittenRoadmap(root, config) {
8394
8453
  const written = readRoadmapSetting(root).issue;
8395
8454
  return written === null ? config : { ...config, roadmapIssue: written };
8396
8455
  }
8456
+ function retrySeamsOf(options) {
8457
+ const { sleep, onRetry } = options;
8458
+ return {
8459
+ ...sleep === undefined ? {} : { sleep },
8460
+ ...onRetry === undefined ? {} : { onRetry }
8461
+ };
8462
+ }
8397
8463
  async function runProjectStep(options) {
8398
8464
  const { wanted, board, root, isTerminal, openPrompter } = options;
8399
8465
  if (board.status !== "ran") {
@@ -8403,7 +8469,16 @@ async function runProjectStep(options) {
8403
8469
  return noProject("declined", false);
8404
8470
  const run = async (asked) => {
8405
8471
  const setUp = options.setUp ?? setUpProject;
8406
- const report2 = await setUp({ root, config: withWrittenRoadmap(root, options.config), gh: options.openGh() });
8472
+ const config = withWrittenRoadmap(root, options.config);
8473
+ const gh = openProjectRunner(options.openGh(), config, retrySeamsOf(options));
8474
+ const { sleep, progress } = options;
8475
+ const report2 = await setUp({
8476
+ root,
8477
+ config,
8478
+ gh,
8479
+ ...sleep === undefined ? {} : { sleep },
8480
+ ...progress === undefined ? {} : { progress }
8481
+ });
8407
8482
  return { status: "ran", asked, report: report2, warnings: [] };
8408
8483
  };
8409
8484
  if (wanted === true)
@@ -8481,9 +8556,10 @@ function fieldsRow(project, template) {
8481
8556
  return rowOf2("fields", FIELDS_ROW, "missing", `${sentences} ${fix}`);
8482
8557
  }
8483
8558
  async function readDoctorProject(input) {
8484
- const { gh, number, template } = input;
8485
- if (gh === null || number === null)
8559
+ const { number, template } = input;
8560
+ if (input.gh === null || number === null)
8486
8561
  return null;
8562
+ const gh = openProjectRunner(input.gh, input.config, input.retry);
8487
8563
  const scope = await readScopeRow(gh);
8488
8564
  if (scope.outcome !== "present") {
8489
8565
  const project = notRead("project", `${PROJECT_ROW} ${String(number)}`, scope);
@@ -8753,7 +8829,7 @@ function optionalString(record, key) {
8753
8829
  }
8754
8830
 
8755
8831
  // src/commands/stretch/item.ts
8756
- import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
8832
+ import { closeSync, mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
8757
8833
  import { dirname as dirname6, join as join20, posix } from "path";
8758
8834
 
8759
8835
  // src/stretch/operators.ts
@@ -8928,83 +9004,6 @@ function lineText(argv) {
8928
9004
  // src/commands/stretch/item-merge.ts
8929
9005
  import { readFileSync as readFileSync14 } from "fs";
8930
9006
 
8931
- // src/loop/events-file.ts
8932
- import { closeSync, fstatSync, openSync, readSync } from "fs";
8933
- var NEWLINE = 10;
8934
- function eventsFileOf(root, record) {
8935
- return eventsFilePath(root, record.sessionId);
8936
- }
8937
- function isPlainObject(value) {
8938
- return typeof value === "object" && value !== null && !Array.isArray(value);
8939
- }
8940
- function parseEventLine(line) {
8941
- let value;
8942
- try {
8943
- value = JSON.parse(line);
8944
- } catch {
8945
- return null;
8946
- }
8947
- if (!isPlainObject(value))
8948
- return null;
8949
- const { name: name2, summary, data, ts } = value;
8950
- if (typeof name2 !== "string" || typeof summary !== "string" || typeof ts !== "string")
8951
- return null;
8952
- if (!isPlainObject(data))
8953
- return null;
8954
- return Object.freeze({ name: name2, summary, data: Object.freeze({ ...data }), ts });
8955
- }
8956
- function bytesFrom(fd, offset) {
8957
- const length = fstatSync(fd).size - offset;
8958
- if (length <= 0)
8959
- return null;
8960
- const buffer = Buffer.alloc(length);
8961
- let filled = 0;
8962
- while (filled < length) {
8963
- const read = readSync(fd, buffer, filled, length - filled, offset + filled);
8964
- if (read === 0)
8965
- break;
8966
- filled += read;
8967
- }
8968
- return buffer.subarray(0, filled);
8969
- }
8970
- function readTail(file, offset) {
8971
- let fd;
8972
- try {
8973
- fd = openSync(file, "r");
8974
- } catch (error) {
8975
- if (errorCode(error) === "ENOENT")
8976
- return "absent";
8977
- throw error;
8978
- }
8979
- try {
8980
- return bytesFrom(fd, offset);
8981
- } finally {
8982
- closeSync(fd);
8983
- }
8984
- }
8985
- function readEventsFrom(file, offset) {
8986
- if (!Number.isSafeInteger(offset) || offset < 0) {
8987
- throw new RangeError(`events file: unusable offset ${String(offset)}`);
8988
- }
8989
- const tail = readTail(file, offset);
8990
- if (tail === "absent")
8991
- return Object.freeze({ kind: "absent" });
8992
- const end = tail?.lastIndexOf(NEWLINE) ?? -1;
8993
- if (tail === null || end < 0) {
8994
- return Object.freeze({ kind: "read", events: Object.freeze([]), malformed: 0, offset });
8995
- }
8996
- const lines = tail.subarray(0, end).toString("utf8").split(`
8997
- `);
8998
- const parsed = lines.map(parseEventLine);
8999
- const events = parsed.filter((event) => event !== null);
9000
- return Object.freeze({
9001
- kind: "read",
9002
- events: Object.freeze(events),
9003
- malformed: parsed.length - events.length,
9004
- offset: offset + end + 1
9005
- });
9006
- }
9007
-
9008
9007
  // src/stretch/items.ts
9009
9008
  import { appendFileSync, mkdirSync as mkdirSync4, readFileSync as readFileSync11 } from "fs";
9010
9009
  import { dirname as dirname3, join as join17 } from "path";
@@ -10127,7 +10126,7 @@ async function spawnOnTerminal(argv, cwd, env) {
10127
10126
  function spawnDetached(argv, cwd, env, logPath) {
10128
10127
  let fd;
10129
10128
  try {
10130
- fd = openSync2(logPath, "a");
10129
+ fd = openSync(logPath, "a");
10131
10130
  } catch {
10132
10131
  return null;
10133
10132
  }
@@ -10138,7 +10137,7 @@ function spawnDetached(argv, cwd, env, logPath) {
10138
10137
  } catch {
10139
10138
  return null;
10140
10139
  } finally {
10141
- closeSync2(fd);
10140
+ closeSync(fd);
10142
10141
  }
10143
10142
  }
10144
10143
  function defaultStretchItemSeams() {
@@ -10614,7 +10613,7 @@ async function runDoctor(context, seams) {
10614
10613
  const modeSet = preflight.resolved.sources.boardRelationships !== "default";
10615
10614
  const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue, preflight.config.boardRelationships, modeSet);
10616
10615
  const { boardProjectNumber: number, boardProjectTemplate: template } = preflight.config;
10617
- const projectRows = await readDoctorProject({ gh, number, template });
10616
+ const projectRows = await readDoctorProject({ gh, number, template, config: preflight.config, retry: commandRetrySeams(context.output, context.outputMode) });
10618
10617
  const readings = { ...board, project: projectRows, cleanup, refs, release, tiers, effortSchema, effortSync };
10619
10618
  writeText(context, renderDoctor(preflight));
10620
10619
  await announceRisk(context, preflight, seams);
@@ -12002,7 +12001,8 @@ async function refreshProjectAfterEpic(context, seams, target) {
12002
12001
  });
12003
12002
  if (config.boardProjectNumber === null)
12004
12003
  return null;
12005
- const openGh = () => seams.gh ?? createGhRunner({ cwd: project.root });
12004
+ const retry = commandRetrySeams(context.output, context.outputMode, seams.sleep);
12005
+ const openGh = () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, retry);
12006
12006
  warnings = await refreshLines(config, openGh, seams.projectRefresh ?? refreshIssueItems, target, named2);
12007
12007
  } catch (error) {
12008
12008
  warnings = [epicProjectProblemLine(named2, messageOf(error))];
@@ -13091,48 +13091,7 @@ var cancel_default = createEpicCancelCommand();
13091
13091
 
13092
13092
  // src/commands/epic/close.ts
13093
13093
  import { randomUUID as randomUUID3 } from "crypto";
13094
- import { join as join28 } from "path";
13095
-
13096
- // src/board/epic-template.ts
13097
- import { existsSync as existsSync8, readFileSync as readFileSync18 } from "fs";
13098
- import { dirname as dirname8, join as join25 } from "path";
13099
- import { fileURLToPath as fileURLToPath2 } from "url";
13100
- var MODULE_DIR2 = dirname8(fileURLToPath2(import.meta.url));
13101
- var EPIC_TEMPLATE_FILE = "epic.md";
13102
- var CRITERIA_PLACEHOLDERS = Object.freeze([
13103
- "- Placeholder: one line per thing that makes this epic finished," + " written so a check against main can tell whether it holds."
13104
- ]);
13105
- var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
13106
- function epicTemplateSource(moduleDir = MODULE_DIR2) {
13107
- return join25(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
13108
- }
13109
- function readEpicTemplate(moduleDir = MODULE_DIR2) {
13110
- const source = epicTemplateSource(moduleDir);
13111
- if (!existsSync8(source)) {
13112
- throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
13113
- }
13114
- return readFileSync18(source, "utf8");
13115
- }
13116
- function placeholderCriteria(criteria) {
13117
- if (criteria === null)
13118
- return Object.freeze([]);
13119
- const held = criteria.split(/\r\n?|\n/u).map((line) => line.trim()).filter((line) => CRITERIA_PLACEHOLDERS.includes(line));
13120
- return Object.freeze(held.map((text) => Object.freeze({ text, reason: PLACEHOLDER_REASON })));
13121
- }
13122
- function renderEpicBody(template = readEpicTemplate()) {
13123
- const read = readEpicBody(template);
13124
- if (read.criteria === null) {
13125
- throw new Error("epic template: the template carries no acceptance criteria section with a placeholder under it");
13126
- }
13127
- const missing = CRITERIA_PLACEHOLDERS.length - placeholderCriteria(read.criteria).length;
13128
- if (missing > 0) {
13129
- throw new Error(`epic template: the acceptance criteria section has lost ${String(missing)} of its placeholder lines`);
13130
- }
13131
- if (read.lines.length > 0) {
13132
- throw new Error(`epic template: the template carries ${String(read.lines.length)} checklist lines; a new epic has none`);
13133
- }
13134
- return template;
13135
- }
13094
+ import { join as join26 } from "path";
13136
13095
 
13137
13096
  // src/effort/epic-cost.ts
13138
13097
  var MEMBERSHIP_NOTE = "cost follows membership at close time: it counts the sessions of the issues in the epic now, " + "so work on an issue moved out is not counted and work on an issue moved in is";
@@ -13201,211 +13160,9 @@ function renderEpicCost(cost, estimate) {
13201
13160
  ];
13202
13161
  }
13203
13162
 
13204
- // src/epic/verify-plan.ts
13205
- import { existsSync as existsSync9, readFileSync as readFileSync19 } from "fs";
13206
- import { dirname as dirname9, join as join26 } from "path";
13207
- import { fileURLToPath as fileURLToPath3 } from "url";
13208
- var MODULE_DIR3 = dirname9(fileURLToPath3(import.meta.url));
13209
- var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
13210
- var VERIFY_BLOCK_FENCE = "rafa:verify";
13211
- var VERIFY_PROMPT_SLOTS = ["EPIC_NUMBER", "EPIC_TITLE", "CRITERIA"];
13212
- var SKIPPED_REASON = "the verification plan did not answer it";
13213
- var FENCE2 = /^\s*(?:`{3,}|~{3,})/u;
13214
- var LIST_ITEM = /^( {0,3})(?:[-*+]|\d{1,9}[.)])(?:\s|$)/u;
13215
- var INDENTED = /^\s/u;
13216
- var MIN_FENCE_LENGTH2 = 3;
13217
- var VERIFY_KIND = VERIFY_BLOCK_FENCE.slice("rafa:".length);
13218
- var SLOT_PATTERN = new RegExp(`\\{(${VERIFY_PROMPT_SLOTS.join("|")})\\}`, "gu");
13219
- var ANSWER_KEYS = ["check", "uncheckable"];
13220
- function opensCriterion(text, state) {
13221
- const item = LIST_ITEM.exec(text);
13222
- if (item !== null) {
13223
- const indent = item[1]?.length ?? 0;
13224
- state.topIndent ??= indent;
13225
- return indent <= state.topIndent;
13226
- }
13227
- return state.afterBlank && !INDENTED.test(text);
13228
- }
13229
- function splitCriteria(criteria) {
13230
- if (criteria === null)
13231
- return Object.freeze([]);
13232
- const gathered = [];
13233
- const state = { fenced: false, afterBlank: false, topIndent: null };
13234
- for (const text of criteria.split(/\r\n?|\n/u)) {
13235
- const insideFence = state.fenced;
13236
- if (FENCE2.test(text))
13237
- state.fenced = !state.fenced;
13238
- if (!insideFence && text.trim() === "") {
13239
- state.afterBlank = true;
13240
- gathered.at(-1)?.push(text);
13241
- continue;
13242
- }
13243
- const current = gathered.at(-1);
13244
- const opens = !insideFence && opensCriterion(text, state);
13245
- if (current === undefined || opens) {
13246
- gathered.push([text]);
13247
- } else {
13248
- current.push(text);
13249
- }
13250
- state.afterBlank = false;
13251
- }
13252
- const numbered = gathered.map((lines, index) => {
13253
- const text = lines.join(`
13254
- `).trim();
13255
- return Object.freeze({ number: index + 1, text, placeholder: CRITERIA_PLACEHOLDERS.includes(text) });
13256
- });
13257
- return Object.freeze(numbered);
13258
- }
13259
- function criteriaToAsk(criteria) {
13260
- return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
13261
- }
13262
- function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
13263
- return [join26(moduleDir, VERIFY_PROMPT_FILE), join26(dirname9(moduleDir), VERIFY_PROMPT_FILE)];
13264
- }
13265
- function readVerifyPrompt(moduleDir = MODULE_DIR3) {
13266
- const candidates = verifyPromptCandidates(moduleDir);
13267
- const found = candidates.find((candidate) => existsSync9(candidate));
13268
- if (found === undefined) {
13269
- throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
13270
- }
13271
- return readFileSync19(found, "utf8");
13272
- }
13273
- function fenceFor2(text) {
13274
- const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
13275
- return "`".repeat(Math.max(MIN_FENCE_LENGTH2, longestRun + 1));
13276
- }
13277
- function renderCriterion(criterion) {
13278
- const fence = fenceFor2(criterion.text);
13279
- return [`### Criterion ${String(criterion.number)}`, "", `${fence}text`, criterion.text, fence].join(`
13280
- `);
13281
- }
13282
- function buildVerifyPrompt(template, input) {
13283
- const missing = VERIFY_PROMPT_SLOTS.find((slot) => !template.includes(`{${slot}}`));
13284
- if (missing !== undefined) {
13285
- throw new Error(`epic verify prompt: the template carries no {${missing}} slot`);
13286
- }
13287
- const values = {
13288
- EPIC_NUMBER: String(input.epic),
13289
- EPIC_TITLE: input.title.replace(/\s+/gu, " ").trim(),
13290
- CRITERIA: criteriaToAsk(input.criteria).map(renderCriterion).join(`
13291
-
13292
- `)
13293
- };
13294
- return template.replace(SLOT_PATTERN, (_whole, slot) => values[slot]);
13295
- }
13296
- function isMapping3(value) {
13297
- return typeof value === "object" && value !== null && !Array.isArray(value);
13298
- }
13299
- function fieldOf2(mapping, key) {
13300
- return Object.entries(mapping).find(([own]) => own === key)?.[1];
13301
- }
13302
- function describeValue3(value) {
13303
- if (typeof value === "string")
13304
- return JSON.stringify(value);
13305
- if (value === null || value === undefined)
13306
- return "nothing";
13307
- if (Array.isArray(value))
13308
- return "a list";
13309
- if (typeof value === "object")
13310
- return "a mapping";
13311
- return `the ${typeof value} ${String(value)}`;
13312
- }
13313
- function messageOf4(error) {
13314
- return error instanceof Error ? error.message : String(error);
13315
- }
13316
- function absent2(reason, block, text) {
13317
- return Object.freeze({ present: false, reason, block, text });
13318
- }
13319
- function readEntry(item, field, asked) {
13320
- if (!isMapping3(item))
13321
- return { problem: `${field} is ${describeValue3(item)}, not a mapping`, number: null };
13322
- const number = fieldOf2(item, "criterion");
13323
- if (typeof number !== "number" || !Number.isInteger(number) || number < 1) {
13324
- return { problem: `${field}.criterion is ${describeValue3(number)}, not a criterion number from 1`, number: null };
13325
- }
13326
- if (!asked.has(number)) {
13327
- return { problem: `${field}.criterion ${String(number)} is no criterion the plan was asked about`, number: null };
13328
- }
13329
- const carried = ANSWER_KEYS.filter((key2) => fieldOf2(item, key2) !== undefined);
13330
- const [key] = carried;
13331
- if (carried.length !== 1 || key === undefined) {
13332
- const count = carried.length === 0 ? "neither" : "both";
13333
- return { problem: `${field} carries ${count} check and uncheckable`, number };
13334
- }
13335
- const value = fieldOf2(item, key);
13336
- if (typeof value !== "string" || value.trim() === "") {
13337
- return { problem: `${field}.${key} is ${describeValue3(value)}, not a non-blank string`, number };
13338
- }
13339
- return { answer: { number, key, text: value.trim() } };
13340
- }
13341
- function verdictFor(criterion, answer, problem) {
13342
- if (criterion.placeholder) {
13343
- return { kind: "uncheckable", criterion, reason: PLACEHOLDER_REASON, source: "placeholder" };
13344
- }
13345
- if (answer?.key === "check")
13346
- return { kind: "check", criterion, check: answer.text };
13347
- if (answer?.key === "uncheckable")
13348
- return { kind: "uncheckable", criterion, reason: answer.text, source: "answer" };
13349
- const reason = problem === undefined ? SKIPPED_REASON : `${SKIPPED_REASON}: its entry was dropped, as ${problem}`;
13350
- return { kind: "uncheckable", criterion, reason, source: "skipped" };
13351
- }
13352
- function readVerdicts(items, criteria, block) {
13353
- const asked = new Set(criteriaToAsk(criteria).map((criterion) => criterion.number));
13354
- const answers = new Map;
13355
- const problems = new Map;
13356
- const issues = [];
13357
- for (const [index, item] of items.entries()) {
13358
- const field = `criteria[${String(index)}]`;
13359
- const reading = readEntry(item, field, asked);
13360
- if ("problem" in reading) {
13361
- issues.push(Object.freeze({ field, text: `${reading.problem}; dropped` }));
13362
- if (reading.number !== null && !problems.has(reading.number))
13363
- problems.set(reading.number, reading.problem);
13364
- continue;
13365
- }
13366
- if (answers.has(reading.answer.number)) {
13367
- const text = `${field} answers criterion ${String(reading.answer.number)} a second time; dropped`;
13368
- issues.push(Object.freeze({ field, text }));
13369
- continue;
13370
- }
13371
- answers.set(reading.answer.number, reading.answer);
13372
- }
13373
- const verdicts = criteria.map((criterion) => Object.freeze(verdictFor(criterion, answers.get(criterion.number), problems.get(criterion.number))));
13374
- return Object.freeze({
13375
- present: true,
13376
- verdicts: Object.freeze(verdicts),
13377
- block,
13378
- issues: Object.freeze(issues)
13379
- });
13380
- }
13381
- function parseVerifyPlan(output, criteria) {
13382
- const block = readRafaBlocks(output).filter((each) => each.kind === VERIFY_KIND).at(-1);
13383
- if (block === undefined) {
13384
- return absent2("no-block", null, `the session output holds no ${VERIFY_BLOCK_FENCE} block`);
13385
- }
13386
- const at = `${VERIFY_BLOCK_FENCE} block at line ${String(block.span.first)}`;
13387
- if (!block.closed) {
13388
- return absent2("unclosed-block", block, `${at} is never closed, so it is not read`);
13389
- }
13390
- let document;
13391
- try {
13392
- document = Bun.YAML.parse(block.body);
13393
- } catch (error) {
13394
- return absent2("malformed-block", block, `${at} is not valid YAML (${messageOf4(error)})`);
13395
- }
13396
- if (!isMapping3(document)) {
13397
- return absent2("malformed-block", block, `${at} holds ${describeValue3(document)}, not a mapping`);
13398
- }
13399
- const listed2 = fieldOf2(document, "criteria") ?? [];
13400
- if (!Array.isArray(listed2)) {
13401
- return absent2("malformed-block", block, `${at} has criteria ${describeValue3(listed2)}, not a list`);
13402
- }
13403
- return readVerdicts(listed2, criteria, block);
13404
- }
13405
-
13406
13163
  // src/epic/verify-run.ts
13407
13164
  import { randomUUID as randomUUID2 } from "crypto";
13408
- import { join as join27 } from "path";
13165
+ import { join as join25 } from "path";
13409
13166
  var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
13410
13167
  var DEFAULT_VERIFY_REMOTE = "origin";
13411
13168
  var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
@@ -13414,7 +13171,7 @@ var CHECK_RESULTS = ["pass", "fail"];
13414
13171
  var WORKTREE_PREFIX = "epic-";
13415
13172
  var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
13416
13173
  function verifyWorktreePath(home, epic) {
13417
- return join27(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${String(epic)}`);
13174
+ return join25(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${String(epic)}`);
13418
13175
  }
13419
13176
  function fetchStep(remote) {
13420
13177
  return { id: "fetch", args: ["fetch", remote] };
@@ -13445,7 +13202,7 @@ function checkFlags(sessionId) {
13445
13202
  return [SESSION_ID_FLAG, sessionId, "--tools", CHECK_TOOLS.join(",")];
13446
13203
  }
13447
13204
  function renderCheckPrompt(input) {
13448
- const fence = fenceFor2(input.check);
13205
+ const fence = fenceFor(input.check);
13449
13206
  return [
13450
13207
  CHECK_PROMPT_PREFIX,
13451
13208
  "",
@@ -13486,13 +13243,13 @@ function renderCheckPrompt(input) {
13486
13243
  ].join(`
13487
13244
  `);
13488
13245
  }
13489
- function isMapping4(value) {
13246
+ function isMapping3(value) {
13490
13247
  return typeof value === "object" && value !== null && !Array.isArray(value);
13491
13248
  }
13492
- function fieldOf3(mapping, key) {
13249
+ function fieldOf2(mapping, key) {
13493
13250
  return Object.entries(mapping).find(([own]) => own === key)?.[1];
13494
13251
  }
13495
- function describeValue4(value) {
13252
+ function describeValue3(value) {
13496
13253
  if (typeof value === "string")
13497
13254
  return JSON.stringify(value);
13498
13255
  if (value === null || value === undefined)
@@ -13506,7 +13263,7 @@ function describeValue4(value) {
13506
13263
  function isResultWord(value) {
13507
13264
  return CHECK_RESULTS.some((word) => word === value);
13508
13265
  }
13509
- function absent3(text, block) {
13266
+ function absent2(text, block) {
13510
13267
  return Object.freeze({ present: false, text, block });
13511
13268
  }
13512
13269
  function readVerdictBody(block, at) {
@@ -13514,29 +13271,29 @@ function readVerdictBody(block, at) {
13514
13271
  try {
13515
13272
  document = Bun.YAML.parse(block.body);
13516
13273
  } catch (error) {
13517
- return absent3(`${at} is not valid YAML (${messageOf(error)})`, block);
13274
+ return absent2(`${at} is not valid YAML (${messageOf(error)})`, block);
13518
13275
  }
13519
- if (!isMapping4(document)) {
13520
- return absent3(`${at} holds ${describeValue4(document)}, not a mapping`, block);
13276
+ if (!isMapping3(document)) {
13277
+ return absent2(`${at} holds ${describeValue3(document)}, not a mapping`, block);
13521
13278
  }
13522
- const result2 = fieldOf3(document, "result");
13279
+ const result2 = fieldOf2(document, "result");
13523
13280
  if (!isResultWord(result2)) {
13524
- return absent3(`${at} has result ${describeValue4(result2)}, not ${CHECK_RESULTS.join(" or ")}`, block);
13281
+ return absent2(`${at} has result ${describeValue3(result2)}, not ${CHECK_RESULTS.join(" or ")}`, block);
13525
13282
  }
13526
- const evidence = fieldOf3(document, "evidence");
13283
+ const evidence = fieldOf2(document, "evidence");
13527
13284
  if (typeof evidence !== "string" || evidence.trim() === "") {
13528
- return absent3(`${at} has evidence ${describeValue4(evidence)}, not a non-blank string`, block);
13285
+ return absent2(`${at} has evidence ${describeValue3(evidence)}, not a non-blank string`, block);
13529
13286
  }
13530
13287
  return Object.freeze({ present: true, result: result2, evidence: evidence.trim(), block });
13531
13288
  }
13532
13289
  function parseCheckVerdict(output) {
13533
13290
  const block = readRafaBlocks(output).filter((each) => each.kind === VERDICT_KIND).at(-1);
13534
13291
  if (block === undefined) {
13535
- return absent3(`the session output holds no ${VERDICT_BLOCK_FENCE} block`, null);
13292
+ return absent2(`the session output holds no ${VERDICT_BLOCK_FENCE} block`, null);
13536
13293
  }
13537
13294
  const at = `${VERDICT_BLOCK_FENCE} block at line ${String(block.span.first)}`;
13538
13295
  if (!block.closed)
13539
- return absent3(`${at} is never closed, so it is not read`, block);
13296
+ return absent2(`${at} is never closed, so it is not read`, block);
13540
13297
  return readVerdictBody(block, at);
13541
13298
  }
13542
13299
  function readCheck(check, sessionId, session) {
@@ -13696,7 +13453,7 @@ async function planVerification(input) {
13696
13453
  return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
13697
13454
  }
13698
13455
  function closeTriageFile(root, epic) {
13699
- return join28(root, ".rafa", `epic-${String(epic)}-close`);
13456
+ return join26(root, ".rafa", `epic-${String(epic)}-close`);
13700
13457
  }
13701
13458
  function failedCheckArtifact(epic, criterion) {
13702
13459
  return `epic ${ref7(epic)} acceptance criterion: ${oneLine3(criterion.text)}`;
@@ -14299,13 +14056,13 @@ function createEpicPromoteCommand(seams = {}) {
14299
14056
  var promote_default = createEpicPromoteCommand();
14300
14057
 
14301
14058
  // src/commands/init.ts
14302
- import { existsSync as existsSync12, readFileSync as readFileSync23 } from "fs";
14059
+ import { existsSync as existsSync10, readFileSync as readFileSync21 } from "fs";
14303
14060
  import { homedir as homedir3 } from "os";
14304
- import { join as join32, relative as relative6, sep as sep3 } from "path";
14061
+ import { join as join30, relative as relative6, sep as sep3 } from "path";
14305
14062
 
14306
14063
  // src/agents/vendorable.ts
14307
- import { readdirSync as readdirSync4, readFileSync as readFileSync20 } from "fs";
14308
- import { isAbsolute as isAbsolute3, join as join29 } from "path";
14064
+ import { readdirSync as readdirSync4, readFileSync as readFileSync18 } from "fs";
14065
+ import { isAbsolute as isAbsolute3, join as join27 } from "path";
14309
14066
  function planFiles(dir) {
14310
14067
  let entries;
14311
14068
  try {
@@ -14317,7 +14074,7 @@ function planFiles(dir) {
14317
14074
  }
14318
14075
  function readText3(path) {
14319
14076
  try {
14320
- return readFileSync20(path, "utf8");
14077
+ return readFileSync18(path, "utf8");
14321
14078
  } catch {
14322
14079
  return null;
14323
14080
  }
@@ -14327,7 +14084,7 @@ function rafaHolds(roster, name2) {
14327
14084
  return item?.holders.some((holder) => holder.source === "rafa") === true;
14328
14085
  }
14329
14086
  function vendorableAgents(scan) {
14330
- const dir = isAbsolute3(scan.planDir) ? scan.planDir : join29(scan.repoRoot, scan.planDir);
14087
+ const dir = isAbsolute3(scan.planDir) ? scan.planDir : join27(scan.repoRoot, scan.planDir);
14331
14088
  const roots = {
14332
14089
  repoRoot: scan.repoRoot,
14333
14090
  home: scan.home,
@@ -14336,7 +14093,7 @@ function vendorableAgents(scan) {
14336
14093
  const roster = resolveAgentRoster(roots, scan.settings);
14337
14094
  const found = [];
14338
14095
  for (const file of planFiles(dir)) {
14339
- const path = join29(dir, file);
14096
+ const path = join27(dir, file);
14340
14097
  const markdown = readText3(path);
14341
14098
  if (markdown === null)
14342
14099
  continue;
@@ -14362,13 +14119,13 @@ function vendorableAgentWarnings(agents, root) {
14362
14119
 
14363
14120
  // src/project/gitignore.ts
14364
14121
  import { createHash } from "crypto";
14365
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync21, writeFileSync as writeFileSync6 } from "fs";
14366
- import { dirname as dirname10, isAbsolute as isAbsolute4, join as join30 } from "path";
14122
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
14123
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join28 } from "path";
14367
14124
  var GITIGNORE_FILE = ".gitignore";
14368
14125
  var DIGEST_NAME = "tracking.digest";
14369
14126
  var PRIVATE_TRIAGE = "triage/private";
14370
14127
  var CACHE_DIR = "cache";
14371
- var TRACKING_DIGEST_FILE = join30(SCOPE_DIR, DIGEST_NAME);
14128
+ var TRACKING_DIGEST_FILE = join28(SCOPE_DIR, DIGEST_NAME);
14372
14129
  var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
14373
14130
  var BLOCK_END = "# <<< rafa tracking";
14374
14131
  var SUB_PATHS = [
@@ -14457,13 +14214,13 @@ function attempt2(file, failure3, act) {
14457
14214
  }
14458
14215
  }
14459
14216
  function readIfPresent2(file) {
14460
- if (!existsSync10(file))
14217
+ if (!existsSync8(file))
14461
14218
  return null;
14462
- return attempt2(file, "cannot be read", () => readFileSync21(file, "utf8"));
14219
+ return attempt2(file, "cannot be read", () => readFileSync19(file, "utf8"));
14463
14220
  }
14464
14221
  function writeTrackingGitignore(root, flags) {
14465
14222
  requireAbsolute(root);
14466
- const file = join30(root, GITIGNORE_FILE);
14223
+ const file = join28(root, GITIGNORE_FILE);
14467
14224
  const text = readIfPresent2(file);
14468
14225
  const next = withTrackingBlock(text, flags, file);
14469
14226
  if (next === text)
@@ -14485,7 +14242,7 @@ function trackingDigest(flags) {
14485
14242
  }
14486
14243
  function exposureNotice(root) {
14487
14244
  return [
14488
- `tracking.all is true, so ${join30(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
14245
+ `tracking.all is true, so ${join28(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
14489
14246
  " - unpatched issues described in specs and plans",
14490
14247
  " - effort rows with model names and token counts",
14491
14248
  " - instincts that may quote error output",
@@ -14498,7 +14255,7 @@ function printWarning(message) {
14498
14255
  }
14499
14256
  function noticeTrackingChange(root, flags, print2 = printWarning) {
14500
14257
  requireAbsolute(root);
14501
- const digestFile = join30(root, TRACKING_DIGEST_FILE);
14258
+ const digestFile = join28(root, TRACKING_DIGEST_FILE);
14502
14259
  const recorded = `${trackingDigest(flags)}
14503
14260
  `;
14504
14261
  if (readIfPresent2(digestFile) === recorded) {
@@ -14507,7 +14264,7 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
14507
14264
  if (flags.trackingAll)
14508
14265
  print2(exposureNotice(root));
14509
14266
  attempt2(digestFile, "cannot be written", () => {
14510
- mkdirSync7(dirname10(digestFile), { recursive: true });
14267
+ mkdirSync7(dirname8(digestFile), { recursive: true });
14511
14268
  writeFileSync6(digestFile, recorded);
14512
14269
  });
14513
14270
  return { digestFile, changed: true, printed: flags.trackingAll };
@@ -14597,14 +14354,14 @@ async function promptForRoot(found, prompter, seams) {
14597
14354
  }
14598
14355
 
14599
14356
  // src/project/scaffold.ts
14600
- import { existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync8, statSync as statSync7, writeFileSync as writeFileSync8 } from "fs";
14601
- import { join as join31 } from "path";
14357
+ import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync8, statSync as statSync7, writeFileSync as writeFileSync8 } from "fs";
14358
+ import { join as join29 } from "path";
14602
14359
 
14603
14360
  // src/commands/init-release.ts
14604
14361
  import { writeFileSync as writeFileSync7 } from "fs";
14605
14362
 
14606
14363
  // src/release/setting.ts
14607
- import { readFileSync as readFileSync22 } from "fs";
14364
+ import { readFileSync as readFileSync20 } from "fs";
14608
14365
  var RELEASE_ENABLED_SETTING = "release.enabled";
14609
14366
  function releaseEnabledIn(text, path) {
14610
14367
  return parseConfigText(text, path).values.releaseEnabled ?? null;
@@ -14613,7 +14370,7 @@ function readReleaseSetting(root) {
14613
14370
  const path = configFilePath(root);
14614
14371
  let text;
14615
14372
  try {
14616
- text = readFileSync22(path, "utf8");
14373
+ text = readFileSync20(path, "utf8");
14617
14374
  } catch (error) {
14618
14375
  return { enabled: null, text: "", problem: `${path} could not be read: ${messageOf(error)}` };
14619
14376
  }
@@ -14832,6 +14589,13 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
14832
14589
  "# loop:",
14833
14590
  `# settingSources: ${CONFIG_DEFAULTS.settingSources.join(",")} # a comma-separated subset of user, project, local`,
14834
14591
  `# worktreeDir: ${CONFIG_DEFAULTS.loopWorktreeDir} # where a loop adds its worktrees, read from the project root`,
14592
+ `# retries: ${String(CONFIG_DEFAULTS.loopRetries)} # 1..3 | false, times in a row a run re-enters its loop after a retry-safe stop`,
14593
+ `# retriesOnContinue: ${String(CONFIG_DEFAULTS.loopRetriesOnContinue)} # 1..3 | false, the retries a --continue run opens when --retry is not typed`,
14594
+ "# continue:",
14595
+ `# criteria: ${CONFIG_DEFAULTS.loopContinueCriteria} # the project's own criteria for a --continue decision`,
14596
+ `# criteriaMode: ${CONFIG_DEFAULTS.loopContinueCriteriaMode} # extend | replace, how the project criteria meet the bundled base`,
14597
+ "# forceWrapUp:",
14598
+ `# maxNewFailures: ${String(CONFIG_DEFAULTS.loopForceWrapUpMaxNewFailures)} # 1..50 | false, new suite failures --force-wrap-up tolerates`,
14835
14599
  "# wrapUp:",
14836
14600
  `# retries: ${String(CONFIG_DEFAULTS.loopWrapUpRetries)} # 1..3 | false, wrap-up sessions rerun before rafa opens a missing PR itself`,
14837
14601
  "# pr:",
@@ -14846,6 +14610,11 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
14846
14610
  "# project:",
14847
14611
  `# template: ${CONFIG_DEFAULTS.boardProjectTemplate} # the project URL rafa init --board --project copies`,
14848
14612
  "# number: # the project rafa mirrors the board to; unset until rafa init --board --project",
14613
+ `# retries: ${String(CONFIG_DEFAULTS.boardProjectRetries)} # 1..10 | false, tries again after a failed project call`,
14614
+ `# retryWaitSeconds: ${String(CONFIG_DEFAULTS.boardProjectRetryWaitSeconds)} # 1..60, the first wait before a retry; each later one doubles`,
14615
+ `# progressSeconds: ${String(CONFIG_DEFAULTS.boardProjectProgressSeconds)} # 1..300 | false, the most seconds between two progress lines`,
14616
+ `# writeBatchSize: ${String(CONFIG_DEFAULTS.boardProjectWriteBatchSize)} # 1..100, field writes per request`,
14617
+ `# writePauseMs: ${String(CONFIG_DEFAULTS.boardProjectWritePauseMs)} # 0..60000, the pause between two write requests`,
14849
14618
  "# roadmap:",
14850
14619
  "# issue: # the issue plan create --next reads; unset is the one titled Roadmap",
14851
14620
  "# claims:",
@@ -14922,7 +14691,7 @@ class ScaffoldError extends Error {
14922
14691
  }
14923
14692
  function layoutOf2(base, tree) {
14924
14693
  const scope = scopeAt(base);
14925
- return { dirs: [scope.dir, ...tree.map((name2) => join31(scope.dir, name2))], configFile: configFilePath(base) };
14694
+ return { dirs: [scope.dir, ...tree.map((name2) => join29(scope.dir, name2))], configFile: configFilePath(base) };
14926
14695
  }
14927
14696
  function occupied(path) {
14928
14697
  return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
@@ -14933,7 +14702,7 @@ function isDirectory2(path) {
14933
14702
  function directoryConflict(dir) {
14934
14703
  if (!occupied(dir) || isDirectory2(dir))
14935
14704
  return null;
14936
- return existsSync11(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
14705
+ return existsSync9(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
14937
14706
  }
14938
14707
  function layoutConflicts(layout) {
14939
14708
  const [scopeDir, ...tree] = layout.dirs;
@@ -14941,7 +14710,7 @@ function layoutConflicts(layout) {
14941
14710
  if (opening !== null)
14942
14711
  return [opening];
14943
14712
  const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
14944
- const file = occupied(layout.configFile) && !existsSync11(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
14713
+ const file = occupied(layout.configFile) && !existsSync9(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
14945
14714
  return [...dirs, ...file];
14946
14715
  }
14947
14716
  function scaffoldConflicts(root, home) {
@@ -14955,7 +14724,7 @@ function attempt3(path, failure3, act) {
14955
14724
  }
14956
14725
  }
14957
14726
  function ensureDirectory(path) {
14958
- if (existsSync11(path))
14727
+ if (existsSync9(path))
14959
14728
  return { path, kind: "directory", change: "unchanged" };
14960
14729
  attempt3(path, "cannot be created", () => {
14961
14730
  mkdirSync8(path, { recursive: true });
@@ -14963,7 +14732,7 @@ function ensureDirectory(path) {
14963
14732
  return { path, kind: "directory", change: "created" };
14964
14733
  }
14965
14734
  function ensureFile(path, text) {
14966
- if (existsSync11(path))
14735
+ if (existsSync9(path))
14967
14736
  return { path, kind: "file", change: "unchanged" };
14968
14737
  attempt3(path, "cannot be written", () => {
14969
14738
  writeFileSync8(path, text, { encoding: "utf8", flag: "wx" });
@@ -15108,9 +14877,9 @@ function resolvedConfig2(root, home, warn3) {
15108
14877
  }
15109
14878
  }
15110
14879
  function checkGitignore(root, flags) {
15111
- const file = join32(root, GITIGNORE_FILE);
14880
+ const file = join30(root, GITIGNORE_FILE);
15112
14881
  try {
15113
- const text = existsSync12(file) ? readFileSync23(file, "utf8") : null;
14882
+ const text = existsSync10(file) ? readFileSync21(file, "utf8") : null;
15114
14883
  withTrackingBlock(text, flags, file);
15115
14884
  } catch (error) {
15116
14885
  const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
@@ -15142,8 +14911,8 @@ function initialise(root, start2, home, context, entry) {
15142
14911
  const resolved = resolvedConfig2(root.path, home, warn3);
15143
14912
  const config = resolved.config;
15144
14913
  checkGitignore(root.path, config);
15145
- const configExisted = existsSync12(configFilePath(root.path));
15146
- const digestExisted = existsSync12(join32(root.path, TRACKING_DIGEST_FILE));
14914
+ const configExisted = existsSync10(configFilePath(root.path));
14915
+ const digestExisted = existsSync10(join30(root.path, TRACKING_DIGEST_FILE));
15147
14916
  const project = writeProjectScope(root.path);
15148
14917
  const tracking = applyTracking(root.path, config, warn3);
15149
14918
  const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
@@ -15248,7 +15017,9 @@ async function runInit(context, seams) {
15248
15017
  config: scopes.config,
15249
15018
  openGh: () => seams.gh(scopes.written.root),
15250
15019
  isTerminal: seams.isTerminal,
15251
- openPrompter: seams.openPrompter
15020
+ openPrompter: seams.openPrompter,
15021
+ ...commandRetrySeams(context.output, context.outputMode, seams.sleep),
15022
+ progress: commandProgressFeed(context.output, context.outputMode, scopes.config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now })
15252
15023
  });
15253
15024
  const result2 = {
15254
15025
  ...scopes.written,
@@ -15285,7 +15056,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
15285
15056
  subject: "init",
15286
15057
  action: "init",
15287
15058
  summary: "set up a rafa project: its root, .rafa/config.yaml, the tree and the .gitignore entry",
15288
- description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. Last, once the" + " board has run, one question asks whether to copy the GitHub project template to the repository's" + " owner, link it, add the issues and fill their fields; `--project` and `--no-project` answer that one." + " On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
15059
+ description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. Last, once the" + " board has run, one question asks whether to copy the GitHub project template to the repository's" + " owner, link it, add the issues and fill their fields; `--project` and `--no-project` answer that one." + " Adding the issues, reading their facts and writing their fields each print a start line with the" + " total, a progress line at most every board.project.progressSeconds and an end line, one `progress`" + " event each with `--output=json`." + " On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
15289
15060
  args: [],
15290
15061
  flags: [
15291
15062
  {
@@ -15310,7 +15081,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
15310
15081
  },
15311
15082
  {
15312
15083
  name: "project",
15313
- description: "Create the GitHub project without asking, once the board has run: copy" + " `board.project.template` to the repository's owner, link the repository, add its issues, fill" + " their fields and save `board.project.number`. `--no-project` leaves it out. Without either, a" + " terminal is asked once and a run with no terminal leaves it out.",
15084
+ description: "Create the GitHub project without asking, once the board has run: copy" + " `board.project.template` to the repository's owner, link the repository, add its issues, fill" + " their fields and save `board.project.number`. `--no-project` leaves it out. Without either, a" + " terminal is asked once and a run with no terminal leaves it out. The adds, the facts reads and the" + " field writes print their progress, as `adding issues: 146/283, 6m 40s`.",
15314
15085
  type: "boolean"
15315
15086
  },
15316
15087
  {
@@ -16027,8 +15798,8 @@ function createInstinctShowCommand() {
16027
15798
  var show_default3 = createInstinctShowCommand();
16028
15799
 
16029
15800
  // src/commands/issue/check.ts
16030
- import { readdirSync as readdirSync5, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
16031
- import { join as join33, resolve as resolve9 } from "path";
15801
+ import { readdirSync as readdirSync5, readFileSync as readFileSync22, statSync as statSync8 } from "fs";
15802
+ import { join as join31, resolve as resolve9 } from "path";
16032
15803
  var CHECK_USAGE = "rafa issue check <n> [--stamp]";
16033
15804
  var STAMP_FLAG = "stamp";
16034
15805
  var ISSUE_NUMBER8 = /^[1-9]\d*$/u;
@@ -16064,7 +15835,7 @@ function copyNames(dir, issue) {
16064
15835
  }
16065
15836
  const prefix = `${boardId(issue)}-`;
16066
15837
  const notes = notesFileName(issue);
16067
- return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync8(join33(dir, name2)).isFile()).sort();
15838
+ return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync8(join31(dir, name2)).isFile()).sort();
16068
15839
  }
16069
15840
  function findSavedCopy(root, specsDir, issue) {
16070
15841
  const names = copyNames(resolve9(root, specsDir), issue);
@@ -16074,11 +15845,11 @@ function findSavedCopy(root, specsDir, issue) {
16074
15845
  if (names.length > 1) {
16075
15846
  throw new CommandExit(1, [
16076
15847
  `\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
16077
- ...names.map((name2) => ` ${join33(specsDir, name2)}`)
15848
+ ...names.map((name2) => ` ${join31(specsDir, name2)}`)
16078
15849
  ].join(`
16079
15850
  `));
16080
15851
  }
16081
- const path = join33(specsDir, only);
15852
+ const path = join31(specsDir, only);
16082
15853
  return { path, absolute: resolve9(root, path) };
16083
15854
  }
16084
15855
  function liveText(live) {
@@ -16127,7 +15898,7 @@ async function readReferences(absolute, issue, verify, stamp) {
16127
15898
  return rows.map((row2) => checkedRef(row2, row2.stamp));
16128
15899
  }
16129
15900
  const once2 = memoiseVerifier(verify);
16130
- const before = await readRefsText({ copy: readFileSync24(absolute, "utf8"), issue, verify: once2 });
15901
+ const before = await readRefsText({ copy: readFileSync22(absolute, "utf8"), issue, verify: once2 });
16131
15902
  const after = await restampCopyRefs({ path: absolute, issue, verify: once2 });
16132
15903
  return before.rows.map((row2, index) => checkedRef(row2, after.rows[index]?.stamp ?? null));
16133
15904
  }
@@ -16400,7 +16171,7 @@ async function addCreatedToProject(context, seams, ref8) {
16400
16171
  });
16401
16172
  const lines = await addAndRefreshIssue({
16402
16173
  config,
16403
- openGh: () => seams.gh ?? createGhRunner({ cwd: project.root })
16174
+ openGh: () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep))
16404
16175
  }, issue);
16405
16176
  for (const line of lines)
16406
16177
  context.output.warn(line);
@@ -16480,8 +16251,8 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
16480
16251
  var create_default = createIssueCreateCommand();
16481
16252
 
16482
16253
  // src/commands/issue/edit-run.ts
16483
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
16484
- import { join as join35 } from "path";
16254
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
16255
+ import { join as join33 } from "path";
16485
16256
 
16486
16257
  // src/board/issue-edit-body.ts
16487
16258
  var TRAILING_WHITESPACE = /\s+$/u;
@@ -16536,7 +16307,7 @@ function originalPartHolds(before, after, block) {
16536
16307
 
16537
16308
  // src/board/issue-edit-state.ts
16538
16309
  import { readdirSync as readdirSync6, statSync as statSync9 } from "fs";
16539
- import { join as join34, resolve as resolve10 } from "path";
16310
+ import { join as join32, resolve as resolve10 } from "path";
16540
16311
  function isClaimed(labels) {
16541
16312
  return labels.includes(CLAIMED_LABEL) || labels.includes(IN_DEVELOPMENT_LABEL);
16542
16313
  }
@@ -16609,7 +16380,7 @@ function findSavedCopies(root, specsDir, issue) {
16609
16380
  return [];
16610
16381
  throw error;
16611
16382
  }
16612
- return names.filter((name2) => isSavedCopyName(name2, issue)).filter((name2) => statSync9(join34(dir, name2)).isFile()).sort().map((name2) => join34(specsDir, name2));
16383
+ return names.filter((name2) => isSavedCopyName(name2, issue)).filter((name2) => statSync9(join32(dir, name2)).isFile()).sort().map((name2) => join32(specsDir, name2));
16613
16384
  }
16614
16385
 
16615
16386
  // src/board/issue-edit-text.ts
@@ -16633,7 +16404,7 @@ function requireEditText(input) {
16633
16404
  var EDIT_USAGE = "rafa issue edit <n> (--append-file=<file> | --append=<text>) --reason=<text>" + " [--while-in-development] | (--replace-file=<file> [--title=<text>] | --title=<text>) [--dry-run]";
16634
16405
  var EDIT_REFUSAL_EXIT = TRUST_REFUSAL_EXIT;
16635
16406
  var EDIT_CONFLICT_EXIT = 1;
16636
- var EDIT_SCRATCH_DIR = join35(".rafa", "scratch");
16407
+ var EDIT_SCRATCH_DIR = join33(".rafa", "scratch");
16637
16408
  var STDIN_PATH2 = "-";
16638
16409
  var ISSUE_NUMBER9 = /^[1-9]\d*$/u;
16639
16410
  var TRAILING_WHITESPACE2 = /\s+$/u;
@@ -16882,20 +16653,20 @@ function writeHeld(found, planned, reread) {
16882
16653
  }
16883
16654
  function conflictPaths(issue, stamp, attempt4) {
16884
16655
  const suffix = attempt4 === 1 ? "" : `-${String(attempt4)}`;
16885
- const name2 = (side) => join35(EDIT_SCRATCH_DIR, `${boardId(issue)}-edit-${side}-${stamp}${suffix}.md`);
16656
+ const name2 = (side) => join33(EDIT_SCRATCH_DIR, `${boardId(issue)}-edit-${side}-${stamp}${suffix}.md`);
16886
16657
  return { before: name2("before"), after: name2("after") };
16887
16658
  }
16888
16659
  function saveConflictBodies(root, issue, now, before, after) {
16889
- mkdirSync9(join35(root, EDIT_SCRATCH_DIR), { recursive: true });
16660
+ mkdirSync9(join33(root, EDIT_SCRATCH_DIR), { recursive: true });
16890
16661
  const stamp = fileStamp(now);
16891
16662
  let attempt4 = 1;
16892
16663
  let paths = conflictPaths(issue, stamp, attempt4);
16893
- while (existsSync13(join35(root, paths.before)) || existsSync13(join35(root, paths.after))) {
16664
+ while (existsSync11(join33(root, paths.before)) || existsSync11(join33(root, paths.after))) {
16894
16665
  attempt4 += 1;
16895
16666
  paths = conflictPaths(issue, stamp, attempt4);
16896
16667
  }
16897
- writeFileSync9(join35(root, paths.before), before, { flag: "wx" });
16898
- writeFileSync9(join35(root, paths.after), after, { flag: "wx" });
16668
+ writeFileSync9(join33(root, paths.before), before, { flag: "wx" });
16669
+ writeFileSync9(join33(root, paths.after), after, { flag: "wx" });
16899
16670
  return paths;
16900
16671
  }
16901
16672
  function heldOutcome(request, state) {
@@ -17421,7 +17192,7 @@ var wrapped = wrapPhaseZeroCommand({
17421
17192
  subject: "loop",
17422
17193
  action: "start",
17423
17194
  summary: "run a plan task by task, then wrap up, open the PR and wait for CI",
17424
- description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. `--as-worktree` runs the plan in a worktree of its own on" + " that branch instead, leaving the main checkout as it is, and is refused beside" + " `--create-branch`. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task, and under `--roadmap`" + " the hop away, when one is. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
17195
+ description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. `--as-worktree` runs the plan in a worktree of its own on" + " that branch instead, leaving the main checkout as it is, and is refused beside" + " `--create-branch`. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task, and under `--roadmap`" + " the hop away, when one is. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. With `--retry` or" + " `loop.retries` it goes back into its loop instead of stopping, at most that many times in a row," + " after a red suite step, a task session that exited nonzero, or one that left neither a report nor" + " a commit; a later task done, neither the stopped one nor a repair, starts the count over. With `--continue` a stop that would end the run is handed to a decision instead: retry the" + " task with a new approach, stop with exit code 20, jump over it or defer it until a later task is" + " done; a run left with only passed-over tasks ends with exit code 22 and no wrap-up unless" + " `--force-wrap-up`, and under `--output=json` a decision the line does not name ends the run with" + " exit code 21 and the decision prompt. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
17425
17196
  args: [],
17426
17197
  flags: [
17427
17198
  {
@@ -17467,6 +17238,36 @@ var wrapped = wrapPhaseZeroCommand({
17467
17238
  type: "number",
17468
17239
  default: DEFAULT_CI_ATTEMPTS
17469
17240
  },
17241
+ {
17242
+ name: "retry",
17243
+ description: "Goes back into the loop instead of stopping, at most this many times in a row, 1 to 3," + " after a red suite step, a task session that exited nonzero but not on its budget, or one that" + " left neither a report nor a commit. The count starts over once a later task is done, neither" + " the stopped one nor a repair. A report that blocks its task, a refused commit, a moved" + " checkout and an interrupt still stop the run. Outranks `loop.retries` in" + " `.rafa/config.yaml`, `false` unless the config names a count; any other value is refused" + " before the run starts.",
17244
+ type: "number"
17245
+ },
17246
+ {
17247
+ name: "continue",
17248
+ description: "Hands a stop that would end the run to a decision instead: a report that holds its task at" + " once, and a retry-safe stop once its retries are spent. One read-only decision session picks" + " `retry` (with a new approach), `stop`, `jump` (pass the task over this run) or `defer` (pass" + " it over until a later task is done), by the bundled criteria and `loop.continue.criteria`." + " The retries are `loop.retriesOnContinue` unless `--retry` names a count.",
17249
+ type: "boolean"
17250
+ },
17251
+ {
17252
+ name: "decide",
17253
+ description: "Names the decision on the line, `retry`, `stop`, `jump` or `defer`, applied once to the first" + " decision point the run meets, so no decision session is spawned for it. Needs `--continue`.",
17254
+ type: "string"
17255
+ },
17256
+ {
17257
+ name: "approach",
17258
+ description: "The new approach a `--decide=retry` hands the task's next session; required by it and" + " read beside nothing else.",
17259
+ type: "string"
17260
+ },
17261
+ {
17262
+ name: "after",
17263
+ description: "The tracker line, counted from 1, of the task a `--decide=defer` waits on; required by it" + " and read beside nothing else.",
17264
+ type: "number"
17265
+ },
17266
+ {
17267
+ name: "force-wrap-up",
17268
+ description: "Wraps up a `--continue` run left with only passed-over tasks anyway, as a draft pull request" + " listing them, once the pre-wrap-up suite step finds no more new failures than" + " `loop.forceWrapUp.maxNewFailures` tolerates. Needs `--continue`.",
17269
+ type: "boolean"
17270
+ },
17470
17271
  {
17471
17272
  name: "create-branch",
17472
17273
  description: "On `main` or `master`, creates `feat/<plan-stub>` from the latest `origin/<base>`" + " and runs the plan there without asking, or switches to that branch when it already" + " exists. Refuses rather than move when a tracked file is modified, the fetch fails or the" + " base has diverged from its remote.",
@@ -17513,7 +17314,7 @@ var wrapped = wrapPhaseZeroCommand({
17513
17314
  outputs: ["text", "json", "events"],
17514
17315
  spends: {
17515
17316
  when: "always",
17516
- what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request, and repair sessions while CI is red"
17317
+ what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request," + " repair sessions while CI is red, the sessions of up to `--retry` (`loop.retries`) more passes of the loop" + " in a row after the stops it retries, and under `--continue` one read-only decision session per decision"
17517
17318
  }
17518
17319
  }, start);
17519
17320
  var loopStart = Object.freeze({
@@ -18754,7 +18555,7 @@ var wrapped2 = wrapPhaseZeroCommand({
18754
18555
  var create_default2 = endingWith(wrapped2);
18755
18556
 
18756
18557
  // src/commands/plan/risk.ts
18757
- import { readFileSync as readFileSync25 } from "fs";
18558
+ import { readFileSync as readFileSync23 } from "fs";
18758
18559
  import { resolve as resolve11 } from "path";
18759
18560
  var COMMAND6 = "rafa plan risk";
18760
18561
  var USAGE34 = "rafa plan risk [<plan>] [--strict]";
@@ -18782,7 +18583,7 @@ function strictRefusal(report3) {
18782
18583
  }
18783
18584
  async function readRisk(path, project, config, context, seams) {
18784
18585
  const makeRunners = seams.runners ?? spawningRunners;
18785
- return assessPlanRisk({ path, text: readFileSync25(path, "utf8") }, {
18586
+ return assessPlanRisk({ path, text: readFileSync23(path, "utf8") }, {
18786
18587
  repoRoot: project.root,
18787
18588
  home: project.home,
18788
18589
  settingSources: config.settingSources,
@@ -18865,8 +18666,8 @@ function createPlanRiskCommand(seams = {}) {
18865
18666
  var risk_default = createPlanRiskCommand();
18866
18667
 
18867
18668
  // src/commands/plan/show.ts
18868
- import { readFileSync as readFileSync26 } from "fs";
18869
- import { join as join36 } from "path";
18669
+ import { readFileSync as readFileSync24 } from "fs";
18670
+ import { join as join34 } from "path";
18870
18671
  var USAGE35 = "rafa plan show <stub> [--tracker]";
18871
18672
  var COMMAND7 = "rafa plan show";
18872
18673
  function readTrackerFlag(value) {
@@ -18884,10 +18685,10 @@ function showPlan(plans, stub, tracker) {
18884
18685
  throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
18885
18686
  }
18886
18687
  const name2 = planFileName(stub, tracker);
18887
- const file = join36(plans.path, name2);
18688
+ const file = join34(plans.path, name2);
18888
18689
  if (!isFile(file))
18889
- throw new CommandExit(1, missingText(stub, tracker, join36(plans.label, name2)));
18890
- const model = parsePlan(readFileSync26(file, "utf8"));
18690
+ throw new CommandExit(1, missingText(stub, tracker, join34(plans.label, name2)));
18691
+ const model = parsePlan(readFileSync24(file, "utf8"));
18891
18692
  const { header } = model;
18892
18693
  return {
18893
18694
  stub,
@@ -18910,7 +18711,7 @@ function taskLine(task) {
18910
18711
  return ` ${checkbox(task.status)} ${task.text}`;
18911
18712
  }
18912
18713
  function renderShownPlan(shown, dirLabel) {
18913
- const relative9 = join36(dirLabel, planFileName(shown.stub, shown.tracker));
18714
+ const relative9 = join34(dirLabel, planFileName(shown.stub, shown.tracker));
18914
18715
  const fileLabel = shown.tracker ? "Tracker" : "File";
18915
18716
  const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative9}`];
18916
18717
  if (shown.header.issue !== null)
@@ -19075,6 +18876,10 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
19075
18876
  versionGuard: mergeGuardSettings(config),
19076
18877
  projectRefresh: {
19077
18878
  boardProjectNumber: config.boardProjectNumber,
18879
+ boardProjectRetries: config.boardProjectRetries,
18880
+ boardProjectRetryWaitSeconds: config.boardProjectRetryWaitSeconds,
18881
+ boardProjectWriteBatchSize: config.boardProjectWriteBatchSize,
18882
+ boardProjectWritePauseMs: config.boardProjectWritePauseMs,
19078
18883
  boardRelationships: config.boardRelationships,
19079
18884
  roadmapIssue: config.roadmapIssue,
19080
18885
  releaseFragments: config.releaseFragments
@@ -19879,7 +19684,7 @@ async function refreshProjectAfterMerge(options) {
19879
19684
  const closed = [...new Set(closedIssuesIn(options.body))];
19880
19685
  if (config.boardProjectNumber === null || closed.length === 0)
19881
19686
  return null;
19882
- const gh = options.openGh();
19687
+ const gh = openProjectRunner(options.openGh(), config, options.retry);
19883
19688
  const wanted = new Set(closed);
19884
19689
  const warnings = [];
19885
19690
  let listed2 = [];
@@ -19902,7 +19707,7 @@ async function refreshProjectAfterMerge(options) {
19902
19707
 
19903
19708
  // src/commands/pr/merge-loop-worktree.ts
19904
19709
  import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync10, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
19905
- import { dirname as dirname11, isAbsolute as isAbsolute6, join as join37, relative as relative9, resolve as resolve12, sep as sep4 } from "path";
19710
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join35, relative as relative9, resolve as resolve12, sep as sep4 } from "path";
19906
19711
  var REFUSAL_PREFIX = "rafa pr merge refuses";
19907
19712
  var INDENT4 = " ";
19908
19713
  var LIVE_STATES = new Set(["running", "paused"]);
@@ -19912,7 +19717,7 @@ function defaultLoopWorktreeSeams(mainCheckout) {
19912
19717
  gitAt: createGitRunner,
19913
19718
  modifiedAt: (path) => statSync10(path, { throwIfNoEntry: false })?.mtime ?? null,
19914
19719
  copyFile: (from, to) => {
19915
- mkdirSync10(dirname11(to), { recursive: true });
19720
+ mkdirSync10(dirname9(to), { recursive: true });
19916
19721
  copyFileSync3(from, to);
19917
19722
  },
19918
19723
  realPath: (path) => {
@@ -19947,7 +19752,7 @@ function freeLoopWorktree(seams, reading, holder) {
19947
19752
  const files = [];
19948
19753
  for (const name2 of planFileNames(holder.stub)) {
19949
19754
  const from = resolve12(holder.path, reading.planDir, name2);
19950
- const to = join37(into, name2);
19755
+ const to = join35(into, name2);
19951
19756
  try {
19952
19757
  files.push({ name: name2, from, to, outcome: copyPlanFile(seams, from, to) });
19953
19758
  } catch (error) {
@@ -20202,7 +20007,7 @@ function workflowNamesBase(workflowText, base) {
20202
20007
  return readWorkflow(document, base) !== false;
20203
20008
  }
20204
20009
  function readWorkflow(document, base) {
20205
- if (!isPlainObject2(document) || !("on" in document))
20010
+ if (!isPlainObject(document) || !("on" in document))
20206
20011
  return UNKNOWN;
20207
20012
  const on = document.on;
20208
20013
  if (typeof on === "string")
@@ -20212,7 +20017,7 @@ function readWorkflow(document, base) {
20212
20017
  return UNKNOWN;
20213
20018
  return on.includes(PULL_REQUEST_EVENT);
20214
20019
  }
20215
- if (!isPlainObject2(on))
20020
+ if (!isPlainObject(on))
20216
20021
  return UNKNOWN;
20217
20022
  if (!(PULL_REQUEST_EVENT in on))
20218
20023
  return false;
@@ -20221,7 +20026,7 @@ function readWorkflow(document, base) {
20221
20026
  function readPullRequestTrigger(trigger, base) {
20222
20027
  if (trigger === null || trigger === undefined)
20223
20028
  return true;
20224
- if (!isPlainObject2(trigger))
20029
+ if (!isPlainObject(trigger))
20225
20030
  return UNKNOWN;
20226
20031
  const branches = trigger.branches;
20227
20032
  const ignored = trigger["branches-ignore"];
@@ -20322,7 +20127,7 @@ function regexSourceOf(pattern) {
20322
20127
  function escapeRegex(char) {
20323
20128
  return char.replace(REGEX_SPECIAL, "\\$&");
20324
20129
  }
20325
- function isPlainObject2(value) {
20130
+ function isPlainObject(value) {
20326
20131
  return typeof value === "object" && value !== null && !Array.isArray(value);
20327
20132
  }
20328
20133
 
@@ -20646,7 +20451,14 @@ async function runMerge2(context, seams) {
20646
20451
  const roadmapTicks = await reportTick(context, pr, seams, detail);
20647
20452
  const steps = cleanUpAfterMerge(git, detail, { info: info2, warn: warn3 });
20648
20453
  const board = await reportAfterCleanUp(context, pr, seams, detail);
20649
- await refreshProjectAfterMerge({ body: detail.body, config: pr.projectRefresh, openGh: () => openGh(pr, seams), unblocked: board.unblocked, warn: warn3 });
20454
+ await refreshProjectAfterMerge({
20455
+ body: detail.body,
20456
+ config: pr.projectRefresh,
20457
+ openGh: () => openGh(pr, seams),
20458
+ unblocked: board.unblocked,
20459
+ warn: warn3,
20460
+ retry: commandRetrySeams(context.output, context.outputMode)
20461
+ });
20650
20462
  const followUps = reportFollowUps({
20651
20463
  root: pr.project.root,
20652
20464
  home: pr.project.home,
@@ -20909,7 +20721,7 @@ var FOLLOW_UP_TASKS = Object.freeze({
20909
20721
  "ci-test": "A test failed. Read the excerpt below, reproduce the named test" + " locally, and fix the implementation rather than the assertion unless the" + " assertion is itself wrong. Run the gates and push.",
20910
20722
  "ci-other": "A step nothing recognised failed. Read the excerpt below, work out" + " from the step name what that job does, and fix it; there is no pinned plan" + " for this class, so nothing has been assumed about the fix."
20911
20723
  });
20912
- var MIN_FENCE_LENGTH3 = 3;
20724
+ var MIN_FENCE_LENGTH2 = 3;
20913
20725
  function isUsableCap(value) {
20914
20726
  return value !== undefined && Number.isInteger(value) && value > 0;
20915
20727
  }
@@ -20924,7 +20736,7 @@ function excerptLines(evidence, maxLines) {
20924
20736
  }
20925
20737
  function fencedBlock(body, language) {
20926
20738
  const runs = Array.from(body.matchAll(/`+/g), (run) => run[0].length);
20927
- const fence = "`".repeat(Math.max(MIN_FENCE_LENGTH3, ...runs.map((length) => length + 1)));
20739
+ const fence = "`".repeat(Math.max(MIN_FENCE_LENGTH2, ...runs.map((length) => length + 1)));
20928
20740
  const text = body.endsWith(`
20929
20741
  `) || body === "" ? body : `${body}
20930
20742
  `;
@@ -21024,7 +20836,7 @@ function findTriageComment(comments) {
21024
20836
  function read(value) {
21025
20837
  return { value, problem: null };
21026
20838
  }
21027
- function absent4() {
20839
+ function absent3() {
21028
20840
  return { value: null, problem: null };
21029
20841
  }
21030
20842
  function unusable(key, value, expected) {
@@ -21033,7 +20845,7 @@ function unusable(key, value, expected) {
21033
20845
  function readText4(fields, key) {
21034
20846
  const value = fields[key];
21035
20847
  if (value === undefined)
21036
- return absent4();
20848
+ return absent3();
21037
20849
  if (typeof value !== "string")
21038
20850
  return unusable(key, value, `text; write it quoted, as ${key}: "..."`);
21039
20851
  const trimmed = value.trim();
@@ -21042,19 +20854,19 @@ function readText4(fields, key) {
21042
20854
  function readBoolean(fields, key) {
21043
20855
  const value = fields[key];
21044
20856
  if (value === undefined)
21045
- return absent4();
20857
+ return absent3();
21046
20858
  return typeof value === "boolean" ? read(value) : unusable(key, value, "true or false");
21047
20859
  }
21048
20860
  function readCount(fields, key) {
21049
20861
  const value = fields[key];
21050
20862
  if (value === undefined)
21051
- return absent4();
20863
+ return absent3();
21052
20864
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? read(value) : unusable(key, value, "a whole number from 0");
21053
20865
  }
21054
20866
  function readPaths(fields, key) {
21055
20867
  const value = fields[key];
21056
20868
  if (value === undefined)
21057
- return absent4();
20869
+ return absent3();
21058
20870
  if (!Array.isArray(value))
21059
20871
  return unusable(key, value, "a list of quoted paths");
21060
20872
  const paths = value.filter((item) => typeof item === "string" && item.trim() !== "");
@@ -22254,10 +22066,10 @@ function withTaskBudget(plan2, usd) {
22254
22066
  }
22255
22067
 
22256
22068
  // src/pr/plans/load.ts
22257
- import { existsSync as existsSync14, readFileSync as readFileSync27 } from "fs";
22258
- import { dirname as dirname12, join as join38 } from "path";
22259
- import { fileURLToPath as fileURLToPath4 } from "url";
22260
- var MODULE_DIR4 = dirname12(fileURLToPath4(import.meta.url));
22069
+ import { existsSync as existsSync12, readFileSync as readFileSync25 } from "fs";
22070
+ import { dirname as dirname10, join as join36 } from "path";
22071
+ import { fileURLToPath as fileURLToPath2 } from "url";
22072
+ var MODULE_DIR2 = dirname10(fileURLToPath2(import.meta.url));
22261
22073
  var PINNED_PLANS_DIRNAME = "plans";
22262
22074
  var PINNED_PLAN_CLASSES = Object.freeze([
22263
22075
  ...SIMPLE_TRIAGE_CLASSES,
@@ -22272,22 +22084,22 @@ function hasPinnedPlan(triageClass) {
22272
22084
  function pinnedPlanCandidates(moduleDir, triageClass) {
22273
22085
  const name2 = pinnedPlanFileName(triageClass);
22274
22086
  return [
22275
- join38(moduleDir, PINNED_PLANS_DIRNAME, name2),
22276
- join38(moduleDir, name2)
22087
+ join36(moduleDir, PINNED_PLANS_DIRNAME, name2),
22088
+ join36(moduleDir, name2)
22277
22089
  ];
22278
22090
  }
22279
22091
  function readPinnedPlan(triageClass, lookup = {}) {
22280
22092
  if (!hasPinnedPlan(triageClass)) {
22281
22093
  throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
22282
22094
  }
22283
- const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
22284
- const found = candidates.find((candidate) => existsSync14(candidate));
22095
+ const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR2, triageClass);
22096
+ const found = candidates.find((candidate) => existsSync12(candidate));
22285
22097
  if (found === undefined) {
22286
22098
  throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
22287
22099
  }
22288
- return readFileSync27(found, "utf8");
22100
+ return readFileSync25(found, "utf8");
22289
22101
  }
22290
- var SLOT_PATTERN2 = /\{[A-Z][A-Z0-9_]*\}/g;
22102
+ var SLOT_PATTERN = /\{[A-Z][A-Z0-9_]*\}/g;
22291
22103
  var CONFLICT_SENTENCE_SLOT = "CONFLICT_SENTENCE";
22292
22104
  var CONFLICT_FILES_SLOT = "CONFLICT_FILES";
22293
22105
  var NO_CONFLICT_FILES = "the paths `git status` reports as conflicted";
@@ -22297,7 +22109,7 @@ var FAILING_LOG_EXCERPT_LINES = FOLLOW_UP_EXCERPT_LINES;
22297
22109
  var NO_FAILING_LOG = "No failing-job log was carried into this plan. Read it yourself with" + " `gh run view <run-id> --log-failed` before changing anything, and say that" + " the excerpt was missing rather than guessing what failed.";
22298
22110
  function fillPinnedPlan(template, values) {
22299
22111
  const missing = [];
22300
- const filled = template.replaceAll(SLOT_PATTERN2, (token) => {
22112
+ const filled = template.replaceAll(SLOT_PATTERN, (token) => {
22301
22113
  const name2 = token.slice(1, -1);
22302
22114
  const value = values[name2];
22303
22115
  if (value === undefined) {
@@ -22428,19 +22240,19 @@ function readAttemptRepeat(input) {
22428
22240
 
22429
22241
  // src/commands/pr/resolve-loop.ts
22430
22242
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
22431
- import { join as join39 } from "path";
22432
- var RESOLVE_SUBDIR = join39(".rafa", "resolve");
22243
+ import { join as join37 } from "path";
22244
+ var RESOLVE_SUBDIR = join37(".rafa", "resolve");
22433
22245
  var RUN_PREFIX = "pr-";
22434
22246
  var ATTEMPT_PREFIX = "attempt-";
22435
22247
  var LOOP_WORDS = Object.freeze(["loop", "start"]);
22436
22248
  var CHILD_OUTPUT = "text";
22437
22249
  function resolveRunDir(home, number, attempt4) {
22438
- return join39(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt4}`);
22250
+ return join37(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt4}`);
22439
22251
  }
22440
22252
  function writeResolvePlan(write) {
22441
22253
  const dir = resolveRunDir(write.home, write.number, write.attempt);
22442
22254
  mkdirSync11(dir, { recursive: true });
22443
- const path = join39(dir, pinnedPlanFileName(write.triageClass));
22255
+ const path = join37(dir, pinnedPlanFileName(write.triageClass));
22444
22256
  writeFileSync10(path, write.plan, "utf8");
22445
22257
  return path;
22446
22258
  }
@@ -22481,8 +22293,8 @@ async function runResolveLoop(run, seams = {}) {
22481
22293
  }
22482
22294
 
22483
22295
  // src/pr/triage/version-convert.ts
22484
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync28, writeFileSync as writeFileSync11 } from "fs";
22485
- import { dirname as dirname13, join as join40, posix as posix2 } from "path";
22296
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
22297
+ import { dirname as dirname11, join as join38, posix as posix2 } from "path";
22486
22298
  var FALLBACK_PLAN_PREFIX = "pr-";
22487
22299
  var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
22488
22300
  var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
@@ -22594,7 +22406,7 @@ function planConversion(input, problems) {
22594
22406
  }
22595
22407
  let branchVersionText;
22596
22408
  try {
22597
- branchVersionText = readFileSync28(join40(input.worktree, settings.releaseVersionFile), "utf8");
22409
+ branchVersionText = readFileSync26(join38(input.worktree, settings.releaseVersionFile), "utf8");
22598
22410
  } catch (error) {
22599
22411
  return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
22600
22412
  }
@@ -22620,9 +22432,9 @@ function writeConversion(input, plan2) {
22620
22432
  [input.settings.releaseChangelog, plan2.changelogText]
22621
22433
  ];
22622
22434
  for (const [path, text] of writes) {
22623
- const target = join40(input.worktree, path);
22435
+ const target = join38(input.worktree, path);
22624
22436
  try {
22625
- mkdirSync12(dirname13(target), { recursive: true });
22437
+ mkdirSync12(dirname11(target), { recursive: true });
22626
22438
  writeFileSync11(target, text);
22627
22439
  } catch (error) {
22628
22440
  return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
@@ -23621,9 +23433,9 @@ function tagSettle(worktree, mode, delivered) {
23621
23433
  }
23622
23434
 
23623
23435
  // src/release/settle-worktree.ts
23624
- import { existsSync as existsSync15, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync3 } from "fs";
23436
+ import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync3 } from "fs";
23625
23437
  import { tmpdir as tmpdir2 } from "os";
23626
- import { join as join41 } from "path";
23438
+ import { join as join39 } from "path";
23627
23439
  var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
23628
23440
  var WORKTREE_DIRECTORY = "base";
23629
23441
  function removeScratch(git, scratch, path) {
@@ -23640,14 +23452,14 @@ function removeScratch(git, scratch, path) {
23640
23452
  problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
23641
23453
  }
23642
23454
  }
23643
- if (problems.length === 0 && existsSync15(scratch)) {
23455
+ if (problems.length === 0 && existsSync13(scratch)) {
23644
23456
  problems.push(`the scratch directory ${scratch} is still there`);
23645
23457
  }
23646
23458
  return problems.length === 0 ? null : problems.join("; ");
23647
23459
  }
23648
23460
  function makeScratch(root) {
23649
23461
  try {
23650
- return realpathSync4(mkdtempSync2(join41(root, SETTLE_SCRATCH_PREFIX)));
23462
+ return realpathSync4(mkdtempSync2(join39(root, SETTLE_SCRATCH_PREFIX)));
23651
23463
  } catch (error) {
23652
23464
  return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
23653
23465
  }
@@ -23669,7 +23481,7 @@ function addWorktree(options) {
23669
23481
  const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
23670
23482
  if (typeof scratch !== "string")
23671
23483
  return scratch;
23672
- const path = join41(scratch, WORKTREE_DIRECTORY);
23484
+ const path = join39(scratch, WORKTREE_DIRECTORY);
23673
23485
  const added = git(["worktree", "add", "--detach", path, commit]);
23674
23486
  if (!added.ok) {
23675
23487
  return {
@@ -23822,7 +23634,7 @@ async function refreshProjectAfterSettle(options) {
23822
23634
  const commits = releasedCommits(options.delivered);
23823
23635
  if (config.boardProjectNumber === null || commits === null || commits.length === 0)
23824
23636
  return null;
23825
- const gh = options.openGh();
23637
+ const gh = openProjectRunner(options.openGh(), config, options.retry);
23826
23638
  let pulls;
23827
23639
  try {
23828
23640
  pulls = await readPulls(gh, commits, branch);
@@ -23998,7 +23810,8 @@ async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
23998
23810
  branch,
23999
23811
  config,
24000
23812
  openGh: () => (seams.gh ?? ((cwd) => createGhRunner({ cwd })))(project.root),
24001
- refresh: seams.refresh
23813
+ refresh: seams.refresh,
23814
+ retry: commandRetrySeams(context.output, context.outputMode)
24002
23815
  });
24003
23816
  return { ...ran.value, project: refreshed };
24004
23817
  }
@@ -24057,8 +23870,8 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
24057
23870
  var settle_default = createReleaseSettleCommand();
24058
23871
 
24059
23872
  // src/commands/release/tag.ts
24060
- import { readFileSync as readFileSync29 } from "fs";
24061
- import { join as join42 } from "path";
23873
+ import { readFileSync as readFileSync27 } from "fs";
23874
+ import { join as join40 } from "path";
24062
23875
 
24063
23876
  // src/release/tag-push.ts
24064
23877
  var LOCAL_REMOTE = ".";
@@ -24134,13 +23947,13 @@ function readBranch2(git) {
24134
23947
  }
24135
23948
  function textOf(path) {
24136
23949
  try {
24137
- return readFileSync29(path, "utf8");
23950
+ return readFileSync27(path, "utf8");
24138
23951
  } catch {
24139
23952
  return null;
24140
23953
  }
24141
23954
  }
24142
23955
  function readVersion(root, configured) {
24143
- const resolved = join42(root, configured);
23956
+ const resolved = join40(root, configured);
24144
23957
  const text = textOf(resolved);
24145
23958
  if (text === null) {
24146
23959
  return {
@@ -24154,7 +23967,7 @@ function readVersion(root, configured) {
24154
23967
  return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
24155
23968
  }
24156
23969
  function readNewestRelease(root, configured) {
24157
- const resolved = join42(root, configured);
23970
+ const resolved = join40(root, configured);
24158
23971
  const text = textOf(resolved);
24159
23972
  if (text === null) {
24160
23973
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -24438,16 +24251,16 @@ var roadmap_default = createRoadmapCommand();
24438
24251
 
24439
24252
  // src/commands/skill/backfill.ts
24440
24253
  import { statSync as statSync12 } from "fs";
24441
- import { join as join47, resolve as resolve15 } from "path";
24254
+ import { join as join45, resolve as resolve15 } from "path";
24442
24255
 
24443
24256
  // src/backfill/backup.ts
24444
- import { copyFileSync as copyFileSync4, existsSync as existsSync16, mkdirSync as mkdirSync13 } from "fs";
24445
- import { dirname as dirname14, isAbsolute as isAbsolute7, join as join44, relative as relative10, resolve as resolve13, sep as sep5 } from "path";
24257
+ import { copyFileSync as copyFileSync4, existsSync as existsSync14, mkdirSync as mkdirSync13 } from "fs";
24258
+ import { dirname as dirname12, isAbsolute as isAbsolute7, join as join42, relative as relative10, resolve as resolve13, sep as sep5 } from "path";
24446
24259
 
24447
24260
  // src/backfill/proposal-file.ts
24448
- import { readdirSync as readdirSync7, readFileSync as readFileSync30 } from "fs";
24449
- import { join as join43 } from "path";
24450
- var BACKFILL_PATH = join43(".rafa", "backfill");
24261
+ import { readdirSync as readdirSync7, readFileSync as readFileSync28 } from "fs";
24262
+ import { join as join41 } from "path";
24263
+ var BACKFILL_PATH = join41(".rafa", "backfill");
24451
24264
  var PROPOSAL_FILE_PREFIX = "proposals-";
24452
24265
  var PROPOSAL_FILE_EXTENSION = ".yaml";
24453
24266
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
@@ -24473,7 +24286,7 @@ var PROPOSAL_FILE_HEADER = [
24473
24286
  ].join(`
24474
24287
  `);
24475
24288
  function proposalPath(dir, batch) {
24476
- return join43(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
24289
+ return join41(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
24477
24290
  }
24478
24291
  function isProposalFileName(name2) {
24479
24292
  return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
@@ -24609,9 +24422,9 @@ function readProposalFiles(dir) {
24609
24422
  return [];
24610
24423
  }
24611
24424
  return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
24612
- const path = join43(dir, name2);
24425
+ const path = join41(dir, name2);
24613
24426
  try {
24614
- return { path, result: parseProposalFile(readFileSync30(path, "utf8")) };
24427
+ return { path, result: parseProposalFile(readFileSync28(path, "utf8")) };
24615
24428
  } catch (error) {
24616
24429
  return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
24617
24430
  }
@@ -24619,10 +24432,10 @@ function readProposalFiles(dir) {
24619
24432
  }
24620
24433
 
24621
24434
  // src/backfill/backup.ts
24622
- var BACKUP_PATH = join44(BACKFILL_PATH, "backup");
24435
+ var BACKUP_PATH = join42(BACKFILL_PATH, "backup");
24623
24436
  var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
24624
24437
  function backupDirectory(base) {
24625
- return join44(base, BACKUP_PATH);
24438
+ return join42(base, BACKUP_PATH);
24626
24439
  }
24627
24440
  function isUnder(root, path) {
24628
24441
  const step = relative10(resolve13(root), resolve13(path));
@@ -24631,7 +24444,7 @@ function isUnder(root, path) {
24631
24444
  function backupPathFor(path, root, backupDir) {
24632
24445
  if (!isUnder(root, path))
24633
24446
  return null;
24634
- return join44(backupDir, relative10(resolve13(root), resolve13(path)));
24447
+ return join42(backupDir, relative10(resolve13(root), resolve13(path)));
24635
24448
  }
24636
24449
  function backupFile(path, options) {
24637
24450
  if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
@@ -24640,11 +24453,11 @@ function backupFile(path, options) {
24640
24453
  const backup = backupPathFor(path, options.root, options.backupDir);
24641
24454
  if (backup === null)
24642
24455
  return { path, kind: "outside", backup: null };
24643
- if (!existsSync16(path))
24456
+ if (!existsSync14(path))
24644
24457
  return { path, kind: "missing", backup: null };
24645
- if (existsSync16(backup))
24458
+ if (existsSync14(backup))
24646
24459
  return { path, kind: "kept", backup };
24647
- mkdirSync13(dirname14(backup), { recursive: true });
24460
+ mkdirSync13(dirname12(backup), { recursive: true });
24648
24461
  copyFileSync4(path, backup);
24649
24462
  return { path, kind: "copied", backup };
24650
24463
  }
@@ -24667,7 +24480,7 @@ function countBackups(results) {
24667
24480
  }
24668
24481
 
24669
24482
  // src/backfill/derive.ts
24670
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync12 } from "fs";
24483
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync12 } from "fs";
24671
24484
  import { basename as basename7 } from "path";
24672
24485
  var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
24673
24486
  function renderWhenToUse(trigger, prevents) {
@@ -24793,7 +24606,7 @@ function planFile(entry, options) {
24793
24606
  }
24794
24607
  let original;
24795
24608
  try {
24796
- original = readFileSync31(entry.path, "utf8");
24609
+ original = readFileSync29(entry.path, "utf8");
24797
24610
  } catch (error) {
24798
24611
  return inert(entry, "skipped", `the file cannot be read (${error instanceof Error ? error.message : String(error)})`);
24799
24612
  }
@@ -24837,7 +24650,7 @@ function planDerivation(root, options) {
24837
24650
  function applyFile(action, options) {
24838
24651
  if (action.text === null || action.original === null)
24839
24652
  return action;
24840
- const current = readFileSync31(action.path, "utf8");
24653
+ const current = readFileSync29(action.path, "utf8");
24841
24654
  if (current !== action.original) {
24842
24655
  return {
24843
24656
  ...action,
@@ -24868,7 +24681,7 @@ function applyDerivation(plan2, options) {
24868
24681
  }
24869
24682
 
24870
24683
  // src/backfill/proposal-batch.ts
24871
- import { readFileSync as readFileSync32 } from "fs";
24684
+ import { readFileSync as readFileSync30 } from "fs";
24872
24685
  import { relative as relative11 } from "path";
24873
24686
 
24874
24687
  // src/demote/report.ts
@@ -25167,7 +24980,7 @@ function readCandidate(root, entry) {
25167
24980
  return null;
25168
24981
  let text;
25169
24982
  try {
25170
- text = readFileSync32(entry.path, "utf8");
24983
+ text = readFileSync30(entry.path, "utf8");
25171
24984
  } catch {
25172
24985
  return null;
25173
24986
  }
@@ -25273,8 +25086,8 @@ function parseSessionAnswer(stdout) {
25273
25086
  }
25274
25087
 
25275
25088
  // src/backfill/propose.ts
25276
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync33, writeFileSync as writeFileSync13 } from "fs";
25277
- import { join as join45, resolve as resolve14 } from "path";
25089
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync31, writeFileSync as writeFileSync13 } from "fs";
25090
+ import { join as join43, resolve as resolve14 } from "path";
25278
25091
  var PROPOSAL_ACTION_KINDS = [
25279
25092
  "applied",
25280
25093
  "unchanged",
@@ -25423,7 +25236,7 @@ function writeRow(path, row2, original, options) {
25423
25236
  };
25424
25237
  }
25425
25238
  function applyRow(root, row2, options) {
25426
- const path = join45(root, ...row2.path.split("/"));
25239
+ const path = join43(root, ...row2.path.split("/"));
25427
25240
  if (row2.status === UNANSWERED_ROW) {
25428
25241
  return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
25429
25242
  }
@@ -25432,7 +25245,7 @@ function applyRow(root, row2, options) {
25432
25245
  }
25433
25246
  let original;
25434
25247
  try {
25435
- original = readFileSync33(path, "utf8");
25248
+ original = readFileSync31(path, "utf8");
25436
25249
  } catch {
25437
25250
  return inert2(path, row2, "refused", "the file the row names is gone");
25438
25251
  }
@@ -25460,7 +25273,7 @@ function applyProposals(files, root, options) {
25460
25273
  for (const file of files) {
25461
25274
  const refusal19 = fileRefusal(file, root);
25462
25275
  for (const row2 of file.rows) {
25463
- const action = refusal19 === null ? applyRow(root, row2, options) : inert2(join45(root, ...row2.path.split("/")), row2, "refused", refusal19);
25276
+ const action = refusal19 === null ? applyRow(root, row2, options) : inert2(join43(root, ...row2.path.split("/")), row2, "refused", refusal19);
25464
25277
  actions.push(action);
25465
25278
  if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
25466
25279
  triggers[row2.name] = row2.trigger;
@@ -25471,13 +25284,13 @@ function applyProposals(files, root, options) {
25471
25284
  }
25472
25285
 
25473
25286
  // src/demote/select.ts
25474
- import { readdirSync as readdirSync8, readFileSync as readFileSync34, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
25475
- import { basename as basename8, dirname as dirname15, join as join46 } from "path";
25287
+ import { readdirSync as readdirSync8, readFileSync as readFileSync32, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
25288
+ import { basename as basename8, dirname as dirname13, join as join44 } from "path";
25476
25289
  var SKILLS_DIRECTORY = "skills";
25477
25290
  var CLAUDE_DIRECTORY = ".claude";
25478
25291
  var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
25479
- var DEMOTED_PATH = join46(".rafa", "demoted");
25480
- var INSTINCTS_PATH = join46(".rafa", "instincts");
25292
+ var DEMOTED_PATH = join44(".rafa", "demoted");
25293
+ var INSTINCTS_PATH = join44(".rafa", "instincts");
25481
25294
  var REPORT_FILE = "report.md";
25482
25295
  function realOrSelf(path) {
25483
25296
  try {
@@ -25496,18 +25309,18 @@ function samePath(left, right) {
25496
25309
  function resolveDemotionScope(dir, home) {
25497
25310
  if (basename8(dir) !== SKILLS_DIRECTORY)
25498
25311
  return null;
25499
- const claude = dirname15(dir);
25312
+ const claude = dirname13(dir);
25500
25313
  if (basename8(claude) !== CLAUDE_DIRECTORY)
25501
25314
  return null;
25502
- const base = dirname15(claude);
25503
- const demotedDir = join46(base, DEMOTED_PATH);
25315
+ const base = dirname13(claude);
25316
+ const demotedDir = join44(base, DEMOTED_PATH);
25504
25317
  return {
25505
25318
  scope: samePath(base, home) ? "user" : "project",
25506
25319
  dir,
25507
25320
  base,
25508
25321
  demotedDir,
25509
- instinctsDir: join46(base, INSTINCTS_PATH),
25510
- reportPath: join46(demotedDir, REPORT_FILE)
25322
+ instinctsDir: join44(base, INSTINCTS_PATH),
25323
+ reportPath: join44(demotedDir, REPORT_FILE)
25511
25324
  };
25512
25325
  }
25513
25326
  function entryKind(path) {
@@ -25533,7 +25346,7 @@ function readSelected(absolute, path, entries) {
25533
25346
  let text;
25534
25347
  let mtime;
25535
25348
  try {
25536
- text = readFileSync34(absolute, "utf8");
25349
+ text = readFileSync32(absolute, "utf8");
25537
25350
  mtime = statSync11(absolute).mtime.toISOString();
25538
25351
  } catch {
25539
25352
  return null;
@@ -25549,14 +25362,14 @@ function selectsLearned(text, scope) {
25549
25362
  return origin === AUTO_EXTRACTED_ORIGIN;
25550
25363
  }
25551
25364
  function selectLearned(scope) {
25552
- const dir = join46(scope.dir, LEARNED_DIRECTORY);
25365
+ const dir = join44(scope.dir, LEARNED_DIRECTORY);
25553
25366
  if (entryKind(dir) !== "directory")
25554
25367
  return [];
25555
25368
  const selected = [];
25556
25369
  for (const name2 of visibleNames(dir)) {
25557
25370
  if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
25558
25371
  continue;
25559
- const absolute = join46(dir, name2);
25372
+ const absolute = join44(dir, name2);
25560
25373
  if (entryKind(absolute) !== "file")
25561
25374
  continue;
25562
25375
  const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
@@ -25570,10 +25383,10 @@ function selectSkillFiles(scope) {
25570
25383
  for (const name2 of visibleNames(scope.dir)) {
25571
25384
  if (name2 === LEARNED_DIRECTORY)
25572
25385
  continue;
25573
- const skillDir = join46(scope.dir, name2);
25386
+ const skillDir = join44(scope.dir, name2);
25574
25387
  if (entryKind(skillDir) !== "directory")
25575
25388
  continue;
25576
- const absolute = join46(skillDir, SKILL_FILE);
25389
+ const absolute = join44(skillDir, SKILL_FILE);
25577
25390
  if (entryKind(absolute) !== "file")
25578
25391
  continue;
25579
25392
  const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
@@ -25758,7 +25571,7 @@ function reviewedProposals(dir) {
25758
25571
  return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
25759
25572
  }
25760
25573
  function rowPaths(files, root) {
25761
- return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join47(root, ...row2.path.split("/"))));
25574
+ return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join45(root, ...row2.path.split("/"))));
25762
25575
  }
25763
25576
  function runApply(context, scope, backfillDir, projectRoot2) {
25764
25577
  const options = checkOptions(context, projectRoot2);
@@ -25804,7 +25617,7 @@ async function runBackfill(context, seams) {
25804
25617
  const dir = readBackfillDirectory(context.args, seams.cwd);
25805
25618
  const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
25806
25619
  const scope = backfillScopeOf(dir, projectOf11(context).home);
25807
- const backfillDir = join47(scope.base, BACKFILL_PATH);
25620
+ const backfillDir = join45(scope.base, BACKFILL_PATH);
25808
25621
  if (mode === "propose") {
25809
25622
  await runPropose(context, scope, backfillDir, seams);
25810
25623
  return;
@@ -25918,13 +25731,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
25918
25731
  var check_default3 = createSkillCheckCommand();
25919
25732
 
25920
25733
  // src/commands/skill/demote.ts
25921
- import { existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync36, statSync as statSync14, writeFileSync as writeFileSync15 } from "fs";
25922
- import { dirname as dirname17, resolve as resolve16 } from "path";
25734
+ import { existsSync as existsSync16, mkdirSync as mkdirSync16, readFileSync as readFileSync34, statSync as statSync14, writeFileSync as writeFileSync15 } from "fs";
25735
+ import { dirname as dirname15, resolve as resolve16 } from "path";
25923
25736
 
25924
25737
  // src/demote/apply.ts
25925
- import { copyFileSync as copyFileSync5, existsSync as existsSync17, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readdirSync as readdirSync9, readFileSync as readFileSync35, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync13, unlinkSync, writeFileSync as writeFileSync14 } from "fs";
25738
+ import { copyFileSync as copyFileSync5, existsSync as existsSync15, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readdirSync as readdirSync9, readFileSync as readFileSync33, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync13, unlinkSync, writeFileSync as writeFileSync14 } from "fs";
25926
25739
  import { tmpdir as tmpdir3 } from "os";
25927
- import { dirname as dirname16, join as join48 } from "path";
25740
+ import { dirname as dirname14, join as join46 } from "path";
25928
25741
  var DEMOTION_ACTION_KINDS = [
25929
25742
  "demoted",
25930
25743
  "kept",
@@ -25945,13 +25758,13 @@ function learnedSkillName(path) {
25945
25758
  return base.slice(0, -MARKDOWN_EXTENSION.length);
25946
25759
  }
25947
25760
  function sourcePath(scope, path) {
25948
- return join48(scope.dir, ...path.split("/"));
25761
+ return join46(scope.dir, ...path.split("/"));
25949
25762
  }
25950
25763
  function demotedPath(scope, path) {
25951
- return join48(scope.demotedDir, ...path.split("/"));
25764
+ return join46(scope.demotedDir, ...path.split("/"));
25952
25765
  }
25953
25766
  function promotedPath(scope, path) {
25954
- return join48(scope.dir, learnedSkillName(path), SKILL_FILE);
25767
+ return join46(scope.dir, learnedSkillName(path), SKILL_FILE);
25955
25768
  }
25956
25769
  function takenInstinctIds(dir) {
25957
25770
  let names;
@@ -25964,7 +25777,7 @@ function takenInstinctIds(dir) {
25964
25777
  }
25965
25778
  function readText5(path) {
25966
25779
  try {
25967
- return statSync13(path).isFile() ? readFileSync35(path, "utf8") : null;
25780
+ return statSync13(path).isFile() ? readFileSync33(path, "utf8") : null;
25968
25781
  } catch {
25969
25782
  return null;
25970
25783
  }
@@ -25992,7 +25805,7 @@ function planKept(scope, row2, file) {
25992
25805
  return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
25993
25806
  }
25994
25807
  const to = promotedPath(scope, row2.path);
25995
- if (existsSync17(to) || existsSync17(dirname16(to))) {
25808
+ if (existsSync15(to) || existsSync15(dirname14(to))) {
25996
25809
  return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
25997
25810
  }
25998
25811
  return {
@@ -26006,7 +25819,7 @@ function planKept(scope, row2, file) {
26006
25819
  };
26007
25820
  }
26008
25821
  function checkCandidate(dir, id2, text, pathDirs) {
26009
- const path = join48(dir, `${id2}${MARKDOWN_EXTENSION}`);
25822
+ const path = join46(dir, `${id2}${MARKDOWN_EXTENSION}`);
26010
25823
  writeFileSync14(path, text, "utf8");
26011
25824
  const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
26012
25825
  return failureLines2(report5.issues);
@@ -26027,11 +25840,11 @@ function planDemoted(scope, row2, file, context) {
26027
25840
  if (failures.length > 0) {
26028
25841
  return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
26029
25842
  }
26030
- const destination = join48(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
25843
+ const destination = join46(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
26031
25844
  const kept = demotedPath(scope, row2.path);
26032
- if (existsSync17(destination))
25845
+ if (existsSync15(destination))
26033
25846
  return inert3(row2.path, "observation", "refused", `${destination} is already there`);
26034
- if (existsSync17(kept))
25847
+ if (existsSync15(kept))
26035
25848
  return inert3(row2.path, "observation", "refused", `${kept} is already there`);
26036
25849
  context.taken.push(id2);
26037
25850
  return {
@@ -26074,7 +25887,7 @@ function countActions(actions) {
26074
25887
  return counts;
26075
25888
  }
26076
25889
  function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
26077
- const temp = mkdtempSync3(join48(tmpdir3(), "rafa-demote-"));
25890
+ const temp = mkdtempSync3(join46(tmpdir3(), "rafa-demote-"));
26078
25891
  const taken = [...takenInstinctIds(scope.instinctsDir)];
26079
25892
  try {
26080
25893
  const actions = report5.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
@@ -26084,7 +25897,7 @@ function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
26084
25897
  }
26085
25898
  }
26086
25899
  function moveFile(move3) {
26087
- mkdirSync15(dirname16(move3.to), { recursive: true });
25900
+ mkdirSync15(dirname14(move3.to), { recursive: true });
26088
25901
  try {
26089
25902
  renameSync2(move3.from, move3.to);
26090
25903
  } catch {
@@ -26095,7 +25908,7 @@ function moveFile(move3) {
26095
25908
  function applyDemotion(plan2) {
26096
25909
  for (const action of plan2.actions) {
26097
25910
  if (action.write !== null) {
26098
- mkdirSync15(dirname16(action.write.path), { recursive: true });
25911
+ mkdirSync15(dirname14(action.write.path), { recursive: true });
26099
25912
  writeFileSync14(action.write.path, action.write.text, "utf8");
26100
25913
  }
26101
25914
  if (action.move !== null)
@@ -26193,9 +26006,9 @@ function verdictLine2(report5) {
26193
26006
  return `${String(report5.rows.length)} file(s): ${parts.join(", ")}`;
26194
26007
  }
26195
26008
  function previousReport(path, context) {
26196
- if (!existsSync18(path))
26009
+ if (!existsSync16(path))
26197
26010
  return null;
26198
- const parsed = parseDemotionReport(readFileSync36(path, "utf8"));
26011
+ const parsed = parseDemotionReport(readFileSync34(path, "utf8"));
26199
26012
  if (parsed.report !== null)
26200
26013
  return parsed.report;
26201
26014
  context.output.warn(`the report already at ${path} does not parse, so no override in it is carried forward:` + ` ${parsed.issues.map((issue2) => `line ${String(issue2.line)} ${issue2.code}`).join(", ")}`);
@@ -26204,7 +26017,7 @@ function previousReport(path, context) {
26204
26017
  function runWrite(context, scope) {
26205
26018
  const files = selectSources(scope);
26206
26019
  const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
26207
- mkdirSync16(dirname17(scope.reportPath), { recursive: true });
26020
+ mkdirSync16(dirname15(scope.reportPath), { recursive: true });
26208
26021
  writeFileSync15(scope.reportPath, writeDemotionReport(report5), "utf8");
26209
26022
  const result2 = {
26210
26023
  applied: false,
@@ -26228,10 +26041,10 @@ function runWrite(context, scope) {
26228
26041
  context.output.info(line);
26229
26042
  }
26230
26043
  function reviewedReport(scope) {
26231
- if (!existsSync18(scope.reportPath)) {
26044
+ if (!existsSync16(scope.reportPath)) {
26232
26045
  throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
26233
26046
  }
26234
- const parsed = parseDemotionReport(readFileSync36(scope.reportPath, "utf8"));
26047
+ const parsed = parseDemotionReport(readFileSync34(scope.reportPath, "utf8"));
26235
26048
  if (parsed.report === null) {
26236
26049
  const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
26237
26050
  throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
@@ -26604,7 +26417,7 @@ function statusData(sections) {
26604
26417
  }
26605
26418
 
26606
26419
  // src/status/sections.ts
26607
- import { existsSync as existsSync19 } from "fs";
26420
+ import { existsSync as existsSync17 } from "fs";
26608
26421
 
26609
26422
  // src/status/blocked-count.ts
26610
26423
  async function labelsCount(options) {
@@ -26864,7 +26677,7 @@ function nextOfPlace(current, epic, walk) {
26864
26677
  return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
26865
26678
  }
26866
26679
  function hasBoards(root, listing2) {
26867
- if (existsSync19(positionFilePath(root)))
26680
+ if (existsSync17(positionFilePath(root)))
26868
26681
  return true;
26869
26682
  return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
26870
26683
  }
@@ -27039,8 +26852,8 @@ function createStatusCommand(seams = DEFAULT_STATUS_SEAMS) {
27039
26852
  var status_default4 = createStatusCommand();
27040
26853
 
27041
26854
  // src/commands/stretch/end.ts
27042
- import { existsSync as existsSync20, mkdirSync as mkdirSync17, readFileSync as readFileSync37, writeFileSync as writeFileSync16 } from "fs";
27043
- import { dirname as dirname18, join as join49 } from "path";
26855
+ import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
26856
+ import { dirname as dirname16, join as join47 } from "path";
27044
26857
  var COMMAND_NAME19 = "rafa stretch end";
27045
26858
  var STRETCH_END_USAGE = "rafa stretch end [--dry-run]";
27046
26859
  var REPORT_FILE2 = "report.md";
@@ -27048,10 +26861,10 @@ var BODY_FILE = "pr-body.md";
27048
26861
  var REFUSED_EXIT10 = 1;
27049
26862
  var FAILED_EXIT6 = 2;
27050
26863
  function reportPath(root, n) {
27051
- return join49(stretchFolder(root, n), REPORT_FILE2);
26864
+ return join47(stretchFolder(root, n), REPORT_FILE2);
27052
26865
  }
27053
26866
  function bodyPath(root, n) {
27054
- return join49(stretchFolder(root, n), BODY_FILE);
26867
+ return join47(stretchFolder(root, n), BODY_FILE);
27055
26868
  }
27056
26869
  function endTitle(n) {
27057
26870
  return `Stretch ${String(n)}`;
@@ -27087,11 +26900,11 @@ function requireStretchBase2(prBase) {
27087
26900
  }
27088
26901
  function readReport(root, n) {
27089
26902
  const path = reportPath(root, n);
27090
- if (!existsSync20(path)) {
26903
+ if (!existsSync18(path)) {
27091
26904
  throw refusal19(`there is no ${path}: write the stretch report first, since it is the pull request's body. Nothing was run.`);
27092
26905
  }
27093
26906
  try {
27094
- return readFileSync37(path, "utf8");
26907
+ return readFileSync35(path, "utf8");
27095
26908
  } catch (error) {
27096
26909
  throw refusal19(`${path} could not be read: ${messageOf(error)}. Nothing was run.`);
27097
26910
  }
@@ -27104,11 +26917,11 @@ function recordText(record, name2, path) {
27104
26917
  }
27105
26918
  function readStretchRecord(root, n) {
27106
26919
  const path = stretchRecordPath(root, n);
27107
- if (!existsSync20(path))
26920
+ if (!existsSync18(path))
27108
26921
  return null;
27109
26922
  let value;
27110
26923
  try {
27111
- value = JSON.parse(readFileSync37(path, "utf8"));
26924
+ value = JSON.parse(readFileSync35(path, "utf8"));
27112
26925
  } catch (error) {
27113
26926
  throw refusal19(`${path} could not be read as JSON: ${messageOf(error)}. Nothing was run.`);
27114
26927
  }
@@ -27153,7 +26966,7 @@ function putBack(context, input, merged2) {
27153
26966
  const path = configFilePath(input.root);
27154
26967
  let edit;
27155
26968
  try {
27156
- edit = withConfigSetting(readFileSync37(path, "utf8"), PR_BASE_KEY, value, path);
26969
+ edit = withConfigSetting(readFileSync35(path, "utf8"), PR_BASE_KEY, value, path);
27157
26970
  } catch (error) {
27158
26971
  const why = error instanceof ConfigSetRefusal ? `${PR_BASE_KEY} cannot be set: ${error.message}` : `${path} could not be read: ${messageOf(error)}`;
27159
26972
  throw refusal19(`${why}. Nothing was set.`);
@@ -27190,7 +27003,7 @@ async function openEnd(context, input, body, seams) {
27190
27003
  return;
27191
27004
  }
27192
27005
  try {
27193
- mkdirSync17(dirname18(path), { recursive: true });
27006
+ mkdirSync17(dirname16(path), { recursive: true });
27194
27007
  writeFileSync16(path, body, "utf8");
27195
27008
  } catch (error) {
27196
27009
  throw failure4(`${path} could not be written: ${messageOf(error)}. No pull request was opened.`);
@@ -27275,8 +27088,8 @@ function createUpdateStub(spec) {
27275
27088
  var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
27276
27089
 
27277
27090
  // src/project/lock.ts
27278
- import { existsSync as existsSync21, lstatSync as lstatSync2, readFileSync as readFileSync38, writeFileSync as writeFileSync17 } from "fs";
27279
- import { join as join50 } from "path";
27091
+ import { existsSync as existsSync19, lstatSync as lstatSync2, readFileSync as readFileSync36, writeFileSync as writeFileSync17 } from "fs";
27092
+ import { join as join48 } from "path";
27280
27093
  var LOCK_FILE = "rafa.lock";
27281
27094
  var LOCKFILE_VERSION = 1;
27282
27095
 
@@ -27284,7 +27097,7 @@ class ProjectLockError extends Error {
27284
27097
  name = "ProjectLockError";
27285
27098
  }
27286
27099
  function lockPath(root) {
27287
- return join50(root, LOCK_FILE);
27100
+ return join48(root, LOCK_FILE);
27288
27101
  }
27289
27102
  function projectLockText(version2) {
27290
27103
  const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
@@ -27317,14 +27130,14 @@ function lockOf(value) {
27317
27130
  }
27318
27131
  function readProjectLock(root) {
27319
27132
  const path = lockPath(root);
27320
- if (!existsSync21(path)) {
27133
+ if (!existsSync19(path)) {
27321
27134
  if (isLink(path))
27322
27135
  refuse10("is a link to nothing");
27323
27136
  return null;
27324
27137
  }
27325
27138
  let text;
27326
27139
  try {
27327
- text = readFileSync38(path, "utf8");
27140
+ text = readFileSync36(path, "utf8");
27328
27141
  } catch (error) {
27329
27142
  refuse10(`could not be read: ${messageOf(error)}`);
27330
27143
  }
@@ -27341,11 +27154,11 @@ function writeProjectLock(root, version2) {
27341
27154
  }
27342
27155
 
27343
27156
  // src/project/update-current.ts
27344
- import { existsSync as existsSync22 } from "fs";
27345
- import { join as join51 } from "path";
27157
+ import { existsSync as existsSync20 } from "fs";
27158
+ import { join as join49 } from "path";
27346
27159
  var DEPRECATIONS = Object.freeze([]);
27347
27160
  function missingFolders(root) {
27348
- return PROJECT_TREE.map((name2) => join51(SCOPE_DIR, name2)).filter((relative12) => !existsSync22(join51(root, relative12)));
27161
+ return PROJECT_TREE.map((name2) => join49(SCOPE_DIR, name2)).filter((relative12) => !existsSync20(join49(root, relative12)));
27349
27162
  }
27350
27163
  async function readBoard3(access) {
27351
27164
  if (access.gh === null)
@@ -27398,7 +27211,7 @@ class UpdateStepError extends Error {
27398
27211
  }
27399
27212
  }
27400
27213
  function foldersNowThere(plan2) {
27401
- return plan2.folders.filter((relative12) => existsSync22(join51(plan2.root, relative12)));
27214
+ return plan2.folders.filter((relative12) => existsSync20(join49(plan2.root, relative12)));
27402
27215
  }
27403
27216
  function writeFolders(plan2) {
27404
27217
  if (plan2.folders.length === 0)