@open-tomato/rafa 0.9.0 → 0.9.2

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.
Files changed (4) hide show
  1. package/README.md +25 -8
  2. package/dist/cli.js +800 -505
  3. package/dist/index.js +401 -146
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1914,7 +1914,7 @@ async function report(args, repoRoot) {
1914
1914
  }
1915
1915
  }
1916
1916
  // package.json
1917
- var version = "0.9.0";
1917
+ var version = "0.9.2";
1918
1918
 
1919
1919
  // src/modules/manifest.ts
1920
1920
  var MANIFEST_VERSION = 1;
@@ -3503,8 +3503,8 @@ function createGhIssueBoard(options) {
3503
3503
  }
3504
3504
 
3505
3505
  // src/board/issue.ts
3506
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
3507
- import { dirname as dirname2, resolve as resolve5 } from "path";
3506
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, statSync as statSync5, writeFileSync as writeFileSync2 } from "fs";
3507
+ import { dirname as dirname2, resolve as resolve6 } from "path";
3508
3508
 
3509
3509
  // src/board/naming.ts
3510
3510
  import path2 from "path";
@@ -3584,8 +3584,72 @@ function notesPath(specsDir, issue) {
3584
3584
  return path2.join(specsDir, notesFileName(issue));
3585
3585
  }
3586
3586
 
3587
+ // src/board/previous-copy.ts
3588
+ import { linkSync, mkdirSync as mkdirSync2, readdirSync as readdirSync3, statSync as statSync4, unlinkSync } from "fs";
3589
+ import { basename as basename3, extname, join as join5, resolve as resolve5 } from "path";
3590
+ var PREFIX2 = "board previous copy";
3591
+ var PREVIOUS_DIR_NAME = "previous";
3592
+ var COPY_EXTENSION = ".md";
3593
+ var FIRST_SUFFIX = 2;
3594
+ function previousDir(specsDir) {
3595
+ return join5(specsDir, PREVIOUS_DIR_NAME);
3596
+ }
3597
+ function two(value) {
3598
+ return String(value).padStart(2, "0");
3599
+ }
3600
+ function previousTimestamp(mtime) {
3601
+ const date = `${String(mtime.getUTCFullYear()).padStart(4, "0")}${two(mtime.getUTCMonth() + 1)}${two(mtime.getUTCDate())}`;
3602
+ const time = `${two(mtime.getUTCHours())}${two(mtime.getUTCMinutes())}${two(mtime.getUTCSeconds())}`;
3603
+ return `${date}T${time}Z`;
3604
+ }
3605
+ function previousCopyName(savedName, timestamp, attempt) {
3606
+ const stem = basename3(savedName, extname(savedName));
3607
+ const suffix = attempt < FIRST_SUFFIX ? "" : `-${String(attempt)}`;
3608
+ return `${stem}.${timestamp}${suffix}${COPY_EXTENSION}`;
3609
+ }
3610
+ function isTaken(error) {
3611
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
3612
+ }
3613
+ function linkUnderFreeName(source, directory, savedName, timestamp) {
3614
+ for (let attempt = 1;; attempt += 1) {
3615
+ const name = previousCopyName(savedName, timestamp, attempt);
3616
+ try {
3617
+ linkSync(source, join5(directory, name));
3618
+ return name;
3619
+ } catch (error) {
3620
+ if (!isTaken(error))
3621
+ throw error;
3622
+ }
3623
+ }
3624
+ }
3625
+ function movePreviousCopy(options) {
3626
+ const { repoRoot, specsDir, path: path3 } = options;
3627
+ const source = resolve5(repoRoot, path3);
3628
+ const directory = previousDir(specsDir);
3629
+ const absoluteDirectory = resolve5(repoRoot, directory);
3630
+ const savedName = basename3(path3);
3631
+ let name;
3632
+ try {
3633
+ const timestamp = previousTimestamp(statSync4(source).mtime);
3634
+ mkdirSync2(absoluteDirectory, { recursive: true });
3635
+ name = linkUnderFreeName(source, absoluteDirectory, savedName, timestamp);
3636
+ } catch (error) {
3637
+ throw new Error(`${PREFIX2}: ${path3} could not be moved to ${directory}: ${messageOf(error)}`, { cause: error });
3638
+ }
3639
+ const moved = {
3640
+ path: join5(directory, name),
3641
+ absolute: join5(absoluteDirectory, name)
3642
+ };
3643
+ try {
3644
+ unlinkSync(source);
3645
+ } catch (error) {
3646
+ throw new Error(`${PREFIX2}: ${path3} was copied to ${moved.path} but could not be removed: ${messageOf(error)}`, { cause: error });
3647
+ }
3648
+ return moved;
3649
+ }
3650
+
3587
3651
  // src/board/issue.ts
3588
- var PREFIX2 = "board issue";
3652
+ var PREFIX3 = "board issue";
3589
3653
  var ISSUE_VIEW_FIELDS = "number,title,body,state,labels,author";
3590
3654
  var SPEC_LABEL = "type:spec";
3591
3655
  var ISSUE_REFUSAL_EXIT = 2;
@@ -3596,7 +3660,7 @@ function detailOf3(result, command) {
3596
3660
  }
3597
3661
  function issueNumber2(value) {
3598
3662
  if (!Number.isSafeInteger(value) || value < 1) {
3599
- throw new TypeError(`${PREFIX2}: the read refused issue number ${describeValue(value)}, expected a positive whole number`);
3663
+ throw new TypeError(`${PREFIX3}: the read refused issue number ${describeValue(value)}, expected a positive whole number`);
3600
3664
  }
3601
3665
  return String(value);
3602
3666
  }
@@ -3631,11 +3695,11 @@ function readViewedIssue(stdout, number, command) {
3631
3695
  try {
3632
3696
  payload = JSON.parse(stdout);
3633
3697
  } catch (error) {
3634
- throw new Error(`${PREFIX2}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
3698
+ throw new Error(`${PREFIX3}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
3635
3699
  }
3636
3700
  const problem = viewProblem(payload, number);
3637
3701
  if (problem !== null)
3638
- throw new Error(`${PREFIX2}: ${command} answered ${problem}`);
3702
+ throw new Error(`${PREFIX3}: ${command} answered ${problem}`);
3639
3703
  const checked = payload;
3640
3704
  return {
3641
3705
  number,
@@ -3653,7 +3717,7 @@ function createGhSpecIssueReader(options) {
3653
3717
  const command = `gh issue view ${number} --json ${ISSUE_VIEW_FIELDS}`;
3654
3718
  const result = await gh(["issue", "view", number, "--json", ISSUE_VIEW_FIELDS]);
3655
3719
  if (!result.ok)
3656
- throw new Error(`${PREFIX2}: ${detailOf3(result, command)}`);
3720
+ throw new Error(`${PREFIX3}: ${detailOf3(result, command)}`);
3657
3721
  return readViewedIssue(result.stdout, issue, command);
3658
3722
  };
3659
3723
  }
@@ -3691,10 +3755,91 @@ ${local}`
3691
3755
  `)}
