@open-tomato/rafa 0.36.0 → 0.37.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.
@@ -6,6 +6,7 @@ import {
6
6
  BOARD_LISTING_LIMIT,
7
7
  BRANCH_PREFIX,
8
8
  CI_POLL_INTERVAL_MS,
9
+ CLAIMED_LABEL,
9
10
  DEFAULT_BASE_BRANCH,
10
11
  DEFAULT_CI_ATTEMPTS,
11
12
  DEFAULT_CI_TIMEOUT_MIN,
@@ -22,6 +23,7 @@ import {
22
23
  HORIZONS,
23
24
  HORIZON_LABEL_PREFIX,
24
25
  INDENT,
26
+ IN_DEVELOPMENT_LABEL,
25
27
  ISSUE_LIST_FLAGS,
26
28
  LABELS_ONLY_ACTIONS,
27
29
  LABELS_READS,
@@ -56,6 +58,7 @@ import {
56
58
  SKILL_USE_CLI_VERSION,
57
59
  SPEC_BLOCKED_LABEL,
58
60
  SPEC_EXTENSION,
61
+ SPEC_READY_LABEL,
59
62
  SPEC_TEMPLATE_PATH,
60
63
  STAGE_LABELS,
61
64
  TEMPLATES_DIRNAME,
@@ -81,8 +84,10 @@ import {
81
84
  boardChanged,
82
85
  boardId,
83
86
  boardOfEpic,
87
+ boardRepoLabel,
84
88
  bodySections,
85
89
  branchClaims,
90
+ branchName,
86
91
  buildReport,
87
92
  buildSettle,
88
93
  bundledAgentsDirectory,
@@ -143,6 +148,7 @@ import {
143
148
  expectTwoArguments,
144
149
  failingRows,
145
150
  fetchClaimBranches,
151
+ findReadinessGaps,
146
152
  fingerprintText,
147
153
  firstNowEpic,
148
154
  firstSentence,
@@ -169,6 +175,7 @@ import {
169
175
  hasDiverged,
170
176
  hasPlanFor,
171
177
  hasSpecBlockedLabel,
178
+ hasSpecLabel,
172
179
  horizonOf,
173
180
  idleText,
174
181
  invalidateRows,
@@ -204,6 +211,7 @@ import {
204
211
  nextOwnerGate,
205
212
  nextQuestion,
206
213
  noBoardListsLine,
214
+ normaliseReason,
207
215
  notesFileName,
208
216
  onTracker,
209
217
  openBoards,
@@ -299,7 +307,9 @@ import {
299
307
  replaceManifestVersion,
300
308
  report,
301
309
  reportFollowUps,
310
+ requireCompleteSpec,
302
311
  requireGhProvider,
312
+ requireNoLeak,
303
313
  resolveAgentRoster,
304
314
  resolveCreateSpec,
305
315
  resolveDefaultBoard,
@@ -364,7 +374,7 @@ import {
364
374
  worktreesHolding,
365
375
  writeSession,
366
376
  writeShippedFile
367
- } from "./index-2n2nedrk.js";
377
+ } from "./index-35ze56rz.js";
368
378
  import {
369
379
  PROBE_TIMEOUT_MS,
370
380
  loadPlanPrerequisites,
@@ -404,6 +414,7 @@ import {
404
414
  SQLITE_STORE_FILE_NAME,
405
415
  SchemaRefusedError,
406
416
  SessionRecordError,
417
+ TRUST_REFUSAL_EXIT,
407
418
  UnionSchemaMismatch,
408
419
  allRecords,
409
420
  checkbox,
@@ -411,9 +422,11 @@ import {
411
422
  copyEffortStore,
412
423
  countTasks,
413
424
  createFileSync,
425
+ createGhEditorLogin,
414
426
  createGhPermissions,
415
427
  createGhRunner,
416
428
  createGitRunner,
429
+ editorRefusalMessage,
417
430
  effortStoreDir,
418
431
  effortStoreFileName,
419
432
  errorCode,
@@ -424,6 +437,7 @@ import {
424
437
  findRecords,
425
438
  fix_schema_default,
426
439
  formatCounts,
440
+ ghBoardTrust,
427
441
  gitRemoteUrl,
428
442
  gitSaid,
429
443
  hop,
@@ -447,6 +461,8 @@ import {
447
461
  plural,
448
462
  positionFilePath,
449
463
  readAuthorTrust,
464
+ readBoardTrust,
465
+ readEditorTrust,
450
466
  readFrontmatter,
451
467
  readFrontmatterDocument,
452
468
  readHopRecord,
@@ -471,11 +487,12 @@ import {
471
487
  staleAgainst,
472
488
  toHeldRecords,
473
489
  trustRefusalClause,
490
+ trustRefusalMessage,
474
491
  version,
475
492
  versionLine,
476
493
  writeFrontmatter,
477
494
  writePositionFile
478
- } from "./index-qvbeqrx3.js";
495
+ } from "./index-1mxv55ec.js";
479
496
  import {
480
497
  CONFIG_DEFAULTS,
481
498
  CONFIG_FILE,
@@ -13277,6 +13294,615 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
13277
13294
  }
13278
13295
  var create_default = createIssueCreateCommand();
13279
13296
 
13297
+ // src/commands/issue/edit-run.ts
13298
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
13299
+ import { join as join29 } from "path";
13300
+
13301
+ // src/board/issue-edit-body.ts
13302
+ var TRAILING_WHITESPACE = /\s+$/u;
13303
+ function trimmedText(text, what) {
13304
+ const trimmed = text.replace(TRAILING_WHITESPACE, "");
13305
+ if (trimmed.trim() === "")
13306
+ throw new RangeError(`${what} holds nothing but whitespace`);
13307
+ return trimmed;
13308
+ }
13309
+ function renderUpdateHeading(now, reason) {
13310
+ const normalised = normaliseReason(reason);
13311
+ if (normalised === "")
13312
+ throw new RangeError("the reason holds nothing but whitespace");
13313
+ return `**Updated ${localDay(now)}, ${normalised}:**`;
13314
+ }
13315
+ function renderUpdateBlock(now, reason, text) {
13316
+ return `${renderUpdateHeading(now, reason)}
13317
+
13318
+ ${trimmedText(text, "the added text")}`;
13319
+ }
13320
+ function separatorAfter(body) {
13321
+ if (body === "" || body.endsWith(`
13322
+
13323
+ `))
13324
+ return "";
13325
+ return body.endsWith(`
13326
+ `) ? `
13327
+ ` : `
13328
+
13329
+ `;
13330
+ }
13331
+ function appendedBody(body, block) {
13332
+ return `${body}${separatorAfter(body)}${block}`;
13333
+ }
13334
+ function replacedBody(text) {
13335
+ return trimmedText(text, "the replacing text");
13336
+ }
13337
+ function carriesUpdateBlock(body, block) {
13338
+ const trimmed = body.replace(TRAILING_WHITESPACE, "");
13339
+ if (!trimmed.endsWith(block))
13340
+ return false;
13341
+ const before = trimmed.slice(0, trimmed.length - block.length);
13342
+ return before === "" || before.endsWith(`
13343
+ `);
13344
+ }
13345
+ function originalPartHolds(before, after, block) {
13346
+ if (!after.startsWith(before))
13347
+ return false;
13348
+ const added = after.slice(before.length).replace(TRAILING_WHITESPACE, "");
13349
+ return added === `${separatorAfter(before)}${block}`;
13350
+ }
13351
+
13352
+ // src/board/issue-edit-state.ts
13353
+ import { readdirSync as readdirSync3, statSync as statSync8 } from "fs";
13354
+ import { join as join28, resolve as resolve10 } from "path";
13355
+ function isClaimed(labels) {
13356
+ return labels.includes(CLAIMED_LABEL) || labels.includes(IN_DEVELOPMENT_LABEL);
13357
+ }
13358
+ function refused(refusal10, branch = null) {
13359
+ return { pass: false, refusal: refusal10, branch };
13360
+ }
13361
+ function readEditState(input) {
13362
+ if (!input.open)
13363
+ return refused("closed");
13364
+ if (isClaimed(input.labels)) {
13365
+ const branch = branchName(input.issue, input.title);
13366
+ return input.kind === "append" && input.whileInDevelopment ? { pass: true, staleCopy: true, branch } : refused("in-development", branch);
13367
+ }
13368
+ const planned = input.savedCopies.length > 0;
13369
+ if (input.kind === "append")
13370
+ return { pass: true, staleCopy: planned, branch: null };
13371
+ if (input.labels.includes(SPEC_READY_LABEL))
13372
+ return refused("spec-ready");
13373
+ if (planned)
13374
+ return refused("planned");
13375
+ return { pass: true, staleCopy: false, branch: null };
13376
+ }
13377
+ function refreshCommand(issue) {
13378
+ return `rafa plan create --issue=${String(issue)} --refresh`;
13379
+ }
13380
+ function staleCopyMessage(issue, branch) {
13381
+ const n = String(issue);
13382
+ const refresh = refreshCommand(issue);
13383
+ if (branch === null) {
13384
+ return `Issue #${n} has a saved copy this edit makes stale: ${refresh} rebuilds it.`;
13385
+ }
13386
+ return `Issue #${n} is under way on ${branch}: the loop plans from the copy it already has, ` + `and ${refresh} rebuilds that copy.`;
13387
+ }
13388
+ function editName(kind) {
13389
+ if (kind === "append")
13390
+ return "An append to";
13391
+ return kind === "replace" ? "A replace of the body of" : "A title change on";
13392
+ }
13393
+ function appendLine2(issue, extra) {
13394
+ return `rafa issue edit ${String(issue)} --append-file=<file> --reason=<text>${extra}`;
13395
+ }
13396
+ function editStateRefusalMessage(input, reading) {
13397
+ if (reading.pass)
13398
+ throw new TypeError("issue edit state: a passing reading has no refusal to name");
13399
+ const what = `${editName(input.kind)} issue #${String(input.issue)}`;
13400
+ switch (reading.refusal) {
13401
+ case "closed":
13402
+ return `${what} is refused: the issue is closed, and rafa edits open issues only.`;
13403
+ case "in-development": {
13404
+ const where = `a loop holds it on ${reading.branch ?? branchName(input.issue, input.title)}`;
13405
+ return input.kind === "append" ? `${what} is refused: ${where}; add --while-in-development for an amendment the loop must not miss.` : `${what} is refused: ${where}, and a spec under way changes by append: ${appendLine2(input.issue, " --while-in-development")}`;
13406
+ }
13407
+ case "spec-ready":
13408
+ return `${what} is refused: it carries ${SPEC_READY_LABEL}, and a ready spec changes by append: ${appendLine2(input.issue, "")}`;
13409
+ case "planned":
13410
+ return `${what} is refused: it has a saved copy under specs.dir, and a planned spec changes by append: ${appendLine2(input.issue, "")}`;
13411
+ }
13412
+ }
13413
+ function isSavedCopyName(name, issue) {
13414
+ const prefix = `${boardId(issue)}-`;
13415
+ return name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name.length > prefix.length + SPEC_EXTENSION.length && name !== notesFileName(issue);
13416
+ }
13417
+ function findSavedCopies(root, specsDir, issue) {
13418
+ const dir = resolve10(root, specsDir);
13419
+ let names;
13420
+ try {
13421
+ names = readdirSync3(dir);
13422
+ } catch (error) {
13423
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
13424
+ return [];
13425
+ throw error;
13426
+ }
13427
+ return names.filter((name) => isSavedCopyName(name, issue)).filter((name) => statSync8(join28(dir, name)).isFile()).sort().map((name) => join28(specsDir, name));
13428
+ }
13429
+
13430
+ // src/board/issue-edit-text.ts
13431
+ function addedTextSource(issue) {
13432
+ return `the text added to issue #${String(issue)}`;
13433
+ }
13434
+ function editedBodySource(issue) {
13435
+ return `issue #${String(issue)} as edited`;
13436
+ }
13437
+ function completenessApplies(labels, before) {
13438
+ return hasSpecLabel(labels) && findReadinessGaps(before).length === 0;
13439
+ }
13440
+ function requireEditText(input) {
13441
+ requireNoLeak(addedTextSource(input.issue), input.added);
13442
+ if (!completenessApplies(input.labels, input.before))
13443
+ return;
13444
+ requireCompleteSpec(editedBodySource(input.issue), input.after);
13445
+ }
13446
+
13447
+ // src/commands/issue/edit-run.ts
13448
+ var EDIT_USAGE = "rafa issue edit <n> (--append-file=<file> | --append=<text>) --reason=<text>" + " [--while-in-development] | (--replace-file=<file> [--title=<text>] | --title=<text>) [--dry-run]";
13449
+ var EDIT_REFUSAL_EXIT = TRUST_REFUSAL_EXIT;
13450
+ var EDIT_CONFLICT_EXIT = 1;
13451
+ var EDIT_SCRATCH_DIR = join29(".rafa", "scratch");
13452
+ var STDIN_PATH2 = "-";
13453
+ var ISSUE_NUMBER8 = /^[1-9]\d*$/u;
13454
+ var TRAILING_WHITESPACE2 = /\s+$/u;
13455
+ var ACCOUNTLESS_KIND = "local";
13456
+ var LOCAL_OWNERSHIP = "local: the local tracker holds no accounts, so nobody is asked about";
13457
+ var FLAG = Object.freeze({
13458
+ appendFile: "append-file",
13459
+ append: "append",
13460
+ reason: "reason",
13461
+ replaceFile: "replace-file",
13462
+ title: "title",
13463
+ dryRun: "dry-run",
13464
+ whileInDevelopment: "while-in-development"
13465
+ });
13466
+
13467
+ class EditRefusal extends CommandExit {
13468
+ refusal;
13469
+ readings;
13470
+ constructor(refusal10, sentence, readings, dryRun) {
13471
+ const passed = dryRun ? readings.map(renderReading) : [];
13472
+ super(EDIT_REFUSAL_EXIT, [...passed, `\u274C refused ${refusal10}: ${sentence}`].join(`
13473
+ `));
13474
+ this.refusal = refusal10;
13475
+ this.readings = readings;
13476
+ }
13477
+ }
13478
+ function editExitCode(outcome) {
13479
+ return outcome === "conflict" ? EDIT_CONFLICT_EXIT : 0;
13480
+ }
13481
+ function renderReading(reading) {
13482
+ return `${reading.gate}: ${reading.line}`;
13483
+ }
13484
+ function readSwitch3(flags, name) {
13485
+ const value = flags[name];
13486
+ if (value === undefined)
13487
+ return false;
13488
+ if (typeof value === "boolean")
13489
+ return value;
13490
+ if (value === "true" || value === "false")
13491
+ return value === "true";
13492
+ throw lineRefusal(`--${name} takes no value, and read "${value}" as one`, EDIT_USAGE);
13493
+ }
13494
+ function readEditIssue(args) {
13495
+ if (args.length > 1) {
13496
+ throw lineRefusal(`Expected one issue number, got ${String(args.length)}: ${args.join(" ")}`, EDIT_USAGE);
13497
+ }
13498
+ const word = args[0];
13499
+ if (word === undefined)
13500
+ throw lineRefusal("Name the issue number to edit", EDIT_USAGE);
13501
+ if (!ISSUE_NUMBER8.test(word)) {
13502
+ throw lineRefusal(`"${word}" is no issue number, which is a whole number from 1`, EDIT_USAGE);
13503
+ }
13504
+ return Number(word);
13505
+ }
13506
+ function notTogether(first, second, why) {
13507
+ return lineRefusal(`--${first} and --${second} cannot be used together: ${why}`, EDIT_USAGE);
13508
+ }
13509
+ function readEditFlags(flags) {
13510
+ return {
13511
+ appendFile: readNonBlankFlag(flags, FLAG.appendFile, EDIT_USAGE),
13512
+ append: readNonBlankFlag(flags, FLAG.append, EDIT_USAGE),
13513
+ reason: readNonBlankFlag(flags, FLAG.reason, EDIT_USAGE),
13514
+ replaceFile: readNonBlankFlag(flags, FLAG.replaceFile, EDIT_USAGE),
13515
+ title: readNonBlankFlag(flags, FLAG.title, EDIT_USAGE),
13516
+ whileInDevelopment: readSwitch3(flags, FLAG.whileInDevelopment),
13517
+ dryRun: readSwitch3(flags, FLAG.dryRun)
13518
+ };
13519
+ }
13520
+ function appendLine3(issue, read) {
13521
+ if (read.appendFile !== undefined && read.append !== undefined) {
13522
+ throw notTogether(FLAG.appendFile, FLAG.append, "name the added text one way");
13523
+ }
13524
+ if (read.title !== undefined)
13525
+ throw notTogether(FLAG.title, FLAG.append, "a title changes under the gates of a replace");
13526
+ if (read.reason === undefined) {
13527
+ throw lineRefusal(`--${FLAG.reason} is required with an append: --${FLAG.reason}=<text>`, EDIT_USAGE);
13528
+ }
13529
+ const source = read.appendFile === undefined ? { from: "line", text: read.append ?? "" } : { from: "file", flag: FLAG.appendFile, path: read.appendFile };
13530
+ return { ...lineOf(issue, read, "append", source), reason: read.reason, whileInDevelopment: read.whileInDevelopment };
13531
+ }
13532
+ function lineOf(issue, read, kind, source) {
13533
+ return { issue, kind, source, reason: null, title: read.title ?? null, whileInDevelopment: false, dryRun: read.dryRun };
13534
+ }
13535
+ function readEditLine(args, flags) {
13536
+ const issue = readEditIssue(args);
13537
+ const read = readEditFlags(flags);
13538
+ const appending = read.appendFile !== undefined || read.append !== undefined;
13539
+ if (appending && read.replaceFile !== undefined) {
13540
+ const appendFlag = read.appendFile === undefined ? FLAG.append : FLAG.appendFile;
13541
+ throw notTogether(appendFlag, FLAG.replaceFile, "an edit either appends or replaces");
13542
+ }
13543
+ if (appending)
13544
+ return appendLine3(issue, read);
13545
+ if (read.reason !== undefined) {
13546
+ throw lineRefusal(`--${FLAG.reason} goes with an append only: a replace and a title change take none`, EDIT_USAGE);
13547
+ }
13548
+ if (read.whileInDevelopment) {
13549
+ throw lineRefusal(`--${FLAG.whileInDevelopment} goes with an append only: a spec under way is never replaced or retitled`, EDIT_USAGE);
13550
+ }
13551
+ if (read.replaceFile !== undefined) {
13552
+ return lineOf(issue, read, "replace", { from: "file", flag: FLAG.replaceFile, path: read.replaceFile });
13553
+ }
13554
+ if (read.title !== undefined)
13555
+ return lineOf(issue, read, "title", null);
13556
+ throw lineRefusal(`Name the edit: --${FLAG.appendFile}, --${FLAG.append}, --${FLAG.replaceFile} or --${FLAG.title}`, EDIT_USAGE);
13557
+ }
13558
+ async function readEditText(source, stdin) {
13559
+ if (source.from === "line")
13560
+ return source.text;
13561
+ const where = source.path === STDIN_PATH2 ? "standard input" : `"${source.path}"`;
13562
+ let text;
13563
+ try {
13564
+ text = source.path === STDIN_PATH2 ? await stdin() : await Bun.file(source.path).text();
13565
+ } catch (error) {
13566
+ throw lineRefusal(`--${source.flag} cannot read ${where}: ${messageOf(error)}`, EDIT_USAGE);
13567
+ }
13568
+ if (text.trim() === "")
13569
+ throw lineRefusal(`--${source.flag} read nothing but whitespace from ${where}`, EDIT_USAGE);
13570
+ return text;
13571
+ }
13572
+ async function readEditRequest(line, stdin) {
13573
+ const { source, ...rest } = line;
13574
+ if (source === null)
13575
+ return { ...rest, text: null };
13576
+ return { ...rest, text: await readEditText(source, stdin) };
13577
+ }
13578
+ function requireEditingTracker(tracker) {
13579
+ if (holdsEditing(tracker))
13580
+ return tracker;
13581
+ throw new CommandExit(1, `\u274C The ${tracker.kind} tracker cannot edit an issue: it reads and writes no issue body.`);
13582
+ }
13583
+ function holdsEditing(tracker) {
13584
+ return tracker.editable !== undefined && tracker.edit !== undefined;
13585
+ }
13586
+ function planEdit(request, found, now) {
13587
+ const titled = request.title === null ? {} : { title: request.title };
13588
+ if (request.kind === "append") {
13589
+ const block = renderUpdateBlock(now, request.reason ?? "", request.text ?? "");
13590
+ const already = carriesUpdateBlock(found.body, block);
13591
+ const after = already ? found.body : appendedBody(found.body, block);
13592
+ return { added: request.text ?? "", shown: block, block, after, already, change: { body: after } };
13593
+ }
13594
+ if (request.kind === "replace") {
13595
+ const body = replacedBody(request.text ?? "");
13596
+ const added = [request.title, body].filter((part) => part !== null).join(`
13597
+
13598
+ `);
13599
+ return { added, shown: added, block: null, after: body, already: false, change: { body, ...titled } };
13600
+ }
13601
+ const title = request.title ?? "";
13602
+ return { added: title, shown: title, block: null, after: found.body, already: false, change: { title } };
13603
+ }
13604
+ function trustedLine(reading, repo) {
13605
+ return reading.source === "allow-list" ? `${reading.login}, listed in board.trustedAuthors` : `${reading.login}, who has write access to ${repo}`;
13606
+ }
13607
+ function gatesFor(request) {
13608
+ const readings = [];
13609
+ return {
13610
+ readings,
13611
+ pass: (gate, line) => {
13612
+ readings.push({ gate, line });
13613
+ },
13614
+ refuse: (refusal10, sentence) => {
13615
+ throw new EditRefusal(refusal10, sentence, [...readings], request.dryRun);
13616
+ }
13617
+ };
13618
+ }
13619
+ async function weighOwnership(options, found, gates) {
13620
+ const { request, tracker } = options;
13621
+ if (tracker.kind === ACCOUNTLESS_KIND) {
13622
+ gates.pass("editor", LOCAL_OWNERSHIP);
13623
+ gates.pass("author", LOCAL_OWNERSHIP);
13624
+ return;
13625
+ }
13626
+ const trust = options.trust();
13627
+ const item = { kind: "issue", number: request.issue, repo: trust.repo };
13628
+ const editor = await readEditorTrust(trust, options.editor);
13629
+ if (!editor.trusted || editor.reading === null) {
13630
+ gates.refuse(editor.refusal ?? "ownership-unknown", editorRefusalMessage(item, editor));
13631
+ }
13632
+ gates.pass("editor", trustedLine(editor.reading, trust.repo));
13633
+ const author = await readBoardTrust(trust, found.author);
13634
+ if (!author.trusted) {
13635
+ const refusal10 = author.refusal === "lookup-failed" ? "ownership-unknown" : "ownership";
13636
+ gates.refuse(refusal10, trustRefusalMessage(item, author));
13637
+ }
13638
+ gates.pass("author", `#${String(request.issue)} was opened by ${trustedLine(author, trust.repo)}`);
13639
+ }
13640
+ function weighText(request, found, planned, gates) {
13641
+ try {
13642
+ requireEditText({
13643
+ issue: request.issue,
13644
+ labels: found.labels,
13645
+ before: found.body,
13646
+ added: planned.added,
13647
+ after: planned.after
13648
+ });
13649
+ } catch (error) {
13650
+ if (error instanceof CommandExit)
13651
+ gates.refuse("text", error.message);
13652
+ throw error;
13653
+ }
13654
+ const complete = completenessApplies(found.labels, found.body) ? ", and the edited body still fills the spec template" : "";
13655
+ gates.pass("text", `the added text names no machine path or credential${complete}`);
13656
+ }
13657
+ function stateLine(input, reading) {
13658
+ if (reading.branch !== null) {
13659
+ return `a loop holds it on ${reading.branch}; --while-in-development lets the append through`;
13660
+ }
13661
+ if (input.savedCopies.length > 0)
13662
+ return `planned, its saved copy at ${input.savedCopies.join(", ")}`;
13663
+ return input.labels.length === 0 ? "open, with no plan and no loop building from it" : `open, labelled ${input.labels.join(", ")}, with no saved copy and no loop building from it`;
13664
+ }
13665
+ function weighState(options, found, gates) {
13666
+ const { request } = options;
13667
+ const input = {
13668
+ issue: request.issue,
13669
+ title: found.title,
13670
+ open: found.open,
13671
+ labels: found.labels,
13672
+ savedCopies: findSavedCopies(options.root, options.specsDir, request.issue),
13673
+ kind: request.kind,
13674
+ whileInDevelopment: request.whileInDevelopment
13675
+ };
13676
+ const reading = readEditState(input);
13677
+ if (!reading.pass)
13678
+ gates.refuse(reading.refusal, editStateRefusalMessage(input, reading));
13679
+ gates.pass("state", stateLine(input, reading));
13680
+ return reading;
13681
+ }
13682
+ function normalised(text) {
13683
+ return text.replace(/\r\n/gu, `
13684
+ `).replace(TRAILING_WHITESPACE2, "");
13685
+ }
13686
+ function writeHeld(found, planned, reread) {
13687
+ const { change } = planned;
13688
+ if (change.title !== undefined && reread.title !== change.title)
13689
+ return false;
13690
+ const before = found.body.replace(/\r\n/gu, `
13691
+ `);
13692
+ const after = reread.body.replace(/\r\n/gu, `
13693
+ `);
13694
+ if (planned.block !== null)
13695
+ return originalPartHolds(before, after, normalised(planned.block));
13696
+ return normalised(after) === normalised(change.body ?? found.body);
13697
+ }
13698
+ function conflictPaths(issue, stamp, attempt3) {
13699
+ const suffix = attempt3 === 1 ? "" : `-${String(attempt3)}`;
13700
+ const name = (side) => join29(EDIT_SCRATCH_DIR, `${boardId(issue)}-edit-${side}-${stamp}${suffix}.md`);
13701
+ return { before: name("before"), after: name("after") };
13702
+ }
13703
+ function saveConflictBodies(root, issue, now, before, after) {
13704
+ mkdirSync5(join29(root, EDIT_SCRATCH_DIR), { recursive: true });
13705
+ const stamp = fileStamp(now);
13706
+ let attempt3 = 1;
13707
+ let paths = conflictPaths(issue, stamp, attempt3);
13708
+ while (existsSync9(join29(root, paths.before)) || existsSync9(join29(root, paths.after))) {
13709
+ attempt3 += 1;
13710
+ paths = conflictPaths(issue, stamp, attempt3);
13711
+ }
13712
+ writeFileSync6(join29(root, paths.before), before, { flag: "wx" });
13713
+ writeFileSync6(join29(root, paths.after), after, { flag: "wx" });
13714
+ return paths;
13715
+ }
13716
+ function heldOutcome(request, state) {
13717
+ if (request.kind !== "append")
13718
+ return "replaced";
13719
+ return state.pass && state.staleCopy ? "stale-copy" : "appended";
13720
+ }
13721
+ function outcomeMessage(request, outcome, branch, name) {
13722
+ switch (outcome) {
13723
+ case "appended":
13724
+ return `Appended an update to ${name}; the original part is unchanged.`;
13725
+ case "stale-copy":
13726
+ return `Appended an update to ${name}. ${staleCopyMessage(request.issue, branch)}`;
13727
+ case "already":
13728
+ return `${name} already ends with this update; nothing was written.`;
13729
+ case "replaced": {
13730
+ if (request.text === null)
13731
+ return `Changed the title of ${name}.`;
13732
+ return request.title === null ? `Replaced the body of ${name}.` : `Replaced the body and the title of ${name}.`;
13733
+ }
13734
+ case "conflict":
13735
+ throw new TypeError("issue edit: a conflict is worded with the paths it saved");
13736
+ }
13737
+ }
13738
+ function dryRunMessage(issue, due, branch) {
13739
+ const stale = due === "stale-copy" ? ` ${staleCopyMessage(issue, branch)}` : "";
13740
+ return `--dry-run: nothing was written; the edit would answer ${due}.${stale}`;
13741
+ }
13742
+ function conflictMessage(name, scratch) {
13743
+ return `The edit was written, but ${name} read back otherwise: somebody changed it around the write. ` + `The body read before is at ${scratch.before} and the body read after at ${scratch.after}; ` + "compare the two and make the edit again over the body as it stands.";
13744
+ }
13745
+ function report2(request, planned, readings, fields) {
13746
+ return Object.freeze({
13747
+ ...fields,
13748
+ issue: request.issue,
13749
+ exitCode: editExitCode(fields.outcome),
13750
+ dryRun: request.dryRun,
13751
+ added: planned.shown,
13752
+ readings,
13753
+ refresh: fields.outcome === "stale-copy" ? refreshCommand(request.issue) : null,
13754
+ scratch: null
13755
+ });
13756
+ }
13757
+ async function runIssueEdit(options) {
13758
+ const { request, tracker } = options;
13759
+ const ref8 = issueRef(tracker, String(request.issue));
13760
+ const name = issueName(ref8);
13761
+ const now = options.now();
13762
+ const found = await onTracker(tracker, `read ${name}`, () => tracker.editable(ref8));
13763
+ const gates = gatesFor(request);
13764
+ await weighOwnership(options, found, gates);
13765
+ const planned = planEdit(request, found, now);
13766
+ weighText(request, found, planned, gates);
13767
+ const state = weighState(options, found, gates);
13768
+ const { branch } = state;
13769
+ function done(outcome, written, message) {
13770
+ return report2(request, planned, gates.readings, { outcome, written, branch, message });
13771
+ }
13772
+ const due = planned.already ? "already" : heldOutcome(request, state);
13773
+ if (request.dryRun)
13774
+ return done(due, false, dryRunMessage(request.issue, due, branch));
13775
+ if (planned.already)
13776
+ return done("already", false, outcomeMessage(request, "already", branch, name));
13777
+ await onTracker(tracker, `edit ${name}`, () => tracker.edit(ref8, planned.change));
13778
+ const reread = await onTracker(tracker, `read ${name} again after the edit was written`, () => tracker.editable(ref8));
13779
+ if (writeHeld(found, planned, reread))
13780
+ return done(due, true, outcomeMessage(request, due, branch, name));
13781
+ const scratch = saveConflictBodies(options.root, request.issue, now, found.body, reread.body);
13782
+ return Object.freeze({ ...done("conflict", true, conflictMessage(name, scratch)), scratch });
13783
+ }
13784
+ function renderEditReport(made) {
13785
+ if (!made.dryRun)
13786
+ return [made.message];
13787
+ return [...made.readings.map(renderReading), "added:", made.added, made.message];
13788
+ }
13789
+ async function editIssue2(context, seams) {
13790
+ const line = readEditLine(context.args, context.flags);
13791
+ const request = await readEditRequest(line, seams.stdin ?? (() => Bun.stdin.text()));
13792
+ const project = issueProject(context);
13793
+ const { tracker, data } = await resolveIssueTracker(context, seams);
13794
+ const editing = requireEditingTracker(tracker);
13795
+ const config = issueSubjectConfig(project, () => {
13796
+ return;
13797
+ });
13798
+ const gh = seams.gh ?? createGhRunner({ cwd: project.root });
13799
+ const openGit = seams.openGit ?? createGitRunner;
13800
+ const made = await runIssueEdit({
13801
+ request,
13802
+ tracker: editing,
13803
+ root: project.root,
13804
+ specsDir: config.specsDir,
13805
+ trust: () => ghBoardTrust({
13806
+ gh,
13807
+ trustedAuthors: config.boardTrustedAuthors,
13808
+ repo: boardRepoLabel(openGit(project.root))
13809
+ }),
13810
+ editor: createGhEditorLogin({ gh }),
13811
+ now: seams.now ?? (() => new Date)
13812
+ });
13813
+ return Object.freeze({ ...made, tracker: data });
13814
+ }
13815
+
13816
+ // src/commands/issue/edit.ts
13817
+ var DEFAULT_EDIT_SEAMS = Object.freeze({});
13818
+ function createIssueEditCommand(seams = DEFAULT_EDIT_SEAMS) {
13819
+ const command = {
13820
+ name: "issue edit",
13821
+ subject: "issue",
13822
+ action: "edit",
13823
+ summary: "append a dated update to an issue body, or replace its body or title, after four gates",
13824
+ description: "Edits one issue on the tracker the chain lands on, after four gates weighed in order, the first" + " refusal ending the run with nothing written and exit code 2: the account editing must have write" + " access or be in board.trustedAuthors; so must the issue's author; the text added must leak nothing," + " and a complete spec must stay complete; and the issue must be open, its labels and saved spec copies" + " allowing the edit. An append (`--append-file` or `--append`, with `--reason`) adds a block" + " `**Updated <YYYY-MM-DD>, <reason>:**` below the body, leaving the original part byte for byte; the" + " same block already closing the body answers `already`. `--replace-file` replaces the body and" + " `--title` the title. After the write the issue is read again: a body that no longer holds what was" + " written answers `conflict` with exit code 1, the bodies read before and after saved under" + " `.rafa/scratch/`. An append on an issue already planned answers `stale-copy` and names" + " `rafa plan create --issue=<n> --refresh`, which it never runs. `--dry-run` prints each gate's" + " reading and the added part and writes nothing. With `--output=json` the report and the tracker are" + " the data of the terminal result event.",
13825
+ args: [
13826
+ {
13827
+ name: "n",
13828
+ description: "The issue number on the tracker.",
13829
+ type: "string",
13830
+ required: true
13831
+ }
13832
+ ],
13833
+ flags: [
13834
+ {
13835
+ name: "append-file",
13836
+ description: "A file holding the text to append, read whole; `-` reads standard input." + " Needs --reason. Not with --append, --replace-file or --title.",
13837
+ type: "string"
13838
+ },
13839
+ {
13840
+ name: "append",
13841
+ description: "The text to append. Needs --reason. Not with --append-file, --replace-file or --title.",
13842
+ type: "string"
13843
+ },
13844
+ {
13845
+ name: "reason",
13846
+ description: "Why the update was made, written into its heading. Required with an append, refused otherwise.",
13847
+ type: "string"
13848
+ },
13849
+ {
13850
+ name: "replace-file",
13851
+ description: "A file holding the new body, read whole; `-` reads standard input. Not with an append.",
13852
+ type: "string"
13853
+ },
13854
+ {
13855
+ name: "title",
13856
+ description: "The new title, alone or beside --replace-file. Not with an append.",
13857
+ type: "string"
13858
+ },
13859
+ {
13860
+ name: "while-in-development",
13861
+ description: "Allows an append on an issue a loop has claimed, answering stale-copy. Only with an append.",
13862
+ type: "boolean"
13863
+ },
13864
+ {
13865
+ name: "dry-run",
13866
+ description: "Prints the added part and each gate's reading and writes nothing; a refusal still exits 2.",
13867
+ type: "boolean"
13868
+ }
13869
+ ],
13870
+ examples: [
13871
+ {
13872
+ cmd: 'rafa issue edit 57 --append-file=update.md --reason="scope narrowed"',
13873
+ note: "Appends what update.md holds to #57 below a dated heading naming the reason."
13874
+ },
13875
+ {
13876
+ cmd: 'rafa issue edit 57 --append="Also covers --dry-run." --reason=clarified --dry-run',
13877
+ note: "Prints each gate's reading and the block it would append, and writes nothing."
13878
+ },
13879
+ {
13880
+ cmd: 'rafa issue edit 57 --title="Faster plan show"',
13881
+ note: "Changes the title of #57 and leaves its body alone."
13882
+ },
13883
+ {
13884
+ cmd: "rafa issue edit 57 --replace-file=spec.md --output=json",
13885
+ note: "Replaces the body of #57; the report and the tracker are the data of the result event."
13886
+ }
13887
+ ],
13888
+ outputs: ["text", "json"],
13889
+ run: async (context) => {
13890
+ const made = await editIssue2(context, seams);
13891
+ if (made.exitCode !== 0)
13892
+ throw new CommandExit(made.exitCode, renderEditReport(made).join(`
13893
+ `));
13894
+ if (context.outputMode === "json") {
13895
+ context.output.result(made);
13896
+ return;
13897
+ }
13898
+ for (const line of renderEditReport(made))
13899
+ context.output.info(line);
13900
+ }
13901
+ };
13902
+ return Object.freeze(command);
13903
+ }
13904
+ var edit_default = createIssueEditCommand();
13905
+
13280
13906
  // src/commands/issue/move.ts
13281
13907
  var USAGE23 = "rafa issue move <id> <state>";
13282
13908
  async function moveIssue(context, seams) {
@@ -14201,7 +14827,7 @@ function poll(following, before, context, seams) {
14201
14827
  }
14202
14828
  return { cursor, found: null };
14203
14829
  }
14204
- function lineOf(found, following, session, reading) {
14830
+ function lineOf2(found, following, session, reading) {
14205
14831
  if (found.event !== null)
14206
14832
  return eventLine(found.event);
14207
14833
  if (found.reason === "exit")
@@ -14222,7 +14848,7 @@ function resultOf5(found, following, seams) {
14222
14848
  return {
14223
14849
  reason: found.reason,
14224
14850
  exitCode: found.reason === "timeout" ? WAIT_TIMEOUT_EXIT : waitExitCode(found.reason),
14225
- line: lineOf(found, following, session, reading),
14851
+ line: lineOf2(found, following, session, reading),
14226
14852
  event: found.event,
14227
14853
  session,
14228
14854
  awakeMinutes: reading.awakeMinutes,
@@ -14520,7 +15146,7 @@ var STATE_MARK = "\uD83D\uDCCD";
14520
15146
  var PROPOSAL_MARK = "\uD83D\uDC49";
14521
15147
  var STOP_MARK = "\u23F9";
14522
15148
  var MAX_ACTIONS = 12;
14523
- function stateLine(state) {
15149
+ function stateLine2(state) {
14524
15150
  return `${STATE_MARK} ${state.reading}.`;
14525
15151
  }
14526
15152
  function proposalLine(state, invocation) {
@@ -14677,7 +15303,7 @@ async function playTurn(options, state, previous) {
14677
15303
  for (const problem2 of state.problems)
14678
15304
  warn3(problem2);
14679
15305
  const invocation = actionInvocation(state, passedOptions(roadmap));
14680
- info2(stateLine(state));
15306
+ info2(stateLine2(state));
14681
15307
  info2(proposalLine(state, invocation));
14682
15308
  const halt = (stop, over = {}) => ({ stop, step: stepOf2(state, invocation, over) });
14683
15309
  if (dryRun !== null)
@@ -14703,7 +15329,7 @@ async function runNextChain(options) {
14703
15329
  let steps = [];
14704
15330
  let hops = [];
14705
15331
  let previous = null;
14706
- const report2 = (stop) => Object.freeze({
15332
+ const report3 = (stop) => Object.freeze({
14707
15333
  steps: Object.freeze([...steps]),
14708
15334
  stop,
14709
15335
  ...roadmap === undefined ? {} : { hops: Object.freeze([...hops]) }
@@ -14712,7 +15338,7 @@ async function runNextChain(options) {
14712
15338
  const line = stopLine(stop, state, ceiling, dryRun, roadmap !== undefined);
14713
15339
  if (line !== null)
14714
15340
  info2(line);
14715
- return report2(stop);
15341
+ return report3(stop);
14716
15342
  };
14717
15343
  const turn = async (state, before) => {
14718
15344
  const end = await playTurn(options, state, before);
@@ -14752,8 +15378,8 @@ async function runStateAction(context, sources, state, passed) {
14752
15378
  if (outcome.said !== "")
14753
15379
  context.output.info(outcome.said);
14754
15380
  }
14755
- function endOfEpic(context, sources, report2, epic) {
14756
- if (epic === null || report2.steps.at(-1)?.state !== "nothing-left")
15381
+ function endOfEpic(context, sources, report3, epic) {
15382
+ if (epic === null || report3.steps.at(-1)?.state !== "nothing-left")
14757
15383
  return;
14758
15384
  const release = sources.release();
14759
15385
  if (release.problem !== null)
@@ -14787,7 +15413,7 @@ async function runNext(context, seams) {
14787
15413
  const prompter = lazyPrompter(openPrompter);
14788
15414
  const dry = watchDryEpic();
14789
15415
  try {
14790
- const report2 = await runNextChain({
15416
+ const report3 = await runNextChain({
14791
15417
  read: () => readNextState(dry.watch(sources.answer())),
14792
15418
  run: (state) => runStateAction(context, sources, state, {
14793
15419
  roadmap: sources.roadmap !== undefined,
@@ -14808,8 +15434,8 @@ async function runNext(context, seams) {
14808
15434
  }),
14809
15435
  ...roadmap ? { roadmap: { afterLoop: () => homeAfterLoop(context, sources), claimAhead } } : {}
14810
15436
  });
14811
- endOfEpic(context, sources, report2, dry.last());
14812
- return report2;
15437
+ endOfEpic(context, sources, report3, dry.last());
15438
+ return report3;
14813
15439
  } finally {
14814
15440
  prompter.close();
14815
15441
  }
@@ -14871,9 +15497,9 @@ function createNextCommand(seams = DEFAULT_NEXT_SEAMS) {
14871
15497
  outputs: ["text", "json"],
14872
15498
  spends: { when: "through", what: "when the step it runs is one of the above" },
14873
15499
  run: async (context) => {
14874
- const report2 = await runNext(context, seams);
15500
+ const report3 = await runNext(context, seams);
14875
15501
  if (context.outputMode === "json")
14876
- context.output.result(report2);
15502
+ context.output.result(report3);
14877
15503
  }
14878
15504
  };
14879
15505
  return Object.freeze(command);
@@ -14991,7 +15617,7 @@ var create_default2 = endingWith(wrapped2);
14991
15617
 
14992
15618
  // src/commands/plan/risk.ts
14993
15619
  import { readFileSync as readFileSync16 } from "fs";
14994
- import { resolve as resolve10 } from "path";
15620
+ import { resolve as resolve11 } from "path";
14995
15621
  var COMMAND6 = "rafa plan risk";
14996
15622
  var USAGE33 = "rafa plan risk [<plan>] [--strict]";
14997
15623
  var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
@@ -15004,7 +15630,7 @@ function riskPlanPath(root, planDir, typed) {
15004
15630
  return path;
15005
15631
  if (typed !== null)
15006
15632
  throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
15007
- const lookedFor = [...new Set([resolve10(root, planDir, DEFAULT_PLAN_FILE), resolve10(root, DEFAULT_PLAN_FILE)])];
15633
+ const lookedFor = [...new Set([resolve11(root, planDir, DEFAULT_PLAN_FILE), resolve11(root, DEFAULT_PLAN_FILE)])];
15008
15634
  throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
15009
15635
  Usage: ${USAGE33}`);
15010
15636
  }
@@ -15013,8 +15639,8 @@ function riskLine(finding) {
15013
15639
  const place2 = finding.file === undefined ? "" : `${finding.file}${line}: `;
15014
15640
  return `${place2}${finding.kind}: ${finding.text}`;
15015
15641
  }
15016
- function strictRefusal(report2) {
15017
- return `\u274C ${report2.plan}: ${plural(report2.total.high, "high finding")}; --strict refuses a plan with any`;
15642
+ function strictRefusal(report3) {
15643
+ return `\u274C ${report3.plan}: ${plural(report3.total.high, "high finding")}; --strict refuses a plan with any`;
15018
15644
  }
15019
15645
  async function readRisk(path, project, config, context, seams) {
15020
15646
  const makeRunners = seams.runners ?? spawningRunners;
@@ -15076,24 +15702,24 @@ function createPlanRiskCommand(seams = {}) {
15076
15702
  context.output.warn(message);
15077
15703
  });
15078
15704
  const path = riskPlanPath(project.root, config.planDir, typed);
15079
- const report2 = await readRisk(path, project, config, context, seams);
15080
- const refused = strict && report2.total.high > 0;
15705
+ const report3 = await readRisk(path, project, config, context, seams);
15706
+ const refused2 = strict && report3.total.high > 0;
15081
15707
  if (context.outputMode === "json") {
15082
- if (!refused) {
15083
- context.output.result(report2);
15708
+ if (!refused2) {
15709
+ context.output.result(report3);
15084
15710
  return;
15085
15711
  }
15086
- for (const finding of report2.findings) {
15712
+ for (const finding of report3.findings) {
15087
15713
  if (finding.level === "high")
15088
15714
  context.output.error(riskLine(finding));
15089
15715
  }
15090
15716
  } else {
15091
- for (const line of renderRiskText(report2).split(`
15717
+ for (const line of renderRiskText(report3).split(`
15092
15718
  `))
15093
15719
  context.output.info(line);
15094
15720
  }
15095
- if (refused)
15096
- throw new CommandExit(1, strictRefusal(report2));
15721
+ if (refused2)
15722
+ throw new CommandExit(1, strictRefusal(report3));
15097
15723
  }
15098
15724
  };
15099
15725
  return Object.freeze(command);
@@ -15102,7 +15728,7 @@ var risk_default = createPlanRiskCommand();
15102
15728
 
15103
15729
  // src/commands/plan/show.ts
15104
15730
  import { readFileSync as readFileSync17 } from "fs";
15105
- import { join as join28 } from "path";
15731
+ import { join as join30 } from "path";
15106
15732
  var USAGE34 = "rafa plan show <stub> [--tracker]";
15107
15733
  var COMMAND7 = "rafa plan show";
15108
15734
  function readTrackerFlag(value) {
@@ -15120,9 +15746,9 @@ function showPlan(plans, stub, tracker) {
15120
15746
  throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
15121
15747
  }
15122
15748
  const name = planFileName(stub, tracker);
15123
- const file = join28(plans.path, name);
15749
+ const file = join30(plans.path, name);
15124
15750
  if (!isFile(file))
15125
- throw new CommandExit(1, missingText(stub, tracker, join28(plans.label, name)));
15751
+ throw new CommandExit(1, missingText(stub, tracker, join30(plans.label, name)));
15126
15752
  const model = parsePlan(readFileSync17(file, "utf8"));
15127
15753
  const { header } = model;
15128
15754
  return {
@@ -15146,7 +15772,7 @@ function taskLine(task) {
15146
15772
  return ` ${checkbox(task.status)} ${task.text}`;
15147
15773
  }
15148
15774
  function renderShownPlan(shown, dirLabel) {
15149
- const relative8 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
15775
+ const relative8 = join30(dirLabel, planFileName(shown.stub, shown.tracker));
15150
15776
  const fileLabel = shown.tracker ? "Tracker" : "File";
15151
15777
  const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative8}`];
15152
15778
  if (shown.header.issue !== null)
@@ -15700,7 +16326,7 @@ function freedIssueLine(issue) {
15700
16326
  const title = issue.title.trim();
15701
16327
  return title === "" ? `${INDENT}#${String(issue.number)}` : `${INDENT}#${String(issue.number)} ${title}`;
15702
16328
  }
15703
- function report2(closed, freed, problem2) {
16329
+ function report3(closed, freed, problem2) {
15704
16330
  return Object.freeze({ relationships: "native", closed, freed: Object.freeze(freed), problem: problem2 });
15705
16331
  }
15706
16332
  async function readFreed(gh, closed) {
@@ -15721,14 +16347,14 @@ async function freedAfterMerge(options) {
15721
16347
  } catch (error) {
15722
16348
  const problem2 = messageOf(error);
15723
16349
  warn3(freedProblemLine(problem2));
15724
- return report2(closed, [], problem2);
16350
+ return report3(closed, [], problem2);
15725
16351
  }
15726
16352
  if (freed.length > 0) {
15727
16353
  info2(freedHeaderLine(closed, freed.length));
15728
16354
  for (const issue of freed)
15729
16355
  info2(freedIssueLine(issue));
15730
16356
  }
15731
- return report2(closed, freed, null);
16357
+ return report3(closed, freed, null);
15732
16358
  }
15733
16359
 
15734
16360
  // src/commands/pr/merge-guard.ts
@@ -15785,18 +16411,18 @@ async function guardBeforeMerge(step) {
15785
16411
  const answer2 = answerOf(reading);
15786
16412
  const reaction = guardReaction(reading, settings);
15787
16413
  const lines = guardLines(reading, settings.releaseVersionFile);
15788
- const report3 = { answer: answer2, reaction, lines };
16414
+ const report4 = { answer: answer2, reaction, lines };
15789
16415
  if (reaction === "refuse")
15790
16416
  throw guardRefusal(refusalHead(step.detail.number, answer2), lines);
15791
16417
  if (reaction === "ask")
15792
- return { report: report3, go: await askGuard(step, answer2, lines) };
16418
+ return { report: report4, go: await askGuard(step, answer2, lines) };
15793
16419
  printLines(step, reaction, lines);
15794
- return { report: report3, go: true };
16420
+ return { report: report4, go: true };
15795
16421
  }
15796
16422
 
15797
16423
  // src/commands/pr/merge-loop-worktree.ts
15798
- import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync5, realpathSync as realpathSync3, statSync as statSync8 } from "fs";
15799
- import { dirname as dirname6, isAbsolute as isAbsolute6, join as join29, relative as relative8, resolve as resolve11, sep as sep4 } from "path";
16424
+ import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync6, realpathSync as realpathSync3, statSync as statSync9 } from "fs";
16425
+ import { dirname as dirname6, isAbsolute as isAbsolute6, join as join31, relative as relative8, resolve as resolve12, sep as sep4 } from "path";
15800
16426
  var REFUSAL_PREFIX = "rafa pr merge refuses";
15801
16427
  var INDENT4 = " ";
15802
16428
  var LIVE_STATES = new Set(["running", "paused"]);
@@ -15804,9 +16430,9 @@ function defaultLoopWorktreeSeams(mainCheckout) {
15804
16430
  return {
15805
16431
  git: createGitRunner(mainCheckout),
15806
16432
  gitAt: createGitRunner,
15807
- modifiedAt: (path) => statSync8(path, { throwIfNoEntry: false })?.mtime ?? null,
16433
+ modifiedAt: (path) => statSync9(path, { throwIfNoEntry: false })?.mtime ?? null,
15808
16434
  copyFile: (from, to) => {
15809
- mkdirSync5(dirname6(to), { recursive: true });
16435
+ mkdirSync6(dirname6(to), { recursive: true });
15810
16436
  copyFileSync3(from, to);
15811
16437
  },
15812
16438
  realPath: (path) => {
@@ -15837,11 +16463,11 @@ function readLoopHolder(seams, reading) {
15837
16463
  };
15838
16464
  }
15839
16465
  function freeLoopWorktree(seams, reading, holder) {
15840
- const into = resolve11(reading.mainCheckout, reading.planDir);
16466
+ const into = resolve12(reading.mainCheckout, reading.planDir);
15841
16467
  const files = [];
15842
16468
  for (const name of planFileNames(holder.stub)) {
15843
- const from = resolve11(holder.path, reading.planDir, name);
15844
- const to = join29(into, name);
16469
+ const from = resolve12(holder.path, reading.planDir, name);
16470
+ const to = join31(into, name);
15845
16471
  try {
15846
16472
  files.push({ name, from, to, outcome: copyPlanFile(seams, from, to) });
15847
16473
  } catch (error) {
@@ -17633,8 +18259,8 @@ function describeAge(updatedAt, now) {
17633
18259
  return `${plural3(hours, "hour", "hours")} ago`;
17634
18260
  return `${plural3(Math.floor(age / DAY_MS2), "day", "days")} ago`;
17635
18261
  }
17636
- function triageCommand(number, resolve12 = false) {
17637
- return resolve12 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
18262
+ function triageCommand(number, resolve13 = false) {
18263
+ return resolve13 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
17638
18264
  }
17639
18265
  function joinNumbers(pulls) {
17640
18266
  const names = pulls.map((pull2) => `#${pull2.number}`);
@@ -17645,14 +18271,14 @@ function joinNumbers(pulls) {
17645
18271
  function byNumber2(pulls) {
17646
18272
  return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
17647
18273
  }
17648
- function skipped(pull2, now, resolve12) {
18274
+ function skipped(pull2, now, resolve13) {
17649
18275
  return Object.freeze({
17650
18276
  number: pull2.number,
17651
18277
  title: pull2.title,
17652
18278
  updatedAt: pull2.updatedAt,
17653
18279
  age: describeAge(pull2.updatedAt, now),
17654
18280
  fresh: isFreshCandidate(pull2.updatedAt, now),
17655
- command: triageCommand(pull2.number, resolve12)
18281
+ command: triageCommand(pull2.number, resolve13)
17656
18282
  });
17657
18283
  }
17658
18284
  function skippedLine(one) {
@@ -17696,15 +18322,15 @@ function listReading(rest, count) {
17696
18322
  return listing("list", Object.freeze([]), rest, headline);
17697
18323
  }
17698
18324
  function selectTriagePullRequests(input) {
17699
- const resolve12 = input.resolve === true;
18325
+ const resolve13 = input.resolve === true;
17700
18326
  const { current, now } = input;
17701
18327
  if (current !== null) {
17702
18328
  return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
17703
18329
  }
17704
18330
  const candidates = byNumber2(input.candidates);
17705
18331
  const count = candidates.length;
17706
- const lines = candidates.map((pull2) => skipped(pull2, now, resolve12));
17707
- if (resolve12 && count > 1)
18332
+ const lines = candidates.map((pull2) => skipped(pull2, now, resolve13));
18333
+ if (resolve13 && count > 1)
17708
18334
  return refusal12(lines, count);
17709
18335
  if (count === 0) {
17710
18336
  return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
@@ -18064,8 +18690,8 @@ function withTaskBudget(plan2, usd) {
18064
18690
  }
18065
18691
 
18066
18692
  // src/pr/plans/load.ts
18067
- import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
18068
- import { dirname as dirname7, join as join30 } from "path";
18693
+ import { existsSync as existsSync10, readFileSync as readFileSync18 } from "fs";
18694
+ import { dirname as dirname7, join as join32 } from "path";
18069
18695
  import { fileURLToPath as fileURLToPath4 } from "url";
18070
18696
  var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
18071
18697
  var PINNED_PLANS_DIRNAME = "plans";
@@ -18082,8 +18708,8 @@ function hasPinnedPlan(triageClass) {
18082
18708
  function pinnedPlanCandidates(moduleDir, triageClass) {
18083
18709
  const name = pinnedPlanFileName(triageClass);
18084
18710
  return [
18085
- join30(moduleDir, PINNED_PLANS_DIRNAME, name),
18086
- join30(moduleDir, name)
18711
+ join32(moduleDir, PINNED_PLANS_DIRNAME, name),
18712
+ join32(moduleDir, name)
18087
18713
  ];
18088
18714
  }
18089
18715
  function readPinnedPlan(triageClass, lookup = {}) {
@@ -18091,7 +18717,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
18091
18717
  throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
18092
18718
  }
18093
18719
  const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
18094
- const found = candidates.find((candidate) => existsSync9(candidate));
18720
+ const found = candidates.find((candidate) => existsSync10(candidate));
18095
18721
  if (found === undefined) {
18096
18722
  throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
18097
18723
  }
@@ -18235,21 +18861,21 @@ function readAttemptRepeat(input) {
18235
18861
  }
18236
18862
 
18237
18863
  // src/commands/pr/resolve-loop.ts
18238
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
18239
- import { join as join31 } from "path";
18240
- var RESOLVE_SUBDIR = join31(".rafa", "resolve");
18864
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
18865
+ import { join as join33 } from "path";
18866
+ var RESOLVE_SUBDIR = join33(".rafa", "resolve");
18241
18867
  var RUN_PREFIX = "pr-";
18242
18868
  var ATTEMPT_PREFIX = "attempt-";
18243
18869
  var LOOP_WORDS = Object.freeze(["loop", "start"]);
18244
18870
  var CHILD_OUTPUT = "text";
18245
18871
  function resolveRunDir(home, number, attempt3) {
18246
- return join31(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
18872
+ return join33(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
18247
18873
  }
18248
18874
  function writeResolvePlan(write) {
18249
18875
  const dir = resolveRunDir(write.home, write.number, write.attempt);
18250
- mkdirSync6(dir, { recursive: true });
18251
- const path = join31(dir, pinnedPlanFileName(write.triageClass));
18252
- writeFileSync6(path, write.plan, "utf8");
18876
+ mkdirSync7(dir, { recursive: true });
18877
+ const path = join33(dir, pinnedPlanFileName(write.triageClass));
18878
+ writeFileSync7(path, write.plan, "utf8");
18253
18879
  return path;
18254
18880
  }
18255
18881
  function resolveLoopArgv(planPath) {
@@ -18289,8 +18915,8 @@ async function runResolveLoop(run, seams = {}) {
18289
18915
  }
18290
18916
 
18291
18917
  // src/pr/triage/version-convert.ts
18292
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
18293
- import { dirname as dirname8, join as join32, posix } from "path";
18918
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "fs";
18919
+ import { dirname as dirname8, join as join34, posix } from "path";
18294
18920
  var FALLBACK_PLAN_PREFIX = "pr-";
18295
18921
  var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
18296
18922
  var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
@@ -18333,7 +18959,7 @@ function fragmentNamesAt(git, commit, directory) {
18333
18959
  return [];
18334
18960
  return listed2.stdout.split("\x00").filter((path) => path !== "").map((path) => posix.basename(path));
18335
18961
  }
18336
- function refused(problem2, problems) {
18962
+ function refused2(problem2, problems) {
18337
18963
  return { outcome: "refused", problem: problem2, problems };
18338
18964
  }
18339
18965
  function fragmentTitle(title, plan2) {
@@ -18370,9 +18996,9 @@ function planConversion(input, problems) {
18370
18996
  const baseRef = `${input.remote ?? RELEASE_REMOTE}/${input.base}`;
18371
18997
  const headCommit = commitOf(git, "HEAD");
18372
18998
  if (headCommit === null)
18373
- return refused(`the worktree at ${input.worktree} has no HEAD commit`, problems);
18999
+ return refused2(`the worktree at ${input.worktree} has no HEAD commit`, problems);
18374
19000
  if (input.head !== null && headCommit !== input.head) {
18375
- return refused(`the worktree's ${input.branch} is at ${headCommit.slice(0, SHORT_HASH_LENGTH)}, not at the pull request's` + ` head ${input.head.slice(0, SHORT_HASH_LENGTH)}; bring the branch up to its pull request's head and run it again`, problems);
19001
+ return refused2(`the worktree's ${input.branch} is at ${headCommit.slice(0, SHORT_HASH_LENGTH)}, not at the pull request's` + ` head ${input.head.slice(0, SHORT_HASH_LENGTH)}; bring the branch up to its pull request's head and run it again`, problems);
18376
19002
  }
18377
19003
  const guard = readGuard({
18378
19004
  git,
@@ -18382,7 +19008,7 @@ function planConversion(input, problems) {
18382
19008
  now: input.now
18383
19009
  });
18384
19010
  if (!guard.ok)
18385
- return refused(`the release guard could not read ${input.branch}: ${guard.problem}`, problems);
19011
+ return refused2(`the release guard could not read ${input.branch}: ${guard.problem}`, problems);
18386
19012
  const { verdict } = guard;
18387
19013
  if (verdict.answer === "clean" || verdict.answer === "missing") {
18388
19014
  return { outcome: "unstamped", answer: verdict.answer, problems };
@@ -18390,25 +19016,25 @@ function planConversion(input, problems) {
18390
19016
  const { stamp } = verdict;
18391
19017
  const start2 = mergeBaseSide(git, guard.base.commit, headCommit, settings);
18392
19018
  if (start2 === null)
18393
- return refused(`${input.branch} and ${baseRef} share no merge base`, problems);
19019
+ return refused2(`${input.branch} and ${baseRef} share no merge base`, problems);
18394
19020
  const level = conversionLevel(start2.version, stamp.version);
18395
19021
  if (level === null) {
18396
- return refused(`no release level reads off the stamp: ${input.branch} stamped ${stamp.version} over` + ` ${start2.version ?? "no version"} at its merge base with ${baseRef}`, problems);
19022
+ return refused2(`no release level reads off the stamp: ${input.branch} stamped ${stamp.version} over` + ` ${start2.version ?? "no version"} at its merge base with ${baseRef}`, problems);
18397
19023
  }
18398
19024
  if (start2.version === null)
18399
- return refused(`the merge base declares no version in ${settings.releaseVersionFile} to restore`, problems);
19025
+ return refused2(`the merge base declares no version in ${settings.releaseVersionFile} to restore`, problems);
18400
19026
  if (start2.changelog === null) {
18401
- return refused(`the merge base holds no ${settings.releaseChangelog}, so ${input.branch} created it and there is no text to restore`, problems);
19027
+ return refused2(`the merge base holds no ${settings.releaseChangelog}, so ${input.branch} created it and there is no text to restore`, problems);
18402
19028
  }
18403
19029
  let branchVersionText;
18404
19030
  try {
18405
- branchVersionText = readFileSync19(join32(input.worktree, settings.releaseVersionFile), "utf8");
19031
+ branchVersionText = readFileSync19(join34(input.worktree, settings.releaseVersionFile), "utf8");
18406
19032
  } catch (error) {
18407
- return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
19033
+ return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
18408
19034
  }
18409
19035
  const rewritten = replaceManifestVersion(branchVersionText, start2.version);
18410
19036
  if (rewritten === null) {
18411
- return refused(`${input.branch}'s ${settings.releaseVersionFile} holds no version pair ${start2.version} could be written over`, problems);
19037
+ return refused2(`${input.branch}'s ${settings.releaseVersionFile} holds no version pair ${start2.version} could be written over`, problems);
18412
19038
  }
18413
19039
  return {
18414
19040
  stamp,
@@ -18428,10 +19054,10 @@ function writeConversion(input, plan2) {
18428
19054
  [input.settings.releaseChangelog, plan2.changelogText]
18429
19055
  ];
18430
19056
  for (const [path, text] of writes) {
18431
- const target = join32(input.worktree, path);
19057
+ const target = join34(input.worktree, path);
18432
19058
  try {
18433
- mkdirSync7(dirname8(target), { recursive: true });
18434
- writeFileSync7(target, text);
19059
+ mkdirSync8(dirname8(target), { recursive: true });
19060
+ writeFileSync8(target, text);
18435
19061
  } catch (error) {
18436
19062
  return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
18437
19063
  }
@@ -18450,18 +19076,18 @@ function convertStampedVersion(input) {
18450
19076
  return plan2;
18451
19077
  const written = writeConversion(input, plan2);
18452
19078
  if (written !== null)
18453
- return refused(written, problems);
19079
+ return refused2(written, problems);
18454
19080
  const paths = [plan2.fragmentPath, settings.releaseVersionFile, settings.releaseChangelog].map((path) => gitPathOf(path));
18455
19081
  const added = git(["add", "--", ...paths]);
18456
19082
  if (!added.ok)
18457
- return refused(`the conversion's files could not be staged, and are left in the worktree: ${gitSaid(added)}`, problems);
19083
+ return refused2(`the conversion's files could not be staged, and are left in the worktree: ${gitSaid(added)}`, problems);
18458
19084
  const subject = conversionCommitSubject(plan2.plan, plan2.stamp.version);
18459
19085
  const made = git(["commit", "--cleanup=whitespace", "-m", subject, "--", ...paths]);
18460
19086
  if (!made.ok)
18461
- return refused(`the conversion could not be committed, and is left staged in the worktree: ${gitSaid(made)}`, problems);
19087
+ return refused2(`the conversion could not be committed, and is left staged in the worktree: ${gitSaid(made)}`, problems);
18462
19088
  const commit = commitOf(git, "HEAD");
18463
19089
  if (commit === null)
18464
- return refused("the conversion was committed, but HEAD names no commit to report", problems);
19090
+ return refused2("the conversion was committed, but HEAD names no commit to report", problems);
18465
19091
  return {
18466
19092
  outcome: "converted",
18467
19093
  commit,
@@ -18898,16 +19524,16 @@ async function redPullRequests(pr) {
18898
19524
  }
18899
19525
  return red;
18900
19526
  }
18901
- async function selectTargets(pr, warn3, now, resolve12) {
19527
+ async function selectTargets(pr, warn3, now, resolve13) {
18902
19528
  const branch = readBranchOrNone(pr, warn3);
18903
19529
  const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
18904
19530
  if (current !== null)
18905
- return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve12 });
19531
+ return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve13 });
18906
19532
  return selectTriagePullRequests({
18907
19533
  current: null,
18908
19534
  candidates: await redPullRequests(pr),
18909
19535
  now,
18910
- resolve: resolve12
19536
+ resolve: resolve13
18911
19537
  });
18912
19538
  }
18913
19539
  async function detailOf2(pr, number) {
@@ -19044,7 +19670,7 @@ async function runTriage(context, seams) {
19044
19670
  }
19045
19671
  const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
19046
19672
  const readings = [];
19047
- let resolve12 = null;
19673
+ let resolve13 = null;
19048
19674
  for (const number of numbers) {
19049
19675
  const assess = () => assessOne({
19050
19676
  pr,
@@ -19064,7 +19690,7 @@ async function runTriage(context, seams) {
19064
19690
  readings.push(reading);
19065
19691
  continue;
19066
19692
  }
19067
- resolve12 = await resolvePullRequest({
19693
+ resolve13 = await resolvePullRequest({
19068
19694
  trust: {
19069
19695
  permissions,
19070
19696
  trustedAuthors: pr.trustedAuthors,
@@ -19086,16 +19712,16 @@ async function runTriage(context, seams) {
19086
19712
  ...seams.clock === undefined ? {} : { clock: seams.clock },
19087
19713
  ...seams.sleep === undefined ? {} : { sleep: seams.sleep }
19088
19714
  });
19089
- readings.push(resolve12.reading);
19715
+ readings.push(resolve13.reading);
19090
19716
  }
19091
19717
  const text = [
19092
19718
  ...selection === null ? [] : [selection.message],
19093
19719
  ...readings.length === 0 ? [] : [renderTriages(readings)],
19094
- ...resolve12 === null ? [] : resolve12.lines
19720
+ ...resolve13 === null ? [] : resolve13.lines
19095
19721
  ].join(`
19096
19722
 
19097
19723
  `);
19098
- return { selection, readings, resolve: resolve12, text, exitCode: resolve12?.exitCode ?? 0 };
19724
+ return { selection, readings, resolve: resolve13, text, exitCode: resolve13?.exitCode ?? 0 };
19099
19725
  }
19100
19726
  function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
19101
19727
  const command = {
@@ -19495,9 +20121,9 @@ async function settleByPr(worktree, settings, pulls) {
19495
20121
  if ("problem" in branch)
19496
20122
  return failed2("unpushed", build, false, branch.problem);
19497
20123
  if (!branch.same) {
19498
- const refused2 = pushReleaseBranch(worktree, build, branch.current);
19499
- if (refused2 !== null)
19500
- return refused2;
20124
+ const refused3 = pushReleaseBranch(worktree, build, branch.current);
20125
+ if (refused3 !== null)
20126
+ return refused3;
19501
20127
  }
19502
20128
  const head = branch.same && branch.current !== null ? branch.current : build.release;
19503
20129
  const draft = {
@@ -19660,9 +20286,9 @@ function tagSettle(worktree, mode, delivered) {
19660
20286
  }
19661
20287
 
19662
20288
  // src/release/settle-worktree.ts
19663
- import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync2 } from "fs";
20289
+ import { existsSync as existsSync11, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync2 } from "fs";
19664
20290
  import { tmpdir as tmpdir2 } from "os";
19665
- import { join as join33 } from "path";
20291
+ import { join as join35 } from "path";
19666
20292
  var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
19667
20293
  var WORKTREE_DIRECTORY = "base";
19668
20294
  function removeScratch(git, scratch, path) {
@@ -19679,14 +20305,14 @@ function removeScratch(git, scratch, path) {
19679
20305
  problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
19680
20306
  }
19681
20307
  }
19682
- if (problems.length === 0 && existsSync10(scratch)) {
20308
+ if (problems.length === 0 && existsSync11(scratch)) {
19683
20309
  problems.push(`the scratch directory ${scratch} is still there`);
19684
20310
  }
19685
20311
  return problems.length === 0 ? null : problems.join("; ");
19686
20312
  }
19687
20313
  function makeScratch(root) {
19688
20314
  try {
19689
- return realpathSync4(mkdtempSync2(join33(root, SETTLE_SCRATCH_PREFIX)));
20315
+ return realpathSync4(mkdtempSync2(join35(root, SETTLE_SCRATCH_PREFIX)));
19690
20316
  } catch (error) {
19691
20317
  return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
19692
20318
  }
@@ -19708,7 +20334,7 @@ function addWorktree(options) {
19708
20334
  const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
19709
20335
  if (typeof scratch !== "string")
19710
20336
  return scratch;
19711
- const path = join33(scratch, WORKTREE_DIRECTORY);
20337
+ const path = join35(scratch, WORKTREE_DIRECTORY);
19712
20338
  const added = git(["worktree", "add", "--detach", path, commit]);
19713
20339
  if (!added.ok) {
19714
20340
  return {
@@ -19931,7 +20557,7 @@ var settle_default = createReleaseSettleCommand();
19931
20557
 
19932
20558
  // src/commands/release/tag.ts
19933
20559
  import { readFileSync as readFileSync20 } from "fs";
19934
- import { join as join34 } from "path";
20560
+ import { join as join36 } from "path";
19935
20561
 
19936
20562
  // src/commands/release/release-commit.ts
19937
20563
  function unread(reason, message) {
@@ -19995,7 +20621,7 @@ function textOf(path) {
19995
20621
  }
19996
20622
  }
19997
20623
  function readVersion(root, configured) {
19998
- const resolved = join34(root, configured);
20624
+ const resolved = join36(root, configured);
19999
20625
  const text = textOf(resolved);
20000
20626
  if (text === null) {
20001
20627
  return {
@@ -20009,7 +20635,7 @@ function readVersion(root, configured) {
20009
20635
  return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
20010
20636
  }
20011
20637
  function readNewestRelease(root, configured) {
20012
- const resolved = join34(root, configured);
20638
+ const resolved = join36(root, configured);
20013
20639
  const text = textOf(resolved);
20014
20640
  if (text === null) {
20015
20641
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -20279,17 +20905,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
20279
20905
  var roadmap_default = createRoadmapCommand();
20280
20906
 
20281
20907
  // src/commands/skill/backfill.ts
20282
- import { statSync as statSync10 } from "fs";
20283
- import { join as join39, resolve as resolve14 } from "path";
20908
+ import { statSync as statSync11 } from "fs";
20909
+ import { join as join41, resolve as resolve15 } from "path";
20284
20910
 
20285
20911
  // src/backfill/backup.ts
20286
- import { copyFileSync as copyFileSync4, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
20287
- import { dirname as dirname9, isAbsolute as isAbsolute7, join as join36, relative as relative9, resolve as resolve12, sep as sep5 } from "path";
20912
+ import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
20913
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join38, relative as relative9, resolve as resolve13, sep as sep5 } from "path";
20288
20914
 
20289
20915
  // src/backfill/proposal-file.ts
20290
- import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
20291
- import { join as join35 } from "path";
20292
- var BACKFILL_PATH = join35(".rafa", "backfill");
20916
+ import { readdirSync as readdirSync4, readFileSync as readFileSync21 } from "fs";
20917
+ import { join as join37 } from "path";
20918
+ var BACKFILL_PATH = join37(".rafa", "backfill");
20293
20919
  var PROPOSAL_FILE_PREFIX = "proposals-";
20294
20920
  var PROPOSAL_FILE_EXTENSION = ".yaml";
20295
20921
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
@@ -20315,7 +20941,7 @@ var PROPOSAL_FILE_HEADER = [
20315
20941
  ].join(`
20316
20942
  `);
20317
20943
  function proposalPath(dir, batch) {
20318
- return join35(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
20944
+ return join37(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
20319
20945
  }
20320
20946
  function isProposalFileName(name) {
20321
20947
  return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
@@ -20446,12 +21072,12 @@ function parseProposalFile(text) {
20446
21072
  function readProposalFiles(dir) {
20447
21073
  let names;
20448
21074
  try {
20449
- names = readdirSync3(dir);
21075
+ names = readdirSync4(dir);
20450
21076
  } catch {
20451
21077
  return [];
20452
21078
  }
20453
21079
  return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
20454
- const path = join35(dir, name);
21080
+ const path = join37(dir, name);
20455
21081
  try {
20456
21082
  return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
20457
21083
  } catch (error) {
@@ -20461,19 +21087,19 @@ function readProposalFiles(dir) {
20461
21087
  }
20462
21088
 
20463
21089
  // src/backfill/backup.ts
20464
- var BACKUP_PATH = join36(BACKFILL_PATH, "backup");
21090
+ var BACKUP_PATH = join38(BACKFILL_PATH, "backup");
20465
21091
  var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
20466
21092
  function backupDirectory(base) {
20467
- return join36(base, BACKUP_PATH);
21093
+ return join38(base, BACKUP_PATH);
20468
21094
  }
20469
21095
  function isUnder(root, path) {
20470
- const step = relative9(resolve12(root), resolve12(path));
21096
+ const step = relative9(resolve13(root), resolve13(path));
20471
21097
  return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute7(step);
20472
21098
  }
20473
21099
  function backupPathFor(path, root, backupDir) {
20474
21100
  if (!isUnder(root, path))
20475
21101
  return null;
20476
- return join36(backupDir, relative9(resolve12(root), resolve12(path)));
21102
+ return join38(backupDir, relative9(resolve13(root), resolve13(path)));
20477
21103
  }
20478
21104
  function backupFile(path, options) {
20479
21105
  if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
@@ -20482,11 +21108,11 @@ function backupFile(path, options) {
20482
21108
  const backup = backupPathFor(path, options.root, options.backupDir);
20483
21109
  if (backup === null)
20484
21110
  return { path, kind: "outside", backup: null };
20485
- if (!existsSync11(path))
21111
+ if (!existsSync12(path))
20486
21112
  return { path, kind: "missing", backup: null };
20487
- if (existsSync11(backup))
21113
+ if (existsSync12(backup))
20488
21114
  return { path, kind: "kept", backup };
20489
- mkdirSync8(dirname9(backup), { recursive: true });
21115
+ mkdirSync9(dirname9(backup), { recursive: true });
20490
21116
  copyFileSync4(path, backup);
20491
21117
  return { path, kind: "copied", backup };
20492
21118
  }
@@ -20509,7 +21135,7 @@ function countBackups(results) {
20509
21135
  }
20510
21136
 
20511
21137
  // src/backfill/derive.ts
20512
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "fs";
21138
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
20513
21139
  import { basename as basename6 } from "path";
20514
21140
  var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
20515
21141
  function renderWhenToUse(trigger, prevents) {
@@ -20688,7 +21314,7 @@ function applyFile(action, options) {
20688
21314
  text: null
20689
21315
  };
20690
21316
  }
20691
- writeFileSync8(action.path, action.text, "utf8");
21317
+ writeFileSync9(action.path, action.text, "utf8");
20692
21318
  const after = failureLines(checkFile(action.path, "skill", {
20693
21319
  projectRoot: options.projectRoot,
20694
21320
  pathDirs: options.pathDirs
@@ -20696,7 +21322,7 @@ function applyFile(action, options) {
20696
21322
  const added = addedFailures(action.before, after);
20697
21323
  if (added.length === 0)
20698
21324
  return action;
20699
- writeFileSync8(action.path, action.original, "utf8");
21325
+ writeFileSync9(action.path, action.original, "utf8");
20700
21326
  return {
20701
21327
  ...action,
20702
21328
  kind: "refused",
@@ -20800,8 +21426,8 @@ function summaryLine3(rows) {
20800
21426
  const parts = DEMOTION_VERDICTS.map((verdict) => `${counts[verdict]} ${verdict}`);
20801
21427
  return `${files}: ${parts.join(", ")}.`;
20802
21428
  }
20803
- function writeDemotionReport(report3) {
20804
- const rows = report3.rows.map((row2) => tableLine2([
21429
+ function writeDemotionReport(report4) {
21430
+ const rows = report4.rows.map((row2) => tableLine2([
20805
21431
  escapeCell(row2.path),
20806
21432
  escapeCell(row2.hash),
20807
21433
  escapeCell(row2.verdict),
@@ -20810,11 +21436,11 @@ function writeDemotionReport(report3) {
20810
21436
  escapeCell(row2.overrideReason ?? EMPTY_CELL)
20811
21437
  ]));
20812
21438
  return [
20813
- renderHeader(report3.status),
21439
+ renderHeader(report4.status),
20814
21440
  "",
20815
21441
  REPORT_TITLE,
20816
21442
  "",
20817
- summaryLine3(report3.rows),
21443
+ summaryLine3(report4.rows),
20818
21444
  "",
20819
21445
  tableLine2(DEMOTION_REPORT_COLUMNS),
20820
21446
  tableLine2(DEMOTION_REPORT_COLUMNS.map(() => "---")),
@@ -21115,8 +21741,8 @@ function parseSessionAnswer(stdout) {
21115
21741
  }
21116
21742
 
21117
21743
  // src/backfill/propose.ts
21118
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
21119
- import { join as join37, resolve as resolve13 } from "path";
21744
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "fs";
21745
+ import { join as join39, resolve as resolve14 } from "path";
21120
21746
  var PROPOSAL_ACTION_KINDS = [
21121
21747
  "applied",
21122
21748
  "unchanged",
@@ -21195,12 +21821,12 @@ async function runProposalPass(options) {
21195
21821
  const batches = batchCandidates(candidates);
21196
21822
  const written = [];
21197
21823
  if (batches.length > 0)
21198
- mkdirSync9(options.backfillDir, { recursive: true });
21824
+ mkdirSync10(options.backfillDir, { recursive: true });
21199
21825
  for (const [index, batch] of batches.entries()) {
21200
21826
  const number = index + 1;
21201
21827
  const file = await runProposalBatch(number, batch, options);
21202
21828
  const path = proposalPath(options.backfillDir, number);
21203
- writeFileSync9(path, renderProposalFile(file), "utf8");
21829
+ writeFileSync10(path, renderProposalFile(file), "utf8");
21204
21830
  written.push({ path, file });
21205
21831
  }
21206
21832
  return { written, candidates: candidates.length };
@@ -21242,7 +21868,7 @@ function writeRow(path, row2, original, options) {
21242
21868
  return inert2(path, row2, "unchanged", "every field the row proposes is already what the file carries");
21243
21869
  }
21244
21870
  const before = checkLines(path, options);
21245
- writeFileSync9(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
21871
+ writeFileSync10(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
21246
21872
  const added = addedFailures(before, checkLines(path, options));
21247
21873
  if (added.length === 0) {
21248
21874
  return {
@@ -21254,7 +21880,7 @@ function writeRow(path, row2, original, options) {
21254
21880
  added: []
21255
21881
  };
21256
21882
  }
21257
- writeFileSync9(path, original, "utf8");
21883
+ writeFileSync10(path, original, "utf8");
21258
21884
  return {
21259
21885
  path,
21260
21886
  relative: row2.path,
@@ -21265,7 +21891,7 @@ function writeRow(path, row2, original, options) {
21265
21891
  };
21266
21892
  }
21267
21893
  function applyRow(root, row2, options) {
21268
- const path = join37(root, ...row2.path.split("/"));
21894
+ const path = join39(root, ...row2.path.split("/"));
21269
21895
  if (row2.status === UNANSWERED_ROW) {
21270
21896
  return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
21271
21897
  }
@@ -21288,7 +21914,7 @@ function fileRefusal(file, root) {
21288
21914
  if (file.status !== REVIEWED_FILE) {
21289
21915
  return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
21290
21916
  }
21291
- return resolve13(file.skills) === resolve13(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
21917
+ return resolve14(file.skills) === resolve14(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
21292
21918
  }
21293
21919
  function countProposalActions(actions) {
21294
21920
  const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
@@ -21302,7 +21928,7 @@ function applyProposals(files, root, options) {
21302
21928
  for (const file of files) {
21303
21929
  const refusal13 = fileRefusal(file, root);
21304
21930
  for (const row2 of file.rows) {
21305
- const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join37(root, ...row2.path.split("/")), row2, "refused", refusal13);
21931
+ const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join39(root, ...row2.path.split("/")), row2, "refused", refusal13);
21306
21932
  actions.push(action);
21307
21933
  if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
21308
21934
  triggers[row2.name] = row2.trigger;
@@ -21313,13 +21939,13 @@ function applyProposals(files, root, options) {
21313
21939
  }
21314
21940
 
21315
21941
  // src/demote/select.ts
21316
- import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
21317
- import { basename as basename7, dirname as dirname10, join as join38 } from "path";
21942
+ import { readdirSync as readdirSync5, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
21943
+ import { basename as basename7, dirname as dirname10, join as join40 } from "path";
21318
21944
  var SKILLS_DIRECTORY = "skills";
21319
21945
  var CLAUDE_DIRECTORY = ".claude";
21320
21946
  var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
21321
- var DEMOTED_PATH = join38(".rafa", "demoted");
21322
- var INSTINCTS_PATH = join38(".rafa", "instincts");
21947
+ var DEMOTED_PATH = join40(".rafa", "demoted");
21948
+ var INSTINCTS_PATH = join40(".rafa", "instincts");
21323
21949
  var REPORT_FILE = "report.md";
21324
21950
  function realOrSelf(path) {
21325
21951
  try {
@@ -21342,19 +21968,19 @@ function resolveDemotionScope(dir, home) {
21342
21968
  if (basename7(claude) !== CLAUDE_DIRECTORY)
21343
21969
  return null;
21344
21970
  const base = dirname10(claude);
21345
- const demotedDir = join38(base, DEMOTED_PATH);
21971
+ const demotedDir = join40(base, DEMOTED_PATH);
21346
21972
  return {
21347
21973
  scope: samePath(base, home) ? "user" : "project",
21348
21974
  dir,
21349
21975
  base,
21350
21976
  demotedDir,
21351
- instinctsDir: join38(base, INSTINCTS_PATH),
21352
- reportPath: join38(demotedDir, REPORT_FILE)
21977
+ instinctsDir: join40(base, INSTINCTS_PATH),
21978
+ reportPath: join40(demotedDir, REPORT_FILE)
21353
21979
  };
21354
21980
  }
21355
21981
  function entryKind(path) {
21356
21982
  try {
21357
- const stats = statSync9(path);
21983
+ const stats = statSync10(path);
21358
21984
  if (stats.isFile())
21359
21985
  return "file";
21360
21986
  if (stats.isDirectory())
@@ -21366,7 +21992,7 @@ function entryKind(path) {
21366
21992
  }
21367
21993
  function visibleNames(dir) {
21368
21994
  try {
21369
- return readdirSync4(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
21995
+ return readdirSync5(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
21370
21996
  } catch {
21371
21997
  return [];
21372
21998
  }
@@ -21376,7 +22002,7 @@ function readSelected(absolute, path, entries) {
21376
22002
  let mtime;
21377
22003
  try {
21378
22004
  text = readFileSync25(absolute, "utf8");
21379
- mtime = statSync9(absolute).mtime.toISOString();
22005
+ mtime = statSync10(absolute).mtime.toISOString();
21380
22006
  } catch {
21381
22007
  return null;
21382
22008
  }
@@ -21391,14 +22017,14 @@ function selectsLearned(text, scope) {
21391
22017
  return origin === AUTO_EXTRACTED_ORIGIN;
21392
22018
  }
21393
22019
  function selectLearned(scope) {
21394
- const dir = join38(scope.dir, LEARNED_DIRECTORY);
22020
+ const dir = join40(scope.dir, LEARNED_DIRECTORY);
21395
22021
  if (entryKind(dir) !== "directory")
21396
22022
  return [];
21397
22023
  const selected = [];
21398
22024
  for (const name of visibleNames(dir)) {
21399
22025
  if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
21400
22026
  continue;
21401
- const absolute = join38(dir, name);
22027
+ const absolute = join40(dir, name);
21402
22028
  if (entryKind(absolute) !== "file")
21403
22029
  continue;
21404
22030
  const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
@@ -21412,10 +22038,10 @@ function selectSkillFiles(scope) {
21412
22038
  for (const name of visibleNames(scope.dir)) {
21413
22039
  if (name === LEARNED_DIRECTORY)
21414
22040
  continue;
21415
- const skillDir = join38(scope.dir, name);
22041
+ const skillDir = join40(scope.dir, name);
21416
22042
  if (entryKind(skillDir) !== "directory")
21417
22043
  continue;
21418
- const absolute = join38(skillDir, SKILL_FILE);
22044
+ const absolute = join40(skillDir, SKILL_FILE);
21419
22045
  if (entryKind(absolute) !== "file")
21420
22046
  continue;
21421
22047
  const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
@@ -21434,7 +22060,7 @@ var DEFAULT_BACKFILL_SEAMS = Object.freeze({
21434
22060
  });
21435
22061
  var BACKFILL_USAGE = "rafa skill backfill <dir> [--propose|--apply] [--project=<root>]";
21436
22062
  var REFUSED_CAP = 255;
21437
- function readSwitch3(name, value) {
22063
+ function readSwitch4(name, value) {
21438
22064
  if (value === undefined || value === false || value === "false")
21439
22065
  return false;
21440
22066
  if (value === true || value === "true")
@@ -21443,8 +22069,8 @@ function readSwitch3(name, value) {
21443
22069
  Usage: ${BACKFILL_USAGE}`);
21444
22070
  }
21445
22071
  function readMode(flags) {
21446
- const propose = readSwitch3("propose", flags["propose"]);
21447
- const apply = readSwitch3("apply", flags["apply"]);
22072
+ const propose = readSwitch4("propose", flags["propose"]);
22073
+ const apply = readSwitch4("apply", flags["apply"]);
21448
22074
  if (propose && apply) {
21449
22075
  throw new CommandExit(1, "\u274C --propose asks the sessions and --apply writes their reviewed answers, so a run is one or the" + ` other
21450
22076
  Usage: ${BACKFILL_USAGE}`);
@@ -21463,7 +22089,7 @@ Usage: ${BACKFILL_USAGE}`);
21463
22089
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
21464
22090
  Usage: ${BACKFILL_USAGE}`);
21465
22091
  }
21466
- return resolve14(cwd(), dir);
22092
+ return resolve15(cwd(), dir);
21467
22093
  }
21468
22094
  function readConsumerRoot(value, cwd) {
21469
22095
  if (value === undefined || value === false)
@@ -21472,11 +22098,11 @@ function readConsumerRoot(value, cwd) {
21472
22098
  throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
21473
22099
  Usage: ${BACKFILL_USAGE}`);
21474
22100
  }
21475
- return resolve14(cwd(), value);
22101
+ return resolve15(cwd(), value);
21476
22102
  }
21477
22103
  function isDirectory3(path) {
21478
22104
  try {
21479
- return statSync10(path).isDirectory();
22105
+ return statSync11(path).isDirectory();
21480
22106
  } catch {
21481
22107
  return false;
21482
22108
  }
@@ -21525,7 +22151,7 @@ function actionLines(actions) {
21525
22151
  function checkOptions(context, projectRoot2) {
21526
22152
  return { projectRoot: projectRoot2, pathDirs: pathDirectories(context.env["PATH"]) };
21527
22153
  }
21528
- function report3(context, result2, lines) {
22154
+ function report4(context, result2, lines) {
21529
22155
  if (context.outputMode === "json") {
21530
22156
  context.output.result(result2);
21531
22157
  return;
@@ -21546,7 +22172,7 @@ function runPlan(context, scope, backfillDir, projectRoot2) {
21546
22172
  counts: plan2.counts,
21547
22173
  candidates
21548
22174
  };
21549
- report3(context, result2, [
22175
+ report4(context, result2, [
21550
22176
  `${scope.scope} scope: ${scope.dir}`,
21551
22177
  ...actionLines(actions),
21552
22178
  derivationLine(plan2),
@@ -21580,7 +22206,7 @@ async function runPropose(context, scope, backfillDir, seams) {
21580
22206
  candidates: pass.candidates,
21581
22207
  files
21582
22208
  };
21583
- report3(context, result2, [
22209
+ report4(context, result2, [
21584
22210
  `${scope.scope} scope: ${scope.dir}`,
21585
22211
  `${String(pass.candidates)} file(s) to ask about, in ${String(files.length)} batch(es)`,
21586
22212
  ...files.map((file) => `\u2705 ${file.path}: batch ${String(file.batch)}, session exit ${String(file.exitCode)},` + ` ${String(file.rows)} row(s), ${String(file.unanswered)} unanswered`),
@@ -21600,7 +22226,7 @@ function reviewedProposals(dir) {
21600
22226
  return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
21601
22227
  }
21602
22228
  function rowPaths(files, root) {
21603
- return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join39(root, ...row2.path.split("/"))));
22229
+ return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join41(root, ...row2.path.split("/"))));
21604
22230
  }
21605
22231
  function runApply(context, scope, backfillDir, projectRoot2) {
21606
22232
  const options = checkOptions(context, projectRoot2);
@@ -21635,18 +22261,18 @@ function runApply(context, scope, backfillDir, projectRoot2) {
21635
22261
  derivationLine(derived),
21636
22262
  `${String(copied)} file(s) copied under ${backupDir} before they were rewritten`
21637
22263
  ];
21638
- const refused2 = proposals.counts.refused + derived.counts.refused;
21639
- if (refused2 > 0)
21640
- throw new CommandExit(Math.min(refused2, REFUSED_CAP), lines.join(`
22264
+ const refused3 = proposals.counts.refused + derived.counts.refused;
22265
+ if (refused3 > 0)
22266
+ throw new CommandExit(Math.min(refused3, REFUSED_CAP), lines.join(`
21641
22267
  `));
21642
- report3(context, result2, lines);
22268
+ report4(context, result2, lines);
21643
22269
  }
21644
22270
  async function runBackfill(context, seams) {
21645
22271
  const mode = readMode(context.flags);
21646
22272
  const dir = readBackfillDirectory(context.args, seams.cwd);
21647
22273
  const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
21648
22274
  const scope = backfillScopeOf(dir, projectOf11(context).home);
21649
- const backfillDir = join39(scope.base, BACKFILL_PATH);
22275
+ const backfillDir = join41(scope.base, BACKFILL_PATH);
21650
22276
  if (mode === "propose") {
21651
22277
  await runPropose(context, scope, backfillDir, seams);
21652
22278
  return;
@@ -21760,13 +22386,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
21760
22386
  var check_default3 = createSkillCheckCommand();
21761
22387
 
21762
22388
  // src/commands/skill/demote.ts
21763
- import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync27, statSync as statSync12, writeFileSync as writeFileSync11 } from "fs";
21764
- import { dirname as dirname12, resolve as resolve15 } from "path";
22389
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync27, statSync as statSync13, writeFileSync as writeFileSync12 } from "fs";
22390
+ import { dirname as dirname12, resolve as resolve16 } from "path";
21765
22391
 
21766
22392
  // src/demote/apply.ts
21767
- import { copyFileSync as copyFileSync5, existsSync as existsSync12, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
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";
21768
22394
  import { tmpdir as tmpdir3 } from "os";
21769
- import { dirname as dirname11, join as join40 } from "path";
22395
+ import { dirname as dirname11, join as join42 } from "path";
21770
22396
  var DEMOTION_ACTION_KINDS = [
21771
22397
  "demoted",
21772
22398
  "kept",
@@ -21787,18 +22413,18 @@ function learnedSkillName(path) {
21787
22413
  return base.slice(0, -MARKDOWN_EXTENSION.length);
21788
22414
  }
21789
22415
  function sourcePath(scope, path) {
21790
- return join40(scope.dir, ...path.split("/"));
22416
+ return join42(scope.dir, ...path.split("/"));
21791
22417
  }
21792
22418
  function demotedPath(scope, path) {
21793
- return join40(scope.demotedDir, ...path.split("/"));
22419
+ return join42(scope.demotedDir, ...path.split("/"));
21794
22420
  }
21795
22421
  function promotedPath(scope, path) {
21796
- return join40(scope.dir, learnedSkillName(path), SKILL_FILE);
22422
+ return join42(scope.dir, learnedSkillName(path), SKILL_FILE);
21797
22423
  }
21798
22424
  function takenInstinctIds(dir) {
21799
22425
  let names;
21800
22426
  try {
21801
- names = readdirSync5(dir);
22427
+ names = readdirSync6(dir);
21802
22428
  } catch {
21803
22429
  return [];
21804
22430
  }
@@ -21806,7 +22432,7 @@ function takenInstinctIds(dir) {
21806
22432
  }
21807
22433
  function readText4(path) {
21808
22434
  try {
21809
- return statSync11(path).isFile() ? readFileSync26(path, "utf8") : null;
22435
+ return statSync12(path).isFile() ? readFileSync26(path, "utf8") : null;
21810
22436
  } catch {
21811
22437
  return null;
21812
22438
  }
@@ -21834,7 +22460,7 @@ function planKept(scope, row2, file) {
21834
22460
  return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
21835
22461
  }
21836
22462
  const to = promotedPath(scope, row2.path);
21837
- if (existsSync12(to) || existsSync12(dirname11(to))) {
22463
+ if (existsSync13(to) || existsSync13(dirname11(to))) {
21838
22464
  return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
21839
22465
  }
21840
22466
  return {
@@ -21848,10 +22474,10 @@ function planKept(scope, row2, file) {
21848
22474
  };
21849
22475
  }
21850
22476
  function checkCandidate(dir, id2, text, pathDirs) {
21851
- const path = join40(dir, `${id2}${MARKDOWN_EXTENSION}`);
21852
- writeFileSync10(path, text, "utf8");
21853
- const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
21854
- return failureLines2(report4.issues);
22477
+ const path = join42(dir, `${id2}${MARKDOWN_EXTENSION}`);
22478
+ writeFileSync11(path, text, "utf8");
22479
+ const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
22480
+ return failureLines2(report5.issues);
21855
22481
  }
21856
22482
  function planDemoted(scope, row2, file, context) {
21857
22483
  const conversion = convertToInstinct(file, {
@@ -21869,11 +22495,11 @@ function planDemoted(scope, row2, file, context) {
21869
22495
  if (failures.length > 0) {
21870
22496
  return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
21871
22497
  }
21872
- const destination = join40(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
22498
+ const destination = join42(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
21873
22499
  const kept = demotedPath(scope, row2.path);
21874
- if (existsSync12(destination))
22500
+ if (existsSync13(destination))
21875
22501
  return inert3(row2.path, "observation", "refused", `${destination} is already there`);
21876
- if (existsSync12(kept))
22502
+ if (existsSync13(kept))
21877
22503
  return inert3(row2.path, "observation", "refused", `${kept} is already there`);
21878
22504
  context.taken.push(id2);
21879
22505
  return {
@@ -21905,7 +22531,7 @@ function planRow(scope, row2, context) {
21905
22531
  entries: undefined,
21906
22532
  absolute,
21907
22533
  hash,
21908
- mtime: statSync11(absolute).mtime.toISOString()
22534
+ mtime: statSync12(absolute).mtime.toISOString()
21909
22535
  };
21910
22536
  return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
21911
22537
  }
@@ -21915,18 +22541,18 @@ function countActions(actions) {
21915
22541
  counts[action.kind] += 1;
21916
22542
  return counts;
21917
22543
  }
21918
- function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
21919
- const temp = mkdtempSync3(join40(tmpdir3(), "rafa-demote-"));
22544
+ function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
22545
+ const temp = mkdtempSync3(join42(tmpdir3(), "rafa-demote-"));
21920
22546
  const taken = [...takenInstinctIds(scope.instinctsDir)];
21921
22547
  try {
21922
- const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
22548
+ const actions = report5.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
21923
22549
  return { scope, actions, counts: countActions(actions) };
21924
22550
  } finally {
21925
22551
  rmSync3(temp, { recursive: true, force: true });
21926
22552
  }
21927
22553
  }
21928
22554
  function moveFile(move3) {
21929
- mkdirSync10(dirname11(move3.to), { recursive: true });
22555
+ mkdirSync11(dirname11(move3.to), { recursive: true });
21930
22556
  try {
21931
22557
  renameSync(move3.from, move3.to);
21932
22558
  } catch {
@@ -21937,8 +22563,8 @@ function moveFile(move3) {
21937
22563
  function applyDemotion(plan2) {
21938
22564
  for (const action of plan2.actions) {
21939
22565
  if (action.write !== null) {
21940
- mkdirSync10(dirname11(action.write.path), { recursive: true });
21941
- writeFileSync10(action.write.path, action.write.text, "utf8");
22566
+ mkdirSync11(dirname11(action.write.path), { recursive: true });
22567
+ writeFileSync11(action.write.path, action.write.text, "utf8");
21942
22568
  }
21943
22569
  if (action.move !== null)
21944
22570
  moveFile(action.move);
@@ -22003,11 +22629,11 @@ Usage: ${DEMOTE_USAGE}`);
22003
22629
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
22004
22630
  Usage: ${DEMOTE_USAGE}`);
22005
22631
  }
22006
- return resolve15(cwd(), dir);
22632
+ return resolve16(cwd(), dir);
22007
22633
  }
22008
22634
  function isDirectory4(path) {
22009
22635
  try {
22010
- return statSync12(path).isDirectory();
22636
+ return statSync13(path).isDirectory();
22011
22637
  } catch {
22012
22638
  return false;
22013
22639
  }
@@ -22029,13 +22655,13 @@ function projectOf12(context) {
22029
22655
  throw new Error("rafa skill demote runs inside a project, and was handed none");
22030
22656
  return context.project;
22031
22657
  }
22032
- function verdictLine2(report4) {
22033
- const counts = countVerdicts(report4.rows);
22658
+ function verdictLine2(report5) {
22659
+ const counts = countVerdicts(report5.rows);
22034
22660
  const parts = DEMOTION_VERDICTS.map((verdict) => `${String(counts[verdict])} ${verdict}`);
22035
- return `${String(report4.rows.length)} file(s): ${parts.join(", ")}`;
22661
+ return `${String(report5.rows.length)} file(s): ${parts.join(", ")}`;
22036
22662
  }
22037
22663
  function previousReport(path, context) {
22038
- if (!existsSync13(path))
22664
+ if (!existsSync14(path))
22039
22665
  return null;
22040
22666
  const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
22041
22667
  if (parsed.report !== null)
@@ -22045,17 +22671,17 @@ function previousReport(path, context) {
22045
22671
  }
22046
22672
  function runWrite(context, scope) {
22047
22673
  const files = selectSources(scope);
22048
- const report4 = buildDemotionReport(files, previousReport(scope.reportPath, context));
22049
- mkdirSync11(dirname12(scope.reportPath), { recursive: true });
22050
- writeFileSync11(scope.reportPath, writeDemotionReport(report4), "utf8");
22674
+ const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
22675
+ mkdirSync12(dirname12(scope.reportPath), { recursive: true });
22676
+ writeFileSync12(scope.reportPath, writeDemotionReport(report5), "utf8");
22051
22677
  const result2 = {
22052
22678
  applied: false,
22053
22679
  scope: scope.scope,
22054
22680
  dir: scope.dir,
22055
22681
  reportPath: scope.reportPath,
22056
- status: report4.status,
22057
- files: report4.rows.length,
22058
- counts: countVerdicts(report4.rows)
22682
+ status: report5.status,
22683
+ files: report5.rows.length,
22684
+ counts: countVerdicts(report5.rows)
22059
22685
  };
22060
22686
  if (context.outputMode === "json") {
22061
22687
  context.output.result(result2);
@@ -22063,14 +22689,14 @@ function runWrite(context, scope) {
22063
22689
  }
22064
22690
  for (const line of [
22065
22691
  `${scope.scope} scope: ${scope.dir}`,
22066
- verdictLine2(report4),
22067
- `Report written to ${scope.reportPath} (status: ${report4.status}); nothing was moved`,
22692
+ verdictLine2(report5),
22693
+ `Report written to ${scope.reportPath} (status: ${report5.status}); nothing was moved`,
22068
22694
  `Review each row, then mark the report ${REVIEWED_STATUS} and re-run with --apply`
22069
22695
  ])
22070
22696
  context.output.info(line);
22071
22697
  }
22072
22698
  function reviewedReport(scope) {
22073
- if (!existsSync13(scope.reportPath)) {
22699
+ if (!existsSync14(scope.reportPath)) {
22074
22700
  throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
22075
22701
  }
22076
22702
  const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
@@ -22100,8 +22726,8 @@ function renderApply(result2) {
22100
22726
  return [...rows, `${String(result2.actions.length)} row(s) of ${result2.reportPath}: ${parts.join(", ")}`];
22101
22727
  }
22102
22728
  function runApply2(context, scope) {
22103
- const report4 = reviewedReport(scope);
22104
- const plan2 = planDemotion(report4, scope, {
22729
+ const report5 = reviewedReport(scope);
22730
+ const plan2 = planDemotion(report5, scope, {
22105
22731
  now: () => new Date().toISOString(),
22106
22732
  pathDirs: pathDirectories(context.env["PATH"])
22107
22733
  });
@@ -22446,12 +23072,12 @@ function statusData(sections) {
22446
23072
  }
22447
23073
 
22448
23074
  // src/status/sections.ts
22449
- import { existsSync as existsSync14 } from "fs";
23075
+ import { existsSync as existsSync15 } from "fs";
22450
23076
 
22451
23077
  // src/status/blocked-count.ts
22452
23078
  async function labelsCount(options) {
22453
- const report4 = await options.blockedIssues(options.gh);
22454
- return report4.problem === null ? { count: report4.readings.length, notes: [] } : { count: null, notes: [`the issues labelled ${SPEC_BLOCKED_LABEL} could not be read: ${report4.problem}`] };
23079
+ const report5 = await options.blockedIssues(options.gh);
23080
+ return report5.problem === null ? { count: report5.readings.length, notes: [] } : { count: null, notes: [`the issues labelled ${SPEC_BLOCKED_LABEL} could not be read: ${report5.problem}`] };
22455
23081
  }
22456
23082
  async function nativeCount(relations, listing2) {
22457
23083
  try {
@@ -22614,9 +23240,9 @@ function boundedRunner(open, root, deadline) {
22614
23240
  }
22615
23241
  async function networkSection(what, deadline, work) {
22616
23242
  let timer;
22617
- const expired = new Promise((resolve16) => {
23243
+ const expired = new Promise((resolve17) => {
22618
23244
  timer = setTimeout(() => {
22619
- resolve16(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
23245
+ resolve17(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
22620
23246
  }, Math.max(0, deadline.left()));
22621
23247
  });
22622
23248
  try {
@@ -22706,7 +23332,7 @@ function nextOfPlace(current, epic, walk) {
22706
23332
  return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
22707
23333
  }
22708
23334
  function hasBoards(root, listing2) {
22709
- if (existsSync14(positionFilePath(root)))
23335
+ if (existsSync15(positionFilePath(root)))
22710
23336
  return true;
22711
23337
  return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
22712
23338
  }
@@ -22907,8 +23533,8 @@ function createUpdateStub(spec) {
22907
23533
  var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
22908
23534
 
22909
23535
  // src/project/lock.ts
22910
- import { existsSync as existsSync15, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
22911
- import { join as join41 } from "path";
23536
+ import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "fs";
23537
+ import { join as join43 } from "path";
22912
23538
  var LOCK_FILE = "rafa.lock";
22913
23539
  var LOCKFILE_VERSION = 1;
22914
23540
 
@@ -22916,7 +23542,7 @@ class ProjectLockError extends Error {
22916
23542
  name = "ProjectLockError";
22917
23543
  }
22918
23544
  function lockPath(root) {
22919
- return join41(root, LOCK_FILE);
23545
+ return join43(root, LOCK_FILE);
22920
23546
  }
22921
23547
  function projectLockText(version2) {
22922
23548
  const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
@@ -22949,7 +23575,7 @@ function lockOf(value) {
22949
23575
  }
22950
23576
  function readProjectLock(root) {
22951
23577
  const path = lockPath(root);
22952
- if (!existsSync15(path)) {
23578
+ if (!existsSync16(path)) {
22953
23579
  if (isLink(path))
22954
23580
  refuse8("is a link to nothing");
22955
23581
  return null;
@@ -22969,15 +23595,15 @@ function readProjectLock(root) {
22969
23595
  return lockOf(value);
22970
23596
  }
22971
23597
  function writeProjectLock(root, version2) {
22972
- writeFileSync12(lockPath(root), projectLockText(version2), "utf8");
23598
+ writeFileSync13(lockPath(root), projectLockText(version2), "utf8");
22973
23599
  }
22974
23600
 
22975
23601
  // src/project/update-current.ts
22976
- import { existsSync as existsSync16 } from "fs";
22977
- import { join as join42 } from "path";
23602
+ import { existsSync as existsSync17 } from "fs";
23603
+ import { join as join44 } from "path";
22978
23604
  var DEPRECATIONS = Object.freeze([]);
22979
23605
  function missingFolders(root) {
22980
- return PROJECT_TREE.map((name) => join42(SCOPE_DIR, name)).filter((relative11) => !existsSync16(join42(root, relative11)));
23606
+ return PROJECT_TREE.map((name) => join44(SCOPE_DIR, name)).filter((relative11) => !existsSync17(join44(root, relative11)));
22981
23607
  }
22982
23608
  async function readBoard3(access) {
22983
23609
  if (access.gh === null)
@@ -22992,10 +23618,10 @@ async function readBoard3(access) {
22992
23618
  function lockChangeOf(range) {
22993
23619
  if (range.kind === "adopt")
22994
23620
  return "created";
22995
- return range.kind === "patch" ? "updated" : "unchanged";
23621
+ return range.kind === "same" ? "unchanged" : "updated";
22996
23622
  }
22997
23623
  function fromOf(range) {
22998
- if (range.kind === "patch")
23624
+ if (range.kind === "patch" || range.kind === "minor")
22999
23625
  return range.from;
23000
23626
  if (range.kind === "same")
23001
23627
  return range.to;
@@ -23030,7 +23656,7 @@ class UpdateStepError extends Error {
23030
23656
  }
23031
23657
  }
23032
23658
  function foldersNowThere(plan2) {
23033
- return plan2.folders.filter((relative11) => existsSync16(join42(plan2.root, relative11)));
23659
+ return plan2.folders.filter((relative11) => existsSync17(join44(plan2.root, relative11)));
23034
23660
  }
23035
23661
  function writeFolders(plan2) {
23036
23662
  if (plan2.folders.length === 0)
@@ -23058,27 +23684,29 @@ async function applyUpdatePlan(plan2, gh) {
23058
23684
  }
23059
23685
 
23060
23686
  // src/project/update-range.ts
23061
- function refused2(reason, message) {
23687
+ function refused3(reason, message) {
23062
23688
  return { kind: "refused", reason, message };
23063
23689
  }
23064
23690
  function readCurrentRange(recorded, installed) {
23065
23691
  const to = parseSemanticVersion(installed);
23066
23692
  if (to === null)
23067
- return refused2("unreadable", `the installed rafa answers "${installed}", which is no version`);
23693
+ return refused3("unreadable", `the installed rafa answers "${installed}", which is no version`);
23068
23694
  if (recorded === null)
23069
23695
  return { kind: "adopt", to: installed };
23070
23696
  const from = parseSemanticVersion(recorded);
23071
23697
  if (from === null)
23072
- return refused2("unreadable", `the project records rafa "${recorded}", which is no version`);
23698
+ return refused3("unreadable", `the project records rafa "${recorded}", which is no version`);
23073
23699
  const order = compareVersions(to, from);
23074
23700
  if (order < 0) {
23075
- return refused2("older", `the installed rafa ${installed} is older than the project's ${recorded}, and no downgrade runs before 1.0.0`);
23701
+ return refused3("older", `the installed rafa ${installed} is older than the project's ${recorded}, and no downgrade runs before 1.0.0`);
23076
23702
  }
23077
23703
  if (to.major !== from.major) {
23078
- return refused2("newer-major", `the installed rafa ${installed} is a newer major than the project's ${recorded}; that move is rafa update latest`);
23704
+ return refused3("newer-major", `the installed rafa ${installed} is a newer major than the project's ${recorded}; that move is rafa update latest`);
23079
23705
  }
23706
+ if (to.minor !== from.minor && to.major === 0)
23707
+ return { kind: "minor", from: recorded, to: installed };
23080
23708
  if (to.minor !== from.minor) {
23081
- return refused2("newer-minor", `the installed rafa ${installed} is a newer minor than the project's ${recorded}; that move is rafa update next`);
23709
+ return refused3("newer-minor", `the installed rafa ${installed} is a newer minor than the project's ${recorded}; that move is rafa update next`);
23082
23710
  }
23083
23711
  return order === 0 ? { kind: "same", to: installed } : { kind: "patch", from: recorded, to: installed };
23084
23712
  }
@@ -23102,7 +23730,7 @@ var DEFAULT_UPDATE_CURRENT_SEAMS = Object.freeze({
23102
23730
  openPrompter: () => createLinePrompter(process.stdin, process.stderr),
23103
23731
  lockRootOf: (root) => checkoutLockRoot(root, process.cwd())
23104
23732
  });
23105
- function readSwitch4(context, name) {
23733
+ function readSwitch5(context, name) {
23106
23734
  const value = context.flags[name];
23107
23735
  if (value === undefined || value === false || value === "false")
23108
23736
  return false;
@@ -23232,10 +23860,10 @@ async function applyAndReport(plan2, gh, context) {
23232
23860
  return applied;
23233
23861
  }
23234
23862
  function refusedLabels(applied) {
23235
- const refused3 = applied.labels.filter((part) => part.outcome === "refused");
23236
- if (refused3.length === 0)
23863
+ const refused4 = applied.labels.filter((part) => part.outcome === "refused");
23864
+ if (refused4.length === 0)
23237
23865
  return null;
23238
- return new CommandExit(1, `${String(refused3.length)} board label(s) were not created; everything else was applied.` + " Run rafa update current again once gh can create them.");
23866
+ return new CommandExit(1, `${String(refused4.length)} board label(s) were not created; everything else was applied.` + " Run rafa update current again once gh can create them.");
23239
23867
  }
23240
23868
  function stopLine2(plan2, dryRun) {
23241
23869
  if (!planChanges(plan2))
@@ -23246,8 +23874,8 @@ function stopLine2(plan2, dryRun) {
23246
23874
  }
23247
23875
  async function runUpdateCurrent(context, seams) {
23248
23876
  expectNoArgument(context.args, USAGE43);
23249
- const dryRun = readSwitch4(context, "dry-run");
23250
- const yes = readSwitch4(context, "yes");
23877
+ const dryRun = readSwitch5(context, "dry-run");
23878
+ const yes = readSwitch5(context, "yes");
23251
23879
  const project = projectOf15(context);
23252
23880
  const lockRoot = seams.lockRootOf(project.root);
23253
23881
  const range = readRange(lockRoot, seams.installed);
@@ -23283,8 +23911,8 @@ function createUpdateCurrentCommand(seams = DEFAULT_UPDATE_CURRENT_SEAMS) {
23283
23911
  name: "update current",
23284
23912
  subject: "update",
23285
23913
  action: "current",
23286
- summary: "bring this project to the installed rafa, within its patch range",
23287
- description: "Brings the project to the installed rafa when the installed version shares the major and" + ` minor of the version \`${LOCK_FILE}\` records at the project root, and is the same patch or newer.` + " A project with no lock is adopted at the installed version. It creates the missing `.rafa/` folders" + " and the missing board labels on a GitHub board, runs the deprecations step (none registered yet)," + ` and writes \`${LOCK_FILE}\`. It prints every change first; \`--dry-run\` stops there. Otherwise it` + " warns that the changes cannot be rolled back and asks once, `--yes` answering; with no terminal and" + " no `--yes` it refuses with exit code 1. A newer minor or major, or an installed rafa older than the" + " lock, refuses with exit code 1, and so does a board label `gh` would not create, once everything" + " else is applied. A lock or config that cannot be read, or a `.rafa/` path holding something else," + " exits 2 before anything is asked; a folder or the lock that cannot be written exits 2 naming the" + " step and what was already written. In a linked worktree the lock is the worktree's, on its branch." + " With `--output=json` the plan and what was applied are the data of the terminal result event.",
23914
+ summary: "bring this project to the installed rafa: a newer patch, or any newer minor below 1.0.0",
23915
+ description: "Brings the project to the installed rafa when the installed version shares the major and" + ` minor of the version \`${LOCK_FILE}\` records at the project root, and is the same patch or newer;` + " below 1.0.0 it also crosses newer minors, as `0.34.1` to `0.36.0`." + " A project with no lock is adopted at the installed version. It creates the missing `.rafa/` folders" + " and the missing board labels on a GitHub board, runs the deprecations step (none registered yet)," + ` and writes \`${LOCK_FILE}\`. It prints every change first; \`--dry-run\` stops there. Otherwise it` + " warns that the changes cannot be rolled back and asks once, `--yes` answering; with no terminal and" + " no `--yes` it refuses with exit code 1. A newer minor from 1.0.0 on, a newer major, or an installed rafa older than the" + " lock, refuses with exit code 1, and so does a board label `gh` would not create, once everything" + " else is applied. A lock or config that cannot be read, or a `.rafa/` path holding something else," + " exits 2 before anything is asked; a folder or the lock that cannot be written exits 2 naming the" + " step and what was already written. In a linked worktree the lock is the worktree's, on its branch." + " With `--output=json` the plan and what was applied are the data of the terminal result event.",
23288
23916
  args: [],
23289
23917
  flags: [
23290
23918
  { name: "dry-run", description: "Print every change and write nothing.", type: "boolean" },
@@ -23323,7 +23951,7 @@ var self_default = createUpdateStub({ action: "self", summary: "update rafa itse
23323
23951
  var CORE_SUBJECTS = Object.freeze([
23324
23952
  { name: "plan", summary: "create plans from specs; list, show and validate them; read their risk and needs" },
23325
23953
  { name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
23326
- { name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
23954
+ { name: "issue", summary: "the tracker: list, show, create, edit, comment on and move issues; mark one ready, unblock it and check its references" },
23327
23955
  { name: "pr", summary: "the pull request of a branch: one line, in full or in the browser; list, wait on, merge and triage them" },
23328
23956
  { name: "effort", summary: "collect session and commit rows; report per plan; read and repair the store: its schema, a copy for testing, migrations, and fixes; merge or import another device's store; move an NDJSON store to SQLite" },
23329
23957
  { name: "module", summary: "list the configured modules; run an action a module provides" },
@@ -23358,6 +23986,7 @@ var CORE_COMMANDS = Object.freeze([
23358
23986
  ready_default,
23359
23987
  unblock_default,
23360
23988
  check_default2,
23989
+ edit_default,
23361
23990
  current_default,
23362
23991
  show_default6,
23363
23992
  view_default,