@open-tomato/rafa 0.37.0 → 0.38.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.
@@ -4,6 +4,7 @@ import {
4
4
  BLOCKED_BY_FIELD,
5
5
  BOARD_LABELS,
6
6
  BOARD_LISTING_LIMIT,
7
+ BOARD_SYNC_FIX,
7
8
  BRANCH_PREFIX,
8
9
  CI_POLL_INTERVAL_MS,
9
10
  CLAIMED_LABEL,
@@ -38,6 +39,8 @@ import {
38
39
  NoSessionRefusal,
39
40
  ORIGIN_FIELD,
40
41
  PREVIOUS_COPY_WARN_ABOVE,
42
+ PROJECT_SCOPE_FIX,
43
+ PROJECT_WRITE_PAUSE_MS,
41
44
  PROVENANCE_FIELD,
42
45
  REASON_FLAG,
43
46
  RELEASE_BASE_BRANCH,
@@ -117,6 +120,7 @@ import {
117
120
  createGhBoardListing,
118
121
  createGhIssueBoard,
119
122
  createGhOpenPullRequests,
123
+ createGhProjectPort,
120
124
  createGhPullRequests,
121
125
  createGhRoadmapBody,
122
126
  createGhRoadmapSearch,
@@ -181,6 +185,7 @@ import {
181
185
  invalidateRows,
182
186
  isFirstDispatch,
183
187
  isLive,
188
+ isMissingProjectScope,
184
189
  isNotPlanned,
185
190
  isStackList,
186
191
  isWaiting,
@@ -199,6 +204,7 @@ import {
199
204
  localDay,
200
205
  localPlanNotes,
201
206
  makeOwnershipCommit,
207
+ matchProjectFields,
202
208
  memoiseVerifier,
203
209
  mergeGuardSettings,
204
210
  missingBoardLabels,
@@ -212,6 +218,7 @@ import {
212
218
  nextQuestion,
213
219
  noBoardListsLine,
214
220
  normaliseReason,
221
+ notFoundWarning,
215
222
  notesFileName,
216
223
  onTracker,
217
224
  openBoards,
@@ -237,6 +244,8 @@ import {
237
244
  pushOwnershipCommit,
238
245
  pushResolved,
239
246
  rankCandidates,
247
+ rankOf,
248
+ rateLimitWarning,
240
249
  readAgentDirectory,
241
250
  readBinPath,
242
251
  readBlockedBy,
@@ -260,6 +269,7 @@ import {
260
269
  readEpics,
261
270
  readGuard,
262
271
  readHomeAfterLoop,
272
+ readIssueFacts,
263
273
  readListedEpics,
264
274
  readManifestVersion,
265
275
  readMergeGuard,
@@ -272,6 +282,7 @@ import {
272
282
  readReason,
273
283
  readReceiptVerdict,
274
284
  readRecords,
285
+ readRefreshBoard,
275
286
  readRefsText,
276
287
  readRequiredFlag,
277
288
  readRoadmapSetting,
@@ -289,6 +300,9 @@ import {
289
300
  ready_default,
290
301
  receiptProblem,
291
302
  recordPlanCi,
303
+ refreshFailedWarning,
304
+ refreshIssueItems,
305
+ refreshProjectItems,
292
306
  refusal,
293
307
  refuseMisplacedRuntime,
294
308
  releaseCommitSubject,
@@ -329,6 +343,7 @@ import {
329
343
  runIssueList,
330
344
  samePlace,
331
345
  scanClaimBranches,
346
+ scopeWarning,
332
347
  selectBoardRelations,
333
348
  self_update_default,
334
349
  serializeFragment,
@@ -345,6 +360,7 @@ import {
345
360
  sourceItem,
346
361
  stackPaths,
347
362
  stacksFromEccSkillName,
363
+ stageOf,
348
364
  start,
349
365
  status_default,
350
366
  tickLine,
@@ -374,7 +390,7 @@ import {
374
390
  worktreesHolding,
375
391
  writeSession,
376
392
  writeShippedFile
377
- } from "./index-35ze56rz.js";
393
+ } from "./index-e3vhhfka.js";
378
394
  import {
379
395
  PROBE_TIMEOUT_MS,
380
396
  loadPlanPrerequisites,
@@ -386,7 +402,7 @@ import {
386
402
  runPreflight,
387
403
  splitBlockerComment,
388
404
  trackerPathFor
389
- } from "./index-7bb0fdb7.js";
405
+ } from "./index-zdz0mp63.js";
390
406
  import {
391
407
  ACTION_HEADING,
392
408
  CAUSE_HEADING,
@@ -492,7 +508,7 @@ import {
492
508
  versionLine,
493
509
  writeFrontmatter,
494
510
  writePositionFile
495
- } from "./index-1mxv55ec.js";
511
+ } from "./index-74btmn1e.js";
496
512
  import {
497
513
  CONFIG_DEFAULTS,
498
514
  CONFIG_FILE,
@@ -507,7 +523,7 @@ import {
507
523
  readItemBytes,
508
524
  readRafaBlocks,
509
525
  resolveTiers
510
- } from "./index-jfas157k.js";
526
+ } from "./index-w9naz372.js";
511
527
  import {
512
528
  promotable
513
529
  } from "./index-d6d0942q.js";
@@ -515,10 +531,10 @@ import {
515
531
  createCommandRegistry,
516
532
  describeRegistry,
517
533
  mountKey
518
- } from "./index-k6sd4ym5.js";
534
+ } from "./index-eyjq63mx.js";
519
535
  import {
520
536
  CommandExit
521
- } from "./index-3mzw3kzh.js";
537
+ } from "./index-sr3kc4mf.js";
522
538
  import {
523
539
  CLAIMS_STALE_DISABLED,
524
540
  MERGE_METHODS,
@@ -531,7 +547,7 @@ import {
531
547
  isSkillTier,
532
548
  messageOf,
533
549
  parseBudgetUsd
534
- } from "./index-fzhd3d4y.js";
550
+ } from "./index-m2ahxjw8.js";
535
551
  import {
536
552
  triggerKey
537
553
  } from "./index-wp8c28fd.js";
@@ -3261,8 +3277,187 @@ function createBoardListCommand(seams = {}) {
3261
3277
  }
3262
3278
  var list_default5 = createBoardListCommand();
3263
3279
 
3280
+ // src/board/project/sync.ts
3281
+ var WHOLE_PROJECT = Object.freeze({ everyItem: true, openIssues: true });
3282
+ function addedWrites(first, second) {
3283
+ return {
3284
+ written: first.written + second.written,
3285
+ notUpdated: first.notUpdated + second.notUpdated,
3286
+ rateLimited: first.rateLimited || second.rateLimited,
3287
+ detail: [first.detail, second.detail].filter((detail) => detail !== "").join(`
3288
+ `)
3289
+ };
3290
+ }
3291
+ async function addIssues(options, ref, issues) {
3292
+ const repository = await readBoardRepository(options.gh);
3293
+ const port = createGhProjectPort(options.gh);
3294
+ const project = await port.find(ref);
3295
+ if (project === null)
3296
+ return null;
3297
+ for (const number of issues)
3298
+ await port.addItem(project.id, { repository, number });
3299
+ return issues;
3300
+ }
3301
+ async function addMissing(options, first, synced) {
3302
+ if (synced.dryRun || first.missing.length === 0 || first.writes.rateLimited)
3303
+ return synced;
3304
+ try {
3305
+ const added = await addIssues(options, first.project, first.missing);
3306
+ if (added === null)
3307
+ return { kind: "not-found", project: first.project, warnings: [notFoundWarning(first.project)] };
3308
+ const second = await refreshProjectItems(options, added);
3309
+ if (second.kind !== "refreshed")
3310
+ return second;
3311
+ return {
3312
+ ...synced,
3313
+ changes: [...first.changes, ...second.changes],
3314
+ added,
3315
+ writes: addedWrites(first.writes, second.writes),
3316
+ warnings: [...first.warnings, ...second.warnings.filter((line) => !first.warnings.includes(line))]
3317
+ };
3318
+ } catch (error) {
3319
+ if (isMissingProjectScope(error))
3320
+ return { kind: "refused", reason: "scope", project: first.project, warnings: [scopeWarning()] };
3321
+ throw error;
3322
+ }
3323
+ }
3324
+ async function syncProject(options) {
3325
+ const first = await refreshProjectItems(options, [], WHOLE_PROJECT);
3326
+ if (first.kind !== "refreshed")
3327
+ return first;
3328
+ return addMissing(options, first, {
3329
+ kind: "synced",
3330
+ project: first.project,
3331
+ dryRun: options.dryRun === true,
3332
+ changes: first.changes,
3333
+ missing: first.missing,
3334
+ added: [],
3335
+ writes: first.writes,
3336
+ warnings: first.warnings
3337
+ });
3338
+ }
3339
+
3340
+ // src/commands/board/sync.ts
3341
+ var USAGE7 = "rafa board sync [--dry-run]";
3342
+ var DRY_RUN_FLAG = "dry-run";
3343
+ var BOARD_SYNC_REFUSAL_EXIT = 2;
3344
+ var PROJECT_INIT_FIX = "rafa init --board --project";
3345
+ var EMPTY_VALUE = "(empty)";
3346
+ function counted(count, noun) {
3347
+ return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
3348
+ }
3349
+ function changeLine(change) {
3350
+ return `#${String(change.issue)} ${change.name}: ${change.from ?? EMPTY_VALUE} \u2192 ${change.to ?? EMPTY_VALUE}`;
3351
+ }
3352
+ function closingLine(result) {
3353
+ const project = `project #${String(result.project.number)}`;
3354
+ const adds = result.dryRun ? result.missing : result.added;
3355
+ const changes = counted(result.changes.length, "change");
3356
+ const issues = counted(adds.length, "issue");
3357
+ if (result.dryRun) {
3358
+ return result.changes.length === 0 && adds.length === 0 ? `Dry run on ${project}: in step, nothing to change.` : `Dry run on ${project}: ${changes} and ${issues} to add; nothing written.`;
3359
+ }
3360
+ if (result.rateLimited)
3361
+ return `Stopped on ${project}: ${String(result.written)} of ${changes} written; the rate limit refused the rest.`;
3362
+ return result.changes.length === 0 && adds.length === 0 ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added.`;
3363
+ }
3364
+ function syncLines(result) {
3365
+ const adds = result.dryRun ? result.missing.map((issue) => `#${String(issue)} is not on the project: would be added`) : result.added.map((issue) => `#${String(issue)} added to the project`);
3366
+ return [...result.changes.map(changeLine), ...adds, closingLine(result)];
3367
+ }
3368
+ function plainChange(change) {
3369
+ return { issue: change.issue, field: change.field, name: change.name, from: change.from, to: change.to };
3370
+ }
3371
+ function syncResultOf(synced) {
3372
+ return {
3373
+ project: synced.project,
3374
+ dryRun: synced.dryRun,
3375
+ changes: synced.changes.map(plainChange),
3376
+ missing: synced.missing,
3377
+ added: synced.added,
3378
+ written: synced.writes.written,
3379
+ rateLimited: synced.writes.rateLimited,
3380
+ notUpdated: synced.writes.notUpdated,
3381
+ warnings: synced.warnings
3382
+ };
3383
+ }
3384
+ async function runBoardSync(context, seams) {
3385
+ if (context.args.length > 0) {
3386
+ throw lineRefusal(`Expected no arguments, got ${String(context.args.length)}: ${context.args.join(" ")}`, USAGE7);
3387
+ }
3388
+ const project = issueProject(context);
3389
+ const config = issueSubjectConfig(project, (message) => {
3390
+ context.output.warn(message);
3391
+ });
3392
+ if (config.boardProjectNumber === null) {
3393
+ throw new CommandExit(1, `\u274C board.project.number is not set, so there is no project to sync. Run \`${PROJECT_INIT_FIX}\` to create one.`);
3394
+ }
3395
+ const gh = seams.gh ?? createGhRunner({ cwd: project.root });
3396
+ const dryRun = context.flags[DRY_RUN_FLAG] === true;
3397
+ const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun } : { config, gh, dryRun, sleep: seams.sleep }).catch((error) => {
3398
+ throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C Could not sync the project: ${messageOf(error)}`);
3399
+ });
3400
+ if (synced.kind !== "synced") {
3401
+ throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C ${synced.warnings.join(`
3402
+ `)}`);
3403
+ }
3404
+ return syncResultOf(synced);
3405
+ }
3406
+ function writeResult(context, result) {
3407
+ const refusal3 = rateLimitWarning(result.notUpdated);
3408
+ if (context.outputMode !== "json") {
3409
+ for (const line of syncLines(result))
3410
+ context.output.info(line);
3411
+ }
3412
+ for (const line of result.warnings) {
3413
+ if (!result.rateLimited || line !== refusal3)
3414
+ context.output.warn(line);
3415
+ }
3416
+ if (result.rateLimited)
3417
+ throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C ${refusal3}`);
3418
+ if (context.outputMode === "json")
3419
+ context.output.result(result);
3420
+ }
3421
+ function createBoardSyncCommand(seams = {}) {
3422
+ const command = {
3423
+ name: "sync",
3424
+ subject: "board",
3425
+ action: "sync",
3426
+ summary: "bring the repository's GitHub project in step with its issues",
3427
+ description: "Refreshes every item of this repository on the GitHub project board.project.number names, writing" + " only the Stage, Horizon, Rank, Blocked by and Progress values that differ from what rafa reads of the" + " issues, then adds every open issue missing from the project and fills its fields. It repairs what changed" + " outside rafa: a label edited in the web UI, an issue closed by hand, the Roadmap issue's order edited in" + " its body. Prints one line per change and per issue added, then a closing count. With `--dry-run` it" + " prints the same lines and writes nothing. With `--output=json` the changes are the data of the terminal" + " result event. Refused with exit code 1 when board.project.number is unset, and with exit code 2 when the" + " token lacks the project scope, the number names no project, or the rate limit stopped the writes.",
3428
+ args: [],
3429
+ flags: [
3430
+ {
3431
+ name: DRY_RUN_FLAG,
3432
+ description: "Print each change the sync would make and each issue it would add, and write nothing.",
3433
+ type: "boolean"
3434
+ }
3435
+ ],
3436
+ examples: [
3437
+ {
3438
+ cmd: "rafa board sync --dry-run",
3439
+ note: "Shows what drifted on the project since rafa last wrote it, writing nothing."
3440
+ },
3441
+ {
3442
+ cmd: "rafa board sync",
3443
+ note: "Writes those changes and adds the open issues the project lacks."
3444
+ },
3445
+ {
3446
+ cmd: "rafa board sync --output=json",
3447
+ note: "Gives the changes, the issues added and the counts as the terminal result."
3448
+ }
3449
+ ],
3450
+ outputs: ["text", "json"],
3451
+ run: async (context) => {
3452
+ writeResult(context, await runBoardSync(context, seams));
3453
+ }
3454
+ };
3455
+ return Object.freeze(command);
3456
+ }
3457
+ var sync_default = createBoardSyncCommand();
3458
+
3264
3459
  // src/commands/claim/release.ts
3265
- var USAGE7 = "rafa claim release <n>";
3460
+ var USAGE8 = "rafa claim release <n>";
3266
3461
  var COMMAND = "rafa claim release";
3267
3462
  var ISSUE_NUMBER2 = /^[1-9]\d*$/;
3268
3463
  var RELEASING = { during: "was being released", nothing: "nothing was released" };
@@ -3344,7 +3539,7 @@ function pushOnClaimTip(git, reading, record, refuseWith, doing) {
3344
3539
  return made.sha;
3345
3540
  }
3346
3541
  async function releaseClaim(context, makeSeams) {
3347
- const issue = readIssueArgument(context.args, USAGE7);
3542
+ const issue = readIssueArgument(context.args, USAGE8);
3348
3543
  const project = requireProject(context, COMMAND);
3349
3544
  const config = resolveProjectConfig(project, COMMAND, (message) => context.output.warn(message));
3350
3545
  const seams = makeSeams(project.root, config);
@@ -3402,7 +3597,7 @@ function createClaimReleaseCommand(makeSeams = DEFAULT_SEAMS) {
3402
3597
  var release_default = createClaimReleaseCommand();
3403
3598
 
3404
3599
  // src/commands/claim/accept.ts
3405
- var USAGE8 = "rafa claim accept <n>";
3600
+ var USAGE9 = "rafa claim accept <n>";
3406
3601
  var COMMAND2 = "rafa claim accept";
3407
3602
  var ACCEPTING = { during: "was being accepted", nothing: "nothing was accepted" };
3408
3603
  function refuse2(message) {
@@ -3475,7 +3670,7 @@ ${reasons.map((reason) => ` ${reason}`).join(`
3475
3670
  `)}`);
3476
3671
  }
3477
3672
  function acceptClaim(context, makeSeams) {
3478
- const issue = readIssueArgument(context.args, USAGE8);
3673
+ const issue = readIssueArgument(context.args, USAGE9);
3479
3674
  const project = requireProject(context, COMMAND2);
3480
3675
  const config = resolveProjectConfig(project, COMMAND2, (message) => context.output.warn(message));
3481
3676
  const seams = makeSeams(project.root, config);
@@ -3530,7 +3725,7 @@ function createClaimAcceptCommand(makeSeams = DEFAULT_SEAMS2) {
3530
3725
  var accept_default = createClaimAcceptCommand();
3531
3726
 
3532
3727
  // src/commands/claim/hand.ts
3533
- var USAGE9 = "rafa claim hand <n> --to=<store id> | rafa claim hand <n> --withdraw";
3728
+ var USAGE10 = "rafa claim hand <n> --to=<store id> | rafa claim hand <n> --withdraw";
3534
3729
  var COMMAND3 = "rafa claim hand";
3535
3730
  var TO_FLAG = "to";
3536
3731
  var WITHDRAW_FLAG = "withdraw";
@@ -3541,7 +3736,7 @@ function refuse3(message) {
3541
3736
  }
3542
3737
  function lineRefusal3(message) {
3543
3738
  return refuse3(`${message}
3544
- Usage: ${USAGE9}`);
3739
+ Usage: ${USAGE10}`);
3545
3740
  }
3546
3741
  function readWithdraw(flags) {
3547
3742
  const value = flags[WITHDRAW_FLAG];
@@ -3594,7 +3789,7 @@ function recordFor(reading, request, issue, storeId) {
3594
3789
  }
3595
3790
  function handClaim(context, makeSeams) {
3596
3791
  const request = readHandRequest(context.flags);
3597
- const issue = readIssueArgument(context.args, USAGE9);
3792
+ const issue = readIssueArgument(context.args, USAGE10);
3598
3793
  const project = requireProject(context, COMMAND3);
3599
3794
  const config = resolveProjectConfig(project, COMMAND3, (message) => context.output.warn(message));
3600
3795
  const seams = makeSeams(project.root, config);
@@ -3669,7 +3864,7 @@ function createClaimHandCommand(makeSeams = DEFAULT_SEAMS3) {
3669
3864
  var hand_default = createClaimHandCommand();
3670
3865
 
3671
3866
  // src/commands/claim/take.ts
3672
- var USAGE10 = "rafa claim take <n> [--stale]";
3867
+ var USAGE11 = "rafa claim take <n> [--stale]";
3673
3868
  var COMMAND4 = "rafa claim take";
3674
3869
  var STALE_FLAG = "stale";
3675
3870
  var TAKING = { during: "was being taken over", nothing: "nothing was taken" };
@@ -3683,7 +3878,7 @@ function readStaleFlag(flags) {
3683
3878
  if (typeof value === "boolean")
3684
3879
  return value;
3685
3880
  throw refuse4(`--${STALE_FLAG} takes no value, and read "${value}" as one; type the issue number before the flags
3686
- Usage: ${USAGE10}`);
3881
+ Usage: ${USAGE11}`);
3687
3882
  }
3688
3883
  function labelNote(weights) {
3689
3884
  return weights.labelWarning === null ? "" : ` (${weights.labelWarning}, so it reads as in development)`;
@@ -3766,7 +3961,7 @@ ${reasons.map((reason) => ` ${reason}`).join(`
3766
3961
  }
3767
3962
  async function takeClaim(context, makeSeams) {
3768
3963
  const stale = readStaleFlag(context.flags);
3769
- const issue = readIssueArgument(context.args, USAGE10);
3964
+ const issue = readIssueArgument(context.args, USAGE11);
3770
3965
  const project = requireProject(context, COMMAND4);
3771
3966
  const config = resolveProjectConfig(project, COMMAND4, (message) => context.output.warn(message));
3772
3967
  const seams = makeSeams(project.root, config);
@@ -4199,7 +4394,7 @@ function worktreeData(row) {
4199
4394
  // src/commands/cleanup.ts
4200
4395
  var COMMAND_NAME4 = "rafa cleanup";
4201
4396
  var CLEANUP_USAGE = "rafa cleanup [--dry-run]";
4202
- var DRY_RUN_FLAG = "dry-run";
4397
+ var DRY_RUN_FLAG2 = "dry-run";
4203
4398
  var CLEANUP_MESSAGE = "Tick what to remove (space ticks, a ticks a group, enter goes on)";
4204
4399
  var NOTHING_LISTED_TEXT = "Nothing to clean up: no branch or worktree is listed.";
4205
4400
  var NOTHING_REMOVED_TEXT = "Nothing removed.";
@@ -4207,14 +4402,14 @@ var DEFAULT_CLEANUP_SEAMS = Object.freeze({});
4207
4402
  function isBranchRow(row) {
4208
4403
  return "group" in row;
4209
4404
  }
4210
- function counted(count, noun, pluralNoun) {
4405
+ function counted2(count, noun, pluralNoun) {
4211
4406
  return count === 1 ? `1 ${noun}` : `${String(count)} ${pluralNoun}`;
4212
4407
  }
4213
4408
  function cleanupQuestion(branches, worktrees) {
4214
- return `Delete ${counted(branches, "branch", "branches")} and remove ${counted(worktrees, "worktree", "worktrees")}? [y/N] `;
4409
+ return `Delete ${counted2(branches, "branch", "branches")} and remove ${counted2(worktrees, "worktree", "worktrees")}? [y/N] `;
4215
4410
  }
4216
4411
  function notPushedQuestion(row) {
4217
- const commits = counted(row.commits, "commit", "commits");
4412
+ const commits = counted2(row.commits, "commit", "commits");
4218
4413
  return `${row.branch.name} holds ${commits} no remote has, which deleting it loses. Delete ${row.branch.name}? [y/N] `;
4219
4414
  }
4220
4415
  function worktreeLabel(row, width) {
@@ -4286,7 +4481,7 @@ function writeOutcomes(context, outcomes) {
4286
4481
  const failed = outcomes.filter((outcome) => !outcome.ok).length;
4287
4482
  if (failed === 0)
4288
4483
  return;
4289
- throw new CommandExit(1, `\u274C ${COMMAND_NAME4}: ${counted(failed, "step", "steps")} of ${String(outcomes.length)} did not run clean; each is named above.`);
4484
+ throw new CommandExit(1, `\u274C ${COMMAND_NAME4}: ${counted2(failed, "step", "steps")} of ${String(outcomes.length)} did not run clean; each is named above.`);
4290
4485
  }
4291
4486
  async function askAndRemove(run) {
4292
4487
  const { context, read, seams } = run;
@@ -4338,7 +4533,7 @@ async function readOrRefuse(seams, cleanup, settings) {
4338
4533
  }
4339
4534
  async function runCleanup(context, seams) {
4340
4535
  expectNoArgument(context.args, CLEANUP_USAGE);
4341
- const dryRun = readSwitch(DRY_RUN_FLAG, context.flags[DRY_RUN_FLAG], `Usage: ${CLEANUP_USAGE}`);
4536
+ const dryRun = readSwitch(DRY_RUN_FLAG2, context.flags[DRY_RUN_FLAG2], `Usage: ${CLEANUP_USAGE}`);
4342
4537
  const project = projectOf6(context);
4343
4538
  const config = resolveProjectConfig(project, COMMAND_NAME4, (message) => {
4344
4539
  context.output.warn(message);
@@ -4379,7 +4574,7 @@ function createCleanupCommand(seams = DEFAULT_CLEANUP_SEAMS) {
4379
4574
  args: [],
4380
4575
  flags: [
4381
4576
  {
4382
- name: DRY_RUN_FLAG,
4577
+ name: DRY_RUN_FLAG2,
4383
4578
  description: "Show the checklist and ask as usual, then print the `git` commands the answer would run" + " instead of running them. Removes nothing.",
4384
4579
  type: "boolean"
4385
4580
  }
@@ -6136,7 +6331,7 @@ function isUnmet(need) {
6136
6331
  // src/commands/plan/needs.ts
6137
6332
  import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve2 } from "path";
6138
6333
  var COMMAND5 = "rafa plan needs";
6139
- var USAGE11 = "rafa plan needs [<plan> | --spec=<file> | --issue=<n>] [--missing] [--source=<source>]";
6334
+ var USAGE12 = "rafa plan needs [<plan> | --spec=<file> | --issue=<n>] [--missing] [--source=<source>]";
6140
6335
  var MISSING_HINT = `Type the plan first: ${COMMAND5} <plan> --missing`;
6141
6336
  var MET_MARK = "\u2713";
6142
6337
  var UNMET_MARK = "\u2717";
@@ -6151,7 +6346,7 @@ function readNeedsSource(value) {
6151
6346
  return value;
6152
6347
  const shown = typeof value === "string" ? `"${value}"` : "given no value";
6153
6348
  throw new CommandExit(1, `\u274C --source is ${shown}, expected one of: project, rafa, user, plugin:<name>, addon:<name>` + `
6154
- Usage: ${USAGE11}`);
6349
+ Usage: ${USAGE12}`);
6155
6350
  }
6156
6351
  function specRouteWords(flags) {
6157
6352
  const words = ["spec", "issue"].flatMap((name) => {
@@ -6174,7 +6369,7 @@ function needsPlanPath(root, planDir, typed) {
6174
6369
  throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
6175
6370
  const lookedFor = [...new Set([resolve2(root, planDir, DEFAULT_PLAN_FILE), resolve2(root, DEFAULT_PLAN_FILE)])];
6176
6371
  throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
6177
- Usage: ${USAGE11}`);
6372
+ Usage: ${USAGE12}`);
6178
6373
  }
6179
6374
  function fromSource(need, source) {
6180
6375
  if (source === null)
@@ -6319,11 +6514,11 @@ async function routedSpec(words, project, config, seams) {
6319
6514
  async function runNeeds(context, seams) {
6320
6515
  const missing = readSwitch("missing", context.flags["missing"], MISSING_HINT);
6321
6516
  const source = readNeedsSource(context.flags["source"]);
6322
- const typed = expectAtMostOneArgument(context.args, USAGE11);
6517
+ const typed = expectAtMostOneArgument(context.args, USAGE12);
6323
6518
  const words = specRouteWords(context.flags);
6324
6519
  if (typed !== null && words !== null) {
6325
6520
  throw new CommandExit(1, `\u274C ${typed} and ${words.join(" and ")} each name what to read; give one
6326
- Usage: ${USAGE11}`);
6521
+ Usage: ${USAGE12}`);
6327
6522
  }
6328
6523
  const project = requireProject(context, COMMAND5);
6329
6524
  const { config, needs } = await readMachine(project, context, seams);
@@ -6934,7 +7129,7 @@ function renderDeep(reading) {
6934
7129
  }
6935
7130
 
6936
7131
  // src/commands/doctor-description.ts
6937
- var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
7132
+ var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. Where `board.project.number` is set, it then" + " prints, under `GitHub project:`, the `project` scope, the project at that number and its five fields as" + " present, missing or unknown, each one not present naming its fix and a project in place naming" + " `rafa board sync --dry-run`; it reads no item and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
6938
7133
 
6939
7134
  // src/commands/doctor-effort-schema.ts
6940
7135
  import { join as join12 } from "path";
@@ -7135,15 +7330,485 @@ function writeInstall(context, install) {
7135
7330
  }
7136
7331
  }
7137
7332
 
7333
+ // src/commands/init-board-project-setting.ts
7334
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
7335
+ var PROJECT_NUMBER_SETTING = "board.project.number";
7336
+ var BOARD_KEY = /^board:\s*$/u;
7337
+ var PROJECT_KEY = /^ {2}project:\s*$/u;
7338
+ var ANY_PROJECT_KEY = /^ {2}project:/u;
7339
+ var NUMBER_KEY = /^ {4}number:/u;
7340
+ var COMMENTED_BOARD_KEY = /^#\s*board:\s*$/u;
7341
+ var COMMENTED_CHILD = /^# {2,}\S/u;
7342
+ var TOP_LEVEL = /^[^\s#]/u;
7343
+ var ABOVE_SECOND_LEVEL = /^ {0,3}[^\s#]/u;
7344
+ function settingLines(number, withProject) {
7345
+ return withProject ? [" project:", numberLine(number)] : [numberLine(number)];
7346
+ }
7347
+ function insertedAfter(lines, at, inserted) {
7348
+ return [...lines.slice(0, at + 1), ...inserted, ...lines.slice(at + 1)];
7349
+ }
7350
+ function replacedAt(lines, at, line) {
7351
+ return lines.map((held, index) => index === at ? line : held);
7352
+ }
7353
+ function matching(pattern) {
7354
+ return (line) => pattern.test(line);
7355
+ }
7356
+ function matchingUncommented(pattern) {
7357
+ return (line) => pattern.test(line.replace(/^# /u, ""));
7358
+ }
7359
+ function indexAfter(lines, from, end, test) {
7360
+ const found = lines.slice(from + 1, end).findIndex(test);
7361
+ return found < 0 ? -1 : from + 1 + found;
7362
+ }
7363
+ function blockEnd(lines, open, end, closes) {
7364
+ const found = indexAfter(lines, open, end, closes);
7365
+ return found < 0 ? end : found;
7366
+ }
7367
+ function numberLine(number) {
7368
+ return ` number: ${String(number)}`;
7369
+ }
7370
+ function underOpenBoard(lines, open, number) {
7371
+ const end = blockEnd(lines, open, lines.length, matching(TOP_LEVEL));
7372
+ const project = indexAfter(lines, open, end, matching(PROJECT_KEY));
7373
+ if (project < 0) {
7374
+ return indexAfter(lines, open, end, matching(ANY_PROJECT_KEY)) < 0 ? insertedAfter(lines, open, settingLines(number, true)) : null;
7375
+ }
7376
+ const projectEnd = blockEnd(lines, project, end, matching(ABOVE_SECOND_LEVEL));
7377
+ const held = indexAfter(lines, project, projectEnd, matching(NUMBER_KEY));
7378
+ return held < 0 ? insertedAfter(lines, project, settingLines(number, false)) : replacedAt(lines, held, numberLine(number));
7379
+ }
7380
+ function underCommentedBoard(lines, open, number) {
7381
+ const opened = replacedAt(lines, open, "board:");
7382
+ const end = blockEnd(lines, open, lines.length, (line) => !COMMENTED_CHILD.test(line));
7383
+ const project = indexAfter(lines, open, end, matchingUncommented(PROJECT_KEY));
7384
+ if (project < 0)
7385
+ return insertedAfter(opened, open, settingLines(number, true));
7386
+ const projectEnd = blockEnd(lines, project, end, matchingUncommented(ABOVE_SECOND_LEVEL));
7387
+ const held = indexAfter(lines, project, projectEnd, matchingUncommented(NUMBER_KEY));
7388
+ const withProject = replacedAt(opened, project, " project:");
7389
+ return held < 0 ? insertedAfter(withProject, project, settingLines(number, false)) : replacedAt(withProject, held, numberLine(number));
7390
+ }
7391
+ function namesBoard(text) {
7392
+ let document;
7393
+ try {
7394
+ document = Bun.YAML.parse(text);
7395
+ } catch {
7396
+ return false;
7397
+ }
7398
+ return isMapping(document) && Object.hasOwn(document, "board");
7399
+ }
7400
+ function appended(lines, block) {
7401
+ const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
7402
+ return [...body, ...block, ""];
7403
+ }
7404
+ function withProjectNumber(text, number) {
7405
+ const lines = text.split(`
7406
+ `);
7407
+ const open = lines.findIndex((line) => BOARD_KEY.test(line));
7408
+ if (open >= 0)
7409
+ return underOpenBoard(lines, open, number)?.join(`
7410
+ `) ?? null;
7411
+ if (namesBoard(text))
7412
+ return null;
7413
+ const commented = lines.findIndex((line) => COMMENTED_BOARD_KEY.test(line));
7414
+ if (commented >= 0)
7415
+ return underCommentedBoard(lines, commented, number).join(`
7416
+ `);
7417
+ return appended(lines, ["board:", ...settingLines(number, true)]).join(`
7418
+ `);
7419
+ }
7420
+ function projectNumberIn(text, path) {
7421
+ return parseConfigText(text, path).values.boardProjectNumber ?? null;
7422
+ }
7423
+ function readBackProblem(text, written, path, number) {
7424
+ let before;
7425
+ let after;
7426
+ try {
7427
+ before = parseConfigText(text, path);
7428
+ after = parseConfigText(written, path);
7429
+ } catch (error) {
7430
+ return `does not parse: ${messageOf(error)}`;
7431
+ }
7432
+ const read = after.values.boardProjectNumber ?? null;
7433
+ if (read !== number)
7434
+ return `reads ${PROJECT_NUMBER_SETTING} as ${describeValue(read)}`;
7435
+ const others = (file) => ({ values: { ...file.values, boardProjectNumber: null }, extras: file.extras });
7436
+ return Bun.deepEquals(others(before), others(after), true) ? null : `reads a setting other than ${PROJECT_NUMBER_SETTING} differently, or a key no setting names`;
7437
+ }
7438
+ function settingPart(outcome, detail) {
7439
+ return { kind: "setting", name: PROJECT_NUMBER_SETTING, outcome, detail };
7440
+ }
7441
+ function writeProjectNumber(root, number) {
7442
+ const path = configFilePath(root);
7443
+ let text;
7444
+ let held;
7445
+ try {
7446
+ text = readFileSync6(path, "utf8");
7447
+ held = projectNumberIn(text, path);
7448
+ } catch (error) {
7449
+ return settingPart("refused", `${path} could not be read: ${messageOf(error)}`);
7450
+ }
7451
+ if (held === number)
7452
+ return settingPart("present", `${path} names project ${String(number)}`);
7453
+ const written = withProjectNumber(text, number);
7454
+ if (written === null) {
7455
+ const why = `it spells board or board.project in a shape this command does not edit, so set ${PROJECT_NUMBER_SETTING}: ${String(number)} by hand`;
7456
+ return settingPart("refused", `${path} was left as it was: ${why}`);
7457
+ }
7458
+ const problem = readBackProblem(text, written, path, number);
7459
+ if (problem !== null)
7460
+ return settingPart("refused", `${path} was left as it was: the file this run would have written ${problem}`);
7461
+ try {
7462
+ writeFileSync2(path, written, "utf8");
7463
+ } catch (error) {
7464
+ return settingPart("refused", `${path} could not be written: ${messageOf(error)}`);
7465
+ }
7466
+ return settingPart("created", `${path} now names project ${String(number)}`);
7467
+ }
7468
+
7469
+ // src/commands/init-board-project.ts
7470
+ var PROJECT_STEP_FIX = "rafa init --board --project";
7471
+ var SCOPE_PART = "project scope";
7472
+ var COPY_PART = "project";
7473
+ var ITEMS_PART = "project issues";
7474
+ var FIELDS_PART = "project fields";
7475
+ var PROJECT_SCOPE = "project";
7476
+ var SCOPE_ARGS = Object.freeze(["auth", "status", "--active", "--json", "hosts"]);
7477
+ var LOGGED_IN = "success";
7478
+ function partOf(kind, name, outcome, detail) {
7479
+ return { kind, name, outcome, detail };
7480
+ }
7481
+ function notTried(kinds, why) {
7482
+ const names = {
7483
+ scope: SCOPE_PART,
7484
+ project: COPY_PART,
7485
+ items: ITEMS_PART,
7486
+ fields: FIELDS_PART,
7487
+ setting: PROJECT_NUMBER_SETTING
7488
+ };
7489
+ return kinds.map((kind) => partOf(kind, names[kind], "refused", `not tried: ${why}`));
7490
+ }
7491
+ function scopeDetail() {
7492
+ return `the gh token has no \`${PROJECT_SCOPE}\` scope; run \`${PROJECT_SCOPE_FIX}\`, then \`${PROJECT_STEP_FIX}\``;
7493
+ }
7494
+ function refusalOf(error) {
7495
+ return isMissingProjectScope(error) ? scopeDetail() : messageOf(error);
7496
+ }
7497
+ function holdsProjectScope(hosts) {
7498
+ if (!isMapping(hosts))
7499
+ return false;
7500
+ return Object.values(hosts).some((accounts) => Array.isArray(accounts) && accounts.some((account) => isMapping(account) && account["active"] === true && account["state"] === LOGGED_IN && typeof account["scopes"] === "string" && account["scopes"].split(",").map((scope) => scope.trim()).includes(PROJECT_SCOPE)));
7501
+ }
7502
+ async function readScopePart(gh) {
7503
+ const command = `gh ${SCOPE_ARGS.join(" ")}`;
7504
+ const result2 = await gh(SCOPE_ARGS);
7505
+ if (!result2.ok) {
7506
+ const written = result2.stderr.trim() || result2.stdout.trim() || "it wrote nothing";
7507
+ return partOf("scope", SCOPE_PART, "refused", `${command} failed: ${written}; ${scopeDetail()}`);
7508
+ }
7509
+ let payload;
7510
+ try {
7511
+ payload = JSON.parse(result2.stdout);
7512
+ } catch (error) {
7513
+ return partOf("scope", SCOPE_PART, "refused", `${command} wrote output that is not JSON: ${messageOf(error)}`);
7514
+ }
7515
+ const hosts = isMapping(payload) ? payload["hosts"] : null;
7516
+ return holdsProjectScope(hosts) ? partOf("scope", SCOPE_PART, "present", `the gh token holds the \`${PROJECT_SCOPE}\` scope`) : partOf("scope", SCOPE_PART, "refused", scopeDetail());
7517
+ }
7518
+ function projectRefOf(url) {
7519
+ const match = /^https:\/\/github\.com\/(?:orgs|users)\/([A-Za-z0-9][A-Za-z0-9-]*)\/projects\/([1-9]\d*)$/u.exec(url);
7520
+ if (match === null)
7521
+ return null;
7522
+ return { owner: match[1] ?? "", number: Number.parseInt(match[2] ?? "", 10) };
7523
+ }
7524
+ async function copyTemplate(port, config, repository, owner) {
7525
+ const template = config.boardProjectTemplate;
7526
+ const ref2 = projectRefOf(template);
7527
+ if (ref2 === null) {
7528
+ return { part: partOf("project", COPY_PART, "refused", `board.project.template is ${template}, not a project URL`), project: null };
7529
+ }
7530
+ const found = await port.find(ref2);
7531
+ if (found === null) {
7532
+ const why = `the template ${template} was not found; set board.project.template to a project the gh account can read`;
7533
+ return { part: partOf("project", COPY_PART, "refused", why), project: null };
7534
+ }
7535
+ const title = repository.split("/")[1] ?? repository;
7536
+ const project = await port.copy({ templateId: found.id, owner, title });
7537
+ const copied = `copied ${template} to ${project.url}, titled ${title}`;
7538
+ try {
7539
+ await port.link(project.id, repository);
7540
+ } catch (error) {
7541
+ return { part: partOf("project", COPY_PART, "refused", `${copied}, but ${repository} was not linked: ${refusalOf(error)}`), project };
7542
+ }
7543
+ return { part: partOf("project", COPY_PART, "created", `${copied}, and linked ${repository}`), project };
7544
+ }
7545
+ async function copyStep(port, config, repository) {
7546
+ const owner = repository.split("/")[0] ?? "";
7547
+ try {
7548
+ const number = config.boardProjectNumber;
7549
+ const held = number === null ? null : await port.find({ owner, number });
7550
+ if (held !== null)
7551
+ return { part: partOf("project", COPY_PART, "present", held.url), project: held };
7552
+ return await copyTemplate(port, config, repository, owner);
7553
+ } catch (error) {
7554
+ return { part: partOf("project", COPY_PART, "refused", refusalOf(error)), project: null };
7555
+ }
7556
+ }
7557
+ function heldIssues(items, repository) {
7558
+ const wanted = repository.toLowerCase();
7559
+ return new Set(items.flatMap(({ content }) => content.kind === "issue" && content.repository.toLowerCase() === wanted ? [content.number] : []));
7560
+ }
7561
+ function issueCount(count) {
7562
+ return count === 1 ? "1 issue" : `${String(count)} issues`;
7563
+ }
7564
+ async function missingIssues(options, repository, project) {
7565
+ const { config, gh } = options;
7566
+ const board = await readRefreshBoard(config, gh, repository);
7567
+ const held = heldIssues(await createGhProjectPort(gh).items(project.id), repository);
7568
+ const ranked = board.closed.filter((issue) => rankOf(board.ranks, issue) !== null);
7569
+ const unread = board.closed.filter((issue) => rankOf(board.ranks, issue) === null && !held.has(issue));
7570
+ const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
7571
+ const inReview = unread.filter((issue) => {
7572
+ const read = facts.get(issue);
7573
+ return read !== undefined && stageOf(read) === "In review";
7574
+ });
7575
+ return [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
7576
+ }
7577
+ async function itemsStep(options, repository, project) {
7578
+ const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
7579
+ let missing;
7580
+ try {
7581
+ missing = await missingIssues(options, repository, project);
7582
+ } catch (error) {
7583
+ return partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`);
7584
+ }
7585
+ if (missing.length === 0)
7586
+ return partOf("items", ITEMS_PART, "present", "the project holds every issue it shows");
7587
+ const port = createGhProjectPort(options.gh);
7588
+ let added = 0;
7589
+ try {
7590
+ for (const number of missing) {
7591
+ if (added > 0)
7592
+ await sleep(PROJECT_WRITE_PAUSE_MS);
7593
+ await port.addItem(project.id, { repository, number });
7594
+ added += 1;
7595
+ }
7596
+ } catch (error) {
7597
+ const went = `${issueCount(added)} of ${String(missing.length)} added`;
7598
+ return partOf("items", ITEMS_PART, "refused", `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`);
7599
+ }
7600
+ return partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`);
7601
+ }
7602
+ async function fieldsStep(options, project) {
7603
+ const config = { ...options.config, boardProjectNumber: project.number };
7604
+ const refreshOptions = options.sleep === undefined ? { config, gh: options.gh } : { config, gh: options.gh, sleep: options.sleep };
7605
+ let refreshed;
7606
+ try {
7607
+ refreshed = await refreshProjectItems(refreshOptions, [], { everyItem: true });
7608
+ } catch (error) {
7609
+ return { part: partOf("fields", FIELDS_PART, "refused", refusalOf(error)), problems: [] };
7610
+ }
7611
+ if (refreshed.kind === "refused")
7612
+ return { part: partOf("fields", FIELDS_PART, "refused", scopeDetail()), problems: [] };
7613
+ if (refreshed.kind !== "refreshed") {
7614
+ return { part: partOf("fields", FIELDS_PART, "refused", refreshed.warnings.join(" ") || "the refresh sent no call"), problems: [] };
7615
+ }
7616
+ const { writes, changes, skipped } = refreshed;
7617
+ const problems = refreshed.warnings.slice(writes.rateLimited ? 1 : 0);
7618
+ if (writes.rateLimited) {
7619
+ return { part: partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), problems };
7620
+ }
7621
+ if (changes.length === 0) {
7622
+ const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
7623
+ return { part: partOf("fields", FIELDS_PART, "present", held), problems };
7624
+ }
7625
+ const issues = new Set(changes.map(({ issue }) => issue)).size;
7626
+ const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
7627
+ return { part: partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}`), problems };
7628
+ }
7629
+ async function onProject(options, repository, project) {
7630
+ const items = await itemsStep(options, repository, project);
7631
+ const fields = await fieldsStep(options, project);
7632
+ const setting = writeProjectNumber(options.root, project.number);
7633
+ return { parts: [items, fields.part, setting], problems: fields.problems };
7634
+ }
7635
+ async function setUpProject(options) {
7636
+ const { gh, config } = options;
7637
+ const scope = await readScopePart(gh);
7638
+ if (scope.outcome === "refused") {
7639
+ return { parts: [scope, ...notTried(["project", "items", "fields", "setting"], "the gh token has no project scope")], project: null, problems: [] };
7640
+ }
7641
+ let repository;
7642
+ try {
7643
+ repository = await readBoardRepository(gh);
7644
+ } catch (error) {
7645
+ const why = messageOf(error);
7646
+ return { parts: [scope, partOf("project", COPY_PART, "refused", why), ...notTried(["items", "fields", "setting"], "no project")], project: null, problems: [] };
7647
+ }
7648
+ const copied = await copyStep(createGhProjectPort(gh), config, repository);
7649
+ if (copied.project === null) {
7650
+ return { parts: [scope, copied.part, ...notTried(["items", "fields", "setting"], "no project")], project: null, problems: [] };
7651
+ }
7652
+ const { project } = copied;
7653
+ const rest = await onProject(options, repository, project);
7654
+ return {
7655
+ parts: [scope, copied.part, ...rest.parts],
7656
+ project: { owner: project.owner, number: project.number, url: project.url },
7657
+ problems: rest.problems
7658
+ };
7659
+ }
7660
+ function projectSetupChanged(report2) {
7661
+ return report2.parts.some((part) => part.outcome === "created");
7662
+ }
7663
+ var PROJECT_HEADING = "GitHub project:";
7664
+ var OUTCOME_WIDTH3 = 7;
7665
+ function projectPartLine(part) {
7666
+ const row = ` ${part.outcome.padEnd(OUTCOME_WIDTH3, " ")} ${part.name}`;
7667
+ return part.outcome === "present" ? row : `${row}: ${part.detail}`;
7668
+ }
7669
+ function renderProjectSetup(report2) {
7670
+ return [PROJECT_HEADING, ...report2.parts.map((part) => projectPartLine(part)), ...report2.problems];
7671
+ }
7672
+ var PROJECT_QUESTION = "Also create a GitHub project with roadmap and kanban views? [y/N] ";
7673
+ var PROJECT_NO_BOARD_WARNING = "--project creates the GitHub project with the GitHub board," + ` and the board step did not run, so no project was created; run ${PROJECT_STEP_FIX}.`;
7674
+ function noProject(status, asked, warnings = []) {
7675
+ return { status, asked, report: null, warnings: Object.freeze([...warnings]) };
7676
+ }
7677
+ async function askProject(openPrompter) {
7678
+ const prompter = openPrompter();
7679
+ try {
7680
+ const answer = await prompter.ask(PROJECT_QUESTION);
7681
+ return answer !== null && YES_ANSWERS.includes(answer.trim().toLowerCase());
7682
+ } finally {
7683
+ prompter.close();
7684
+ }
7685
+ }
7686
+ function withWrittenRoadmap(root, config) {
7687
+ const written = readRoadmapSetting(root).issue;
7688
+ return written === null ? config : { ...config, roadmapIssue: written };
7689
+ }
7690
+ async function runProjectStep(options) {
7691
+ const { wanted, board, root, isTerminal, openPrompter } = options;
7692
+ if (board.status !== "ran") {
7693
+ return noProject("not-run", false, wanted === true ? [PROJECT_NO_BOARD_WARNING] : []);
7694
+ }
7695
+ if (wanted === false)
7696
+ return noProject("declined", false);
7697
+ const run = async (asked) => {
7698
+ const setUp = options.setUp ?? setUpProject;
7699
+ const report2 = await setUp({ root, config: withWrittenRoadmap(root, options.config), gh: options.openGh() });
7700
+ return { status: "ran", asked, report: report2, warnings: [] };
7701
+ };
7702
+ if (wanted === true)
7703
+ return run(false);
7704
+ if (!isTerminal())
7705
+ return noProject("unasked", false);
7706
+ return await askProject(openPrompter) ? run(true) : noProject("declined", true);
7707
+ }
7708
+ function projectStepChanged(result2) {
7709
+ return result2.report !== null && projectSetupChanged(result2.report);
7710
+ }
7711
+ function renderProjectStep(result2) {
7712
+ if (result2.report !== null)
7713
+ return renderProjectSetup(result2.report);
7714
+ if (result2.status === "declined")
7715
+ return [`The GitHub project was left out; run ${PROJECT_STEP_FIX} to create it.`];
7716
+ if (result2.status === "unasked")
7717
+ return [`The GitHub project question needs a terminal; run ${PROJECT_STEP_FIX} to create it.`];
7718
+ return [];
7719
+ }
7720
+
7721
+ // src/commands/doctor-project.ts
7722
+ var SCOPE_ROW = "project scope";
7723
+ var PROJECT_ROW = "project";
7724
+ var FIELDS_ROW = "project fields";
7725
+ var DRIFT_CHECK = "rafa board sync --dry-run";
7726
+ var OUTCOME_WIDTH4 = 7;
7727
+ function rowOf2(kind, name, outcome, detail) {
7728
+ return { kind, name, outcome, detail };
7729
+ }
7730
+ function noScope() {
7731
+ return `the gh token has no \`${PROJECT_SCOPE}\` scope; run \`${PROJECT_SCOPE_FIX}\``;
7732
+ }
7733
+ async function readScopeRow(gh) {
7734
+ const command = `gh ${SCOPE_ARGS.join(" ")}`;
7735
+ const result2 = await gh(SCOPE_ARGS);
7736
+ if (!result2.ok) {
7737
+ const written = result2.stderr.trim() || result2.stdout.trim() || "it wrote nothing";
7738
+ return rowOf2("scope", SCOPE_ROW, "unknown", `${command} failed: ${written}`);
7739
+ }
7740
+ let payload;
7741
+ try {
7742
+ payload = JSON.parse(result2.stdout);
7743
+ } catch (error) {
7744
+ return rowOf2("scope", SCOPE_ROW, "unknown", `${command} wrote output that is not JSON: ${messageOf(error)}`);
7745
+ }
7746
+ const hosts = isMapping(payload) ? payload["hosts"] : null;
7747
+ return holdsProjectScope(hosts) ? rowOf2("scope", SCOPE_ROW, "present", `the gh token holds the \`${PROJECT_SCOPE}\` scope`) : rowOf2("scope", SCOPE_ROW, "missing", noScope());
7748
+ }
7749
+ function notRead(kind, name, before) {
7750
+ return rowOf2(kind, name, "unknown", `not read: the ${before.name} row is ${before.outcome}`);
7751
+ }
7752
+ async function readProjectStep(gh, number) {
7753
+ const name = `${PROJECT_ROW} ${String(number)}`;
7754
+ try {
7755
+ const owner = (await readBoardRepository(gh)).split("/")[0] ?? "";
7756
+ const project = await createGhProjectPort(gh).find({ owner, number });
7757
+ if (project === null) {
7758
+ const why = `board.project.number ${String(number)} names no project of ${owner}; run \`${PROJECT_STEP_FIX}\` to make one`;
7759
+ return { row: rowOf2("project", name, "missing", why), project: null };
7760
+ }
7761
+ return { row: rowOf2("project", name, "present", project.url), project };
7762
+ } catch (error) {
7763
+ const row = isMissingProjectScope(error) ? rowOf2("project", name, "missing", noScope()) : rowOf2("project", name, "unknown", messageOf(error));
7764
+ return { row, project: null };
7765
+ }
7766
+ }
7767
+ function fieldsRow(project, template) {
7768
+ const { mismatched } = matchProjectFields(project);
7769
+ if (mismatched.length === 0)
7770
+ return rowOf2("fields", FIELDS_ROW, "present", "the five fields carry the template's names and options");
7771
+ const sentences = mismatched.map((mismatch) => mismatch.sentence).join(" ");
7772
+ const skipped = mismatched.length === 1 ? "that field" : "those fields";
7773
+ const fix = `Give the project the fields and options of board.project.template (${template}); until then the refresh skips ${skipped}.`;
7774
+ return rowOf2("fields", FIELDS_ROW, "missing", `${sentences} ${fix}`);
7775
+ }
7776
+ async function readDoctorProject(input) {
7777
+ const { gh, number, template } = input;
7778
+ if (gh === null || number === null)
7779
+ return null;
7780
+ const scope = await readScopeRow(gh);
7781
+ if (scope.outcome !== "present") {
7782
+ const project = notRead("project", `${PROJECT_ROW} ${String(number)}`, scope);
7783
+ return { number, rows: [scope, project, notRead("fields", FIELDS_ROW, project)] };
7784
+ }
7785
+ const step = await readProjectStep(gh, number);
7786
+ const fields = step.project === null ? notRead("fields", FIELDS_ROW, step.row) : fieldsRow(step.project, template);
7787
+ return { number, rows: [scope, step.row, fields] };
7788
+ }
7789
+ function projectRowLine(row) {
7790
+ const line = ` ${row.outcome.padEnd(OUTCOME_WIDTH4, " ")} ${row.name}`;
7791
+ if (row.outcome !== "present")
7792
+ return `${line}: ${row.detail}`;
7793
+ return row.kind === "project" ? `${line}: ${row.detail}` : line;
7794
+ }
7795
+ function renderDoctorProject(reading) {
7796
+ if (reading === null)
7797
+ return [];
7798
+ const inPlace = reading.rows.every((row) => row.outcome === "present");
7799
+ const drift = inPlace ? [`Run ${DRIFT_CHECK} to check the project for drift.`] : [];
7800
+ return [PROJECT_HEADING, ...reading.rows.map(projectRowLine), ...drift];
7801
+ }
7802
+
7138
7803
  // src/commands/doctor-release.ts
7139
- import { readFileSync as readFileSync6 } from "fs";
7804
+ import { readFileSync as readFileSync7 } from "fs";
7140
7805
  import { join as join14 } from "path";
7141
7806
  var SETTLE_COMMAND = "rafa release settle";
7142
7807
  var UNREAD = "?";
7143
7808
  function readTopHeading(root, configured) {
7144
7809
  const resolved = join14(root, configured);
7145
7810
  try {
7146
- const text = readFileSync6(resolved, "utf8");
7811
+ const text = readFileSync7(resolved, "utf8");
7147
7812
  return { path: configured, version: changelogVersions(text)[0] ?? null, problem: null };
7148
7813
  } catch {
7149
7814
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -7186,8 +7851,8 @@ function waitingPart(waiting) {
7186
7851
  return "no fragment waits";
7187
7852
  const ids = waiting.fragments.map((each) => each.id);
7188
7853
  const named = ids.length === 0 ? "" : ` (${ids.join(", ")})`;
7189
- const counted2 = count === 1 ? "1 fragment waits" : `${String(count)} fragments wait`;
7190
- return `${counted2}${named}: ${forecastPhrase(waiting)}; run ${SETTLE_COMMAND} to fold them into one version`;
7854
+ const counted3 = count === 1 ? "1 fragment waits" : `${String(count)} fragments wait`;
7855
+ return `${counted3}${named}: ${forecastPhrase(waiting)}; run ${SETTLE_COMMAND} to fold them into one version`;
7191
7856
  }
7192
7857
  function releaseRow(reading) {
7193
7858
  const { waiting } = reading;
@@ -7225,14 +7890,14 @@ function writeDoctorRelease(context, reading) {
7225
7890
  }
7226
7891
 
7227
7892
  // src/commands/doctor-tiers.ts
7228
- import { readFileSync as readFileSync7, realpathSync as realpathSync2 } from "fs";
7893
+ import { readFileSync as readFileSync8, realpathSync as realpathSync2 } from "fs";
7229
7894
  import { basename as basename4, dirname as dirname2 } from "path";
7230
7895
  var TIERS_SECTION_TITLE = "Skill tiers";
7231
7896
  var CLAUDE_VERSION_TIMEOUT_MS = 1e4;
7232
7897
  var VERSION_PATTERN = /^\s*(\d+\.\d+\.\d+)/;
7233
7898
  function readTextFile(path) {
7234
7899
  try {
7235
- return readFileSync7(path, "utf8");
7900
+ return readFileSync8(path, "utf8");
7236
7901
  } catch {
7237
7902
  return null;
7238
7903
  }
@@ -7531,6 +8196,7 @@ function resultOf(preflight, install, readings, deep) {
7531
8196
  epics: readings.epics,
7532
8197
  boards: readings.boards,
7533
8198
  ...relationsResultOf(readings),
8199
+ project: readings.project,
7534
8200
  cleanup: readings.cleanup,
7535
8201
  refs: readings.refs,
7536
8202
  release: readings.release,
@@ -7575,10 +8241,12 @@ async function runDoctor(context, seams) {
7575
8241
  const effortSync = await readDoctorEffortSync({ root: project.root, home: project.home, resolved: preflight.resolved }, seams);
7576
8242
  const modeSet = preflight.resolved.sources.boardRelationships !== "default";
7577
8243
  const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue, preflight.config.boardRelationships, modeSet);
7578
- const readings = { ...board, cleanup, refs, release, tiers, effortSchema, effortSync };
8244
+ const { boardProjectNumber: number, boardProjectTemplate: template } = preflight.config;
8245
+ const projectRows = await readDoctorProject({ gh, number, template });
8246
+ const readings = { ...board, project: projectRows, cleanup, refs, release, tiers, effortSchema, effortSync };
7579
8247
  writeText(context, renderDoctor(preflight));
7580
8248
  await announceRisk(context, preflight, seams);
7581
- const repository = [...renderDoctorBoard(readings), ...renderDoctorCleanup(readings.cleanup)];
8249
+ const repository = [...renderDoctorBoard(readings), ...renderDoctorProject(projectRows), ...renderDoctorCleanup(readings.cleanup)];
7582
8250
  writeText(context, [...repository, ...renderDoctorRefs(readings.refs)]);
7583
8251
  writeDoctorRelease(context, release);
7584
8252
  writeText(context, renderDoctorTiers(readings.tiers));
@@ -8054,10 +8722,10 @@ function readDashboard(options) {
8054
8722
 
8055
8723
  // src/commands/effort/dashboard.ts
8056
8724
  var COMMAND_NAME6 = "rafa effort dashboard";
8057
- var USAGE12 = "rafa effort dashboard [--days=<n>] [--recent=<n>] [--loops=<n>] [--by=effort|model|agent]";
8725
+ var USAGE13 = "rafa effort dashboard [--days=<n>] [--recent=<n>] [--loops=<n>] [--by=effort|model|agent]";
8058
8726
  var TREND_FLAGS = ["days", "recent", "loops", "by"];
8059
8727
  function refuse5(problems) {
8060
- throw new CommandExit(1, [...problems.map((problem) => `\u274C ${COMMAND_NAME6}: ${problem}`), `Usage: ${USAGE12}`].join(`
8728
+ throw new CommandExit(1, [...problems.map((problem) => `\u274C ${COMMAND_NAME6}: ${problem}`), `Usage: ${USAGE13}`].join(`
8061
8729
  `));
8062
8730
  }
8063
8731
  function trendOptionsOf(context) {
@@ -8073,7 +8741,7 @@ function trendOptionsOf(context) {
8073
8741
  return parsed.trend;
8074
8742
  }
8075
8743
  function runDashboard(context, seams) {
8076
- expectNoArgument(context.args, USAGE12);
8744
+ expectNoArgument(context.args, USAGE13);
8077
8745
  const root = requireProject(context, COMMAND_NAME6).root;
8078
8746
  const trend = trendOptionsOf(context);
8079
8747
  let dashboard;
@@ -8150,7 +8818,7 @@ import { resolve as resolve6 } from "path";
8150
8818
  import { join as join16, resolve as resolve5 } from "path";
8151
8819
  var COMMAND_NAME7 = "rafa effort merge";
8152
8820
  var MERGE_USAGE = "rafa effort merge <file> [--dry-run]";
8153
- var DRY_RUN_FLAG2 = "dry-run";
8821
+ var DRY_RUN_FLAG3 = "dry-run";
8154
8822
  var REFUSED_EXIT2 = 1;
8155
8823
  var UNMERGEABLE_EXIT = 2;
8156
8824
  var UNMERGEABLE_REASONS = new Set(["other-project", "ndjson"]);
@@ -8167,7 +8835,7 @@ function tableLine(entry) {
8167
8835
  ];
8168
8836
  return ` ${entry.table}: ${counts.join(", ")}`;
8169
8837
  }
8170
- function closingLine(result2, command) {
8838
+ function closingLine2(result2, command) {
8171
8839
  if (result2.status === "would-merge") {
8172
8840
  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.`;
8173
8841
  }
@@ -8181,7 +8849,7 @@ function renderMerge(result2, command = COMMAND_NAME7) {
8181
8849
  ...forward,
8182
8850
  ...result2.tables.map(tableLine),
8183
8851
  `Total: ${String(result2.rowsAdded)} added, ${String(result2.rowsSkipped)} skipped,` + ` ${String(result2.rowsInConflict)} in conflict; ${String(result2.gapsRewritten)} commit gaps recomputed.`,
8184
- closingLine(result2, command)
8852
+ closingLine2(result2, command)
8185
8853
  ];
8186
8854
  }
8187
8855
  function mergeTargetPath(context, root, command = COMMAND_NAME7) {
@@ -8210,7 +8878,7 @@ function probeSeams(seams) {
8210
8878
  }
8211
8879
  function runMerge(context, seams) {
8212
8880
  const file = expectOneArgument(context.args, MERGE_USAGE);
8213
- const dryRun = readSwitch(DRY_RUN_FLAG2, context.flags[DRY_RUN_FLAG2], `Usage: ${MERGE_USAGE}`);
8881
+ const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MERGE_USAGE}`);
8214
8882
  const project = requireProject(context, COMMAND_NAME7);
8215
8883
  const path = mergeTargetPath(context, project.root);
8216
8884
  const backend = resolveProjectConfig(project, COMMAND_NAME7, (message) => context.output.warn(message)).store;
@@ -8246,7 +8914,7 @@ function createMergeCommand(seams = {}) {
8246
8914
  ],
8247
8915
  flags: [
8248
8916
  {
8249
- name: DRY_RUN_FLAG2,
8917
+ name: DRY_RUN_FLAG3,
8250
8918
  description: "Build and check the merged store, print what it would add, then delete it. This store is" + " only read, so this runs beside a live loop and from a development build, and can be repeated.",
8251
8919
  type: "boolean"
8252
8920
  }
@@ -8274,7 +8942,7 @@ var merge_default = createMergeCommand();
8274
8942
  // src/commands/effort/import.ts
8275
8943
  var COMMAND_NAME8 = "rafa effort import";
8276
8944
  var IMPORT_USAGE = "rafa effort import <file> [--dry-run]";
8277
- var DRY_RUN_FLAG3 = "dry-run";
8945
+ var DRY_RUN_FLAG4 = "dry-run";
8278
8946
  function syncOptions(context, repoRoot, backend, seams) {
8279
8947
  const optional = {
8280
8948
  now: seams.now,
@@ -8301,7 +8969,7 @@ async function pull(sync, otherPath, dryRun) {
8301
8969
  }
8302
8970
  async function runImport(context, seams) {
8303
8971
  const file = expectOneArgument(context.args, IMPORT_USAGE);
8304
- const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${IMPORT_USAGE}`);
8972
+ const dryRun = readSwitch(DRY_RUN_FLAG4, context.flags[DRY_RUN_FLAG4], `Usage: ${IMPORT_USAGE}`);
8305
8973
  const project = requireProject(context, COMMAND_NAME8);
8306
8974
  mergeTargetPath(context, project.root, COMMAND_NAME8);
8307
8975
  const backend = resolveProjectConfig(project, COMMAND_NAME8, (message) => context.output.warn(message)).store;
@@ -8332,7 +9000,7 @@ function createImportCommand(seams = {}) {
8332
9000
  ],
8333
9001
  flags: [
8334
9002
  {
8335
- name: DRY_RUN_FLAG3,
9003
+ name: DRY_RUN_FLAG4,
8336
9004
  description: "Build and check the merged store, print what it would add, then delete it. This store is" + " only read, so this runs beside a live loop and from a development build, and can be repeated.",
8337
9005
  type: "boolean"
8338
9006
  }
@@ -8360,7 +9028,7 @@ var import_default = createImportCommand();
8360
9028
  import { join as join17 } from "path";
8361
9029
  var COMMAND_NAME9 = "rafa effort migrate";
8362
9030
  var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
8363
- var DRY_RUN_FLAG4 = "dry-run";
9031
+ var DRY_RUN_FLAG5 = "dry-run";
8364
9032
  var REFUSED_EXIT3 = 1;
8365
9033
  function builtLines(result2) {
8366
9034
  const rows = result2.counted.reduce((sum, table) => sum + table.rows, 0);
@@ -8397,7 +9065,7 @@ function storePath(context, root) {
8397
9065
  }
8398
9066
  function runMigrate(context, seams) {
8399
9067
  expectNoArgument(context.args, MIGRATE_USAGE);
8400
- const dryRun = readSwitch(DRY_RUN_FLAG4, context.flags[DRY_RUN_FLAG4], `Usage: ${MIGRATE_USAGE}`);
9068
+ const dryRun = readSwitch(DRY_RUN_FLAG5, context.flags[DRY_RUN_FLAG5], `Usage: ${MIGRATE_USAGE}`);
8401
9069
  const path = storePath(context, requireProject(context, COMMAND_NAME9).root);
8402
9070
  const now = seams.now ?? (() => new Date);
8403
9071
  let result2;
@@ -8435,7 +9103,7 @@ function createMigrateCommand(seams = {}) {
8435
9103
  args: [],
8436
9104
  flags: [
8437
9105
  {
8438
- name: DRY_RUN_FLAG4,
9106
+ name: DRY_RUN_FLAG5,
8439
9107
  description: "Build and check the migrated store, print what it applies, then delete it. The live store" + " is only read, so this runs beside a live loop and from a development build, and can be repeated.",
8440
9108
  type: "boolean"
8441
9109
  }
@@ -8461,7 +9129,7 @@ function createMigrateCommand(seams = {}) {
8461
9129
  var migrate_default = createMigrateCommand();
8462
9130
 
8463
9131
  // src/effort/store/move.ts
8464
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
9132
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
8465
9133
  var MOVED_KINDS = Object.freeze(["sessions", "commits"]);
8466
9134
  var MOVE_TARGET = "sqlite";
8467
9135
  var STORE_LINE = `store: ${MOVE_TARGET}`;
@@ -8503,7 +9171,7 @@ function withSqliteStoreSetting(text) {
8503
9171
  function plannedConfig(path) {
8504
9172
  let text;
8505
9173
  try {
8506
- text = readFileSync8(path, "utf8");
9174
+ text = readFileSync9(path, "utf8");
8507
9175
  } catch (error) {
8508
9176
  throw new MoveRefusal("config", `${path} could not be read: ${messageOf(error)}. Nothing was moved.`);
8509
9177
  }
@@ -8543,22 +9211,22 @@ function moveKind(kind, source, target) {
8543
9211
  const { keyed, unkeyed } = keyedRows(kind, reading.rows);
8544
9212
  const project = EFFORT_KEY_PROJECTIONS[kind];
8545
9213
  const before = target.keys(kind).size;
8546
- const appended = target.append(kind, keyed);
9214
+ const appended2 = target.append(kind, keyed);
8547
9215
  const keys = keyed.map((row2) => project(row2) ?? "");
8548
- checkCounts(kind, before, appended.appended, keys, target.keys(kind));
9216
+ checkCounts(kind, before, appended2.appended, keys, target.keys(kind));
8549
9217
  return {
8550
9218
  kind,
8551
9219
  from: source.path(kind),
8552
9220
  read: keyed.length,
8553
- added: appended.appended,
8554
- skipped: appended.skipped,
9221
+ added: appended2.appended,
9222
+ skipped: appended2.skipped,
8555
9223
  unkeyed,
8556
9224
  unparsed: reading.unparsedLineCount
8557
9225
  };
8558
9226
  }
8559
9227
  function writeConfig(path, text) {
8560
9228
  try {
8561
- writeFileSync2(path, text, "utf8");
9229
+ writeFileSync3(path, text, "utf8");
8562
9230
  } catch (error) {
8563
9231
  throw new MoveRefusal("config", `${path} could not be written (${messageOf(error)}). The rows were moved and checked; set ${STORE_LINE}` + " there by hand, or run the move again, which adds nothing.");
8564
9232
  }
@@ -8884,6 +9552,93 @@ function keptLinksLine(issue, waitsOn) {
8884
9552
  return `${ref2(issue)} keeps its blocked-by ${links} to ${waitsOn.map(ref2).join(" ")}: in native mode the cancel clears` + " nothing, and GitHub stops holding an issue back once its blocker closes.";
8885
9553
  }
8886
9554
 
9555
+ // src/board/project/add-issue.ts
9556
+ async function addItem(gh, number, issue) {
9557
+ const repository = await readBoardRepository(gh);
9558
+ const ref3 = { owner: repository.split("/")[0] ?? "", number };
9559
+ const port = createGhProjectPort(gh);
9560
+ const project = await port.find(ref3);
9561
+ if (project === null)
9562
+ return notFoundWarning(ref3);
9563
+ await port.addItem(project.id, { repository, number: issue });
9564
+ return null;
9565
+ }
9566
+ async function addAndRefreshIssue(options, issue) {
9567
+ const { config, openGh, refresh = refreshProjectItems } = options;
9568
+ if (config.boardProjectNumber === null)
9569
+ return [];
9570
+ const gh = openGh();
9571
+ try {
9572
+ const refused = await addItem(gh, config.boardProjectNumber, issue);
9573
+ if (refused !== null)
9574
+ return [refused];
9575
+ const refreshOptions = options.sleep === undefined ? { config, gh } : { config, gh, sleep: options.sleep };
9576
+ return (await refresh(refreshOptions, [issue])).warnings;
9577
+ } catch (error) {
9578
+ return isMissingProjectScope(error) ? [scopeWarning()] : [refreshFailedWarning(issue, error)];
9579
+ }
9580
+ }
9581
+
9582
+ // src/commands/epic/epic-project.ts
9583
+ function oneLine2(message) {
9584
+ return message.replace(/^\u274C\s*/u, "").split(`
9585
+ `).map((line) => line.trim()).filter((line) => line !== "").join(" ");
9586
+ }
9587
+ function epicProjectProblemLine(issues, problem) {
9588
+ const named = issues.map((issue) => `#${String(issue)}`).join(", ");
9589
+ return `The project was not updated for ${named}: ${oneLine2(problem)}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
9590
+ }
9591
+ function newEpicTarget(result2) {
9592
+ return { epics: [result2.epic.number], issues: [], added: result2.epic.number };
9593
+ }
9594
+ function horizonTarget(result2) {
9595
+ return result2.status === "moved" ? { epics: [result2.epic], issues: [], added: null } : null;
9596
+ }
9597
+ function moveTarget(result2) {
9598
+ return result2.outcome === null ? null : { epics: [result2.from, result2.to], issues: [result2.issue], added: null };
9599
+ }
9600
+ function cancelTarget(result2) {
9601
+ if (result2.status !== "cancelled")
9602
+ return null;
9603
+ const moves = result2.applied.flatMap(({ answer }) => answer.kind === "moved" ? [answer.from, answer.to] : []);
9604
+ return { epics: [result2.epic, ...moves], issues: result2.applied.map(({ issue }) => issue), added: null };
9605
+ }
9606
+ function closeTarget(result2) {
9607
+ return { epics: [result2.epic], issues: [], added: null };
9608
+ }
9609
+ async function refreshLines(config, openGh, refresh, target, named) {
9610
+ const widening = { membersOf: target.epics, shiftedRanks: true };
9611
+ if (target.added !== null) {
9612
+ return addAndRefreshIssue({ config, openGh, refresh: (options, issues) => refresh(options, [...new Set([...issues, ...named])], widening) }, target.added);
9613
+ }
9614
+ try {
9615
+ return (await refresh({ config, gh: openGh() }, named, widening)).warnings;
9616
+ } catch (error) {
9617
+ return [epicProjectProblemLine(named, messageOf(error))];
9618
+ }
9619
+ }
9620
+ async function refreshProjectAfterEpic(context, seams, target) {
9621
+ if (target === null)
9622
+ return null;
9623
+ const project = issueProject(context);
9624
+ const named = [...new Set([...target.epics, ...target.issues])];
9625
+ let warnings;
9626
+ try {
9627
+ const config = issueSubjectConfig(project, () => {
9628
+ return;
9629
+ });
9630
+ if (config.boardProjectNumber === null)
9631
+ return null;
9632
+ const openGh = () => seams.gh ?? createGhRunner({ cwd: project.root });
9633
+ warnings = await refreshLines(config, openGh, seams.projectRefresh ?? refreshIssueItems, target, named);
9634
+ } catch (error) {
9635
+ warnings = [epicProjectProblemLine(named, messageOf(error))];
9636
+ }
9637
+ for (const line of warnings)
9638
+ context.output.warn(line);
9639
+ return { issues: named, membersOf: target.epics, added: target.added, warnings };
9640
+ }
9641
+
8887
9642
  // src/board/epic-horizon.ts
8888
9643
  var EPIC_HORIZON_REFUSAL_EXIT = 2;
8889
9644
  var BRANCH_REF = /^refs\/(?:heads\/|remotes\/[^/]+\/)/u;
@@ -9019,11 +9774,11 @@ function readHorizonLine(context, action) {
9019
9774
  const reason = readTextFlag(flags, REASON_FLAG, action.usage) ?? null;
9020
9775
  return Object.freeze({ epic: Number(word), to, reason });
9021
9776
  }
9022
- function rankOf(horizon) {
9777
+ function rankOf2(horizon) {
9023
9778
  return HORIZONS.indexOf(horizon);
9024
9779
  }
9025
9780
  function wrongDirection(action, change) {
9026
- const later = rankOf(change.to) > rankOf(change.from);
9781
+ const later = rankOf2(change.to) > rankOf2(change.from);
9027
9782
  if (later === (action.name === "defer"))
9028
9783
  return null;
9029
9784
  const other = action.name === "defer" ? "promote" : "defer";
@@ -9176,25 +9931,24 @@ function closeFailure(result2) {
9176
9931
  async function runEpicHorizon(context, action, seams) {
9177
9932
  const result2 = await changeEpicHorizon(context, action, seams);
9178
9933
  const failure = closeFailure(result2);
9179
- if (context.outputMode === "json") {
9180
- if (failure !== null)
9181
- throw failure;
9182
- context.output.result(result2);
9183
- return;
9184
- }
9185
- for (const line of renderEpicHorizon(result2)) {
9186
- if (line.warn)
9187
- context.output.warn(line.text);
9188
- else
9189
- context.output.info(line.text);
9934
+ if (context.outputMode !== "json") {
9935
+ for (const line of renderEpicHorizon(result2)) {
9936
+ if (line.warn)
9937
+ context.output.warn(line.text);
9938
+ else
9939
+ context.output.info(line.text);
9940
+ }
9190
9941
  }
9942
+ await refreshProjectAfterEpic(context, seams, horizonTarget(result2));
9191
9943
  if (failure !== null)
9192
9944
  throw failure;
9945
+ if (context.outputMode === "json")
9946
+ context.output.result(result2);
9193
9947
  }
9194
9948
 
9195
9949
  // src/commands/epic/move.ts
9196
9950
  var EPIC_MOVE_REFUSAL_EXIT = 2;
9197
- var USAGE13 = 'rafa epic move <issue> --to=<epic> [--reason="<why>"]';
9951
+ var USAGE14 = 'rafa epic move <issue> --to=<epic> [--reason="<why>"]';
9198
9952
  var ISSUE_NUMBER4 = /^[1-9]\d*$/u;
9199
9953
  function isNativeReading(reading) {
9200
9954
  return "relationships" in reading;
@@ -9207,7 +9961,7 @@ function refusal5(message) {
9207
9961
  }
9208
9962
  function issueNumber(word, what) {
9209
9963
  if (!ISSUE_NUMBER4.test(word))
9210
- throw lineRefusal(`"${word}" is no ${what}, which is a whole number from 1`, USAGE13);
9964
+ throw lineRefusal(`"${word}" is no ${what}, which is a whole number from 1`, USAGE14);
9211
9965
  return Number(word);
9212
9966
  }
9213
9967
  function readMoveLine(context) {
@@ -9215,14 +9969,14 @@ function readMoveLine(context) {
9215
9969
  const [word] = args;
9216
9970
  if (args.length !== 1 || word === undefined) {
9217
9971
  const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
9218
- throw lineRefusal(`Expected one issue number, got ${got}`, USAGE13);
9972
+ throw lineRefusal(`Expected one issue number, got ${got}`, USAGE14);
9219
9973
  }
9220
9974
  const issue = issueNumber(word, "issue number");
9221
- const target = readTextFlag(flags, TO_FLAG4, USAGE13);
9975
+ const target = readTextFlag(flags, TO_FLAG4, USAGE14);
9222
9976
  if (target === undefined)
9223
- throw lineRefusal(`--${TO_FLAG4} is required: --${TO_FLAG4}=<epic>`, USAGE13);
9977
+ throw lineRefusal(`--${TO_FLAG4} is required: --${TO_FLAG4}=<epic>`, USAGE14);
9224
9978
  const to = issueNumber(target, "epic number");
9225
- const reason = readTextFlag(flags, REASON_FLAG, USAGE13) ?? null;
9979
+ const reason = readTextFlag(flags, REASON_FLAG, USAGE14) ?? null;
9226
9980
  return Object.freeze({ issue, to, reason });
9227
9981
  }
9228
9982
  function memberRow(issues, number) {
@@ -9504,20 +10258,19 @@ function moveFailure(result2) {
9504
10258
  async function runEpicMove(context, seams) {
9505
10259
  const result2 = await moveEpicIssue(context, seams);
9506
10260
  const failure = moveFailure(result2);
9507
- if (context.outputMode === "json") {
9508
- if (failure !== null)
9509
- throw failure;
9510
- context.output.result(result2);
9511
- return;
9512
- }
9513
- for (const out of renderEpicMove(result2)) {
9514
- if (out.warn)
9515
- context.output.warn(out.text);
9516
- else
9517
- context.output.info(out.text);
10261
+ if (context.outputMode !== "json") {
10262
+ for (const out of renderEpicMove(result2)) {
10263
+ if (out.warn)
10264
+ context.output.warn(out.text);
10265
+ else
10266
+ context.output.info(out.text);
10267
+ }
9518
10268
  }
10269
+ await refreshProjectAfterEpic(context, seams, moveTarget(result2));
9519
10270
  if (failure !== null)
9520
10271
  throw failure;
10272
+ if (context.outputMode === "json")
10273
+ context.output.result(result2);
9521
10274
  }
9522
10275
  function createEpicMoveCommand(seams = {}) {
9523
10276
  const command = {
@@ -9562,7 +10315,7 @@ var move_default2 = createEpicMoveCommand();
9562
10315
 
9563
10316
  // src/commands/epic/cancel.ts
9564
10317
  var EPIC_CANCEL_REFUSAL_EXIT = 2;
9565
- var USAGE14 = 'rafa epic cancel <n> [--reason="<why>"]';
10318
+ var USAGE15 = 'rafa epic cancel <n> [--reason="<why>"]';
9566
10319
  var ISSUE_NUMBER5 = /^[1-9]\d*$/u;
9567
10320
  var EPIC_ANSWER = /^#?([1-9]\d*)$/u;
9568
10321
  var LINE_BREAK = /\r\n|\n|\r/u;
@@ -9581,11 +10334,11 @@ function readCancelLine(context) {
9581
10334
  const [word] = args;
9582
10335
  if (args.length !== 1 || word === undefined) {
9583
10336
  const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
9584
- throw lineRefusal(`Expected one epic number, got ${got}`, USAGE14);
10337
+ throw lineRefusal(`Expected one epic number, got ${got}`, USAGE15);
9585
10338
  }
9586
10339
  if (!ISSUE_NUMBER5.test(word))
9587
- throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE14);
9588
- const reason = readTextFlag(flags, REASON_FLAG, USAGE14) ?? null;
10340
+ throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE15);
10341
+ const reason = readTextFlag(flags, REASON_FLAG, USAGE15) ?? null;
9589
10342
  return Object.freeze({ epic: Number(word), reason });
9590
10343
  }
9591
10344
  function readEpicToCancel(issues, number, relations = LABELS_READS) {
@@ -9711,7 +10464,7 @@ async function commentOn(writes, issue, body) {
9711
10464
  }
9712
10465
  async function applyMove(input, dependent, reading) {
9713
10466
  const { change } = reading;
9714
- const base = { issue: change.issue, waitsOn: dependent.waitsOn, answer: { kind: "moved", to: change.to } };
10467
+ const base = { issue: change.issue, waitsOn: dependent.waitsOn, answer: { kind: "moved", from: change.from, to: change.to } };
9715
10468
  try {
9716
10469
  const writes = { board: input.writes.board, relations: input.writes.moves, issues: input.issues };
9717
10470
  const outcome = await applyEpicMove(writes, reading, cancelMoveReason(input.epic));
@@ -9921,6 +10674,7 @@ function cancelFailure(result2) {
9921
10674
  }
9922
10675
  async function runEpicCancel(context, seams) {
9923
10676
  const result2 = await cancelEpic(context, seams);
10677
+ await refreshProjectAfterEpic(context, seams, cancelTarget(result2));
9924
10678
  const failure = cancelFailure(result2);
9925
10679
  if (failure !== null)
9926
10680
  throw failure;
@@ -9967,7 +10721,7 @@ import { randomUUID as randomUUID3 } from "crypto";
9967
10721
  import { join as join22 } from "path";
9968
10722
 
9969
10723
  // src/board/epic-template.ts
9970
- import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
10724
+ import { existsSync as existsSync4, readFileSync as readFileSync10 } from "fs";
9971
10725
  import { dirname as dirname3, join as join19 } from "path";
9972
10726
  import { fileURLToPath as fileURLToPath2 } from "url";
9973
10727
  var MODULE_DIR2 = dirname3(fileURLToPath2(import.meta.url));
@@ -9984,7 +10738,7 @@ function readEpicTemplate(moduleDir = MODULE_DIR2) {
9984
10738
  if (!existsSync4(source)) {
9985
10739
  throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
9986
10740
  }
9987
- return readFileSync9(source, "utf8");
10741
+ return readFileSync10(source, "utf8");
9988
10742
  }
9989
10743
  function placeholderCriteria(criteria) {
9990
10744
  if (criteria === null)
@@ -10075,7 +10829,7 @@ function renderEpicCost(cost, estimate) {
10075
10829
  }
10076
10830
 
10077
10831
  // src/epic/verify-plan.ts
10078
- import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
10832
+ import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
10079
10833
  import { dirname as dirname4, join as join20 } from "path";
10080
10834
  import { fileURLToPath as fileURLToPath3 } from "url";
10081
10835
  var MODULE_DIR3 = dirname4(fileURLToPath3(import.meta.url));
@@ -10141,7 +10895,7 @@ function readVerifyPrompt(moduleDir = MODULE_DIR3) {
10141
10895
  if (found === undefined) {
10142
10896
  throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
10143
10897
  }
10144
- return readFileSync10(found, "utf8");
10898
+ return readFileSync11(found, "utf8");
10145
10899
  }
10146
10900
  function fenceFor2(text) {
10147
10901
  const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
@@ -10467,16 +11221,16 @@ async function runVerification(options) {
10467
11221
  var EPIC_CLOSE_REFUSAL_EXIT = 2;
10468
11222
  var ACCEPT_UNCHECKED_FLAG = "accept-unchecked";
10469
11223
  var PLAN_TOOLS = ["Read", "Grep", "Glob"];
10470
- var USAGE15 = `rafa epic close <n> [--${ACCEPT_UNCHECKED_FLAG}]`;
11224
+ var USAGE16 = `rafa epic close <n> [--${ACCEPT_UNCHECKED_FLAG}]`;
10471
11225
  var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
10472
11226
  function ref7(issue) {
10473
11227
  return `#${String(issue)}`;
10474
11228
  }
10475
- function oneLine2(text) {
11229
+ function oneLine3(text) {
10476
11230
  return text.replace(/\s+/gu, " ").trim();
10477
11231
  }
10478
11232
  function criterionName(criterion) {
10479
- return `criterion ${String(criterion.number)}: ${oneLine2(criterion.text)}`;
11233
+ return `criterion ${String(criterion.number)}: ${oneLine3(criterion.text)}`;
10480
11234
  }
10481
11235
  function criteria(count) {
10482
11236
  return count === 1 ? "1 criterion" : `${String(count)} criteria`;
@@ -10489,10 +11243,10 @@ function readCloseLine(context) {
10489
11243
  const [word] = args;
10490
11244
  if (args.length !== 1 || word === undefined) {
10491
11245
  const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
10492
- throw lineRefusal(`Expected one epic number, got ${got}`, USAGE15);
11246
+ throw lineRefusal(`Expected one epic number, got ${got}`, USAGE16);
10493
11247
  }
10494
11248
  if (!ISSUE_NUMBER6.test(word))
10495
- throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE15);
11249
+ throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE16);
10496
11250
  const acceptUnchecked = readSwitch(ACCEPT_UNCHECKED_FLAG, flags[ACCEPT_UNCHECKED_FLAG], `Write it after the epic number: rafa epic close <n> --${ACCEPT_UNCHECKED_FLAG}.`);
10497
11251
  return Object.freeze({ epic: Number(word), acceptUnchecked });
10498
11252
  }
@@ -10500,7 +11254,7 @@ function refuseOpenMembers(number, members) {
10500
11254
  const open = members.filter((member) => member.state === "OPEN");
10501
11255
  if (open.length === 0)
10502
11256
  return;
10503
- const named = open.map((member) => `${ref7(member.number)} ${oneLine2(member.title)}`).join(", ");
11257
+ const named = open.map((member) => `${ref7(member.number)} ${oneLine3(member.title)}`).join(", ");
10504
11258
  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");
10505
11259
  }
10506
11260
  function labelsEpicToClose(issues, epic) {
@@ -10572,7 +11326,7 @@ function closeTriageFile(root, epic) {
10572
11326
  return join22(root, ".rafa", `epic-${String(epic)}-close`);
10573
11327
  }
10574
11328
  function failedCheckArtifact(epic, criterion) {
10575
- return `epic ${ref7(epic)} acceptance criterion: ${oneLine2(criterion.text)}`;
11329
+ return `epic ${ref7(epic)} acceptance criterion: ${oneLine3(criterion.text)}`;
10576
11330
  }
10577
11331
  function failedCheckReport(epic, failure, commit) {
10578
11332
  const { criterion } = failure.check;
@@ -10596,7 +11350,7 @@ function failedCheckReport(epic, failure, commit) {
10596
11350
  skillsUsed: [],
10597
11351
  blockers: [],
10598
11352
  outOfScopeBugs: [Object.freeze({
10599
- what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine2(criterion.text)}`,
11353
+ what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine3(criterion.text)}`,
10600
11354
  artifact: failedCheckArtifact(epic, criterion),
10601
11355
  security: false,
10602
11356
  extras: []
@@ -10689,9 +11443,9 @@ async function weighResults(run, epic, ran) {
10689
11443
  if (result2.kind === "unanswered")
10690
11444
  warn2(run, `No answer for ${name} \u2014 ${result2.reason}`);
10691
11445
  else if (result2.result === "pass")
10692
- say(run, `\u2713 Passed ${name} \u2014 ${oneLine2(result2.evidence)}`);
11446
+ say(run, `\u2713 Passed ${name} \u2014 ${oneLine3(result2.evidence)}`);
10693
11447
  else
10694
- warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine2(result2.evidence)}`);
11448
+ warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine3(result2.evidence)}`);
10695
11449
  }
10696
11450
  if (!ran.removal.ok) {
10697
11451
  const said = ran.removal.said === "" ? "git said nothing" : ran.removal.said;
@@ -10788,6 +11542,7 @@ async function closeEpic(context, seams) {
10788
11542
  }
10789
11543
  async function runEpicClose(context, seams) {
10790
11544
  const result2 = await closeEpic(context, seams);
11545
+ await refreshProjectAfterEpic(context, seams, closeTarget(result2));
10791
11546
  if (context.outputMode === "json")
10792
11547
  context.output.result(result2);
10793
11548
  }
@@ -10874,7 +11629,7 @@ function createEpicDeferCommand(seams = {}) {
10874
11629
  var defer_default = createEpicDeferCommand();
10875
11630
 
10876
11631
  // src/commands/epic/new.ts
10877
- var USAGE16 = 'rafa epic new "<title>" --slug=<slug> [--horizon=now|next|later]';
11632
+ var USAGE17 = 'rafa epic new "<title>" --slug=<slug> [--horizon=now|next|later]';
10878
11633
  var EPIC_NEW_REFUSAL_EXIT = 2;
10879
11634
  var DEFAULT_HORIZON = "later";
10880
11635
  var KEBAB_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
@@ -10912,26 +11667,26 @@ function readTitle(args) {
10912
11667
  const [title] = args;
10913
11668
  if (args.length !== 1 || title === undefined) {
10914
11669
  const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
10915
- throw lineRefusal(`Expected one title, quoted, got ${got}`, USAGE16);
11670
+ throw lineRefusal(`Expected one title, quoted, got ${got}`, USAGE17);
10916
11671
  }
10917
11672
  if (title.trim() === "" || LINE_BREAK2.test(title)) {
10918
- throw lineRefusal("The title has to be one line holding some text", USAGE16);
11673
+ throw lineRefusal("The title has to be one line holding some text", USAGE17);
10919
11674
  }
10920
11675
  return title.trim();
10921
11676
  }
10922
11677
  function readEpicDraft(args, flags) {
10923
11678
  const title = readTitle(args);
10924
- const slug = readRequiredFlag(flags, "slug", USAGE16);
10925
- const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE16) ?? DEFAULT_HORIZON;
11679
+ const slug = readRequiredFlag(flags, "slug", USAGE17);
11680
+ const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE17) ?? DEFAULT_HORIZON;
10926
11681
  if (!KEBAB_SLUG.test(slug)) {
10927
11682
  throw refusal8(`--slug is "${slug}", which is not a kebab word: lowercase letters and digits` + ` joined by single hyphens, as sign-in
10928
- Usage: ${USAGE16}`);
11683
+ Usage: ${USAGE17}`);
10929
11684
  }
10930
11685
  return Object.freeze({ title, slug, horizon });
10931
11686
  }
10932
11687
  function readNativeEpicDraft(args, flags) {
10933
11688
  const title = readTitle(args);
10934
- const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE16) ?? DEFAULT_HORIZON;
11689
+ const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE17) ?? DEFAULT_HORIZON;
10935
11690
  return Object.freeze({ title, horizon });
10936
11691
  }
10937
11692
  function sameLabel(label, name) {
@@ -11074,12 +11829,13 @@ function renderEpicNew(result2) {
11074
11829
  }
11075
11830
  async function runEpicNew(context, seams) {
11076
11831
  const result2 = await createEpic(context, seams);
11077
- if (context.outputMode === "json") {
11078
- context.output.result(result2);
11079
- return;
11832
+ if (context.outputMode !== "json") {
11833
+ for (const text of renderEpicNew(result2))
11834
+ context.output.info(text);
11080
11835
  }
11081
- for (const text of renderEpicNew(result2))
11082
- context.output.info(text);
11836
+ await refreshProjectAfterEpic(context, seams, newEpicTarget(result2));
11837
+ if (context.outputMode === "json")
11838
+ context.output.result(result2);
11083
11839
  }
11084
11840
  function createEpicNewCommand(seams = {}) {
11085
11841
  const command = {
@@ -11170,12 +11926,12 @@ function createEpicPromoteCommand(seams = {}) {
11170
11926
  var promote_default = createEpicPromoteCommand();
11171
11927
 
11172
11928
  // src/commands/init.ts
11173
- import { existsSync as existsSync8, readFileSync as readFileSync14 } from "fs";
11929
+ import { existsSync as existsSync8, readFileSync as readFileSync15 } from "fs";
11174
11930
  import { homedir as homedir2 } from "os";
11175
11931
  import { join as join26, relative as relative5, sep as sep3 } from "path";
11176
11932
 
11177
11933
  // src/agents/vendorable.ts
11178
- import { readdirSync, readFileSync as readFileSync11 } from "fs";
11934
+ import { readdirSync, readFileSync as readFileSync12 } from "fs";
11179
11935
  import { isAbsolute as isAbsolute3, join as join23 } from "path";
11180
11936
  function planFiles(dir) {
11181
11937
  let entries;
@@ -11188,7 +11944,7 @@ function planFiles(dir) {
11188
11944
  }
11189
11945
  function readText2(path) {
11190
11946
  try {
11191
- return readFileSync11(path, "utf8");
11947
+ return readFileSync12(path, "utf8");
11192
11948
  } catch {
11193
11949
  return null;
11194
11950
  }
@@ -11233,7 +11989,7 @@ function vendorableAgentWarnings(agents, root) {
11233
11989
 
11234
11990
  // src/project/gitignore.ts
11235
11991
  import { createHash } from "crypto";
11236
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
11992
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
11237
11993
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join24 } from "path";
11238
11994
  var GITIGNORE_FILE = ".gitignore";
11239
11995
  var DIGEST_NAME = "tracking.digest";
@@ -11330,7 +12086,7 @@ function attempt(file, failure, act) {
11330
12086
  function readIfPresent2(file) {
11331
12087
  if (!existsSync6(file))
11332
12088
  return null;
11333
- return attempt(file, "cannot be read", () => readFileSync12(file, "utf8"));
12089
+ return attempt(file, "cannot be read", () => readFileSync13(file, "utf8"));
11334
12090
  }
11335
12091
  function writeTrackingGitignore(root, flags) {
11336
12092
  requireAbsolute(root);
@@ -11340,7 +12096,7 @@ function writeTrackingGitignore(root, flags) {
11340
12096
  if (next === text)
11341
12097
  return { file, change: "unchanged" };
11342
12098
  attempt(file, "cannot be written", () => {
11343
- writeFileSync3(file, next);
12099
+ writeFileSync4(file, next);
11344
12100
  });
11345
12101
  const change = text === null ? "created" : "updated";
11346
12102
  return { file, change };
@@ -11379,7 +12135,7 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
11379
12135
  print2(exposureNotice(root));
11380
12136
  attempt(digestFile, "cannot be written", () => {
11381
12137
  mkdirSync3(dirname5(digestFile), { recursive: true });
11382
- writeFileSync3(digestFile, recorded);
12138
+ writeFileSync4(digestFile, recorded);
11383
12139
  });
11384
12140
  return { digestFile, changed: true, printed: flags.trackingAll };
11385
12141
  }
@@ -11468,14 +12224,14 @@ async function promptForRoot(found, prompter, seams) {
11468
12224
  }
11469
12225
 
11470
12226
  // src/project/scaffold.ts
11471
- import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
12227
+ import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
11472
12228
  import { join as join25 } from "path";
11473
12229
 
11474
12230
  // src/commands/init-release.ts
11475
- import { writeFileSync as writeFileSync4 } from "fs";
12231
+ import { writeFileSync as writeFileSync5 } from "fs";
11476
12232
 
11477
12233
  // src/release/setting.ts
11478
- import { readFileSync as readFileSync13 } from "fs";
12234
+ import { readFileSync as readFileSync14 } from "fs";
11479
12235
  var RELEASE_ENABLED_SETTING = "release.enabled";
11480
12236
  function releaseEnabledIn(text, path) {
11481
12237
  return parseConfigText(text, path).values.releaseEnabled ?? null;
@@ -11484,7 +12240,7 @@ function readReleaseSetting(root) {
11484
12240
  const path = configFilePath(root);
11485
12241
  let text;
11486
12242
  try {
11487
- text = readFileSync13(path, "utf8");
12243
+ text = readFileSync14(path, "utf8");
11488
12244
  } catch (error) {
11489
12245
  return { enabled: null, text: "", problem: `${path} could not be read: ${messageOf(error)}` };
11490
12246
  }
@@ -11511,7 +12267,7 @@ function enabledLineIn(lines, open) {
11511
12267
  }
11512
12268
  return null;
11513
12269
  }
11514
- function appended(lines, block) {
12270
+ function appended2(lines, block) {
11515
12271
  const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
11516
12272
  return [...body, ...block, ""];
11517
12273
  }
@@ -11547,7 +12303,7 @@ function withReleaseEnabled(text, enabled) {
11547
12303
  }
11548
12304
  if (namesRelease(text))
11549
12305
  return null;
11550
- return appended(lines, ["release:", setting]).join(`
12306
+ return appended2(lines, ["release:", setting]).join(`
11551
12307
  `);
11552
12308
  }
11553
12309
 
@@ -11613,7 +12369,7 @@ function writeSetting(root, text, enabled) {
11613
12369
  return `the file this run would have written reads ${RELEASE_ENABLED_SETTING} as ${describeValue(read)}`;
11614
12370
  }
11615
12371
  try {
11616
- writeFileSync4(path, written, "utf8");
12372
+ writeFileSync5(path, written, "utf8");
11617
12373
  } catch (error) {
11618
12374
  return `it could not be written: ${messageOf(error)}`;
11619
12375
  }
@@ -11714,6 +12470,9 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
11714
12470
  "# board:",
11715
12471
  "# trustedAuthors: [] # logins trusted with board text besides the repo write-holders",
11716
12472
  `# relationships: ${CONFIG_DEFAULTS.boardRelationships} # labels | native, where epics and blockers are recorded`,
12473
+ "# project:",
12474
+ `# template: ${CONFIG_DEFAULTS.boardProjectTemplate} # the project URL rafa init --board --project copies`,
12475
+ "# number: # the project rafa mirrors the board to; unset until rafa init --board --project",
11717
12476
  "# roadmap:",
11718
12477
  "# issue: # the issue plan create --next reads; unset is the one titled Roadmap",
11719
12478
  "# claims:",
@@ -11834,7 +12593,7 @@ function ensureFile(path, text) {
11834
12593
  if (existsSync7(path))
11835
12594
  return { path, kind: "file", change: "unchanged" };
11836
12595
  attempt2(path, "cannot be written", () => {
11837
- writeFileSync5(path, text, { encoding: "utf8", flag: "wx" });
12596
+ writeFileSync6(path, text, { encoding: "utf8", flag: "wx" });
11838
12597
  });
11839
12598
  return { path, kind: "file", change: "created" };
11840
12599
  }
@@ -11905,6 +12664,15 @@ function readEpicGuardFlag(value) {
11905
12664
  return false;
11906
12665
  throw refusal9([`rafa init: --epic-guard takes no value, and read "${value}" as one;` + " install the workflow with --epic-guard, or leave it out with --no-epic-guard"]);
11907
12666
  }
12667
+ function readProjectFlag(value) {
12668
+ if (value === undefined)
12669
+ return null;
12670
+ if (value === true || value === "true")
12671
+ return true;
12672
+ if (value === false || value === "false")
12673
+ return false;
12674
+ throw refusal9([`rafa init: --project takes no value, and read "${value}" as one;` + " create the GitHub project with --project, or leave it out with --no-project"]);
12675
+ }
11908
12676
  function readReleaseFlag(value) {
11909
12677
  if (value === undefined)
11910
12678
  return null;
@@ -11969,7 +12737,7 @@ function resolvedConfig2(root, home, warn3) {
11969
12737
  function checkGitignore(root, flags) {
11970
12738
  const file = join26(root, GITIGNORE_FILE);
11971
12739
  try {
11972
- const text = existsSync8(file) ? readFileSync14(file, "utf8") : null;
12740
+ const text = existsSync8(file) ? readFileSync15(file, "utf8") : null;
11973
12741
  withTrackingBlock(text, flags, file);
11974
12742
  } catch (error) {
11975
12743
  const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
@@ -12066,7 +12834,8 @@ function renderInit(result2) {
12066
12834
  ...renderReleaseStep(result2.release),
12067
12835
  ...renderBoardStep(result2.board),
12068
12836
  ...renderEpicGuardStep(result2.epicGuard),
12069
- ...renderRelationsMoveStep(result2.relationsMove ?? null)
12837
+ ...renderRelationsMoveStep(result2.relationsMove ?? null),
12838
+ ...renderProjectStep(result2.project)
12070
12839
  ];
12071
12840
  return result2.changed ? [head, ...changed, ...steps] : [head, ...steps, "Nothing changed."];
12072
12841
  }
@@ -12075,6 +12844,7 @@ async function runInit(context, seams) {
12075
12844
  const wantsBoard = readBoardFlag(context.flags["board"]);
12076
12845
  const wantsGuard = readEpicGuardFlag(context.flags["epic-guard"]);
12077
12846
  const wantsRelease = readReleaseFlag(context.flags["release"]);
12847
+ const wantsProject = readProjectFlag(context.flags["project"]);
12078
12848
  const yes = readYesFlag(context.flags["yes"]);
12079
12849
  const start2 = seams.cwd();
12080
12850
  const home = seams.home();
@@ -12098,13 +12868,23 @@ async function runInit(context, seams) {
12098
12868
  isTerminal: seams.isTerminal,
12099
12869
  openPrompter: seams.openPrompter
12100
12870
  });
12871
+ const project = await runProjectStep({
12872
+ wanted: wantsProject,
12873
+ board,
12874
+ root: scopes.written.root,
12875
+ config: scopes.config,
12876
+ openGh: () => seams.gh(scopes.written.root),
12877
+ isTerminal: seams.isTerminal,
12878
+ openPrompter: seams.openPrompter
12879
+ });
12101
12880
  const result2 = {
12102
12881
  ...scopes.written,
12103
- changed: scopes.written.changed || release.changed || boardStepChanged(board) || epicGuardChanged(epicGuard) || relationsMoveChanged(relationsMove),
12882
+ changed: scopes.written.changed || release.changed || boardStepChanged(board) || epicGuardChanged(epicGuard) || relationsMoveChanged(relationsMove) || projectStepChanged(project),
12104
12883
  release,
12105
12884
  board,
12106
12885
  epicGuard,
12107
- ...relationsMove === null ? {} : { relationsMove }
12886
+ ...relationsMove === null ? {} : { relationsMove },
12887
+ project
12108
12888
  };
12109
12889
  if (context.outputMode === "json")
12110
12890
  context.output.result(result2);
@@ -12121,6 +12901,8 @@ async function runInit(context, seams) {
12121
12901
  context.output.warn(line);
12122
12902
  for (const line of relationsMove?.warnings ?? [])
12123
12903
  context.output.warn(line);
12904
+ for (const line of project.warnings)
12905
+ context.output.warn(line);
12124
12906
  for (const line of vendorableAgentWarnings(result2.vendorableAgents, result2.root))
12125
12907
  context.output.warn(line);
12126
12908
  }
@@ -12130,7 +12912,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
12130
12912
  subject: "init",
12131
12913
  action: "init",
12132
12914
  summary: "set up a rafa project: its root, .rafa/config.yaml, the tree and the .gitignore entry",
12133
- description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
12915
+ description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. Last, once the" + " board has run, one question asks whether to copy the GitHub project template to the repository's" + " owner, link it, add the issues and fill their fields; `--project` and `--no-project` answer that one." + " On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
12134
12916
  args: [],
12135
12917
  flags: [
12136
12918
  {
@@ -12153,6 +12935,11 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
12153
12935
  description: "Install the epic guard workflow without asking, once the board has run:" + " `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue and" + " comments why. `--no-epic-guard` leaves it out. Without either, a terminal is asked once and a" + " run with no terminal leaves it out. A file already at that path is left as it is.",
12154
12936
  type: "boolean"
12155
12937
  },
12938
+ {
12939
+ name: "project",
12940
+ description: "Create the GitHub project without asking, once the board has run: copy" + " `board.project.template` to the repository's owner, link the repository, add its issues, fill" + " their fields and save `board.project.number`. `--no-project` leaves it out. Without either, a" + " terminal is asked once and a run with no terminal leaves it out.",
12941
+ type: "boolean"
12942
+ },
12156
12943
  {
12157
12944
  name: "release",
12158
12945
  description: "Write `release.enabled: true` without asking, so every pull request bumps" + " `release.versionFile` and gains a `release.changelog` entry. `--no-release` writes `false`." + " Without either, a terminal is asked once, and `--yes` or a run with no terminal leaves the" + " setting unset at its default `auto`.",
@@ -12183,6 +12970,10 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
12183
12970
  {
12184
12971
  cmd: "rafa init --yes --board --epic-guard",
12185
12972
  note: "Sets up the GitHub board and installs the epic guard workflow, asking nothing."
12973
+ },
12974
+ {
12975
+ cmd: "rafa init --yes --board --project",
12976
+ note: "Sets up the GitHub board and its GitHub project, asking nothing. A second run writes nothing."
12186
12977
  }
12187
12978
  ],
12188
12979
  outputs: ["text", "json"],
@@ -12341,7 +13132,7 @@ var check_default = createInstinctCheckCommand();
12341
13132
 
12342
13133
  // src/commands/instinct/flag.ts
12343
13134
  var NAME = "rafa instinct flag";
12344
- var USAGE17 = "rafa instinct flag <id> <reason>";
13135
+ var USAGE18 = "rafa instinct flag <id> <reason>";
12345
13136
  function refusedFlagMessage(id2, kind, reason) {
12346
13137
  return [
12347
13138
  `\u274C ${NAME}: the \`${kind}\` learning adapter did not flag "${id2}": ${reason}`,
@@ -12351,7 +13142,7 @@ function refusedFlagMessage(id2, kind, reason) {
12351
13142
  }
12352
13143
  function blankReasonMessage2(id2) {
12353
13144
  return `\u274C ${NAME}: the reason to flag "${id2}" is blank; say why the lesson is wrong.
12354
- Usage: ${USAGE17}`;
13145
+ Usage: ${USAGE18}`;
12355
13146
  }
12356
13147
  function makeAdapter(registry, kind, context) {
12357
13148
  try {
@@ -12365,7 +13156,7 @@ function makeAdapter(registry, kind, context) {
12365
13156
  }
12366
13157
  }
12367
13158
  async function flagInstinct(context, seams) {
12368
- const [id2, reason] = expectTwoArguments(context.args, USAGE17);
13159
+ const [id2, reason] = expectTwoArguments(context.args, USAGE18);
12369
13160
  if (reason.trim() === "")
12370
13161
  throw new CommandExit(1, blankReasonMessage2(id2));
12371
13162
  const project = requireProject(context, NAME);
@@ -12434,7 +13225,7 @@ var flag_default = createInstinctFlagCommand();
12434
13225
 
12435
13226
  // src/commands/instinct/promote.ts
12436
13227
  var NAME2 = "rafa instinct promote";
12437
- var USAGE18 = "rafa instinct promote";
13228
+ var USAGE19 = "rafa instinct promote";
12438
13229
  var CONFIDENCE_DIGITS = 2;
12439
13230
  function refusedPullMessage(kind, reason, name = NAME2) {
12440
13231
  return `\u274C ${name}: the \`${kind}\` learning adapter answered no blessed set: ${reason}`;
@@ -12451,7 +13242,7 @@ function makeLearningAdapter(registry, kind, context, name = NAME2) {
12451
13242
  }
12452
13243
  }
12453
13244
  async function promotableLessons(context, seams) {
12454
- expectNoArgument(context.args, USAGE18);
13245
+ expectNoArgument(context.args, USAGE19);
12455
13246
  const project = requireProject(context, NAME2);
12456
13247
  const config = resolveProjectConfig(project, NAME2, (message) => {
12457
13248
  context.output.warn(message);
@@ -12521,7 +13312,7 @@ var promote_default2 = createInstinctPromoteCommand();
12521
13312
 
12522
13313
  // src/commands/instinct/list.ts
12523
13314
  var NAME3 = "rafa instinct list";
12524
- var USAGE19 = "rafa instinct list [--blessed | --conflicts]";
13315
+ var USAGE20 = "rafa instinct list [--blessed | --conflicts]";
12525
13316
  var CONFIDENCE_DIGITS2 = 2;
12526
13317
  var HUNDREDTHS = 100;
12527
13318
  function instinctRowLine(entry) {
@@ -12554,20 +13345,20 @@ function readViewFlag(name, value) {
12554
13345
  if (value === true || value === "true")
12555
13346
  return true;
12556
13347
  throw new CommandExit(1, `\u274C --${name} takes no value, and read "${value}" as one
12557
- Usage: ${USAGE19}`);
13348
+ Usage: ${USAGE20}`);
12558
13349
  }
12559
13350
  function readListView(flags) {
12560
13351
  const blessed = readViewFlag("blessed", flags["blessed"]);
12561
13352
  const conflicts = readViewFlag("conflicts", flags["conflicts"]);
12562
13353
  if (blessed && conflicts) {
12563
13354
  throw new CommandExit(1, `\u274C --blessed and --conflicts are two views; give one
12564
- Usage: ${USAGE19}`);
13355
+ Usage: ${USAGE20}`);
12565
13356
  }
12566
13357
  if (blessed)
12567
13358
  return "blessed";
12568
13359
  return conflicts ? "conflicts" : "all";
12569
13360
  }
12570
- function oneLine3(text) {
13361
+ function oneLine4(text) {
12571
13362
  return text.trim().replace(/\s+/gu, " ");
12572
13363
  }
12573
13364
  function byConfidence(left, right) {
@@ -12600,7 +13391,7 @@ function conflictLines(conflict) {
12600
13391
  const width = Math.max(...conflict.records.map((record) => record.id.length));
12601
13392
  return [
12602
13393
  ` ${conflict.scope} ${conflict.trigger}`,
12603
- ...conflict.records.map((record) => lessonLine(" ", record, width, oneLine3(record.action)))
13394
+ ...conflict.records.map((record) => lessonLine(" ", record, width, oneLine4(record.action)))
12604
13395
  ];
12605
13396
  }
12606
13397
  function renderConflicts(result2) {
@@ -12665,7 +13456,7 @@ function answer(context, result2, lines) {
12665
13456
  context.output.info(line);
12666
13457
  }
12667
13458
  async function runList4(context, seams) {
12668
- expectNoArgument(context.args, USAGE19);
13459
+ expectNoArgument(context.args, USAGE20);
12669
13460
  const view = readListView(context.flags);
12670
13461
  if (view === "blessed") {
12671
13462
  const result3 = await blessedLessons(context, seams);
@@ -12728,7 +13519,7 @@ function createInstinctListCommand(seams = {}) {
12728
13519
  var list_default6 = createInstinctListCommand();
12729
13520
 
12730
13521
  // src/commands/instinct/show.ts
12731
- var USAGE20 = "rafa instinct show <id>";
13522
+ var USAGE21 = "rafa instinct show <id>";
12732
13523
  var FIELD_ORDER = [
12733
13524
  "trigger",
12734
13525
  "kind",
@@ -12800,11 +13591,11 @@ function unreadableMessage(entry) {
12800
13591
  }
12801
13592
  function unknownIdMessage(id2, scopes) {
12802
13593
  const looked = scopes.map((listing) => ` ${listing.scope} ${listing.dir}${listing.exists ? "" : " (no such directory)"}`);
12803
- return [`\u274C No instinct is filed under "${id2}". Looked in:`, ...looked, `Usage: ${USAGE20}`].join(`
13594
+ return [`\u274C No instinct is filed under "${id2}". Looked in:`, ...looked, `Usage: ${USAGE21}`].join(`
12804
13595
  `);
12805
13596
  }
12806
13597
  function runShow2(context) {
12807
- const id2 = expectOneArgument(context.args, USAGE20);
13598
+ const id2 = expectOneArgument(context.args, USAGE21);
12808
13599
  const project = instinctProject(context, "rafa instinct show");
12809
13600
  const scopes = readScopes({ home: project.home, projectRoot: project.root });
12810
13601
  const found = findRecords(scopes, id2);
@@ -12863,7 +13654,7 @@ function createInstinctShowCommand() {
12863
13654
  var show_default3 = createInstinctShowCommand();
12864
13655
 
12865
13656
  // src/commands/issue/check.ts
12866
- import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
13657
+ import { readdirSync as readdirSync2, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
12867
13658
  import { join as join27, resolve as resolve9 } from "path";
12868
13659
  var CHECK_USAGE = "rafa issue check <n> [--stamp]";
12869
13660
  var STAMP_FLAG = "stamp";
@@ -12963,7 +13754,7 @@ async function readReferences(absolute, issue, verify, stamp) {
12963
13754
  return rows.map((row2) => checkedRef(row2, row2.stamp));
12964
13755
  }
12965
13756
  const once2 = memoiseVerifier(verify);
12966
- const before = await readRefsText({ copy: readFileSync15(absolute, "utf8"), issue, verify: once2 });
13757
+ const before = await readRefsText({ copy: readFileSync16(absolute, "utf8"), issue, verify: once2 });
12967
13758
  const after = await restampCopyRefs({ path: absolute, issue, verify: once2 });
12968
13759
  return before.rows.map((row2, index) => checkedRef(row2, after.rows[index]?.stamp ?? null));
12969
13760
  }
@@ -13038,10 +13829,10 @@ function createIssueCheckCommand(seams = {}) {
13038
13829
  var check_default2 = createIssueCheckCommand();
13039
13830
 
13040
13831
  // src/commands/issue/comment.ts
13041
- var USAGE21 = "rafa issue comment <id> --body=<text>";
13832
+ var USAGE22 = "rafa issue comment <id> --body=<text>";
13042
13833
  async function commentOnIssue(context, seams) {
13043
- const id2 = expectOneArgument(context.args, USAGE21);
13044
- const body = readRequiredFlag(context.flags, "body", USAGE21);
13834
+ const id2 = expectOneArgument(context.args, USAGE22);
13835
+ const body = readRequiredFlag(context.flags, "body", USAGE22);
13045
13836
  const { tracker, data } = await resolveIssueTracker(context, seams);
13046
13837
  const ref8 = issueRef(tracker, id2);
13047
13838
  await onTracker(tracker, `comment on issue ${id2}`, () => tracker.comment(ref8, body));
@@ -13166,24 +13957,24 @@ async function settleSpecLine(draft, tracker) {
13166
13957
  }
13167
13958
 
13168
13959
  // src/commands/issue/create.ts
13169
- var USAGE22 = "rafa issue create --title=<text> [--body=<text> | --body-file=<path>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
13960
+ var USAGE23 = "rafa issue create --title=<text> [--body=<text> | --body-file=<path>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
13170
13961
  var STDIN_PATH = "-";
13171
13962
  var DEFAULT_ISSUE_TYPE = "code";
13172
13963
  function readBodyFileFlag(flags) {
13173
- const path = readNonBlankFlag(flags, "body-file", USAGE22);
13964
+ const path = readNonBlankFlag(flags, "body-file", USAGE23);
13174
13965
  if (path === undefined || flags.body === undefined)
13175
13966
  return path;
13176
- throw lineRefusal("--body and --body-file cannot be used together: name the body one way", USAGE22);
13967
+ throw lineRefusal("--body and --body-file cannot be used together: name the body one way", USAGE23);
13177
13968
  }
13178
13969
  function readIssueDraft(flags) {
13179
13970
  readBodyFileFlag(flags);
13180
13971
  return {
13181
13972
  opt: 0,
13182
- title: readRequiredFlag(flags, "title", USAGE22),
13183
- body: readTextFlag(flags, "body", USAGE22) ?? "",
13184
- type: readChoiceFlag(flags, "type", ISSUE_TYPES, USAGE22) ?? DEFAULT_ISSUE_TYPE,
13185
- module: readNonBlankFlag(flags, "module", USAGE22) ?? TRIAGE_MODULE,
13186
- priority: readChoiceFlag(flags, "priority", ISSUE_PRIORITIES, USAGE22) ?? null,
13973
+ title: readRequiredFlag(flags, "title", USAGE23),
13974
+ body: readTextFlag(flags, "body", USAGE23) ?? "",
13975
+ type: readChoiceFlag(flags, "type", ISSUE_TYPES, USAGE23) ?? DEFAULT_ISSUE_TYPE,
13976
+ module: readNonBlankFlag(flags, "module", USAGE23) ?? TRIAGE_MODULE,
13977
+ priority: readChoiceFlag(flags, "priority", ISSUE_PRIORITIES, USAGE23) ?? null,
13187
13978
  project: null,
13188
13979
  blockedBy: []
13189
13980
  };
@@ -13194,7 +13985,7 @@ async function readBodyFile(path, seams) {
13194
13985
  return await read();
13195
13986
  } catch (error) {
13196
13987
  const where = path === STDIN_PATH ? "standard input" : `"${path}"`;
13197
- throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`, USAGE22);
13988
+ throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`, USAGE23);
13198
13989
  }
13199
13990
  }
13200
13991
  async function readIssueLine(flags, seams) {
@@ -13206,7 +13997,7 @@ function renderCreated(ref8) {
13206
13997
  return [`Created ${issueName(ref8)}.`, ...urlLines(ref8)];
13207
13998
  }
13208
13999
  async function createIssue(context, seams) {
13209
- expectNoArgument(context.args, USAGE22);
14000
+ expectNoArgument(context.args, USAGE23);
13210
14001
  const read = await readIssueLine(context.flags, seams);
13211
14002
  const blockedLine = readSpecLine(read);
13212
14003
  const { tracker, data } = await resolveIssueTracker(context, seams);
@@ -13220,13 +14011,34 @@ async function settledDraft(read, tracker, context) {
13220
14011
  context.output.warn(`issue create: ${settled.unchecked}`);
13221
14012
  return settled.draft;
13222
14013
  }
14014
+ function projectIssueNumber(ref8) {
14015
+ if (ref8.kind !== "github" || !/^[1-9]\d*$/u.test(ref8.externalId))
14016
+ return null;
14017
+ const number = Number(ref8.externalId);
14018
+ return Number.isSafeInteger(number) ? number : null;
14019
+ }
14020
+ async function addCreatedToProject(context, seams, ref8) {
14021
+ const issue = projectIssueNumber(ref8);
14022
+ if (issue === null)
14023
+ return;
14024
+ const project = issueProject(context);
14025
+ const config = issueSubjectConfig(project, () => {
14026
+ return;
14027
+ });
14028
+ const lines = await addAndRefreshIssue({
14029
+ config,
14030
+ openGh: () => seams.gh ?? createGhRunner({ cwd: project.root })
14031
+ }, issue);
14032
+ for (const line of lines)
14033
+ context.output.warn(line);
14034
+ }
13223
14035
  function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13224
14036
  const command = {
13225
14037
  name: "issue create",
13226
14038
  subject: "issue",
13227
14039
  action: "create",
13228
14040
  summary: "create an issue on the tracker from a title and a body",
13229
- 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.",
14041
+ 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. With `board.project.number` set, a GitHub issue is" + " added to the project and its fields filled, any failure there a warning that keeps the exit code. 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.",
13230
14042
  args: [],
13231
14043
  flags: [
13232
14044
  {
@@ -13282,12 +14094,12 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13282
14094
  const created = await createIssue(context, seams);
13283
14095
  if (created.ref.warning !== undefined)
13284
14096
  context.output.warn(`${issueName(created.ref)}: ${created.ref.warning}`);
13285
- if (context.outputMode === "json") {
14097
+ if (context.outputMode === "json")
13286
14098
  context.output.result(created);
13287
- return;
13288
- }
13289
- for (const line of renderCreated(created.ref))
13290
- context.output.info(line);
14099
+ else
14100
+ for (const line of renderCreated(created.ref))
14101
+ context.output.info(line);
14102
+ await addCreatedToProject(context, seams, created.ref);
13291
14103
  }
13292
14104
  };
13293
14105
  return Object.freeze(command);
@@ -13295,7 +14107,7 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13295
14107
  var create_default = createIssueCreateCommand();
13296
14108
 
13297
14109
  // src/commands/issue/edit-run.ts
13298
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
14110
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
13299
14111
  import { join as join29 } from "path";
13300
14112
 
13301
14113
  // src/board/issue-edit-body.ts
@@ -13709,8 +14521,8 @@ function saveConflictBodies(root, issue, now, before, after) {
13709
14521
  attempt3 += 1;
13710
14522
  paths = conflictPaths(issue, stamp, attempt3);
13711
14523
  }
13712
- writeFileSync6(join29(root, paths.before), before, { flag: "wx" });
13713
- writeFileSync6(join29(root, paths.after), after, { flag: "wx" });
14524
+ writeFileSync7(join29(root, paths.before), before, { flag: "wx" });
14525
+ writeFileSync7(join29(root, paths.after), after, { flag: "wx" });
13714
14526
  return paths;
13715
14527
  }
13716
14528
  function heldOutcome(request, state) {
@@ -13903,16 +14715,43 @@ function createIssueEditCommand(seams = DEFAULT_EDIT_SEAMS) {
13903
14715
  }
13904
14716
  var edit_default = createIssueEditCommand();
13905
14717
 
14718
+ // src/commands/issue/move-project.ts
14719
+ function issueNumberOf(externalId) {
14720
+ const number = Number(externalId);
14721
+ return Number.isSafeInteger(number) && number >= 1 ? number : null;
14722
+ }
14723
+ async function refreshMovedIssue(tracker, externalId, options) {
14724
+ const issue = issueNumberOf(externalId);
14725
+ if (tracker !== "github" || issue === null || options.config.boardProjectNumber === null)
14726
+ return [];
14727
+ const refresh = options.refresh ?? refreshIssueItems;
14728
+ try {
14729
+ return (await refresh({ config: options.config, gh: options.openGh() }, [issue])).warnings;
14730
+ } catch (error) {
14731
+ return [refreshFailedWarning(issue, error)];
14732
+ }
14733
+ }
14734
+
13906
14735
  // src/commands/issue/move.ts
13907
- var USAGE23 = "rafa issue move <id> <state>";
14736
+ var USAGE24 = "rafa issue move <id> <state>";
13908
14737
  async function moveIssue(context, seams) {
13909
- const [id2, word] = expectTwoArguments(context.args, USAGE23);
13910
- const state = readChoice(word, "The state", ISSUE_STATES, USAGE23);
14738
+ const [id2, word] = expectTwoArguments(context.args, USAGE24);
14739
+ const state = readChoice(word, "The state", ISSUE_STATES, USAGE24);
13911
14740
  const { tracker, data } = await resolveIssueTracker(context, seams);
13912
14741
  const ref8 = issueRef(tracker, id2);
13913
14742
  const moved = await onTracker(tracker, `move issue ${id2} to ${state}`, () => tracker.transition(ref8, state));
13914
14743
  return { tracker: data, ref: ref8, state, warning: moved.warning ?? null };
13915
14744
  }
14745
+ function moveProjectOptions(context, seams) {
14746
+ const project = issueProject(context);
14747
+ const { gh } = seams;
14748
+ return {
14749
+ config: issueSubjectConfig(project, () => {
14750
+ return;
14751
+ }),
14752
+ openGh: () => gh ?? createGhRunner({ cwd: project.root })
14753
+ };
14754
+ }
13916
14755
  function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
13917
14756
  const command = {
13918
14757
  name: "issue move",
@@ -13948,9 +14787,12 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
13948
14787
  outputs: ["text", "json"],
13949
14788
  run: async (context) => {
13950
14789
  const moved = await moveIssue(context, seams);
14790
+ const refreshed = await refreshMovedIssue(moved.tracker.kind, moved.ref.externalId, moveProjectOptions(context, seams));
13951
14791
  const warn3 = () => {
13952
14792
  if (moved.warning !== null)
13953
14793
  context.output.warn(`${issueName(moved.ref)}: ${moved.warning}`);
14794
+ for (const line of refreshed)
14795
+ context.output.warn(line);
13954
14796
  };
13955
14797
  if (context.outputMode === "json") {
13956
14798
  warn3();
@@ -13966,7 +14808,7 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
13966
14808
  var move_default3 = createIssueMoveCommand();
13967
14809
 
13968
14810
  // src/commands/issue/show.ts
13969
- var USAGE24 = "rafa issue show <id>";
14811
+ var USAGE25 = "rafa issue show <id>";
13970
14812
  var NO_PRIORITY = "needs-triage";
13971
14813
  function renderIssue(kind, issue) {
13972
14814
  const project = issue.project === null ? [] : [`Project: ${issue.project}`];
@@ -13988,7 +14830,7 @@ function renderIssue(kind, issue) {
13988
14830
  ];
13989
14831
  }
13990
14832
  async function showIssue(context, seams) {
13991
- const id2 = expectOneArgument(context.args, USAGE24);
14833
+ const id2 = expectOneArgument(context.args, USAGE25);
13992
14834
  const { tracker, data } = await resolveIssueTracker(context, seams);
13993
14835
  const issue = await onTracker(tracker, `read issue ${id2}`, () => tracker.get(issueRef(tracker, id2)));
13994
14836
  return { tracker: data, issue };
@@ -14036,7 +14878,7 @@ var show_default4 = createIssueShowCommand();
14036
14878
 
14037
14879
  // src/commands/loop/list.ts
14038
14880
  import { isAbsolute as isAbsolute5, relative as relative6 } from "path";
14039
- var USAGE25 = "rafa loop list";
14881
+ var USAGE26 = "rafa loop list";
14040
14882
  function worktreeLabel2(root, worktree) {
14041
14883
  const under = relative6(root, worktree);
14042
14884
  return under === "" || under.startsWith("..") || isAbsolute5(under) ? worktree : under;
@@ -14056,7 +14898,7 @@ function renderSessionList(root, list) {
14056
14898
  ];
14057
14899
  }
14058
14900
  async function runList5(context, seams) {
14059
- expectNoArgument(context.args, USAGE25);
14901
+ expectNoArgument(context.args, USAGE26);
14060
14902
  const root = projectRoot(context);
14061
14903
  const sessions = readRecords(root, seams).filter(isLive).map((session) => {
14062
14904
  const checklist = readSessionChecklist(root, session);
@@ -14102,7 +14944,7 @@ function createLoopListCommand(seams = {}) {
14102
14944
  var list_default7 = createLoopListCommand();
14103
14945
 
14104
14946
  // src/commands/loop/pause.ts
14105
- var USAGE26 = "rafa loop pause [-s|--session-id=<id>]";
14947
+ var USAGE27 = "rafa loop pause [-s|--session-id=<id>]";
14106
14948
  function renderPause(result2) {
14107
14949
  const { session } = result2;
14108
14950
  if (!result2.changed)
@@ -14111,8 +14953,8 @@ function renderPause(result2) {
14111
14953
  return [when, " Nothing is marked. `rafa loop resume` goes on, and `rafa loop stop` ends the run."];
14112
14954
  }
14113
14955
  async function runPause(context, seams) {
14114
- expectNoArgument(context.args, USAGE26);
14115
- const { root, record } = pickSession(context, USAGE26, "live", seams);
14956
+ expectNoArgument(context.args, USAGE27);
14957
+ const { root, record } = pickSession(context, USAGE27, "live", seams);
14116
14958
  if (!isLive(record)) {
14117
14959
  throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to pause.`);
14118
14960
  }
@@ -14152,7 +14994,7 @@ function createLoopPauseCommand(seams = {}) {
14152
14994
  var pause_default = createLoopPauseCommand();
14153
14995
 
14154
14996
  // src/commands/loop/resume.ts
14155
- var USAGE27 = "rafa loop resume [-s|--session-id=<id>]";
14997
+ var USAGE28 = "rafa loop resume [-s|--session-id=<id>]";
14156
14998
  function renderResume(result2) {
14157
14999
  const { session } = result2;
14158
15000
  if (!result2.changed)
@@ -14160,8 +15002,8 @@ function renderResume(result2) {
14160
15002
  return [session.task === null ? `Resumed session ${session.sessionId}: it goes on with its next task.` : `Resumed session ${session.sessionId}: it goes on past its running task, line ${session.task.line}, ${session.task.text}`];
14161
15003
  }
14162
15004
  async function runResume(context, seams) {
14163
- expectNoArgument(context.args, USAGE27);
14164
- const { root, record } = pickSession(context, USAGE27, "live", seams);
15005
+ expectNoArgument(context.args, USAGE28);
15006
+ const { root, record } = pickSession(context, USAGE28, "live", seams);
14165
15007
  if (!isLive(record)) {
14166
15008
  throw refusal(`Session ${record.sessionId} reads ${record.state}: its run has ended, so there is nothing to resume.`, `\`rafa loop start --plan=${record.plan}\` runs its plan again as a new session, its blocked tasks first.`);
14167
15009
  }
@@ -14311,7 +15153,7 @@ var loopStart = Object.freeze({
14311
15153
  var start_default = endingWith(loopStart);
14312
15154
 
14313
15155
  // src/commands/loop/status.ts
14314
- var USAGE28 = "rafa loop status [-s|--session-id=<id>]";
15156
+ var USAGE29 = "rafa loop status [-s|--session-id=<id>]";
14315
15157
  var BLOCKED_TASK_LINE = /^- \[BLOCKED\] (.+)/;
14316
15158
  var NO_BLOCKER = "the line trails no blocker comment";
14317
15159
  function blockerAt(checklist, lineNum) {
@@ -14347,8 +15189,8 @@ function renderStatus(status) {
14347
15189
  ];
14348
15190
  }
14349
15191
  async function runStatus(context, seams) {
14350
- expectNoArgument(context.args, USAGE28);
14351
- const { root, record } = pickSession(context, USAGE28, "live-or-newest", seams);
15192
+ expectNoArgument(context.args, USAGE29);
15193
+ const { root, record } = pickSession(context, USAGE29, "live-or-newest", seams);
14352
15194
  const checklist = readSessionChecklist(root, record);
14353
15195
  const tasks = checklist === null ? null : countTasks(checklist.tasks);
14354
15196
  let eta = null;
@@ -14402,7 +15244,7 @@ function createLoopStatusCommand(seams = {}) {
14402
15244
  var status_default2 = createLoopStatusCommand();
14403
15245
 
14404
15246
  // src/commands/loop/stop.ts
14405
- var USAGE29 = "rafa loop stop [-s|--session-id=<id>]";
15247
+ var USAGE30 = "rafa loop stop [-s|--session-id=<id>]";
14406
15248
  function signalRun(record, interrupt) {
14407
15249
  try {
14408
15250
  interrupt(record.pid);
@@ -14455,8 +15297,8 @@ function renderStop(result2) {
14455
15297
  ];
14456
15298
  }
14457
15299
  async function runStop(context, seams) {
14458
- expectNoArgument(context.args, USAGE29);
14459
- const { root, record } = pickSession(context, USAGE29, "live", seams);
15300
+ expectNoArgument(context.args, USAGE30);
15301
+ const { root, record } = pickSession(context, USAGE30, "live", seams);
14460
15302
  if (!isLive(record)) {
14461
15303
  throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to stop.`);
14462
15304
  }
@@ -14647,14 +15489,14 @@ class WaitUntilError extends Error {
14647
15489
  this.entry = entry;
14648
15490
  }
14649
15491
  }
14650
- function rowOf2(reason) {
15492
+ function rowOf3(reason) {
14651
15493
  const row2 = WAIT_REASONS.find((candidate) => candidate.reason === reason);
14652
15494
  if (row2 === undefined)
14653
15495
  throw new Error(`wait reasons: no row for ${reason}`);
14654
15496
  return row2;
14655
15497
  }
14656
15498
  function waitExitCode(reason) {
14657
- return rowOf2(reason).exit;
15499
+ return rowOf3(reason).exit;
14658
15500
  }
14659
15501
  function isPlainReason(entry) {
14660
15502
  return WAIT_REASONS.some((row2) => row2.reason === entry && row2.reason !== "quiet");
@@ -14711,7 +15553,7 @@ function matchQuiet(awakeMinutes, until) {
14711
15553
  }
14712
15554
 
14713
15555
  // src/commands/loop/wait.ts
14714
- var USAGE30 = "rafa loop wait [-s|--session-id=<id>] [--until=<reasons>] [--timeout=<minutes>]";
15556
+ var USAGE31 = "rafa loop wait [-s|--session-id=<id>] [--until=<reasons>] [--timeout=<minutes>]";
14715
15557
  var WAIT_POLL_MS = 5000;
14716
15558
  var UNTIL_FLAG = "until";
14717
15559
  var TIMEOUT_FLAG = "timeout";
@@ -14732,12 +15574,12 @@ function readUntil(context) {
14732
15574
  if (value === undefined)
14733
15575
  return DEFAULT_WAIT_UNTIL;
14734
15576
  if (typeof value !== "string")
14735
- throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`, USAGE30);
15577
+ throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`, USAGE31);
14736
15578
  try {
14737
15579
  return parseWaitUntil(value);
14738
15580
  } catch (error) {
14739
15581
  if (error instanceof WaitUntilError)
14740
- throw lineRefusal2(error.message, USAGE30);
15582
+ throw lineRefusal2(error.message, USAGE31);
14741
15583
  throw error;
14742
15584
  }
14743
15585
  }
@@ -14748,7 +15590,7 @@ function readTimeout(context) {
14748
15590
  const minutes = typeof value === "string" ? Number(value) : Number.NaN;
14749
15591
  if (typeof value !== "string" || !/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(minutes)) {
14750
15592
  const spelled = typeof value === "string" ? JSON.stringify(value) : `--${TIMEOUT_FLAG} with no value`;
14751
- throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`, USAGE30);
15593
+ throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`, USAGE31);
14752
15594
  }
14753
15595
  return minutes;
14754
15596
  }
@@ -14857,7 +15699,7 @@ function resultOf5(found, following, seams) {
14857
15699
  }
14858
15700
  function pickOrNoSession(context, seams) {
14859
15701
  try {
14860
- return pickSession(context, USAGE30, "live-or-newest", {
15702
+ return pickSession(context, USAGE31, "live-or-newest", {
14861
15703
  ...resolveLoopSeams(),
14862
15704
  readBranch: seams.readBranch,
14863
15705
  isAlive: seams.isAlive
@@ -14869,7 +15711,7 @@ function pickOrNoSession(context, seams) {
14869
15711
  }
14870
15712
  }
14871
15713
  async function runLoopWait(context, seams = {}) {
14872
- expectNoArgument(context.args, USAGE30);
15714
+ expectNoArgument(context.args, USAGE31);
14873
15715
  const until = readUntil(context);
14874
15716
  const timeout = readTimeout(context);
14875
15717
  const resolved = resolveWaitSeams(seams);
@@ -14956,7 +15798,7 @@ function createLoopWaitCommand(seams = {}) {
14956
15798
  var wait_default = createLoopWaitCommand();
14957
15799
 
14958
15800
  // src/commands/module/exec.ts
14959
- var USAGE31 = "rafa module exec <module> <action> [args] [flags]";
15801
+ var USAGE32 = "rafa module exec <module> <action> [args] [flags]";
14960
15802
  var execCommand = {
14961
15803
  name: "module exec",
14962
15804
  subject: "module",
@@ -14985,13 +15827,13 @@ var execCommand = {
14985
15827
  const mounts = context.registry.mounts();
14986
15828
  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(", ");
14987
15829
  throw new CommandExit(1, `\u274C Expected a module and one of its actions; mounted: ${mounted}
14988
- Usage: ${USAGE31}`);
15830
+ Usage: ${USAGE32}`);
14989
15831
  }
14990
15832
  };
14991
15833
  var exec_default = Object.freeze(execCommand);
14992
15834
 
14993
15835
  // src/commands/module/list.ts
14994
- var USAGE32 = "rafa module list";
15836
+ var USAGE33 = "rafa module list";
14995
15837
  function sourceText(source) {
14996
15838
  const ref8 = source.ref === null ? "" : `@${source.ref}`;
14997
15839
  return `${source.kind} ${source.location}${ref8}`;
@@ -15012,10 +15854,10 @@ function renderModuleList(list) {
15012
15854
  return ["Modules:", ...list.modules.flatMap(moduleLines)];
15013
15855
  }
15014
15856
  async function runList6(context, seams) {
15015
- expectNoArgument(context.args, USAGE32);
15857
+ expectNoArgument(context.args, USAGE33);
15016
15858
  const { project } = context;
15017
15859
  if (project === null)
15018
- throw new CommandExit(1, `\u274C ${USAGE32} runs inside a project`);
15860
+ throw new CommandExit(1, `\u274C ${USAGE33} runs inside a project`);
15019
15861
  let loaded;
15020
15862
  try {
15021
15863
  const resolved = loadConfig({ root: project.root, home: project.home });
@@ -15139,7 +15981,7 @@ function allowedUnasked(action, ceiling) {
15139
15981
 
15140
15982
  // src/next/lines.ts
15141
15983
  var NEXT_USAGE = "rafa next [--dry-run] [--roadmap [--claim-ahead]] [--yes[=<action ids>]]";
15142
- var DRY_RUN_FLAG5 = "dry-run";
15984
+ var DRY_RUN_FLAG6 = "dry-run";
15143
15985
  var ROADMAP_FLAG = "roadmap";
15144
15986
  var CLAIM_AHEAD_FLAG = "claim-ahead";
15145
15987
  var STATE_MARK = "\uD83D\uDCCD";
@@ -15164,7 +16006,7 @@ function dryRunLine(dryRun, roadmap) {
15164
16006
  if (dryRun === "no-terminal") {
15165
16007
  return `${STOP_MARK} There is no terminal to answer on, so nothing ran; run rafa next where you can answer,` + ` or type --${YES_FLAG}=${shownIds(BARE_YES_ACTIONS, roadmap).join(",")} to allow those steps unasked.`;
15166
16008
  }
15167
- return `${STOP_MARK} --${DRY_RUN_FLAG5}: nothing ran.`;
16009
+ return `${STOP_MARK} --${DRY_RUN_FLAG6}: nothing ran.`;
15168
16010
  }
15169
16011
  function stopLine(stop, state, ceiling, dryRun, roadmap = false) {
15170
16012
  if (stop === "dry-run")
@@ -15194,7 +16036,7 @@ function lineRefusal4(problem2) {
15194
16036
  Usage: ${NEXT_USAGE}`);
15195
16037
  }
15196
16038
  function readDryRun(flags) {
15197
- return readBareFlag(flags, DRY_RUN_FLAG5);
16039
+ return readBareFlag(flags, DRY_RUN_FLAG6);
15198
16040
  }
15199
16041
  function readRoadmap(flags) {
15200
16042
  return readBareFlag(flags, ROADMAP_FLAG);
@@ -15243,19 +16085,19 @@ function trackingOf(plan2) {
15243
16085
  function fastForwardBase(git, plan2) {
15244
16086
  const { base } = plan2;
15245
16087
  const tracking = trackingOf(plan2);
15246
- const counted2 = git(["rev-list", "--left-right", "--count", `${base}...${tracking}`]);
15247
- if (!counted2.ok) {
16088
+ const counted3 = git(["rev-list", "--left-right", "--count", `${base}...${tracking}`]);
16089
+ if (!counted3.ok) {
15248
16090
  return refuse6([
15249
16091
  `\u274C Could not read how ${base} stands against ${tracking}.`,
15250
- ...quotedLines(gitSaid(counted2)),
16092
+ ...quotedLines(gitSaid(counted3)),
15251
16093
  stillAt(base)
15252
16094
  ]);
15253
16095
  }
15254
- const standing = parseBaseStanding(counted2.stdout);
16096
+ const standing = parseBaseStanding(counted3.stdout);
15255
16097
  if (standing === null) {
15256
16098
  return refuse6([
15257
16099
  `\u274C Could not read how ${base} stands against ${tracking}.`,
15258
- `${INDENT2}git answered ${JSON.stringify(counted2.stdout)}, which is no pair of counts.`,
16100
+ `${INDENT2}git answered ${JSON.stringify(counted3.stdout)}, which is no pair of counts.`,
15259
16101
  stillAt(base)
15260
16102
  ]);
15261
16103
  }
@@ -15443,7 +16285,7 @@ async function runNext(context, seams) {
15443
16285
  var YES_ID_LIST = YES_ACTIONS.join(", ");
15444
16286
  var NEXT_FLAGS = Object.freeze([
15445
16287
  {
15446
- name: DRY_RUN_FLAG5,
16288
+ name: DRY_RUN_FLAG6,
15447
16289
  description: "Print where the project stands and what to do about it, and stop without asking or running it." + " A run with no terminal to answer on and no --yes does this whether or not the flag is typed.",
15448
16290
  type: "boolean"
15449
16291
  },
@@ -15616,10 +16458,10 @@ var wrapped2 = wrapPhaseZeroCommand({
15616
16458
  var create_default2 = endingWith(wrapped2);
15617
16459
 
15618
16460
  // src/commands/plan/risk.ts
15619
- import { readFileSync as readFileSync16 } from "fs";
16461
+ import { readFileSync as readFileSync17 } from "fs";
15620
16462
  import { resolve as resolve11 } from "path";
15621
16463
  var COMMAND6 = "rafa plan risk";
15622
- var USAGE33 = "rafa plan risk [<plan>] [--strict]";
16464
+ var USAGE34 = "rafa plan risk [<plan>] [--strict]";
15623
16465
  var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
15624
16466
  function spawningRunners(root) {
15625
16467
  return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
@@ -15632,7 +16474,7 @@ function riskPlanPath(root, planDir, typed) {
15632
16474
  throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
15633
16475
  const lookedFor = [...new Set([resolve11(root, planDir, DEFAULT_PLAN_FILE), resolve11(root, DEFAULT_PLAN_FILE)])];
15634
16476
  throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
15635
- Usage: ${USAGE33}`);
16477
+ Usage: ${USAGE34}`);
15636
16478
  }
15637
16479
  function riskLine(finding) {
15638
16480
  const line = finding.line === undefined ? "" : `:${String(finding.line)}`;
@@ -15644,7 +16486,7 @@ function strictRefusal(report3) {
15644
16486
  }
15645
16487
  async function readRisk(path, project, config, context, seams) {
15646
16488
  const makeRunners = seams.runners ?? spawningRunners;
15647
- return assessPlanRisk({ path, text: readFileSync16(path, "utf8") }, {
16489
+ return assessPlanRisk({ path, text: readFileSync17(path, "utf8") }, {
15648
16490
  repoRoot: project.root,
15649
16491
  home: project.home,
15650
16492
  settingSources: config.settingSources,
@@ -15696,7 +16538,7 @@ function createPlanRiskCommand(seams = {}) {
15696
16538
  outputs: ["text", "json"],
15697
16539
  run: async (context) => {
15698
16540
  const strict = readSwitch("strict", context.flags["strict"], STRICT_HINT);
15699
- const typed = expectAtMostOneArgument(context.args, USAGE33);
16541
+ const typed = expectAtMostOneArgument(context.args, USAGE34);
15700
16542
  const project = requireProject(context, COMMAND6);
15701
16543
  const config = resolveProjectConfig(project, COMMAND6, (message) => {
15702
16544
  context.output.warn(message);
@@ -15727,9 +16569,9 @@ function createPlanRiskCommand(seams = {}) {
15727
16569
  var risk_default = createPlanRiskCommand();
15728
16570
 
15729
16571
  // src/commands/plan/show.ts
15730
- import { readFileSync as readFileSync17 } from "fs";
16572
+ import { readFileSync as readFileSync18 } from "fs";
15731
16573
  import { join as join30 } from "path";
15732
- var USAGE34 = "rafa plan show <stub> [--tracker]";
16574
+ var USAGE35 = "rafa plan show <stub> [--tracker]";
15733
16575
  var COMMAND7 = "rafa plan show";
15734
16576
  function readTrackerFlag(value) {
15735
16577
  if (value === undefined || value === false || value === "false")
@@ -15749,7 +16591,7 @@ function showPlan(plans, stub, tracker) {
15749
16591
  const file = join30(plans.path, name);
15750
16592
  if (!isFile(file))
15751
16593
  throw new CommandExit(1, missingText(stub, tracker, join30(plans.label, name)));
15752
- const model = parsePlan(readFileSync17(file, "utf8"));
16594
+ const model = parsePlan(readFileSync18(file, "utf8"));
15753
16595
  const { header } = model;
15754
16596
  return {
15755
16597
  stub,
@@ -15825,7 +16667,7 @@ var planShowCommand = {
15825
16667
  outputs: ["text", "json"],
15826
16668
  run: async (context) => {
15827
16669
  const tracker = readTrackerFlag(context.flags.tracker);
15828
- const stub = expectOneArgument(context.args, USAGE34);
16670
+ const stub = expectOneArgument(context.args, USAGE35);
15829
16671
  const plans = resolvePlansDir(requireProject(context, COMMAND7), COMMAND7, (message) => {
15830
16672
  context.output.warn(message);
15831
16673
  });
@@ -15932,7 +16774,13 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
15932
16774
  planDir: config.planDir,
15933
16775
  worktreeDir: config.loopWorktreeDir,
15934
16776
  readBranch: () => readBranch2(project.root),
15935
- versionGuard: mergeGuardSettings(config)
16777
+ versionGuard: mergeGuardSettings(config),
16778
+ projectRefresh: {
16779
+ boardProjectNumber: config.boardProjectNumber,
16780
+ boardRelationships: config.boardRelationships,
16781
+ roadmapIssue: config.roadmapIssue,
16782
+ releaseFragments: config.releaseFragments
16783
+ }
15936
16784
  };
15937
16785
  }
15938
16786
  async function onProvider(doing, call) {
@@ -15968,7 +16816,7 @@ Run rafa pr list to see the open pull requests.`, usage);
15968
16816
  }
15969
16817
 
15970
16818
  // src/commands/pr/current.ts
15971
- var USAGE35 = PR_USAGE.current;
16819
+ var USAGE36 = PR_USAGE.current;
15972
16820
  var SEPARATOR2 = " \u2014 ";
15973
16821
  function trimmedOrNull(value) {
15974
16822
  const trimmed = value.trim();
@@ -15999,9 +16847,9 @@ async function probeChecks(pulls, number) {
15999
16847
  }
16000
16848
  }
16001
16849
  async function readCurrent(context, seams) {
16002
- expectNoArguments(context.args, USAGE35);
16850
+ expectNoArguments(context.args, USAGE36);
16003
16851
  const pr = openPrContext(context, seams);
16004
- const pick = await pickPullRequest(pr, null, USAGE35);
16852
+ const pick = await pickPullRequest(pr, null, USAGE36);
16005
16853
  const summary = pick.summary;
16006
16854
  const probe = await probeChecks(pr.pulls, pick.number);
16007
16855
  const reading = {
@@ -16166,7 +17014,7 @@ function forecastLine(number, forecast) {
16166
17014
  }
16167
17015
 
16168
17016
  // src/commands/pr/list.ts
16169
- var USAGE36 = PR_USAGE.list;
17017
+ var USAGE37 = PR_USAGE.list;
16170
17018
  var UNREADABLE = "?";
16171
17019
  var PROBE_LIMIT = 6;
16172
17020
  var TITLE_WIDTH = 60;
@@ -16254,7 +17102,7 @@ async function probe(call) {
16254
17102
  return { value: null, problem: messageOf(error) };
16255
17103
  }
16256
17104
  }
16257
- async function rowOf3(pulls, pull2, readBaseOf) {
17105
+ async function rowOf4(pulls, pull2, readBaseOf) {
16258
17106
  const [checks, detail] = await Promise.all([
16259
17107
  probe(() => pulls.checks(pull2.number)),
16260
17108
  probe(() => pulls.get(pull2.number))
@@ -16270,12 +17118,12 @@ async function rowOf3(pulls, pull2, readBaseOf) {
16270
17118
  };
16271
17119
  }
16272
17120
  async function readList(context, seams, now) {
16273
- expectNoArguments(context.args, USAGE36);
17121
+ expectNoArguments(context.args, USAGE37);
16274
17122
  const pr = openPrContext(context, seams);
16275
17123
  const open = await onProvider("list the open pull requests", () => pr.pulls.list());
16276
17124
  const root = pr.project.root;
16277
17125
  const readBaseOf = resolveReleaseEnabled(pr.versionGuard, root).enabled ? createBaseReader((seams.git ?? createGitRunner)(root), pr.versionGuard) : null;
16278
- const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf3(pr.pulls, pull2, readBaseOf));
17126
+ const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf4(pr.pulls, pull2, readBaseOf));
16279
17127
  return { rows, text: renderList(rows, now) };
16280
17128
  }
16281
17129
  function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
@@ -16597,6 +17445,55 @@ function plural2(count, noun) {
16597
17445
  return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
16598
17446
  }
16599
17447
 
17448
+ // src/commands/pr/merge-project.ts
17449
+ function named2(issues) {
17450
+ return issues.map((issue) => `#${String(issue)}`).join(", ");
17451
+ }
17452
+ function blockingProblemLine(closed, problem2) {
17453
+ return `The project was not updated for the issues ${named2(closed)} were blocking: ${problem2}.` + ` Run \`${BOARD_SYNC_FIX}\` to catch up.`;
17454
+ }
17455
+ function refreshProblemLine(issues, problem2) {
17456
+ return `The project was not updated for ${named2(issues)}: ${problem2}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
17457
+ }
17458
+ function waitsOnOneOf(blockers, closed) {
17459
+ return blockers.kind === "blocked" && blockers.blockers.some((blocker) => blocker.repository === null && closed.has(blocker.number));
17460
+ }
17461
+ async function readBlocking(config, gh, closed) {
17462
+ const repository = config.boardRelationships === "native" ? await readBoardRepository(gh) : "";
17463
+ const relations = selectBoardRelations(config, { gh, repository });
17464
+ const listing = await createGhBoardListing({ gh, mode: relations.mode })();
17465
+ const reading = relations.read(listing);
17466
+ return listing.filter((row2) => waitsOnOneOf(reading.blockersOf(row2), closed)).map((row2) => row2.number);
17467
+ }
17468
+ function consideredBlocking(unblocked, closed) {
17469
+ return (unblocked?.issues ?? []).filter((outcome) => outcome.blockers.some((blocker) => closed.has(blocker))).map((outcome) => outcome.issue);
17470
+ }
17471
+ async function refreshProjectAfterMerge(options) {
17472
+ const { config, warn: warn3, refresh = refreshIssueItems } = options;
17473
+ const closed = [...new Set(closedIssuesIn(options.body))];
17474
+ if (config.boardProjectNumber === null || closed.length === 0)
17475
+ return null;
17476
+ const gh = options.openGh();
17477
+ const wanted = new Set(closed);
17478
+ const warnings = [];
17479
+ let listed2 = [];
17480
+ try {
17481
+ listed2 = await readBlocking(config, gh, wanted);
17482
+ } catch (error) {
17483
+ warnings.push(blockingProblemLine(closed, messageOf(error)));
17484
+ }
17485
+ const blocking = [...new Set([...listed2, ...consideredBlocking(options.unblocked, wanted)])].filter((issue) => !wanted.has(issue)).sort((a, b) => a - b);
17486
+ const issues = [...closed, ...blocking];
17487
+ try {
17488
+ warnings.push(...(await refresh({ config, gh }, issues)).warnings);
17489
+ } catch (error) {
17490
+ warnings.push(refreshProblemLine(issues, messageOf(error)));
17491
+ }
17492
+ for (const line of warnings)
17493
+ warn3(line);
17494
+ return { closed, blocking, warnings };
17495
+ }
17496
+
16600
17497
  // src/pr/unchecked.ts
16601
17498
  var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
16602
17499
  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";
@@ -16704,16 +17601,16 @@ function readBranches(filter, base) {
16704
17601
  const patterns = patternListOf(filter);
16705
17602
  if (patterns === null)
16706
17603
  return UNKNOWN;
16707
- let named2 = false;
17604
+ let named3 = false;
16708
17605
  for (const pattern of patterns) {
16709
17606
  const negated = pattern.startsWith("!");
16710
17607
  const matches = branchPatternMatches(negated ? pattern.slice(1) : pattern, base);
16711
17608
  if (matches === null)
16712
17609
  return UNKNOWN;
16713
17610
  if (matches)
16714
- named2 = !negated;
17611
+ named3 = !negated;
16715
17612
  }
16716
- return named2;
17613
+ return named3;
16717
17614
  }
16718
17615
  function readBranchesIgnore(filter, base) {
16719
17616
  const patterns = patternListOf(filter);
@@ -16888,9 +17785,21 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
16888
17785
  return null;
16889
17786
  }
16890
17787
  }
17788
+ function uncheckedReport(unchecked, commentUrl) {
17789
+ return unchecked === null ? null : { workflowCount: unchecked.workflowCount, case: unchecked.reading.case, commentUrl };
17790
+ }
17791
+ async function commentIfUnchecked(pulls, number, unchecked, report4) {
17792
+ if (unchecked === null)
17793
+ return null;
17794
+ const posted = await postUncheckedComment(pulls, number, unchecked, report4.warn);
17795
+ if (posted === null)
17796
+ return null;
17797
+ report4.info(`Commented on #${number} that it was merged with no checks: ${posted.url}`);
17798
+ return posted.url;
17799
+ }
16891
17800
 
16892
17801
  // src/commands/pr/merge.ts
16893
- var USAGE37 = PR_USAGE.merge;
17802
+ var USAGE38 = PR_USAGE.merge;
16894
17803
  var YES_ANSWERS4 = ["y", "yes"];
16895
17804
  var DEFAULT_MERGE_SEAMS = Object.freeze({});
16896
17805
  function refusal11(lines) {
@@ -17059,32 +17968,18 @@ async function askToMerge(context, pr, seams, ask) {
17059
17968
  }
17060
17969
  return { go: yes || await confirmed(seams), unchecked };
17061
17970
  }
17062
- function uncheckedReport(unchecked, commentUrl) {
17063
- return unchecked === null ? null : { workflowCount: unchecked.workflowCount, case: unchecked.reading.case, commentUrl };
17064
- }
17065
- async function commentIfUnchecked(context, pr, number, unchecked) {
17066
- if (unchecked === null)
17067
- return null;
17068
- const posted = await postUncheckedComment(pr.pulls, number, unchecked, (message) => {
17069
- context.output.warn(message);
17070
- });
17071
- if (posted === null)
17072
- return null;
17073
- context.output.info(`Commented on #${number} that it was merged with no checks: ${posted.url}`);
17074
- return posted.url;
17075
- }
17076
17971
  async function runMerge2(context, seams) {
17077
- const yes = readBooleanFlag(context.flags, "yes", USAGE37);
17078
- const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE37);
17079
- const wanted = readMethodFlag(context.flags, USAGE37);
17080
- const asked = readPullArgument(context.args, USAGE37);
17972
+ const yes = readBooleanFlag(context.flags, "yes", USAGE38);
17973
+ const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE38);
17974
+ const wanted = readMethodFlag(context.flags, USAGE38);
17975
+ const asked = readPullArgument(context.args, USAGE38);
17081
17976
  const pr = openPrContext(context, seams);
17082
17977
  const git = (seams.git ?? createGitRunner)(pr.project.root);
17083
17978
  const method = wanted ?? pr.mergeMethod;
17084
- const pick = await pickPullRequest(pr, asked, USAGE37);
17979
+ const pick = await pickPullRequest(pr, asked, USAGE38);
17085
17980
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
17086
17981
  if (detail === null) {
17087
- throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`, USAGE37);
17982
+ throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`, USAGE38);
17088
17983
  }
17089
17984
  const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
17090
17985
  recordPlanCi({
@@ -17140,16 +18035,17 @@ async function runMerge2(context, seams) {
17140
18035
  throw refusal11([`\u274C ${pr.pulls.kind} would not merge #${detail.number}: ${outcome.detail}`]);
17141
18036
  }
17142
18037
  context.output.info(`Merged #${detail.number} into ${detail.baseRefName} (${method}).`);
17143
- const commentUrl = await commentIfUnchecked(context, pr, detail.number, answer2.unchecked);
17144
- const roadmapTicks = await reportTick(context, pr, seams, detail);
17145
18038
  const warn3 = (message) => {
17146
18039
  context.output.warn(message);
17147
18040
  };
17148
18041
  const info2 = (message) => {
17149
18042
  context.output.info(message);
17150
18043
  };
18044
+ const commentUrl = await commentIfUnchecked(pr.pulls, detail.number, answer2.unchecked, { info: info2, warn: warn3 });
18045
+ const roadmapTicks = await reportTick(context, pr, seams, detail);
17151
18046
  const steps = cleanUpAfterMerge(git, detail, { info: info2, warn: warn3 });
17152
18047
  const board = await reportAfterCleanUp(context, pr, seams, detail);
18048
+ await refreshProjectAfterMerge({ body: detail.body, config: pr.projectRefresh, openGh: () => openGh(pr, seams), unblocked: board.unblocked, warn: warn3 });
17153
18049
  const followUps = reportFollowUps({
17154
18050
  root: pr.project.root,
17155
18051
  home: pr.project.home,
@@ -17243,11 +18139,11 @@ var BOM = "\uFEFF";
17243
18139
  var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z /;
17244
18140
  var COLOUR = new RegExp(`(?:${String.fromCharCode(27)}|\\^\\[)\\[[0-9;]*m`, "g");
17245
18141
  var PREFIX2 = "pr triage evidence";
17246
- function named2(value) {
18142
+ function named3(value) {
17247
18143
  return value === "" ? undefined : value;
17248
18144
  }
17249
18145
  function stepColumn(value) {
17250
- return value === UNKNOWN_STEP ? undefined : named2(value);
18146
+ return value === UNKNOWN_STEP ? undefined : named3(value);
17251
18147
  }
17252
18148
  function stripText(text) {
17253
18149
  const withoutBom = text.startsWith(BOM) ? text.slice(BOM.length) : text;
@@ -17256,7 +18152,7 @@ function stripText(text) {
17256
18152
  function groupStep(text) {
17257
18153
  if (!text.startsWith(GROUP_STEP_PREFIX))
17258
18154
  return;
17259
- return named2(text.slice(GROUP_STEP_PREFIX.length).trim());
18155
+ return named3(text.slice(GROUP_STEP_PREFIX.length).trim());
17260
18156
  }
17261
18157
  function parseFailedLogLine(line) {
17262
18158
  const firstTab = line.indexOf(COLUMN);
@@ -17265,7 +18161,7 @@ function parseFailedLogLine(line) {
17265
18161
  return { job: undefined, step: undefined, text: stripText(line) };
17266
18162
  }
17267
18163
  return {
17268
- job: named2(line.slice(0, firstTab)),
18164
+ job: named3(line.slice(0, firstTab)),
17269
18165
  step: stepColumn(line.slice(firstTab + 1, secondTab)),
17270
18166
  text: stripText(line.slice(secondTab + 1))
17271
18167
  };
@@ -17373,12 +18269,12 @@ function fencedBlock(body, language) {
17373
18269
  return `${fence}${language}
17374
18270
  ${text}${fence}`;
17375
18271
  }
17376
- function counted2(count, noun) {
18272
+ function counted3(count, noun) {
17377
18273
  return count === 1 ? `1 ${noun}` : `${count} ${noun}s`;
17378
18274
  }
17379
18275
  function excerptCaption(reading) {
17380
- const shown = counted2(reading.lines.length, "line");
17381
- return reading.omitted === 0 ? `The failing job log, all ${shown} of it.` : `The last ${shown} of the failing job log, ${counted2(reading.omitted, "earlier line")}` + ` omitted of ${reading.total} in all.`;
18276
+ const shown = counted3(reading.lines.length, "line");
18277
+ return reading.omitted === 0 ? `The failing job log, all ${shown} of it.` : `The last ${shown} of the failing job log, ${counted3(reading.omitted, "earlier line")}` + ` omitted of ${reading.total} in all.`;
17382
18278
  }
17383
18279
  function pullRequestSection(pr) {
17384
18280
  const fork = pr.isCrossRepository ? `
@@ -17391,7 +18287,7 @@ function pullRequestSection(pr) {
17391
18287
  function classSection(assessment) {
17392
18288
  const eligibility = assessment.simple ? "simple, so a pinned resolve plan covers it" : "not simple, so the fix needs judgement about what this pull request is for";
17393
18289
  const bump = assessment.dependencyBump ? "yes" : "no";
17394
- const failing = counted2(assessment.failing.length, "failing row");
18290
+ const failing = counted3(assessment.failing.length, "failing row");
17395
18291
  return `- Class: \`${assessment.triageClass}\` (${eligibility})
17396
18292
  ` + `- Why: ${assessment.reason}
17397
18293
  ` + `- Checks verdict: \`${assessment.verdict}\`, ${failing} in the rollup
@@ -17419,7 +18315,7 @@ function excerptSection(input) {
17419
18315
  const reading = excerptLines(evidence, input.maxExcerptLines);
17420
18316
  if (reading.lines.length === 0)
17421
18317
  return NO_EXCERPT;
17422
- const jobs = evidence.jobs.length === 0 ? "" : `${counted2(evidence.jobs.length, "failing job")}: ` + `${evidence.jobs.map((job) => `\`${job}\``).join(", ")}.
18318
+ const jobs = evidence.jobs.length === 0 ? "" : `${counted3(evidence.jobs.length, "failing job")}: ` + `${evidence.jobs.map((job) => `\`${job}\``).join(", ")}.
17423
18319
  `;
17424
18320
  return `${jobs}${excerptCaption(reading)}
17425
18321
 
@@ -17670,7 +18566,7 @@ function readLastTriage(comments) {
17670
18566
  }
17671
18567
 
17672
18568
  // src/commands/pr/show.ts
17673
- var USAGE38 = PR_USAGE.show;
18569
+ var USAGE39 = PR_USAGE.show;
17674
18570
  var INDENT6 = " ";
17675
18571
  var SHORT_SHA = 7;
17676
18572
  var NOT_IN_BODY = "not named by the body";
@@ -17708,19 +18604,19 @@ function factsLine(detail) {
17708
18604
  function repositoryOf(detail) {
17709
18605
  return PULL_URL_REPOSITORY.exec(detail.url.trim())?.[1] ?? null;
17710
18606
  }
17711
- function linkedWord(issue, own, named3) {
18607
+ function linkedWord(issue, own, named4) {
17712
18608
  if (own !== null && issue.repository !== own) {
17713
18609
  return `${issue.repository}#${issue.number} (${OTHER_REPOSITORY})`;
17714
18610
  }
17715
- return named3.has(issue.number) ? `#${issue.number}` : `#${issue.number} (${NOT_IN_BODY})`;
18611
+ return named4.has(issue.number) ? `#${issue.number}` : `#${issue.number} (${NOT_IN_BODY})`;
17716
18612
  }
17717
18613
  function closesLine(reading) {
17718
18614
  const own = repositoryOf(reading.detail);
17719
- const named3 = new Set(reading.bodyCloses);
18615
+ const named4 = new Set(reading.bodyCloses);
17720
18616
  const linked = new Set(reading.detail.closes.filter((issue) => own === null || issue.repository === own).map((issue) => issue.number));
17721
18617
  const words = [
17722
- ...reading.detail.closes.map((issue) => linkedWord(issue, own, named3)),
17723
- ...[...named3].filter((number) => !linked.has(number)).map((number) => `#${number} (${KEYWORD_ONLY})`)
18618
+ ...reading.detail.closes.map((issue) => linkedWord(issue, own, named4)),
18619
+ ...[...named4].filter((number) => !linked.has(number)).map((number) => `#${number} (${KEYWORD_ONLY})`)
17724
18620
  ];
17725
18621
  return words.length === 0 ? "closes none" : `closes ${words.join(", ")}`;
17726
18622
  }
@@ -17801,12 +18697,12 @@ async function probe2(call) {
17801
18697
  }
17802
18698
  }
17803
18699
  async function readShow(context, seams) {
17804
- const asked = readPullArgument(context.args, USAGE38);
18700
+ const asked = readPullArgument(context.args, USAGE39);
17805
18701
  const pr = openPrContext(context, seams);
17806
- const pick = await pickPullRequest(pr, asked, USAGE38);
18702
+ const pick = await pickPullRequest(pr, asked, USAGE39);
17807
18703
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
17808
18704
  if (detail === null) {
17809
- throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE38);
18705
+ throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE39);
17810
18706
  }
17811
18707
  const checks = await probe2(() => pr.pulls.checks(pick.number));
17812
18708
  const comments = await probe2(() => pr.pulls.comments(pick.number));
@@ -18690,7 +19586,7 @@ function withTaskBudget(plan2, usd) {
18690
19586
  }
18691
19587
 
18692
19588
  // src/pr/plans/load.ts
18693
- import { existsSync as existsSync10, readFileSync as readFileSync18 } from "fs";
19589
+ import { existsSync as existsSync10, readFileSync as readFileSync19 } from "fs";
18694
19590
  import { dirname as dirname7, join as join32 } from "path";
18695
19591
  import { fileURLToPath as fileURLToPath4 } from "url";
18696
19592
  var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
@@ -18721,7 +19617,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
18721
19617
  if (found === undefined) {
18722
19618
  throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
18723
19619
  }
18724
- return readFileSync18(found, "utf8");
19620
+ return readFileSync19(found, "utf8");
18725
19621
  }
18726
19622
  var SLOT_PATTERN2 = /\{[A-Z][A-Z0-9_]*\}/g;
18727
19623
  var CONFLICT_SENTENCE_SLOT = "CONFLICT_SENTENCE";
@@ -18861,7 +19757,7 @@ function readAttemptRepeat(input) {
18861
19757
  }
18862
19758
 
18863
19759
  // src/commands/pr/resolve-loop.ts
18864
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
19760
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
18865
19761
  import { join as join33 } from "path";
18866
19762
  var RESOLVE_SUBDIR = join33(".rafa", "resolve");
18867
19763
  var RUN_PREFIX = "pr-";
@@ -18875,7 +19771,7 @@ function writeResolvePlan(write) {
18875
19771
  const dir = resolveRunDir(write.home, write.number, write.attempt);
18876
19772
  mkdirSync7(dir, { recursive: true });
18877
19773
  const path = join33(dir, pinnedPlanFileName(write.triageClass));
18878
- writeFileSync7(path, write.plan, "utf8");
19774
+ writeFileSync8(path, write.plan, "utf8");
18879
19775
  return path;
18880
19776
  }
18881
19777
  function resolveLoopArgv(planPath) {
@@ -18915,7 +19811,7 @@ async function runResolveLoop(run, seams = {}) {
18915
19811
  }
18916
19812
 
18917
19813
  // src/pr/triage/version-convert.ts
18918
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "fs";
19814
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
18919
19815
  import { dirname as dirname8, join as join34, posix } from "path";
18920
19816
  var FALLBACK_PLAN_PREFIX = "pr-";
18921
19817
  var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
@@ -19028,7 +19924,7 @@ function planConversion(input, problems) {
19028
19924
  }
19029
19925
  let branchVersionText;
19030
19926
  try {
19031
- branchVersionText = readFileSync19(join34(input.worktree, settings.releaseVersionFile), "utf8");
19927
+ branchVersionText = readFileSync20(join34(input.worktree, settings.releaseVersionFile), "utf8");
19032
19928
  } catch (error) {
19033
19929
  return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
19034
19930
  }
@@ -19057,7 +19953,7 @@ function writeConversion(input, plan2) {
19057
19953
  const target = join34(input.worktree, path);
19058
19954
  try {
19059
19955
  mkdirSync8(dirname8(target), { recursive: true });
19060
- writeFileSync8(target, text);
19956
+ writeFileSync9(target, text);
19061
19957
  } catch (error) {
19062
19958
  return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
19063
19959
  }
@@ -19154,9 +20050,9 @@ function ghPermissionsIn(root) {
19154
20050
  return createGhPermissions({ gh: createGhRunner({ cwd: root }) });
19155
20051
  }
19156
20052
  function repoLabel(url, remote) {
19157
- const named3 = PULL_URL.exec(url.trim())?.[1];
19158
- if (named3 !== undefined)
19159
- return named3;
20053
+ const named4 = PULL_URL.exec(url.trim())?.[1];
20054
+ if (named4 !== undefined)
20055
+ return named4;
19160
20056
  const normalized = remote === null ? "" : normalizeRemote(remote.trim());
19161
20057
  return normalized === "" ? UNNAMED_REPO : normalized;
19162
20058
  }
@@ -19483,7 +20379,7 @@ async function runAttempts(run, detail, first) {
19483
20379
  }
19484
20380
 
19485
20381
  // src/commands/pr/triage.ts
19486
- var USAGE39 = PR_USAGE.triage;
20382
+ var USAGE40 = PR_USAGE.triage;
19487
20383
  var DEFAULT_MAX_ATTEMPTS = 2;
19488
20384
  var DETACHED_HEAD2 = "HEAD";
19489
20385
  var RESOLVED = false;
@@ -19539,7 +20435,7 @@ async function selectTargets(pr, warn3, now, resolve13) {
19539
20435
  async function detailOf2(pr, number) {
19540
20436
  const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
19541
20437
  if (detail === null) {
19542
- throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE39);
20438
+ throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE40);
19543
20439
  }
19544
20440
  return detail;
19545
20441
  }
@@ -19650,14 +20546,14 @@ async function assessOne(options) {
19650
20546
  function refuseResolveWithoutComment(wantsResolve, wantsComment) {
19651
20547
  if (!wantsResolve || wantsComment)
19652
20548
  return;
19653
- throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE39);
20549
+ throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE40);
19654
20550
  }
19655
20551
  async function runTriage(context, seams) {
19656
- const wantsComment = readBooleanFlag(context.flags, "comment", USAGE39, true);
19657
- const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE39, false);
19658
- const maxAttempts = readMaxAttempts(context.flags, USAGE39);
20552
+ const wantsComment = readBooleanFlag(context.flags, "comment", USAGE40, true);
20553
+ const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE40, false);
20554
+ const maxAttempts = readMaxAttempts(context.flags, USAGE40);
19659
20555
  refuseResolveWithoutComment(wantsResolve, wantsComment);
19660
- const asked = readPullArgument(context.args, USAGE39);
20556
+ const asked = readPullArgument(context.args, USAGE40);
19661
20557
  const pr = openPrContext(context, seams);
19662
20558
  const git = (seams.git ?? createGitRunner)(pr.project.root);
19663
20559
  const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
@@ -19795,7 +20691,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
19795
20691
  var triage_default = createPrTriageCommand();
19796
20692
 
19797
20693
  // src/commands/pr/view.ts
19798
- var USAGE40 = PR_USAGE.view;
20694
+ var USAGE41 = PR_USAGE.view;
19799
20695
  function trimmedOrNull2(value) {
19800
20696
  const trimmed = value.trim();
19801
20697
  return trimmed === "" ? null : trimmed;
@@ -19805,9 +20701,9 @@ function renderView(number, url) {
19805
20701
  return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
19806
20702
  }
19807
20703
  async function runView2(context, seams) {
19808
- const asked = readPullArgument(context.args, USAGE40);
20704
+ const asked = readPullArgument(context.args, USAGE41);
19809
20705
  const pr = openPrContext(context, seams);
19810
- const pick = await pickPullRequest(pr, asked, USAGE40);
20706
+ const pick = await pickPullRequest(pr, asked, USAGE41);
19811
20707
  await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
19812
20708
  const url = trimmedOrNull2(pick.summary?.url ?? "");
19813
20709
  return {
@@ -19862,7 +20758,7 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
19862
20758
  var view_default = createPrViewCommand();
19863
20759
 
19864
20760
  // src/commands/pr/wait.ts
19865
- var USAGE41 = PR_USAGE.wait;
20761
+ var USAGE42 = PR_USAGE.wait;
19866
20762
  var TIMEOUT_FLAG2 = "timeout";
19867
20763
  var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
19868
20764
  var MS_PER_MIN = 60000;
@@ -19928,10 +20824,10 @@ function renderWait(reading) {
19928
20824
  `);
19929
20825
  }
19930
20826
  async function runWait(context, seams) {
19931
- const minutes = readTimeoutMinutes(context.flags, USAGE41);
19932
- const asked = readPullArgument(context.args, USAGE41);
20827
+ const minutes = readTimeoutMinutes(context.flags, USAGE42);
20828
+ const asked = readPullArgument(context.args, USAGE42);
19933
20829
  const pr = openPrContext(context, seams);
19934
- const pick = await pickPullRequest(pr, asked, USAGE41);
20830
+ const pick = await pickPullRequest(pr, asked, USAGE42);
19935
20831
  const timeoutMs = minutes * MS_PER_MIN;
19936
20832
  context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
19937
20833
  const waited = await waitForChecks({
@@ -20037,11 +20933,11 @@ function releasePullBody(build, base) {
20037
20933
  `);
20038
20934
  }
20039
20935
  function foreignSentence(pull2, base) {
20040
- const named3 = `pull request #${pull2.number} from ${RELEASE_PR_BRANCH}`;
20936
+ const named4 = `pull request #${pull2.number} from ${RELEASE_PR_BRANCH}`;
20041
20937
  if (pull2.isCrossRepository)
20042
- return `${named3} comes from a fork, so settle will not replace the branch under it; close it and settle again`;
20938
+ return `${named4} comes from a fork, so settle will not replace the branch under it; close it and settle again`;
20043
20939
  if (pull2.baseRefName !== base) {
20044
- return `${named3} goes into ${pull2.baseRefName}, not ${base}, so settle will not replace the branch under it; close it, or settle with pr.base ${pull2.baseRefName}`;
20940
+ return `${named4} goes into ${pull2.baseRefName}, not ${base}, so settle will not replace the branch under it; close it, or settle with pr.base ${pull2.baseRefName}`;
20045
20941
  }
20046
20942
  return null;
20047
20943
  }
@@ -20363,10 +21259,152 @@ async function withSettleWorktree(options, body) {
20363
21259
  return { ok: true, value, ref: worktree.ref, commit: worktree.commit, leftover };
20364
21260
  }
20365
21261
 
21262
+ // src/commands/release/settle-project.ts
21263
+ var COMMIT_BATCH = 20;
21264
+ var COMMIT_PULL_LIMIT = 10;
21265
+ var CLOSING_LIMIT = 100;
21266
+ var PREFIX5 = "release settle project";
21267
+ function named4(issues) {
21268
+ return issues.map((issue) => `#${String(issue)}`).join(", ");
21269
+ }
21270
+ function settleProblemLine(issues, problem2) {
21271
+ const which = issues.length === 0 ? "the issues this release closes" : named4(issues);
21272
+ return `The project was not updated for ${which}: ${problem2}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
21273
+ }
21274
+ function commitAlias(index) {
21275
+ return `c${String(index)}`;
21276
+ }
21277
+ function commitPullsArgs(commits) {
21278
+ const declared = commits.map((_, index) => `, $${commitAlias(index)}: GitObjectID!`).join("");
21279
+ const objects = commits.map((_, index) => ` ${commitAlias(index)}: object(oid: $${commitAlias(index)})` + ` { ... on Commit { associatedPullRequests(first: ${String(COMMIT_PULL_LIMIT)})` + " { pageInfo { hasNextPage } nodes { number state baseRefName body mergeCommit { oid }" + ` closingIssuesReferences(first: ${String(CLOSING_LIMIT)}) { pageInfo { hasNextPage }` + " nodes { number repository { nameWithOwner } } } } } } }").join("");
21280
+ const query = `query($owner: String!, $repo: String!${declared}) { repository(owner: $owner, name: $repo) { nameWithOwner${objects} } }`;
21281
+ return Object.freeze([
21282
+ "api",
21283
+ "graphql",
21284
+ "-F",
21285
+ "owner={owner}",
21286
+ "-F",
21287
+ "repo={repo}",
21288
+ ...commits.flatMap((commit, index) => ["-f", `${commitAlias(index)}=${commit}`]),
21289
+ "-f",
21290
+ `query=${query}`
21291
+ ]);
21292
+ }
21293
+ function refuse7(problem2) {
21294
+ throw new Error(`${PREFIX5}: ${problem2}`);
21295
+ }
21296
+ function readMapping2(value, where) {
21297
+ if (!isMapping(value))
21298
+ refuse7(`${where} is ${describeValue(value)}, expected a mapping`);
21299
+ return value;
21300
+ }
21301
+ function readList2(value, where) {
21302
+ if (!Array.isArray(value))
21303
+ refuse7(`${where} is ${describeValue(value)}, expected a list`);
21304
+ return value;
21305
+ }
21306
+ function readNumber(value, where) {
21307
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
21308
+ refuse7(`${where} is ${describeValue(value)}, expected a positive whole number`);
21309
+ }
21310
+ return value;
21311
+ }
21312
+ function readString(value, where) {
21313
+ if (typeof value !== "string")
21314
+ refuse7(`${where} is ${describeValue(value)}, expected a string`);
21315
+ return value;
21316
+ }
21317
+ function readOnePage(value, where) {
21318
+ const connection = readMapping2(value, where);
21319
+ if (readMapping2(connection["pageInfo"], `${where}.pageInfo`)["hasNextPage"] !== false) {
21320
+ refuse7(`${where} holds more entries than one page`);
21321
+ }
21322
+ return readList2(connection["nodes"], `${where}.nodes`);
21323
+ }
21324
+ function readPull(value, where, repository) {
21325
+ const pull2 = readMapping2(value, where);
21326
+ if (pull2["state"] !== "MERGED")
21327
+ return null;
21328
+ const merge = pull2["mergeCommit"];
21329
+ const linked = readOnePage(pull2["closingIssuesReferences"], `${where}.closingIssuesReferences`).flatMap((node, index) => {
21330
+ const at = `${where}.closingIssuesReferences[${String(index)}]`;
21331
+ const issue = readMapping2(node, at);
21332
+ const owner = readString(readMapping2(issue["repository"], `${at}.repository`)["nameWithOwner"], `${at}.repository.nameWithOwner`);
21333
+ return owner.toLowerCase() === repository.toLowerCase() ? [readNumber(issue["number"], `${at}.number`)] : [];
21334
+ });
21335
+ return {
21336
+ number: readNumber(pull2["number"], `${where}.number`),
21337
+ baseRefName: readString(pull2["baseRefName"], `${where}.baseRefName`),
21338
+ mergeCommit: isMapping(merge) ? readString(merge["oid"], `${where}.mergeCommit.oid`) : null,
21339
+ issues: [...linked, ...closedIssuesIn(readString(pull2["body"], `${where}.body`))]
21340
+ };
21341
+ }
21342
+ function broughtBy(pulls, commit, branch) {
21343
+ const merged2 = pulls.filter((pull2) => pull2.mergeCommit === commit);
21344
+ return merged2.length > 0 ? merged2 : pulls.filter((pull2) => pull2.baseRefName === branch);
21345
+ }
21346
+ async function readBatch(gh, commits, branch) {
21347
+ const result2 = await gh(commitPullsArgs(commits));
21348
+ if (!result2.ok)
21349
+ refuse7(`gh api graphql failed: ${result2.stderr.trim() || result2.stdout.trim()}`);
21350
+ let parsed;
21351
+ try {
21352
+ parsed = JSON.parse(result2.stdout);
21353
+ } catch {
21354
+ refuse7("gh api graphql answered text that is not JSON");
21355
+ }
21356
+ const data = readMapping2(readMapping2(parsed, "the answer")["data"], "data");
21357
+ const answer2 = readMapping2(data["repository"], "data.repository");
21358
+ const repository = readString(answer2["nameWithOwner"], "data.repository.nameWithOwner");
21359
+ return commits.flatMap((commit, index) => {
21360
+ const where = `commit ${commit}`;
21361
+ const object = answer2[commitAlias(index)];
21362
+ if (object === null || object === undefined)
21363
+ return [];
21364
+ const pulls = readOnePage(readMapping2(object, where)["associatedPullRequests"], `${where}.associatedPullRequests`).map((pull2, at) => readPull(pull2, `${where}.associatedPullRequests[${String(at)}]`, repository)).filter((pull2) => pull2 !== null);
21365
+ return broughtBy(pulls, commit, branch);
21366
+ });
21367
+ }
21368
+ async function readPulls(gh, commits, branch) {
21369
+ const pulls = [];
21370
+ for (let start2 = 0;start2 < commits.length; start2 += COMMIT_BATCH) {
21371
+ pulls.push(...await readBatch(gh, commits.slice(start2, start2 + COMMIT_BATCH), branch));
21372
+ }
21373
+ return pulls;
21374
+ }
21375
+ function releasedCommits(delivered) {
21376
+ const { outcome } = delivered;
21377
+ if (outcome.outcome !== "pushed" && outcome.outcome !== "superseded")
21378
+ return null;
21379
+ return [...new Set(outcome.build.fragments.map((fragment) => fragment.commit))];
21380
+ }
21381
+ async function refreshProjectAfterSettle(options) {
21382
+ const { config, branch, refresh = refreshIssueItems } = options;
21383
+ const commits = releasedCommits(options.delivered);
21384
+ if (config.boardProjectNumber === null || commits === null || commits.length === 0)
21385
+ return null;
21386
+ const gh = options.openGh();
21387
+ let pulls;
21388
+ try {
21389
+ pulls = await readPulls(gh, commits, branch);
21390
+ } catch (error) {
21391
+ return { commits, pullRequests: [], issues: [], warnings: [settleProblemLine([], messageOf(error))] };
21392
+ }
21393
+ const pullRequests = [...new Set(pulls.map((pull2) => pull2.number))];
21394
+ const issues = [...new Set(pulls.flatMap((pull2) => pull2.issues))];
21395
+ if (issues.length === 0)
21396
+ return { commits, pullRequests, issues, warnings: [] };
21397
+ try {
21398
+ return { commits, pullRequests, issues, warnings: (await refresh({ config, gh }, issues)).warnings };
21399
+ } catch (error) {
21400
+ return { commits, pullRequests, issues, warnings: [settleProblemLine(issues, messageOf(error))] };
21401
+ }
21402
+ }
21403
+
20366
21404
  // src/commands/release/settle.ts
20367
21405
  var RELEASE_SETTLE_USAGE = "rafa release settle [--dry-run]";
20368
21406
  var COMMAND_NAME12 = "rafa release settle";
20369
- var DRY_RUN_FLAG6 = "dry-run";
21407
+ var DRY_RUN_FLAG7 = "dry-run";
20370
21408
  var INDENT9 = " ";
20371
21409
  var SHORT_HASH4 = 12;
20372
21410
  var DEFAULT_RELEASE_SETTLE_SEAMS = Object.freeze({});
@@ -20423,7 +21461,7 @@ function unwrittenEnding(reading) {
20423
21461
  }
20424
21462
  function resultOf6(base, lines, problem2) {
20425
21463
  const exitCode = problem2 === null ? 0 : 1;
20426
- return { ...base, lines, problem: problem2, exitCode };
21464
+ return { ...base, lines, problem: problem2, exitCode, project: null };
20427
21465
  }
20428
21466
  function dryRunResult(reading, ref8, branch) {
20429
21467
  const base = { dryRun: true, delivery: null, reading, delivered: null, tag: null };
@@ -20481,7 +21519,7 @@ function providerFor(project, config, seams) {
20481
21519
  }
20482
21520
  async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20483
21521
  expectNoArgument(context.args, RELEASE_SETTLE_USAGE);
20484
- const dryRun = readSwitch(DRY_RUN_FLAG6, context.flags[DRY_RUN_FLAG6], `Usage: ${RELEASE_SETTLE_USAGE}`);
21522
+ const dryRun = readSwitch(DRY_RUN_FLAG7, context.flags[DRY_RUN_FLAG7], `Usage: ${RELEASE_SETTLE_USAGE}`);
20485
21523
  const project = projectOf9(context);
20486
21524
  const config = resolveProjectConfig(project, COMMAND_NAME12, (message) => {
20487
21525
  context.output.warn(message);
@@ -20505,7 +21543,16 @@ async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20505
21543
  context.output.warn(`The settle worktree was not fully removed: ${ran.leftover}`);
20506
21544
  if (!ran.ok)
20507
21545
  throw new CommandExit(1, `\u274C ${ran.problem}`);
20508
- return ran.value;
21546
+ if (ran.value.delivered === null)
21547
+ return ran.value;
21548
+ const refreshed = await refreshProjectAfterSettle({
21549
+ delivered: ran.value.delivered,
21550
+ branch,
21551
+ config,
21552
+ openGh: () => (seams.gh ?? ((cwd) => createGhRunner({ cwd })))(project.root),
21553
+ refresh: seams.refresh
21554
+ });
21555
+ return { ...ran.value, project: refreshed };
20509
21556
  }
20510
21557
  function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20511
21558
  const command = {
@@ -20517,7 +21564,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20517
21564
  args: [],
20518
21565
  flags: [
20519
21566
  {
20520
- name: DRY_RUN_FLAG6,
21567
+ name: DRY_RUN_FLAG7,
20521
21568
  description: "Print the fragments, their order, the strategy and the version, and write nothing:" + " no commit, no push, no tag.",
20522
21569
  type: "boolean"
20523
21570
  }
@@ -20539,7 +21586,12 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20539
21586
  outputs: ["text", "json"],
20540
21587
  run: async (context) => {
20541
21588
  const result2 = await runSettle(context, seams);
21589
+ const warnProject = () => {
21590
+ for (const line of result2.project?.warnings ?? [])
21591
+ context.output.warn(line);
21592
+ };
20542
21593
  if (context.outputMode === "json") {
21594
+ warnProject();
20543
21595
  if (result2.problem !== null)
20544
21596
  throw new CommandExit(1, result2.problem);
20545
21597
  context.output.result(result2);
@@ -20547,6 +21599,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20547
21599
  }
20548
21600
  for (const line of result2.lines)
20549
21601
  context.output.info(line);
21602
+ warnProject();
20550
21603
  if (result2.problem !== null)
20551
21604
  throw new CommandExit(1, result2.problem);
20552
21605
  }
@@ -20556,7 +21609,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
20556
21609
  var settle_default = createReleaseSettleCommand();
20557
21610
 
20558
21611
  // src/commands/release/tag.ts
20559
- import { readFileSync as readFileSync20 } from "fs";
21612
+ import { readFileSync as readFileSync21 } from "fs";
20560
21613
  import { join as join36 } from "path";
20561
21614
 
20562
21615
  // src/commands/release/release-commit.ts
@@ -20615,7 +21668,7 @@ function readBranch2(git) {
20615
21668
  }
20616
21669
  function textOf(path) {
20617
21670
  try {
20618
- return readFileSync20(path, "utf8");
21671
+ return readFileSync21(path, "utf8");
20619
21672
  } catch {
20620
21673
  return null;
20621
21674
  }
@@ -20694,24 +21747,24 @@ function followUpsFor(tag, version2, target, publish, place2 = ON_HEAD) {
20694
21747
  }
20695
21748
  ];
20696
21749
  }
20697
- function refuse7(reason, message) {
21750
+ function refuse8(reason, message) {
20698
21751
  return { kind: "refused", reason, message };
20699
21752
  }
20700
21753
  function branchRefusal(inputs) {
20701
21754
  const { branch, problem: problem2 } = inputs.branch;
20702
21755
  if (branch === null) {
20703
- return refuse7("branch", `${problem2 ?? "no branch is checked out"}, and a release is tagged` + ` on ${inputs.releaseBranch}`);
21756
+ return refuse8("branch", `${problem2 ?? "no branch is checked out"}, and a release is tagged` + ` on ${inputs.releaseBranch}`);
20704
21757
  }
20705
- return branch === inputs.releaseBranch ? null : refuse7("branch", `a release is tagged on ${inputs.releaseBranch}, and ${branch} is checked out`);
21758
+ return branch === inputs.releaseBranch ? null : refuse8("branch", `a release is tagged on ${inputs.releaseBranch}, and ${branch} is checked out`);
20706
21759
  }
20707
21760
  function changelogRefusal(inputs, version2) {
20708
21761
  const { path, version: newest, problem: problem2 } = inputs.changelog;
20709
21762
  if (problem2 !== null)
20710
- return refuse7("changelog", problem2);
21763
+ return refuse8("changelog", problem2);
20711
21764
  if (newest === null) {
20712
- return refuse7("changelog", `${path} names no release, so nothing says ${version2} is the version to tag`);
21765
+ return refuse8("changelog", `${path} names no release, so nothing says ${version2} is the version to tag`);
20713
21766
  }
20714
- return newest === version2 ? null : refuse7("changelog", `${inputs.version.path} says ${version2} and the newest section of ${path}` + ` says ${newest}`);
21767
+ return newest === version2 ? null : refuse8("changelog", `${inputs.version.path} says ${version2} and the newest section of ${path}` + ` says ${newest}`);
20715
21768
  }
20716
21769
  function decideTag(inputs) {
20717
21770
  const onBranch = branchRefusal(inputs);
@@ -20719,13 +21772,13 @@ function decideTag(inputs) {
20719
21772
  return onBranch;
20720
21773
  const version2 = inputs.version.version;
20721
21774
  if (version2 === null) {
20722
- return refuse7("version", inputs.version.problem ?? `${inputs.version.path} declares no version`);
21775
+ return refuse8("version", inputs.version.problem ?? `${inputs.version.path} declares no version`);
20723
21776
  }
20724
21777
  if (inputs.tags.problem !== null)
20725
- return refuse7("git", inputs.tags.problem);
21778
+ return refuse8("git", inputs.tags.problem);
20726
21779
  const held = inputs.tags.tags.find((candidate) => candidate.version === version2);
20727
21780
  if (held !== undefined) {
20728
- return refuse7("tagged", `${held.tag} already names ${version2}, so there is nothing to tag`);
21781
+ return refuse8("tagged", `${held.tag} already names ${version2}, so there is nothing to tag`);
20729
21782
  }
20730
21783
  const disagrees = changelogRefusal(inputs, version2);
20731
21784
  if (disagrees !== null)
@@ -20733,14 +21786,14 @@ function decideTag(inputs) {
20733
21786
  const path = inputs.changelog.path;
20734
21787
  const unreceipted = inputs.receipt === null ? `${path} was not read for the receipt of ${version2}` : receiptProblem(inputs.receipt, version2, path);
20735
21788
  if (unreceipted !== null)
20736
- return refuse7("receipt", unreceipted);
21789
+ return refuse8("receipt", unreceipted);
20737
21790
  const release = inputs.release;
20738
21791
  if (release === null)
20739
- return refuse7("version", `${inputs.version.path} was not looked up in the history`);
21792
+ return refuse8("version", `${inputs.version.path} was not looked up in the history`);
20740
21793
  if (release.problem !== null)
20741
- return refuse7(release.problem.reason, release.problem.message);
21794
+ return refuse8(release.problem.reason, release.problem.message);
20742
21795
  if (release.commit === null)
20743
- return refuse7("git", `no commit could be named for ${version2}`);
21796
+ return refuse8("git", `no commit could be named for ${version2}`);
20744
21797
  return { kind: "ready", version: version2, tag: versionTag(version2), commit: release.commit, ahead: release.ahead };
20745
21798
  }
20746
21799
  function projectOf10(context) {
@@ -20913,7 +21966,7 @@ import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as
20913
21966
  import { dirname as dirname9, isAbsolute as isAbsolute7, join as join38, relative as relative9, resolve as resolve13, sep as sep5 } from "path";
20914
21967
 
20915
21968
  // src/backfill/proposal-file.ts
20916
- import { readdirSync as readdirSync4, readFileSync as readFileSync21 } from "fs";
21969
+ import { readdirSync as readdirSync4, readFileSync as readFileSync22 } from "fs";
20917
21970
  import { join as join37 } from "path";
20918
21971
  var BACKFILL_PATH = join37(".rafa", "backfill");
20919
21972
  var PROPOSAL_FILE_PREFIX = "proposals-";
@@ -21079,7 +22132,7 @@ function readProposalFiles(dir) {
21079
22132
  return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
21080
22133
  const path = join37(dir, name);
21081
22134
  try {
21082
- return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
22135
+ return { path, result: parseProposalFile(readFileSync22(path, "utf8")) };
21083
22136
  } catch (error) {
21084
22137
  return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
21085
22138
  }
@@ -21135,7 +22188,7 @@ function countBackups(results) {
21135
22188
  }
21136
22189
 
21137
22190
  // src/backfill/derive.ts
21138
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
22191
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
21139
22192
  import { basename as basename6 } from "path";
21140
22193
  var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
21141
22194
  function renderWhenToUse(trigger, prevents) {
@@ -21261,7 +22314,7 @@ function planFile(entry, options) {
21261
22314
  }
21262
22315
  let original;
21263
22316
  try {
21264
- original = readFileSync22(entry.path, "utf8");
22317
+ original = readFileSync23(entry.path, "utf8");
21265
22318
  } catch (error) {
21266
22319
  return inert(entry, "skipped", `the file cannot be read (${error instanceof Error ? error.message : String(error)})`);
21267
22320
  }
@@ -21305,7 +22358,7 @@ function planDerivation(root, options) {
21305
22358
  function applyFile(action, options) {
21306
22359
  if (action.text === null || action.original === null)
21307
22360
  return action;
21308
- const current = readFileSync22(action.path, "utf8");
22361
+ const current = readFileSync23(action.path, "utf8");
21309
22362
  if (current !== action.original) {
21310
22363
  return {
21311
22364
  ...action,
@@ -21314,7 +22367,7 @@ function applyFile(action, options) {
21314
22367
  text: null
21315
22368
  };
21316
22369
  }
21317
- writeFileSync9(action.path, action.text, "utf8");
22370
+ writeFileSync10(action.path, action.text, "utf8");
21318
22371
  const after = failureLines(checkFile(action.path, "skill", {
21319
22372
  projectRoot: options.projectRoot,
21320
22373
  pathDirs: options.pathDirs
@@ -21322,7 +22375,7 @@ function applyFile(action, options) {
21322
22375
  const added = addedFailures(action.before, after);
21323
22376
  if (added.length === 0)
21324
22377
  return action;
21325
- writeFileSync9(action.path, action.original, "utf8");
22378
+ writeFileSync10(action.path, action.original, "utf8");
21326
22379
  return {
21327
22380
  ...action,
21328
22381
  kind: "refused",
@@ -21336,7 +22389,7 @@ function applyDerivation(plan2, options) {
21336
22389
  }
21337
22390
 
21338
22391
  // src/backfill/proposal-batch.ts
21339
- import { readFileSync as readFileSync23 } from "fs";
22392
+ import { readFileSync as readFileSync24 } from "fs";
21340
22393
  import { relative as relative10 } from "path";
21341
22394
 
21342
22395
  // src/demote/report.ts
@@ -21635,7 +22688,7 @@ function readCandidate(root, entry) {
21635
22688
  return null;
21636
22689
  let text;
21637
22690
  try {
21638
- text = readFileSync23(entry.path, "utf8");
22691
+ text = readFileSync24(entry.path, "utf8");
21639
22692
  } catch {
21640
22693
  return null;
21641
22694
  }
@@ -21741,7 +22794,7 @@ function parseSessionAnswer(stdout) {
21741
22794
  }
21742
22795
 
21743
22796
  // src/backfill/propose.ts
21744
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "fs";
22797
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync25, writeFileSync as writeFileSync11 } from "fs";
21745
22798
  import { join as join39, resolve as resolve14 } from "path";
21746
22799
  var PROPOSAL_ACTION_KINDS = [
21747
22800
  "applied",
@@ -21826,7 +22879,7 @@ async function runProposalPass(options) {
21826
22879
  const number = index + 1;
21827
22880
  const file = await runProposalBatch(number, batch, options);
21828
22881
  const path = proposalPath(options.backfillDir, number);
21829
- writeFileSync10(path, renderProposalFile(file), "utf8");
22882
+ writeFileSync11(path, renderProposalFile(file), "utf8");
21830
22883
  written.push({ path, file });
21831
22884
  }
21832
22885
  return { written, candidates: candidates.length };
@@ -21868,7 +22921,7 @@ function writeRow(path, row2, original, options) {
21868
22921
  return inert2(path, row2, "unchanged", "every field the row proposes is already what the file carries");
21869
22922
  }
21870
22923
  const before = checkLines(path, options);
21871
- writeFileSync10(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
22924
+ writeFileSync11(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
21872
22925
  const added = addedFailures(before, checkLines(path, options));
21873
22926
  if (added.length === 0) {
21874
22927
  return {
@@ -21880,7 +22933,7 @@ function writeRow(path, row2, original, options) {
21880
22933
  added: []
21881
22934
  };
21882
22935
  }
21883
- writeFileSync10(path, original, "utf8");
22936
+ writeFileSync11(path, original, "utf8");
21884
22937
  return {
21885
22938
  path,
21886
22939
  relative: row2.path,
@@ -21900,7 +22953,7 @@ function applyRow(root, row2, options) {
21900
22953
  }
21901
22954
  let original;
21902
22955
  try {
21903
- original = readFileSync24(path, "utf8");
22956
+ original = readFileSync25(path, "utf8");
21904
22957
  } catch {
21905
22958
  return inert2(path, row2, "refused", "the file the row names is gone");
21906
22959
  }
@@ -21939,7 +22992,7 @@ function applyProposals(files, root, options) {
21939
22992
  }
21940
22993
 
21941
22994
  // src/demote/select.ts
21942
- import { readdirSync as readdirSync5, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
22995
+ import { readdirSync as readdirSync5, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
21943
22996
  import { basename as basename7, dirname as dirname10, join as join40 } from "path";
21944
22997
  var SKILLS_DIRECTORY = "skills";
21945
22998
  var CLAUDE_DIRECTORY = ".claude";
@@ -22001,7 +23054,7 @@ function readSelected(absolute, path, entries) {
22001
23054
  let text;
22002
23055
  let mtime;
22003
23056
  try {
22004
- text = readFileSync25(absolute, "utf8");
23057
+ text = readFileSync26(absolute, "utf8");
22005
23058
  mtime = statSync10(absolute).mtime.toISOString();
22006
23059
  } catch {
22007
23060
  return null;
@@ -22386,11 +23439,11 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
22386
23439
  var check_default3 = createSkillCheckCommand();
22387
23440
 
22388
23441
  // src/commands/skill/demote.ts
22389
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync27, statSync as statSync13, writeFileSync as writeFileSync12 } from "fs";
23442
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync28, statSync as statSync13, writeFileSync as writeFileSync13 } from "fs";
22390
23443
  import { dirname as dirname12, resolve as resolve16 } from "path";
22391
23444
 
22392
23445
  // src/demote/apply.ts
22393
- import { copyFileSync as copyFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync12, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
23446
+ import { copyFileSync as copyFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync27, renameSync, rmSync as rmSync3, statSync as statSync12, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
22394
23447
  import { tmpdir as tmpdir3 } from "os";
22395
23448
  import { dirname as dirname11, join as join42 } from "path";
22396
23449
  var DEMOTION_ACTION_KINDS = [
@@ -22432,7 +23485,7 @@ function takenInstinctIds(dir) {
22432
23485
  }
22433
23486
  function readText4(path) {
22434
23487
  try {
22435
- return statSync12(path).isFile() ? readFileSync26(path, "utf8") : null;
23488
+ return statSync12(path).isFile() ? readFileSync27(path, "utf8") : null;
22436
23489
  } catch {
22437
23490
  return null;
22438
23491
  }
@@ -22475,7 +23528,7 @@ function planKept(scope, row2, file) {
22475
23528
  }
22476
23529
  function checkCandidate(dir, id2, text, pathDirs) {
22477
23530
  const path = join42(dir, `${id2}${MARKDOWN_EXTENSION}`);
22478
- writeFileSync11(path, text, "utf8");
23531
+ writeFileSync12(path, text, "utf8");
22479
23532
  const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
22480
23533
  return failureLines2(report5.issues);
22481
23534
  }
@@ -22564,7 +23617,7 @@ function applyDemotion(plan2) {
22564
23617
  for (const action of plan2.actions) {
22565
23618
  if (action.write !== null) {
22566
23619
  mkdirSync11(dirname11(action.write.path), { recursive: true });
22567
- writeFileSync11(action.write.path, action.write.text, "utf8");
23620
+ writeFileSync12(action.write.path, action.write.text, "utf8");
22568
23621
  }
22569
23622
  if (action.move !== null)
22570
23623
  moveFile(action.move);
@@ -22663,7 +23716,7 @@ function verdictLine2(report5) {
22663
23716
  function previousReport(path, context) {
22664
23717
  if (!existsSync14(path))
22665
23718
  return null;
22666
- const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
23719
+ const parsed = parseDemotionReport(readFileSync28(path, "utf8"));
22667
23720
  if (parsed.report !== null)
22668
23721
  return parsed.report;
22669
23722
  context.output.warn(`the report already at ${path} does not parse, so no override in it is carried forward:` + ` ${parsed.issues.map((issue2) => `line ${String(issue2.line)} ${issue2.code}`).join(", ")}`);
@@ -22673,7 +23726,7 @@ function runWrite(context, scope) {
22673
23726
  const files = selectSources(scope);
22674
23727
  const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
22675
23728
  mkdirSync12(dirname12(scope.reportPath), { recursive: true });
22676
- writeFileSync12(scope.reportPath, writeDemotionReport(report5), "utf8");
23729
+ writeFileSync13(scope.reportPath, writeDemotionReport(report5), "utf8");
22677
23730
  const result2 = {
22678
23731
  applied: false,
22679
23732
  scope: scope.scope,
@@ -22699,7 +23752,7 @@ function reviewedReport(scope) {
22699
23752
  if (!existsSync14(scope.reportPath)) {
22700
23753
  throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
22701
23754
  }
22702
- const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
23755
+ const parsed = parseDemotionReport(readFileSync28(scope.reportPath, "utf8"));
22703
23756
  if (parsed.report === null) {
22704
23757
  const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
22705
23758
  throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
@@ -22806,7 +23859,7 @@ var demote_default = createSkillDemoteCommand();
22806
23859
  // src/commands/skill/show.ts
22807
23860
  var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
22808
23861
  var COMMAND_NAME13 = "rafa skill show";
22809
- var USAGE42 = "rafa skill show <name> [--full]";
23862
+ var USAGE43 = "rafa skill show <name> [--full]";
22810
23863
  function unknownSkillMessage(name, inventory) {
22811
23864
  const agent = findShown(inventory.records, "agent", name);
22812
23865
  const hint = agent === null ? [] : [`An agent is named "${name}" (${agent.source}): rafa agent show ${name}`];
@@ -22814,7 +23867,7 @@ function unknownSkillMessage(name, inventory) {
22814
23867
  `\u274C No skill is named "${name}".`,
22815
23868
  ...hint,
22816
23869
  "Run `rafa skill list` to see every skill and where it comes from.",
22817
- `Usage: ${USAGE42}`
23870
+ `Usage: ${USAGE43}`
22818
23871
  ].join(`
22819
23872
  `);
22820
23873
  }
@@ -22830,8 +23883,8 @@ function projectOf13(context) {
22830
23883
  return context.project;
22831
23884
  }
22832
23885
  async function runShow3(context, seams) {
22833
- const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE42}`);
22834
- const name = expectOneArgument(context.args, USAGE42);
23886
+ const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE43}`);
23887
+ const name = expectOneArgument(context.args, USAGE43);
22835
23888
  const project = projectOf13(context);
22836
23889
  const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME13);
22837
23890
  const warnings = warningLines(inventory);
@@ -22943,7 +23996,7 @@ var VERDICT_WORDS = Object.freeze({
22943
23996
  pending: "checks pending",
22944
23997
  none: "no checks"
22945
23998
  });
22946
- function counted3(count, noun, plural4 = `${noun}s`) {
23999
+ function counted4(count, noun, plural4 = `${noun}s`) {
22947
24000
  return `${String(count)} ${count === 1 ? noun : plural4}`;
22948
24001
  }
22949
24002
  function info2(text) {
@@ -22958,7 +24011,7 @@ function blockedTaskCount(blocked) {
22958
24011
  }
22959
24012
  function loopsText(reading) {
22960
24013
  const tasks = blockedTaskCount(reading.blocked);
22961
- return `${String(reading.live.length)} running, ${counted3(tasks, "task")} blocked`;
24014
+ return `${String(reading.live.length)} running, ${counted4(tasks, "task")} blocked`;
22962
24015
  }
22963
24016
  function loopsBody(reading) {
22964
24017
  const running = reading.live.map((record) => info2(`${INDENT11}${sessionLine(record)}`));
@@ -22977,9 +24030,9 @@ function pullText(reading) {
22977
24030
  function blockedText(reading) {
22978
24031
  const { blockedIssues } = reading;
22979
24032
  if (reading.mode === "native") {
22980
- return blockedIssues === null ? "the issues with an open blocker were not read" : `${counted3(blockedIssues, "issue")} with an open blocker`;
24033
+ return blockedIssues === null ? "the issues with an open blocker were not read" : `${counted4(blockedIssues, "issue")} with an open blocker`;
22981
24034
  }
22982
- return blockedIssues === null ? `the ${SPEC_BLOCKED_LABEL} issues were not read` : `${counted3(blockedIssues, "issue")} labelled ${SPEC_BLOCKED_LABEL}`;
24035
+ return blockedIssues === null ? `the ${SPEC_BLOCKED_LABEL} issues were not read` : `${counted4(blockedIssues, "issue")} labelled ${SPEC_BLOCKED_LABEL}`;
22983
24036
  }
22984
24037
  function boardText(reading) {
22985
24038
  const roadmap = `roadmap #${String(reading.roadmap)}`;
@@ -23008,7 +24061,7 @@ function claimsText(reading) {
23008
24061
  if (claims.length === 0)
23009
24062
  return "none on origin as last fetched";
23010
24063
  const count = (states) => claims.filter((row2) => states.includes(row2.state)).length;
23011
- return `${counted3(claims.length, "claim branch", "claim branches")} on origin as last fetched:` + ` ${String(count(["held"]))} held, ${String(count(["stale-claimed", "stale-in-development"]))} stale,` + ` ${String(count(["released"]))} released`;
24064
+ return `${counted4(claims.length, "claim branch", "claim branches")} on origin as last fetched:` + ` ${String(count(["held"]))} held, ${String(count(["stale-claimed", "stale-in-development"]))} stale,` + ` ${String(count(["released"]))} released`;
23012
24065
  }
23013
24066
  function claimHolder(row2) {
23014
24067
  if (row2.owner === null)
@@ -23037,7 +24090,7 @@ function claimsBody(reading) {
23037
24090
  }
23038
24091
  function housekeepingText(reading) {
23039
24092
  const { merged: merged2, stale, notPushed, worktrees } = reading.counts;
23040
- return `${String(merged2)} merged, ${String(stale)} stale, ${String(notPushed)} not pushed,` + ` ${counted3(worktrees, "worktree")} (${String(reading.idleWorktrees)} idle)`;
24093
+ return `${String(merged2)} merged, ${String(stale)} stale, ${String(notPushed)} not pushed,` + ` ${counted4(worktrees, "worktree")} (${String(reading.idleWorktrees)} idle)`;
23041
24094
  }
23042
24095
  function sectionLines(key, section, text, body = () => []) {
23043
24096
  const title = STATUS_SECTION_TITLES[key];
@@ -23115,7 +24168,7 @@ function readClaimBranches(git) {
23115
24168
  function isClaim(reading) {
23116
24169
  return reading.state === "found" && reading.ownership.state !== "none";
23117
24170
  }
23118
- function stageOf(labels) {
24171
+ function stageOf2(labels) {
23119
24172
  return STAGE_LABELS.filter((label) => labels.includes(label));
23120
24173
  }
23121
24174
  function labelsOf2(labels, issue2) {
@@ -23123,7 +24176,7 @@ function labelsOf2(labels, issue2) {
23123
24176
  return null;
23124
24177
  return labels.issues.find((one) => one.number === issue2)?.labels ?? null;
23125
24178
  }
23126
- function rowOf4(reading, input) {
24179
+ function rowOf5(reading, input) {
23127
24180
  const { ownership, tipCommittedAt, branch } = reading;
23128
24181
  const issue2 = claimBranchIssue(branch) ?? 0;
23129
24182
  const labels = labelsOf2(input.labels, issue2);
@@ -23135,7 +24188,7 @@ function rowOf4(reading, input) {
23135
24188
  owner: ownership.state === "held" ? ownership.owner : null,
23136
24189
  handingTo: ownership.state === "held" ? ownership.pending?.to ?? null : null,
23137
24190
  releasedBy: ownership.state === "released" ? ownership.releasedBy : null,
23138
- stage: labels === null ? null : stageOf(labels),
24191
+ stage: labels === null ? null : stageOf2(labels),
23139
24192
  state: state.state,
23140
24193
  tipCommittedAt: tipCommittedAt.toISOString(),
23141
24194
  idleMs: Number.isFinite(elapsed) ? Math.max(0, elapsed) : 0
@@ -23149,7 +24202,7 @@ function labelNotes(rows, labels) {
23149
24202
  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`);
23150
24203
  }
23151
24204
  function readClaims2(input) {
23152
- const claims = input.branches.found.map((reading) => rowOf4(reading, input));
24205
+ const claims = input.branches.found.map((reading) => rowOf5(reading, input));
23153
24206
  return { claims, notes: [...input.branches.notes, ...labelNotes(claims, input.labels)] };
23154
24207
  }
23155
24208
 
@@ -23295,9 +24348,9 @@ function pullSection(sources, branch, provider, deadline) {
23295
24348
  if (!branch.read) {
23296
24349
  return Promise.resolve(unread2(`no pull request was looked for, as the branch was not read: ${branch.problem}`));
23297
24350
  }
23298
- return networkSection("pull request", deadline, () => readPull(sources, branch.branch));
24351
+ return networkSection("pull request", deadline, () => readPull2(sources, branch.branch));
23299
24352
  }
23300
- async function readPull(sources, branch) {
24353
+ async function readPull2(sources, branch) {
23301
24354
  let notes = [];
23302
24355
  const pull2 = await readOpenPull(sources, branch, (problem2) => {
23303
24356
  notes = [...notes, problem2];
@@ -23361,7 +24414,7 @@ async function placeOrNote(sources, walk, listing2) {
23361
24414
  }
23362
24415
  }
23363
24416
  async function readBoard2(sources, board, listing2, blocked, native) {
23364
- const [walk, counted4] = await Promise.all([board.next(), blocked]);
24417
+ const [walk, counted5] = await Promise.all([board.next(), blocked]);
23365
24418
  const { line } = walk;
23366
24419
  const next = line === null ? null : { line, ready: await board.isReady(line.issue), blocked: await board.blocking(line.issue) };
23367
24420
  const { place: place2, notes } = await placeOrNote(sources, walk, listing2);
@@ -23369,8 +24422,8 @@ async function readBoard2(sources, board, listing2, blocked, native) {
23369
24422
  roadmap: walk.roadmap,
23370
24423
  next,
23371
24424
  passed: walk.passed,
23372
- blockedIssues: counted4.count,
23373
- notes: [...walk.problems, ...counted4.notes, ...notes]
24425
+ blockedIssues: counted5.count,
24426
+ notes: [...walk.problems, ...counted5.notes, ...notes]
23374
24427
  };
23375
24428
  const reading = native ? { ...read3, mode: "native" } : read3;
23376
24429
  return place2 === null ? reading : { ...reading, place: place2 };
@@ -23533,7 +24586,7 @@ function createUpdateStub(spec) {
23533
24586
  var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
23534
24587
 
23535
24588
  // src/project/lock.ts
23536
- import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "fs";
24589
+ import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "fs";
23537
24590
  import { join as join43 } from "path";
23538
24591
  var LOCK_FILE = "rafa.lock";
23539
24592
  var LOCKFILE_VERSION = 1;
@@ -23556,20 +24609,20 @@ function isLink(path) {
23556
24609
  return false;
23557
24610
  }
23558
24611
  }
23559
- function refuse8(problem2) {
24612
+ function refuse9(problem2) {
23560
24613
  throw new ProjectLockError(`${LOCK_FILE} ${problem2}`);
23561
24614
  }
23562
24615
  function lockOf(value) {
23563
24616
  if (typeof value !== "object" || value === null || Array.isArray(value))
23564
- refuse8("holds no JSON object");
24617
+ refuse9("holds no JSON object");
23565
24618
  const record = value;
23566
24619
  const format = record["lockfileVersion"];
23567
24620
  if (format !== LOCKFILE_VERSION) {
23568
- refuse8(`is in lockfileVersion ${String(format)}, and this rafa reads ${String(LOCKFILE_VERSION)}: a newer rafa wrote it`);
24621
+ refuse9(`is in lockfileVersion ${String(format)}, and this rafa reads ${String(LOCKFILE_VERSION)}: a newer rafa wrote it`);
23569
24622
  }
23570
24623
  const rafa = record["rafa"];
23571
24624
  if (typeof rafa !== "string" || parseSemanticVersion(rafa) === null) {
23572
- refuse8(`names rafa ${JSON.stringify(rafa)}, which is no version`);
24625
+ refuse9(`names rafa ${JSON.stringify(rafa)}, which is no version`);
23573
24626
  }
23574
24627
  return { lockfileVersion: LOCKFILE_VERSION, rafa };
23575
24628
  }
@@ -23577,25 +24630,25 @@ function readProjectLock(root) {
23577
24630
  const path = lockPath(root);
23578
24631
  if (!existsSync16(path)) {
23579
24632
  if (isLink(path))
23580
- refuse8("is a link to nothing");
24633
+ refuse9("is a link to nothing");
23581
24634
  return null;
23582
24635
  }
23583
24636
  let text;
23584
24637
  try {
23585
- text = readFileSync28(path, "utf8");
24638
+ text = readFileSync29(path, "utf8");
23586
24639
  } catch (error) {
23587
- refuse8(`could not be read: ${messageOf(error)}`);
24640
+ refuse9(`could not be read: ${messageOf(error)}`);
23588
24641
  }
23589
24642
  let value;
23590
24643
  try {
23591
24644
  value = JSON.parse(text);
23592
24645
  } catch {
23593
- refuse8("is not JSON");
24646
+ refuse9("is not JSON");
23594
24647
  }
23595
24648
  return lockOf(value);
23596
24649
  }
23597
24650
  function writeProjectLock(root, version2) {
23598
- writeFileSync13(lockPath(root), projectLockText(version2), "utf8");
24651
+ writeFileSync14(lockPath(root), projectLockText(version2), "utf8");
23599
24652
  }
23600
24653
 
23601
24654
  // src/project/update-current.ts
@@ -23712,7 +24765,7 @@ function readCurrentRange(recorded, installed) {
23712
24765
  }
23713
24766
 
23714
24767
  // src/commands/update/current.ts
23715
- var USAGE43 = "rafa update current [--dry-run] [--yes]";
24768
+ var USAGE44 = "rafa update current [--dry-run] [--yes]";
23716
24769
  var APPLY_QUESTION = "Apply these changes? [y/N] ";
23717
24770
  var YES_ANSWERS5 = ["y", "yes"];
23718
24771
  function checkoutLockRoot(root, dir) {
@@ -23737,7 +24790,7 @@ function readSwitch5(context, name) {
23737
24790
  if (value === true || value === "true")
23738
24791
  return true;
23739
24792
  throw new CommandExit(1, `\u274C --${name} takes no value, and read "${String(value)}" as one
23740
- Usage: ${USAGE43}`);
24793
+ Usage: ${USAGE44}`);
23741
24794
  }
23742
24795
  function couldNotRun(problem2) {
23743
24796
  return new CommandExit(2, `FAIL \u2014 ${problem2}
@@ -23873,7 +24926,7 @@ function stopLine2(plan2, dryRun) {
23873
24926
  return null;
23874
24927
  }
23875
24928
  async function runUpdateCurrent(context, seams) {
23876
- expectNoArgument(context.args, USAGE43);
24929
+ expectNoArgument(context.args, USAGE44);
23877
24930
  const dryRun = readSwitch5(context, "dry-run");
23878
24931
  const yes = readSwitch5(context, "yes");
23879
24932
  const project = projectOf15(context);
@@ -23959,7 +25012,7 @@ var CORE_SUBJECTS = Object.freeze([
23959
25012
  { name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
23960
25013
  { name: "instinct", summary: "check an instincts directory; list, show, flag and promote its records" },
23961
25014
  { name: "release", summary: "read the release state of the project; settle the waiting fragments into a version; tag the commit that set it" },
23962
- { name: "board", summary: "list the boards with their owner, epic count, and which is current and home" },
25015
+ { name: "board", summary: "list the boards with their owner, epic count, and which is current and home; sync the GitHub project" },
23963
25016
  { name: "epic", summary: "show one epic's issues as the Roadmap table; create an epic; defer or promote it; move an issue to it; close it through the gate or cancel it" },
23964
25017
  { name: "claim", summary: "give up this device's claim on an issue; hand it to another store or withdraw the offer; accept a handover; take over a stale claim" },
23965
25018
  { name: "update", summary: "bring this project to the installed rafa; the other updates are in development" }
@@ -24025,6 +25078,7 @@ var CORE_COMMANDS = Object.freeze([
24025
25078
  settle_default,
24026
25079
  tag_default,
24027
25080
  list_default5,
25081
+ sync_default,
24028
25082
  show_default,
24029
25083
  new_default,
24030
25084
  defer_default,