@open-tomato/rafa 0.34.1 → 0.36.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.
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AGENT_DEFINITION_DIR,
4
+ BLOCKED_BY_FIELD,
4
5
  BOARD_LABELS,
5
6
  BOARD_LISTING_LIMIT,
6
7
  BRANCH_PREFIX,
@@ -32,6 +33,7 @@ import {
32
33
  NATIVE_RETRY_HINT,
33
34
  NEXT_COMMAND_ACTIONS,
34
35
  NO_NEXT_STEP,
36
+ NoSessionRefusal,
35
37
  ORIGIN_FIELD,
36
38
  PREVIOUS_COPY_WARN_ABOVE,
37
39
  PROVENANCE_FIELD,
@@ -61,6 +63,7 @@ import {
61
63
  VENDOR_COMMAND,
62
64
  WHEN_TO_USE_HEADING,
63
65
  WORKTREES_SUBDIR,
66
+ WORKTREE_STATUS,
64
67
  actionInvocation,
65
68
  actionOutput,
66
69
  addBlockerToBody,
@@ -103,6 +106,7 @@ import {
103
106
  compareVersions,
104
107
  configuredMoveRelations,
105
108
  convertToInstinct,
109
+ countChanges,
106
110
  countCharacters,
107
111
  countPreviousCopies,
108
112
  createGhBoardListing,
@@ -135,6 +139,7 @@ import {
135
139
  epicTickSentence,
136
140
  estimateEta,
137
141
  etaLine,
142
+ eventsFilePath,
138
143
  expectTwoArguments,
139
144
  failingRows,
140
145
  fetchClaimBranches,
@@ -179,6 +184,7 @@ import {
179
184
  labelsLineBlockersOf,
180
185
  lazyPrompter,
181
186
  lineRefusal,
187
+ lineRefusal1 as lineRefusal2,
182
188
  listBoardLabels,
183
189
  list_default,
184
190
  list_default1 as list_default2,
@@ -346,7 +352,6 @@ import {
346
352
  unlabelledRoadmapMessage,
347
353
  unresolvedTracker,
348
354
  urlLines,
349
- usage,
350
355
  validateManifest,
351
356
  validate_default,
352
357
  verdictOf,
@@ -355,9 +360,11 @@ import {
355
360
  waitingSettingsOf,
356
361
  watchDryEpic,
357
362
  worktreeAt,
363
+ worktreeDirAt,
364
+ worktreesHolding,
358
365
  writeSession,
359
366
  writeShippedFile
360
- } from "./index-s8ff86xt.js";
367
+ } from "./index-2n2nedrk.js";
361
368
  import {
362
369
  PROBE_TIMEOUT_MS,
363
370
  loadPlanPrerequisites,
@@ -369,7 +376,7 @@ import {
369
376
  runPreflight,
370
377
  splitBlockerComment,
371
378
  trackerPathFor
372
- } from "./index-v8d7c6sw.js";
379
+ } from "./index-7bb0fdb7.js";
373
380
  import {
374
381
  ACTION_HEADING,
375
382
  CAUSE_HEADING,
@@ -379,6 +386,7 @@ import {
379
386
  EFFORT_DIR_VARIABLE,
380
387
  EFFORT_KEY_PROJECTIONS,
381
388
  EFFORT_STORE_DIR,
389
+ EVENT_PREFIX,
382
390
  EffortCopyFailure,
383
391
  EffortCopyRefusal,
384
392
  HOP_FILE,
@@ -422,6 +430,7 @@ import {
422
430
  instinctProject,
423
431
  isFile,
424
432
  issueLine,
433
+ keepsIdLine,
425
434
  loadConfig,
426
435
  mainCheckoutOf,
427
436
  mergeFrontmatter,
@@ -429,8 +438,10 @@ import {
429
438
  migrateStore,
430
439
  minutesBetween,
431
440
  normalizeRemote,
441
+ oneLine,
432
442
  openNdjsonStore,
433
443
  openSqliteStore,
444
+ padKind,
434
445
  planFileName,
435
446
  plansDirAt,
436
447
  plural,
@@ -464,7 +475,7 @@ import {
464
475
  versionLine,
465
476
  writeFrontmatter,
466
477
  writePositionFile
467
- } from "./index-bebv92ha.js";
478
+ } from "./index-qvbeqrx3.js";
468
479
  import {
469
480
  CONFIG_DEFAULTS,
470
481
  CONFIG_FILE,
@@ -479,7 +490,7 @@ import {
479
490
  readItemBytes,
480
491
  readRafaBlocks,
481
492
  resolveTiers
482
- } from "./index-vp0xghk2.js";
493
+ } from "./index-jfas157k.js";
483
494
  import {
484
495
  promotable
485
496
  } from "./index-d6d0942q.js";
@@ -1751,12 +1762,12 @@ function interactiveFlag() {
1751
1762
  function interactiveInstead(commandName) {
1752
1763
  return `Run \`${commandName} --output=json\` to read the rows without one.`;
1753
1764
  }
1754
- function interactiveTerminal(context, seams, commandName, usage2) {
1765
+ function interactiveTerminal(context, seams, commandName, usage) {
1755
1766
  if (context.flags["interactive"] !== true)
1756
1767
  return null;
1757
1768
  if (context.outputMode === "json") {
1758
1769
  throw new CommandExit(1, "\u274C -i browses the rows in a terminal and --output=json writes them as data;" + ` give one or the other
1759
- Usage: ${usage2}`);
1770
+ Usage: ${usage}`);
1760
1771
  }
1761
1772
  const terminal = seams.terminal?.() ?? processTerminal();
1762
1773
  refuseWithoutTerminal(terminal, { instead: interactiveInstead(commandName) });
@@ -2456,14 +2467,14 @@ function usageOf(kind) {
2456
2467
  return `rafa ${kind} search "<question>" [--all] [--no-model]`;
2457
2468
  }
2458
2469
  function readSearchLine(context, kind) {
2459
- const usage2 = usageOf(kind);
2460
- const hint = `Type the question ahead of it: ${usage2}`;
2470
+ const usage = usageOf(kind);
2471
+ const hint = `Type the question ahead of it: ${usage}`;
2461
2472
  const all = readSwitch("all", context.flags["all"], hint);
2462
2473
  const model = readSwitch("model", context.flags["model"] ?? true, hint);
2463
- const question = expectOneArgument(context.args, usage2);
2474
+ const question = expectOneArgument(context.args, usage);
2464
2475
  if (question.trim() === "")
2465
2476
  throw new CommandExit(1, `\u274C The question is blank.
2466
- Usage: ${usage2}`);
2477
+ Usage: ${usage}`);
2467
2478
  const kinds = all ? [kind, otherKind(kind)] : [kind];
2468
2479
  return { question, kinds, model };
2469
2480
  }
@@ -3241,11 +3252,11 @@ var RELEASING = { during: "was being released", nothing: "nothing was released"
3241
3252
  function refuse(message) {
3242
3253
  return new CommandExit(1, `\u274C ${COMMAND}: ${message}`);
3243
3254
  }
3244
- function readIssueArgument(args, usage2) {
3245
- const word = expectOneArgument(args, usage2);
3255
+ function readIssueArgument(args, usage) {
3256
+ const word = expectOneArgument(args, usage);
3246
3257
  if (!ISSUE_NUMBER2.test(word) || !Number.isSafeInteger(Number(word))) {
3247
3258
  throw new CommandExit(1, `\u274C "${word}" is no issue number, expected a positive whole number
3248
- Usage: ${usage2}`);
3259
+ Usage: ${usage}`);
3249
3260
  }
3250
3261
  return Number(word);
3251
3262
  }
@@ -3511,7 +3522,7 @@ var HANDING = { during: "was being handed over", nothing: "nothing was pushed" }
3511
3522
  function refuse3(message) {
3512
3523
  return new CommandExit(1, `\u274C ${COMMAND3}: ${message}`);
3513
3524
  }
3514
- function lineRefusal2(message) {
3525
+ function lineRefusal3(message) {
3515
3526
  return refuse3(`${message}
3516
3527
  Usage: ${USAGE9}`);
3517
3528
  }
@@ -3521,29 +3532,29 @@ function readWithdraw(flags) {
3521
3532
  return false;
3522
3533
  if (typeof value === "boolean")
3523
3534
  return value;
3524
- throw lineRefusal2(`--${WITHDRAW_FLAG} takes no value, and read "${value}" as one; type the issue number before the flags`);
3535
+ throw lineRefusal3(`--${WITHDRAW_FLAG} takes no value, and read "${value}" as one; type the issue number before the flags`);
3525
3536
  }
3526
3537
  function readTo(flags) {
3527
3538
  const value = flags[TO_FLAG];
3528
3539
  if (value === undefined)
3529
3540
  return null;
3530
3541
  if (typeof value === "boolean")
3531
- throw lineRefusal2(`--${TO_FLAG} needs a store id, as --${TO_FLAG}=<store id>`);
3542
+ throw lineRefusal3(`--${TO_FLAG} needs a store id, as --${TO_FLAG}=<store id>`);
3532
3543
  const to = value.trim();
3533
3544
  if (!STORE_ID.test(to))
3534
- throw lineRefusal2(`--${TO_FLAG}=${JSON.stringify(value)} is no store id, which is one word with no blank`);
3545
+ throw lineRefusal3(`--${TO_FLAG}=${JSON.stringify(value)} is no store id, which is one word with no blank`);
3535
3546
  return to;
3536
3547
  }
3537
3548
  function readHandRequest(flags) {
3538
3549
  const withdraw = readWithdraw(flags);
3539
3550
  const to = readTo(flags);
3540
3551
  if (withdraw && to !== null) {
3541
- throw lineRefusal2(`--${TO_FLAG} and --${WITHDRAW_FLAG} are two actions; give one`);
3552
+ throw lineRefusal3(`--${TO_FLAG} and --${WITHDRAW_FLAG} are two actions; give one`);
3542
3553
  }
3543
3554
  if (withdraw)
3544
3555
  return { action: "withdraw" };
3545
3556
  if (to === null)
3546
- throw lineRefusal2(`name the receiving store with --${TO_FLAG}=<store id>, or take a pending handover back with --${WITHDRAW_FLAG}`);
3557
+ throw lineRefusal3(`name the receiving store with --${TO_FLAG}=<store id>, or take a pending handover back with --${WITHDRAW_FLAG}`);
3547
3558
  return { action: "hand", to };
3548
3559
  }
3549
3560
  function recordFor(reading, request, issue, storeId) {
@@ -4326,7 +4337,8 @@ async function runCleanup(context, seams) {
4326
4337
  now: (seams.now ?? (() => new Date))(),
4327
4338
  home: project.home,
4328
4339
  cwd,
4329
- projectRoot: project.root
4340
+ projectRoot: project.root,
4341
+ worktreeDir: config.loopWorktreeDir
4330
4342
  });
4331
4343
  if (context.outputMode === "json") {
4332
4344
  context.output.result(cleanupData(read));
@@ -4346,7 +4358,7 @@ function createCleanupCommand(seams = DEFAULT_CLEANUP_SEAMS) {
4346
4358
  subject: "cleanup",
4347
4359
  action: "cleanup",
4348
4360
  summary: "list merged, stale and unpushed branches and leftover worktrees; remove the ones ticked",
4349
- description: "Runs `git fetch --prune`, then lists the local branches and worktrees that have piled up in" + " four groups, each row its name, its last commit date and why it is listed: Merged (reachable from" + " the base, its pull request merged per the provider, or its upstream gone), Stale (an upstream, not" + " merged, no commit in `cleanup.staleDays`), Not pushed (no upstream, or commits ahead of it) and" + " Worktrees (under `.claude/worktrees/` and `~/.rafa/worktrees/`). The current branch, the base and" + " every name a `cleanup.keep` glob matches are never listed. With a terminal the groups are one" + " checklist: space ticks a row, `a` ticks a whole group, and Merged rows and clean worktrees on a" + " Merged branch start ticked; a worktree that is dirty, locked, the current one, running a loop" + " session or modified within `cleanup.worktreeIdleDays` cannot be ticked, and says why. A ticked" + " Not-pushed branch asks again, naming the commits deleting it loses. Enter then asks `Delete <n>" + " branches and remove <m> worktrees? [y/N]`, and a yes runs `git worktree remove` for each worktree" + " and `git branch -d` for each Merged branch, `-D` for a squash-merged one and for a Stale or" + " Not-pushed branch ticked and confirmed. Nothing runs with `--force` and nothing remote is" + " deleted: a Stale row names the `git push origin --delete <b>` to run by hand. It exits 1 when a" + " step did not run clean. Without a terminal, or with `--output=json`, it prints the four groups," + " asks nothing and removes nothing, exiting 0; with `--output=json` they are the data of the terminal" + " result event. Starts no session.",
4361
+ description: "Runs `git fetch --prune`, then lists the local branches and worktrees that have piled up in" + " four groups, each row its name, its last commit date and why it is listed: Merged (reachable from" + " the base, its pull request merged per the provider, or its upstream gone), Stale (an upstream, not" + " merged, no commit in `cleanup.staleDays`), Not pushed (no upstream, or commits ahead of it) and" + " Worktrees (under `.claude/worktrees/`, `~/.rafa/worktrees/` and `loop.worktreeDir`). The current" + " branch, the base and every name a `cleanup.keep` glob matches are never listed. With a terminal" + " the groups are one checklist: space ticks a row, `a` ticks a whole group, and Merged rows and" + " clean worktrees on a" + " Merged branch start ticked; a worktree that is dirty, locked, the current one, running a loop" + " session or modified within `cleanup.worktreeIdleDays` cannot be ticked, and says why. A ticked" + " Not-pushed branch asks again, naming the commits deleting it loses. Enter then asks `Delete <n>" + " branches and remove <m> worktrees? [y/N]`, and a yes runs `git worktree remove` for each worktree" + " and `git branch -d` for each Merged branch, `-D` for a squash-merged one and for a Stale or" + " Not-pushed branch ticked and confirmed. Nothing runs with `--force` and nothing remote is" + " deleted: a Stale row names the `git push origin --delete <b>` to run by hand. It exits 1 when a" + " step did not run clean. Without a terminal, or with `--output=json`, it prints the four groups," + " asks nothing and removes nothing, exiting 0; with `--output=json` they are the data of the terminal" + " result event. Starts no session.",
4350
4362
  args: [],
4351
4363
  flags: [
4352
4364
  {
@@ -5530,7 +5542,8 @@ function doctorCleanupSettings(input, now) {
5530
5542
  now,
5531
5543
  home: input.home,
5532
5544
  cwd: input.root,
5533
- projectRoot: input.root
5545
+ projectRoot: input.root,
5546
+ worktreeDir: config.loopWorktreeDir ?? CONFIG_DEFAULTS.loopWorktreeDir
5534
5547
  };
5535
5548
  }
5536
5549
  async function readDoctorCleanup(input, seams = {}) {
@@ -8141,7 +8154,7 @@ function closingLine(result2, command) {
8141
8154
  if (result2.status === "would-merge") {
8142
8155
  return "\uD83D\uDD0D Dry run: the merged store was built beside it, checked (row counts, integrity_check, this rafa's" + ` schema plan) and deleted. Run \`${command} ${result2.otherPath}\` to swap it in; the original would be` + ` kept whole at ${result2.path}.before-merge-<stamp>.bak.`;
8143
8156
  }
8144
- return `\u2705 Merged. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.`;
8157
+ return `\u2705 Merged. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.` + ` ${keepsIdLine("merged store")}`;
8145
8158
  }
8146
8159
  function renderMerge(result2, command = COMMAND_NAME7) {
8147
8160
  const other = result2.otherStore === null ? "a store that names no origin" : `store ${result2.otherStore}`;
@@ -8205,7 +8218,7 @@ function createMergeCommand(seams = {}) {
8205
8218
  subject: "effort",
8206
8219
  action: "merge",
8207
8220
  summary: "merge another device's effort store file into this one, keeping every row and the original",
8208
- description: "Adds to `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, every row another" + " device's store file holds that this one lacks. A row matches on its origin pair, or on its table's" + " identity columns when either side has no origin; an equal pair is skipped, a set-once field NULL here" + " is filled, and any other difference is kept on both sides and recorded in `merge_conflicts`. The gap" + " of each commit brought in, and of the commit after it, is recomputed. The other file is only read." + " The merged store is built beside this one, checked (each table's count plus the rows added," + " integrity_check, the schema plan), and swapped in with the original kept whole as" + " `effort.sqlite.before-merge-<stamp>.bak`. It prints the rows added, skipped and in conflict per table" + " and the backup's name. Exit code 2 for another project's store, and for a `store: ndjson` project," + " naming `rafa effort move --to=sqlite`; exit code 1, changing nothing, for a missing or damaged file," + " a write in flight, a loop session running or paused, and a development build over a store it does not" + " own. With `--output=json` the outcome is the data of the terminal result event. Starts no session.",
8221
+ description: "Adds to `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, every row another" + " device's store file holds that this one lacks. A row matches on its origin pair, or on its table's" + " identity columns when either side has no origin; an equal pair is skipped, a set-once field NULL here" + " is filled, and any other difference is kept on both sides and recorded in `merge_conflicts`. The gap" + " of each commit brought in, and of the commit after it, is recomputed. The other file is only read." + " The merged store is built beside this one, checked (each table's count plus the rows added," + " integrity_check, the schema plan), and swapped in with the original kept whole as" + " `effort.sqlite.before-merge-<stamp>.bak`. The merged store keeps this store's id; the backup is a copy," + " so renamed back it takes a new id on its next write. It prints the rows added, skipped and in conflict per table" + " and the backup's name. Exit code 2 for another project's store, and for a `store: ndjson` project," + " naming `rafa effort move --to=sqlite`; exit code 1, changing nothing, for a missing or damaged file," + " a write in flight, a loop session running or paused, and a development build over a store it does not" + " own. With `--output=json` the outcome is the data of the terminal result event. Starts no session.",
8209
8222
  args: [
8210
8223
  {
8211
8224
  name: "file",
@@ -8354,7 +8367,7 @@ function renderMigrate(result2) {
8354
8367
  case "migrated":
8355
8368
  return [
8356
8369
  ...builtLines(result2),
8357
- `\u2705 Migrated. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.`
8370
+ `\u2705 Migrated. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.` + ` ${keepsIdLine("migrated store")}`
8358
8371
  ];
8359
8372
  }
8360
8373
  }
@@ -8401,7 +8414,7 @@ function createMigrateCommand(seams = {}) {
8401
8414
  subject: "effort",
8402
8415
  action: "migrate",
8403
8416
  summary: "apply the effort store's pending migrations, one that breaks older runtimes included, keeping the original",
8404
- description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then renames the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
8417
+ description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then writes the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. The" + " migrated store keeps the store's id; the backup is a copy, so renamed back it takes a new id on its" + " next write. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
8405
8418
  args: [],
8406
8419
  flags: [
8407
8420
  {
@@ -10000,8 +10013,8 @@ function spanMinutes(row2) {
10000
10013
  return minutesBetween(first, last);
10001
10014
  }
10002
10015
  function tokensOf(row2) {
10003
- const usage2 = row2.usage;
10004
- return usage2.inputTokens + usage2.outputTokens + usage2.cacheReadInputTokens + usage2.cacheCreationInputTokens;
10016
+ const usage = row2.usage;
10017
+ return usage.inputTokens + usage.outputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
10005
10018
  }
10006
10019
  function summariseEpicCost(rows, members) {
10007
10020
  const ids = members.map(boardId);
@@ -10442,11 +10455,11 @@ var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
10442
10455
  function ref7(issue) {
10443
10456
  return `#${String(issue)}`;
10444
10457
  }
10445
- function oneLine(text) {
10458
+ function oneLine2(text) {
10446
10459
  return text.replace(/\s+/gu, " ").trim();
10447
10460
  }
10448
10461
  function criterionName(criterion) {
10449
- return `criterion ${String(criterion.number)}: ${oneLine(criterion.text)}`;
10462
+ return `criterion ${String(criterion.number)}: ${oneLine2(criterion.text)}`;
10450
10463
  }
10451
10464
  function criteria(count) {
10452
10465
  return count === 1 ? "1 criterion" : `${String(count)} criteria`;
@@ -10470,7 +10483,7 @@ function refuseOpenMembers(number, members) {
10470
10483
  const open = members.filter((member) => member.state === "OPEN");
10471
10484
  if (open.length === 0)
10472
10485
  return;
10473
- const named = open.map((member) => `${ref7(member.number)} ${oneLine(member.title)}`).join(", ");
10486
+ const named = open.map((member) => `${ref7(member.number)} ${oneLine2(member.title)}`).join(", ");
10474
10487
  throw refusal7(`Epic ${ref7(number)} has ${String(open.length)} open ${open.length === 1 ? "member" : "members"}: ${named}. The gate checks an epic only once every member is closed: close them, or move them` + " out with rafa epic move <issue> --to=<epic>. No session was started");
10475
10488
  }
10476
10489
  function labelsEpicToClose(issues, epic) {
@@ -10542,7 +10555,7 @@ function closeTriageFile(root, epic) {
10542
10555
  return join22(root, ".rafa", `epic-${String(epic)}-close`);
10543
10556
  }
10544
10557
  function failedCheckArtifact(epic, criterion) {
10545
- return `epic ${ref7(epic)} acceptance criterion: ${oneLine(criterion.text)}`;
10558
+ return `epic ${ref7(epic)} acceptance criterion: ${oneLine2(criterion.text)}`;
10546
10559
  }
10547
10560
  function failedCheckReport(epic, failure, commit) {
10548
10561
  const { criterion } = failure.check;
@@ -10566,7 +10579,7 @@ function failedCheckReport(epic, failure, commit) {
10566
10579
  skillsUsed: [],
10567
10580
  blockers: [],
10568
10581
  outOfScopeBugs: [Object.freeze({
10569
- what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine(criterion.text)}`,
10582
+ what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine2(criterion.text)}`,
10570
10583
  artifact: failedCheckArtifact(epic, criterion),
10571
10584
  security: false,
10572
10585
  extras: []
@@ -10659,9 +10672,9 @@ async function weighResults(run, epic, ran) {
10659
10672
  if (result2.kind === "unanswered")
10660
10673
  warn2(run, `No answer for ${name} \u2014 ${result2.reason}`);
10661
10674
  else if (result2.result === "pass")
10662
- say(run, `\u2713 Passed ${name} \u2014 ${oneLine(result2.evidence)}`);
10675
+ say(run, `\u2713 Passed ${name} \u2014 ${oneLine2(result2.evidence)}`);
10663
10676
  else
10664
- warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine(result2.evidence)}`);
10677
+ warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine2(result2.evidence)}`);
10665
10678
  }
10666
10679
  if (!ran.removal.ok) {
10667
10680
  const said = ran.removal.said === "" ? "git said nothing" : ran.removal.said;
@@ -11721,7 +11734,8 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
11721
11734
  `# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`,
11722
11735
  "# tests:",
11723
11736
  `# fullSuiteTriggers: ${globsLine(CONFIG_DEFAULTS.testsFullSuiteTriggers)} # a task whose diff matches one runs the full suite`,
11724
- `# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`
11737
+ `# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`,
11738
+ `# alwaysRun: ${globsLine(CONFIG_DEFAULTS.testsAlwaysRun)} # test files a task step runs beside its changed-file tests`
11725
11739
  ]);
11726
11740
  var VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
11727
11741
  var PROJECT_HEADER = Object.freeze([
@@ -12169,32 +12183,32 @@ var DEFAULT_CHECK_SEAMS = Object.freeze({
12169
12183
  });
12170
12184
  var SKILL_CHECK_USAGE = "rafa skill check <dir> [--fix] [--project=<root>]";
12171
12185
  var INSTINCT_CHECK_USAGE = "rafa instinct check <dir>";
12172
- function readCheckDirectory(args, usage2, cwd) {
12186
+ function readCheckDirectory(args, usage, cwd) {
12173
12187
  const [dir, extra] = args;
12174
12188
  if (dir === undefined) {
12175
12189
  throw new CommandExit(1, `\u274C Expected a directory to check, got none
12176
- Usage: ${usage2}`);
12190
+ Usage: ${usage}`);
12177
12191
  }
12178
12192
  if (extra !== undefined) {
12179
12193
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
12180
- Usage: ${usage2}`);
12194
+ Usage: ${usage}`);
12181
12195
  }
12182
12196
  return resolve8(cwd(), dir);
12183
12197
  }
12184
- function readFix(value, usage2) {
12198
+ function readFix(value, usage) {
12185
12199
  if (value === undefined || value === false || value === "false")
12186
12200
  return false;
12187
12201
  if (value === true || value === "true")
12188
12202
  return true;
12189
12203
  throw new CommandExit(1, `\u274C --fix takes no value, and read "${value}" as one; type it after the directory
12190
- Usage: ${usage2}`);
12204
+ Usage: ${usage}`);
12191
12205
  }
12192
- function readProjectRoot(value, usage2, cwd) {
12206
+ function readProjectRoot(value, usage, cwd) {
12193
12207
  if (value === undefined || value === false)
12194
12208
  return null;
12195
12209
  if (value === true || value === "") {
12196
12210
  throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
12197
- Usage: ${usage2}`);
12211
+ Usage: ${usage}`);
12198
12212
  }
12199
12213
  return resolve8(cwd(), value);
12200
12214
  }
@@ -12238,12 +12252,12 @@ function checkCommandResult(report2, projectRoot2) {
12238
12252
  warningFiles: warningFileCount(report2.reports)
12239
12253
  };
12240
12254
  }
12241
- function scan(root, kind, options, usage2) {
12255
+ function scan(root, kind, options, usage) {
12242
12256
  try {
12243
12257
  return checkDirectory(root, kind, options);
12244
12258
  } catch (error) {
12245
12259
  const reason = error instanceof Error ? error.message : String(error);
12246
- throw new CommandExit(1, `\u274C ${usage2}: ${reason}`);
12260
+ throw new CommandExit(1, `\u274C ${usage}: ${reason}`);
12247
12261
  }
12248
12262
  }
12249
12263
  async function runCheckCommand(context, spec, seams) {
@@ -12536,7 +12550,7 @@ Usage: ${USAGE19}`);
12536
12550
  return "blessed";
12537
12551
  return conflicts ? "conflicts" : "all";
12538
12552
  }
12539
- function oneLine2(text) {
12553
+ function oneLine3(text) {
12540
12554
  return text.trim().replace(/\s+/gu, " ");
12541
12555
  }
12542
12556
  function byConfidence(left, right) {
@@ -12569,7 +12583,7 @@ function conflictLines(conflict) {
12569
12583
  const width = Math.max(...conflict.records.map((record) => record.id.length));
12570
12584
  return [
12571
12585
  ` ${conflict.scope} ${conflict.trigger}`,
12572
- ...conflict.records.map((record) => lessonLine(" ", record, width, oneLine2(record.action)))
12586
+ ...conflict.records.map((record) => lessonLine(" ", record, width, oneLine3(record.action)))
12573
12587
  ];
12574
12588
  }
12575
12589
  function renderConflicts(result2) {
@@ -12837,7 +12851,11 @@ import { join as join27, resolve as resolve9 } from "path";
12837
12851
  var CHECK_USAGE = "rafa issue check <n> [--stamp]";
12838
12852
  var STAMP_FLAG = "stamp";
12839
12853
  var ISSUE_NUMBER7 = /^[1-9]\d*$/u;
12840
- var STATE_ORDER = ["ok", "dangling", "suspect", "resolved", "unknown"];
12854
+ var STATE_ORDER = ["ok", "new", "dangling", "suspect", "resolved", "unknown"];
12855
+ var NOTES = new Map([
12856
+ ["new", " \u2014 not there yet, read as a target the spec is to add"],
12857
+ ["unknown", " \u2014 its repository could not be read, so it is not checked"]
12858
+ ]);
12841
12859
  function readCheckIssue(args) {
12842
12860
  if (args.length > 1) {
12843
12861
  throw lineRefusal(`Expected one issue number, got ${String(args.length)}: ${args.join(" ")}`, CHECK_USAGE);
@@ -12901,8 +12919,8 @@ function rowSuffix(ref8) {
12901
12919
  const noun = ref8.changedHeadings.length === 1 ? "heading" : "headings";
12902
12920
  const headings = ref8.changedHeadings.length === 0 ? "" : `: ${noun} ${ref8.changedHeadings.map((heading) => `"${heading}"`).join(", ")} changed`;
12903
12921
  const after = ref8.unblock === null ? "" : ` \u2014 ${ref8.unblock}`;
12904
- const unknown = ref8.state === "unknown" ? " \u2014 its repository could not be read, so it is not checked" : "";
12905
- return `${headings} (line ${String(ref8.line)})${after}${unknown}`;
12922
+ const note = NOTES.get(ref8.state) ?? "";
12923
+ return `${headings} (line ${String(ref8.line)})${after}${note}`;
12906
12924
  }
12907
12925
  function checkedRefLine(ref8) {
12908
12926
  return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix(ref8)}`;
@@ -12957,7 +12975,7 @@ function createIssueCheckCommand(seams = {}) {
12957
12975
  subject: "issue",
12958
12976
  action: "check",
12959
12977
  summary: "read the references a spec's saved copy names, each with its state",
12960
- description: "Reads the saved copy of issue <n> under `specs.dir` and prints every reference it names \u2014 an" + " issue, a file, an exported symbol, a command, a flag, a config key \u2014 with its state against the" + " stamp the copy keeps: ok, dangling, suspect, resolved or unknown. A reference the copy keeps no" + " stamp for is stamped on this read. Exits 0 whatever the states are, and plans nothing. A missing" + " saved copy is refused, naming the `rafa plan create --issue=<n>` that writes one. With" + " `--output=json` every reference, with its kind, line, state and fingerprint, is the data of the" + " terminal result event.",
12978
+ description: "Reads the saved copy of issue <n> under `specs.dir` and prints every reference it names \u2014 an" + " issue, a file, a declared symbol, a command, a flag, a config key \u2014 with its state against the" + " stamp the copy keeps: ok, new, dangling, suspect, resolved or unknown. A reference the copy keeps no" + " stamp for is stamped on this read. Exits 0 whatever the states are, and plans nothing. A missing" + " saved copy is refused, naming the `rafa plan create --issue=<n>` that writes one. With" + " `--output=json` every reference, with its kind, line, state and fingerprint, is the data of the" + " terminal result event.",
12961
12979
  args: [
12962
12980
  {
12963
12981
  name: "n",
@@ -13059,10 +13077,89 @@ function createIssueCommentCommand(seams = DEFAULT_ISSUE_SEAMS) {
13059
13077
  }
13060
13078
  var comment_default = createIssueCommentCommand();
13061
13079
 
13080
+ // src/commands/issue/create-blocked.ts
13081
+ var KNOWN_LIST_LIMIT = 1000;
13082
+ var UNFILED = 0;
13083
+ var REMEDY = `name the issues it waits on as "${BLOCKED_BY_FIELD}: #24 #26", or take the line out`;
13084
+ function nameIds(ids) {
13085
+ return ids.map((id2) => `#${String(id2)}`).join(" ");
13086
+ }
13087
+ function faultClause(read) {
13088
+ if (read.kind === "no-ids" && read.foreign.length > 0) {
13089
+ return `names only issues on other repositories: ${read.foreign.join(" ")}`;
13090
+ }
13091
+ if (read.kind === "no-ids")
13092
+ return `names no issue: "${read.text ?? ""}"`;
13093
+ if (read.kind === "self-reference") {
13094
+ return `names #${String(read.issue)}, the number this issue would be filed as`;
13095
+ }
13096
+ return `names ${nameIds(read.unknown)}, which the board has no issue for`;
13097
+ }
13098
+ function specLineFault(read) {
13099
+ if (read.kind === "blocked" || read.kind === "no-line") {
13100
+ throw new TypeError(`issue create: a ${read.kind} reading of the "${BLOCKED_BY_FIELD}:" line has no fault to name`);
13101
+ }
13102
+ return `The spec's "${BLOCKED_BY_FIELD}:" line, line ${String(read.line ?? 0)} of the body, ` + `${faultClause(read)}: ${REMEDY}`;
13103
+ }
13104
+ function faultRefusal(read) {
13105
+ return new CommandExit(1, `\u274C ${specLineFault(read)}`);
13106
+ }
13107
+ function readSpecLine(draft) {
13108
+ if (draft.type !== "spec")
13109
+ return null;
13110
+ const read = readBlockedBy(UNFILED, draft.body);
13111
+ if (read.kind === "no-line")
13112
+ return null;
13113
+ if (read.kind === "no-ids")
13114
+ throw faultRefusal(read);
13115
+ return read;
13116
+ }
13117
+ function issueNumbers(ids) {
13118
+ const numbers = [];
13119
+ for (const id2 of ids) {
13120
+ if (!/^[1-9]\d*$/u.test(id2))
13121
+ return null;
13122
+ const number = Number(id2);
13123
+ if (!Number.isSafeInteger(number))
13124
+ return null;
13125
+ numbers.push(number);
13126
+ }
13127
+ return numbers;
13128
+ }
13129
+ function uncheckedNumbers(why) {
13130
+ return { known: undefined, next: UNFILED, unchecked: `${why}, so no "${BLOCKED_BY_FIELD}:" id was checked against it` };
13131
+ }
13132
+ async function readBoardNumbers(tracker) {
13133
+ const refs2 = await onTracker(tracker, "list the board's issues", () => tracker.find({ limit: KNOWN_LIST_LIMIT }));
13134
+ if (refs2.length >= KNOWN_LIST_LIMIT) {
13135
+ return uncheckedNumbers(`the ${tracker.kind} tracker answered the ${String(KNOWN_LIST_LIMIT)} issues the listing asked for and may hold more`);
13136
+ }
13137
+ const numbers = issueNumbers(refs2.map((ref8) => ref8.externalId));
13138
+ if (numbers === null) {
13139
+ return uncheckedNumbers(`the ${tracker.kind} tracker answered an issue id that is not an issue number`);
13140
+ }
13141
+ return { known: new Set(numbers), next: Math.max(0, ...numbers) + 1, unchecked: null };
13142
+ }
13143
+ async function settleSpecLine(draft, tracker) {
13144
+ const board = await readBoardNumbers(tracker);
13145
+ const read = readBlockedBy(board.next, draft.body, board.known);
13146
+ if (read.kind !== "blocked")
13147
+ throw faultRefusal(read);
13148
+ return { draft: { ...draft, specBlocked: true }, unchecked: board.unchecked };
13149
+ }
13150
+
13062
13151
  // src/commands/issue/create.ts
13063
- var USAGE22 = "rafa issue create --title=<text> [--body=<text>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
13152
+ var USAGE22 = "rafa issue create --title=<text> [--body=<text> | --body-file=<path>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
13153
+ var STDIN_PATH = "-";
13064
13154
  var DEFAULT_ISSUE_TYPE = "code";
13155
+ function readBodyFileFlag(flags) {
13156
+ const path = readNonBlankFlag(flags, "body-file", USAGE22);
13157
+ if (path === undefined || flags.body === undefined)
13158
+ return path;
13159
+ throw lineRefusal("--body and --body-file cannot be used together: name the body one way", USAGE22);
13160
+ }
13065
13161
  function readIssueDraft(flags) {
13162
+ readBodyFileFlag(flags);
13066
13163
  return {
13067
13164
  opt: 0,
13068
13165
  title: readRequiredFlag(flags, "title", USAGE22),
@@ -13074,23 +13171,45 @@ function readIssueDraft(flags) {
13074
13171
  blockedBy: []
13075
13172
  };
13076
13173
  }
13174
+ async function readBodyFile(path, seams) {
13175
+ const read = path === STDIN_PATH ? seams.stdin ?? (() => Bun.stdin.text()) : () => Bun.file(path).text();
13176
+ try {
13177
+ return await read();
13178
+ } catch (error) {
13179
+ const where = path === STDIN_PATH ? "standard input" : `"${path}"`;
13180
+ throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`, USAGE22);
13181
+ }
13182
+ }
13183
+ async function readIssueLine(flags, seams) {
13184
+ const draft = readIssueDraft(flags);
13185
+ const path = readBodyFileFlag(flags);
13186
+ return path === undefined ? draft : { ...draft, body: await readBodyFile(path, seams) };
13187
+ }
13077
13188
  function renderCreated(ref8) {
13078
13189
  return [`Created ${issueName(ref8)}.`, ...urlLines(ref8)];
13079
13190
  }
13080
13191
  async function createIssue(context, seams) {
13081
13192
  expectNoArgument(context.args, USAGE22);
13082
- const draft = readIssueDraft(context.flags);
13193
+ const read = await readIssueLine(context.flags, seams);
13194
+ const blockedLine = readSpecLine(read);
13083
13195
  const { tracker, data } = await resolveIssueTracker(context, seams);
13196
+ const draft = blockedLine === null ? read : await settledDraft(read, tracker, context);
13084
13197
  const ref8 = await onTracker(tracker, "create the issue", () => tracker.create(draft));
13085
13198
  return { tracker: data, ref: ref8 };
13086
13199
  }
13200
+ async function settledDraft(read, tracker, context) {
13201
+ const settled = await settleSpecLine(read, tracker);
13202
+ if (settled.unchecked !== null)
13203
+ context.output.warn(`issue create: ${settled.unchecked}`);
13204
+ return settled.draft;
13205
+ }
13087
13206
  function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13088
13207
  const command = {
13089
13208
  name: "issue create",
13090
13209
  subject: "issue",
13091
13210
  action: "create",
13092
13211
  summary: "create an issue on the tracker from a title and a body",
13093
- description: "Files one issue on the tracker `tracker.default` names in `.rafa/config.yaml`, or on the first" + " `tracker.fallback` kind whose preflight passes when it fails, each kind passed over warned about." + " `--title` is required. The issue is of type `code` and module `unassigned` unless `--type` and" + " `--module` say otherwise, and with no `--priority` the tracker marks it needs-triage. Prints the" + " tracker and id of the issue filed, and its URL when there is one. With `--output=json` the tracker" + " and the ref are the data of the terminal result event.",
13212
+ description: "Files one issue on the tracker `tracker.default` names in `.rafa/config.yaml`, or on the first" + " `tracker.fallback` kind whose preflight passes when it fails, each kind passed over warned about." + " `--title` is required. The issue is of type `code` and module `unassigned` unless `--type` and" + " `--module` say otherwise, and with no `--priority` the tracker marks it needs-triage. The body is" + " `--body`, or what the file `--body-file` names holds, standard input for `-`. A spec whose body" + " carries a `Blocked by: #24` line is filed blocked, and refused when the line names no issue, the" + " issue itself or an issue the board does not hold. Prints the" + " tracker and id of the issue filed, and its URL when there is one. With `--output=json` the tracker" + " and the ref are the data of the terminal result event.",
13094
13213
  args: [],
13095
13214
  flags: [
13096
13215
  {
@@ -13101,7 +13220,12 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13101
13220
  },
13102
13221
  {
13103
13222
  name: "body",
13104
- description: "The body of the issue, as markdown. Empty when left out.",
13223
+ description: "The body of the issue, as markdown. Empty when left out. Not with --body-file.",
13224
+ type: "string"
13225
+ },
13226
+ {
13227
+ name: "body-file",
13228
+ description: "A file holding the body of the issue, read whole; `-` reads standard input. Not with --body.",
13105
13229
  type: "string"
13106
13230
  },
13107
13231
  {
@@ -13130,6 +13254,10 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13130
13254
  {
13131
13255
  cmd: 'rafa issue create --title="Document issue move" --priority=low --output=json',
13132
13256
  note: "Writes a start event, then a result event whose data holds the tracker and the ref of the issue."
13257
+ },
13258
+ {
13259
+ cmd: 'rafa issue create --title="Faster plan show" --body-file=spec.md',
13260
+ note: "Files an issue whose body is what spec.md holds; --body-file=- reads it from standard input."
13133
13261
  }
13134
13262
  ],
13135
13263
  outputs: ["text", "json"],
@@ -13752,8 +13880,457 @@ function createLoopStopCommand(seams = {}) {
13752
13880
  }
13753
13881
  var stop_default = createLoopStopCommand();
13754
13882
 
13883
+ // src/commands/loop/wait.ts
13884
+ import { relative as relative7 } from "path";
13885
+
13886
+ // src/loop/awake-clock.ts
13887
+ var NS_PER_MINUTE = 60000000000n;
13888
+ var MS_PER_MINUTE = 60000;
13889
+ var SUSPEND_REPORT_MINUTES = 1;
13890
+ var SYSTEM_CLOCK_SOURCE = Object.freeze({
13891
+ monotonicNs: () => process.hrtime.bigint(),
13892
+ wallMs: () => Date.now()
13893
+ });
13894
+ function suspendedMinutesOf(wallMinutes, awakeMinutes) {
13895
+ const gap = wallMinutes - awakeMinutes;
13896
+ return gap > SUSPEND_REPORT_MINUTES ? gap : 0;
13897
+ }
13898
+ function createAwakeClock(source = SYSTEM_CLOCK_SOURCE) {
13899
+ const mark = () => Object.freeze({
13900
+ monotonicNs: source.monotonicNs(),
13901
+ wallMs: source.wallMs()
13902
+ });
13903
+ const since = (from) => {
13904
+ const monotonicNs = source.monotonicNs() - from.monotonicNs;
13905
+ const awakeMinutes = monotonicNs > 0n ? Number(monotonicNs) / Number(NS_PER_MINUTE) : 0;
13906
+ const wallMinutes = (source.wallMs() - from.wallMs) / MS_PER_MINUTE;
13907
+ return Object.freeze({
13908
+ awakeMinutes,
13909
+ suspendedMinutes: suspendedMinutesOf(wallMinutes, awakeMinutes)
13910
+ });
13911
+ };
13912
+ return Object.freeze({ mark, since });
13913
+ }
13914
+
13915
+ // src/loop/events-file.ts
13916
+ import { closeSync, fstatSync, openSync, readSync } from "fs";
13917
+ var NEWLINE = 10;
13918
+ function eventsFileOf(root, record) {
13919
+ return eventsFilePath(root, record.sessionId);
13920
+ }
13921
+ function isPlainObject(value) {
13922
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13923
+ }
13924
+ function parseEventLine(line) {
13925
+ let value;
13926
+ try {
13927
+ value = JSON.parse(line);
13928
+ } catch {
13929
+ return null;
13930
+ }
13931
+ if (!isPlainObject(value))
13932
+ return null;
13933
+ const { name, summary, data, ts } = value;
13934
+ if (typeof name !== "string" || typeof summary !== "string" || typeof ts !== "string")
13935
+ return null;
13936
+ if (!isPlainObject(data))
13937
+ return null;
13938
+ return Object.freeze({ name, summary, data: Object.freeze({ ...data }), ts });
13939
+ }
13940
+ function bytesFrom(fd, offset) {
13941
+ const length = fstatSync(fd).size - offset;
13942
+ if (length <= 0)
13943
+ return null;
13944
+ const buffer = Buffer.alloc(length);
13945
+ let filled = 0;
13946
+ while (filled < length) {
13947
+ const read = readSync(fd, buffer, filled, length - filled, offset + filled);
13948
+ if (read === 0)
13949
+ break;
13950
+ filled += read;
13951
+ }
13952
+ return buffer.subarray(0, filled);
13953
+ }
13954
+ function readTail(file, offset) {
13955
+ let fd;
13956
+ try {
13957
+ fd = openSync(file, "r");
13958
+ } catch (error) {
13959
+ if (errorCode(error) === "ENOENT")
13960
+ return "absent";
13961
+ throw error;
13962
+ }
13963
+ try {
13964
+ return bytesFrom(fd, offset);
13965
+ } finally {
13966
+ closeSync(fd);
13967
+ }
13968
+ }
13969
+ function readEventsFrom(file, offset) {
13970
+ if (!Number.isSafeInteger(offset) || offset < 0) {
13971
+ throw new RangeError(`events file: unusable offset ${String(offset)}`);
13972
+ }
13973
+ const tail = readTail(file, offset);
13974
+ if (tail === "absent")
13975
+ return Object.freeze({ kind: "absent" });
13976
+ const end = tail?.lastIndexOf(NEWLINE) ?? -1;
13977
+ if (tail === null || end < 0) {
13978
+ return Object.freeze({ kind: "read", events: Object.freeze([]), malformed: 0, offset });
13979
+ }
13980
+ const lines = tail.subarray(0, end).toString("utf8").split(`
13981
+ `);
13982
+ const parsed = lines.map(parseEventLine);
13983
+ const events = parsed.filter((event) => event !== null);
13984
+ return Object.freeze({
13985
+ kind: "read",
13986
+ events: Object.freeze(events),
13987
+ malformed: parsed.length - events.length,
13988
+ offset: offset + end + 1
13989
+ });
13990
+ }
13991
+
13992
+ // src/loop/wait-reasons.ts
13993
+ var WAIT_REASONS = Object.freeze([
13994
+ Object.freeze({ reason: "pr", event: "pr", matches: "event pr", exit: 0 }),
13995
+ Object.freeze({ reason: "no-pr", event: "no-pr", matches: "event no-pr", exit: 10 }),
13996
+ Object.freeze({ reason: "halt", event: "halt", matches: "event halt", exit: 11 }),
13997
+ Object.freeze({ reason: "error", event: "error", matches: "event error", exit: 12 }),
13998
+ Object.freeze({ reason: "blocked", event: "task-blocked", matches: "event task-blocked", exit: 13 }),
13999
+ Object.freeze({
14000
+ reason: "exit",
14001
+ event: null,
14002
+ matches: "the run's pid not alive, or its record reading stopped or done",
14003
+ exit: 14
14004
+ }),
14005
+ Object.freeze({ reason: "quiet", event: null, matches: "no event for <minutes> awake minutes", exit: 15 })
14006
+ ]);
14007
+ var WAIT_TIMEOUT_EXIT = 16;
14008
+ var WAIT_NO_SESSION_EXIT = 2;
14009
+ var QUIET_PREFIX = "quiet:";
14010
+ var POSITIVE_WHOLE = /^[1-9]\d*$/;
14011
+ var DEFAULT_WAIT_UNTIL = Object.freeze({
14012
+ reasons: Object.freeze(["pr", "no-pr", "halt", "error", "exit"]),
14013
+ quietMinutes: null
14014
+ });
14015
+
14016
+ class WaitUntilError extends Error {
14017
+ entry;
14018
+ constructor(entry, why) {
14019
+ super(`--until: ${why}: '${entry}'`);
14020
+ this.name = "WaitUntilError";
14021
+ this.entry = entry;
14022
+ }
14023
+ }
14024
+ function rowOf2(reason) {
14025
+ const row2 = WAIT_REASONS.find((candidate) => candidate.reason === reason);
14026
+ if (row2 === undefined)
14027
+ throw new Error(`wait reasons: no row for ${reason}`);
14028
+ return row2;
14029
+ }
14030
+ function waitExitCode(reason) {
14031
+ return rowOf2(reason).exit;
14032
+ }
14033
+ function isPlainReason(entry) {
14034
+ return WAIT_REASONS.some((row2) => row2.reason === entry && row2.reason !== "quiet");
14035
+ }
14036
+ function quietMinutesOf(entry, written) {
14037
+ const minutes = entry.slice(QUIET_PREFIX.length);
14038
+ const value = Number(minutes);
14039
+ if (!POSITIVE_WHOLE.test(minutes) || !Number.isSafeInteger(value)) {
14040
+ throw new WaitUntilError(written, "quiet takes a positive whole number of minutes");
14041
+ }
14042
+ return value;
14043
+ }
14044
+ function parseWaitUntil(raw) {
14045
+ if (raw === undefined)
14046
+ return DEFAULT_WAIT_UNTIL;
14047
+ const asked = new Set;
14048
+ let quietMinutes = null;
14049
+ for (const written of raw.split(",")) {
14050
+ const entry = written.trim();
14051
+ if (entry === "")
14052
+ throw new WaitUntilError(written, "empty entry");
14053
+ if (isPlainReason(entry)) {
14054
+ asked.add(entry);
14055
+ continue;
14056
+ }
14057
+ if (entry === "quiet")
14058
+ throw new WaitUntilError(written, "quiet needs minutes, as quiet:<minutes>");
14059
+ if (!entry.startsWith(QUIET_PREFIX))
14060
+ throw new WaitUntilError(written, "unknown reason");
14061
+ if (quietMinutes !== null)
14062
+ throw new WaitUntilError(written, "quiet asked for twice");
14063
+ quietMinutes = quietMinutesOf(entry, written);
14064
+ asked.add("quiet");
14065
+ }
14066
+ const reasons = WAIT_REASONS.map((row2) => row2.reason).filter((reason) => asked.has(reason));
14067
+ return Object.freeze({ reasons: Object.freeze(reasons), quietMinutes });
14068
+ }
14069
+ function matchEvent(event, until) {
14070
+ const row2 = WAIT_REASONS.find((candidate) => candidate.event === event.name);
14071
+ if (row2 === undefined || !until.reasons.includes(row2.reason))
14072
+ return null;
14073
+ return row2.reason;
14074
+ }
14075
+ function matchRecord(reading, until) {
14076
+ if (!until.reasons.includes("exit"))
14077
+ return null;
14078
+ const ended = !reading.pidAlive || reading.state === "stopped" || reading.state === "done";
14079
+ return ended ? "exit" : null;
14080
+ }
14081
+ function matchQuiet(awakeMinutes, until) {
14082
+ if (until.quietMinutes === null)
14083
+ return null;
14084
+ return awakeMinutes >= until.quietMinutes ? "quiet" : null;
14085
+ }
14086
+
14087
+ // src/commands/loop/wait.ts
14088
+ var USAGE30 = "rafa loop wait [-s|--session-id=<id>] [--until=<reasons>] [--timeout=<minutes>]";
14089
+ var WAIT_POLL_MS = 5000;
14090
+ var UNTIL_FLAG = "until";
14091
+ var TIMEOUT_FLAG = "timeout";
14092
+ var WAIT_EVENT = "wait";
14093
+ function resolveWaitSeams(seams) {
14094
+ const base = resolveLoopSeams();
14095
+ return {
14096
+ readBranch: seams.readBranch ?? base.readBranch,
14097
+ isAlive: seams.isAlive ?? base.isAlive,
14098
+ sleep: seams.sleep ?? base.sleep,
14099
+ readRecord: seams.readRecord ?? ((root, sessionId, isAlive) => readSession(root, sessionId, { isAlive })),
14100
+ clock: seams.clock ?? createAwakeClock(),
14101
+ pollMs: seams.pollMs ?? WAIT_POLL_MS
14102
+ };
14103
+ }
14104
+ function readUntil(context) {
14105
+ const value = context.flags[UNTIL_FLAG];
14106
+ if (value === undefined)
14107
+ return DEFAULT_WAIT_UNTIL;
14108
+ if (typeof value !== "string")
14109
+ throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`, USAGE30);
14110
+ try {
14111
+ return parseWaitUntil(value);
14112
+ } catch (error) {
14113
+ if (error instanceof WaitUntilError)
14114
+ throw lineRefusal2(error.message, USAGE30);
14115
+ throw error;
14116
+ }
14117
+ }
14118
+ function readTimeout(context) {
14119
+ const value = context.flags[TIMEOUT_FLAG];
14120
+ if (value === undefined)
14121
+ return null;
14122
+ const minutes = typeof value === "string" ? Number(value) : Number.NaN;
14123
+ if (typeof value !== "string" || !/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(minutes)) {
14124
+ const spelled = typeof value === "string" ? JSON.stringify(value) : `--${TIMEOUT_FLAG} with no value`;
14125
+ throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`, USAGE30);
14126
+ }
14127
+ return minutes;
14128
+ }
14129
+ function suspendedNote(reading) {
14130
+ const minutes = Math.round(reading.suspendedMinutes);
14131
+ return minutes > 0 ? `, ${plural(minutes, "minute")} more suspended` : "";
14132
+ }
14133
+ function ownLine(kind, text) {
14134
+ return `${EVENT_PREFIX}${padKind(kind)}${text}`;
14135
+ }
14136
+ function eventLine(event) {
14137
+ return `${EVENT_PREFIX}${oneLine(event.summary)}`;
14138
+ }
14139
+ function exitLine(session) {
14140
+ const pid = session.pidAlive ? "alive" : "not alive";
14141
+ return ownLine("exit", `session ${session.sessionId} ${session.state}, pid ${String(session.pid)} ${pid}`);
14142
+ }
14143
+ function quietLine(minutes, reading) {
14144
+ return ownLine("quiet", `no event for ${plural(minutes, "awake minute")}${suspendedNote(reading)}`);
14145
+ }
14146
+ function timeoutLine(minutes, reading) {
14147
+ return ownLine("timeout", `no reason asked for in ${plural(minutes, "awake minute")}${suspendedNote(reading)}`);
14148
+ }
14149
+ function noEventsNote(following) {
14150
+ const path = relative7(following.root, following.file);
14151
+ return `Session ${following.record.sessionId} has no events file at ${path}: a run from an older rafa writes none,` + " and no event can answer until one appears.";
14152
+ }
14153
+ function firstAsked(events, until) {
14154
+ for (const event of events) {
14155
+ const reason = matchEvent(event, until);
14156
+ if (reason !== null)
14157
+ return { reason, event };
14158
+ }
14159
+ return null;
14160
+ }
14161
+ function readNewEvents(following, cursor, context, seams) {
14162
+ const read = readEventsFrom(following.file, cursor.offset);
14163
+ if (read.kind === "absent") {
14164
+ if (!cursor.noted)
14165
+ context.output.warn(noEventsNote(following));
14166
+ return { cursor: { ...cursor, noted: true }, found: null };
14167
+ }
14168
+ const moved = {
14169
+ ...cursor,
14170
+ offset: read.offset,
14171
+ quietMark: read.events.length > 0 ? seams.clock.mark() : cursor.quietMark
14172
+ };
14173
+ return { cursor: moved, found: firstAsked(read.events, following.until) };
14174
+ }
14175
+ function readRecordOrRefuse(following, seams) {
14176
+ const { sessionId } = following.record;
14177
+ try {
14178
+ return seams.readRecord(following.root, sessionId, seams.isAlive);
14179
+ } catch (error) {
14180
+ if (!(error instanceof SessionRecordError))
14181
+ throw error;
14182
+ throw refusal(`Session ${sessionId}: its record cannot be read any more.`, messageOf(error));
14183
+ }
14184
+ }
14185
+ function poll(following, before, context, seams) {
14186
+ const fromEvents = readNewEvents(following, before, context, seams);
14187
+ if (fromEvents.found !== null)
14188
+ return fromEvents;
14189
+ const { cursor } = fromEvents;
14190
+ const record = readRecordOrRefuse(following, seams);
14191
+ if (matchRecord({ state: record.state, pidAlive: seams.isAlive(record.pid) }, following.until) !== null) {
14192
+ const last = readNewEvents(following, cursor, context, seams);
14193
+ return { cursor: last.cursor, found: last.found ?? { reason: "exit", event: null } };
14194
+ }
14195
+ if (matchQuiet(seams.clock.since(cursor.quietMark).awakeMinutes, following.until) !== null) {
14196
+ return { cursor, found: { reason: "quiet", event: null } };
14197
+ }
14198
+ const { timeout } = following;
14199
+ if (timeout !== null && seams.clock.since(following.start).awakeMinutes >= timeout) {
14200
+ return { cursor, found: { reason: "timeout", event: null } };
14201
+ }
14202
+ return { cursor, found: null };
14203
+ }
14204
+ function lineOf(found, following, session, reading) {
14205
+ if (found.event !== null)
14206
+ return eventLine(found.event);
14207
+ if (found.reason === "exit")
14208
+ return exitLine(session);
14209
+ if (found.reason === "quiet")
14210
+ return quietLine(following.until.quietMinutes ?? 0, reading);
14211
+ return timeoutLine(following.timeout ?? 0, reading);
14212
+ }
14213
+ function resultOf5(found, following, seams) {
14214
+ const record = readRecordOrRefuse(following, seams);
14215
+ const session = {
14216
+ sessionId: record.sessionId,
14217
+ state: record.state,
14218
+ pid: record.pid,
14219
+ pidAlive: seams.isAlive(record.pid)
14220
+ };
14221
+ const reading = seams.clock.since(following.start);
14222
+ return {
14223
+ reason: found.reason,
14224
+ exitCode: found.reason === "timeout" ? WAIT_TIMEOUT_EXIT : waitExitCode(found.reason),
14225
+ line: lineOf(found, following, session, reading),
14226
+ event: found.event,
14227
+ session,
14228
+ awakeMinutes: reading.awakeMinutes,
14229
+ suspendedMinutes: reading.suspendedMinutes
14230
+ };
14231
+ }
14232
+ function pickOrNoSession(context, seams) {
14233
+ try {
14234
+ return pickSession(context, USAGE30, "live-or-newest", {
14235
+ ...resolveLoopSeams(),
14236
+ readBranch: seams.readBranch,
14237
+ isAlive: seams.isAlive
14238
+ });
14239
+ } catch (error) {
14240
+ if (error instanceof NoSessionRefusal)
14241
+ throw new CommandExit(WAIT_NO_SESSION_EXIT, error.message);
14242
+ throw error;
14243
+ }
14244
+ }
14245
+ async function runLoopWait(context, seams = {}) {
14246
+ expectNoArgument(context.args, USAGE30);
14247
+ const until = readUntil(context);
14248
+ const timeout = readTimeout(context);
14249
+ const resolved = resolveWaitSeams(seams);
14250
+ const { root, record } = pickOrNoSession(context, resolved);
14251
+ const start2 = resolved.clock.mark();
14252
+ const following = { root, record, file: eventsFileOf(root, record), until, timeout, start: start2 };
14253
+ let cursor = { offset: 0, quietMark: start2, noted: false };
14254
+ for (;; ) {
14255
+ const polled = poll(following, cursor, context, resolved);
14256
+ if (polled.found !== null)
14257
+ return resultOf5(polled.found, following, resolved);
14258
+ cursor = polled.cursor;
14259
+ await resolved.sleep(resolved.pollMs);
14260
+ }
14261
+ }
14262
+ function spelled(row2) {
14263
+ return row2.reason === "quiet" ? "quiet:<minutes>" : row2.reason;
14264
+ }
14265
+ function reasonsText() {
14266
+ return WAIT_REASONS.map((row2) => `\`${spelled(row2)}\`, ${row2.matches}, exits ${String(row2.exit)}`).join("; ");
14267
+ }
14268
+ function description() {
14269
+ return "Follows a loop run until a reason `--until` asks for happens, then prints one `rafa\xB7` line naming it" + " and exits with that reason's code. It reads the run's events file, `.rafa/runs/<session-id>.events.ndjson`," + " from its first line, so a run that has already ended still answers, and reads the run's record and" + ` probes its pid every ${String(WAIT_POLL_MS / 1000)} seconds. The reasons: ${reasonsText()}.` + ` \`--timeout\` reached exits ${String(WAIT_TIMEOUT_EXIT)}, and a session it cannot find exits` + ` ${String(WAIT_NO_SESSION_EXIT)}. Without \`--until\` it waits for \`${DEFAULT_WAIT_UNTIL.reasons.join(",")}\`;` + " `blocked` and `quiet` are never in that set. Quiet minutes and the timeout count time the machine is" + " awake, so a suspended laptop never reads as a stuck loop; on macOS the clock keeps counting through" + " sleep, so there a long sleep can raise a false `quiet` or an early timeout. A run with no events file," + " from an older rafa, is noted once, and only `exit`, `quiet` and the timeout can answer for it. Without" + " `--session-id` it waits on the session running on the branch checked out at the project root, or the" + " newest one that ran there. With `--output=json` the reason, the event, the session's state and the" + ` minutes are the data of a \`${WAIT_EVENT}\` event, and of the terminal result when it exits 0.`;
14270
+ }
14271
+ function createLoopWaitCommand(seams = {}) {
14272
+ const command = {
14273
+ name: "loop wait",
14274
+ subject: "loop",
14275
+ action: "wait",
14276
+ summary: "wait on a session until a reason --until names happens",
14277
+ description: description(),
14278
+ args: [],
14279
+ flags: [
14280
+ sessionIdFlag("to wait on"),
14281
+ {
14282
+ name: UNTIL_FLAG,
14283
+ description: `The reasons to wait for, a comma list of ${WAIT_REASONS.map((row2) => spelled(row2)).join(", ")}.` + ` \`${DEFAULT_WAIT_UNTIL.reasons.join(",")}\` when it is left out.`,
14284
+ type: "string"
14285
+ },
14286
+ {
14287
+ name: TIMEOUT_FLAG,
14288
+ description: `How many awake minutes to wait before giving up with exit code ${String(WAIT_TIMEOUT_EXIT)}.` + " No limit when it is left out.",
14289
+ type: "string"
14290
+ }
14291
+ ],
14292
+ examples: [
14293
+ {
14294
+ cmd: "rafa loop wait",
14295
+ note: "Waits on the session of the branch checked out here until it opens a PR, ends without one, halts," + " fails or exits."
14296
+ },
14297
+ {
14298
+ cmd: "rafa loop wait --session-id=9185b41c-65f7-4dd6-a0c1-6494c4028f0f --until=blocked,exit,quiet:30",
14299
+ note: "Wakes on a blocked task, the run's end, or 30 awake minutes with no event."
14300
+ },
14301
+ {
14302
+ cmd: "rafa loop wait --timeout=60 --output=json",
14303
+ note: "Waits up to an hour awake, then writes the reason, the event and the session's state as a wait event."
14304
+ }
14305
+ ],
14306
+ outputs: ["text", "json"],
14307
+ run: async (context) => {
14308
+ const result2 = await runLoopWait(context, seams);
14309
+ if (context.outputMode === "json") {
14310
+ context.output.emit({
14311
+ type: "event",
14312
+ name: WAIT_EVENT,
14313
+ summary: result2.line.slice(EVENT_PREFIX.length),
14314
+ data: result2,
14315
+ ts: new Date().toISOString()
14316
+ });
14317
+ if (result2.exitCode === 0)
14318
+ context.output.result(result2);
14319
+ else
14320
+ throw new CommandExit(result2.exitCode, result2.line);
14321
+ return;
14322
+ }
14323
+ context.output.info(result2.line);
14324
+ if (result2.exitCode !== 0)
14325
+ throw new CommandExit(result2.exitCode);
14326
+ }
14327
+ };
14328
+ return Object.freeze(command);
14329
+ }
14330
+ var wait_default = createLoopWaitCommand();
14331
+
13755
14332
  // src/commands/module/exec.ts
13756
- var USAGE30 = "rafa module exec <module> <action> [args] [flags]";
14333
+ var USAGE31 = "rafa module exec <module> <action> [args] [flags]";
13757
14334
  var execCommand = {
13758
14335
  name: "module exec",
13759
14336
  subject: "module",
@@ -13782,13 +14359,13 @@ var execCommand = {
13782
14359
  const mounts = context.registry.mounts();
13783
14360
  const mounted = mounts.length === 0 ? "none is mounted; a module is mounted when allowList: names it and modules: gives its path source" : mounts.map((mount) => `${mount.name} (${context.registry.actionsOf(mountKey(mount.name)).map((held) => held.action).join(", ")})`).join(", ");
13784
14361
  throw new CommandExit(1, `\u274C Expected a module and one of its actions; mounted: ${mounted}
13785
- Usage: ${USAGE30}`);
14362
+ Usage: ${USAGE31}`);
13786
14363
  }
13787
14364
  };
13788
14365
  var exec_default = Object.freeze(execCommand);
13789
14366
 
13790
14367
  // src/commands/module/list.ts
13791
- var USAGE31 = "rafa module list";
14368
+ var USAGE32 = "rafa module list";
13792
14369
  function sourceText(source) {
13793
14370
  const ref8 = source.ref === null ? "" : `@${source.ref}`;
13794
14371
  return `${source.kind} ${source.location}${ref8}`;
@@ -13809,10 +14386,10 @@ function renderModuleList(list) {
13809
14386
  return ["Modules:", ...list.modules.flatMap(moduleLines)];
13810
14387
  }
13811
14388
  async function runList6(context, seams) {
13812
- expectNoArgument(context.args, USAGE31);
14389
+ expectNoArgument(context.args, USAGE32);
13813
14390
  const { project } = context;
13814
14391
  if (project === null)
13815
- throw new CommandExit(1, `\u274C ${USAGE31} runs inside a project`);
14392
+ throw new CommandExit(1, `\u274C ${USAGE32} runs inside a project`);
13816
14393
  let loaded;
13817
14394
  try {
13818
14395
  const resolved = loadConfig({ root: project.root, home: project.home });
@@ -13903,30 +14480,30 @@ function yesActionsFor(mode) {
13903
14480
  function bareYesActionsFor(mode) {
13904
14481
  return BARE_YES_ACTIONS_BY_MODE[mode];
13905
14482
  }
13906
- function ceilingRefusal(problem2, usage2) {
14483
+ function ceilingRefusal(problem2, usage) {
13907
14484
  return new CommandExit(CEILING_REFUSAL_EXIT, `\u274C ${problem2}
13908
- Usage: ${usage2}`);
14485
+ Usage: ${usage}`);
13909
14486
  }
13910
- function readCeilingId(word, usage2, mode) {
14487
+ function readCeilingId(word, usage, mode) {
13911
14488
  if (isAlwaysAsked(word)) {
13912
- throw ceilingRefusal(`--${YES_FLAG} names ${word}, which no list runs unasked: ${ALWAYS_ASKED_WHY[word]}`, usage2);
14489
+ throw ceilingRefusal(`--${YES_FLAG} names ${word}, which no list runs unasked: ${ALWAYS_ASKED_WHY[word]}`, usage);
13913
14490
  }
13914
14491
  const ids = yesActionsFor(mode);
13915
14492
  if (mode === "native" && isLabelsOnly(word)) {
13916
- throw ceilingRefusal(`--${YES_FLAG} names ${word}, which is offered in labels mode only and board.relationships is native:` + ` the tracker clears a blocker when it closes; the ids are ${ids.join(", ")}`, usage2);
14493
+ throw ceilingRefusal(`--${YES_FLAG} names ${word}, which is offered in labels mode only and board.relationships is native:` + ` the tracker clears a blocker when it closes; the ids are ${ids.join(", ")}`, usage);
13917
14494
  }
13918
14495
  const named = ids.find((action) => action === word);
13919
14496
  if (named !== undefined)
13920
14497
  return named;
13921
- throw ceilingRefusal(`--${YES_FLAG} names "${word}", which is no step of rafa next;` + ` the ids are ${ids.join(", ")}`, usage2);
14498
+ throw ceilingRefusal(`--${YES_FLAG} names "${word}", which is no step of rafa next;` + ` the ids are ${ids.join(", ")}`, usage);
13922
14499
  }
13923
- function readYesCeiling(flags, usage2, mode = "labels") {
14500
+ function readYesCeiling(flags, usage, mode = "labels") {
13924
14501
  const value = flags[YES_FLAG];
13925
14502
  if (value === undefined || value === false)
13926
14503
  return null;
13927
14504
  if (value === true)
13928
14505
  return bareYesActionsFor(mode);
13929
- return Object.freeze(value.split(",").map((word) => word.trim()).filter((word) => word !== "").map((word) => readCeilingId(word, usage2, mode)));
14506
+ return Object.freeze(value.split(",").map((word) => word.trim()).filter((word) => word !== "").map((word) => readCeilingId(word, usage, mode)));
13930
14507
  }
13931
14508
  function allowedUnasked(action, ceiling) {
13932
14509
  if (ceiling === null || ALWAYS_ASKED.has(action))
@@ -13986,7 +14563,7 @@ function stopLine(stop, state, ceiling, dryRun, roadmap = false) {
13986
14563
  }
13987
14564
  return null;
13988
14565
  }
13989
- function lineRefusal3(problem2) {
14566
+ function lineRefusal4(problem2) {
13990
14567
  return new CommandExit(1, `\u274C ${problem2}
13991
14568
  Usage: ${NEXT_USAGE}`);
13992
14569
  }
@@ -13999,7 +14576,7 @@ function readRoadmap(flags) {
13999
14576
  function readClaimAhead(flags, roadmap) {
14000
14577
  const claimAhead = readBareFlag(flags, CLAIM_AHEAD_FLAG);
14001
14578
  if (claimAhead && !roadmap) {
14002
- throw lineRefusal3(`--${CLAIM_AHEAD_FLAG} is passed on to plan create with --${ROADMAP_FLAG}, and this line gives no` + ` --${ROADMAP_FLAG}; write --${ROADMAP_FLAG} --${CLAIM_AHEAD_FLAG}, or drop --${CLAIM_AHEAD_FLAG}`);
14579
+ throw lineRefusal4(`--${CLAIM_AHEAD_FLAG} is passed on to plan create with --${ROADMAP_FLAG}, and this line gives no` + ` --${ROADMAP_FLAG}; write --${ROADMAP_FLAG} --${CLAIM_AHEAD_FLAG}, or drop --${CLAIM_AHEAD_FLAG}`);
14003
14580
  }
14004
14581
  return claimAhead;
14005
14582
  }
@@ -14011,7 +14588,7 @@ function readBareFlag(flags, name) {
14011
14588
  return value;
14012
14589
  if (value === "true" || value === "false")
14013
14590
  return value === "true";
14014
- throw lineRefusal3(`--${name} takes no value, and read "${value}" as one`);
14591
+ throw lineRefusal4(`--${name} takes no value, and read "${value}" as one`);
14015
14592
  }
14016
14593
  function dryRunOf(flag, ceiling, hasTerminal) {
14017
14594
  if (flag)
@@ -14416,7 +14993,7 @@ var create_default2 = endingWith(wrapped2);
14416
14993
  import { readFileSync as readFileSync16 } from "fs";
14417
14994
  import { resolve as resolve10 } from "path";
14418
14995
  var COMMAND6 = "rafa plan risk";
14419
- var USAGE32 = "rafa plan risk [<plan>] [--strict]";
14996
+ var USAGE33 = "rafa plan risk [<plan>] [--strict]";
14420
14997
  var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
14421
14998
  function spawningRunners(root) {
14422
14999
  return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
@@ -14429,7 +15006,7 @@ function riskPlanPath(root, planDir, typed) {
14429
15006
  throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
14430
15007
  const lookedFor = [...new Set([resolve10(root, planDir, DEFAULT_PLAN_FILE), resolve10(root, DEFAULT_PLAN_FILE)])];
14431
15008
  throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
14432
- Usage: ${USAGE32}`);
15009
+ Usage: ${USAGE33}`);
14433
15010
  }
14434
15011
  function riskLine(finding) {
14435
15012
  const line = finding.line === undefined ? "" : `:${String(finding.line)}`;
@@ -14493,7 +15070,7 @@ function createPlanRiskCommand(seams = {}) {
14493
15070
  outputs: ["text", "json"],
14494
15071
  run: async (context) => {
14495
15072
  const strict = readSwitch("strict", context.flags["strict"], STRICT_HINT);
14496
- const typed = expectAtMostOneArgument(context.args, USAGE32);
15073
+ const typed = expectAtMostOneArgument(context.args, USAGE33);
14497
15074
  const project = requireProject(context, COMMAND6);
14498
15075
  const config = resolveProjectConfig(project, COMMAND6, (message) => {
14499
15076
  context.output.warn(message);
@@ -14526,7 +15103,7 @@ var risk_default = createPlanRiskCommand();
14526
15103
  // src/commands/plan/show.ts
14527
15104
  import { readFileSync as readFileSync17 } from "fs";
14528
15105
  import { join as join28 } from "path";
14529
- var USAGE33 = "rafa plan show <stub> [--tracker]";
15106
+ var USAGE34 = "rafa plan show <stub> [--tracker]";
14530
15107
  var COMMAND7 = "rafa plan show";
14531
15108
  function readTrackerFlag(value) {
14532
15109
  if (value === undefined || value === false || value === "false")
@@ -14535,8 +15112,8 @@ function readTrackerFlag(value) {
14535
15112
  return true;
14536
15113
  throw new CommandExit(1, `\u274C --tracker takes no value, and read "${value}" as one. Type the stub first: rafa plan show <stub> --tracker`);
14537
15114
  }
14538
- function missingText(stub, tracker, relative7) {
14539
- return tracker ? `\u274C No tracker for plan "${stub}": ${relative7} does not exist until rafa loop start first runs the plan.` : `\u274C No plan "${stub}": ${relative7} does not exist. rafa plan list names the plans there are.`;
15115
+ function missingText(stub, tracker, relative8) {
15116
+ return tracker ? `\u274C No tracker for plan "${stub}": ${relative8} does not exist until rafa loop start first runs the plan.` : `\u274C No plan "${stub}": ${relative8} does not exist. rafa plan list names the plans there are.`;
14540
15117
  }
14541
15118
  function showPlan(plans, stub, tracker) {
14542
15119
  if (!isStampableStub(stub)) {
@@ -14569,9 +15146,9 @@ function taskLine(task) {
14569
15146
  return ` ${checkbox(task.status)} ${task.text}`;
14570
15147
  }
14571
15148
  function renderShownPlan(shown, dirLabel) {
14572
- const relative7 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
15149
+ const relative8 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
14573
15150
  const fileLabel = shown.tracker ? "Tracker" : "File";
14574
- const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
15151
+ const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative8}`];
14575
15152
  if (shown.header.issue !== null)
14576
15153
  lines.push(`Issue: ${shown.header.issue}`);
14577
15154
  if (shown.header.spec !== null)
@@ -14584,7 +15161,7 @@ function renderShownPlan(shown, dirLabel) {
14584
15161
  lines.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
14585
15162
  }
14586
15163
  if (shown.issues.length > 0) {
14587
- lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative7, issue)}`));
15164
+ lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative8, issue)}`));
14588
15165
  }
14589
15166
  return lines;
14590
15167
  }
@@ -14622,7 +15199,7 @@ var planShowCommand = {
14622
15199
  outputs: ["text", "json"],
14623
15200
  run: async (context) => {
14624
15201
  const tracker = readTrackerFlag(context.flags.tracker);
14625
- const stub = expectOneArgument(context.args, USAGE33);
15202
+ const stub = expectOneArgument(context.args, USAGE34);
14626
15203
  const plans = resolvePlansDir(requireProject(context, COMMAND7), COMMAND7, (message) => {
14627
15204
  context.output.warn(message);
14628
15205
  });
@@ -14650,28 +15227,28 @@ var PR_USAGE = Object.freeze({
14650
15227
  });
14651
15228
  var PULL_NUMBER = /^[1-9]\d*$/;
14652
15229
  var DETACHED_HEAD = "HEAD";
14653
- function lineRefusal4(problem2, usage2) {
15230
+ function lineRefusal5(problem2, usage) {
14654
15231
  return new CommandExit(1, `\u274C ${problem2}
14655
- Usage: ${usage2}`);
15232
+ Usage: ${usage}`);
14656
15233
  }
14657
- function expectNoArguments(args, usage2) {
15234
+ function expectNoArguments(args, usage) {
14658
15235
  if (args.length === 0)
14659
15236
  return;
14660
- throw lineRefusal4(`Expected no arguments, got ${args.length}: ${args.join(" ")}`, usage2);
15237
+ throw lineRefusal5(`Expected no arguments, got ${args.length}: ${args.join(" ")}`, usage);
14661
15238
  }
14662
- function readPullArgument(args, usage2) {
15239
+ function readPullArgument(args, usage) {
14663
15240
  if (args.length === 0)
14664
15241
  return null;
14665
15242
  if (args.length > 1) {
14666
- throw lineRefusal4(`Expected at most one pull request number, got ${args.length}: ${args.join(" ")}`, usage2);
15243
+ throw lineRefusal5(`Expected at most one pull request number, got ${args.length}: ${args.join(" ")}`, usage);
14667
15244
  }
14668
15245
  const word = args[0] ?? "";
14669
15246
  if (!PULL_NUMBER.test(word)) {
14670
- throw lineRefusal4(`"${word}" is no pull request number, which is a whole number from 1`, usage2);
15247
+ throw lineRefusal5(`"${word}" is no pull request number, which is a whole number from 1`, usage);
14671
15248
  }
14672
15249
  return Number(word);
14673
15250
  }
14674
- function readBooleanFlag(flags, name, usage2, whenAbsent = false) {
15251
+ function readBooleanFlag(flags, name, usage, whenAbsent = false) {
14675
15252
  const value = flags[name];
14676
15253
  if (value === undefined)
14677
15254
  return whenAbsent;
@@ -14679,7 +15256,7 @@ function readBooleanFlag(flags, name, usage2, whenAbsent = false) {
14679
15256
  return value;
14680
15257
  if (value === "true" || value === "false")
14681
15258
  return value === "true";
14682
- throw lineRefusal4(`--${name} takes no value, and read "${value}" as one; type the pull request number before the flags`, usage2);
15259
+ throw lineRefusal5(`--${name} takes no value, and read "${value}" as one; type the pull request number before the flags`, usage);
14683
15260
  }
14684
15261
  var DEFAULT_PR_SEAMS = Object.freeze({});
14685
15262
  function projectOf8(context) {
@@ -14727,6 +15304,7 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
14727
15304
  roadmapIssue: config.roadmapIssue,
14728
15305
  relationships: config.boardRelationships,
14729
15306
  planDir: config.planDir,
15307
+ worktreeDir: config.loopWorktreeDir,
14730
15308
  readBranch: () => readBranch2(project.root),
14731
15309
  versionGuard: mergeGuardSettings(config)
14732
15310
  };
@@ -14738,33 +15316,33 @@ async function onProvider(doing, call) {
14738
15316
  throw new CommandExit(1, `\u274C Could not ${doing}: ${messageOf(error)}`);
14739
15317
  }
14740
15318
  }
14741
- function branchOf(pr, usage2) {
15319
+ function branchOf(pr, usage) {
14742
15320
  let branch;
14743
15321
  try {
14744
15322
  branch = pr.readBranch();
14745
15323
  } catch (error) {
14746
- throw lineRefusal4(`The branch checked out at ${pr.project.root} cannot be read: ${messageOf(error)}`, usage2);
15324
+ throw lineRefusal5(`The branch checked out at ${pr.project.root} cannot be read: ${messageOf(error)}`, usage);
14747
15325
  }
14748
15326
  if (branch === DETACHED_HEAD || branch === "") {
14749
- throw lineRefusal4(`${pr.project.root} is on no branch, and a detached HEAD names no pull request`, usage2);
15327
+ throw lineRefusal5(`${pr.project.root} is on no branch, and a detached HEAD names no pull request`, usage);
14750
15328
  }
14751
15329
  return branch;
14752
15330
  }
14753
- async function pickPullRequest(pr, asked, usage2) {
15331
+ async function pickPullRequest(pr, asked, usage) {
14754
15332
  if (asked !== null) {
14755
15333
  return { number: asked, source: "argument", branch: null, summary: null };
14756
15334
  }
14757
- const branch = branchOf(pr, usage2);
15335
+ const branch = branchOf(pr, usage);
14758
15336
  const summary = await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
14759
15337
  if (summary === null) {
14760
- throw lineRefusal4(`No open pull request for the branch "${branch}" at ${pr.project.root}` + `
14761
- Run rafa pr list to see the open pull requests.`, usage2);
15338
+ throw lineRefusal5(`No open pull request for the branch "${branch}" at ${pr.project.root}` + `
15339
+ Run rafa pr list to see the open pull requests.`, usage);
14762
15340
  }
14763
15341
  return { number: summary.number, source: "branch", branch, summary };
14764
15342
  }
14765
15343
 
14766
15344
  // src/commands/pr/current.ts
14767
- var USAGE34 = PR_USAGE.current;
15345
+ var USAGE35 = PR_USAGE.current;
14768
15346
  var SEPARATOR2 = " \u2014 ";
14769
15347
  function trimmedOrNull(value) {
14770
15348
  const trimmed = value.trim();
@@ -14795,9 +15373,9 @@ async function probeChecks(pulls, number) {
14795
15373
  }
14796
15374
  }
14797
15375
  async function readCurrent(context, seams) {
14798
- expectNoArguments(context.args, USAGE34);
15376
+ expectNoArguments(context.args, USAGE35);
14799
15377
  const pr = openPrContext(context, seams);
14800
- const pick = await pickPullRequest(pr, null, USAGE34);
15378
+ const pick = await pickPullRequest(pr, null, USAGE35);
14801
15379
  const summary = pick.summary;
14802
15380
  const probe = await probeChecks(pr.pulls, pick.number);
14803
15381
  const reading = {
@@ -14962,7 +15540,7 @@ function forecastLine(number, forecast) {
14962
15540
  }
14963
15541
 
14964
15542
  // src/commands/pr/list.ts
14965
- var USAGE35 = PR_USAGE.list;
15543
+ var USAGE36 = PR_USAGE.list;
14966
15544
  var UNREADABLE = "?";
14967
15545
  var PROBE_LIMIT = 6;
14968
15546
  var TITLE_WIDTH = 60;
@@ -15050,7 +15628,7 @@ async function probe(call) {
15050
15628
  return { value: null, problem: messageOf(error) };
15051
15629
  }
15052
15630
  }
15053
- async function rowOf2(pulls, pull2, readBaseOf) {
15631
+ async function rowOf3(pulls, pull2, readBaseOf) {
15054
15632
  const [checks, detail] = await Promise.all([
15055
15633
  probe(() => pulls.checks(pull2.number)),
15056
15634
  probe(() => pulls.get(pull2.number))
@@ -15066,12 +15644,12 @@ async function rowOf2(pulls, pull2, readBaseOf) {
15066
15644
  };
15067
15645
  }
15068
15646
  async function readList(context, seams, now) {
15069
- expectNoArguments(context.args, USAGE35);
15647
+ expectNoArguments(context.args, USAGE36);
15070
15648
  const pr = openPrContext(context, seams);
15071
15649
  const open = await onProvider("list the open pull requests", () => pr.pulls.list());
15072
15650
  const root = pr.project.root;
15073
15651
  const readBaseOf = resolveReleaseEnabled(pr.versionGuard, root).enabled ? createBaseReader((seams.git ?? createGitRunner)(root), pr.versionGuard) : null;
15074
- const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf2(pr.pulls, pull2, readBaseOf));
15652
+ const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf3(pr.pulls, pull2, readBaseOf));
15075
15653
  return { rows, text: renderList(rows, now) };
15076
15654
  }
15077
15655
  function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
@@ -15216,6 +15794,183 @@ async function guardBeforeMerge(step) {
15216
15794
  return { report: report3, go: true };
15217
15795
  }
15218
15796
 
15797
+ // src/commands/pr/merge-loop-worktree.ts
15798
+ import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync5, realpathSync as realpathSync3, statSync as statSync8 } from "fs";
15799
+ import { dirname as dirname6, isAbsolute as isAbsolute6, join as join29, relative as relative8, resolve as resolve11, sep as sep4 } from "path";
15800
+ var REFUSAL_PREFIX = "rafa pr merge refuses";
15801
+ var INDENT4 = " ";
15802
+ var LIVE_STATES = new Set(["running", "paused"]);
15803
+ function defaultLoopWorktreeSeams(mainCheckout) {
15804
+ return {
15805
+ git: createGitRunner(mainCheckout),
15806
+ gitAt: createGitRunner,
15807
+ modifiedAt: (path) => statSync8(path, { throwIfNoEntry: false })?.mtime ?? null,
15808
+ copyFile: (from, to) => {
15809
+ mkdirSync5(dirname6(to), { recursive: true });
15810
+ copyFileSync3(from, to);
15811
+ },
15812
+ realPath: (path) => {
15813
+ try {
15814
+ return realpathSync3(path);
15815
+ } catch {
15816
+ return path;
15817
+ }
15818
+ }
15819
+ };
15820
+ }
15821
+ function readLoopHolder(seams, reading) {
15822
+ const holder = findLoopHolder(seams, reading);
15823
+ if (holder === null)
15824
+ return { kind: "none" };
15825
+ const reasons = [...liveReasons(reading, holder.path), ...dirtyReasons(seams.gitAt(holder.path))];
15826
+ if (reasons.length === 0)
15827
+ return { kind: "freeable", ...holder };
15828
+ return {
15829
+ kind: "refused",
15830
+ path: holder.path,
15831
+ message: [
15832
+ `${REFUSAL_PREFIX}: branch ${reading.branch} is checked out in the loop worktree ${holder.path}, which cannot be freed:`,
15833
+ ...reasons.map((reason) => `${INDENT4}${reason}`),
15834
+ "Once none of that holds, run rafa pr merge again."
15835
+ ].join(`
15836
+ `)
15837
+ };
15838
+ }
15839
+ function freeLoopWorktree(seams, reading, holder) {
15840
+ const into = resolve11(reading.mainCheckout, reading.planDir);
15841
+ const files = [];
15842
+ for (const name of planFileNames(holder.stub)) {
15843
+ const from = resolve11(holder.path, reading.planDir, name);
15844
+ const to = join29(into, name);
15845
+ try {
15846
+ files.push({ name, from, to, outcome: copyPlanFile(seams, from, to) });
15847
+ } catch (error) {
15848
+ return {
15849
+ ok: false,
15850
+ message: [
15851
+ `${REFUSAL_PREFIX}: ${name} could not be copied into ${into}: ${messageOf(error)}`,
15852
+ `${INDENT4}${filesClause(files) ?? "nothing was copied"}; the worktree was left in place, and nothing was merged.`
15853
+ ].join(`
15854
+ `)
15855
+ };
15856
+ }
15857
+ }
15858
+ const removed = seams.git(["worktree", "remove", holder.path]);
15859
+ const copies = filesClause(files);
15860
+ if (!removed.ok) {
15861
+ return {
15862
+ ok: false,
15863
+ message: [
15864
+ `${REFUSAL_PREFIX}: the loop worktree ${holder.path} holding ${reading.branch} could not be removed, so nothing was merged.`,
15865
+ ...quotedLines2(gitSaid(removed)),
15866
+ `${INDENT4}${copies ?? "no plan file was there to copy"} into ${into}.`,
15867
+ `Run git worktree remove ${shellQuote(holder.path)} yourself, then rafa pr merge again.`
15868
+ ].join(`
15869
+ `)
15870
+ };
15871
+ }
15872
+ return {
15873
+ ok: true,
15874
+ files,
15875
+ line: `Freed the ended loop worktree ${holder.path} holding ${reading.branch}: ` + `${copies ?? "no plan file to copy"} into ${into}, then the worktree removed.`
15876
+ };
15877
+ }
15878
+ function freeLoopHolder(seams, reading) {
15879
+ const answer2 = readLoopHolder(seams, reading);
15880
+ if (answer2.kind === "none")
15881
+ return answer2;
15882
+ if (answer2.kind === "refused")
15883
+ return { kind: "refused", message: answer2.message };
15884
+ const freed = freeLoopWorktree(seams, reading, answer2);
15885
+ return freed.ok ? { kind: "freed", line: freed.line } : { kind: "refused", message: freed.message };
15886
+ }
15887
+ function findLoopHolder(seams, reading) {
15888
+ const holders = worktreesHolding(reading.worktrees, reading.branch, reading.mainCheckout);
15889
+ const holder = holders[0];
15890
+ if (holders.length !== 1 || holder === undefined)
15891
+ return null;
15892
+ const stub = loopStubOf(seams, reading, holder.path);
15893
+ return stub === null ? null : { path: holder.path, stub };
15894
+ }
15895
+ function freeLoopHolderBeforeMerge(seams, place2, sessions) {
15896
+ const holder = findLoopHolder(seams, place2);
15897
+ if (holder === null)
15898
+ return { kind: "none" };
15899
+ let liveLoops;
15900
+ try {
15901
+ liveLoops = readSessions(sessions.root, { isAlive: sessions.isAlive });
15902
+ } catch (error) {
15903
+ return {
15904
+ kind: "refused",
15905
+ message: `${REFUSAL_PREFIX}: branch ${place2.branch} is checked out in the loop worktree ${holder.path}, ` + `and whether a loop still runs in it could not be read: ${messageOf(error)}`
15906
+ };
15907
+ }
15908
+ return freeLoopHolder(seams, { ...place2, liveLoops });
15909
+ }
15910
+ function planFileNames(stub) {
15911
+ return [`CLOSEOUT-${stub}.md`, `PLAN_TRACKER-${stub}.md`];
15912
+ }
15913
+ function loopStubOf(seams, reading, path) {
15914
+ const dir = worktreeDirAt(reading.mainCheckout, reading.worktreeDir);
15915
+ const forms = [...new Set([dir, seams.realPath(dir)])];
15916
+ for (const form of forms) {
15917
+ const below = relative8(form, path);
15918
+ if (below !== "" && !below.startsWith("..") && !isAbsolute6(below) && !below.includes(sep4))
15919
+ return below;
15920
+ }
15921
+ return null;
15922
+ }
15923
+ function liveReasons(reading, path) {
15924
+ return reading.liveLoops.filter((loop) => LIVE_STATES.has(loop.state)).filter((loop) => loop.branch === reading.branch || loop.worktree === path).map((loop) => `loop session ${loop.sessionId} (pid ${String(loop.pid)}) is ${loop.state} in it`);
15925
+ }
15926
+ function dirtyReasons(git) {
15927
+ const status = git(WORKTREE_STATUS);
15928
+ if (!status.ok)
15929
+ return [`its changes could not be read: ${failure("git status", status)}`];
15930
+ const { changed, untracked } = countChanges(status.stdout);
15931
+ const parts = [
15932
+ ...changed > 0 ? [plural2(changed, "uncommitted change")] : [],
15933
+ ...untracked > 0 ? [plural2(untracked, "untracked file")] : []
15934
+ ];
15935
+ return parts.length === 0 ? [] : [parts.join(", ")];
15936
+ }
15937
+ function copyPlanFile(seams, from, to) {
15938
+ if (from === to)
15939
+ return "in-place";
15940
+ const source = seams.modifiedAt(from);
15941
+ if (source === null)
15942
+ return "absent";
15943
+ const destination = seams.modifiedAt(to);
15944
+ if (destination !== null && destination.getTime() > source.getTime())
15945
+ return "kept";
15946
+ seams.copyFile(from, to);
15947
+ return "copied";
15948
+ }
15949
+ function filesClause(files) {
15950
+ if (files.length === 0)
15951
+ return null;
15952
+ return files.map((file) => `${file.name} ${outcomeWords(file.outcome)}`).join(" and ");
15953
+ }
15954
+ function outcomeWords(outcome) {
15955
+ if (outcome === "kept")
15956
+ return "kept (the main checkout's is newer)";
15957
+ if (outcome === "in-place")
15958
+ return "already in place";
15959
+ return outcome;
15960
+ }
15961
+ function quotedLines2(said) {
15962
+ return said === "" ? [] : said.split(`
15963
+ `).map((line) => `${INDENT4}${line}`);
15964
+ }
15965
+ function failure(what, result2) {
15966
+ const said = gitSaid(result2).split(`
15967
+ `).map((line) => line.trim()).find((line) => line !== "") ?? "";
15968
+ return said === "" ? `${what} failed` : `${what} failed: ${said}`;
15969
+ }
15970
+ function plural2(count, noun) {
15971
+ return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
15972
+ }
15973
+
15219
15974
  // src/pr/unchecked.ts
15220
15975
  var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
15221
15976
  var WORKFLOWS_EXIST_WARNING = "CI may not have started (a path filter, a draft, Actions disabled, or it has not registered yet); this is probably not what you want";
@@ -15288,7 +16043,7 @@ function workflowNamesBase(workflowText, base) {
15288
16043
  return readWorkflow(document, base) !== false;
15289
16044
  }
15290
16045
  function readWorkflow(document, base) {
15291
- if (!isPlainObject(document) || !("on" in document))
16046
+ if (!isPlainObject2(document) || !("on" in document))
15292
16047
  return UNKNOWN;
15293
16048
  const on = document.on;
15294
16049
  if (typeof on === "string")
@@ -15298,7 +16053,7 @@ function readWorkflow(document, base) {
15298
16053
  return UNKNOWN;
15299
16054
  return on.includes(PULL_REQUEST_EVENT);
15300
16055
  }
15301
- if (!isPlainObject(on))
16056
+ if (!isPlainObject2(on))
15302
16057
  return UNKNOWN;
15303
16058
  if (!(PULL_REQUEST_EVENT in on))
15304
16059
  return false;
@@ -15307,7 +16062,7 @@ function readWorkflow(document, base) {
15307
16062
  function readPullRequestTrigger(trigger, base) {
15308
16063
  if (trigger === null || trigger === undefined)
15309
16064
  return true;
15310
- if (!isPlainObject(trigger))
16065
+ if (!isPlainObject2(trigger))
15311
16066
  return UNKNOWN;
15312
16067
  const branches = trigger.branches;
15313
16068
  const ignored = trigger["branches-ignore"];
@@ -15408,12 +16163,12 @@ function regexSourceOf(pattern) {
15408
16163
  function escapeRegex(char) {
15409
16164
  return char.replace(REGEX_SPECIAL, "\\$&");
15410
16165
  }
15411
- function isPlainObject(value) {
16166
+ function isPlainObject2(value) {
15412
16167
  return typeof value === "object" && value !== null && !Array.isArray(value);
15413
16168
  }
15414
16169
 
15415
16170
  // src/commands/pr/merge-unchecked.ts
15416
- var INDENT4 = " ";
16171
+ var INDENT5 = " ";
15417
16172
  var YES_ANSWERS3 = ["y", "yes"];
15418
16173
  var WORKFLOWS_DIR = ".github/workflows/";
15419
16174
  var WORKFLOW_ENDINGS = [".yml", ".yaml"];
@@ -15421,8 +16176,8 @@ var LS_TREE_ENTRY = /^\d+ (\w+) ([0-9a-f]+)\t(.+)$/s;
15421
16176
  function refusal10(head, reading, summary, tail) {
15422
16177
  return new CommandExit(1, [
15423
16178
  `\u274C ${head}`,
15424
- ...reading.warning.map((line) => `${INDENT4}${line}`),
15425
- `${INDENT4}${summary}`,
16179
+ ...reading.warning.map((line) => `${INDENT5}${line}`),
16180
+ `${INDENT5}${summary}`,
15426
16181
  tail
15427
16182
  ].join(`
15428
16183
  `));
@@ -15493,7 +16248,7 @@ async function confirmUncheckedMerge(unchecked, options) {
15493
16248
  }
15494
16249
  function commentProblemLine(number, problem2, body) {
15495
16250
  const quoted = body.split(`
15496
- `).map((line) => `${INDENT4}${line}`).join(`
16251
+ `).map((line) => `${INDENT5}${line}`).join(`
15497
16252
  `);
15498
16253
  return `the unchecked-merge comment was not posted on #${number}: ${problem2}. Post it yourself:
15499
16254
  ${quoted}`;
@@ -15509,21 +16264,21 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
15509
16264
  }
15510
16265
 
15511
16266
  // src/commands/pr/merge.ts
15512
- var USAGE36 = PR_USAGE.merge;
16267
+ var USAGE37 = PR_USAGE.merge;
15513
16268
  var YES_ANSWERS4 = ["y", "yes"];
15514
16269
  var DEFAULT_MERGE_SEAMS = Object.freeze({});
15515
16270
  function refusal11(lines) {
15516
16271
  return new CommandExit(1, lines.join(`
15517
16272
  `));
15518
16273
  }
15519
- function readMethodFlag(flags, usage2) {
16274
+ function readMethodFlag(flags, usage) {
15520
16275
  const value = flags["method"];
15521
16276
  if (value === undefined)
15522
16277
  return null;
15523
16278
  if (isMergeMethod(value))
15524
16279
  return value;
15525
- const spelled = typeof value === "boolean" ? "--method with no value" : `"${value}"`;
15526
- throw lineRefusal4(`${spelled} is no merge method; one of: ${MERGE_METHODS.join(", ")}`, usage2);
16280
+ const spelled2 = typeof value === "boolean" ? "--method with no value" : `"${value}"`;
16281
+ throw lineRefusal5(`${spelled2} is no merge method; one of: ${MERGE_METHODS.join(", ")}`, usage);
15527
16282
  }
15528
16283
  function gitOrRefuse(git, args, doing) {
15529
16284
  const result2 = git(args);
@@ -15536,10 +16291,20 @@ function summaryLine2(detail, method) {
15536
16291
  const head = title === "" ? `#${detail.number}` : `#${detail.number} ${title}`;
15537
16292
  return `${head} \u2014 ${detail.headRefName} \u2192 ${detail.baseRefName} \u2014 ${method}`;
15538
16293
  }
15539
- function refuseFromGit(git, detail, checks, skipChecks) {
16294
+ function refuseFromGit(merge, detail, checks, skipChecks) {
16295
+ const { git, pr, seams } = merge;
15540
16296
  const tree = parseWorkingTree(gitOrRefuse(git, ["status", "--porcelain"], "read the working tree"));
15541
- const worktrees = parseWorktrees(gitOrRefuse(git, ["worktree", "list", "--porcelain"], "list the worktrees"));
16297
+ const listed2 = () => parseWorktrees(gitOrRefuse(git, ["worktree", "list", "--porcelain"], "list the worktrees"));
16298
+ const held = listed2();
15542
16299
  const at = gitOrRefuse(git, ["rev-parse", "--show-toplevel"], "read the repository root").trim();
16300
+ const place2 = { worktrees: held, branch: detail.headRefName, mainCheckout: at, worktreeDir: pr.worktreeDir, planDir: pr.planDir };
16301
+ const loopSeams = { ...defaultLoopWorktreeSeams(at), git, gitAt: seams.git ?? createGitRunner };
16302
+ const freed = freeLoopHolderBeforeMerge(loopSeams, place2, { root: pr.project.root, isAlive: seams.isAlive });
16303
+ if (freed.kind === "refused")
16304
+ throw refusal11([`\u274C ${freed.message}`]);
16305
+ if (freed.kind === "freed")
16306
+ merge.info(freed.line);
16307
+ const worktrees = freed.kind === "freed" ? listed2() : held;
15543
16308
  const found = readMergeRefusal({
15544
16309
  number: detail.number,
15545
16310
  branch: detail.headRefName,
@@ -15683,17 +16448,17 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
15683
16448
  return posted.url;
15684
16449
  }
15685
16450
  async function runMerge2(context, seams) {
15686
- const yes = readBooleanFlag(context.flags, "yes", USAGE36);
15687
- const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE36);
15688
- const wanted = readMethodFlag(context.flags, USAGE36);
15689
- const asked = readPullArgument(context.args, USAGE36);
16451
+ const yes = readBooleanFlag(context.flags, "yes", USAGE37);
16452
+ const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE37);
16453
+ const wanted = readMethodFlag(context.flags, USAGE37);
16454
+ const asked = readPullArgument(context.args, USAGE37);
15690
16455
  const pr = openPrContext(context, seams);
15691
16456
  const git = (seams.git ?? createGitRunner)(pr.project.root);
15692
16457
  const method = wanted ?? pr.mergeMethod;
15693
- const pick = await pickPullRequest(pr, asked, USAGE36);
16458
+ const pick = await pickPullRequest(pr, asked, USAGE37);
15694
16459
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
15695
16460
  if (detail === null) {
15696
- throw lineRefusal4(`No pull request #${pick.number} at ${pr.project.root}`, USAGE36);
16461
+ throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`, USAGE37);
15697
16462
  }
15698
16463
  const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
15699
16464
  recordPlanCi({
@@ -15705,7 +16470,7 @@ async function runMerge2(context, seams) {
15705
16470
  context.output.warn(message);
15706
16471
  }
15707
16472
  });
15708
- refuseFromGit(git, detail, checks, skipChecks);
16473
+ refuseFromGit({ git, pr, seams, info: (message) => context.output.info(message) }, detail, checks, skipChecks);
15709
16474
  const guard = await guardBeforeMerge({
15710
16475
  pr,
15711
16476
  git,
@@ -15785,7 +16550,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
15785
16550
  subject: "pr",
15786
16551
  action: "merge",
15787
16552
  summary: "merge a pull request and clean up both branches",
15788
- description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
16553
+ description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree, except a" + " clean, ended loop worktree under `loop.worktreeDir`, which it removes after copying its two plan files out. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
15789
16554
  args: [
15790
16555
  {
15791
16556
  name: "n",
@@ -16279,9 +17044,22 @@ function readLastTriage(comments) {
16279
17044
  }
16280
17045
 
16281
17046
  // src/commands/pr/show.ts
16282
- var USAGE37 = PR_USAGE.show;
16283
- var INDENT5 = " ";
17047
+ var USAGE38 = PR_USAGE.show;
17048
+ var INDENT6 = " ";
16284
17049
  var SHORT_SHA = 7;
17050
+ var NOT_IN_BODY = "not named by the body";
17051
+ var KEYWORD_ONLY = "keyword only, not linked";
17052
+ var OTHER_REPOSITORY = "another repository";
17053
+ var PULL_URL_REPOSITORY = /^https?:\/\/[^/]+\/([^/]+\/[^/]+)\/pull\/\d+\/?$/u;
17054
+ var PR_SHOW_JSON_FIELDS = Object.freeze([
17055
+ "detail",
17056
+ "bodyCloses",
17057
+ "checks",
17058
+ "checksProblem",
17059
+ "triage",
17060
+ "triageProblem",
17061
+ "text"
17062
+ ]);
16285
17063
  function headLine3(detail) {
16286
17064
  const title = detail.title.trim();
16287
17065
  return title === "" ? `#${detail.number}` : `#${detail.number} ${title}`;
@@ -16301,6 +17079,25 @@ function factsLine(detail) {
16301
17079
  mergeWord(detail)
16302
17080
  ].join(SEPARATOR2);
16303
17081
  }
17082
+ function repositoryOf(detail) {
17083
+ return PULL_URL_REPOSITORY.exec(detail.url.trim())?.[1] ?? null;
17084
+ }
17085
+ function linkedWord(issue, own, named3) {
17086
+ if (own !== null && issue.repository !== own) {
17087
+ return `${issue.repository}#${issue.number} (${OTHER_REPOSITORY})`;
17088
+ }
17089
+ return named3.has(issue.number) ? `#${issue.number}` : `#${issue.number} (${NOT_IN_BODY})`;
17090
+ }
17091
+ function closesLine(reading) {
17092
+ const own = repositoryOf(reading.detail);
17093
+ const named3 = new Set(reading.bodyCloses);
17094
+ const linked = new Set(reading.detail.closes.filter((issue) => own === null || issue.repository === own).map((issue) => issue.number));
17095
+ const words = [
17096
+ ...reading.detail.closes.map((issue) => linkedWord(issue, own, named3)),
17097
+ ...[...named3].filter((number) => !linked.has(number)).map((number) => `#${number} (${KEYWORD_ONLY})`)
17098
+ ];
17099
+ return words.length === 0 ? "closes none" : `closes ${words.join(", ")}`;
17100
+ }
16304
17101
  function checksLines(reading) {
16305
17102
  if (reading.checks === null) {
16306
17103
  return [`checks could not be read${SEPARATOR2}${reading.checksProblem ?? "the provider gave no reason"}`];
@@ -16338,8 +17135,8 @@ function foundTriageLines(triage, headRefOid) {
16338
17135
  ].filter((word) => word !== null);
16339
17136
  return [
16340
17137
  words.join(SEPARATOR2),
16341
- `${INDENT5}${triage.url}${SEPARATOR2}by ${triage.author}`,
16342
- ...triage.problems.map((problem2) => `${INDENT5}${problem2}`)
17138
+ `${INDENT6}${triage.url}${SEPARATOR2}by ${triage.author}`,
17139
+ ...triage.problems.map((problem2) => `${INDENT6}${problem2}`)
16343
17140
  ];
16344
17141
  }
16345
17142
  function triageLines(reading) {
@@ -16356,7 +17153,8 @@ function renderShow(reading) {
16356
17153
  const head = [
16357
17154
  headLine3(reading.detail),
16358
17155
  factsLine(reading.detail),
16359
- ...url === "" ? [] : [url]
17156
+ ...url === "" ? [] : [url],
17157
+ closesLine(reading)
16360
17158
  ];
16361
17159
  return [
16362
17160
  head.join(`
@@ -16377,17 +17175,18 @@ async function probe2(call) {
16377
17175
  }
16378
17176
  }
16379
17177
  async function readShow(context, seams) {
16380
- const asked = readPullArgument(context.args, USAGE37);
17178
+ const asked = readPullArgument(context.args, USAGE38);
16381
17179
  const pr = openPrContext(context, seams);
16382
- const pick = await pickPullRequest(pr, asked, USAGE37);
17180
+ const pick = await pickPullRequest(pr, asked, USAGE38);
16383
17181
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
16384
17182
  if (detail === null) {
16385
- throw lineRefusal4(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE37);
17183
+ throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE38);
16386
17184
  }
16387
17185
  const checks = await probe2(() => pr.pulls.checks(pick.number));
16388
17186
  const comments = await probe2(() => pr.pulls.comments(pick.number));
16389
17187
  const reading = {
16390
17188
  detail,
17189
+ bodyCloses: closedIssuesIn(detail.body),
16391
17190
  checks: checks.value,
16392
17191
  checksProblem: checks.problem,
16393
17192
  triage: comments.value === null ? null : readLastTriage(comments.value),
@@ -16401,7 +17200,7 @@ function createPrShowCommand(seams = DEFAULT_PR_SEAMS) {
16401
17200
  subject: "pr",
16402
17201
  action: "show",
16403
17202
  summary: "show one pull request in detail, with its checks and its last triage",
16404
- description: "Reads one pull request in full and prints its number and title, its state, author, head branch," + " base branch and mergeability, its URL, every check with its state and its link, and the last rafa triage" + " comment made on it. Without a number it reads the open pull request whose head is the branch checked out" + " at the project root. The checks and the triage comment are read separately, and one that cannot be read" + " says so in its own section rather than refusing the command, where a pull request with no checks reads" + " `checks none`. With `--output=json` the pull request, the checks, the triage and the rendered text are" + " the data of the terminal result event. Refuses with exit code 2 where `pr.provider` is not `gh`.",
17203
+ description: "Reads one pull request in full and prints its number and title, its state, author, head branch," + " base branch and mergeability, its URL, the issues it closes, every check with its state and its link, and" + " the last rafa triage comment made on it. The closes line marks an issue the provider links that the body" + " names by no closing keyword, and an issue the body names by keyword that the provider does not link." + " Without a number it reads the open pull request whose head is the branch checked out" + " at the project root. The checks and the triage comment are read separately, and one that cannot be read" + " says so in its own section rather than refusing the command, where a pull request with no checks reads" + " `checks none`. With `--output=json` the data of the terminal result event holds the fields " + PR_SHOW_JSON_FIELDS.map((field) => `\`${field}\``).join(", ") + ": the pull request, the issues its body closes by keyword, the checks, the triage and the rendered text," + " each read that failed carrying its problem. Refuses with exit code 2 where `pr.provider` is not `gh`.",
16405
17204
  args: [
16406
17205
  {
16407
17206
  name: "n",
@@ -16805,8 +17604,8 @@ var TRIAGE_FRESH_WINDOW_MS = TRIAGE_FRESH_HOURS * HOUR_MS2;
16805
17604
  var MAX_TRIAGE_CANDIDATES = 3;
16806
17605
  var REFUSE_EXIT_CODE = 2;
16807
17606
  var DAYS_FROM_HOURS = 48;
16808
- var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
16809
- var INDENT6 = " ";
17607
+ var REFUSAL_PREFIX2 = "rafa pr triage --resolve refuses";
17608
+ var INDENT7 = " ";
16810
17609
  function ageMs(updatedAt, now) {
16811
17610
  const then = Date.parse(updatedAt);
16812
17611
  const at = Date.parse(now);
@@ -16818,7 +17617,7 @@ function isFreshCandidate(updatedAt, now) {
16818
17617
  const age = ageMs(updatedAt, now);
16819
17618
  return age !== null && age <= TRIAGE_FRESH_WINDOW_MS;
16820
17619
  }
16821
- function plural2(count, singular, many) {
17620
+ function plural3(count, singular, many) {
16822
17621
  return count === 1 ? `1 ${singular}` : `${count} ${many}`;
16823
17622
  }
16824
17623
  function describeAge(updatedAt, now) {
@@ -16831,11 +17630,11 @@ function describeAge(updatedAt, now) {
16831
17630
  if (hours < 1)
16832
17631
  return "less than an hour ago";
16833
17632
  if (hours < DAYS_FROM_HOURS)
16834
- return `${plural2(hours, "hour", "hours")} ago`;
16835
- return `${plural2(Math.floor(age / DAY_MS2), "day", "days")} ago`;
17633
+ return `${plural3(hours, "hour", "hours")} ago`;
17634
+ return `${plural3(Math.floor(age / DAY_MS2), "day", "days")} ago`;
16836
17635
  }
16837
- function triageCommand(number, resolve11 = false) {
16838
- return resolve11 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
17636
+ function triageCommand(number, resolve12 = false) {
17637
+ return resolve12 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
16839
17638
  }
16840
17639
  function joinNumbers(pulls) {
16841
17640
  const names = pulls.map((pull2) => `#${pull2.number}`);
@@ -16846,18 +17645,18 @@ function joinNumbers(pulls) {
16846
17645
  function byNumber2(pulls) {
16847
17646
  return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
16848
17647
  }
16849
- function skipped(pull2, now, resolve11) {
17648
+ function skipped(pull2, now, resolve12) {
16850
17649
  return Object.freeze({
16851
17650
  number: pull2.number,
16852
17651
  title: pull2.title,
16853
17652
  updatedAt: pull2.updatedAt,
16854
17653
  age: describeAge(pull2.updatedAt, now),
16855
17654
  fresh: isFreshCandidate(pull2.updatedAt, now),
16856
- command: triageCommand(pull2.number, resolve11)
17655
+ command: triageCommand(pull2.number, resolve12)
16857
17656
  });
16858
17657
  }
16859
17658
  function skippedLine(one) {
16860
- return `${INDENT6}#${one.number} last moved ${one.age}, run ${one.command}`;
17659
+ return `${INDENT7}#${one.number} last moved ${one.age}, run ${one.command}`;
16861
17660
  }
16862
17661
  function plain(decision, assess, headline) {
16863
17662
  return Object.freeze({
@@ -16884,11 +17683,11 @@ function listing(decision, assess, rest, headline, exitCode = 0) {
16884
17683
  });
16885
17684
  }
16886
17685
  function refusal12(rest, count) {
16887
- const headline = `${REFUSAL_PREFIX}: ${plural2(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
17686
+ const headline = `${REFUSAL_PREFIX2}: ${plural3(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
16888
17687
  return listing("refuse", Object.freeze([]), rest, headline, REFUSE_EXIT_CODE);
16889
17688
  }
16890
17689
  function someReading(fresh, stale, count) {
16891
- const older = stale.length === 0 ? "" : `; ${plural2(stale.length, "older one is", "older ones are")} skipped`;
17690
+ const older = stale.length === 0 ? "" : `; ${plural3(stale.length, "older one is", "older ones are")} skipped`;
16892
17691
  const headline = `${count} red: assessing ${joinNumbers(fresh)}${older}`;
16893
17692
  return listing("some", fresh, stale, headline);
16894
17693
  }
@@ -16897,15 +17696,15 @@ function listReading(rest, count) {
16897
17696
  return listing("list", Object.freeze([]), rest, headline);
16898
17697
  }
16899
17698
  function selectTriagePullRequests(input) {
16900
- const resolve11 = input.resolve === true;
17699
+ const resolve12 = input.resolve === true;
16901
17700
  const { current, now } = input;
16902
17701
  if (current !== null) {
16903
17702
  return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
16904
17703
  }
16905
17704
  const candidates = byNumber2(input.candidates);
16906
17705
  const count = candidates.length;
16907
- const lines = candidates.map((pull2) => skipped(pull2, now, resolve11));
16908
- if (resolve11 && count > 1)
17706
+ const lines = candidates.map((pull2) => skipped(pull2, now, resolve12));
17707
+ if (resolve12 && count > 1)
16909
17708
  return refusal12(lines, count);
16910
17709
  if (count === 0) {
16911
17710
  return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
@@ -17088,10 +17887,10 @@ function readTriageGuard(input) {
17088
17887
  }
17089
17888
 
17090
17889
  // src/commands/pr/triage-report.ts
17091
- var INDENT7 = " ";
17890
+ var INDENT8 = " ";
17092
17891
  var SHORT_SHA2 = 7;
17093
17892
  function ignoredLines(ignored) {
17094
- return ignored.map((comment) => `${INDENT7}${comment.reason}`);
17893
+ return ignored.map((comment) => `${INDENT8}${comment.reason}`);
17095
17894
  }
17096
17895
  function evidenceOf(reading) {
17097
17896
  return reading.logs?.chosen?.evidence;
@@ -17137,8 +17936,8 @@ function excerptBlock(reading) {
17137
17936
  if (excerpt.lines.length === 0)
17138
17937
  return [];
17139
17938
  return [
17140
- `${INDENT7}${excerptCaption(excerpt)}`,
17141
- ...excerpt.lines.map((line) => `${INDENT7}${INDENT7}${line}`)
17939
+ `${INDENT8}${excerptCaption(excerpt)}`,
17940
+ ...excerpt.lines.map((line) => `${INDENT8}${INDENT8}${line}`)
17142
17941
  ];
17143
17942
  }
17144
17943
  function noChecksLines2(reading, assessment) {
@@ -17149,19 +17948,19 @@ function noChecksLines2(reading, assessment) {
17149
17948
  const [count = "", warning = ""] = unchecked.warning;
17150
17949
  const yes = unchecked.yesMayAnswer ? "--yes may answer it" : "--yes is refused, so a person must answer it";
17151
17950
  return [
17152
- `${INDENT7}Workflows: ${count}`,
17153
- `${INDENT7}Warning: ${warning}`,
17154
- `${INDENT7}To merge it anyway: ${skipChecksCommand(number)} (it asks first; ${yes})`
17951
+ `${INDENT8}Workflows: ${count}`,
17952
+ `${INDENT8}Warning: ${warning}`,
17953
+ `${INDENT8}To merge it anyway: ${skipChecksCommand(number)} (it asks first; ${yes})`
17155
17954
  ];
17156
17955
  }
17157
17956
  function evidenceLines2(reading, assessment) {
17158
17957
  const step = assessment.step === undefined ? "none named" : assessment.step.name;
17159
17958
  return [
17160
- `${INDENT7}Why: ${assessment.reason}`,
17161
- `${INDENT7}Conflicting files: ${pathList2(assessment.files, noFiles(reading))}`,
17162
- `${INDENT7}Failing step: ${step}`,
17163
- `${INDENT7}Failing checks: ${pathList2(assessment.failing.map((row2) => row2.name), "none failing")}`,
17164
- `${INDENT7}Checks verdict: ${assessment.verdict}`,
17959
+ `${INDENT8}Why: ${assessment.reason}`,
17960
+ `${INDENT8}Conflicting files: ${pathList2(assessment.files, noFiles(reading))}`,
17961
+ `${INDENT8}Failing step: ${step}`,
17962
+ `${INDENT8}Failing checks: ${pathList2(assessment.failing.map((row2) => row2.name), "none failing")}`,
17963
+ `${INDENT8}Checks verdict: ${assessment.verdict}`,
17165
17964
  ...noChecksLines2(reading, assessment),
17166
17965
  ...excerptBlock(reading)
17167
17966
  ];
@@ -17173,16 +17972,16 @@ function storedBlockLine(block) {
17173
17972
  block.attempts === null ? null : `attempts ${block.attempts}`,
17174
17973
  block.files === null || block.files.length === 0 ? null : `files ${block.files.join(", ")}`
17175
17974
  ].filter((word) => word !== null);
17176
- return `${INDENT7}${words.join(SEPARATOR2)}`;
17975
+ return `${INDENT8}${words.join(SEPARATOR2)}`;
17177
17976
  }
17178
17977
  function storedLines(reading) {
17179
17978
  const comment = reading.rerun.comment;
17180
17979
  if (comment === null)
17181
17980
  return [];
17182
17981
  return [
17183
- `${INDENT7}${comment.url}${SEPARATOR2}by ${comment.author.login}`,
17982
+ `${INDENT8}${comment.url}${SEPARATOR2}by ${comment.author.login}`,
17184
17983
  ...reading.rerun.block === null ? [] : [storedBlockLine(reading.rerun.block)],
17185
- ...reading.rerun.problems.map((problem2) => `${INDENT7}${problem2}`)
17984
+ ...reading.rerun.problems.map((problem2) => `${INDENT8}${problem2}`)
17186
17985
  ];
17187
17986
  }
17188
17987
  function writeLines(reading) {
@@ -17199,8 +17998,8 @@ function logProblemLines(reading) {
17199
17998
  const logs = reading.logs;
17200
17999
  if (logs === null)
17201
18000
  return [];
17202
- const unlinked = logs.unlinked.length === 0 ? [] : [`${INDENT7}no Actions run is linked from ${logs.unlinked.join(", ")}, so no log was read for it`];
17203
- return [...logs.problems.map((problem2) => `${INDENT7}${problem2}`), ...unlinked];
18001
+ const unlinked = logs.unlinked.length === 0 ? [] : [`${INDENT8}no Actions run is linked from ${logs.unlinked.join(", ")}, so no log was read for it`];
18002
+ return [...logs.problems.map((problem2) => `${INDENT8}${problem2}`), ...unlinked];
17204
18003
  }
17205
18004
  function assessedLines(reading, assessment) {
17206
18005
  return [
@@ -17234,11 +18033,11 @@ var TASK_LINE = /^- \[[^\]]*\] /;
17234
18033
  var TRAILING_BLOCK = /\{([^{}]*)\}$/;
17235
18034
  var PREFIX3 = "pr triage --resolve budget";
17236
18035
  function budgetEntry(usd) {
17237
- const spelled = String(usd);
17238
- if (parseBudgetUsd(spelled) === null) {
18036
+ const spelled2 = String(usd);
18037
+ if (parseBudgetUsd(spelled2) === null) {
17239
18038
  throw new TypeError(`${PREFIX3}: refused an amount of ${JSON.stringify(usd)}, expected US dollars above zero` + " with at most six whole digits and six decimal places");
17240
18039
  }
17241
- return `${BUDGET_KEY}=${spelled}`;
18040
+ return `${BUDGET_KEY}=${spelled2}`;
17242
18041
  }
17243
18042
  function declaresBudget(body) {
17244
18043
  return body.split(/\s+/).some((token) => token.startsWith(`${BUDGET_KEY}=`));
@@ -17266,9 +18065,9 @@ function withTaskBudget(plan2, usd) {
17266
18065
 
17267
18066
  // src/pr/plans/load.ts
17268
18067
  import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
17269
- import { dirname as dirname6, join as join29 } from "path";
18068
+ import { dirname as dirname7, join as join30 } from "path";
17270
18069
  import { fileURLToPath as fileURLToPath4 } from "url";
17271
- var MODULE_DIR4 = dirname6(fileURLToPath4(import.meta.url));
18070
+ var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
17272
18071
  var PINNED_PLANS_DIRNAME = "plans";
17273
18072
  var PINNED_PLAN_CLASSES = Object.freeze([
17274
18073
  ...SIMPLE_TRIAGE_CLASSES,
@@ -17283,8 +18082,8 @@ function hasPinnedPlan(triageClass) {
17283
18082
  function pinnedPlanCandidates(moduleDir, triageClass) {
17284
18083
  const name = pinnedPlanFileName(triageClass);
17285
18084
  return [
17286
- join29(moduleDir, PINNED_PLANS_DIRNAME, name),
17287
- join29(moduleDir, name)
18085
+ join30(moduleDir, PINNED_PLANS_DIRNAME, name),
18086
+ join30(moduleDir, name)
17288
18087
  ];
17289
18088
  }
17290
18089
  function readPinnedPlan(triageClass, lookup = {}) {
@@ -17436,20 +18235,20 @@ function readAttemptRepeat(input) {
17436
18235
  }
17437
18236
 
17438
18237
  // src/commands/pr/resolve-loop.ts
17439
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
17440
- import { join as join30 } from "path";
17441
- var RESOLVE_SUBDIR = join30(".rafa", "resolve");
18238
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
18239
+ import { join as join31 } from "path";
18240
+ var RESOLVE_SUBDIR = join31(".rafa", "resolve");
17442
18241
  var RUN_PREFIX = "pr-";
17443
18242
  var ATTEMPT_PREFIX = "attempt-";
17444
18243
  var LOOP_WORDS = Object.freeze(["loop", "start"]);
17445
18244
  var CHILD_OUTPUT = "text";
17446
18245
  function resolveRunDir(home, number, attempt3) {
17447
- return join30(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
18246
+ return join31(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
17448
18247
  }
17449
18248
  function writeResolvePlan(write) {
17450
18249
  const dir = resolveRunDir(write.home, write.number, write.attempt);
17451
- mkdirSync5(dir, { recursive: true });
17452
- const path = join30(dir, pinnedPlanFileName(write.triageClass));
18250
+ mkdirSync6(dir, { recursive: true });
18251
+ const path = join31(dir, pinnedPlanFileName(write.triageClass));
17453
18252
  writeFileSync6(path, write.plan, "utf8");
17454
18253
  return path;
17455
18254
  }
@@ -17490,8 +18289,8 @@ async function runResolveLoop(run, seams = {}) {
17490
18289
  }
17491
18290
 
17492
18291
  // src/pr/triage/version-convert.ts
17493
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
17494
- import { dirname as dirname7, join as join31, posix } from "path";
18292
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
18293
+ import { dirname as dirname8, join as join32, posix } from "path";
17495
18294
  var FALLBACK_PLAN_PREFIX = "pr-";
17496
18295
  var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
17497
18296
  var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
@@ -17603,7 +18402,7 @@ function planConversion(input, problems) {
17603
18402
  }
17604
18403
  let branchVersionText;
17605
18404
  try {
17606
- branchVersionText = readFileSync19(join31(input.worktree, settings.releaseVersionFile), "utf8");
18405
+ branchVersionText = readFileSync19(join32(input.worktree, settings.releaseVersionFile), "utf8");
17607
18406
  } catch (error) {
17608
18407
  return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
17609
18408
  }
@@ -17629,9 +18428,9 @@ function writeConversion(input, plan2) {
17629
18428
  [input.settings.releaseChangelog, plan2.changelogText]
17630
18429
  ];
17631
18430
  for (const [path, text] of writes) {
17632
- const target = join31(input.worktree, path);
18431
+ const target = join32(input.worktree, path);
17633
18432
  try {
17634
- mkdirSync6(dirname7(target), { recursive: true });
18433
+ mkdirSync7(dirname8(target), { recursive: true });
17635
18434
  writeFileSync7(target, text);
17636
18435
  } catch (error) {
17637
18436
  return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
@@ -18058,19 +18857,19 @@ async function runAttempts(run, detail, first) {
18058
18857
  }
18059
18858
 
18060
18859
  // src/commands/pr/triage.ts
18061
- var USAGE38 = PR_USAGE.triage;
18860
+ var USAGE39 = PR_USAGE.triage;
18062
18861
  var DEFAULT_MAX_ATTEMPTS = 2;
18063
18862
  var DETACHED_HEAD2 = "HEAD";
18064
18863
  var RESOLVED = false;
18065
18864
  var DEFAULT_TRIAGE_SEAMS = Object.freeze({});
18066
- function readMaxAttempts(flags, usage2) {
18865
+ function readMaxAttempts(flags, usage) {
18067
18866
  const value = flags["max-attempts"];
18068
18867
  if (value === undefined)
18069
18868
  return DEFAULT_MAX_ATTEMPTS;
18070
- const spelled = typeof value === "boolean" ? "--max-attempts with no value" : `"${value}"`;
18869
+ const spelled2 = typeof value === "boolean" ? "--max-attempts with no value" : `"${value}"`;
18071
18870
  const count = typeof value === "string" ? Number(value) : Number.NaN;
18072
18871
  if (!Number.isSafeInteger(count) || count < 1) {
18073
- throw lineRefusal4(`${spelled} is no attempt count, which is a whole number from 1;` + " type the pull request number before the flags", usage2);
18872
+ throw lineRefusal5(`${spelled2} is no attempt count, which is a whole number from 1;` + " type the pull request number before the flags", usage);
18074
18873
  }
18075
18874
  return count;
18076
18875
  }
@@ -18099,22 +18898,22 @@ async function redPullRequests(pr) {
18099
18898
  }
18100
18899
  return red;
18101
18900
  }
18102
- async function selectTargets(pr, warn3, now, resolve11) {
18901
+ async function selectTargets(pr, warn3, now, resolve12) {
18103
18902
  const branch = readBranchOrNone(pr, warn3);
18104
18903
  const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
18105
18904
  if (current !== null)
18106
- return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve11 });
18905
+ return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve12 });
18107
18906
  return selectTriagePullRequests({
18108
18907
  current: null,
18109
18908
  candidates: await redPullRequests(pr),
18110
18909
  now,
18111
- resolve: resolve11
18910
+ resolve: resolve12
18112
18911
  });
18113
18912
  }
18114
18913
  async function detailOf2(pr, number) {
18115
18914
  const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
18116
18915
  if (detail === null) {
18117
- throw lineRefusal4(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE38);
18916
+ throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE39);
18118
18917
  }
18119
18918
  return detail;
18120
18919
  }
@@ -18225,14 +19024,14 @@ async function assessOne(options) {
18225
19024
  function refuseResolveWithoutComment(wantsResolve, wantsComment) {
18226
19025
  if (!wantsResolve || wantsComment)
18227
19026
  return;
18228
- throw lineRefusal4("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE38);
19027
+ throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE39);
18229
19028
  }
18230
19029
  async function runTriage(context, seams) {
18231
- const wantsComment = readBooleanFlag(context.flags, "comment", USAGE38, true);
18232
- const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE38, false);
18233
- const maxAttempts = readMaxAttempts(context.flags, USAGE38);
19030
+ const wantsComment = readBooleanFlag(context.flags, "comment", USAGE39, true);
19031
+ const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE39, false);
19032
+ const maxAttempts = readMaxAttempts(context.flags, USAGE39);
18234
19033
  refuseResolveWithoutComment(wantsResolve, wantsComment);
18235
- const asked = readPullArgument(context.args, USAGE38);
19034
+ const asked = readPullArgument(context.args, USAGE39);
18236
19035
  const pr = openPrContext(context, seams);
18237
19036
  const git = (seams.git ?? createGitRunner)(pr.project.root);
18238
19037
  const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
@@ -18245,7 +19044,7 @@ async function runTriage(context, seams) {
18245
19044
  }
18246
19045
  const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
18247
19046
  const readings = [];
18248
- let resolve11 = null;
19047
+ let resolve12 = null;
18249
19048
  for (const number of numbers) {
18250
19049
  const assess = () => assessOne({
18251
19050
  pr,
@@ -18265,7 +19064,7 @@ async function runTriage(context, seams) {
18265
19064
  readings.push(reading);
18266
19065
  continue;
18267
19066
  }
18268
- resolve11 = await resolvePullRequest({
19067
+ resolve12 = await resolvePullRequest({
18269
19068
  trust: {
18270
19069
  permissions,
18271
19070
  trustedAuthors: pr.trustedAuthors,
@@ -18287,16 +19086,16 @@ async function runTriage(context, seams) {
18287
19086
  ...seams.clock === undefined ? {} : { clock: seams.clock },
18288
19087
  ...seams.sleep === undefined ? {} : { sleep: seams.sleep }
18289
19088
  });
18290
- readings.push(resolve11.reading);
19089
+ readings.push(resolve12.reading);
18291
19090
  }
18292
19091
  const text = [
18293
19092
  ...selection === null ? [] : [selection.message],
18294
19093
  ...readings.length === 0 ? [] : [renderTriages(readings)],
18295
- ...resolve11 === null ? [] : resolve11.lines
19094
+ ...resolve12 === null ? [] : resolve12.lines
18296
19095
  ].join(`
18297
19096
 
18298
19097
  `);
18299
- return { selection, readings, resolve: resolve11, text, exitCode: resolve11?.exitCode ?? 0 };
19098
+ return { selection, readings, resolve: resolve12, text, exitCode: resolve12?.exitCode ?? 0 };
18300
19099
  }
18301
19100
  function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
18302
19101
  const command = {
@@ -18370,7 +19169,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
18370
19169
  var triage_default = createPrTriageCommand();
18371
19170
 
18372
19171
  // src/commands/pr/view.ts
18373
- var USAGE39 = PR_USAGE.view;
19172
+ var USAGE40 = PR_USAGE.view;
18374
19173
  function trimmedOrNull2(value) {
18375
19174
  const trimmed = value.trim();
18376
19175
  return trimmed === "" ? null : trimmed;
@@ -18380,9 +19179,9 @@ function renderView(number, url) {
18380
19179
  return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
18381
19180
  }
18382
19181
  async function runView2(context, seams) {
18383
- const asked = readPullArgument(context.args, USAGE39);
19182
+ const asked = readPullArgument(context.args, USAGE40);
18384
19183
  const pr = openPrContext(context, seams);
18385
- const pick = await pickPullRequest(pr, asked, USAGE39);
19184
+ const pick = await pickPullRequest(pr, asked, USAGE40);
18386
19185
  await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
18387
19186
  const url = trimmedOrNull2(pick.summary?.url ?? "");
18388
19187
  return {
@@ -18437,8 +19236,8 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
18437
19236
  var view_default = createPrViewCommand();
18438
19237
 
18439
19238
  // src/commands/pr/wait.ts
18440
- var USAGE40 = PR_USAGE.wait;
18441
- var TIMEOUT_FLAG = "timeout";
19239
+ var USAGE41 = PR_USAGE.wait;
19240
+ var TIMEOUT_FLAG2 = "timeout";
18442
19241
  var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
18443
19242
  var MS_PER_MIN = 60000;
18444
19243
  var MS_PER_SECOND2 = 1000;
@@ -18450,14 +19249,14 @@ var WAIT_EXIT_CODES = Object.freeze({
18450
19249
  timeout: 3
18451
19250
  });
18452
19251
  var DEFAULT_WAIT_SEAMS = Object.freeze({});
18453
- function readTimeoutMinutes(flags, usage2) {
18454
- const value = flags[TIMEOUT_FLAG];
19252
+ function readTimeoutMinutes(flags, usage) {
19253
+ const value = flags[TIMEOUT_FLAG2];
18455
19254
  if (value === undefined)
18456
19255
  return DEFAULT_WAIT_TIMEOUT_MIN;
18457
- const spelled = typeof value === "boolean" ? `--${TIMEOUT_FLAG} with no value` : `"${value}"`;
19256
+ const spelled2 = typeof value === "boolean" ? `--${TIMEOUT_FLAG2} with no value` : `"${value}"`;
18458
19257
  const minutes = typeof value === "string" ? Number(value) : Number.NaN;
18459
19258
  if (!Number.isSafeInteger(minutes) || minutes < 1) {
18460
- throw lineRefusal4(`${spelled} is no timeout, which is a whole number of minutes from 1;` + " type the pull request number before the flags", usage2);
19259
+ throw lineRefusal5(`${spelled2} is no timeout, which is a whole number of minutes from 1;` + " type the pull request number before the flags", usage);
18461
19260
  }
18462
19261
  return minutes;
18463
19262
  }
@@ -18503,10 +19302,10 @@ function renderWait(reading) {
18503
19302
  `);
18504
19303
  }
18505
19304
  async function runWait(context, seams) {
18506
- const minutes = readTimeoutMinutes(context.flags, USAGE40);
18507
- const asked = readPullArgument(context.args, USAGE40);
19305
+ const minutes = readTimeoutMinutes(context.flags, USAGE41);
19306
+ const asked = readPullArgument(context.args, USAGE41);
18508
19307
  const pr = openPrContext(context, seams);
18509
- const pick = await pickPullRequest(pr, asked, USAGE40);
19308
+ const pick = await pickPullRequest(pr, asked, USAGE41);
18510
19309
  const timeoutMs = minutes * MS_PER_MIN;
18511
19310
  context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
18512
19311
  const waited = await waitForChecks({
@@ -18548,7 +19347,7 @@ function createPrWaitCommand(seams = DEFAULT_WAIT_SEAMS) {
18548
19347
  ],
18549
19348
  flags: [
18550
19349
  {
18551
- name: TIMEOUT_FLAG,
19350
+ name: TIMEOUT_FLAG2,
18552
19351
  description: `How many minutes to wait before giving up. ${DEFAULT_WAIT_TIMEOUT_MIN} when it is left out.`,
18553
19352
  type: "string"
18554
19353
  },
@@ -18582,7 +19381,7 @@ function createPrWaitCommand(seams = DEFAULT_WAIT_SEAMS) {
18582
19381
  };
18583
19382
  return Object.freeze(command);
18584
19383
  }
18585
- var wait_default = createPrWaitCommand();
19384
+ var wait_default2 = createPrWaitCommand();
18586
19385
 
18587
19386
  // src/release/settle-pr.ts
18588
19387
  var RELEASE_PR_BRANCH = "rafa/release";
@@ -18861,9 +19660,9 @@ function tagSettle(worktree, mode, delivered) {
18861
19660
  }
18862
19661
 
18863
19662
  // src/release/settle-worktree.ts
18864
- import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync3, rmSync as rmSync2 } from "fs";
19663
+ import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync2 } from "fs";
18865
19664
  import { tmpdir as tmpdir2 } from "os";
18866
- import { join as join32 } from "path";
19665
+ import { join as join33 } from "path";
18867
19666
  var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
18868
19667
  var WORKTREE_DIRECTORY = "base";
18869
19668
  function removeScratch(git, scratch, path) {
@@ -18887,7 +19686,7 @@ function removeScratch(git, scratch, path) {
18887
19686
  }
18888
19687
  function makeScratch(root) {
18889
19688
  try {
18890
- return realpathSync3(mkdtempSync2(join32(root, SETTLE_SCRATCH_PREFIX)));
19689
+ return realpathSync4(mkdtempSync2(join33(root, SETTLE_SCRATCH_PREFIX)));
18891
19690
  } catch (error) {
18892
19691
  return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
18893
19692
  }
@@ -18909,7 +19708,7 @@ function addWorktree(options) {
18909
19708
  const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
18910
19709
  if (typeof scratch !== "string")
18911
19710
  return scratch;
18912
- const path = join32(scratch, WORKTREE_DIRECTORY);
19711
+ const path = join33(scratch, WORKTREE_DIRECTORY);
18913
19712
  const added = git(["worktree", "add", "--detach", path, commit]);
18914
19713
  if (!added.ok) {
18915
19714
  return {
@@ -18942,7 +19741,7 @@ async function withSettleWorktree(options, body) {
18942
19741
  var RELEASE_SETTLE_USAGE = "rafa release settle [--dry-run]";
18943
19742
  var COMMAND_NAME12 = "rafa release settle";
18944
19743
  var DRY_RUN_FLAG6 = "dry-run";
18945
- var INDENT8 = " ";
19744
+ var INDENT9 = " ";
18946
19745
  var SHORT_HASH4 = 12;
18947
19746
  var DEFAULT_RELEASE_SETTLE_SEAMS = Object.freeze({});
18948
19747
  function projectOf9(context) {
@@ -18976,7 +19775,7 @@ function readingLines(reading, ref8) {
18976
19775
  lines.push("Fragments, in the order the base received them:");
18977
19776
  reading.fragments.forEach((each, index) => {
18978
19777
  const { level, title } = each.fragment;
18979
- lines.push(`${INDENT8}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
19778
+ lines.push(`${INDENT9}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
18980
19779
  });
18981
19780
  }
18982
19781
  if (reading.outcome === "folded" || reading.outcome === "built" || reading.outcome === "unbuilt") {
@@ -18996,7 +19795,7 @@ function unwrittenEnding(reading) {
18996
19795
  return { line: "Nothing to settle: no fragment waits that ships a release.", refuses: false };
18997
19796
  }
18998
19797
  }
18999
- function resultOf5(base, lines, problem2) {
19798
+ function resultOf6(base, lines, problem2) {
19000
19799
  const exitCode = problem2 === null ? 0 : 1;
19001
19800
  return { ...base, lines, problem: problem2, exitCode };
19002
19801
  }
@@ -19005,10 +19804,10 @@ function dryRunResult(reading, ref8, branch) {
19005
19804
  const lines = [...readingLines(reading, ref8)];
19006
19805
  if (reading.outcome === "folded") {
19007
19806
  lines.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
19008
- return resultOf5(base, lines, null);
19807
+ return resultOf6(base, lines, null);
19009
19808
  }
19010
19809
  const ending = unwrittenEnding(reading);
19011
- return ending.refuses ? resultOf5(base, lines, ending.line) : resultOf5(base, [...lines, ending.line], null);
19810
+ return ending.refuses ? resultOf6(base, lines, ending.line) : resultOf6(base, [...lines, ending.line], null);
19012
19811
  }
19013
19812
  function deliveryEnding(delivered, branch) {
19014
19813
  const { outcome } = delivered;
@@ -19040,15 +19839,15 @@ function settledResult(delivered, tag, ref8, branch) {
19040
19839
  const lines = [...readingLines(reading, ref8)];
19041
19840
  const ending = deliveryEnding(delivered, branch);
19042
19841
  if (ending.refuses)
19043
- return resultOf5(base, lines, ending.line);
19842
+ return resultOf6(base, lines, ending.line);
19044
19843
  lines.push(ending.line);
19045
19844
  if (tag.outcome === "failed")
19046
- return resultOf5(base, lines, `\u274C ${tag.sentence}`);
19845
+ return resultOf6(base, lines, `\u274C ${tag.sentence}`);
19047
19846
  if (tag.outcome === "tagged")
19048
19847
  lines.push(`\u2705 ${tag.sentence}`);
19049
19848
  if (tag.outcome === "skipped" && tag.sentence !== null)
19050
19849
  lines.push(`Tag: ${tag.sentence}`);
19051
- return resultOf5(base, lines, null);
19850
+ return resultOf6(base, lines, null);
19052
19851
  }
19053
19852
  function providerFor(project, config, seams) {
19054
19853
  requireGhProvider(resolvePrProvider({ configured: config.prProvider, dir: project.root, readRemote: seams.readRemote }));
@@ -19132,7 +19931,7 @@ var settle_default = createReleaseSettleCommand();
19132
19931
 
19133
19932
  // src/commands/release/tag.ts
19134
19933
  import { readFileSync as readFileSync20 } from "fs";
19135
- import { join as join33 } from "path";
19934
+ import { join as join34 } from "path";
19136
19935
 
19137
19936
  // src/commands/release/release-commit.ts
19138
19937
  function unread(reason, message) {
@@ -19175,7 +19974,7 @@ var RELEASE_TAG_USAGE = "rafa release tag";
19175
19974
  var DEFAULT_RELEASE_BRANCH = "main";
19176
19975
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
19177
19976
  var RELEASE_REMOTE2 = "origin";
19178
- var INDENT9 = " ";
19977
+ var INDENT10 = " ";
19179
19978
  var SHORT_COMMIT = 7;
19180
19979
  function readBranch2(git) {
19181
19980
  const result2 = git(["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -19196,7 +19995,7 @@ function textOf(path) {
19196
19995
  }
19197
19996
  }
19198
19997
  function readVersion(root, configured) {
19199
- const resolved = join33(root, configured);
19998
+ const resolved = join34(root, configured);
19200
19999
  const text = textOf(resolved);
19201
20000
  if (text === null) {
19202
20001
  return {
@@ -19210,7 +20009,7 @@ function readVersion(root, configured) {
19210
20009
  return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
19211
20010
  }
19212
20011
  function readNewestRelease(root, configured) {
19213
- const resolved = join33(root, configured);
20012
+ const resolved = join34(root, configured);
19214
20013
  const text = textOf(resolved);
19215
20014
  if (text === null) {
19216
20015
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -19370,7 +20169,7 @@ function renderTagged(written, branch, followUps) {
19370
20169
  return [
19371
20170
  `\u2705 Tagged ${written.tag} at ${where}.`,
19372
20171
  "Next:",
19373
- ...followUps.map((followUp2) => `${INDENT9}${followUp2.command} \u2014 ${followUp2.why}`)
20172
+ ...followUps.map((followUp2) => `${INDENT10}${followUp2.command} \u2014 ${followUp2.why}`)
19374
20173
  ];
19375
20174
  }
19376
20175
  function runTag(context, seams = DEFAULT_RELEASE_SEAMS) {
@@ -19480,17 +20279,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
19480
20279
  var roadmap_default = createRoadmapCommand();
19481
20280
 
19482
20281
  // src/commands/skill/backfill.ts
19483
- import { statSync as statSync9 } from "fs";
19484
- import { join as join38, resolve as resolve13 } from "path";
20282
+ import { statSync as statSync10 } from "fs";
20283
+ import { join as join39, resolve as resolve14 } from "path";
19485
20284
 
19486
20285
  // src/backfill/backup.ts
19487
- import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
19488
- import { dirname as dirname8, isAbsolute as isAbsolute6, join as join35, relative as relative7, resolve as resolve11, sep as sep4 } from "path";
20286
+ import { copyFileSync as copyFileSync4, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
20287
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join36, relative as relative9, resolve as resolve12, sep as sep5 } from "path";
19489
20288
 
19490
20289
  // src/backfill/proposal-file.ts
19491
20290
  import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
19492
- import { join as join34 } from "path";
19493
- var BACKFILL_PATH = join34(".rafa", "backfill");
20291
+ import { join as join35 } from "path";
20292
+ var BACKFILL_PATH = join35(".rafa", "backfill");
19494
20293
  var PROPOSAL_FILE_PREFIX = "proposals-";
19495
20294
  var PROPOSAL_FILE_EXTENSION = ".yaml";
19496
20295
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
@@ -19516,7 +20315,7 @@ var PROPOSAL_FILE_HEADER = [
19516
20315
  ].join(`
19517
20316
  `);
19518
20317
  function proposalPath(dir, batch) {
19519
- return join34(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
20318
+ return join35(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
19520
20319
  }
19521
20320
  function isProposalFileName(name) {
19522
20321
  return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
@@ -19652,7 +20451,7 @@ function readProposalFiles(dir) {
19652
20451
  return [];
19653
20452
  }
19654
20453
  return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
19655
- const path = join34(dir, name);
20454
+ const path = join35(dir, name);
19656
20455
  try {
19657
20456
  return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
19658
20457
  } catch (error) {
@@ -19662,19 +20461,19 @@ function readProposalFiles(dir) {
19662
20461
  }
19663
20462
 
19664
20463
  // src/backfill/backup.ts
19665
- var BACKUP_PATH = join35(BACKFILL_PATH, "backup");
20464
+ var BACKUP_PATH = join36(BACKFILL_PATH, "backup");
19666
20465
  var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
19667
20466
  function backupDirectory(base) {
19668
- return join35(base, BACKUP_PATH);
20467
+ return join36(base, BACKUP_PATH);
19669
20468
  }
19670
20469
  function isUnder(root, path) {
19671
- const step = relative7(resolve11(root), resolve11(path));
19672
- return step === "" || !step.startsWith(`..${sep4}`) && step !== ".." && !isAbsolute6(step);
20470
+ const step = relative9(resolve12(root), resolve12(path));
20471
+ return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute7(step);
19673
20472
  }
19674
20473
  function backupPathFor(path, root, backupDir) {
19675
20474
  if (!isUnder(root, path))
19676
20475
  return null;
19677
- return join35(backupDir, relative7(resolve11(root), resolve11(path)));
20476
+ return join36(backupDir, relative9(resolve12(root), resolve12(path)));
19678
20477
  }
19679
20478
  function backupFile(path, options) {
19680
20479
  if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
@@ -19687,8 +20486,8 @@ function backupFile(path, options) {
19687
20486
  return { path, kind: "missing", backup: null };
19688
20487
  if (existsSync11(backup))
19689
20488
  return { path, kind: "kept", backup };
19690
- mkdirSync7(dirname8(backup), { recursive: true });
19691
- copyFileSync3(path, backup);
20489
+ mkdirSync8(dirname9(backup), { recursive: true });
20490
+ copyFileSync4(path, backup);
19692
20491
  return { path, kind: "copied", backup };
19693
20492
  }
19694
20493
  function backupFiles(paths, options) {
@@ -19741,8 +20540,8 @@ function triggerSentence(body) {
19741
20540
  const section = bodySections(body).find((entry) => WHEN_TO_USE_HEADING.test(entry.title));
19742
20541
  return section === undefined ? "" : firstSentence(section.text);
19743
20542
  }
19744
- function trimWhenToUse(description, whenToUse) {
19745
- const budget = LISTING_LIMIT - 1 - countCharacters(description);
20543
+ function trimWhenToUse(description2, whenToUse) {
20544
+ const budget = LISTING_LIMIT - 1 - countCharacters(description2);
19746
20545
  if (budget <= 0)
19747
20546
  return "";
19748
20547
  if (countCharacters(whenToUse) <= budget)
@@ -19912,7 +20711,7 @@ function applyDerivation(plan2, options) {
19912
20711
 
19913
20712
  // src/backfill/proposal-batch.ts
19914
20713
  import { readFileSync as readFileSync23 } from "fs";
19915
- import { relative as relative8 } from "path";
20714
+ import { relative as relative10 } from "path";
19916
20715
 
19917
20716
  // src/demote/report.ts
19918
20717
  import { createHash as createHash2 } from "crypto";
@@ -20201,7 +21000,7 @@ function candidateNeeds(data, body) {
20201
21000
  return needs;
20202
21001
  }
20203
21002
  function posixRelative(root, path) {
20204
- return relative8(root, path).split("\\").join("/");
21003
+ return relative10(root, path).split("\\").join("/");
20205
21004
  }
20206
21005
  function readCandidate(root, entry) {
20207
21006
  if (!entry.isFile || entry.name === null)
@@ -20316,8 +21115,8 @@ function parseSessionAnswer(stdout) {
20316
21115
  }
20317
21116
 
20318
21117
  // src/backfill/propose.ts
20319
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
20320
- import { join as join36, resolve as resolve12 } from "path";
21118
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
21119
+ import { join as join37, resolve as resolve13 } from "path";
20321
21120
  var PROPOSAL_ACTION_KINDS = [
20322
21121
  "applied",
20323
21122
  "unchanged",
@@ -20396,7 +21195,7 @@ async function runProposalPass(options) {
20396
21195
  const batches = batchCandidates(candidates);
20397
21196
  const written = [];
20398
21197
  if (batches.length > 0)
20399
- mkdirSync8(options.backfillDir, { recursive: true });
21198
+ mkdirSync9(options.backfillDir, { recursive: true });
20400
21199
  for (const [index, batch] of batches.entries()) {
20401
21200
  const number = index + 1;
20402
21201
  const file = await runProposalBatch(number, batch, options);
@@ -20466,7 +21265,7 @@ function writeRow(path, row2, original, options) {
20466
21265
  };
20467
21266
  }
20468
21267
  function applyRow(root, row2, options) {
20469
- const path = join36(root, ...row2.path.split("/"));
21268
+ const path = join37(root, ...row2.path.split("/"));
20470
21269
  if (row2.status === UNANSWERED_ROW) {
20471
21270
  return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
20472
21271
  }
@@ -20489,7 +21288,7 @@ function fileRefusal(file, root) {
20489
21288
  if (file.status !== REVIEWED_FILE) {
20490
21289
  return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
20491
21290
  }
20492
- return resolve12(file.skills) === resolve12(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
21291
+ return resolve13(file.skills) === resolve13(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
20493
21292
  }
20494
21293
  function countProposalActions(actions) {
20495
21294
  const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
@@ -20503,7 +21302,7 @@ function applyProposals(files, root, options) {
20503
21302
  for (const file of files) {
20504
21303
  const refusal13 = fileRefusal(file, root);
20505
21304
  for (const row2 of file.rows) {
20506
- const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join36(root, ...row2.path.split("/")), row2, "refused", refusal13);
21305
+ const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join37(root, ...row2.path.split("/")), row2, "refused", refusal13);
20507
21306
  actions.push(action);
20508
21307
  if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
20509
21308
  triggers[row2.name] = row2.trigger;
@@ -20514,17 +21313,17 @@ function applyProposals(files, root, options) {
20514
21313
  }
20515
21314
 
20516
21315
  // src/demote/select.ts
20517
- import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync8 } from "fs";
20518
- import { basename as basename7, dirname as dirname9, join as join37 } from "path";
21316
+ import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
21317
+ import { basename as basename7, dirname as dirname10, join as join38 } from "path";
20519
21318
  var SKILLS_DIRECTORY = "skills";
20520
21319
  var CLAUDE_DIRECTORY = ".claude";
20521
21320
  var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
20522
- var DEMOTED_PATH = join37(".rafa", "demoted");
20523
- var INSTINCTS_PATH = join37(".rafa", "instincts");
21321
+ var DEMOTED_PATH = join38(".rafa", "demoted");
21322
+ var INSTINCTS_PATH = join38(".rafa", "instincts");
20524
21323
  var REPORT_FILE = "report.md";
20525
21324
  function realOrSelf(path) {
20526
21325
  try {
20527
- return realpathSync4(path);
21326
+ return realpathSync5(path);
20528
21327
  } catch {
20529
21328
  return path;
20530
21329
  }
@@ -20539,23 +21338,23 @@ function samePath(left, right) {
20539
21338
  function resolveDemotionScope(dir, home) {
20540
21339
  if (basename7(dir) !== SKILLS_DIRECTORY)
20541
21340
  return null;
20542
- const claude = dirname9(dir);
21341
+ const claude = dirname10(dir);
20543
21342
  if (basename7(claude) !== CLAUDE_DIRECTORY)
20544
21343
  return null;
20545
- const base = dirname9(claude);
20546
- const demotedDir = join37(base, DEMOTED_PATH);
21344
+ const base = dirname10(claude);
21345
+ const demotedDir = join38(base, DEMOTED_PATH);
20547
21346
  return {
20548
21347
  scope: samePath(base, home) ? "user" : "project",
20549
21348
  dir,
20550
21349
  base,
20551
21350
  demotedDir,
20552
- instinctsDir: join37(base, INSTINCTS_PATH),
20553
- reportPath: join37(demotedDir, REPORT_FILE)
21351
+ instinctsDir: join38(base, INSTINCTS_PATH),
21352
+ reportPath: join38(demotedDir, REPORT_FILE)
20554
21353
  };
20555
21354
  }
20556
21355
  function entryKind(path) {
20557
21356
  try {
20558
- const stats = statSync8(path);
21357
+ const stats = statSync9(path);
20559
21358
  if (stats.isFile())
20560
21359
  return "file";
20561
21360
  if (stats.isDirectory())
@@ -20577,7 +21376,7 @@ function readSelected(absolute, path, entries) {
20577
21376
  let mtime;
20578
21377
  try {
20579
21378
  text = readFileSync25(absolute, "utf8");
20580
- mtime = statSync8(absolute).mtime.toISOString();
21379
+ mtime = statSync9(absolute).mtime.toISOString();
20581
21380
  } catch {
20582
21381
  return null;
20583
21382
  }
@@ -20592,14 +21391,14 @@ function selectsLearned(text, scope) {
20592
21391
  return origin === AUTO_EXTRACTED_ORIGIN;
20593
21392
  }
20594
21393
  function selectLearned(scope) {
20595
- const dir = join37(scope.dir, LEARNED_DIRECTORY);
21394
+ const dir = join38(scope.dir, LEARNED_DIRECTORY);
20596
21395
  if (entryKind(dir) !== "directory")
20597
21396
  return [];
20598
21397
  const selected = [];
20599
21398
  for (const name of visibleNames(dir)) {
20600
21399
  if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
20601
21400
  continue;
20602
- const absolute = join37(dir, name);
21401
+ const absolute = join38(dir, name);
20603
21402
  if (entryKind(absolute) !== "file")
20604
21403
  continue;
20605
21404
  const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
@@ -20613,10 +21412,10 @@ function selectSkillFiles(scope) {
20613
21412
  for (const name of visibleNames(scope.dir)) {
20614
21413
  if (name === LEARNED_DIRECTORY)
20615
21414
  continue;
20616
- const skillDir = join37(scope.dir, name);
21415
+ const skillDir = join38(scope.dir, name);
20617
21416
  if (entryKind(skillDir) !== "directory")
20618
21417
  continue;
20619
- const absolute = join37(skillDir, SKILL_FILE);
21418
+ const absolute = join38(skillDir, SKILL_FILE);
20620
21419
  if (entryKind(absolute) !== "file")
20621
21420
  continue;
20622
21421
  const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
@@ -20664,7 +21463,7 @@ Usage: ${BACKFILL_USAGE}`);
20664
21463
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
20665
21464
  Usage: ${BACKFILL_USAGE}`);
20666
21465
  }
20667
- return resolve13(cwd(), dir);
21466
+ return resolve14(cwd(), dir);
20668
21467
  }
20669
21468
  function readConsumerRoot(value, cwd) {
20670
21469
  if (value === undefined || value === false)
@@ -20673,11 +21472,11 @@ function readConsumerRoot(value, cwd) {
20673
21472
  throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
20674
21473
  Usage: ${BACKFILL_USAGE}`);
20675
21474
  }
20676
- return resolve13(cwd(), value);
21475
+ return resolve14(cwd(), value);
20677
21476
  }
20678
21477
  function isDirectory3(path) {
20679
21478
  try {
20680
- return statSync9(path).isDirectory();
21479
+ return statSync10(path).isDirectory();
20681
21480
  } catch {
20682
21481
  return false;
20683
21482
  }
@@ -20801,7 +21600,7 @@ function reviewedProposals(dir) {
20801
21600
  return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
20802
21601
  }
20803
21602
  function rowPaths(files, root) {
20804
- return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join38(root, ...row2.path.split("/"))));
21603
+ return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join39(root, ...row2.path.split("/"))));
20805
21604
  }
20806
21605
  function runApply(context, scope, backfillDir, projectRoot2) {
20807
21606
  const options = checkOptions(context, projectRoot2);
@@ -20847,7 +21646,7 @@ async function runBackfill(context, seams) {
20847
21646
  const dir = readBackfillDirectory(context.args, seams.cwd);
20848
21647
  const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
20849
21648
  const scope = backfillScopeOf(dir, projectOf11(context).home);
20850
- const backfillDir = join38(scope.base, BACKFILL_PATH);
21649
+ const backfillDir = join39(scope.base, BACKFILL_PATH);
20851
21650
  if (mode === "propose") {
20852
21651
  await runPropose(context, scope, backfillDir, seams);
20853
21652
  return;
@@ -20961,13 +21760,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
20961
21760
  var check_default3 = createSkillCheckCommand();
20962
21761
 
20963
21762
  // src/commands/skill/demote.ts
20964
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync27, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
20965
- import { dirname as dirname11, resolve as resolve14 } from "path";
21763
+ import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync27, statSync as statSync12, writeFileSync as writeFileSync11 } from "fs";
21764
+ import { dirname as dirname12, resolve as resolve15 } from "path";
20966
21765
 
20967
21766
  // src/demote/apply.ts
20968
- import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
21767
+ import { copyFileSync as copyFileSync5, existsSync as existsSync12, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
20969
21768
  import { tmpdir as tmpdir3 } from "os";
20970
- import { dirname as dirname10, join as join39 } from "path";
21769
+ import { dirname as dirname11, join as join40 } from "path";
20971
21770
  var DEMOTION_ACTION_KINDS = [
20972
21771
  "demoted",
20973
21772
  "kept",
@@ -20988,13 +21787,13 @@ function learnedSkillName(path) {
20988
21787
  return base.slice(0, -MARKDOWN_EXTENSION.length);
20989
21788
  }
20990
21789
  function sourcePath(scope, path) {
20991
- return join39(scope.dir, ...path.split("/"));
21790
+ return join40(scope.dir, ...path.split("/"));
20992
21791
  }
20993
21792
  function demotedPath(scope, path) {
20994
- return join39(scope.demotedDir, ...path.split("/"));
21793
+ return join40(scope.demotedDir, ...path.split("/"));
20995
21794
  }
20996
21795
  function promotedPath(scope, path) {
20997
- return join39(scope.dir, learnedSkillName(path), SKILL_FILE);
21796
+ return join40(scope.dir, learnedSkillName(path), SKILL_FILE);
20998
21797
  }
20999
21798
  function takenInstinctIds(dir) {
21000
21799
  let names;
@@ -21007,7 +21806,7 @@ function takenInstinctIds(dir) {
21007
21806
  }
21008
21807
  function readText4(path) {
21009
21808
  try {
21010
- return statSync10(path).isFile() ? readFileSync26(path, "utf8") : null;
21809
+ return statSync11(path).isFile() ? readFileSync26(path, "utf8") : null;
21011
21810
  } catch {
21012
21811
  return null;
21013
21812
  }
@@ -21035,7 +21834,7 @@ function planKept(scope, row2, file) {
21035
21834
  return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
21036
21835
  }
21037
21836
  const to = promotedPath(scope, row2.path);
21038
- if (existsSync12(to) || existsSync12(dirname10(to))) {
21837
+ if (existsSync12(to) || existsSync12(dirname11(to))) {
21039
21838
  return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
21040
21839
  }
21041
21840
  return {
@@ -21049,7 +21848,7 @@ function planKept(scope, row2, file) {
21049
21848
  };
21050
21849
  }
21051
21850
  function checkCandidate(dir, id2, text, pathDirs) {
21052
- const path = join39(dir, `${id2}${MARKDOWN_EXTENSION}`);
21851
+ const path = join40(dir, `${id2}${MARKDOWN_EXTENSION}`);
21053
21852
  writeFileSync10(path, text, "utf8");
21054
21853
  const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
21055
21854
  return failureLines2(report4.issues);
@@ -21070,7 +21869,7 @@ function planDemoted(scope, row2, file, context) {
21070
21869
  if (failures.length > 0) {
21071
21870
  return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
21072
21871
  }
21073
- const destination = join39(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
21872
+ const destination = join40(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
21074
21873
  const kept = demotedPath(scope, row2.path);
21075
21874
  if (existsSync12(destination))
21076
21875
  return inert3(row2.path, "observation", "refused", `${destination} is already there`);
@@ -21106,7 +21905,7 @@ function planRow(scope, row2, context) {
21106
21905
  entries: undefined,
21107
21906
  absolute,
21108
21907
  hash,
21109
- mtime: statSync10(absolute).mtime.toISOString()
21908
+ mtime: statSync11(absolute).mtime.toISOString()
21110
21909
  };
21111
21910
  return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
21112
21911
  }
@@ -21117,7 +21916,7 @@ function countActions(actions) {
21117
21916
  return counts;
21118
21917
  }
21119
21918
  function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
21120
- const temp = mkdtempSync3(join39(tmpdir3(), "rafa-demote-"));
21919
+ const temp = mkdtempSync3(join40(tmpdir3(), "rafa-demote-"));
21121
21920
  const taken = [...takenInstinctIds(scope.instinctsDir)];
21122
21921
  try {
21123
21922
  const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
@@ -21127,18 +21926,18 @@ function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
21127
21926
  }
21128
21927
  }
21129
21928
  function moveFile(move3) {
21130
- mkdirSync9(dirname10(move3.to), { recursive: true });
21929
+ mkdirSync10(dirname11(move3.to), { recursive: true });
21131
21930
  try {
21132
21931
  renameSync(move3.from, move3.to);
21133
21932
  } catch {
21134
- copyFileSync4(move3.from, move3.to);
21933
+ copyFileSync5(move3.from, move3.to);
21135
21934
  unlinkSync(move3.from);
21136
21935
  }
21137
21936
  }
21138
21937
  function applyDemotion(plan2) {
21139
21938
  for (const action of plan2.actions) {
21140
21939
  if (action.write !== null) {
21141
- mkdirSync9(dirname10(action.write.path), { recursive: true });
21940
+ mkdirSync10(dirname11(action.write.path), { recursive: true });
21142
21941
  writeFileSync10(action.write.path, action.write.text, "utf8");
21143
21942
  }
21144
21943
  if (action.move !== null)
@@ -21204,11 +22003,11 @@ Usage: ${DEMOTE_USAGE}`);
21204
22003
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
21205
22004
  Usage: ${DEMOTE_USAGE}`);
21206
22005
  }
21207
- return resolve14(cwd(), dir);
22006
+ return resolve15(cwd(), dir);
21208
22007
  }
21209
22008
  function isDirectory4(path) {
21210
22009
  try {
21211
- return statSync11(path).isDirectory();
22010
+ return statSync12(path).isDirectory();
21212
22011
  } catch {
21213
22012
  return false;
21214
22013
  }
@@ -21247,7 +22046,7 @@ function previousReport(path, context) {
21247
22046
  function runWrite(context, scope) {
21248
22047
  const files = selectSources(scope);
21249
22048
  const report4 = buildDemotionReport(files, previousReport(scope.reportPath, context));
21250
- mkdirSync10(dirname11(scope.reportPath), { recursive: true });
22049
+ mkdirSync11(dirname12(scope.reportPath), { recursive: true });
21251
22050
  writeFileSync11(scope.reportPath, writeDemotionReport(report4), "utf8");
21252
22051
  const result2 = {
21253
22052
  applied: false,
@@ -21381,7 +22180,7 @@ var demote_default = createSkillDemoteCommand();
21381
22180
  // src/commands/skill/show.ts
21382
22181
  var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
21383
22182
  var COMMAND_NAME13 = "rafa skill show";
21384
- var USAGE41 = "rafa skill show <name> [--full]";
22183
+ var USAGE42 = "rafa skill show <name> [--full]";
21385
22184
  function unknownSkillMessage(name, inventory) {
21386
22185
  const agent = findShown(inventory.records, "agent", name);
21387
22186
  const hint = agent === null ? [] : [`An agent is named "${name}" (${agent.source}): rafa agent show ${name}`];
@@ -21389,7 +22188,7 @@ function unknownSkillMessage(name, inventory) {
21389
22188
  `\u274C No skill is named "${name}".`,
21390
22189
  ...hint,
21391
22190
  "Run `rafa skill list` to see every skill and where it comes from.",
21392
- `Usage: ${USAGE41}`
22191
+ `Usage: ${USAGE42}`
21393
22192
  ].join(`
21394
22193
  `);
21395
22194
  }
@@ -21405,8 +22204,8 @@ function projectOf13(context) {
21405
22204
  return context.project;
21406
22205
  }
21407
22206
  async function runShow3(context, seams) {
21408
- const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE41}`);
21409
- const name = expectOneArgument(context.args, USAGE41);
22207
+ const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE42}`);
22208
+ const name = expectOneArgument(context.args, USAGE42);
21410
22209
  const project = projectOf13(context);
21411
22210
  const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME13);
21412
22211
  const warnings = warningLines(inventory);
@@ -21506,7 +22305,7 @@ var STATUS_SECTION_TITLES = Object.freeze({
21506
22305
  claims: "Claims",
21507
22306
  housekeeping: "Housekeeping"
21508
22307
  });
21509
- var INDENT10 = " ";
22308
+ var INDENT11 = " ";
21510
22309
  var MERGEABILITY_WORDS = Object.freeze({
21511
22310
  mergeable: "mergeable",
21512
22311
  conflicting: "conflicting",
@@ -21518,8 +22317,8 @@ var VERDICT_WORDS = Object.freeze({
21518
22317
  pending: "checks pending",
21519
22318
  none: "no checks"
21520
22319
  });
21521
- function counted3(count, noun, plural3 = `${noun}s`) {
21522
- return `${String(count)} ${count === 1 ? noun : plural3}`;
22320
+ function counted3(count, noun, plural4 = `${noun}s`) {
22321
+ return `${String(count)} ${count === 1 ? noun : plural4}`;
21523
22322
  }
21524
22323
  function info2(text) {
21525
22324
  return { level: "info", text };
@@ -21536,10 +22335,10 @@ function loopsText(reading) {
21536
22335
  return `${String(reading.live.length)} running, ${counted3(tasks, "task")} blocked`;
21537
22336
  }
21538
22337
  function loopsBody(reading) {
21539
- const running = reading.live.map((record) => info2(`${INDENT10}${sessionLine(record)}`));
22338
+ const running = reading.live.map((record) => info2(`${INDENT11}${sessionLine(record)}`));
21540
22339
  const blocked = reading.blocked.flatMap((one) => one.tasks.map((task) => {
21541
22340
  const blocker = task.blocker === null ? "" : ` (${task.blocker})`;
21542
- return info2(`${INDENT10}blocked: plan \`${planLabel(one.session)}\` line ${String(task.line)}: ${task.text}${blocker}`);
22341
+ return info2(`${INDENT11}blocked: plan \`${planLabel(one.session)}\` line ${String(task.line)}: ${task.text}${blocker}`);
21543
22342
  }));
21544
22343
  return [...running, ...blocked];
21545
22344
  }
@@ -21567,10 +22366,10 @@ function boardBody(reading) {
21567
22366
  return [];
21568
22367
  const away = awayLine(place2.current, place2.home);
21569
22368
  return [
21570
- info2(`${INDENT10}${placeLine2(place2.view)}`),
21571
- ...away === null ? [] : [info2(`${INDENT10}${away}`)],
21572
- ...place2.waiting === undefined ? [] : [info2(`${INDENT10}${waitingLine(place2.waiting.issue)}`)],
21573
- ...place2.notices.map((notice) => ({ level: "warn", text: `${INDENT10}${notice}` }))
22369
+ info2(`${INDENT11}${placeLine2(place2.view)}`),
22370
+ ...away === null ? [] : [info2(`${INDENT11}${away}`)],
22371
+ ...place2.waiting === undefined ? [] : [info2(`${INDENT11}${waitingLine(place2.waiting.issue)}`)],
22372
+ ...place2.notices.map((notice) => ({ level: "warn", text: `${INDENT11}${notice}` }))
21574
22373
  ];
21575
22374
  }
21576
22375
  function nextStanding(next) {
@@ -21608,7 +22407,7 @@ function claimLine(row2) {
21608
22407
  return `#${String(row2.issue)} \`${row2.branch}\`: ${claimHolder(row2)}, ${claimStage(row2.stage)}, ${claimStanding(row2)}`;
21609
22408
  }
21610
22409
  function claimsBody(reading) {
21611
- return reading.claims.map((row2) => info2(`${INDENT10}${claimLine(row2)}`));
22410
+ return reading.claims.map((row2) => info2(`${INDENT11}${claimLine(row2)}`));
21612
22411
  }
21613
22412
  function housekeepingText(reading) {
21614
22413
  const { merged: merged2, stale, notPushed, worktrees } = reading.counts;
@@ -21698,7 +22497,7 @@ function labelsOf2(labels, issue2) {
21698
22497
  return null;
21699
22498
  return labels.issues.find((one) => one.number === issue2)?.labels ?? null;
21700
22499
  }
21701
- function rowOf3(reading, input) {
22500
+ function rowOf4(reading, input) {
21702
22501
  const { ownership, tipCommittedAt, branch } = reading;
21703
22502
  const issue2 = claimBranchIssue(branch) ?? 0;
21704
22503
  const labels = labelsOf2(input.labels, issue2);
@@ -21724,7 +22523,7 @@ function labelNotes(rows, labels) {
21724
22523
  return rows.filter((row2) => row2.stage === null).map((row2) => `#${String(row2.issue)} is not in the board listing, so its stage labels were not read`);
21725
22524
  }
21726
22525
  function readClaims2(input) {
21727
- const claims = input.branches.found.map((reading) => rowOf3(reading, input));
22526
+ const claims = input.branches.found.map((reading) => rowOf4(reading, input));
21728
22527
  return { claims, notes: [...input.branches.notes, ...labelNotes(claims, input.labels)] };
21729
22528
  }
21730
22529
 
@@ -21815,9 +22614,9 @@ function boundedRunner(open, root, deadline) {
21815
22614
  }
21816
22615
  async function networkSection(what, deadline, work) {
21817
22616
  let timer;
21818
- const expired = new Promise((resolve15) => {
22617
+ const expired = new Promise((resolve16) => {
21819
22618
  timer = setTimeout(() => {
21820
- resolve15(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
22619
+ resolve16(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
21821
22620
  }, Math.max(0, deadline.left()));
21822
22621
  });
21823
22622
  try {
@@ -22109,7 +22908,7 @@ var board_default = createUpdateStub({ action: "board", summary: "apply the boar
22109
22908
 
22110
22909
  // src/project/lock.ts
22111
22910
  import { existsSync as existsSync15, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
22112
- import { join as join40 } from "path";
22911
+ import { join as join41 } from "path";
22113
22912
  var LOCK_FILE = "rafa.lock";
22114
22913
  var LOCKFILE_VERSION = 1;
22115
22914
 
@@ -22117,7 +22916,7 @@ class ProjectLockError extends Error {
22117
22916
  name = "ProjectLockError";
22118
22917
  }
22119
22918
  function lockPath(root) {
22120
- return join40(root, LOCK_FILE);
22919
+ return join41(root, LOCK_FILE);
22121
22920
  }
22122
22921
  function projectLockText(version2) {
22123
22922
  const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
@@ -22175,10 +22974,10 @@ function writeProjectLock(root, version2) {
22175
22974
 
22176
22975
  // src/project/update-current.ts
22177
22976
  import { existsSync as existsSync16 } from "fs";
22178
- import { join as join41 } from "path";
22977
+ import { join as join42 } from "path";
22179
22978
  var DEPRECATIONS = Object.freeze([]);
22180
22979
  function missingFolders(root) {
22181
- return PROJECT_TREE.map((name) => join41(SCOPE_DIR, name)).filter((relative9) => !existsSync16(join41(root, relative9)));
22980
+ return PROJECT_TREE.map((name) => join42(SCOPE_DIR, name)).filter((relative11) => !existsSync16(join42(root, relative11)));
22182
22981
  }
22183
22982
  async function readBoard3(access) {
22184
22983
  if (access.gh === null)
@@ -22231,7 +23030,7 @@ class UpdateStepError extends Error {
22231
23030
  }
22232
23031
  }
22233
23032
  function foldersNowThere(plan2) {
22234
- return plan2.folders.filter((relative9) => existsSync16(join41(plan2.root, relative9)));
23033
+ return plan2.folders.filter((relative11) => existsSync16(join42(plan2.root, relative11)));
22235
23034
  }
22236
23035
  function writeFolders(plan2) {
22237
23036
  if (plan2.folders.length === 0)
@@ -22285,7 +23084,7 @@ function readCurrentRange(recorded, installed) {
22285
23084
  }
22286
23085
 
22287
23086
  // src/commands/update/current.ts
22288
- var USAGE42 = "rafa update current [--dry-run] [--yes]";
23087
+ var USAGE43 = "rafa update current [--dry-run] [--yes]";
22289
23088
  var APPLY_QUESTION = "Apply these changes? [y/N] ";
22290
23089
  var YES_ANSWERS5 = ["y", "yes"];
22291
23090
  function checkoutLockRoot(root, dir) {
@@ -22310,7 +23109,7 @@ function readSwitch4(context, name) {
22310
23109
  if (value === true || value === "true")
22311
23110
  return true;
22312
23111
  throw new CommandExit(1, `\u274C --${name} takes no value, and read "${String(value)}" as one
22313
- Usage: ${USAGE42}`);
23112
+ Usage: ${USAGE43}`);
22314
23113
  }
22315
23114
  function couldNotRun(problem2) {
22316
23115
  return new CommandExit(2, `FAIL \u2014 ${problem2}
@@ -22446,7 +23245,7 @@ function stopLine2(plan2, dryRun) {
22446
23245
  return null;
22447
23246
  }
22448
23247
  async function runUpdateCurrent(context, seams) {
22449
- expectNoArgument(context.args, USAGE42);
23248
+ expectNoArgument(context.args, USAGE43);
22450
23249
  const dryRun = readSwitch4(context, "dry-run");
22451
23250
  const yes = readSwitch4(context, "yes");
22452
23251
  const project = projectOf15(context);
@@ -22520,24 +23319,6 @@ var rafa_default = createUpdateStub({ action: "rafa", summary: "move this projec
22520
23319
  // src/commands/update/self.ts
22521
23320
  var self_default = createUpdateStub({ action: "self", summary: "update rafa itself, from npm, a pinned version or another source", issue: 715 });
22522
23321
 
22523
- // src/commands/usage.ts
22524
- var usage_default = wrapPhaseZeroCommand({
22525
- name: "usage",
22526
- subject: "usage",
22527
- action: "usage",
22528
- summary: "show how much of the Claude usage limit is spent",
22529
- description: "Prints the Claude usage percentage beside a bar and a level: OK below 70%, ELEVATED" + " from 70%, HIGH from 80% and CRITICAL from 90%. No live source is wired in yet, so the reading" + " is `CLAUDE_USAGE_PERCENT` when it is set, and unavailable otherwise.",
22530
- args: [],
22531
- flags: [],
22532
- examples: [
22533
- {
22534
- cmd: "rafa usage",
22535
- note: "Prints the usage bar, or says the reading is unavailable."
22536
- }
22537
- ],
22538
- outputs: ["text", "json"]
22539
- }, usage);
22540
-
22541
23322
  // src/commands/index.ts
22542
23323
  var CORE_SUBJECTS = Object.freeze([
22543
23324
  { name: "plan", summary: "create plans from specs; list, show and validate them; read their risk and needs" },
@@ -22568,6 +23349,7 @@ var CORE_COMMANDS = Object.freeze([
22568
23349
  resume_default,
22569
23350
  status_default2,
22570
23351
  list_default7,
23352
+ wait_default,
22571
23353
  list_default,
22572
23354
  show_default4,
22573
23355
  create_default,
@@ -22580,7 +23362,7 @@ var CORE_COMMANDS = Object.freeze([
22580
23362
  show_default6,
22581
23363
  view_default,
22582
23364
  list_default9,
22583
- wait_default,
23365
+ wait_default2,
22584
23366
  merge_default2,
22585
23367
  triage_default,
22586
23368
  collect_default,
@@ -22641,7 +23423,6 @@ var CORE_COMMANDS = Object.freeze([
22641
23423
  doctor_default,
22642
23424
  cleanup_default,
22643
23425
  self_update_default,
22644
- usage_default,
22645
23426
  describe_default
22646
23427
  ]);
22647
23428
  var CORE_REGISTRY = createCommandRegistry({ subjects: CORE_SUBJECTS, commands: CORE_COMMANDS });