3692
3756
  `;
3693
3757
  }
3758
+ var NOTES_SEPARATOR = `
3759
+
3760
+ ${LOCAL_NOTES_HEADING}
3761
+
3762
+ `;
3763
+ var HEADING = /^#{1,6}(?:[ \t]|$)/u;
3764
+ var FENCE = /^ {0,3}(?:`{3,}|~{3,})/u;
3765
+ function commonLines(before, after) {
3766
+ let previous = new Uint32Array(after.length + 1);
3767
+ for (const line of before) {
3768
+ const row = new Uint32Array(after.length + 1);
3769
+ after.forEach((other, index) => {
3770
+ row[index + 1] = line === other ? (previous[index] ?? 0) + 1 : Math.max(previous[index + 1] ?? 0, row[index] ?? 0);
3771
+ });
3772
+ previous = row;
3773
+ }
3774
+ return previous[after.length] ?? 0;
3775
+ }
3776
+ function headingName(line) {
3777
+ const text = line.replace(/^#+/u, "").replace(/[ \t]+#+[ \t]*$/u, "").trim();
3778
+ return text === "" ? line.trim() : text;
3779
+ }
3780
+ function headingSections(lines) {
3781
+ const sections = new Map;
3782
+ let fenced = false;
3783
+ let current = null;
3784
+ const close = () => {
3785
+ if (current === null)
3786
+ return;
3787
+ sections.set(current.key, [...sections.get(current.key) ?? [], current.lines.join(`
3788
+ `)]);
3789
+ };
3790
+ for (const line of lines) {
3791
+ if (FENCE.test(line))
3792
+ fenced = !fenced;
3793
+ if (fenced || !HEADING.test(line)) {
3794
+ current?.lines.push(line);
3795
+ continue;
3796
+ }
3797
+ close();
3798
+ current = { key: line.trim(), lines: [] };
3799
+ }
3800
+ close();
3801
+ return sections;
3802
+ }
3803
+ function changedHeadings(before, after) {
3804
+ const old = headingSections(before);
3805
+ const now = headingSections(after);
3806
+ const keys = [...now.keys(), ...[...old.keys()].filter((key) => !now.has(key))];
3807
+ const changed = keys.filter((key) => JSON.stringify(old.get(key) ?? []) !== JSON.stringify(now.get(key) ?? []));
3808
+ return [...new Set(changed.map(headingName))];
3809
+ }
3810
+ function bodyChangeLine(before, after) {
3811
+ const old = before.split(`
3812
+ `);
3813
+ const now = after.split(`
3814
+ `);
3815
+ const common = commonLines(old, now);
3816
+ const headings = changedHeadings(old, now);
3817
+ const clause = headings.length === 0 ? "no heading changed" : `headings changed: ${headings.join(", ")}`;
3818
+ return `issue body: +${String(now.length - common)} -${String(old.length - common)} lines; ${clause}`;
3819
+ }
3820
+ function notesChangeLine(path3) {
3821
+ return `local notes: ${path3} changed since the saved copy`;
3822
+ }
3823
+ function readSnapshotChange(options) {
3824
+ const { saved, body, notes, notesPath: path3 } = options;
3825
+ if (saved === snapshotText(body, notes))
3826
+ return { kind: "unchanged", bodyLine: null, notesLine: null };
3827
+ const now = normalise(body);
3828
+ if (saved === `${now}
3829
+ ` || saved.startsWith(`${now}${NOTES_SEPARATOR}`)) {
3830
+ return { kind: "notes", bodyLine: null, notesLine: notesChangeLine(path3) };
3831
+ }
3832
+ const split = saved.lastIndexOf(NOTES_SEPARATOR);
3833
+ const oldBody = split === -1 ? saved : saved.slice(0, split);
3834
+ const oldNotes = split === -1 ? "" : normalise(saved.slice(split + NOTES_SEPARATOR.length)).trim();
3835
+ const newNotes = notes === null ? "" : normalise(notes).trim();
3836
+ const bodyLine = bodyChangeLine(normalise(oldBody), now);
3837
+ return oldNotes === newNotes ? { kind: "body", bodyLine, notesLine: null } : { kind: "both", bodyLine, notesLine: notesChangeLine(path3) };
3838
+ }
3694
3839
  function readIfFile(file, what) {
3695
3840
  if (!existsSync7(file))
3696
3841
  return null;
3697
- if (!statSync4(file).isFile()) {
3842
+ if (!statSync5(file).isFile()) {
3698
3843
  throw new CommandExit(ISSUE_REFUSAL_EXIT, `${what} ${file} is not a file, and a spec is one`);
3699
3844
  }
3700
3845
  try {
@@ -3704,7 +3849,7 @@ function readIfFile(file, what) {
3704
3849
  }
3705
3850
  }
3706
3851
  function readLocalNotes(repoRoot, specsDir, issue) {
3707
- return readIfFile(resolve5(repoRoot, notesPath(specsDir, issue)), "the local notes");
3852
+ return readIfFile(resolve6(repoRoot, notesPath(specsDir, issue)), "the local notes");
3708
3853
  }
3709
3854
  function snapshotDiffersMessage(path3, issue) {
3710
3855
  return `${path3} is a snapshot of issue #${String(issue)} that no longer matches it:` + " the issue body, or the local notes appended to it, changed since the snapshot was written;" + ` pass ${REFRESH_FLAG} to plan from the issue as it reads now, or --spec=${path3} to plan from` + " the snapshot as it stands";
@@ -3721,27 +3866,38 @@ function writeSpecSnapshot(options) {
3721
3866
  }
3722
3867
  const local = readLocalNotes(repoRoot, specsDir, issue.number);
3723
3868
  const text = snapshotText(issue.body, local);
3724
- const absolute = resolve5(repoRoot, path3);
3869
+ const absolute = resolve6(repoRoot, path3);
3725
3870
  const existing = readIfFile(absolute, "the spec snapshot");
3726
3871
  const written = {
3727
3872
  path: path3,
3728
3873
  absolute,
3729
3874
  action: "created",
3730
3875
  notes: local === null ? null : notes,
3876
+ previous: null,
3731
3877
  text
3732
3878
  };
3733
3879
  if (existing === text)
3734
3880
  return { ...written, action: "unchanged" };
3735
- if (existing !== null && !refresh) {
3881
+ const action = rewriteAction(existing, issue.body, local, notes, refresh);
3882
+ if (action === null) {
3736
3883
  throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path3, issue.number));
3737
3884
  }
3885
+ const previous = action === "created" ? null : movePreviousCopy({ repoRoot, specsDir, path: path3 }).path;
3738
3886
  try {
3739
- mkdirSync2(dirname2(absolute), { recursive: true });
3887
+ mkdirSync3(dirname2(absolute), { recursive: true });
3740
3888
  writeFileSync2(absolute, text);
3741
3889
  } catch (error) {
3742
- throw new Error(`${PREFIX2}: the snapshot ${path3} could not be written: ${messageOf(error)}`, { cause: error });
3890
+ throw new Error(`${PREFIX3}: the snapshot ${path3} could not be written: ${messageOf(error)}`, { cause: error });
3743
3891
  }
3744
- return existing === null ? written : { ...written, action: "refreshed" };
3892
+ return { ...written, action, previous };
3893
+ }
3894
+ function rewriteAction(existing, body, notes, notesFile, refresh) {
3895
+ if (existing === null)
3896
+ return "created";
3897
+ const { kind } = readSnapshotChange({ saved: existing, body, notes, notesPath: notesFile });
3898
+ if (kind === "notes")
3899
+ return "notes-rebuilt";
3900
+ return refresh ? "refreshed" : null;
3745
3901
  }
3746
3902
 
3747
3903
  // src/board/leak.ts
@@ -3843,7 +3999,7 @@ function requireNoLeak(source, body) {
3843
3999
  }
3844
4000
 
3845
4001
  // src/board/roadmap.ts
3846
- var PREFIX3 = "board roadmap";
4002
+ var PREFIX4 = "board roadmap";
3847
4003
  var ROADMAP_TITLE = "Roadmap";
3848
4004
  var ROADMAP_SETTING = "roadmap.issue";
3849
4005
  var ROADMAP_REFUSAL_EXIT = 2;
@@ -3852,7 +4008,7 @@ var OPEN_PULL_REQUEST_LIMIT = 100;
3852
4008
  var PR_LIST_FIELDS = "number,headRefName,body";
3853
4009
  var ISSUE_LIST_FIELDS = "number,title";
3854
4010
  var TASK_LINE = /^\s*[-*+]\s+\[([ xX])\]\s+#(\d+)\s*(.*)$/u;
3855
- var FENCE = /^\s*(?:`{3,}|~{3,})/u;
4011
+ var FENCE2 = /^\s*(?:`{3,}|~{3,})/u;
3856
4012
  var WHY_SEPARATOR = /^[-:,.\u2013\u2014]\s*/u;
3857
4013
  var CLOSING = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b\s*:?\s*#(\d+)\b/giu;
3858
4014
  function whyOf(rest) {
@@ -3862,7 +4018,7 @@ function parseRoadmapBody(body) {
3862
4018
  let lines = [];
3863
4019
  let fenced = false;
3864
4020
  body.split(/\r\n?|\n/u).forEach((text, index) => {
3865
- if (FENCE.test(text)) {
4021
+ if (FENCE2.test(text)) {
3866
4022
  fenced = !fenced;
3867
4023
  return;
3868
4024
  }
@@ -3893,24 +4049,24 @@ function parseRows(stdout, command) {
3893
4049
  try {
3894
4050
  payload = JSON.parse(stdout);
3895
4051
  } catch (error) {
3896
- throw new Error(`${PREFIX3}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
4052
+ throw new Error(`${PREFIX4}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
3897
4053
  }
3898
4054
  if (!Array.isArray(payload)) {
3899
- throw new Error(`${PREFIX3}: ${command} answered ${describeValue(payload)}, expected a list`);
4055
+ throw new Error(`${PREFIX4}: ${command} answered ${describeValue(payload)}, expected a list`);
3900
4056
  }
3901
4057
  return payload;
3902
4058
  }
3903
4059
  function rowNumber(row, key, command, where) {
3904
4060
  const value = isMapping(row) ? row[key] : null;
3905
4061
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
3906
- throw new Error(`${PREFIX3}: ${command} answered ${where}.${key} as ${describeValue(value)}, expected a positive whole number`);
4062
+ throw new Error(`${PREFIX4}: ${command} answered ${where}.${key} as ${describeValue(value)}, expected a positive whole number`);
3907
4063
  }
3908
4064
  return value;
3909
4065
  }
3910
4066
  function rowText(row, key, command, where) {
3911
4067
  const value = isMapping(row) ? row[key] : null;
3912
4068
  if (typeof value !== "string") {
3913
- throw new Error(`${PREFIX3}: ${command} answered ${where}.${key} as ${describeValue(value)}, expected a string`);
4069
+ throw new Error(`${PREFIX4}: ${command} answered ${where}.${key} as ${describeValue(value)}, expected a string`);
3914
4070
  }
3915
4071
  return value;
3916
4072
  }
@@ -3922,7 +4078,7 @@ function createGhRoadmapSearch(options) {
3922
4078
  return async () => {
3923
4079
  const result = await gh(args);
3924
4080
  if (!result.ok)
3925
- throw new Error(`${PREFIX3}: ${detailOf4(result, command)}`);
4081
+ throw new Error(`${PREFIX4}: ${detailOf4(result, command)}`);
3926
4082
  return Object.freeze(parseRows(result.stdout, command).map((row, index) => ({
3927
4083
  number: rowNumber(row, "number", command, `issue ${String(index)}`),
3928
4084
  title: rowText(row, "title", command, `issue ${String(index)}`)
@@ -3957,7 +4113,7 @@ function createGhOpenPullRequests(options) {
3957
4113
  return async () => {
3958
4114
  const result = await gh(args);
3959
4115
  if (!result.ok)
3960
- throw new Error(`${PREFIX3}: ${detailOf4(result, command)}`);
4116
+ throw new Error(`${PREFIX4}: ${detailOf4(result, command)}`);
3961
4117
  return Object.freeze(parseRows(result.stdout, command).map((row, index) => ({
3962
4118
  number: rowNumber(row, "number", command, `pull request ${String(index)}`),
3963
4119
  headRefName: rowText(row, "headRefName", command, `pull request ${String(index)}`),
@@ -4216,8 +4372,74 @@ function declinedMessage(alternative) {
4216
4372
  return `#${String(alternative)} was not planned, and nothing was written`;
4217
4373
  }
4218
4374
 
4375
+ // src/board/snapshot-settle.ts
4376
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
4377
+ import { resolve as resolve7 } from "path";
4378
+ var PREFIX5 = "board snapshot settle";
4379
+ var DATE_LENGTH = 10;
4380
+ function savedCopyDate(savedAt) {
4381
+ return savedAt.toISOString().slice(0, DATE_LENGTH);
4382
+ }
4383
+ function refreshQuestion(issue, savedAt) {
4384
+ return `Issue #${String(issue)} changed since the saved copy of ${savedCopyDate(savedAt)}.` + " Plan from it as it reads now? [y/N] ";
4385
+ }
4386
+ function notesRebuiltLine(path3, issue, previous) {
4387
+ return `local notes changed; rebuilt ${path3} from issue #${String(issue)} and the notes,` + ` the old copy is at ${previous}`;
4388
+ }
4389
+ function readSavedCopy(absolute, path3) {
4390
+ if (!existsSync8(absolute))
4391
+ return null;
4392
+ try {
4393
+ const stat = statSync6(absolute);
4394
+ if (!stat.isFile())
4395
+ return null;
4396
+ return { text: readFileSync7(absolute, "utf8"), savedAt: stat.mtime };
4397
+ } catch (error) {
4398
+ throw new Error(`${PREFIX5}: the saved copy ${path3} could not be read: ${messageOf(error)}`, { cause: error });
4399
+ }
4400
+ }
4401
+ function printChange(change, output) {
4402
+ for (const line of [change.bodyLine, change.notesLine]) {
4403
+ if (line !== null)
4404
+ output.info(line);
4405
+ }
4406
+ }
4407
+ async function settleSpecSnapshot(options) {
4408
+ const { repoRoot, specsDir, issue, refresh, output } = options;
4409
+ const write = (rewrite) => writeSpecSnapshot({ repoRoot, specsDir, issue, refresh: rewrite });
4410
+ const path3 = specPath(specsDir, issue.number, issue.title);
4411
+ const notes = notesPath(specsDir, issue.number);
4412
+ if (path3 === notes)
4413
+ return write(refresh);
4414
+ const saved = readSavedCopy(resolve7(repoRoot, path3), path3);
4415
+ if (saved === null)
4416
+ return write(refresh);
4417
+ const change = readSnapshotChange({
4418
+ saved: saved.text,
4419
+ body: issue.body,
4420
+ notes: readLocalNotes(repoRoot, specsDir, issue.number),
4421
+ notesPath: notes
4422
+ });
4423
+ if (change.kind === "unchanged")
4424
+ return write(refresh);
4425
+ printChange(change, output);
4426
+ if (change.kind === "notes") {
4427
+ const rebuilt = write(refresh);
4428
+ if (rebuilt.previous !== null)
4429
+ output.info(notesRebuiltLine(rebuilt.path, issue.number, rebuilt.previous));
4430
+ return rebuilt;
4431
+ }
4432
+ if (refresh)
4433
+ return write(true);
4434
+ const offer = options.offerRefresh ?? null;
4435
+ const accepted = offer !== null && await offer({ issue: issue.number, path: path3, savedAt: saved.savedAt });
4436
+ if (!accepted)
4437
+ throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path3, issue.number));
4438
+ return write(true);
4439
+ }
4440
+
4219
4441
  // src/board/spec-source.ts
4220
- var PREFIX4 = "board spec source";
4442
+ var PREFIX6 = "board spec source";
4221
4443
  var SOURCE_FLAGS = [SPEC_FLAG, ISSUE_FLAG, NEXT_FLAG];
4222
4444
  var SOURCE_REFUSAL_EXIT = 1;
4223
4445
  function severalSourcesMessage(given) {
@@ -4336,7 +4558,7 @@ async function settleBlockedPick(options) {
4336
4558
  async function pickRoadmapIssue(request, options, issues, output) {
4337
4559
  const seams = options.roadmap;
4338
4560
  if (seams === undefined) {
4339
- throw new TypeError(`${PREFIX4}: ${NEXT_FLAG} was resolved with no roadmap seams`);
4561
+ throw new TypeError(`${PREFIX6}: ${NEXT_FLAG} was resolved with no roadmap seams`);
4340
4562
  }
4341
4563
  const roadmap = await resolveRoadmapIssue({
4342
4564
  configured: request.roadmap ?? seams.configured,
@@ -4409,7 +4631,14 @@ async function resolveSpecSource(options) {
4409
4631
  output.info(dryRunLine(describeIssue(read)));
4410
4632
  return stopped("dry-run");
4411
4633
  }
4412
- const snapshot = writeSpecSnapshot({ repoRoot, specsDir, issue: read, refresh });
4634
+ const snapshot = await settleSpecSnapshot({
4635
+ repoRoot,
4636
+ specsDir,
4637
+ issue: read,
4638
+ refresh,
4639
+ offerRefresh: options.offerRefresh ?? null,
4640
+ output
4641
+ });
4413
4642
  return Object.freeze({
4414
4643
  outcome: "spec",
4415
4644
  spec: Object.freeze({
@@ -4482,6 +4711,7 @@ async function resolvePlanSpec(options) {
4482
4711
  const offer = offerReady === null || options.dryRun ? undefined : (issue) => offerReady({ issue, gh, trust: trust(), output });
4483
4712
  const offerAlternative = options.offerAlternative ?? null;
4484
4713
  const alternative = offerAlternative === null || options.dryRun ? undefined : offerAlternative;
4714
+ const offerRefresh = options.dryRun ? null : options.offerRefresh ?? null;
4485
4715
  const resolution = await resolveSpecSource({
4486
4716
  request: options.request,
4487
4717
  refresh: options.refresh,
@@ -4491,6 +4721,7 @@ async function resolvePlanSpec(options) {
4491
4721
  findSpec: options.findSpec,
4492
4722
  issues: createGhSpecIssueReader({ gh }),
4493
4723
  inspect: (issue) => inspectSpecIssue(issue, trust(), offer),
4724
+ offerRefresh,
4494
4725
  roadmap: {
4495
4726
  configured: options.roadmapIssue,
4496
4727
  search: createGhRoadmapSearch({ gh }),
@@ -4516,15 +4747,15 @@ async function resolvePlanSpec(options) {
4516
4747
  import { createInterface as createInterface2 } from "readline";
4517
4748
 
4518
4749
  // src/project/roots.ts
4519
- import { accessSync, constants, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
4750
+ import { accessSync, constants, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
4520
4751
 
4521
4752
  // src/project/scope.ts
4522
- import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
4523
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join5 } from "path";
4753
+ import { existsSync as existsSync9, realpathSync as realpathSync2 } from "fs";
4754
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6 } from "path";
4524
4755
  var SCOPE_DIR = dirname3(CONFIG_FILE);
4525
4756
  var INIT_COMMAND = "rafa init";
4526
4757
  var DISK_FILE_SYSTEM = Object.freeze({
4527
- exists: (path3) => existsSync8(path3),
4758
+ exists: (path3) => existsSync9(path3),
4528
4759
  realpath: (path3) => realpathSync2(path3)
4529
4760
  });
4530
4761
 
@@ -4535,7 +4766,7 @@ class ScopeError extends Error {
4535
4766
  }
4536
4767
  }
4537
4768
  function scopeAt(base) {
4538
- return { dir: join5(base, SCOPE_DIR), configFile: configFilePath(base) };
4769
+ return { dir: join6(base, SCOPE_DIR), configFile: configFilePath(base) };
4539
4770
  }
4540
4771
  function initHint(start, passedUserConfig = null) {
4541
4772
  const passed2 = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
@@ -4612,8 +4843,8 @@ var MONOREPO_MARKER_FILES = Object.freeze([
4612
4843
  ]);
4613
4844
  var DISK_ROOTS_FILE_SYSTEM = Object.freeze({
4614
4845
  ...DISK_FILE_SYSTEM,
4615
- readFile: (path3) => readFileSync7(path3, "utf8"),
4616
- isDirectory: (path3) => statSync5(path3).isDirectory(),
4846
+ readFile: (path3) => readFileSync8(path3, "utf8"),
4847
+ isDirectory: (path3) => statSync7(path3).isDirectory(),
4617
4848
  assertWritable: (path3) => accessSync(path3, constants.W_OK)
4618
4849
  });
4619
4850
 
@@ -4731,8 +4962,8 @@ function formatRows(rows) {
4731
4962
  `);
4732
4963
  }
4733
4964
  function defaultSleep(ms) {
4734
- return new Promise((resolve6) => {
4735
- setTimeout(resolve6, ms);
4965
+ return new Promise((resolve8) => {
4966
+ setTimeout(resolve8, ms);
4736
4967
  });
4737
4968
  }
4738
4969
  async function waitForChecks(options) {
@@ -4877,7 +5108,7 @@ function compareUrl(remote, branch) {
4877
5108
  return `https://${segments[0] ?? host}/${path3}/compare/${encodeBranch(branch)}${COMPARE_QUERY}`;
4878
5109
  }
4879
5110
  // src/pr/gh.ts
4880
- var PREFIX5 = "gh pull requests";
5111
+ var PREFIX7 = "gh pull requests";
4881
5112
  var REPO_PATH3 = "{owner}/{repo}";
4882
5113
  var SUMMARY_FIELDS = "author,baseRefName,headRefName,isCrossRepository,number,state,title,updatedAt,url";
4883
5114
  var DETAIL_FIELDS = `${SUMMARY_FIELDS},body,headRefOid,labels,mergeStateStatus,mergeable`;
@@ -4906,16 +5137,16 @@ function detailOf5(result, command) {
4906
5137
  return result.ok ? `${command} exited 0 and wrote nothing` : `${command} exited non-zero and wrote nothing`;
4907
5138
  }
4908
5139
  function failure2(command, result) {
4909
- return new Error(`${PREFIX5}: ${command} failed: ${detailOf5(result, command)}`);
5140
+ return new Error(`${PREFIX7}: ${command} failed: ${detailOf5(result, command)}`);
4910
5141
  }
4911
5142
  function refuse3(command, problem) {
4912
- throw new Error(`${PREFIX5}: ${command} answered ${problem}`);
5143
+ throw new Error(`${PREFIX7}: ${command} answered ${problem}`);
4913
5144
  }
4914
5145
  function parseJson2(stdout, command) {
4915
5146
  try {
4916
5147
  return JSON.parse(stdout);
4917
5148
  } catch (error) {
4918
- throw new Error(`${PREFIX5}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
5149
+ throw new Error(`${PREFIX7}: ${command} wrote output that is not JSON: ${messageOf(error)}`, { cause: error });
4919
5150
  }
4920
5151
  }
4921
5152
  function readString2(value, command, where) {
@@ -5018,7 +5249,7 @@ function readWorkflowCount(stdout) {
5018
5249
  }
5019
5250
  function pullNumber(value, member) {
5020
5251
  if (!Number.isSafeInteger(value) || value < 1) {
5021
- throw new TypeError(`${PREFIX5}: ${member} refused pull request number ${describeValue(value)},` + " expected a positive whole number");
5252
+ throw new TypeError(`${PREFIX7}: ${member} refused pull request number ${describeValue(value)},` + " expected a positive whole number");
5022
5253
  }
5023
5254
  return String(value);
5024
5255
  }
@@ -5026,13 +5257,13 @@ function textArgument(value, member, what, whole) {
5026
5257
  const bad = typeof value !== "string" || value === "" || whole && !(WHOLE_NUMBER.test(value) && Number.isSafeInteger(Number(value)));
5027
5258
  if (bad) {
5028
5259
  const expected2 = whole ? "a positive whole number with no leading zero" : "a non-empty string";
5029
- throw new TypeError(`${PREFIX5}: ${member} refused ${what} ${describeValue(value)}, expected ${expected2}`);
5260
+ throw new TypeError(`${PREFIX7}: ${member} refused ${what} ${describeValue(value)}, expected ${expected2}`);
5030
5261
  }
5031
5262
  return value;
5032
5263
  }
5033
5264
  function bodyText(value, member) {
5034
5265
  if (typeof value !== "string") {
5035
- throw new TypeError(`${PREFIX5}: ${member} refused a body ${describeValue(value)}, expected a string`);
5266
+ throw new TypeError(`${PREFIX7}: ${member} refused a body ${describeValue(value)}, expected a string`);
5036
5267
  }
5037
5268
  return value;
5038
5269
  }
@@ -5102,7 +5333,7 @@ function createGhPullRequests(options) {
5102
5333
  merge: async (number, method) => {
5103
5334
  const target = pullNumber(number, "merge");
5104
5335
  if (!isMergeMethod(method)) {
5105
- throw new TypeError(`${PREFIX5}: merge refused method ${describeValue(method)},` + ` expected one of: ${MERGE_METHODS.join(", ")}`);
5336
+ throw new TypeError(`${PREFIX7}: merge refused method ${describeValue(method)},` + ` expected one of: ${MERGE_METHODS.join(", ")}`);
5106
5337
  }
5107
5338
  const command = `gh pr merge ${target} --${method}`;
5108
5339
  const result = await gh(["pr", "merge", target, `--${method}`]);
@@ -5418,19 +5649,19 @@ function parseArgs(argv, spec = {}) {
5418
5649
 
5419
5650
  // src/commands/loop/loop-sessions.ts
5420
5651
  import { execFileSync as execFileSync2 } from "child_process";
5421
- import { join as join7, relative } from "path";
5652
+ import { join as join8, relative } from "path";
5422
5653
 
5423
5654
  // src/loop/sessions.ts
5424
5655
  import {
5425
- linkSync,
5426
- mkdirSync as mkdirSync3,
5427
- readdirSync as readdirSync3,
5428
- readFileSync as readFileSync8,
5656
+ linkSync as linkSync2,
5657
+ mkdirSync as mkdirSync4,
5658
+ readdirSync as readdirSync4,
5659
+ readFileSync as readFileSync9,
5429
5660
  renameSync as renameSync2,
5430
5661
  rmSync,
5431
5662
  writeFileSync as writeFileSync3
5432
5663
  } from "fs";
5433
- import { basename as basename3, dirname as dirname4, join as join6 } from "path";
5664
+ import { basename as basename4, dirname as dirname4, join as join7 } from "path";
5434
5665
  var SESSION_STATES = Object.freeze(["running", "paused", "stopped", "done"]);
5435
5666
  var RUNS_DIR = "runs";
5436
5667
  var RECORD_EXTENSION = ".json";
@@ -5470,13 +5701,13 @@ function isSessionId(value) {
5470
5701
  return SESSION_ID_PATTERN.test(value);
5471
5702
  }
5472
5703
  function runsDir(root) {
5473
- return join6(scopeAt(root).dir, RUNS_DIR);
5704
+ return join7(scopeAt(root).dir, RUNS_DIR);
5474
5705
  }
5475
5706
  function sessionFilePath(root, sessionId) {
5476
5707
  if (!isSessionId(sessionId)) {
5477
5708
  throw new Error(`session record: unusable session id ${JSON.stringify(sessionId)}`);
5478
5709
  }
5479
- return join6(runsDir(root), `${sessionId}${RECORD_EXTENSION}`);
5710
+ return join7(runsDir(root), `${sessionId}${RECORD_EXTENSION}`);
5480
5711
  }
5481
5712
  function describeValue3(value) {
5482
5713
  return value === undefined ? "missing" : JSON.stringify(value) ?? String(value);
@@ -5507,7 +5738,7 @@ function sessionIdProblem(sessionId, file) {
5507
5738
  if (typeof sessionId !== "string" || !isSessionId(sessionId)) {
5508
5739
  return `sessionId is ${describeValue3(sessionId)}, expected a plain file name`;
5509
5740
  }
5510
- return basename3(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
5741
+ return basename4(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
5511
5742
  }
5512
5743
  function planStubProblem(planStub2) {
5513
5744
  return planStub2 === null || typeof planStub2 === "string" && isStampableStub(planStub2) ? null : `planStub is ${describeValue3(planStub2)}, expected null or a plan stub`;
@@ -5578,7 +5809,7 @@ function readState2(record, isAlive = isPidAlive) {
5578
5809
  function readRecordFile(file) {
5579
5810
  let text;
5580
5811
  try {
5581
- text = readFileSync8(file, "utf8");
5812
+ text = readFileSync9(file, "utf8");
5582
5813
  } catch (error) {
5583
5814
  throw new SessionRecordError(file, `cannot be read: ${messageOf(error)}`);
5584
5815
  }
@@ -5586,7 +5817,7 @@ function readRecordFile(file) {
5586
5817
  }
5587
5818
  function recordNames(dir) {
5588
5819
  try {
5589
- return readdirSync3(dir);
5820
+ return readdirSync4(dir);
5590
5821
  } catch (error) {
5591
5822
  if (errorCode(error) === "ENOENT")
5592
5823
  return [];
@@ -5599,7 +5830,7 @@ function byStart(a, b) {
5599
5830
  function readSessions(root, seams = {}) {
5600
5831
  const dir = runsDir(root);
5601
5832
  const isAlive = seams.isAlive ?? isPidAlive;
5602
- const records = recordNames(dir).filter((name) => name.endsWith(RECORD_EXTENSION)).map((name) => readRecordFile(join6(dir, name))).map((record) => Object.freeze({ ...record, state: readState2(record, isAlive) }));
5833
+ const records = recordNames(dir).filter((name) => name.endsWith(RECORD_EXTENSION)).map((name) => readRecordFile(join7(dir, name))).map((record) => Object.freeze({ ...record, state: readState2(record, isAlive) }));
5603
5834
  return Object.freeze(records.sort(byStart));
5604
5835
  }
5605
5836
  function readSession(root, sessionId, seams = {}) {
@@ -5625,13 +5856,13 @@ function checkRecord(record, file) {
5625
5856
  }
5626
5857
  function writeRecordFile(file, record, isNew) {
5627
5858
  checkRecord(record, file);
5628
- mkdirSync3(dirname4(file), { recursive: true });
5859
+ mkdirSync4(dirname4(file), { recursive: true });
5629
5860
  const temporary = `${file}.${process.pid}.tmp`;
5630
5861
  writeFileSync3(temporary, `${JSON.stringify(record, null, 2)}
5631
5862
  `);
5632
5863
  try {
5633
5864
  if (isNew)
5634
- linkSync(temporary, file);
5865
+ linkSync2(temporary, file);
5635
5866
  else
5636
5867
  renameSync2(temporary, file);
5637
5868
  } finally {
@@ -5708,7 +5939,7 @@ function readRecords(root, seams) {
5708
5939
  }
5709
5940
 
5710
5941
  // src/next/sources.ts
5711
- var PREFIX6 = "rafa next";
5942
+ var PREFIX8 = "rafa next";
5712
5943
  var DEFAULT_BASE_BRANCH = "main";
5713
5944
  var DEFAULT_NEXT_SOURCE_SEAMS = Object.freeze({});
5714
5945
  function memoiseIssues2(issues) {
@@ -5746,7 +5977,7 @@ function ghNextBoard(options) {
5746
5977
  }
5747
5978
  function nextProject(context) {
5748
5979
  if (context.project === null)
5749
- throw new Error(`${PREFIX6} runs inside a project, and was handed none`);
5980
+ throw new Error(`${PREFIX8} runs inside a project, and was handed none`);
5750
5981
  return context.project;
5751
5982
  }
5752
5983
  function nextConfig(project, warn) {
@@ -5756,7 +5987,7 @@ function nextConfig(project, warn) {
5756
5987
  if (!(error instanceof ConfigError))
5757
5988
  throw error;
5758
5989
  throw new CommandExit(1, [
5759
- `\u274C ${PREFIX6}: the config cannot be used:`,
5990
+ `\u274C ${PREFIX8}: the config cannot be used:`,
5760
5991
  ...error.problems.map((problem) => ` ${problem}`)
5761
5992
  ].join(`
5762
5993
  `));
@@ -5775,33 +6006,38 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
5775
6006
  const git = (seams.openGit ?? createGitRunner)(project.root);
5776
6007
  const gh = (seams.openGh ?? ((root) => createGhRunner({ cwd: root })))(project.root);
5777
6008
  const loop = resolveLoopSeams({ isAlive: seams.isAlive });
5778
- return Object.freeze({
6009
+ const board = () => ghNextBoard({ gh, git, configured: config.roadmapIssue });
6010
+ const held = {
5779
6011
  base: config.prBase ?? DEFAULT_BASE_BRANCH,
5780
6012
  plans: plansDirAt(project.root, config.planDir),
5781
6013
  runs: () => readRecords(project.root, loop),
5782
6014
  git,
5783
- pulls: (seams.pullRequests ?? ghPullRequestsIn)(project.root),
5784
- board: ghNextBoard({ gh, git, configured: config.roadmapIssue })
6015
+ pulls: (seams.pullRequests ?? ghPullRequestsIn)(project.root)
6016
+ };
6017
+ return Object.freeze({
6018
+ ...held,
6019
+ board: board(),
6020
+ answer: () => Object.freeze({ ...held, board: board() })
5785
6021
  });
5786
6022
  }
5787
6023
 
5788
6024
  // src/commands/plan/list.ts
5789
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
5790
- import { join as join8 } from "path";
6025
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
6026
+ import { join as join9 } from "path";
5791
6027
  var USAGE2 = "rafa plan list";
5792
6028
  function listing(dir, stub) {
5793
- const plan = join8(dir, planFileName(stub, false));
5794
- const trackerPath = join8(dir, planFileName(stub, true));
6029
+ const plan = join9(dir, planFileName(stub, false));
6030
+ const trackerPath = join9(dir, planFileName(stub, true));
5795
6031
  const tracker = isFile(trackerPath) ? trackerPath : null;
5796
- const planModel = parsePlan(readFileSync9(plan, "utf8"));
5797
- const counted2 = tracker === null ? planModel : parsePlan(readFileSync9(tracker, "utf8"));
6032
+ const planModel = parsePlan(readFileSync10(plan, "utf8"));
6033
+ const counted2 = tracker === null ? planModel : parsePlan(readFileSync10(tracker, "utf8"));
5798
6034
  return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
5799
6035
  }
5800
6036
  function listPlans(plans) {
5801
6037
  const dir = plans.path;
5802
- if (!existsSync9(dir))
6038
+ if (!existsSync10(dir))
5803
6039
  return { dir, plans: [] };
5804
- const stubs = readdirSync4(dir).filter((name) => isFile(join8(dir, name))).map((name) => stubOfPlanFile(name)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
6040
+ const stubs = readdirSync5(dir).filter((name) => isFile(join9(dir, name))).map((name) => stubOfPlanFile(name)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
5805
6041
  return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
5806
6042
  }
5807
6043
  function rowNotes(plan) {
@@ -5996,7 +6232,7 @@ function branchLabel(world) {
5996
6232
  }
5997
6233
 
5998
6234
  // src/next/state.ts
5999
- var PREFIX7 = "rafa next";
6235
+ var PREFIX9 = "rafa next";
6000
6236
  var MAX_NAMED_FILES = 5;
6001
6237
  var NEXT_PRECONDITIONS = Object.freeze([
6002
6238
  "tree-modified",
@@ -6274,7 +6510,7 @@ async function readNextState(sources) {
6274
6510
  if (found !== null)
6275
6511
  return answer(found, world.problems());
6276
6512
  }
6277
- throw new Error(`${PREFIX7}: no row of the table answered, and the last row answers for every reading`);
6513
+ throw new Error(`${PREFIX9}: no row of the table answered, and the last row answers for every reading`);
6278
6514
  }
6279
6515
 
6280
6516
  // src/next/hint.ts
@@ -6300,8 +6536,8 @@ function wantsHint(flags) {
6300
6536
  }
6301
6537
  var DEFAULT_HINT_SEAMS = Object.freeze({});
6302
6538
  function afterDelay(ms) {
6303
- return new Promise((resolve6) => {
6304
- setTimeout(resolve6, ms).unref();
6539
+ return new Promise((resolve8) => {
6540
+ setTimeout(resolve8, ms).unref();
6305
6541
  });
6306
6542
  }
6307
6543
  function silenced() {
@@ -6355,10 +6591,10 @@ async function nextStepHint(context, seams = DEFAULT_HINT_SEAMS) {
6355
6591
  }
6356
6592
 
6357
6593
  // src/next/actions.ts
6358
- var PREFIX8 = "rafa next";
6594
+ var PREFIX10 = "rafa next";
6359
6595
  function needed(state, value, what) {
6360
6596
  if (value === null) {
6361
- throw new Error(`${PREFIX8}: state "${state.id}" proposes "${state.action}" and names no ${what}`);
6597
+ throw new Error(`${PREFIX10}: state "${state.id}" proposes "${state.action}" and names no ${what}`);
6362
6598
  }
6363
6599
  return String(value);
6364
6600
  }
@@ -6408,12 +6644,12 @@ function actionContext(caller, command, argv) {
6408
6644
  async function runAction(caller, state) {
6409
6645
  const invocation = actionInvocation(state);
6410
6646
  if (invocation === null) {
6411
- throw new Error(`${PREFIX8}: the action "${state.action}" of state "${state.id}" runs no registered command`);
6647
+ throw new Error(`${PREFIX10}: the action "${state.action}" of state "${state.id}" runs no registered command`);
6412
6648
  }
6413
6649
  const spec = ACTION_COMMANDS[invocation.action];
6414
6650
  const command = caller.registry.find(spec.subject, spec.action);
6415
6651
  if (command === undefined) {
6416
- throw new CommandExit(1, `\u274C ${PREFIX8}: the "${invocation.action}" step runs "rafa ${invocation.command}", which is registered by nothing`);
6652
+ throw new CommandExit(1, `\u274C ${PREFIX10}: the "${invocation.action}" step runs "rafa ${invocation.command}", which is registered by nothing`);
6417
6653
  }
6418
6654
  await command.run(actionContext(caller, command, invocation.argv));
6419
6655
  }
@@ -6452,7 +6688,7 @@ async function endWithNextStep(context, seams = DEFAULT_ENDING_SEAMS) {
6452
6688
  }
6453
6689
 
6454
6690
  // src/adapters/tracker/resolve.ts
6455
- var PREFIX9 = "tracker chain";
6691
+ var PREFIX11 = "tracker chain";
6456
6692
  var FAILURE_SEPARATOR = "; ";
6457
6693
  function warnThroughActiveOutput(message) {
6458
6694
  activeOutput().warn(message);
@@ -6493,9 +6729,9 @@ async function resolveTracker(options) {
6493
6729
  }
6494
6730
  attempts = [...attempts, Object.freeze({ kind, ok: false, reason: outcome.reason })];
6495
6731
  failures = [...failures, `${kind}: ${outcome.reason}`];
6496
- log(`${PREFIX9}: ${kind} unavailable: ${outcome.reason}`);
6732
+ log(`${PREFIX11}: ${kind} unavailable: ${outcome.reason}`);
6497
6733
  }
6498
- throw new Error(`${PREFIX9}: no tracker available; tried ${failures.join(FAILURE_SEPARATOR)}`);
6734
+ throw new Error(`${PREFIX11}: no tracker available; tried ${failures.join(FAILURE_SEPARATOR)}`);
6499
6735
  }
6500
6736
 
6501
6737
  // src/commands/issue/issue-tracker.ts
@@ -6738,6 +6974,23 @@ function createPlanReadyOffer(seams = DEFAULT_OFFER_SEAMS) {
6738
6974
  };
6739
6975
  }
6740
6976
 
6977
+ // src/commands/plan/refresh-offer.ts
6978
+ var DEFAULT_REFRESH_OFFER_SEAMS = Object.freeze({});
6979
+ function createPlanRefreshOffer(seams = DEFAULT_REFRESH_OFFER_SEAMS) {
6980
+ const isTerminal = seams.isTerminal ?? (() => process.stdin.isTTY === true);
6981
+ if (!isTerminal())
6982
+ return null;
6983
+ const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
6984
+ return async (request) => {
6985
+ const prompter = lazyPrompter(openPrompter);
6986
+ try {
6987
+ return await prompter.ask(refreshQuestion(request.issue, request.savedAt));
6988
+ } finally {
6989
+ prompter.close();
6990
+ }
6991
+ };
6992
+ }
6993
+
6741
6994
  // src/commands/plan/spec-route.ts
6742
6995
  function specCandidates(specArg, specsDir) {
6743
6996
  if (path3.isAbsolute(specArg))
@@ -6767,10 +7020,11 @@ async function resolveCreateSpec(options, seams = DEFAULT_ROUTE_SEAMS) {
6767
7020
  const source = readSpecSourceFlags(options.args);
6768
7021
  if (source.request === null)
6769
7022
  throw new CommandExit(SOURCE_REFUSAL_EXIT, usageRefusal(specsDir));
6770
- const resolve6 = seams.resolve ?? resolvePlanSpec;
7023
+ const resolve8 = seams.resolve ?? resolvePlanSpec;
6771
7024
  const makeReadyOffer = seams.makeReadyOffer ?? createPlanReadyOffer;
6772
7025
  const makeAlternativeOffer = seams.makeAlternativeOffer ?? createPlanBlockedOffer;
6773
- return await resolve6({
7026
+ const makeRefreshOffer = seams.makeRefreshOffer ?? createPlanRefreshOffer;
7027
+ return await resolve8({
6774
7028
  request: source.request,
6775
7029
  refresh: source.refresh,
6776
7030
  dryRun: source.dryRun,
@@ -6780,7 +7034,8 @@ async function resolveCreateSpec(options, seams = DEFAULT_ROUTE_SEAMS) {
6780
7034
  trustedAuthors: options.trustedAuthors,
6781
7035
  findSpec: (spec) => findSpec(repoRoot, spec, specsDir),
6782
7036
  offerReady: makeReadyOffer(),
6783
- offerAlternative: makeAlternativeOffer()
7037
+ offerAlternative: makeAlternativeOffer(),
7038
+ offerRefresh: makeRefreshOffer()
6784
7039
  });
6785
7040
  }
6786
7041
 
@@ -6791,13 +7046,13 @@ import { homedir as homedir3 } from "os";
6791
7046
  var RAFA_VERSION = version;
6792
7047
 
6793
7048
  // src/notices/notices.ts
6794
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
6795
- import { dirname as dirname5, join as join9 } from "path";
7049
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
7050
+ import { dirname as dirname5, join as join10 } from "path";
6796
7051
  var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
6797
7052
  var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
6798
7053
  var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
6799
7054
  function noticesPath(home) {
6800
- return join9(home, ".rafa", "notices.json");
7055
+ return join10(home, ".rafa", "notices.json");
6801
7056
  }
6802
7057
  function isNoticeId(value) {
6803
7058
  return typeof value === "string" && NOTICE_IDS.includes(value);
@@ -6805,7 +7060,7 @@ function isNoticeId(value) {
6805
7060
  function readDismissed(home) {
6806
7061
  let raw;
6807
7062
  try {
6808
- raw = readFileSync10(noticesPath(home), "utf8");
7063
+ raw = readFileSync11(noticesPath(home), "utf8");
6809
7064
  } catch {
6810
7065
  return [];
6811
7066
  }
@@ -6824,7 +7079,7 @@ function readDismissed(home) {
6824
7079
  function writeDismissed(home, ids) {
6825
7080
  const all = NOTICE_IDS.filter((id) => ids.includes(id) || readDismissed(home).includes(id));
6826
7081
  const path4 = noticesPath(home);
6827
- mkdirSync4(dirname5(path4), { recursive: true });
7082
+ mkdirSync5(dirname5(path4), { recursive: true });
6828
7083
  writeFileSync4(path4, `${JSON.stringify({ dismissed: all }, null, 2)}
6829
7084
  `);
6830
7085
  }
@@ -7514,7 +7769,7 @@ import { randomUUID as randomUUID7 } from "crypto";
7514
7769
 
7515
7770
  // src/effort/store/absences.ts
7516
7771
  import { randomUUID as randomUUID3 } from "crypto";
7517
- import { existsSync as existsSync10 } from "fs";
7772
+ import { existsSync as existsSync11 } from "fs";
7518
7773
  var ABSENCE_REASONS = {
7519
7774
  "no-block": true,
7520
7775
  "unclosed-block": true,
@@ -7573,13 +7828,13 @@ function writeReportAbsence(repoRoot, write, seams = {}) {
7573
7828
  write.outcome,
7574
7829
  collectedAt
7575
7830
  ];
7576
- const appended = withSqliteStore(path5, !existsSync10(path5), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
7831
+ const appended = withSqliteStore(path5, !existsSync11(path5), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
7577
7832
  return { path: path5, appended, skipped: 1 - appended };
7578
7833
  }
7579
7834
 
7580
7835
  // src/effort/store/changes.ts
7581
7836
  import { randomUUID as randomUUID4 } from "crypto";
7582
- import { existsSync as existsSync11 } from "fs";
7837
+ import { existsSync as existsSync12 } from "fs";
7583
7838
  var INSERT_CHANGE = `
7584
7839
  INSERT INTO changes (
7585
7840
  id, session_id, plan_stub, task_line,
@@ -7658,7 +7913,7 @@ var SELECT_PLAN_CHANGES = `
7658
7913
  `;
7659
7914
  function readPlanChanges(repoRoot, planStub2) {
7660
7915
  const path5 = sqliteStorePath(repoRoot);
7661
- if (!existsSync11(path5))
7916
+ if (!existsSync12(path5))
7662
7917
  return [];
7663
7918
  const rows = withSqliteStore(path5, false, (db) => db.query(SELECT_PLAN_CHANGES).all(planStub2));
7664
7919
  return rows.map((row) => ({
@@ -7891,8 +8146,8 @@ function describeTaskReportRecord(record) {
7891
8146
  }
7892
8147
 
7893
8148
  // src/utils/progress.ts
7894
- import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "fs";
7895
- import { join as join10 } from "path";
8149
+ import { existsSync as existsSync13, writeFileSync as writeFileSync5 } from "fs";
8150
+ import { join as join11 } from "path";
7896
8151
  var PROGRESS_FILE_NAME = "progress.txt";
7897
8152
  var PROGRESS_CAP_BYTES = 16000;
7898
8153
  var LISTED_FIELDS = [
@@ -7934,7 +8189,7 @@ var ESCAPED_CODE_POINTS = new Set([
7934
8189
  8233
7935
8190
  ]);
7936
8191
  function progressFilePath(repoRoot) {
7937
- return join10(repoRoot, PROGRESS_FILE_NAME);
8192
+ return join11(repoRoot, PROGRESS_FILE_NAME);
7938
8193
  }
7939
8194
  function escapeValue(value) {
7940
8195
  let escaped = "";
@@ -7995,7 +8250,7 @@ function renderProgressText(findings, capBytes = PROGRESS_CAP_BYTES) {
7995
8250
  }
7996
8251
  function readProgressFindings(repoRoot, planStub2) {
7997
8252
  const path5 = sqliteStorePath(repoRoot);
7998
- if (!existsSync12(path5))
8253
+ if (!existsSync13(path5))
7999
8254
  return [];
8000
8255
  const rows = withSqliteStore(path5, false, (db) => {
8001
8256
  const query = db.query(SELECT_FINDINGS);
@@ -8022,16 +8277,16 @@ function writeProgress(repoRoot, planStub2) {
8022
8277
 
8023
8278
  // src/start/preflight.ts
8024
8279
  import { randomUUID as randomUUID6 } from "crypto";
8025
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
8280
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
8026
8281
  import { homedir as homedir5 } from "os";
8027
- import { basename as basename4 } from "path";
8282
+ import { basename as basename5 } from "path";
8028
8283
 
8029
8284
  // src/preflight/first-dispatch.ts
8030
- import { readFileSync as readFileSync11 } from "fs";
8285
+ import { readFileSync as readFileSync12 } from "fs";
8031
8286
  var TICKED_TASK = /^- \[[xX]\] (.+)/;
8032
8287
  function readIfReadable(path5) {
8033
8288
  try {
8034
- return readFileSync11(path5, "utf8");
8289
+ return readFileSync12(path5, "utf8");
8035
8290
  } catch {
8036
8291
  return null;
8037
8292
  }
@@ -8064,11 +8319,11 @@ function knownMissingNotice(lines) {
8064
8319
  }
8065
8320
  function agentSourcePath(planPath) {
8066
8321
  const tracker = trackerPathFor(planPath);
8067
- return existsSync13(tracker) ? tracker : planPath;
8322
+ return existsSync14(tracker) ? tracker : planPath;
8068
8323
  }
8069
8324
  function readIfReadable2(path5) {
8070
8325
  try {
8071
- return readFileSync12(path5, "utf8");
8326
+ return readFileSync13(path5, "utf8");
8072
8327
  } catch {
8073
8328
  return null;
8074
8329
  }
@@ -8084,7 +8339,7 @@ function refuseUnresolvableAgents(options) {
8084
8339
  if (missing.length === 0)
8085
8340
  return;
8086
8341
  throw new CommandExit(1, [
8087
- `\u274C Refusing to start: ${basename4(path5)} names ${missing.length} agent(s) no loaded scope` + ` defines (loop.settingSources: ${settingSources.join(", ")}).`,
8342
+ `\u274C Refusing to start: ${basename5(path5)} names ${missing.length} agent(s) no loaded scope` + ` defines (loop.settingSources: ${settingSources.join(", ")}).`,
8088
8343
  ...missing.map((agent) => ` ${missingAgentLine(agent)}`),
8089
8344
  " Nothing was checked and nothing was dispatched."
8090
8345
  ].join(`
@@ -8117,7 +8372,7 @@ function skippedStartLine(item, tracker) {
8117
8372
  return `\u23ED start-only item ${item.kind} ${JSON.stringify(item.name)} was not checked:` + " it is probed on a first dispatch alone," + ` and ${tracker} already holds a ticked task`;
8118
8373
  }
8119
8374
  function announceSkippedStart(planPath, skipped) {
8120
- const tracker = basename4(trackerPathFor(planPath));
8375
+ const tracker = basename5(trackerPathFor(planPath));
8121
8376
  const output = activeOutput();
8122
8377
  for (const [index, item] of skipped.entries()) {
8123
8378
  const lead = index === 0 ? `
@@ -8136,7 +8391,7 @@ function startTier(planPath, items) {
8136
8391
  function announceReminders(planPath, reminders) {
8137
8392
  if (reminders.length === 0)
8138
8393
  return;
8139
- const file = basename4(prerequisitesPathForPlan(planPath) ?? planPath);
8394
+ const file = basename5(prerequisitesPathForPlan(planPath) ?? planPath);
8140
8395
  const output = activeOutput();
8141
8396
  output.info(`
8142
8397
  \uD83D\uDCCC ${file} names ${reminders.length} step(s) the preflight does not check:`);
@@ -8372,14 +8627,14 @@ async function holdWhilePaused(options) {
8372
8627
  }
8373
8628
 
8374
8629
  // src/start/plan-path.ts
8375
- import { existsSync as existsSync14 } from "fs";
8376
- import { resolve as resolve6 } from "path";
8630
+ import { existsSync as existsSync15 } from "fs";
8631
+ import { resolve as resolve8 } from "path";
8377
8632
  var DEFAULT_PLAN_FILE = "PLAN.md";
8378
8633
  function resolvePlanPath(repoRoot, planDir, planArg) {
8379
8634
  if (planArg)
8380
- return resolve6(repoRoot, planArg);
8381
- const inPlanDir = resolve6(repoRoot, planDir, DEFAULT_PLAN_FILE);
8382
- return existsSync14(inPlanDir) ? inPlanDir : resolve6(repoRoot, DEFAULT_PLAN_FILE);
8635
+ return resolve8(repoRoot, planArg);
8636
+ const inPlanDir = resolve8(repoRoot, planDir, DEFAULT_PLAN_FILE);
8637
+ return existsSync15(inPlanDir) ? inPlanDir : resolve8(repoRoot, DEFAULT_PLAN_FILE);
8383
8638
  }
8384
8639
 
8385
8640
  // src/utils/git.ts
@@ -8562,7 +8817,7 @@ async function handleRedChecks(io, branch, prNumber, rows) {
8562
8817
  }
8563
8818
 
8564
8819
  // src/release/prepare.ts
8565
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
8820
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
8566
8821
 
8567
8822
  // src/release/changelog.ts
8568
8823
  var RELEASE_HEADING_FIELDS = ["version", "date", "title"];
@@ -8747,17 +9002,17 @@ function changelogDate(now) {
8747
9002
  }
8748
9003
 
8749
9004
  // src/release/enabled.ts
8750
- import { statSync as statSync6 } from "fs";
8751
- import { resolve as resolve7 } from "path";
9005
+ import { statSync as statSync8 } from "fs";
9006
+ import { resolve as resolve9 } from "path";
8752
9007
  function isRegularFile(full) {
8753
9008
  try {
8754
- return statSync6(full).isFile();
9009
+ return statSync8(full).isFile();
8755
9010
  } catch {
8756
9011
  return false;
8757
9012
  }
8758
9013
  }
8759
9014
  function readFile(repoRoot, path5) {
8760
- const resolved = resolve7(repoRoot, path5);
9015
+ const resolved = resolve9(repoRoot, path5);
8761
9016
  return { path: path5, resolved, present: isRegularFile(resolved) };
8762
9017
  }
8763
9018
  function resolveReleaseEnabled(settings, repoRoot) {
@@ -8794,7 +9049,7 @@ function resolveReleaseLevel(declared, notes) {
8794
9049
  }
8795
9050
 
8796
9051
  // src/release/version.ts
8797
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
9052
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
8798
9053
  var RELEASE_REMOTE = "origin";
8799
9054
  var RELEASE_BASE_BRANCH = "main";
8800
9055
  var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
@@ -8886,7 +9141,7 @@ function writeManifestVersion(path5, next) {
8886
9141
  const unwritten = { path: path5, written: false, previous: null, version: next };
8887
9142
  let text;
8888
9143
  try {
8889
- text = readFileSync13(path5, "utf8");
9144
+ text = readFileSync14(path5, "utf8");
8890
9145
  } catch (error) {
8891
9146
  return { ...unwritten, problem: `${path5} could not be read: ${messageOf(error)}` };
8892
9147
  }
@@ -8989,7 +9244,7 @@ function planVersion(input, versionFile, level) {
8989
9244
  }
8990
9245
  function textOf(path5) {
8991
9246
  try {
8992
- return readFileSync14(path5, "utf8");
9247
+ return readFileSync15(path5, "utf8");
8993
9248
  } catch {
8994
9249
  return null;
8995
9250
  }
@@ -9102,7 +9357,7 @@ function changelogFailure(failed3, versionEdit, restored) {
9102
9357
  }
9103
9358
 
9104
9359
  // src/release/verify.ts
9105
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
9360
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
9106
9361
  function changelogInsertionSpan(before, after) {
9107
9362
  const beforeLines = before.split(`
9108
9363
  `);
@@ -9119,7 +9374,7 @@ function changelogInsertionSpan(before, after) {
9119
9374
  }
9120
9375
  function textOf2(path5) {
9121
9376
  try {
9122
- return readFileSync15(path5, "utf8");
9377
+ return readFileSync16(path5, "utf8");
9123
9378
  } catch {
9124
9379
  return null;
9125
9380
  }
@@ -9601,11 +9856,11 @@ function announcePlanIssues(planContent) {
9601
9856
  }
9602
9857
 
9603
9858
  // src/start/runtime.ts
9604
- import { existsSync as existsSync15, readdirSync as readdirSync5, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
9859
+ import { existsSync as existsSync16, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync9 } from "fs";
9605
9860
  import { homedir as homedir6 } from "os";
9606
- import { isAbsolute as isAbsolute3, join as join11, relative as relative3, resolve as resolve8, sep } from "path";
9861
+ import { isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve10, sep } from "path";
9607
9862
  var RUNTIME_FLAG = "--runtime";
9608
- var RUNTIME_SUBDIR = join11(".rafa", "runtime");
9863
+ var RUNTIME_SUBDIR = join12(".rafa", "runtime");
9609
9864
  var RUNTIME_ENTRY = "cli.js";
9610
9865
  var SOURCE_DIR = "src";
9611
9866
  var RUNTIME_COMMAND = ["start"];
@@ -9622,7 +9877,7 @@ function resolveRuntime(value, places) {
9622
9877
  throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
9623
9878
  }
9624
9879
  const isVersion = VERSION_SHAPE2.test(value);
9625
- const named = isVersion ? join11(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve8(places.cwd, value);
9880
+ const named = isVersion ? join12(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve10(places.cwd, value);
9626
9881
  refuseInsideSource(value, named, places);
9627
9882
  const entry = entryOf(value, named, isVersion, places.home);
9628
9883
  const real = realpathSync3(entry);
@@ -9633,7 +9888,7 @@ function entryOf(value, named, isVersion, home) {
9633
9888
  if (isVersion) {
9634
9889
  if (isFile2(named))
9635
9890
  return named;
9636
- const root = join11(home, RUNTIME_SUBDIR);
9891
+ const root = join12(home, RUNTIME_SUBDIR);
9637
9892
  const installed = installedVersions(root);
9638
9893
  throw refusal3([
9639
9894
  `\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
@@ -9642,7 +9897,7 @@ function entryOf(value, named, isVersion, home) {
9642
9897
  }
9643
9898
  if (isFile2(named))
9644
9899
  return named;
9645
- const inside = join11(named, RUNTIME_ENTRY);
9900
+ const inside = join12(named, RUNTIME_ENTRY);
9646
9901
  if (isDirectory(named) && isFile2(inside))
9647
9902
  return inside;
9648
9903
  throw refusal3([
@@ -9660,8 +9915,8 @@ function refuseInsideSource(value, path5, places) {
9660
9915
  ]);
9661
9916
  }
9662
9917
  function sourceDirs({ cwd, root }) {
9663
- const dirs = [join11(cwd, SOURCE_DIR), join11(root, SOURCE_DIR)];
9664
- const real = dirs.filter((dir) => existsSync15(dir)).map((dir) => realpathSync3(dir));
9918
+ const dirs = [join12(cwd, SOURCE_DIR), join12(root, SOURCE_DIR)];
9919
+ const real = dirs.filter((dir) => existsSync16(dir)).map((dir) => realpathSync3(dir));
9665
9920
  return [...new Set([...dirs, ...real])];
9666
9921
  }
9667
9922
  function isInside(path5, dir) {
@@ -9670,21 +9925,21 @@ function isInside(path5, dir) {
9670
9925
  }
9671
9926
  function installedVersions(root) {
9672
9927
  try {
9673
- return readdirSync5(root).filter((name) => isFile2(join11(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
9928
+ return readdirSync6(root).filter((name) => isFile2(join12(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
9674
9929
  } catch {
9675
9930
  return [];
9676
9931
  }
9677
9932
  }
9678
9933
  function isFile2(path5) {
9679
9934
  try {
9680
- return statSync7(path5).isFile();
9935
+ return statSync9(path5).isFile();
9681
9936
  } catch {
9682
9937
  return false;
9683
9938
  }
9684
9939
  }
9685
9940
  function isDirectory(path5) {
9686
9941
  try {
9687
- return statSync7(path5).isDirectory();
9942
+ return statSync9(path5).isDirectory();
9688
9943
  } catch {
9689
9944
  return false;
9690
9945
  }
@@ -9715,7 +9970,7 @@ function realOrResolved(path5) {
9715
9970
  try {
9716
9971
  return realpathSync3(path5);
9717
9972
  } catch {
9718
- return resolve8(path5);
9973
+ return resolve10(path5);
9719
9974
  }
9720
9975
  }
9721
9976
  async function runRuntime(entry, words, run) {
@@ -9801,11 +10056,11 @@ function eventOf(line) {
9801
10056
  }
9802
10057
 
9803
10058
  // src/triage/triage.ts
9804
- import { basename as basename5, join as join12 } from "path";
10059
+ import { basename as basename6, join as join13 } from "path";
9805
10060
 
9806
10061
  // src/effort/store/tracker-refs.ts
9807
10062
  import { randomUUID as randomUUID9 } from "crypto";
9808
- import { existsSync as existsSync16 } from "fs";
10063
+ import { existsSync as existsSync17 } from "fs";
9809
10064
  var SELECT_HELD = `
9810
10065
  SELECT seq, tracker_ref FROM findings
9811
10066
  WHERE session_id = ? AND artifact = ?
@@ -9928,7 +10183,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
9928
10183
  wanted.json,
9929
10184
  collectedAt
9930
10185
  ];
9931
- const [action, stored] = withSqliteStore(path5, !existsSync16(path5), (db) => {
10186
+ const [action, stored] = withSqliteStore(path5, !existsSync17(path5), (db) => {
9932
10187
  const place = db.transaction(() => placeRef(db, path5, write, wanted, insertValues));
9933
10188
  return place.immediate();
9934
10189
  });
@@ -9937,14 +10192,14 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
9937
10192
  function readTrackerRef(repoRoot, artifact) {
9938
10193
  checkArtifact("read", artifact);
9939
10194
  const path5 = sqliteStorePath(repoRoot);
9940
- if (!existsSync16(path5))
10195
+ if (!existsSync17(path5))
9941
10196
  return null;
9942
10197
  const oldest = withSqliteStore(path5, false, (db) => db.query(SELECT_OLDEST).get(artifact));
9943
10198
  return oldest === null ? null : heldRefOf(path5, oldest.seq, oldest.tracker_ref).ref;
9944
10199
  }
9945
10200
 
9946
10201
  // src/triage/triage.ts
9947
- var PRIVATE_TRIAGE_DIR = join12(".rafa", "triage", "private");
10202
+ var PRIVATE_TRIAGE_DIR = join13(".rafa", "triage", "private");
9948
10203
  var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
9949
10204
  var TRIAGE_MODULE = "unassigned";
9950
10205
  var BLOCKER_SEPARATOR = "; ";
@@ -9955,7 +10210,7 @@ var MIN_FENCE_LENGTH = 3;
9955
10210
  var ISSUE_OPENING = "An out-of-scope bug a rafa task session reported. The loop filed it and" + " dispatches no task for it: a plan that wants it fixed declares a task.";
9956
10211
  var COMMENT_OPENING = "Reported again by a rafa task session.";
9957
10212
  function privateTriageDir(repoRoot) {
9958
- return join12(repoRoot, PRIVATE_TRIAGE_DIR);
10213
+ return join13(repoRoot, PRIVATE_TRIAGE_DIR);
9959
10214
  }
9960
10215
  function createPrivateTriageTracker(repoRoot, options = {}) {
9961
10216
  return createLocalTracker({
@@ -10018,7 +10273,7 @@ function oneLine3(text) {
10018
10273
  return text.replace(/\s+/g, " ").trim();
10019
10274
  }
10020
10275
  function bugKeyOf(trackerPath, artifact) {
10021
- return `${basename5(trackerPath)}${KEY_SEPARATOR}${oneLine3(artifact)}`;
10276
+ return `${basename6(trackerPath)}${KEY_SEPARATOR}${oneLine3(artifact)}`;
10022
10277
  }
10023
10278
  function fenced(value) {
10024
10279
  const runs = Array.from(value.matchAll(/`+/g), (run) => run[0].length);
@@ -10252,9 +10507,9 @@ function describeStartTriage(result) {
10252
10507
  };
10253
10508
  }
10254
10509
  async function resolvePublicTracker(options, redact) {
10255
- const resolve9 = options.resolve ?? resolveTracker;
10510
+ const resolve11 = options.resolve ?? resolveTracker;
10256
10511
  try {
10257
- const { tracker, attempts } = await resolve9({
10512
+ const { tracker, attempts } = await resolve11({
10258
10513
  config: options.config,
10259
10514
  context: { repoRoot: options.repoRoot }
10260
10515
  });
@@ -10425,8 +10680,8 @@ function msUntil(startAt, now = new Date) {
10425
10680
  return target.getTime() - now.getTime();
10426
10681
  }
10427
10682
  function sleepFor(ms) {
10428
- return new Promise((resolve9) => {
10429
- setTimeout(resolve9, ms);
10683
+ return new Promise((resolve11) => {
10684
+ setTimeout(resolve11, ms);
10430
10685
  });
10431
10686
  }
10432
10687
  async function deferUntil(startAt, now = new Date, sleep = sleepFor) {