@open-tomato/rafa 0.32.0 → 0.34.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.
Files changed (27) hide show
  1. package/README.md +134 -4
  2. package/dist/bundled/agents/build-error-resolver.md +22 -14
  3. package/dist/bundled/agents/code-reviewer.md +22 -14
  4. package/dist/bundled/agents/loop-implementer.md +22 -8
  5. package/dist/bundled/agents/tdd-guide.md +23 -15
  6. package/dist/bundled/operators/agents/rafa-stretch-engineer.md +177 -0
  7. package/dist/bundled/operators/agents/rafa-stretch-watchtower.md +91 -0
  8. package/dist/bundled/operators/skills/rafa-stretch-gap-log/SKILL.md +72 -0
  9. package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +77 -0
  10. package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +111 -0
  11. package/dist/bundled/skills/dev-planner/SKILL.md +22 -4
  12. package/dist/bundled/skills/git-workflow/SKILL.md +1 -1
  13. package/dist/cli.js +4495 -1789
  14. package/dist/{describe-d31jy6dk.js → describe-5hqfwbjc.js} +3 -3
  15. package/dist/effort/store/index.js +24 -4
  16. package/dist/{index-mt6hpsry.js → index-3mzw3kzh.js} +2 -2
  17. package/dist/{index-qgayjcym.js → index-4b3m7s0z.js} +7148 -4603
  18. package/dist/{index-22stnzde.js → index-b8t7q5hc.js} +666 -1137
  19. package/dist/{index-wvm2s4pr.js → index-bvfbfa63.js} +699 -132
  20. package/dist/{index-rs79dq9e.js → index-fzhd3d4y.js} +22 -4
  21. package/dist/{index-5tftj69y.js → index-k6sd4ym5.js} +2 -2
  22. package/dist/{index-pbeyv281.js → index-v8d7c6sw.js} +2 -2
  23. package/dist/{index-9aph6ft9.js → index-vp0xghk2.js} +167 -5
  24. package/dist/index.js +27 -7
  25. package/dist/plan/index.js +3 -3
  26. package/dist/plan-prompt.md +12 -0
  27. package/package.json +5 -2
@@ -60,9 +60,11 @@ import {
60
60
  TRIAGE_MODULE,
61
61
  VENDOR_COMMAND,
62
62
  WHEN_TO_USE_HEADING,
63
+ WORKTREES_SUBDIR,
63
64
  actionInvocation,
64
65
  actionOutput,
65
66
  addBlockerToBody,
67
+ addResolveWorktree,
66
68
  alignRows,
67
69
  allocateFragmentName,
68
70
  announceRiskTotal,
@@ -92,6 +94,8 @@ import {
92
94
  claimBranchIssue,
93
95
  classifySkill,
94
96
  cleanUpAfterMerge,
97
+ cleanupCounts,
98
+ cleanupSteps,
95
99
  closedIssuesIn,
96
100
  collect,
97
101
  collectSessionRow,
@@ -104,7 +108,6 @@ import {
104
108
  createGhBoardListing,
105
109
  createGhIssueBoard,
106
110
  createGhOpenPullRequests,
107
- createGhPermissions,
108
111
  createGhPullRequests,
109
112
  createGhRoadmapBody,
110
113
  createGhRoadmapSearch,
@@ -116,9 +119,12 @@ import {
116
119
  createPlanDirNames,
117
120
  createPlanRefsVerifier,
118
121
  createStartPreflightClaim,
122
+ crossRepositoryRefusal,
123
+ defaultCleanupSeams,
119
124
  developmentApplied,
120
125
  directoryPrefix,
121
126
  driftLines,
127
+ dryRunLines,
122
128
  editChecklist,
123
129
  effortSyncRefusal,
124
130
  endWithNextStep,
@@ -205,16 +211,19 @@ import {
205
211
  parseWorkingTree,
206
212
  parseWorktrees,
207
213
  pathDirectories,
214
+ phaseNote,
215
+ phasedCounts,
208
216
  pickSession,
209
217
  plan,
210
218
  planLabel,
211
219
  previousDir,
212
220
  projectRoot,
213
221
  provenanceBlock,
222
+ pullBeforeRead,
214
223
  pushOwnershipCommit,
224
+ pushResolved,
215
225
  rankCandidates,
216
226
  readAgentDirectory,
217
- readAuthorTrust,
218
227
  readBinPath,
219
228
  readBlockedBy,
220
229
  readBlockedIssues,
@@ -226,6 +235,7 @@ import {
226
235
  readChoiceFlag,
227
236
  readClaimBranch,
228
237
  readClaimState,
238
+ readCleanup,
229
239
  readConfiguredRelations,
230
240
  readCopyRefs,
231
241
  readDoctorEffortSync,
@@ -268,6 +278,7 @@ import {
268
278
  refusal,
269
279
  refuseMisplacedRuntime,
270
280
  releaseCommitSubject,
281
+ removeResolveWorktree,
271
282
  renderBlockedIssues,
272
283
  renderCancelComment,
273
284
  renderCloseComment,
@@ -283,7 +294,6 @@ import {
283
294
  report,
284
295
  reportFollowUps,
285
296
  requireGhProvider,
286
- requireTrustedAuthor,
287
297
  resolveAgentRoster,
288
298
  resolveCreateSpec,
289
299
  resolveDefaultBoard,
@@ -294,10 +304,12 @@ import {
294
304
  resolvePrProvider,
295
305
  resolveReleaseEnabled,
296
306
  resolveTracker,
307
+ resolveWorktreePath,
297
308
  restampCopyRefs,
298
309
  rootCandidates,
299
310
  rootRefusal,
300
311
  runAction,
312
+ runCleanupSteps,
301
313
  runIssueList,
302
314
  samePlace,
303
315
  scanClaimBranches,
@@ -324,7 +336,6 @@ import {
324
336
  toolOf,
325
337
  trendReportOf,
326
338
  triageReport,
327
- trustRefusalClause,
328
339
  unaskedReasonMessage,
329
340
  unblockAfterMerge,
330
341
  unblock_default,
@@ -342,9 +353,10 @@ import {
342
353
  waitForChecks,
343
354
  waitingSettingsOf,
344
355
  watchDryEpic,
356
+ worktreeAt,
345
357
  writeSession,
346
358
  writeShippedFile
347
- } from "./index-qgayjcym.js";
359
+ } from "./index-4b3m7s0z.js";
348
360
  import {
349
361
  PROBE_TIMEOUT_MS,
350
362
  loadPlanPrerequisites,
@@ -356,7 +368,7 @@ import {
356
368
  runPreflight,
357
369
  splitBlockerComment,
358
370
  trackerPathFor
359
- } from "./index-pbeyv281.js";
371
+ } from "./index-v8d7c6sw.js";
360
372
  import {
361
373
  ACTION_HEADING,
362
374
  CAUSE_HEADING,
@@ -389,6 +401,7 @@ import {
389
401
  copyEffortStore,
390
402
  countTasks,
391
403
  createFileSync,
404
+ createGhPermissions,
392
405
  createGhRunner,
393
406
  createGitRunner,
394
407
  effortStoreDir,
@@ -419,6 +432,7 @@ import {
419
432
  plansDirAt,
420
433
  plural,
421
434
  positionFilePath,
435
+ readAuthorTrust,
422
436
  readFrontmatter,
423
437
  readFrontmatterDocument,
424
438
  readHopRecord,
@@ -430,22 +444,24 @@ import {
430
444
  rehome,
431
445
  renderFrontmatter,
432
446
  requireProject,
447
+ requireTrustedAuthor,
433
448
  resolvePlansDir,
434
449
  resolveProjectConfig,
435
450
  runClaudeCaptured,
436
- runsDir,
437
451
  scanLayout,
438
452
  scopeAt,
439
453
  selectEffortStore,
454
+ sessionPhase,
440
455
  sessionSpawnEnv,
441
456
  spawnClaudeCaptured,
442
457
  staleAgainst,
443
458
  toHeldRecords,
459
+ trustRefusalClause,
444
460
  version,
445
461
  versionLine,
446
462
  writeFrontmatter,
447
463
  writePositionFile
448
- } from "./index-wvm2s4pr.js";
464
+ } from "./index-bvfbfa63.js";
449
465
  import {
450
466
  CONFIG_DEFAULTS,
451
467
  CONFIG_FILE,
@@ -460,7 +476,7 @@ import {
460
476
  readItemBytes,
461
477
  readRafaBlocks,
462
478
  resolveTiers
463
- } from "./index-9aph6ft9.js";
479
+ } from "./index-vp0xghk2.js";
464
480
  import {
465
481
  promotable
466
482
  } from "./index-d6d0942q.js";
@@ -468,10 +484,10 @@ import {
468
484
  createCommandRegistry,
469
485
  describeRegistry,
470
486
  mountKey
471
- } from "./index-5tftj69y.js";
487
+ } from "./index-k6sd4ym5.js";
472
488
  import {
473
489
  CommandExit
474
- } from "./index-mt6hpsry.js";
490
+ } from "./index-3mzw3kzh.js";
475
491
  import {
476
492
  CLAIMS_STALE_DISABLED,
477
493
  MERGE_METHODS,
@@ -484,7 +500,7 @@ import {
484
500
  isSkillTier,
485
501
  messageOf,
486
502
  parseBudgetUsd
487
- } from "./index-rs79dq9e.js";
503
+ } from "./index-fzhd3d4y.js";
488
504
  import {
489
505
  triggerKey
490
506
  } from "./index-wp8c28fd.js";
@@ -3797,710 +3813,6 @@ function createClaimTakeCommand(makeSeams = DEFAULT_SEAMS4) {
3797
3813
  }
3798
3814
  var take_default = createClaimTakeCommand();
3799
3815
 
3800
- // src/cleanup/branches.ts
3801
- var BRANCH_FORMAT = [
3802
- "%(refname:lstrip=2)",
3803
- "%(upstream:short)",
3804
- "%(upstream:track,nobracket)",
3805
- "%(committerdate:unix)",
3806
- "%(HEAD)"
3807
- ].join("%09");
3808
- var REMOTE2 = "origin";
3809
- var FIELD_COUNT = 5;
3810
- var MS_PER_SECOND = 1000;
3811
- var GONE_TRACK = "gone";
3812
- function resolveBaseBranch(git, configured) {
3813
- if (configured !== null) {
3814
- return configured;
3815
- }
3816
- const result = git(["symbolic-ref", "--quiet", "--short", `refs/remotes/${REMOTE2}/HEAD`]);
3817
- const target = result.stdout.trim();
3818
- const prefix = `${REMOTE2}/`;
3819
- if (!result.ok || !target.startsWith(prefix) || target.length === prefix.length) {
3820
- return DEFAULT_BASE_BRANCH;
3821
- }
3822
- return target.slice(prefix.length);
3823
- }
3824
- function isKept(name, keep) {
3825
- return keep.some((pattern) => new Bun.Glob(pattern).match(name));
3826
- }
3827
- function readBranches(git, settings) {
3828
- const base = resolveBaseBranch(git, settings.base);
3829
- const result = git(["for-each-ref", `--format=${BRANCH_FORMAT}`, "refs/heads"]);
3830
- if (!result.ok) {
3831
- const said = gitSaid(result);
3832
- const detail = said === "" ? "git for-each-ref failed" : `git for-each-ref failed: ${said}`;
3833
- return { ok: false, detail };
3834
- }
3835
- const branches = [];
3836
- for (const line of result.stdout.split(`
3837
- `)) {
3838
- if (line === "") {
3839
- continue;
3840
- }
3841
- const parsed = parseBranchLine(line);
3842
- if (typeof parsed === "string") {
3843
- return { ok: false, detail: parsed };
3844
- }
3845
- if (!parsed.current && parsed.branch.name !== base && !isKept(parsed.branch.name, settings.keep)) {
3846
- branches.push(parsed.branch);
3847
- }
3848
- }
3849
- return { ok: true, base, branches };
3850
- }
3851
- function parseBranchLine(line) {
3852
- const fields = line.split("\t");
3853
- const [name = "", upstreamField = "", track = "", unix = "", head = ""] = fields;
3854
- if (fields.length !== FIELD_COUNT || name === "") {
3855
- return `git for-each-ref wrote a line with ${String(fields.length)} fields, expected ${String(FIELD_COUNT)}: ${JSON.stringify(line)}`;
3856
- }
3857
- const seconds = Number(unix);
3858
- if (unix === "" || !Number.isInteger(seconds)) {
3859
- return `git for-each-ref wrote no commit date for ${name}: ${JSON.stringify(unix)}`;
3860
- }
3861
- const lastCommit = new Date(seconds * MS_PER_SECOND);
3862
- const current = head === "*";
3863
- if (upstreamField === "") {
3864
- return { current, branch: { name, upstream: null, gone: false, ahead: null, lastCommit } };
3865
- }
3866
- if (track === GONE_TRACK) {
3867
- return { current, branch: { name, upstream: upstreamField, gone: true, ahead: null, lastCommit } };
3868
- }
3869
- return { current, branch: { name, upstream: upstreamField, gone: false, ahead: aheadOf(track), lastCommit } };
3870
- }
3871
- function aheadOf(track) {
3872
- const count = /\bahead (\d+)/.exec(track)?.[1];
3873
- if (count === undefined) {
3874
- return 0;
3875
- }
3876
- return Number(count);
3877
- }
3878
-
3879
- // src/cleanup/groups.ts
3880
- var MERGED_STATE_UNKNOWN = "merged state unknown";
3881
- var MS_PER_DAY = 24 * 60 * 60 * 1000;
3882
- function unreachableNote(detail) {
3883
- return `pull request provider unreachable (${detail}); merged pull requests not read, grouped from git alone`;
3884
- }
3885
- function baseUnreadNote(base, detail) {
3886
- return `could not read which branches are merged into ${base} (${detail}); none is listed as merged into it`;
3887
- }
3888
- async function readProviderMerges(pulls) {
3889
- if (pulls === null) {
3890
- return { state: "none" };
3891
- }
3892
- try {
3893
- return { state: "read", pullRequests: await pulls.listMerged() };
3894
- } catch (error) {
3895
- return { state: "unreachable", detail: firstLine(messageOf(error)) };
3896
- }
3897
- }
3898
- function classifyBranches(git, read, provider, settings) {
3899
- const notes = provider.state === "unreachable" ? [unreachableNote(provider.detail)] : [];
3900
- const baseRef = `refs/heads/${read.base}`;
3901
- const mergedRead = git(["branch", "--merged", baseRef, "--format=%(refname:lstrip=2)"]);
3902
- const reachable = new Set(mergedRead.ok ? lines(mergedRead.stdout) : []);
3903
- if (!mergedRead.ok) {
3904
- notes.push(baseUnreadNote(read.base, failure("git branch --merged", mergedRead)));
3905
- }
3906
- const named = provider.state === "read" ? namedPullRequests(provider.pullRequests, read.branches) : new Map;
3907
- const tips = readTips(git, named);
3908
- if (typeof tips === "string") {
3909
- return { ok: false, detail: tips };
3910
- }
3911
- const merged = [];
3912
- const stale = [];
3913
- const notPushed = [];
3914
- const context = {
3915
- unknown: provider.state === "none",
3916
- exclude: mergedRead.ok ? [baseRef] : []
3917
- };
3918
- for (const branch of read.branches) {
3919
- const pulls = named.get(branch.name) ?? [];
3920
- const atTip = pulls.find((pull) => pull.headRefOid === tips.get(branch.name)) ?? null;
3921
- const mergedBy = mergedReadings(branch, reachable.has(branch.name), atTip);
3922
- if (mergedBy.length > 0) {
3923
- merged.push(mergedRow(branch, mergedBy, atTip, read.base));
3924
- continue;
3925
- }
3926
- const suffix = rowSuffix(context, pulls[0] ?? null);
3927
- if (branch.upstream === null || (branch.ahead ?? 0) > 0) {
3928
- const commits = countLost(git, branch.name, context.exclude);
3929
- if (typeof commits === "string") {
3930
- return { ok: false, detail: commits };
3931
- }
3932
- notPushed.push(notPushedRow(branch, commits, suffix));
3933
- continue;
3934
- }
3935
- const age = settings.now.getTime() - branch.lastCommit.getTime();
3936
- if (age > settings.staleDays * MS_PER_DAY) {
3937
- const idleDays = Math.floor(age / MS_PER_DAY);
3938
- stale.push({ group: "stale", branch, ticked: false, idleDays, reason: `no commit in ${plural2(idleDays, "day")}${suffix}` });
3939
- }
3940
- }
3941
- return { ok: true, base: read.base, merged, stale, notPushed, notes };
3942
- }
3943
- function namedPullRequests(pullRequests, branches) {
3944
- const listed = new Set(branches.map((branch) => branch.name));
3945
- const named = new Map;
3946
- for (const pull of pullRequests) {
3947
- if (listed.has(pull.headRefName)) {
3948
- named.set(pull.headRefName, [...named.get(pull.headRefName) ?? [], pull]);
3949
- }
3950
- }
3951
- return named;
3952
- }
3953
- function readTips(git, named) {
3954
- const tips = new Map;
3955
- if (named.size === 0) {
3956
- return tips;
3957
- }
3958
- const result = git(["for-each-ref", "--format=%(refname:lstrip=2)%09%(objectname)", "refs/heads"]);
3959
- if (!result.ok) {
3960
- return failure("git for-each-ref", result);
3961
- }
3962
- for (const line of lines(result.stdout)) {
3963
- const [name = "", oid = ""] = line.split("\t");
3964
- tips.set(name, oid);
3965
- }
3966
- return tips;
3967
- }
3968
- function mergedReadings(branch, reachable, atTip) {
3969
- const readings = [];
3970
- if (reachable) {
3971
- readings.push("base");
3972
- }
3973
- if (atTip !== null) {
3974
- readings.push("pull-request");
3975
- }
3976
- if (branch.gone) {
3977
- readings.push("gone");
3978
- }
3979
- return readings;
3980
- }
3981
- function mergedRow(branch, mergedBy, pullRequest, base) {
3982
- const reasons = mergedBy.map((by) => {
3983
- if (by === "base") {
3984
- return `merged into ${base}`;
3985
- }
3986
- if (by === "pull-request") {
3987
- return `pull request #${String(pullRequest?.number)} merged`;
3988
- }
3989
- return `upstream ${String(branch.upstream)} is gone`;
3990
- });
3991
- return { group: "merged", branch, ticked: true, mergedBy, pullRequest, reason: reasons.join("; ") };
3992
- }
3993
- function notPushedRow(branch, commits, suffix) {
3994
- const why = branch.upstream === null ? "no upstream" : `${String(branch.ahead)} ahead of ${branch.upstream}`;
3995
- const reason = `${why}; ${plural2(commits, "commit")} not on any remote${suffix}`;
3996
- return { group: "not-pushed", branch, ticked: false, commits, reason };
3997
- }
3998
- function rowSuffix(context, elsewhere) {
3999
- if (context.unknown) {
4000
- return `; ${MERGED_STATE_UNKNOWN}`;
4001
- }
4002
- if (elsewhere !== null) {
4003
- return `; pull request #${String(elsewhere.number)} merged at another commit`;
4004
- }
4005
- return "";
4006
- }
4007
- function countLost(git, name, exclude) {
4008
- const result = git(["rev-list", "--count", `refs/heads/${name}`, "--not", "--remotes", ...exclude, "--"]);
4009
- const count = Number(result.stdout.trim());
4010
- if (!result.ok || result.stdout.trim() === "" || !Number.isInteger(count)) {
4011
- return failure(`git rev-list --count ${name}`, result);
4012
- }
4013
- return count;
4014
- }
4015
- function failure(what, result) {
4016
- const said = firstLine(gitSaid(result));
4017
- return said === "" ? `${what} failed` : `${what} failed: ${said}`;
4018
- }
4019
- function plural2(count, noun) {
4020
- return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
4021
- }
4022
- function lines(text) {
4023
- return text.split(`
4024
- `).filter((line) => line !== "");
4025
- }
4026
- function firstLine(text) {
4027
- return text.split(`
4028
- `).map((line) => line.trim()).find((line) => line !== "") ?? "";
4029
- }
4030
-
4031
- // src/cleanup/worktrees.ts
4032
- import { realpathSync as realpathSync3, statSync as statSync3 } from "fs";
4033
- import { join as join7, sep } from "path";
4034
-
4035
- // src/pr/worktree.ts
4036
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
4037
- import { join as join6 } from "path";
4038
- var WORKTREES_SUBDIR = join6(".rafa", "worktrees");
4039
- var WORKTREE_PREFIX = "pr-";
4040
- var DEFAULT_REMOTE = "origin";
4041
- var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
4042
- var FORCING_FLAG = /^(?:-f|--force(?:-with-lease|-if-includes)?(?:=.*)?|--mirror)$/;
4043
- var FORCING_REFSPEC_MARK = "+";
4044
- function resolveWorktreePath(home, number) {
4045
- return join6(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${number}`);
4046
- }
4047
- function crossRepositoryRefusal(pr) {
4048
- if (!pr.isCrossRepository)
4049
- return null;
4050
- return `${REFUSAL_PREFIX} #${pr.number}: its head branch \`${pr.headRefName}\` is on a fork.` + " There is no branch in this repository to check a worktree out of, and the resolution" + " would have to be pushed to a repository this checkout does not own." + ` Assess it with \`rafa pr triage ${pr.number}\`, which needs no worktree, and resolve it` + " where the fork is.";
4051
- }
4052
- function frozenStep(step) {
4053
- return Object.freeze({ ...step, argv: Object.freeze(step.argv) });
4054
- }
4055
- function addWorktreeStep(path, branch) {
4056
- return frozenStep({
4057
- id: "add",
4058
- label: `add a worktree for ${branch} at ${path}`,
4059
- argv: ["git", "worktree", "add", path, branch]
4060
- });
4061
- }
4062
- function removeWorktreeStep(path) {
4063
- return frozenStep({
4064
- id: "remove",
4065
- label: `remove the worktree at ${path}`,
4066
- argv: ["git", "worktree", "remove", path]
4067
- });
4068
- }
4069
- function pushResolvedStep(branch, remote = DEFAULT_REMOTE) {
4070
- return frozenStep({
4071
- id: "push",
4072
- label: `push ${branch} to ${remote}`,
4073
- argv: ["git", "push", remote, branch]
4074
- });
4075
- }
4076
- function worktreeCommandLine(step) {
4077
- return step.argv.map((word) => shellQuote(word)).join(" ");
4078
- }
4079
- function forcingWords(argv) {
4080
- return argv.filter((word) => FORCING_FLAG.test(word) || word.startsWith(FORCING_REFSPEC_MARK));
4081
- }
4082
- function forcedPushRefusal(argv) {
4083
- const forcing = forcingWords(argv);
4084
- if (forcing.length === 0)
4085
- return null;
4086
- const words = forcing.map((word) => `\`${word}\``).join(", ");
4087
- return `${REFUSAL_PREFIX} a push that git would force: ${words}.` + " A resolve run never overwrites what is on the remote, so nothing was pushed.";
4088
- }
4089
- function runStep(git, step) {
4090
- const result = git(step.argv.slice(1));
4091
- return {
4092
- id: step.id,
4093
- ok: result.ok,
4094
- command: worktreeCommandLine(step),
4095
- said: gitSaid(result)
4096
- };
4097
- }
4098
- function addResolveWorktree(git, path, branch) {
4099
- return runStep(git, addWorktreeStep(path, branch));
4100
- }
4101
- function removeResolveWorktree(git, path) {
4102
- return runStep(git, removeWorktreeStep(path));
4103
- }
4104
- function pushResolved(git, branch, remote = DEFAULT_REMOTE) {
4105
- const step = pushResolvedStep(branch, remote);
4106
- const refusal3 = forcedPushRefusal(step.argv);
4107
- if (refusal3 === null)
4108
- return runStep(git, step);
4109
- return { id: step.id, ok: false, command: worktreeCommandLine(step), said: refusal3 };
4110
- }
4111
- function pathForms(path) {
4112
- if (!existsSync3(path))
4113
- return [path];
4114
- return [path, realpathSync2(path)];
4115
- }
4116
- function worktreeAt(git, path) {
4117
- const listed = git(["worktree", "list", "--porcelain"]);
4118
- if (!listed.ok)
4119
- return null;
4120
- const forms = pathForms(path);
4121
- return parseWorktrees(listed.stdout).find((entry) => forms.includes(entry.path)) ?? null;
4122
- }
4123
-
4124
- // src/cleanup/worktrees.ts
4125
- var CLAUDE_WORKTREES_SUBDIR = join7(".claude", "worktrees");
4126
- var WORKTREE_LIST = Object.freeze(["worktree", "list", "--porcelain"]);
4127
- var GIT_DIR = Object.freeze(["rev-parse", "--absolute-git-dir"]);
4128
- var WORKTREE_STATUS = Object.freeze([
4129
- "--no-optional-locks",
4130
- "status",
4131
- "--porcelain=v1",
4132
- "--untracked-files=all"
4133
- ]);
4134
- var ADMIN_FILES = Object.freeze(["HEAD", join7("logs", "HEAD"), "index"]);
4135
- var MS_PER_DAY2 = 24 * 60 * 60 * 1000;
4136
- var UNTRACKED_MARK = "?? ";
4137
- var LOCKED_LINE = "locked";
4138
- var LIVE_STATES = new Set(["running", "paused"]);
4139
- function defaultWorktreeSeams(cwd) {
4140
- return {
4141
- git: createGitRunner(cwd),
4142
- gitAt: createGitRunner,
4143
- sessions: (root) => readSessions(root),
4144
- modifiedAt: (path) => {
4145
- try {
4146
- return statSync3(path).mtime;
4147
- } catch {
4148
- return null;
4149
- }
4150
- },
4151
- realPath: (path) => {
4152
- try {
4153
- return realpathSync3(path);
4154
- } catch {
4155
- return path;
4156
- }
4157
- }
4158
- };
4159
- }
4160
- function readWorktrees(seams, read) {
4161
- const listed = seams.git(WORKTREE_LIST);
4162
- if (!listed.ok) {
4163
- return { ok: false, detail: failure2("git worktree list", listed) };
4164
- }
4165
- const entries = parseWorktrees(listed.stdout);
4166
- const main = entries[0]?.path;
4167
- if (main === undefined) {
4168
- return { ok: true, worktrees: [] };
4169
- }
4170
- const roots = [
4171
- ...pathForms2(seams, join7(main, CLAUDE_WORKTREES_SUBDIR)),
4172
- ...pathForms2(seams, join7(read.home, WORKTREES_SUBDIR))
4173
- ];
4174
- const locks = parseLocks(listed.stdout);
4175
- const context = {
4176
- seams,
4177
- read,
4178
- cwd: pathForms2(seams, read.cwd),
4179
- projectSessions: readLive(seams, read.projectRoot),
4180
- merged: new Set(read.mergedBranches)
4181
- };
4182
- const worktrees = entries.slice(1).filter((entry) => roots.some((root) => isInside(entry.path, root))).map((entry) => worktreeRow(context, entry.path, entry.branch, locks.get(entry.path) ?? null));
4183
- return { ok: true, worktrees };
4184
- }
4185
- function parseLocks(stdout) {
4186
- const locks = new Map;
4187
- let path = null;
4188
- for (const raw of stdout.split(`
4189
- `)) {
4190
- const line = raw.replace(/\r$/, "");
4191
- if (line.startsWith("worktree ")) {
4192
- path = line.slice("worktree ".length).trim();
4193
- continue;
4194
- }
4195
- if (path !== null && (line === LOCKED_LINE || line.startsWith(`${LOCKED_LINE} `))) {
4196
- locks.set(path, line.slice(LOCKED_LINE.length).trim());
4197
- }
4198
- }
4199
- return locks;
4200
- }
4201
- function countChanges(stdout) {
4202
- const entries = stdout.split(`
4203
- `).filter((line) => line.trim() !== "");
4204
- const untracked = entries.filter((line) => line.startsWith(UNTRACKED_MARK)).length;
4205
- return { changed: entries.length - untracked, untracked };
4206
- }
4207
- function worktreeRow(context, path, branch, lock) {
4208
- const { seams, read } = context;
4209
- const git = seams.gitAt(path);
4210
- const modified = lastModified(seams, git, path);
4211
- const blockers = [];
4212
- blockers.push(...dirtyBlockers(git));
4213
- if (lock !== null) {
4214
- const reason = lock === "" ? LOCKED_LINE : `${LOCKED_LINE}: ${lock}`;
4215
- blockers.push({ kind: "locked", reason });
4216
- }
4217
- if (context.cwd.some((dir) => dir === path || isInside(dir, path))) {
4218
- blockers.push({ kind: "current", reason: "rafa cleanup runs from it" });
4219
- }
4220
- blockers.push(...sessionBlockers(context, path, branch));
4221
- blockers.push(...recentBlockers(modified, read));
4222
- const ordered = orderBlockers(blockers);
4223
- const branchMerged = branch !== null && context.merged.has(branch);
4224
- const tickable = ordered.length === 0;
4225
- return {
4226
- path,
4227
- branch,
4228
- lastModified: typeof modified === "string" ? null : modified,
4229
- branchMerged,
4230
- blockers: ordered,
4231
- tickable,
4232
- ticked: tickable && branchMerged,
4233
- reason: rowReason(ordered, branch, branchMerged)
4234
- };
4235
- }
4236
- function lastModified(seams, git, path) {
4237
- const gitDir = git(GIT_DIR);
4238
- const adminDir = gitDir.stdout.trim();
4239
- if (!gitDir.ok || adminDir === "") {
4240
- return failure2("git rev-parse --absolute-git-dir", gitDir);
4241
- }
4242
- const times = [path, ...ADMIN_FILES.map((file) => join7(adminDir, file))].map((file) => seams.modifiedAt(file)).filter((time) => time !== null).map((time) => time.getTime());
4243
- if (times.length === 0) {
4244
- return `no modification time could be read under ${path} or ${adminDir}`;
4245
- }
4246
- return new Date(Math.max(...times));
4247
- }
4248
- function dirtyBlockers(git) {
4249
- const status = git(WORKTREE_STATUS);
4250
- if (!status.ok) {
4251
- return [{ kind: "unreadable", reason: `its changes could not be read: ${failure2("git status", status)}` }];
4252
- }
4253
- const { changed, untracked } = countChanges(status.stdout);
4254
- const parts = [
4255
- ...changed > 0 ? [plural3(changed, "uncommitted change")] : [],
4256
- ...untracked > 0 ? [plural3(untracked, "untracked file")] : []
4257
- ];
4258
- return parts.length === 0 ? [] : [{ kind: "dirty", reason: parts.join(", ") }];
4259
- }
4260
- function sessionBlockers(context, path, branch) {
4261
- const own = readLive(context.seams, path);
4262
- const readings = [own];
4263
- if (typeof context.projectSessions === "string") {
4264
- readings.push(context.projectSessions);
4265
- } else if (branch !== null) {
4266
- readings.push(context.projectSessions.filter((record) => record.branch === branch));
4267
- }
4268
- const blockers = [];
4269
- for (const reading of readings) {
4270
- if (typeof reading === "string") {
4271
- blockers.push({ kind: "unreadable", reason: reading });
4272
- continue;
4273
- }
4274
- for (const record of reading) {
4275
- blockers.push({ kind: "session", reason: `loop session ${record.sessionId} is ${record.state} in it` });
4276
- }
4277
- }
4278
- return blockers;
4279
- }
4280
- function recentBlockers(modified, read) {
4281
- if (typeof modified === "string") {
4282
- return [{ kind: "unreadable", reason: `when it was last modified could not be read: ${modified}` }];
4283
- }
4284
- const age = read.now.getTime() - modified.getTime();
4285
- if (age >= read.idleDays * MS_PER_DAY2) {
4286
- return [];
4287
- }
4288
- const days = Math.max(0, Math.floor(age / MS_PER_DAY2));
4289
- const when = days === 0 ? "today" : `${plural3(days, "day")} ago`;
4290
- return [{ kind: "recent", reason: `modified ${when}, within cleanup.worktreeIdleDays (${String(read.idleDays)})` }];
4291
- }
4292
- function readLive(seams, root) {
4293
- try {
4294
- return seams.sessions(root).filter((record) => LIVE_STATES.has(record.state));
4295
- } catch (error) {
4296
- return `the session records under ${runsDir(root)} could not be read: ${messageOf(error)}`;
4297
- }
4298
- }
4299
- var BLOCK_ORDER = ["dirty", "locked", "current", "session", "recent", "unreadable"];
4300
- function orderBlockers(blockers) {
4301
- return BLOCK_ORDER.flatMap((kind) => blockers.filter((blocker) => blocker.kind === kind));
4302
- }
4303
- function rowReason(blockers, branch, merged) {
4304
- if (blockers.length > 0) {
4305
- return blockers.map((blocker) => blocker.reason).join("; ");
4306
- }
4307
- if (branch === null) {
4308
- return "clean; detached HEAD";
4309
- }
4310
- return merged ? `clean; branch ${branch} is merged` : `clean; branch ${branch} is not merged`;
4311
- }
4312
- function pathForms2(seams, path) {
4313
- const resolved = seams.realPath(path);
4314
- return resolved === path ? [path] : [path, resolved];
4315
- }
4316
- function isInside(path, dir) {
4317
- const prefix = dir.endsWith(sep) ? dir : `${dir}${sep}`;
4318
- return path.startsWith(prefix);
4319
- }
4320
- function failure2(what, result) {
4321
- const said = gitSaid(result).split(`
4322
- `).map((line) => line.trim()).find((line) => line !== "") ?? "";
4323
- return said === "" ? `${what} failed` : `${what} failed: ${said}`;
4324
- }
4325
- function plural3(count, noun) {
4326
- return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
4327
- }
4328
- // src/cleanup/steps.ts
4329
- var FORCING_FLAG2 = /^(?:--force(?:=.*)?|-[^-]*f[^-]*)$/;
4330
- function frozenStep2(step) {
4331
- return Object.freeze({ ...step, argv: Object.freeze([...step.argv]) });
4332
- }
4333
- function removeWorktreeStep2(path) {
4334
- return frozenStep2({
4335
- kind: "remove-worktree",
4336
- subject: path,
4337
- argv: ["git", "worktree", "remove", path],
4338
- after: null
4339
- });
4340
- }
4341
- function deleteBranchStep(branch, forced, after = null) {
4342
- const flag = forced ? "-D" : "-d";
4343
- return frozenStep2({
4344
- kind: "delete-branch",
4345
- subject: branch,
4346
- argv: ["git", "branch", flag, branch],
4347
- after
4348
- });
4349
- }
4350
- function needsForcedDelete(row) {
4351
- return !row.mergedBy.includes("base") && row.mergedBy.includes("pull-request") && row.pullRequest !== null;
4352
- }
4353
- function branchPicks(selection) {
4354
- return [
4355
- ...selection.merged.map((row) => ({ name: row.branch.name, merged: true, forced: needsForcedDelete(row) })),
4356
- ...selection.stale.map((row) => ({ name: row.branch.name, merged: false, forced: true })),
4357
- ...selection.notPushed.map((row) => ({ name: row.branch.name, merged: false, forced: true }))
4358
- ];
4359
- }
4360
- function heldReason(path) {
4361
- return `held by the worktree at ${path}; only a Merged branch is deleted with its worktree`;
4362
- }
4363
- function blockedHolderReason(path) {
4364
- return `held by the worktree at ${path}, which is not removed`;
4365
- }
4366
- function branchStep(pick, removed, blocked) {
4367
- const blockedPath = blocked.get(pick.name);
4368
- if (blockedPath !== undefined) {
4369
- return { kind: "delete-branch", subject: pick.name, reason: blockedHolderReason(blockedPath) };
4370
- }
4371
- const path = removed.get(pick.name) ?? null;
4372
- if (path !== null && !pick.merged) {
4373
- return { kind: "delete-branch", subject: pick.name, reason: heldReason(path) };
4374
- }
4375
- return deleteBranchStep(pick.name, pick.forced, path);
4376
- }
4377
- function isStep(entry) {
4378
- return "argv" in entry;
4379
- }
4380
- function holders(rows) {
4381
- return new Map(rows.flatMap((row) => row.branch === null ? [] : [[row.branch, row.path]]));
4382
- }
4383
- function cleanupSteps(selection) {
4384
- const removable = selection.worktrees.filter((row) => row.tickable);
4385
- const untickable = selection.worktrees.filter((row) => !row.tickable);
4386
- const branchEntries = branchPicks(selection).map((pick) => branchStep(pick, holders(removable), holders(untickable)));
4387
- return Object.freeze({
4388
- steps: Object.freeze([
4389
- ...removable.map((row) => removeWorktreeStep2(row.path)),
4390
- ...branchEntries.filter(isStep)
4391
- ]),
4392
- withheld: Object.freeze([
4393
- ...untickable.map((row) => ({ kind: "remove-worktree", subject: row.path, reason: row.reason })),
4394
- ...branchEntries.filter((entry) => !isStep(entry))
4395
- ])
4396
- });
4397
- }
4398
- function cleanupCommandLine(step) {
4399
- return step.argv.map((word) => shellQuote(word)).join(" ");
4400
- }
4401
- function dryRunLines(plan2) {
4402
- return plan2.steps.map((step) => cleanupCommandLine(step));
4403
- }
4404
- function forcingWords2(argv) {
4405
- return argv.filter((word) => FORCING_FLAG2.test(word));
4406
- }
4407
- function forcedFlagRefusal(argv) {
4408
- const forcing = forcingWords2(argv);
4409
- if (forcing.length === 0)
4410
- return null;
4411
- const words = forcing.map((word) => `\`${word}\``).join(", ");
4412
- return `rafa cleanup refuses a forced removal: ${words}. Nothing was run.`;
4413
- }
4414
- function afterFailedReason(path) {
4415
- return `not run: the worktree at ${path} was not removed`;
4416
- }
4417
- function notRun(step, said) {
4418
- return { step, ran: false, ok: false, command: cleanupCommandLine(step), said };
4419
- }
4420
- function runStep2(git, step, removed) {
4421
- const refusal3 = forcedFlagRefusal(step.argv);
4422
- if (refusal3 !== null)
4423
- return notRun(step, refusal3);
4424
- if (step.after !== null && !removed.has(step.after))
4425
- return notRun(step, afterFailedReason(step.after));
4426
- const result = git(step.argv.slice(1));
4427
- return { step, ran: true, ok: result.ok, command: cleanupCommandLine(step), said: gitSaid(result) };
4428
- }
4429
- function runCleanupSteps(git, plan2) {
4430
- const removed = new Set;
4431
- return plan2.steps.map((step) => {
4432
- const outcome = runStep2(git, step, removed);
4433
- if (outcome.ok && step.kind === "remove-worktree")
4434
- removed.add(step.subject);
4435
- return outcome;
4436
- });
4437
- }
4438
-
4439
- // src/cleanup/index.ts
4440
- var FETCH_PRUNE = Object.freeze(["fetch", "--prune"]);
4441
- function defaultCleanupSeams(cwd, pulls) {
4442
- return { ...defaultWorktreeSeams(cwd), pulls };
4443
- }
4444
- function fetchFailedNote(detail) {
4445
- return `git fetch --prune failed (${detail}); upstreams read as gone as of the last fetch`;
4446
- }
4447
- async function readCleanup(seams, settings) {
4448
- const notes = [];
4449
- let fetched = false;
4450
- if (settings.fetch) {
4451
- const result = seams.git(FETCH_PRUNE);
4452
- fetched = result.ok;
4453
- if (!result.ok) {
4454
- notes.push(fetchFailedNote(firstLine2(gitSaid(result)) || "git said nothing"));
4455
- }
4456
- }
4457
- const branches = readBranches(seams.git, { base: settings.base, keep: settings.keep });
4458
- if (!branches.ok) {
4459
- return branches;
4460
- }
4461
- const provider = await readProviderMerges(seams.pulls);
4462
- const groups = classifyBranches(seams.git, branches, provider, {
4463
- staleDays: settings.staleDays,
4464
- now: settings.now
4465
- });
4466
- if (!groups.ok) {
4467
- return groups;
4468
- }
4469
- const worktrees = readWorktrees(seams, {
4470
- home: settings.home,
4471
- cwd: settings.cwd,
4472
- projectRoot: settings.projectRoot,
4473
- idleDays: settings.worktreeIdleDays,
4474
- now: settings.now,
4475
- mergedBranches: groups.merged.map((row) => row.branch.name)
4476
- });
4477
- if (!worktrees.ok) {
4478
- return worktrees;
4479
- }
4480
- return {
4481
- ok: true,
4482
- base: groups.base,
4483
- fetched,
4484
- merged: groups.merged,
4485
- stale: groups.stale,
4486
- notPushed: groups.notPushed,
4487
- worktrees: worktrees.worktrees,
4488
- notes: [...notes, ...groups.notes]
4489
- };
4490
- }
4491
- function cleanupCounts(read) {
4492
- return {
4493
- merged: read.merged.length,
4494
- stale: read.stale.length,
4495
- notPushed: read.notPushed.length,
4496
- worktrees: read.worktrees.length
4497
- };
4498
- }
4499
- function firstLine2(text) {
4500
- return text.split(`
4501
- `).map((line) => line.trim()).find((line) => line !== "") ?? "";
4502
- }
4503
-
4504
3816
  // src/cli/prompt/multi-select.ts
4505
3817
  var NONE_CHECKED_TEXT = "(none)";
4506
3818
  var CHECKED_MARK = "\u25C9";
@@ -4531,8 +3843,8 @@ function scrollTo2(top, cursor, pageSize) {
4531
3843
  return cursor - pageSize + 1;
4532
3844
  return top;
4533
3845
  }
4534
- function eraseFrame3(lines2) {
4535
- return lines2 > 1 ? `\x1B[${lines2 - 1}A${CLEAR_DOWN3}` : CLEAR_DOWN3;
3846
+ function eraseFrame3(lines) {
3847
+ return lines > 1 ? `\x1B[${lines - 1}A${CLEAR_DOWN3}` : CLEAR_DOWN3;
4536
3848
  }
4537
3849
  function move2(state, step, pageSize) {
4538
3850
  const count = state.matches.length;
@@ -4602,19 +3914,19 @@ function layoutOf(groups) {
4602
3914
  const choices = [];
4603
3915
  const groupOf = [];
4604
3916
  const lineOf = [];
4605
- const lines2 = [];
3917
+ const lines = [];
4606
3918
  groups.forEach((group, groupIndex) => {
4607
3919
  if (group.choices.length === 0)
4608
3920
  return;
4609
- lines2.push({ heading: group.title });
3921
+ lines.push({ heading: group.title });
4610
3922
  for (const choice of group.choices) {
4611
- lineOf.push(lines2.length);
4612
- lines2.push({ index: choices.length });
3923
+ lineOf.push(lines.length);
3924
+ lines.push({ index: choices.length });
4613
3925
  groupOf.push(groupIndex);
4614
3926
  choices.push(choice);
4615
3927
  }
4616
3928
  });
4617
- return { choices, groupOf, lineOf, lines: lines2 };
3929
+ return { choices, groupOf, lineOf, lines };
4618
3930
  }
4619
3931
  function tickable(choice) {
4620
3932
  return choice !== undefined && choice.disabled === undefined;
@@ -4749,7 +4061,7 @@ var CLEANUP_GROUP_TITLES = Object.freeze({
4749
4061
  });
4750
4062
  var NO_ROWS_TEXT = "(none)";
4751
4063
  var UNKNOWN_DATE = "unknown";
4752
- var DEFAULT_REMOTE2 = "origin";
4064
+ var DEFAULT_REMOTE = "origin";
4753
4065
  var ROW_INDENT = " ";
4754
4066
  var COLUMN_GAP = " ";
4755
4067
  function cleanupDate(date) {
@@ -4758,7 +4070,7 @@ function cleanupDate(date) {
4758
4070
  function remoteDeleteLine(branch, upstream) {
4759
4071
  const slash = upstream === null ? -1 : upstream.indexOf("/");
4760
4072
  if (upstream === null || slash <= 0 || slash === upstream.length - 1) {
4761
- return `git push ${DEFAULT_REMOTE2} --delete ${shellQuote(branch)}`;
4073
+ return `git push ${DEFAULT_REMOTE} --delete ${shellQuote(branch)}`;
4762
4074
  }
4763
4075
  const remote = upstream.slice(0, slash);
4764
4076
  const name = upstream.slice(slash + 1);
@@ -5097,8 +4409,8 @@ var describe_default = Object.freeze(describeCommand);
5097
4409
  import { basename as basename5, resolve as resolve3 } from "path";
5098
4410
 
5099
4411
  // src/board/status.ts
5100
- import { statSync as statSync4 } from "fs";
5101
- import { join as join8 } from "path";
4412
+ import { statSync as statSync3 } from "fs";
4413
+ import { join as join6 } from "path";
5102
4414
  var ROADMAP_ROW_NAME = `${ROADMAP_TITLE} issue`;
5103
4415
  function rowOf(kind, name, outcome, detail) {
5104
4416
  return { kind, name, outcome, detail };
@@ -5118,11 +4430,11 @@ async function readLabelRows(gh) {
5118
4430
  return Object.freeze(BOARD_LABELS.map((label) => missing.includes(label) ? rowOf("label", label.name, "missing", "the repository carries no label of that name") : rowOf("label", label.name, "present", "the repository carries it")));
5119
4431
  }
5120
4432
  function readTemplateRow(root) {
5121
- const path = join8(root, SPEC_TEMPLATE_PATH);
4433
+ const path = join6(root, SPEC_TEMPLATE_PATH);
5122
4434
  const name = SPEC_TEMPLATE_PATH;
5123
4435
  let found;
5124
4436
  try {
5125
- found = statSync4(path);
4437
+ found = statSync3(path);
5126
4438
  } catch {
5127
4439
  return rowOf("template", name, "missing", "the repository carries no spec issue template");
5128
4440
  }
@@ -5320,21 +4632,21 @@ function renderDoctorEpics(report2) {
5320
4632
  }
5321
4633
 
5322
4634
  // src/commands/init-board.ts
5323
- import { join as join10 } from "path";
4635
+ import { join as join8 } from "path";
5324
4636
 
5325
4637
  // src/board/epic-guard.ts
5326
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
5327
- import { dirname, join as join9 } from "path";
4638
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
4639
+ import { dirname, join as join7 } from "path";
5328
4640
  import { fileURLToPath } from "url";
5329
4641
  var MODULE_DIR = dirname(fileURLToPath(import.meta.url));
5330
- var EPIC_GUARD_PATH = join9(".github", "workflows", "epic-guard.yml");
4642
+ var EPIC_GUARD_PATH = join7(".github", "workflows", "epic-guard.yml");
5331
4643
  var EPIC_GUARD_FILE = "epic-guard.yml";
5332
4644
  function epicGuardSource(moduleDir = MODULE_DIR) {
5333
- return join9(moduleDir, TEMPLATES_DIRNAME, EPIC_GUARD_FILE);
4645
+ return join7(moduleDir, TEMPLATES_DIRNAME, EPIC_GUARD_FILE);
5334
4646
  }
5335
4647
  function readEpicGuard(moduleDir = MODULE_DIR) {
5336
4648
  const source = epicGuardSource(moduleDir);
5337
- if (!existsSync4(source)) {
4649
+ if (!existsSync3(source)) {
5338
4650
  throw new Error(`board setup: the epic guard workflow is missing: no file at ${source}`);
5339
4651
  }
5340
4652
  return readFileSync5(source, "utf8");
@@ -5434,14 +4746,14 @@ function toNativeMembers(readings, epic) {
5434
4746
  }
5435
4747
  function checklistWrite(epic, members) {
5436
4748
  const named = new Set(parseRoadmapBody(epic.body).map((line) => line.issue));
5437
- const lines2 = members.filter((member) => !named.has(member.number)).map((member) => Object.freeze({ issue: member.number, why: member.title, ticked: member.state === "CLOSED" }));
5438
- if (lines2.length === 0)
4749
+ const lines = members.filter((member) => !named.has(member.number)).map((member) => Object.freeze({ issue: member.number, why: member.title, ticked: member.state === "CLOSED" }));
4750
+ if (lines.length === 0)
5439
4751
  return [];
5440
4752
  return [Object.freeze({
5441
4753
  kind: "checklist",
5442
4754
  epic: epic.number,
5443
- lines: Object.freeze(lines2),
5444
- what: `put ${lines2.map((line) => ref(line.issue)).join(", ")} on epic ${ref(epic.number)}'s checklist`
4755
+ lines: Object.freeze(lines),
4756
+ what: `put ${lines.map((line) => ref(line.issue)).join(", ")} on epic ${ref(epic.number)}'s checklist`
5445
4757
  })];
5446
4758
  }
5447
4759
  function toLabelsMembers(readings, epic) {
@@ -5567,9 +4879,9 @@ function withoutBlockedLine(issue, body) {
5567
4879
  const { line } = readBlockedBy(issue, body);
5568
4880
  if (line === null)
5569
4881
  return body;
5570
- const lines2 = body.split(`
4882
+ const lines = body.split(`
5571
4883
  `);
5572
- return [...lines2.slice(0, line - 1), ...lines2.slice(line)].join(`
4884
+ return [...lines.slice(0, line - 1), ...lines.slice(line)].join(`
5573
4885
  `);
5574
4886
  }
5575
4887
  async function sendWrite(gh, bodies, write) {
@@ -5806,7 +5118,7 @@ async function runEpicGuardStep(options) {
5806
5118
  part: writeEpicGuard(root, moduleDir),
5807
5119
  warnings: []
5808
5120
  });
5809
- if (anythingAt(join10(root, EPIC_GUARD_PATH)))
5121
+ if (anythingAt(join8(root, EPIC_GUARD_PATH)))
5810
5122
  return write(false);
5811
5123
  if (wanted === true)
5812
5124
  return write(false);
@@ -5838,9 +5150,9 @@ function moveQuestion(plan2) {
5838
5150
  function moveUnreadWarning(to, problem) {
5839
5151
  return `board.relationships is ${to}: the board could not be read for relationships to move, so nothing` + ` was moved: ${problem}; run ${MOVE_FIX} again.`;
5840
5152
  }
5841
- function moveFailureWarning(result2, failure3) {
5153
+ function moveFailureWarning(result2, failure) {
5842
5154
  const left = result2.left.length;
5843
- return `the board refused "${failure3.what}": ${failure3.problem}. ${String(result2.done.length)} write(s) went` + ` through and ${String(left)} did not, and the old marks were kept; every write is idempotent, so run` + ` ${MOVE_FIX} again to finish the move.`;
5155
+ return `the board refused "${failure.what}": ${failure.problem}. ${String(result2.done.length)} write(s) went` + ` through and ${String(left)} did not, and the old marks were kept; every write is idempotent, so run` + ` ${MOVE_FIX} again to finish the move.`;
5844
5156
  }
5845
5157
  function planLines(plan2) {
5846
5158
  return [
@@ -6071,9 +5383,9 @@ function renderDoctorRelations(report2) {
6071
5383
  }
6072
5384
 
6073
5385
  // src/commands/doctor-render.ts
6074
- import { basename as basename3, relative as relative3, sep as sep2 } from "path";
5386
+ import { basename as basename3, relative as relative3, sep } from "path";
6075
5387
  function shownPath(path, root) {
6076
- return path.startsWith(`${root}${sep2}`) ? relative3(root, path) : path;
5388
+ return path.startsWith(`${root}${sep}`) ? relative3(root, path) : path;
6077
5389
  }
6078
5390
  function checkedPhrase(count, automatic, whose) {
6079
5391
  if (count === 0)
@@ -6368,11 +5680,11 @@ function readSessionEnv(seams) {
6368
5680
  }
6369
5681
 
6370
5682
  // src/plan/needs.ts
6371
- import { statSync as statSync5 } from "fs";
6372
- import { join as join12 } from "path";
5683
+ import { statSync as statSync4 } from "fs";
5684
+ import { join as join10 } from "path";
6373
5685
 
6374
5686
  // src/inventory/mcp.ts
6375
- import { join as join11 } from "path";
5687
+ import { join as join9 } from "path";
6376
5688
  var MCP_JSON_FILE = ".mcp.json";
6377
5689
  var CLAUDE_JSON_FILE = ".claude.json";
6378
5690
  var MCP_SERVERS_KEY = "mcpServers";
@@ -6436,12 +5748,12 @@ function readRejections(seams, warnings) {
6436
5748
  }
6437
5749
  function readMcpServers(seams) {
6438
5750
  const warnings = [];
6439
- const claudeJsonPath = join11(seams.home, CLAUDE_JSON_FILE);
5751
+ const claudeJsonPath = join9(seams.home, CLAUDE_JSON_FILE);
6440
5752
  const claudeJson = readFile(claudeJsonPath, warnings);
6441
5753
  const projects = claudeJson === null ? null : readMapping(claudeJson, PROJECTS_KEY, claudeJsonPath, warnings);
6442
5754
  const root = seams.projectRoot;
6443
5755
  const projectEntry = projects === null || root === null || !Object.hasOwn(projects, root) ? null : readMapping(projects, root, claudeJsonPath, warnings);
6444
- const mcpJsonPath = root === null ? null : join11(root, MCP_JSON_FILE);
5756
+ const mcpJsonPath = root === null ? null : join9(root, MCP_JSON_FILE);
6445
5757
  const projectNames = mcpJsonPath === null ? [] : serverNames(readFile(mcpJsonPath, warnings), mcpJsonPath, warnings);
6446
5758
  const held = {
6447
5759
  local: { path: claudeJsonPath, names: serverNames(projectEntry, claudeJsonPath, warnings) },
@@ -6537,7 +5849,7 @@ function fencePrograms(text) {
6537
5849
  function programDirectory(name, pathDirs) {
6538
5850
  return pathDirs.find((dir) => {
6539
5851
  try {
6540
- const stats = statSync5(join12(dir, name));
5852
+ const stats = statSync4(join10(dir, name));
6541
5853
  return stats.isFile() && (stats.mode & 73) !== 0;
6542
5854
  } catch {
6543
5855
  return false;
@@ -6545,13 +5857,13 @@ function programDirectory(name, pathDirs) {
6545
5857
  }) ?? null;
6546
5858
  }
6547
5859
  function holdersOf2(records) {
6548
- const holders2 = new Map;
5860
+ const holders = new Map;
6549
5861
  for (const record of records) {
6550
5862
  const key = needKey(record.kind, record.name);
6551
- if (!holders2.has(key))
6552
- holders2.set(key, record);
5863
+ if (!holders.has(key))
5864
+ holders.set(key, record);
6553
5865
  }
6554
- return holders2;
5866
+ return holders;
6555
5867
  }
6556
5868
  function needKey(kind, name) {
6557
5869
  return `${kind}\x00${name}`;
@@ -6677,9 +5989,9 @@ function programSearchPath(program, seams) {
6677
5989
  function stackMarker(tool, projectRoot2) {
6678
5990
  if (projectRoot2 === null)
6679
5991
  return null;
6680
- const found = tool.markers.map((marker) => join12(projectRoot2, marker)).find((path) => {
5992
+ const found = tool.markers.map((marker) => join10(projectRoot2, marker)).find((path) => {
6681
5993
  try {
6682
- return statSync5(path).isFile();
5994
+ return statSync4(path).isFile();
6683
5995
  } catch {
6684
5996
  return false;
6685
5997
  }
@@ -6766,17 +6078,17 @@ function wholeWord(name) {
6766
6078
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6767
6079
  return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
6768
6080
  }
6769
- function firstMention(lines2, name) {
6081
+ function firstMention(lines, name) {
6770
6082
  const pattern = wholeWord(name);
6771
- const index = lines2.findIndex((line) => pattern.test(line));
6083
+ const index = lines.findIndex((line) => pattern.test(line));
6772
6084
  return index === -1 ? null : index + 1;
6773
6085
  }
6774
6086
  async function readSpecNeeds(specPath, seams) {
6775
- const lines2 = (await readRequired(specPath)).split(`
6087
+ const lines = (await readRequired(specPath)).split(`
6776
6088
  `);
6777
6089
  return readAgainstMachine(seams, (machine) => {
6778
6090
  const named = [...machine.holders.values()].flatMap((holder) => {
6779
- const line = firstMention(lines2, holder.name);
6091
+ const line = firstMention(lines, holder.name);
6780
6092
  return line === null ? [] : [{ kind: holder.kind, name: holder.name, origin: { by: "mentioned", line } }];
6781
6093
  });
6782
6094
  return Promise.resolve({ named, stacks: [], warnings: [] });
@@ -7176,11 +6488,11 @@ var UNREAD_NONE = Object.freeze({ provider: "none", source: "config", remote: nu
7176
6488
  function trackerChain(config) {
7177
6489
  return [...new Set([config.trackerDefault, ...config.trackerFallback])];
7178
6490
  }
7179
- function firstLine3(result2) {
7180
- const lines2 = `${result2.stderr}
6491
+ function firstLine(result2) {
6492
+ const lines = `${result2.stderr}
7181
6493
  ${result2.stdout}`.split(`
7182
6494
  `);
7183
- return lines2.map((line) => line.trim()).find((line) => line !== "") ?? "";
6495
+ return lines.map((line) => line.trim()).find((line) => line !== "") ?? "";
7184
6496
  }
7185
6497
  function activeLoginOf(stdout, host) {
7186
6498
  let payload;
@@ -7197,7 +6509,7 @@ function activeLoginOf(stdout, host) {
7197
6509
  }
7198
6510
  async function readLoginAndPermission(gh, host) {
7199
6511
  const status = await gh(["auth", "status", "--active", "--hostname", host, "--json", "hosts"]);
7200
- const login = status.ok ? activeLoginOf(status.stdout, host) : { login: null, detail: `gh auth status --json hosts failed: ${firstLine3(status) || "it wrote nothing"}` };
6512
+ const login = status.ok ? activeLoginOf(status.stdout, host) : { login: null, detail: `gh auth status --json hosts failed: ${firstLine(status) || "it wrote nothing"}` };
7201
6513
  if (login.login === null)
7202
6514
  return { login, permission: null };
7203
6515
  const permission = await readAuthorTrust({
@@ -7215,7 +6527,7 @@ async function probeGh(seams, host) {
7215
6527
  const openGh = seams.openGh ?? createGhRunner;
7216
6528
  const gh = openGh({ cwd, command: found, env });
7217
6529
  const status = await gh(["auth", "status", "--hostname", host]);
7218
- const auth = { ok: status.ok, detail: firstLine3(status) };
6530
+ const auth = { ok: status.ok, detail: firstLine(status) };
7219
6531
  if (!auth.ok)
7220
6532
  return { host, found, auth, login: null, permission: null };
7221
6533
  return { host, found, auth, ...await readLoginAndPermission(gh, host) };
@@ -7299,7 +6611,7 @@ function renderDeepSection(section) {
7299
6611
  }
7300
6612
 
7301
6613
  // src/commands/doctor-deep-settings.ts
7302
- import { join as join13 } from "path";
6614
+ import { join as join11 } from "path";
7303
6615
  var SETTINGS_SECTION_TITLE = "Settings";
7304
6616
  var SETTING_SOURCES_KEY = "loop.settingSources";
7305
6617
  function addSourceFix(source) {
@@ -7364,7 +6676,7 @@ function declarationReason(declaration, answering, seams) {
7364
6676
  if (!sources.includes(scope))
7365
6677
  return { why: sourceOffWhy(scope, sources), fix: addSourceFix(scope) };
7366
6678
  if (declaration.disabled) {
7367
- const file = join13(seams.home, CLAUDE_JSON_FILE);
6679
+ const file = join11(seams.home, CLAUDE_JSON_FILE);
7368
6680
  const entry = `${PROJECTS_KEY}["${seams.projectRoot ?? ""}"]`;
7369
6681
  return {
7370
6682
  why: `${DISABLE_KEY} names it in ${file}`,
@@ -7592,7 +6904,7 @@ function renderDeep(reading) {
7592
6904
  var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
7593
6905
 
7594
6906
  // src/commands/doctor-effort-schema.ts
7595
- import { join as join14 } from "path";
6907
+ import { join as join12 } from "path";
7596
6908
  var EFFORT_SCHEMA_ROW = "effort store schema";
7597
6909
  var SCHEMA_COMMAND = "rafa effort schema";
7598
6910
  function developmentAppliedWarning(path, id2, appliedBy) {
@@ -7617,7 +6929,7 @@ function readDoctorEffortSchema(root, env, options = {}) {
7617
6929
  const live = override === undefined || override === "";
7618
6930
  let path;
7619
6931
  try {
7620
- path = join14(effortStoreDir(root, env), SQLITE_STORE_FILE_NAME);
6932
+ path = join12(effortStoreDir(root, env), SQLITE_STORE_FILE_NAME);
7621
6933
  } catch (error) {
7622
6934
  return unreadable(null, live, messageOf(error));
7623
6935
  }
@@ -7655,9 +6967,9 @@ function effortSchemaRefusal(reading) {
7655
6967
  }
7656
6968
 
7657
6969
  // src/effort/store/legacy.ts
7658
- import { statSync as statSync6 } from "fs";
7659
- import { join as join15 } from "path";
7660
- var LEGACY_EFFORT_STORE_DIR = join15(".ralph", "effort");
6970
+ import { statSync as statSync5 } from "fs";
6971
+ import { join as join13 } from "path";
6972
+ var LEGACY_EFFORT_STORE_DIR = join13(".ralph", "effort");
7661
6973
  var NDJSON_KINDS = { sessions: true, commits: true };
7662
6974
  var STORE_FILE_NAMES = Object.freeze([
7663
6975
  SQLITE_STORE_FILE_NAME,
@@ -7670,7 +6982,7 @@ function isAbsence(error) {
7670
6982
  }
7671
6983
  function isStoreFile(path) {
7672
6984
  try {
7673
- return statSync6(path).isFile();
6985
+ return statSync5(path).isFile();
7674
6986
  } catch (error) {
7675
6987
  if (isAbsence(error))
7676
6988
  return false;
@@ -7678,7 +6990,7 @@ function isStoreFile(path) {
7678
6990
  }
7679
6991
  }
7680
6992
  function storeFilesIn(dir) {
7681
- return Object.freeze(STORE_FILE_NAMES.filter((name) => isStoreFile(join15(dir, name))));
6993
+ return Object.freeze(STORE_FILE_NAMES.filter((name) => isStoreFile(join13(dir, name))));
7682
6994
  }
7683
6995
  function shellQuoted(path) {
7684
6996
  return `'${path.replaceAll("'", "'\\''")}'`;
@@ -7688,8 +7000,8 @@ function legacyWarning(legacyDir, storeDir, legacyFiles) {
7688
7000
  return `${legacyDir} holds an effort store (${legacyFiles.join(", ")}) and ${storeDir} holds none;` + ` rafa reads ${storeDir} alone, so its reports start from an empty store. Once no loop is running,` + ` move it: ${move3}`;
7689
7001
  }
7690
7002
  function readLegacyStore(root) {
7691
- const legacyDir = join15(root, LEGACY_EFFORT_STORE_DIR);
7692
- const storeDir = join15(root, EFFORT_STORE_DIR);
7003
+ const legacyDir = join13(root, LEGACY_EFFORT_STORE_DIR);
7004
+ const storeDir = join13(root, EFFORT_STORE_DIR);
7693
7005
  const legacyFiles = storeFilesIn(legacyDir);
7694
7006
  const storeFiles = storeFilesIn(storeDir);
7695
7007
  const warning = legacyFiles.length > 0 && storeFiles.length === 0 ? legacyWarning(legacyDir, storeDir, legacyFiles) : null;
@@ -7697,7 +7009,7 @@ function readLegacyStore(root) {
7697
7009
  }
7698
7010
 
7699
7011
  // src/project/pre-init-dirs.ts
7700
- import { normalize, sep as sep3 } from "path";
7012
+ import { normalize, sep as sep2 } from "path";
7701
7013
  var PRE_INIT_DIRS = Object.freeze([
7702
7014
  Object.freeze({
7703
7015
  setting: "planDir",
@@ -7714,7 +7026,7 @@ var PRE_INIT_DIRS = Object.freeze([
7714
7026
  ]);
7715
7027
  function compared(value) {
7716
7028
  const normalized = normalize(value);
7717
- return normalized.endsWith(sep3) && normalized.length > sep3.length ? normalized.slice(0, -sep3.length) : normalized;
7029
+ return normalized.endsWith(sep2) && normalized.length > sep2.length ? normalized.slice(0, -sep2.length) : normalized;
7718
7030
  }
7719
7031
  function preInitWarning(found) {
7720
7032
  if (found.length === 0)
@@ -7792,11 +7104,11 @@ function writeInstall(context, install) {
7792
7104
 
7793
7105
  // src/commands/doctor-release.ts
7794
7106
  import { readFileSync as readFileSync6 } from "fs";
7795
- import { join as join16 } from "path";
7107
+ import { join as join14 } from "path";
7796
7108
  var SETTLE_COMMAND = "rafa release settle";
7797
7109
  var UNREAD = "?";
7798
7110
  function readTopHeading(root, configured) {
7799
- const resolved = join16(root, configured);
7111
+ const resolved = join14(root, configured);
7800
7112
  try {
7801
7113
  const text = readFileSync6(resolved, "utf8");
7802
7114
  return { path: configured, version: changelogVersions(text)[0] ?? null, problem: null };
@@ -7880,7 +7192,7 @@ function writeDoctorRelease(context, reading) {
7880
7192
  }
7881
7193
 
7882
7194
  // src/commands/doctor-tiers.ts
7883
- import { readFileSync as readFileSync7, realpathSync as realpathSync4 } from "fs";
7195
+ import { readFileSync as readFileSync7, realpathSync as realpathSync2 } from "fs";
7884
7196
  import { basename as basename4, dirname as dirname2 } from "path";
7885
7197
  var TIERS_SECTION_TITLE = "Skill tiers";
7886
7198
  var CLAUDE_VERSION_TIMEOUT_MS = 1e4;
@@ -7894,7 +7206,7 @@ function readTextFile(path) {
7894
7206
  }
7895
7207
  function realPathOf(path) {
7896
7208
  try {
7897
- return realpathSync4(path);
7209
+ return realpathSync2(path);
7898
7210
  } catch {
7899
7211
  return path;
7900
7212
  }
@@ -8023,8 +7335,8 @@ function renderDoctorTiers(reading) {
8023
7335
  // src/commands/doctor.ts
8024
7336
  var DEFAULT_DOCTOR_SEAMS = Object.freeze({ checks: Object.freeze({}) });
8025
7337
  var NOTHING_CHECKED = "Nothing was checked.";
8026
- function refusal3(lines2) {
8027
- return new CommandExit(1, [...lines2, NOTHING_CHECKED].join(`
7338
+ function refusal3(lines) {
7339
+ return new CommandExit(1, [...lines, NOTHING_CHECKED].join(`
8028
7340
  `));
8029
7341
  }
8030
7342
  function expectNoArgument2(args) {
@@ -8156,8 +7468,8 @@ function doctorRefusal(halt, rows) {
8156
7468
  `rafa doctor: ${String(halt)}`,
8157
7469
  "rafa loop start would halt here, before any session. No run was started and nothing was stored."
8158
7470
  ];
8159
- const lines2 = halt === null ? [] : halted;
8160
- const all = [...lines2, ...failed2];
7471
+ const lines = halt === null ? [] : halted;
7472
+ const all = [...lines, ...failed2];
8161
7473
  return all.length === 0 ? null : new CommandExit(1, all.join(`
8162
7474
  `));
8163
7475
  }
@@ -8200,10 +7512,10 @@ function projectOf7(context) {
8200
7512
  throw new Error("rafa doctor runs inside a project, and was handed none");
8201
7513
  return context.project;
8202
7514
  }
8203
- function writeText(context, lines2) {
7515
+ function writeText(context, lines) {
8204
7516
  if (context.outputMode === "json")
8205
7517
  return;
8206
- for (const line of lines2)
7518
+ for (const line of lines)
8207
7519
  context.output.info(line);
8208
7520
  }
8209
7521
  async function announceRisk(context, preflight, seams) {
@@ -8389,13 +7701,13 @@ var collect_default = wrapPhaseZeroCommand({
8389
7701
  }, collect);
8390
7702
 
8391
7703
  // src/commands/effort/copy.ts
8392
- import { join as join17, resolve as resolve4 } from "path";
7704
+ import { join as join15, resolve as resolve4 } from "path";
8393
7705
  var COMMAND_NAME5 = "rafa effort copy";
8394
7706
  var COPY_USAGE = "rafa effort copy [--to=<dir>]";
8395
7707
  var TO_FLAG2 = "to";
8396
7708
  var REFUSED_EXIT = 1;
8397
7709
  var FAILED_EXIT = 2;
8398
- var SCRATCH_DIR = join17(".rafa", "scratch");
7710
+ var SCRATCH_DIR = join15(".rafa", "scratch");
8399
7711
  function effortDirLine(directory) {
8400
7712
  return `${EFFORT_DIR_VARIABLE}=${directory}`;
8401
7713
  }
@@ -8427,10 +7739,10 @@ function runCopy(context, seams) {
8427
7739
  const root = requireProject(context, COMMAND_NAME5).root;
8428
7740
  const named = readTarget(context, root);
8429
7741
  refuseEffortDirOverride(context);
8430
- const target = named ?? join17(root, SCRATCH_DIR, `effort-${fileStamp((seams.now ?? (() => new Date))())}`);
7742
+ const target = named ?? join15(root, SCRATCH_DIR, `effort-${fileStamp((seams.now ?? (() => new Date))())}`);
8431
7743
  let result2;
8432
7744
  try {
8433
- result2 = copyEffortStore({ source: join17(root, EFFORT_STORE_DIR), target });
7745
+ result2 = copyEffortStore({ source: join15(root, EFFORT_STORE_DIR), target });
8434
7746
  } catch (error) {
8435
7747
  if (error instanceof EffortCopyRefusal)
8436
7748
  throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME5}: ${error.message}`);
@@ -8574,7 +7886,7 @@ function formatDashboard(dashboard) {
8574
7886
  }
8575
7887
 
8576
7888
  // src/effort/dashboard-status.ts
8577
- var MS_PER_SECOND2 = 1000;
7889
+ var MS_PER_SECOND = 1000;
8578
7890
  var SECONDS_PER_MINUTE = 60;
8579
7891
  function tasksLeft(counts) {
8580
7892
  return counts.open + counts.blocked;
@@ -8592,7 +7904,7 @@ function byTaskEstimate(row2, counts) {
8592
7904
  function byProgressEstimate(planStartedAt, counts, now) {
8593
7905
  if (counts.done === 0)
8594
7906
  return null;
8595
- const elapsedSeconds = Math.max(0, (now.getTime() - Date.parse(planStartedAt)) / MS_PER_SECOND2);
7907
+ const elapsedSeconds = Math.max(0, (now.getTime() - Date.parse(planStartedAt)) / MS_PER_SECOND);
8596
7908
  const estimate = estimateFrom(elapsedSeconds / counts.done, tasksLeft(counts));
8597
7909
  return estimate === null ? null : { ...estimate, planStartedAt, elapsedSeconds: Math.round(elapsedSeconds) };
8598
7910
  }
@@ -8802,7 +8114,7 @@ var dashboard_default = createEffortDashboardCommand();
8802
8114
  import { resolve as resolve6 } from "path";
8803
8115
 
8804
8116
  // src/commands/effort/merge.ts
8805
- import { join as join18, resolve as resolve5 } from "path";
8117
+ import { join as join16, resolve as resolve5 } from "path";
8806
8118
  var COMMAND_NAME7 = "rafa effort merge";
8807
8119
  var MERGE_USAGE = "rafa effort merge <file> [--dry-run]";
8808
8120
  var DRY_RUN_FLAG2 = "dry-run";
@@ -8841,7 +8153,7 @@ function renderMerge(result2, command = COMMAND_NAME7) {
8841
8153
  }
8842
8154
  function mergeTargetPath(context, root, command = COMMAND_NAME7) {
8843
8155
  try {
8844
- return join18(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8156
+ return join16(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8845
8157
  } catch (error) {
8846
8158
  throw new CommandExit(REFUSED_EXIT2, `\u274C ${command}: ${messageOf(error)}`);
8847
8159
  }
@@ -9012,7 +8324,7 @@ function createImportCommand(seams = {}) {
9012
8324
  var import_default = createImportCommand();
9013
8325
 
9014
8326
  // src/commands/effort/migrate.ts
9015
- import { join as join19 } from "path";
8327
+ import { join as join17 } from "path";
9016
8328
  var COMMAND_NAME9 = "rafa effort migrate";
9017
8329
  var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
9018
8330
  var DRY_RUN_FLAG4 = "dry-run";
@@ -9045,7 +8357,7 @@ function renderMigrate(result2) {
9045
8357
  }
9046
8358
  function storePath(context, root) {
9047
8359
  try {
9048
- return join19(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8360
+ return join17(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
9049
8361
  } catch (error) {
9050
8362
  throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME9}: ${messageOf(error)}`);
9051
8363
  }
@@ -9141,17 +8453,17 @@ function namesStore(text) {
9141
8453
  return isMapping(document) && Object.hasOwn(document, "store");
9142
8454
  }
9143
8455
  function withSqliteStoreSetting(text) {
9144
- const lines2 = text.split(`
8456
+ const lines = text.split(`
9145
8457
  `);
9146
- const at = lines2.findIndex((line) => STORE_KEY.test(line));
9147
- const commented = lines2.findIndex((line) => COMMENTED_STORE_KEY.test(line));
8458
+ const at = lines.findIndex((line) => STORE_KEY.test(line));
8459
+ const commented = lines.findIndex((line) => COMMENTED_STORE_KEY.test(line));
9148
8460
  const target = at >= 0 ? at : commented;
9149
8461
  if (target >= 0)
9150
- return [...lines2.slice(0, target), STORE_LINE, ...lines2.slice(target + 1)].join(`
8462
+ return [...lines.slice(0, target), STORE_LINE, ...lines.slice(target + 1)].join(`
9151
8463
  `);
9152
8464
  if (namesStore(text))
9153
8465
  return null;
9154
- const body = lines2.at(-1) === "" ? lines2.slice(0, -1) : lines2;
8466
+ const body = lines.at(-1) === "" ? lines.slice(0, -1) : lines;
9155
8467
  return [...body, STORE_LINE, ""].join(`
9156
8468
  `);
9157
8469
  }
@@ -9401,7 +8713,7 @@ var report_default = wrapPhaseZeroCommand({
9401
8713
  }, report);
9402
8714
 
9403
8715
  // src/commands/effort/schema.ts
9404
- import { join as join20 } from "path";
8716
+ import { join as join18 } from "path";
9405
8717
  var COMMAND_NAME11 = "rafa effort schema";
9406
8718
  var SCHEMA_USAGE = "rafa effort schema [--check]";
9407
8719
  var CHECK_FLAG = "check";
@@ -9447,7 +8759,7 @@ function renderSchema(report2) {
9447
8759
  }
9448
8760
  function storePath2(context, root) {
9449
8761
  try {
9450
- return join20(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8762
+ return join18(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
9451
8763
  } catch (error) {
9452
8764
  throw new CommandExit(REFUSED_EXIT5, `\u274C ${COMMAND_NAME11}: ${messageOf(error)}`);
9453
8765
  }
@@ -9811,9 +9123,9 @@ function workLines(result2) {
9811
9123
  return [info(`No terminal to ask on, so its open work is kept: ${workPhrase(work)}.`)];
9812
9124
  }
9813
9125
  case "closed": {
9814
- const lines2 = result2.closed.map((pull2) => pull2.status === "closed" ? info(`Closed pull request ${ref4(pull2.number)} with a comment.`) : { text: `Could not close pull request ${ref4(pull2.number)}: ${pull2.problem}`, warn: true });
9126
+ const lines = result2.closed.map((pull2) => pull2.status === "closed" ? info(`Closed pull request ${ref4(pull2.number)} with a comment.`) : { text: `Could not close pull request ${ref4(pull2.number)}: ${pull2.problem}`, warn: true });
9815
9127
  const kept = work.branches.length === 0 ? "No branch was deleted." : `No branch was deleted: ${work.branches.join(", ")}.`;
9816
- return [...lines2, info(kept)];
9128
+ return [...lines, info(kept)];
9817
9129
  }
9818
9130
  }
9819
9131
  }
@@ -9830,10 +9142,10 @@ function closeFailure(result2) {
9830
9142
  }
9831
9143
  async function runEpicHorizon(context, action, seams) {
9832
9144
  const result2 = await changeEpicHorizon(context, action, seams);
9833
- const failure3 = closeFailure(result2);
9145
+ const failure = closeFailure(result2);
9834
9146
  if (context.outputMode === "json") {
9835
- if (failure3 !== null)
9836
- throw failure3;
9147
+ if (failure !== null)
9148
+ throw failure;
9837
9149
  context.output.result(result2);
9838
9150
  return;
9839
9151
  }
@@ -9843,8 +9155,8 @@ async function runEpicHorizon(context, action, seams) {
9843
9155
  else
9844
9156
  context.output.info(line.text);
9845
9157
  }
9846
- if (failure3 !== null)
9847
- throw failure3;
9158
+ if (failure !== null)
9159
+ throw failure;
9848
9160
  }
9849
9161
 
9850
9162
  // src/commands/epic/move.ts
@@ -10130,8 +9442,8 @@ function movedLines2(result2, outcome) {
10130
9442
  };
10131
9443
  const work = result2.work === null || result2.work.branches.length + result2.work.pullRequests.length === 0 ? [] : [{ text: `Its open work stays as it is: ${workPhrase(result2.work)}.`, warn: false }];
10132
9444
  const comment = outcome.commentProblem === "" ? [] : [{ text: `Could not comment on ${ref5(result2.issue)}: ${outcome.commentProblem}`, warn: true }];
10133
- const lines2 = outcome.added === undefined || outcome.removed === undefined ? [{ text: nativeParentLine(result2.to), warn: false }] : [addedLine(result2, outcome.added), removedLine(result2, outcome.removed)];
10134
- return [head, ...lines2, ...work, ...comment];
9445
+ const lines = outcome.added === undefined || outcome.removed === undefined ? [{ text: nativeParentLine(result2.to), warn: false }] : [addedLine(result2, outcome.added), removedLine(result2, outcome.removed)];
9446
+ return [head, ...lines, ...work, ...comment];
10135
9447
  }
10136
9448
  function renderEpicMove(result2) {
10137
9449
  const problems = result2.problems.map((problem) => ({ text: `The open work named may be short: ${problem}`, warn: true }));
@@ -10158,10 +9470,10 @@ function moveFailure(result2) {
10158
9470
  }
10159
9471
  async function runEpicMove(context, seams) {
10160
9472
  const result2 = await moveEpicIssue(context, seams);
10161
- const failure3 = moveFailure(result2);
9473
+ const failure = moveFailure(result2);
10162
9474
  if (context.outputMode === "json") {
10163
- if (failure3 !== null)
10164
- throw failure3;
9475
+ if (failure !== null)
9476
+ throw failure;
10165
9477
  context.output.result(result2);
10166
9478
  return;
10167
9479
  }
@@ -10171,8 +9483,8 @@ async function runEpicMove(context, seams) {
10171
9483
  else
10172
9484
  context.output.info(out.text);
10173
9485
  }
10174
- if (failure3 !== null)
10175
- throw failure3;
9486
+ if (failure !== null)
9487
+ throw failure;
10176
9488
  }
10177
9489
  function createEpicMoveCommand(seams = {}) {
10178
9490
  const command = {
@@ -10370,21 +9682,21 @@ async function applyMove(input, dependent, reading) {
10370
9682
  try {
10371
9683
  const writes = { board: input.writes.board, relations: input.writes.moves, issues: input.issues };
10372
9684
  const outcome = await applyEpicMove(writes, reading, cancelMoveReason(input.epic));
10373
- const lines2 = [info(`Moved ${ref6(change.issue)} from epic ${ref6(change.from)} to ${ref6(change.to)}.`)];
9685
+ const lines = [info(`Moved ${ref6(change.issue)} from epic ${ref6(change.from)} to ${ref6(change.to)}.`)];
10374
9686
  const left = [];
10375
9687
  if (outcome.added?.status === "failed") {
10376
- lines2.push(warn(`Could not add its line to epic ${ref6(change.to)}'s checklist: ${outcome.added.problem}`));
9688
+ lines.push(warn(`Could not add its line to epic ${ref6(change.to)}'s checklist: ${outcome.added.problem}`));
10377
9689
  left.push(`add ${ref6(change.issue)}'s line to epic ${ref6(change.to)}'s checklist`);
10378
9690
  }
10379
9691
  if (outcome.removed?.status === "failed") {
10380
- lines2.push(warn(`Could not take its line off epic ${ref6(change.from)}'s checklist: ${outcome.removed.problem}`));
9692
+ lines.push(warn(`Could not take its line off epic ${ref6(change.from)}'s checklist: ${outcome.removed.problem}`));
10381
9693
  left.push(`take ${ref6(change.issue)}'s line off epic ${ref6(change.from)}'s checklist`);
10382
9694
  }
10383
9695
  if (outcome.commentProblem !== "") {
10384
- lines2.push(warn(`Could not comment on ${ref6(change.issue)}: ${outcome.commentProblem}`));
9696
+ lines.push(warn(`Could not comment on ${ref6(change.issue)}: ${outcome.commentProblem}`));
10385
9697
  left.push(`comment the move on ${ref6(change.issue)}`);
10386
9698
  }
10387
- return Object.freeze({ ...base, landed: true, lines: lines2, left });
9699
+ return Object.freeze({ ...base, landed: true, lines, left });
10388
9700
  } catch (error) {
10389
9701
  return Object.freeze({
10390
9702
  ...base,
@@ -10416,26 +9728,26 @@ async function applyUnblock(input, dependent) {
10416
9728
  const still = readUnblockStill(input.relations, input.issues, issue, waitsOn);
10417
9729
  const note = renderUnblockNote(input.day, input.epic, waitsOn, still.named);
10418
9730
  const edit = await editChecklist({ issue: issue.number, edit: (body) => appendNote(body, note), board: input.writes.bodies });
10419
- const lines2 = [];
9731
+ const lines = [];
10420
9732
  const left = [];
10421
9733
  if (edit.status === "failed") {
10422
- lines2.push(warn(`Could not add the note to ${ref6(issue.number)}'s body: ${edit.problem}`));
9734
+ lines.push(warn(`Could not add the note to ${ref6(issue.number)}'s body: ${edit.problem}`));
10423
9735
  left.push(`add the note to ${ref6(issue.number)}'s body: ${note}`);
10424
9736
  } else {
10425
9737
  const verb = waitsOn.length === 1 ? "blocks" : "block";
10426
- lines2.push(info(edit.status === "edited" ? `Unblocked ${ref6(issue.number)}: noted below its body that ${refs(waitsOn)} no longer ${verb} it.` : `Unblocked ${ref6(issue.number)}: its body carries the note already.`));
9738
+ lines.push(info(edit.status === "edited" ? `Unblocked ${ref6(issue.number)}: noted below its body that ${refs(waitsOn)} no longer ${verb} it.` : `Unblocked ${ref6(issue.number)}: its body carries the note already.`));
10427
9739
  }
10428
9740
  const label = input.relations.mode === "native" ? { line: info(keptLinksLine(issue.number, waitsOn)), left: null } : await unblockLabel(input, issue, still.maybeOpen);
10429
9741
  if (label.line !== null)
10430
- lines2.push(label.line);
9742
+ lines.push(label.line);
10431
9743
  if (label.left !== null)
10432
9744
  left.push(label.left);
10433
9745
  const problem = await commentOn(input.writes, issue.number, renderDependentComment("unblocked", input.epic, waitsOn));
10434
9746
  if (problem !== "") {
10435
- lines2.push(warn(`Could not comment on ${ref6(issue.number)}: ${problem}`));
9747
+ lines.push(warn(`Could not comment on ${ref6(issue.number)}: ${problem}`));
10436
9748
  left.push(`comment the unblock on ${ref6(issue.number)}`);
10437
9749
  }
10438
- return Object.freeze({ issue: issue.number, waitsOn, answer: { kind: "unblocked" }, landed: edit.status !== "failed", lines: lines2, left });
9750
+ return Object.freeze({ issue: issue.number, waitsOn, answer: { kind: "unblocked" }, landed: edit.status !== "failed", lines, left });
10439
9751
  }
10440
9752
  async function applyCancel(input, dependent) {
10441
9753
  const { issue, waitsOn } = dependent;
@@ -10576,9 +9888,9 @@ function cancelFailure(result2) {
10576
9888
  }
10577
9889
  async function runEpicCancel(context, seams) {
10578
9890
  const result2 = await cancelEpic(context, seams);
10579
- const failure3 = cancelFailure(result2);
10580
- if (failure3 !== null)
10581
- throw failure3;
9891
+ const failure = cancelFailure(result2);
9892
+ if (failure !== null)
9893
+ throw failure;
10582
9894
  if (context.outputMode === "json")
10583
9895
  context.output.result(result2);
10584
9896
  }
@@ -10619,11 +9931,11 @@ var cancel_default = createEpicCancelCommand();
10619
9931
 
10620
9932
  // src/commands/epic/close.ts
10621
9933
  import { randomUUID as randomUUID3 } from "crypto";
10622
- import { join as join24 } from "path";
9934
+ import { join as join22 } from "path";
10623
9935
 
10624
9936
  // src/board/epic-template.ts
10625
- import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
10626
- import { dirname as dirname3, join as join21 } from "path";
9937
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
9938
+ import { dirname as dirname3, join as join19 } from "path";
10627
9939
  import { fileURLToPath as fileURLToPath2 } from "url";
10628
9940
  var MODULE_DIR2 = dirname3(fileURLToPath2(import.meta.url));
10629
9941
  var EPIC_TEMPLATE_FILE = "epic.md";
@@ -10632,11 +9944,11 @@ var CRITERIA_PLACEHOLDERS = Object.freeze([
10632
9944
  ]);
10633
9945
  var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
10634
9946
  function epicTemplateSource(moduleDir = MODULE_DIR2) {
10635
- return join21(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
9947
+ return join19(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
10636
9948
  }
10637
9949
  function readEpicTemplate(moduleDir = MODULE_DIR2) {
10638
9950
  const source = epicTemplateSource(moduleDir);
10639
- if (!existsSync5(source)) {
9951
+ if (!existsSync4(source)) {
10640
9952
  throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
10641
9953
  }
10642
9954
  return readFileSync9(source, "utf8");
@@ -10730,8 +10042,8 @@ function renderEpicCost(cost, estimate) {
10730
10042
  }
10731
10043
 
10732
10044
  // src/epic/verify-plan.ts
10733
- import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
10734
- import { dirname as dirname4, join as join22 } from "path";
10045
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
10046
+ import { dirname as dirname4, join as join20 } from "path";
10735
10047
  import { fileURLToPath as fileURLToPath3 } from "url";
10736
10048
  var MODULE_DIR3 = dirname4(fileURLToPath3(import.meta.url));
10737
10049
  var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
@@ -10777,8 +10089,8 @@ function splitCriteria(criteria) {
10777
10089
  }
10778
10090
  state.afterBlank = false;
10779
10091
  }
10780
- const numbered = gathered.map((lines2, index) => {
10781
- const text = lines2.join(`
10092
+ const numbered = gathered.map((lines, index) => {
10093
+ const text = lines.join(`
10782
10094
  `).trim();
10783
10095
  return Object.freeze({ number: index + 1, text, placeholder: CRITERIA_PLACEHOLDERS.includes(text) });
10784
10096
  });
@@ -10788,11 +10100,11 @@ function criteriaToAsk(criteria) {
10788
10100
  return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
10789
10101
  }
10790
10102
  function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
10791
- return [join22(moduleDir, VERIFY_PROMPT_FILE), join22(dirname4(moduleDir), VERIFY_PROMPT_FILE)];
10103
+ return [join20(moduleDir, VERIFY_PROMPT_FILE), join20(dirname4(moduleDir), VERIFY_PROMPT_FILE)];
10792
10104
  }
10793
10105
  function readVerifyPrompt(moduleDir = MODULE_DIR3) {
10794
10106
  const candidates = verifyPromptCandidates(moduleDir);
10795
- const found = candidates.find((candidate) => existsSync6(candidate));
10107
+ const found = candidates.find((candidate) => existsSync5(candidate));
10796
10108
  if (found === undefined) {
10797
10109
  throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
10798
10110
  }
@@ -10933,16 +10245,16 @@ function parseVerifyPlan(output, criteria) {
10933
10245
 
10934
10246
  // src/epic/verify-run.ts
10935
10247
  import { randomUUID as randomUUID2 } from "crypto";
10936
- import { join as join23 } from "path";
10248
+ import { join as join21 } from "path";
10937
10249
  var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
10938
10250
  var DEFAULT_VERIFY_REMOTE = "origin";
10939
10251
  var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
10940
10252
  var VERDICT_BLOCK_FENCE = "rafa:verdict";
10941
10253
  var CHECK_RESULTS = ["pass", "fail"];
10942
- var WORKTREE_PREFIX2 = "epic-";
10254
+ var WORKTREE_PREFIX = "epic-";
10943
10255
  var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
10944
10256
  function verifyWorktreePath(home, epic) {
10945
- return join23(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX2}${String(epic)}`);
10257
+ return join21(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${String(epic)}`);
10946
10258
  }
10947
10259
  function fetchStep(remote) {
10948
10260
  return { id: "fetch", args: ["fetch", remote] };
@@ -10956,7 +10268,7 @@ function addStep(path, commit) {
10956
10268
  function removeStep(path) {
10957
10269
  return { id: "remove", args: ["worktree", "remove", "--force", path] };
10958
10270
  }
10959
- function runStep3(git, step) {
10271
+ function runStep(git, step) {
10960
10272
  const result2 = git(step.args);
10961
10273
  return {
10962
10274
  id: step.id,
@@ -11089,7 +10401,7 @@ async function runChecks(options, worktree, prompt) {
11089
10401
  }
11090
10402
  return Object.freeze(results);
11091
10403
  }
11092
- function notRun2(step, worktree) {
10404
+ function notRun(step, worktree) {
11093
10405
  return Object.freeze({ status: "not-run", step: reported(step), worktree });
11094
10406
  }
11095
10407
  async function runVerification(options) {
@@ -11098,22 +10410,22 @@ async function runVerification(options) {
11098
10410
  const remote = options.remote ?? DEFAULT_VERIFY_REMOTE;
11099
10411
  const base = options.base ?? DEFAULT_BASE_BRANCH;
11100
10412
  const worktree = verifyWorktreePath(options.home, options.epic);
11101
- const fetched = runStep3(options.git, fetchStep(remote));
10413
+ const fetched = runStep(options.git, fetchStep(remote));
11102
10414
  if (!fetched.ok)
11103
- return notRun2(fetched, worktree);
11104
- const resolved = runStep3(options.git, resolveStep(remote, base));
10415
+ return notRun(fetched, worktree);
10416
+ const resolved = runStep(options.git, resolveStep(remote, base));
11105
10417
  const commit = resolved.stdout.trim();
11106
10418
  if (!resolved.ok || commit === "")
11107
- return notRun2(resolved, worktree);
11108
- const added = runStep3(options.git, addStep(worktree, commit));
10419
+ return notRun(resolved, worktree);
10420
+ const added = runStep(options.git, addStep(worktree, commit));
11109
10421
  if (!added.ok)
11110
- return notRun2(added, worktree);
10422
+ return notRun(added, worktree);
11111
10423
  let results;
11112
10424
  let removal;
11113
10425
  try {
11114
10426
  results = await runChecks(options, worktree, { commit, ref: `${remote}/${base}` });
11115
10427
  } finally {
11116
- removal = reported(runStep3(options.git, removeStep(worktree)));
10428
+ removal = reported(runStep(options.git, removeStep(worktree)));
11117
10429
  }
11118
10430
  return Object.freeze({ status: "ran", commit, worktree, results, removal });
11119
10431
  }
@@ -11224,13 +10536,13 @@ async function planVerification(input) {
11224
10536
  return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
11225
10537
  }
11226
10538
  function closeTriageFile(root, epic) {
11227
- return join24(root, ".rafa", `epic-${String(epic)}-close`);
10539
+ return join22(root, ".rafa", `epic-${String(epic)}-close`);
11228
10540
  }
11229
10541
  function failedCheckArtifact(epic, criterion) {
11230
10542
  return `epic ${ref7(epic)} acceptance criterion: ${oneLine(criterion.text)}`;
11231
10543
  }
11232
- function failedCheckReport(epic, failure3, commit) {
11233
- const { criterion } = failure3.check;
10544
+ function failedCheckReport(epic, failure, commit) {
10545
+ const { criterion } = failure.check;
11234
10546
  const feedback = [
11235
10547
  `Epic ${ref7(epic)} was being closed through the closing gate, and this acceptance criterion's check failed.`,
11236
10548
  "",
@@ -11238,10 +10550,10 @@ function failedCheckReport(epic, failure3, commit) {
11238
10550
  criterion.text,
11239
10551
  "",
11240
10552
  "Check:",
11241
- failure3.check.check,
10553
+ failure.check.check,
11242
10554
  "",
11243
10555
  `Evidence, against commit ${commit}:`,
11244
- failure3.evidence
10556
+ failure.evidence
11245
10557
  ].join(`
11246
10558
  `);
11247
10559
  return Object.freeze({
@@ -11272,6 +10584,9 @@ function bugLine(criterion, bug) {
11272
10584
  case "commented": {
11273
10585
  return { text: `${which}: recurs in ${bug.ref === null ? "an issue" : issueName2(bug.ref)} on the ${bug.channel} tracker; commented on it.`, warn: false };
11274
10586
  }
10587
+ case "inherited": {
10588
+ return { text: `${which}: not filed: a test failure the run started with.`, warn: false };
10589
+ }
11275
10590
  case "skipped": {
11276
10591
  return { text: `${which}: not filed: ${bug.problem ?? "no reason given"}.`, warn: true };
11277
10592
  }
@@ -11296,20 +10611,21 @@ async function fileFailures(run, epic, failures, commit) {
11296
10611
  warn2(run, `No tracker resolved, so no failed check is filed: ${messageOf(error)}`);
11297
10612
  tracker = unresolvedTracker(run.config.trackerDefault, messageOf(error));
11298
10613
  }
11299
- for (const failure3 of failures) {
11300
- const { criterion } = failure3.check;
10614
+ for (const failure of failures) {
10615
+ const { criterion } = failure.check;
11301
10616
  try {
11302
10617
  const result2 = await triageReport({
11303
10618
  repoRoot: run.root,
11304
10619
  trackerPath: closeTriageFile(run.root, epic),
11305
10620
  lineNum: 0,
11306
- dispatch: { sessionId: failure3.sessionId, planStub: null, taskLine: `rafa epic close ${String(epic)}` },
10621
+ dispatch: { sessionId: failure.sessionId, planStub: null, taskLine: `rafa epic close ${String(epic)}` },
11307
10622
  outcome: "failed",
11308
- report: failedCheckReport(epic, failure3, commit),
10623
+ report: failedCheckReport(epic, failure, commit),
11309
10624
  tracker,
11310
10625
  privateTracker: run.seams.privateTracker,
11311
10626
  secrets,
11312
- seams: run.seams.findings
10627
+ seams: run.seams.findings,
10628
+ similarity: run.config
11313
10629
  });
11314
10630
  for (const bug of result2.bugs) {
11315
10631
  const line = bugLine(criterion, bug);
@@ -11821,13 +11137,13 @@ function createEpicPromoteCommand(seams = {}) {
11821
11137
  var promote_default = createEpicPromoteCommand();
11822
11138
 
11823
11139
  // src/commands/init.ts
11824
- import { existsSync as existsSync9, readFileSync as readFileSync14 } from "fs";
11140
+ import { existsSync as existsSync8, readFileSync as readFileSync14 } from "fs";
11825
11141
  import { homedir as homedir2 } from "os";
11826
- import { join as join28, relative as relative5, sep as sep4 } from "path";
11142
+ import { join as join26, relative as relative5, sep as sep3 } from "path";
11827
11143
 
11828
11144
  // src/agents/vendorable.ts
11829
11145
  import { readdirSync, readFileSync as readFileSync11 } from "fs";
11830
- import { isAbsolute as isAbsolute3, join as join25 } from "path";
11146
+ import { isAbsolute as isAbsolute3, join as join23 } from "path";
11831
11147
  function planFiles(dir) {
11832
11148
  let entries;
11833
11149
  try {
@@ -11849,7 +11165,7 @@ function rafaHolds(roster, name) {
11849
11165
  return item?.holders.some((holder) => holder.source === "rafa") === true;
11850
11166
  }
11851
11167
  function vendorableAgents(scan) {
11852
- const dir = isAbsolute3(scan.planDir) ? scan.planDir : join25(scan.repoRoot, scan.planDir);
11168
+ const dir = isAbsolute3(scan.planDir) ? scan.planDir : join23(scan.repoRoot, scan.planDir);
11853
11169
  const roots = {
11854
11170
  repoRoot: scan.repoRoot,
11855
11171
  home: scan.home,
@@ -11858,7 +11174,7 @@ function vendorableAgents(scan) {
11858
11174
  const roster = resolveAgentRoster(roots, scan.settings);
11859
11175
  const found = [];
11860
11176
  for (const file of planFiles(dir)) {
11861
- const path = join25(dir, file);
11177
+ const path = join23(dir, file);
11862
11178
  const markdown = readText2(path);
11863
11179
  if (markdown === null)
11864
11180
  continue;
@@ -11884,13 +11200,13 @@ function vendorableAgentWarnings(agents, root) {
11884
11200
 
11885
11201
  // src/project/gitignore.ts
11886
11202
  import { createHash } from "crypto";
11887
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
11888
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join26 } from "path";
11203
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
11204
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join24 } from "path";
11889
11205
  var GITIGNORE_FILE = ".gitignore";
11890
11206
  var DIGEST_NAME = "tracking.digest";
11891
11207
  var PRIVATE_TRIAGE = "triage/private";
11892
11208
  var CACHE_DIR = "cache";
11893
- var TRACKING_DIGEST_FILE = join26(SCOPE_DIR, DIGEST_NAME);
11209
+ var TRACKING_DIGEST_FILE = join24(SCOPE_DIR, DIGEST_NAME);
11894
11210
  var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
11895
11211
  var BLOCK_END = "# <<< rafa tracking";
11896
11212
  var SUB_PATHS = [
@@ -11924,8 +11240,8 @@ function lineBreakOf(text) {
11924
11240
  return /\r?\n/.exec(text)?.[0] ?? `
11925
11241
  `;
11926
11242
  }
11927
- function indicesOf(lines2, marker) {
11928
- return lines2.flatMap((line, index) => line.replace(/\r$/, "") === marker ? [index] : []);
11243
+ function indicesOf(lines, marker) {
11244
+ return lines.flatMap((line, index) => line.replace(/\r$/, "") === marker ? [index] : []);
11929
11245
  }
11930
11246
  function whereLines(indices) {
11931
11247
  if (indices.length === 0)
@@ -11933,9 +11249,9 @@ function whereLines(indices) {
11933
11249
  const numbers = indices.map((index) => String(index + 1));
11934
11250
  return indices.length === 1 ? `line ${numbers.join("")}` : `lines ${numbers.join(", ")}`;
11935
11251
  }
11936
- function blockSpan(lines2, file) {
11937
- const begins = indicesOf(lines2, BLOCK_BEGIN);
11938
- const ends = indicesOf(lines2, BLOCK_END);
11252
+ function blockSpan(lines, file) {
11253
+ const begins = indicesOf(lines, BLOCK_BEGIN);
11254
+ const ends = indicesOf(lines, BLOCK_END);
11939
11255
  if (begins.length === 0 && ends.length === 0)
11940
11256
  return null;
11941
11257
  const [begin] = begins;
@@ -11952,17 +11268,17 @@ function withTrackingBlock(text, flags, file = GITIGNORE_FILE) {
11952
11268
  const block = [BLOCK_BEGIN, ...trackingEntry(flags), BLOCK_END].map((line) => `${line}${eol}`).join("");
11953
11269
  if (source === "")
11954
11270
  return block;
11955
- const lines2 = source.split(`
11271
+ const lines = source.split(`
11956
11272
  `);
11957
- const span = blockSpan(lines2, file);
11273
+ const span = blockSpan(lines, file);
11958
11274
  if (span === null) {
11959
11275
  const separator = source.endsWith(`
11960
11276
  `) ? eol : `${eol}${eol}`;
11961
11277
  return `${source}${separator}${block}`;
11962
11278
  }
11963
- const before = lines2.slice(0, span.begin).map((line) => `${line}
11279
+ const before = lines.slice(0, span.begin).map((line) => `${line}
11964
11280
  `).join("");
11965
- const after = lines2.slice(span.end + 1).join(`
11281
+ const after = lines.slice(span.end + 1).join(`
11966
11282
  `);
11967
11283
  return `${before}${block}${after}`;
11968
11284
  }
@@ -11971,21 +11287,21 @@ function requireAbsolute(root) {
11971
11287
  throw new GitignoreError(`project root is ${describeValue(root)}, expected an absolute path`);
11972
11288
  }
11973
11289
  }
11974
- function attempt(file, failure3, act) {
11290
+ function attempt(file, failure, act) {
11975
11291
  try {
11976
11292
  return act();
11977
11293
  } catch (error) {
11978
- throw new GitignoreError(`${file} ${failure3} (${messageOf(error)})`, { cause: error });
11294
+ throw new GitignoreError(`${file} ${failure} (${messageOf(error)})`, { cause: error });
11979
11295
  }
11980
11296
  }
11981
11297
  function readIfPresent2(file) {
11982
- if (!existsSync7(file))
11298
+ if (!existsSync6(file))
11983
11299
  return null;
11984
11300
  return attempt(file, "cannot be read", () => readFileSync12(file, "utf8"));
11985
11301
  }
11986
11302
  function writeTrackingGitignore(root, flags) {
11987
11303
  requireAbsolute(root);
11988
- const file = join26(root, GITIGNORE_FILE);
11304
+ const file = join24(root, GITIGNORE_FILE);
11989
11305
  const text = readIfPresent2(file);
11990
11306
  const next = withTrackingBlock(text, flags, file);
11991
11307
  if (next === text)
@@ -12007,7 +11323,7 @@ function trackingDigest(flags) {
12007
11323
  }
12008
11324
  function exposureNotice(root) {
12009
11325
  return [
12010
- `tracking.all is true, so ${join26(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
11326
+ `tracking.all is true, so ${join24(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
12011
11327
  " - unpatched issues described in specs and plans",
12012
11328
  " - effort rows with model names and token counts",
12013
11329
  " - instincts that may quote error output",
@@ -12020,7 +11336,7 @@ function printWarning(message) {
12020
11336
  }
12021
11337
  function noticeTrackingChange(root, flags, print2 = printWarning) {
12022
11338
  requireAbsolute(root);
12023
- const digestFile = join26(root, TRACKING_DIGEST_FILE);
11339
+ const digestFile = join24(root, TRACKING_DIGEST_FILE);
12024
11340
  const recorded = `${trackingDigest(flags)}
12025
11341
  `;
12026
11342
  if (readIfPresent2(digestFile) === recorded) {
@@ -12119,8 +11435,8 @@ async function promptForRoot(found, prompter, seams) {
12119
11435
  }
12120
11436
 
12121
11437
  // src/project/scaffold.ts
12122
- import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync4, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
12123
- import { join as join27 } from "path";
11438
+ import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
11439
+ import { join as join25 } from "path";
12124
11440
 
12125
11441
  // src/commands/init-release.ts
12126
11442
  import { writeFileSync as writeFileSync4 } from "fs";
@@ -12150,9 +11466,9 @@ var COMMENTED_RELEASE_KEY = /^#\s*release:\s*$/u;
12150
11466
  var COMMENTED_ENABLED_KEY = /^#\s+enabled:/u;
12151
11467
  var ENABLED_KEY = /^\s+enabled\s*:/u;
12152
11468
  var SKIPPABLE = /^(\s*#|\s*$)/u;
12153
- function enabledLineIn(lines2, open) {
12154
- for (let index = open + 1;index < lines2.length; index += 1) {
12155
- const line = lines2[index] ?? "";
11469
+ function enabledLineIn(lines, open) {
11470
+ for (let index = open + 1;index < lines.length; index += 1) {
11471
+ const line = lines[index] ?? "";
12156
11472
  if (SKIPPABLE.test(line))
12157
11473
  continue;
12158
11474
  if (ENABLED_KEY.test(line))
@@ -12162,8 +11478,8 @@ function enabledLineIn(lines2, open) {
12162
11478
  }
12163
11479
  return null;
12164
11480
  }
12165
- function appended(lines2, block) {
12166
- const body = lines2.at(-1) === "" ? lines2.slice(0, -1) : [...lines2];
11481
+ function appended(lines, block) {
11482
+ const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
12167
11483
  return [...body, ...block, ""];
12168
11484
  }
12169
11485
  function namesRelease(text) {
@@ -12177,28 +11493,28 @@ function namesRelease(text) {
12177
11493
  }
12178
11494
  function withReleaseEnabled(text, enabled) {
12179
11495
  const setting = ` enabled: ${String(enabled)}`;
12180
- const lines2 = text.split(`
11496
+ const lines = text.split(`
12181
11497
  `);
12182
- const open = lines2.findIndex((line) => RELEASE_KEY.test(line));
11498
+ const open = lines.findIndex((line) => RELEASE_KEY.test(line));
12183
11499
  if (open >= 0) {
12184
- const at = enabledLineIn(lines2, open);
11500
+ const at = enabledLineIn(lines, open);
12185
11501
  if (at === null) {
12186
- return [...lines2.slice(0, open + 1), setting, ...lines2.slice(open + 1)].join(`
11502
+ return [...lines.slice(0, open + 1), setting, ...lines.slice(open + 1)].join(`
12187
11503
  `);
12188
11504
  }
12189
- return [...lines2.slice(0, at), setting, ...lines2.slice(at + 1)].join(`
11505
+ return [...lines.slice(0, at), setting, ...lines.slice(at + 1)].join(`
12190
11506
  `);
12191
11507
  }
12192
- const commented = lines2.findIndex((line) => COMMENTED_RELEASE_KEY.test(line));
11508
+ const commented = lines.findIndex((line) => COMMENTED_RELEASE_KEY.test(line));
12193
11509
  if (commented >= 0) {
12194
- const next = lines2[commented + 1] ?? "";
11510
+ const next = lines[commented + 1] ?? "";
12195
11511
  const after = COMMENTED_ENABLED_KEY.test(next) ? commented + 2 : commented + 1;
12196
- return [...lines2.slice(0, commented), "release:", setting, ...lines2.slice(after)].join(`
11512
+ return [...lines.slice(0, commented), "release:", setting, ...lines.slice(after)].join(`
12197
11513
  `);
12198
11514
  }
12199
11515
  if (namesRelease(text))
12200
11516
  return null;
12201
- return appended(lines2, ["release:", setting]).join(`
11517
+ return appended(lines, ["release:", setting]).join(`
12202
11518
  `);
12203
11519
  }
12204
11520
 
@@ -12313,11 +11629,18 @@ function renderReleaseStep(result2) {
12313
11629
  // src/project/scaffold.ts
12314
11630
  var PROJECT_TREE = Object.freeze(["specs", "plans", "runs", "effort", "instincts"]);
12315
11631
  var USER_TREE = Object.freeze(["instincts"]);
11632
+ function globsLine(patterns) {
11633
+ return `[${patterns.map((pattern) => JSON.stringify(pattern)).join(", ")}]`;
11634
+ }
12316
11635
  var CONFIG_SETTINGS_LINES = Object.freeze([
12317
11636
  `# store: ${CONFIG_DEFAULTS.store} # sqlite | ndjson`,
12318
11637
  "# effort:",
12319
11638
  `# busyTimeoutMs: ${String(CONFIG_DEFAULTS.effortBusyTimeoutMs)} # 1..60000 ms; how long a store open waits for another process's lock`,
12320
11639
  `# sync: ${CONFIG_DEFAULTS.effortSync} # local | file | git | service | p2p; how the store travels between devices`,
11640
+ "# hub:",
11641
+ "# url: # the hub effort.sync: service reaches, required with it",
11642
+ "# tokenSecret: # the secret store name the hub token is kept under, never the token",
11643
+ `# timeout: ${CONFIG_DEFAULTS.hubTimeout} # 1s..30s; how long one request to the hub may take`,
12321
11644
  "# plan:",
12322
11645
  `# inject: ${CONFIG_DEFAULTS.inject} # full | stage | task`,
12323
11646
  `# dir: ${CONFIG_DEFAULTS.planDir}`,
@@ -12347,6 +11670,8 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
12347
11670
  "# loop:",
12348
11671
  `# settingSources: ${CONFIG_DEFAULTS.settingSources.join(",")} # a comma-separated subset of user, project, local`,
12349
11672
  `# worktreeDir: ${CONFIG_DEFAULTS.loopWorktreeDir} # where a loop adds its worktrees, read from the project root`,
11673
+ "# wrapUp:",
11674
+ `# retries: ${String(CONFIG_DEFAULTS.loopWrapUpRetries)} # 1..3 | false, wrap-up sessions rerun before rafa opens a missing PR itself`,
12350
11675
  "# pr:",
12351
11676
  "# provider: # gh | none; unset reads it off the origin remote",
12352
11677
  `# mergeMethod: ${CONFIG_DEFAULTS.prMergeMethod} # squash | merge | rebase`,
@@ -12361,6 +11686,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
12361
11686
  "# claims:",
12362
11687
  `# staleAfter: ${CONFIG_DEFAULTS.claimsStaleAfter} # 36h | 3d | disabled; how long a rafa:claimed claim stands before a takeover`,
12363
11688
  `# ahead: ${CONFIG_DEFAULTS.claimsAhead} # off | allow; whether a claim may reach one issue ahead`,
11689
+ "# triage:",
11690
+ "# similarity:",
11691
+ `# threshold: ${String(CONFIG_DEFAULTS.triageSimilarityThreshold)} # above 0 and at most 1 | false; the least score at which triage comments on the nearest open bug`,
11692
+ `# candidates: ${String(CONFIG_DEFAULTS.triageSimilarityCandidates)} # 1..10, the nearest issues a new bug lists as Possible duplicates`,
12364
11693
  "# release:",
12365
11694
  `# enabled: ${String(CONFIG_DEFAULTS.releaseEnabled)} # true | false | auto, which is on when both files below exist`,
12366
11695
  `# versionFile: ${CONFIG_DEFAULTS.releaseVersionFile}`,
@@ -12386,7 +11715,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
12386
11715
  ...DEFAULT_ROUTES.map(([shape, agent]) => `# ${shape}: ${agent}`),
12387
11716
  "# task:",
12388
11717
  `# skills: ${CONFIG_DEFAULTS.taskSkills} # planner | tag | none, the resolver that picks a task's skills`,
12389
- `# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`
11718
+ `# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`,
11719
+ "# tests:",
11720
+ `# fullSuiteTriggers: ${globsLine(CONFIG_DEFAULTS.testsFullSuiteTriggers)} # a task whose diff matches one runs the full suite`,
11721
+ `# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`
12390
11722
  ]);
12391
11723
  var VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
12392
11724
  var PROJECT_HEADER = Object.freeze([
@@ -12424,18 +11756,18 @@ class ScaffoldError extends Error {
12424
11756
  }
12425
11757
  function layoutOf2(base, tree) {
12426
11758
  const scope = scopeAt(base);
12427
- return { dirs: [scope.dir, ...tree.map((name) => join27(scope.dir, name))], configFile: configFilePath(base) };
11759
+ return { dirs: [scope.dir, ...tree.map((name) => join25(scope.dir, name))], configFile: configFilePath(base) };
12428
11760
  }
12429
11761
  function occupied(path) {
12430
11762
  return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
12431
11763
  }
12432
11764
  function isDirectory2(path) {
12433
- return statSync7(path, { throwIfNoEntry: false })?.isDirectory() === true;
11765
+ return statSync6(path, { throwIfNoEntry: false })?.isDirectory() === true;
12434
11766
  }
12435
11767
  function directoryConflict(dir) {
12436
11768
  if (!occupied(dir) || isDirectory2(dir))
12437
11769
  return null;
12438
- return existsSync8(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
11770
+ return existsSync7(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
12439
11771
  }
12440
11772
  function layoutConflicts(layout) {
12441
11773
  const [scopeDir, ...tree] = layout.dirs;
@@ -12443,21 +11775,21 @@ function layoutConflicts(layout) {
12443
11775
  if (opening !== null)
12444
11776
  return [opening];
12445
11777
  const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
12446
- const file = occupied(layout.configFile) && !existsSync8(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
11778
+ const file = occupied(layout.configFile) && !existsSync7(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
12447
11779
  return [...dirs, ...file];
12448
11780
  }
12449
11781
  function scaffoldConflicts(root, home) {
12450
11782
  return [...layoutConflicts(layoutOf2(root, PROJECT_TREE)), ...layoutConflicts(layoutOf2(home, USER_TREE))];
12451
11783
  }
12452
- function attempt2(path, failure3, act) {
11784
+ function attempt2(path, failure, act) {
12453
11785
  try {
12454
11786
  act();
12455
11787
  } catch (error) {
12456
- throw new ScaffoldError(`${path} ${failure3} (${messageOf(error)})`, { cause: error });
11788
+ throw new ScaffoldError(`${path} ${failure} (${messageOf(error)})`, { cause: error });
12457
11789
  }
12458
11790
  }
12459
11791
  function ensureDirectory(path) {
12460
- if (existsSync8(path))
11792
+ if (existsSync7(path))
12461
11793
  return { path, kind: "directory", change: "unchanged" };
12462
11794
  attempt2(path, "cannot be created", () => {
12463
11795
  mkdirSync4(path, { recursive: true });
@@ -12465,7 +11797,7 @@ function ensureDirectory(path) {
12465
11797
  return { path, kind: "directory", change: "created" };
12466
11798
  }
12467
11799
  function ensureFile(path, text) {
12468
- if (existsSync8(path))
11800
+ if (existsSync7(path))
12469
11801
  return { path, kind: "file", change: "unchanged" };
12470
11802
  attempt2(path, "cannot be written", () => {
12471
11803
  writeFileSync5(path, text, { encoding: "utf8", flag: "wx" });
@@ -12504,8 +11836,8 @@ var SOURCE_PHRASES = {
12504
11836
  "root-flag": "named by --root",
12505
11837
  typed: "typed at the prompt"
12506
11838
  };
12507
- function refusal9(lines2) {
12508
- return new CommandExit(1, [...lines2, NOTHING_WRITTEN2].join(`
11839
+ function refusal9(lines) {
11840
+ return new CommandExit(1, [...lines, NOTHING_WRITTEN2].join(`
12509
11841
  `));
12510
11842
  }
12511
11843
  function expectNoArgument3(args) {
@@ -12601,9 +11933,9 @@ function resolvedConfig2(root, home, warn3) {
12601
11933
  }
12602
11934
  }
12603
11935
  function checkGitignore(root, flags) {
12604
- const file = join28(root, GITIGNORE_FILE);
11936
+ const file = join26(root, GITIGNORE_FILE);
12605
11937
  try {
12606
- const text = existsSync9(file) ? readFileSync14(file, "utf8") : null;
11938
+ const text = existsSync8(file) ? readFileSync14(file, "utf8") : null;
12607
11939
  withTrackingBlock(text, flags, file);
12608
11940
  } catch (error) {
12609
11941
  const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
@@ -12635,8 +11967,8 @@ function initialise(root, start2, home, context, entry) {
12635
11967
  const resolved = resolvedConfig2(root.path, home, warn3);
12636
11968
  const config = resolved.config;
12637
11969
  checkGitignore(root.path, config);
12638
- const configExisted = existsSync9(configFilePath(root.path));
12639
- const digestExisted = existsSync9(join28(root.path, TRACKING_DIGEST_FILE));
11970
+ const configExisted = existsSync8(configFilePath(root.path));
11971
+ const digestExisted = existsSync8(join26(root.path, TRACKING_DIGEST_FILE));
12640
11972
  const project = writeProjectScope(root.path);
12641
11973
  const tracking = applyTracking(root.path, config, warn3);
12642
11974
  const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
@@ -12689,7 +12021,7 @@ async function releaseStep(scopes, wanted, yes, seams) {
12689
12021
  });
12690
12022
  }
12691
12023
  function shownPath2(write, root) {
12692
- const shown = write.path.startsWith(`${root}${sep4}`) ? relative5(root, write.path) : write.path;
12024
+ const shown = write.path.startsWith(`${root}${sep3}`) ? relative5(root, write.path) : write.path;
12693
12025
  return write.kind === "directory" ? `${shown}/` : shown;
12694
12026
  }
12695
12027
  function renderInit(result2) {
@@ -12921,15 +12253,15 @@ async function runCheckCommand(context, spec, seams) {
12921
12253
  fix
12922
12254
  }, spec.usage);
12923
12255
  const result2 = checkCommandResult(report2, projectRoot2);
12924
- const lines2 = renderCheck(result2);
12256
+ const lines = renderCheck(result2);
12925
12257
  if (report2.exitCode > 0)
12926
- throw new CommandExit(report2.exitCode, lines2.join(`
12258
+ throw new CommandExit(report2.exitCode, lines.join(`
12927
12259
  `));
12928
12260
  if (context.outputMode === "json") {
12929
12261
  context.output.result(result2);
12930
12262
  return;
12931
12263
  }
12932
- for (const line of lines2)
12264
+ for (const line of lines)
12933
12265
  context.output.info(line);
12934
12266
  }
12935
12267
  function checkCommandRun(spec, seams) {
@@ -13290,12 +12622,12 @@ function everyRecord(context) {
13290
12622
  clean: records.filter((entry) => entry.instinct !== null).length
13291
12623
  };
13292
12624
  }
13293
- function answer(context, result2, lines2) {
12625
+ function answer(context, result2, lines) {
13294
12626
  if (context.outputMode === "json") {
13295
12627
  context.output.result(result2);
13296
12628
  return;
13297
12629
  }
13298
- for (const line of lines2())
12630
+ for (const line of lines())
13299
12631
  context.output.info(line);
13300
12632
  }
13301
12633
  async function runList4(context, seams) {
@@ -13497,8 +12829,8 @@ function createInstinctShowCommand() {
13497
12829
  var show_default3 = createInstinctShowCommand();
13498
12830
 
13499
12831
  // src/commands/issue/check.ts
13500
- import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync8 } from "fs";
13501
- import { join as join29, resolve as resolve9 } from "path";
12832
+ import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
12833
+ import { join as join27, resolve as resolve9 } from "path";
13502
12834
  var CHECK_USAGE = "rafa issue check <n> [--stamp]";
13503
12835
  var STAMP_FLAG = "stamp";
13504
12836
  var ISSUE_NUMBER7 = /^[1-9]\d*$/u;
@@ -13530,7 +12862,7 @@ function copyNames(dir, issue) {
13530
12862
  }
13531
12863
  const prefix = `${boardId(issue)}-`;
13532
12864
  const notes = notesFileName(issue);
13533
- return names.filter((name) => name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name !== notes).filter((name) => name.length > prefix.length + SPEC_EXTENSION.length).filter((name) => statSync8(join29(dir, name)).isFile()).sort();
12865
+ return names.filter((name) => name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name !== notes).filter((name) => name.length > prefix.length + SPEC_EXTENSION.length).filter((name) => statSync7(join27(dir, name)).isFile()).sort();
13534
12866
  }
13535
12867
  function findSavedCopy(root, specsDir, issue) {
13536
12868
  const names = copyNames(resolve9(root, specsDir), issue);
@@ -13540,11 +12872,11 @@ function findSavedCopy(root, specsDir, issue) {
13540
12872
  if (names.length > 1) {
13541
12873
  throw new CommandExit(1, [
13542
12874
  `\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
13543
- ...names.map((name) => ` ${join29(specsDir, name)}`)
12875
+ ...names.map((name) => ` ${join27(specsDir, name)}`)
13544
12876
  ].join(`
13545
12877
  `));
13546
12878
  }
13547
- const path = join29(specsDir, only);
12879
+ const path = join27(specsDir, only);
13548
12880
  return { path, absolute: resolve9(root, path) };
13549
12881
  }
13550
12882
  function liveText(live) {
@@ -13562,7 +12894,7 @@ function checkedRef(row2, stamp) {
13562
12894
  unblock: row2.unblock
13563
12895
  });
13564
12896
  }
13565
- function rowSuffix2(ref8) {
12897
+ function rowSuffix(ref8) {
13566
12898
  const noun = ref8.changedHeadings.length === 1 ? "heading" : "headings";
13567
12899
  const headings = ref8.changedHeadings.length === 0 ? "" : `: ${noun} ${ref8.changedHeadings.map((heading) => `"${heading}"`).join(", ")} changed`;
13568
12900
  const after = ref8.unblock === null ? "" : ` \u2014 ${ref8.unblock}`;
@@ -13570,7 +12902,7 @@ function rowSuffix2(ref8) {
13570
12902
  return `${headings} (line ${String(ref8.line)})${after}${unknown}`;
13571
12903
  }
13572
12904
  function checkedRefLine(ref8) {
13573
- return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix2(ref8)}`;
12905
+ return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix(ref8)}`;
13574
12906
  }
13575
12907
  function countLine(references) {
13576
12908
  const noun = references.length === 1 ? "reference" : "references";
@@ -13961,7 +13293,7 @@ function renderSessionList(root, list) {
13961
13293
  return [
13962
13294
  "Running sessions:",
13963
13295
  ...list.sessions.map(({ session, tasks }) => {
13964
- const counts = tasks === null ? "no plan or tracker to count" : formatCounts(tasks);
13296
+ const counts = tasks === null ? `no plan or tracker to count ${phaseNote(session)}` : phasedCounts(tasks, session);
13965
13297
  return ` ${sessionLine(session)}; ${counts}; ${checkoutColumn(root, session)}`;
13966
13298
  })
13967
13299
  ];
@@ -13973,6 +13305,7 @@ async function runList5(context, seams) {
13973
13305
  const checklist = readSessionChecklist(root, session);
13974
13306
  return {
13975
13307
  session,
13308
+ phase: sessionPhase(session),
13976
13309
  tasks: checklist === null ? null : countTasks(checklist.tasks)
13977
13310
  };
13978
13311
  });
@@ -13991,7 +13324,7 @@ function createLoopListCommand(seams = {}) {
13991
13324
  subject: "loop",
13992
13325
  action: "list",
13993
13326
  summary: "list the running sessions, with the tasks done in each plan",
13994
- description: "Lists every session record under `.rafa/runs/` that reads `running` or `paused`, oldest" + " first: its id, plan, branch, state, pid and start, the tasks of its plan done, blocked and" + " open, and the worktree it runs in, or the main checkout. A record whose pid is gone reads `stopped` and is not listed. Until phase 6 a plan runs in" + " one session at a time, so each row is a plan running. With `--output=json` the sessions are the" + " data of the terminal result event.",
13327
+ description: "Lists every session record under `.rafa/runs/` that reads `running` or `paused`, oldest" + " first: its id, plan, branch, state, pid and start, the tasks of its plan done, blocked and" + " open with the phase its run is in beside them, and the worktree it runs in, or the main checkout. A record whose pid is gone reads `stopped` and is not listed. Until phase 6 a plan runs in" + " one session at a time, so each row is a plan running. With `--output=json` the sessions are the" + " data of the terminal result event.",
13995
13328
  args: [],
13996
13329
  flags: [],
13997
13330
  examples: [
@@ -14205,10 +13538,10 @@ var wrapped = wrapPhaseZeroCommand({
14205
13538
  }
14206
13539
  ],
14207
13540
  aliases: ["start"],
14208
- outputs: ["text", "json"],
13541
+ outputs: ["text", "json", "events"],
14209
13542
  spends: {
14210
13543
  when: "always",
14211
- what: "one session per task, one for the wrap-up, and repair sessions while CI is red"
13544
+ what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request, and repair sessions while CI is red"
14212
13545
  }
14213
13546
  }, start);
14214
13547
  var loopStart = Object.freeze({
@@ -14250,7 +13583,7 @@ function renderStatus(status) {
14250
13583
  const pending = session.state === "paused" && task !== null ? " (the run holds once it ends)" : "";
14251
13584
  return [
14252
13585
  `Session ${sessionLine(session)}`,
14253
- tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count` : ` Tasks: ${formatCounts(tasks)}`,
13586
+ tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count ${phaseNote(session)}` : ` Tasks: ${phasedCounts(tasks, session)}`,
14254
13587
  ...blocked.flatMap(blockedLines),
14255
13588
  ...task === null ? [] : [` Task: line ${task.line}, ${task.text}${pending}`],
14256
13589
  ...eta === null ? [] : [` ${etaLine(eta)}`]
@@ -14271,6 +13604,7 @@ async function runStatus(context, seams) {
14271
13604
  }
14272
13605
  const status = {
14273
13606
  session: record,
13607
+ phase: sessionPhase(record),
14274
13608
  checklist: checklist?.file ?? null,
14275
13609
  tasks,
14276
13610
  blocked: blockedTasks(checklist),
@@ -14290,13 +13624,13 @@ function createLoopStatusCommand(seams = {}) {
14290
13624
  subject: "loop",
14291
13625
  action: "status",
14292
13626
  summary: "show a session: its state, tasks done over total and a rough ETA",
14293
- description: "Reads a session record and its plan's tracker: the state, the pid, the start, the task" + " it names, and the tasks done, blocked and open over the whole plan. A running or paused session" + " also gets a rough ETA from the effort store: the time from its start to the last task it" + " finished, per task finished, times the open and blocked tasks left, and none before its first" + " task finishes. Every blocked task of the checklist is shown with the blocker comment its line" + " trails, which is what the run wrote when that task ended blocked. Without `--session-id` it shows the session running on the branch checked out at" + " the project root, or the newest one that ran there. With `--output=json` the record, the counts" + " and the ETA are the data of the terminal result event.",
13627
+ description: "Reads a session record and its plan's tracker: the state, the pid, the start, the task" + " it names, the tasks done, blocked and open over the whole plan, and beside them the phase the" + " run is in: task, wrap-up, pull-request, ci or repair. A running or paused session" + " also gets a rough ETA from the effort store: the time from its start to the last task it" + " finished, per task finished, times the open and blocked tasks left, and none before its first" + " task finishes. Every blocked task of the checklist is shown with the blocker comment its line" + " trails, which is what the run wrote when that task ended blocked. Without `--session-id` it shows the session running on the branch checked out at" + " the project root, or the newest one that ran there. With `--output=json` the record, the counts" + " and the ETA are the data of the terminal result event.",
14294
13628
  args: [],
14295
13629
  flags: [sessionIdFlag("to show")],
14296
13630
  examples: [
14297
13631
  {
14298
13632
  cmd: "rafa loop status",
14299
- note: "Shows the session on the branch checked out here: its state, tasks done over total and ETA."
13633
+ note: "Shows the session on the branch checked out here: its state, tasks done over total, phase and ETA."
14300
13634
  },
14301
13635
  {
14302
13636
  cmd: "rafa loop status --session-id=9185b41c-65f7-4dd6-a0c1-6494c4028f0f --output=json",
@@ -14686,8 +14020,8 @@ function dryRunOf(flag, ceiling, hasTerminal) {
14686
14020
 
14687
14021
  // src/next/sync.ts
14688
14022
  var INDENT2 = " ";
14689
- function refuse6(lines2) {
14690
- return Object.freeze({ kind: "refused", message: lines2.join(`
14023
+ function refuse6(lines) {
14024
+ return Object.freeze({ kind: "refused", message: lines.join(`
14691
14025
  `) });
14692
14026
  }
14693
14027
  function quotedLines(said) {
@@ -14868,6 +14202,7 @@ async function runNext(context, seams) {
14868
14202
  ...roadmap ? { roadmap } : {},
14869
14203
  ...relations === undefined ? {} : { relations }
14870
14204
  });
14205
+ await (seams.pull ?? pullBeforeRead)({ roots: sources.roots, warn: (line) => context.output.warn(line) });
14871
14206
  const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
14872
14207
  const prompter = lazyPrompter(openPrompter);
14873
14208
  const dry = watchDryEpic();
@@ -15187,7 +14522,7 @@ var risk_default = createPlanRiskCommand();
15187
14522
 
15188
14523
  // src/commands/plan/show.ts
15189
14524
  import { readFileSync as readFileSync17 } from "fs";
15190
- import { join as join30 } from "path";
14525
+ import { join as join28 } from "path";
15191
14526
  var USAGE33 = "rafa plan show <stub> [--tracker]";
15192
14527
  var COMMAND7 = "rafa plan show";
15193
14528
  function readTrackerFlag(value) {
@@ -15205,9 +14540,9 @@ function showPlan(plans, stub, tracker) {
15205
14540
  throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
15206
14541
  }
15207
14542
  const name = planFileName(stub, tracker);
15208
- const file = join30(plans.path, name);
14543
+ const file = join28(plans.path, name);
15209
14544
  if (!isFile(file))
15210
- throw new CommandExit(1, missingText(stub, tracker, join30(plans.label, name)));
14545
+ throw new CommandExit(1, missingText(stub, tracker, join28(plans.label, name)));
15211
14546
  const model = parsePlan(readFileSync17(file, "utf8"));
15212
14547
  const { header } = model;
15213
14548
  return {
@@ -15231,24 +14566,24 @@ function taskLine(task) {
15231
14566
  return ` ${checkbox(task.status)} ${task.text}`;
15232
14567
  }
15233
14568
  function renderShownPlan(shown, dirLabel) {
15234
- const relative7 = join30(dirLabel, planFileName(shown.stub, shown.tracker));
14569
+ const relative7 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
15235
14570
  const fileLabel = shown.tracker ? "Tracker" : "File";
15236
- const lines2 = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
14571
+ const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
15237
14572
  if (shown.header.issue !== null)
15238
- lines2.push(`Issue: ${shown.header.issue}`);
14573
+ lines.push(`Issue: ${shown.header.issue}`);
15239
14574
  if (shown.header.spec !== null)
15240
- lines2.push(`Spec: ${shown.header.spec}`);
15241
- lines2.push(`Tasks: ${formatCounts(shown.counts)}`);
14575
+ lines.push(`Spec: ${shown.header.spec}`);
14576
+ lines.push(`Tasks: ${formatCounts(shown.counts)}`);
15242
14577
  const unstaged = shown.tasks.filter((task) => task.stage === null);
15243
14578
  if (unstaged.length > 0)
15244
- lines2.push("", ...unstaged.map(taskLine));
14579
+ lines.push("", ...unstaged.map(taskLine));
15245
14580
  for (const [index, stage] of shown.stages.entries()) {
15246
- lines2.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
14581
+ lines.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
15247
14582
  }
15248
14583
  if (shown.issues.length > 0) {
15249
- lines2.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative7, issue)}`));
14584
+ lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative7, issue)}`));
15250
14585
  }
15251
- return lines2;
14586
+ return lines;
15252
14587
  }
15253
14588
  var planShowCommand = {
15254
14589
  name: "plan show",
@@ -15820,19 +15155,19 @@ var YES_ANSWERS2 = ["y", "yes"];
15820
15155
  function answerOf(reading) {
15821
15156
  return reading.ok ? reading.verdict.answer : "unread";
15822
15157
  }
15823
- function guardRefusal(head, lines2) {
15824
- return new CommandExit(1, [`\u274C ${head}`, ...lines2.map((line) => ` ${line}`)].join(`
15158
+ function guardRefusal(head, lines) {
15159
+ return new CommandExit(1, [`\u274C ${head}`, ...lines.map((line) => ` ${line}`)].join(`
15825
15160
  `));
15826
15161
  }
15827
15162
  function refusalHead(number, answer2) {
15828
15163
  return answer2 === "collision" ? `rafa pr merge refuses #${number}: its release guard reads collision, which only dangerous.acceptVersionCollision lets through.` : `rafa pr merge refuses #${number}: its release guard reads ${answer2}, and pr.versionCollision is refuse.`;
15829
15164
  }
15830
- async function askGuard(step, answer2, lines2) {
15165
+ async function askGuard(step, answer2, lines) {
15831
15166
  const { number } = step.detail;
15832
15167
  if (!step.isTerminal()) {
15833
- throw guardRefusal(`rafa pr merge asks before merging #${number} with its release guard reading ${answer2} (pr.versionCollision is ask),` + " and standard input is no terminal; --yes does not answer it.", lines2);
15168
+ throw guardRefusal(`rafa pr merge asks before merging #${number} with its release guard reading ${answer2} (pr.versionCollision is ask),` + " and standard input is no terminal; --yes does not answer it.", lines);
15834
15169
  }
15835
- for (const line of lines2)
15170
+ for (const line of lines)
15836
15171
  step.warn(line);
15837
15172
  const prompter = step.openPrompter();
15838
15173
  try {
@@ -15842,11 +15177,11 @@ async function askGuard(step, answer2, lines2) {
15842
15177
  prompter.close();
15843
15178
  }
15844
15179
  }
15845
- function printLines(step, reaction, lines2) {
15180
+ function printLines(step, reaction, lines) {
15846
15181
  if (reaction === "silent")
15847
15182
  return;
15848
15183
  const write = reaction === "print" ? step.info : step.warn;
15849
- for (const line of lines2)
15184
+ for (const line of lines)
15850
15185
  write(line);
15851
15186
  if (reaction === "accept") {
15852
15187
  step.warn(`#${step.detail.number} merges anyway: dangerous.acceptVersionCollision is true.`);
@@ -15868,13 +15203,13 @@ async function guardBeforeMerge(step) {
15868
15203
  });
15869
15204
  const answer2 = answerOf(reading);
15870
15205
  const reaction = guardReaction(reading, settings);
15871
- const lines2 = guardLines(reading, settings.releaseVersionFile);
15872
- const report3 = { answer: answer2, reaction, lines: lines2 };
15206
+ const lines = guardLines(reading, settings.releaseVersionFile);
15207
+ const report3 = { answer: answer2, reaction, lines };
15873
15208
  if (reaction === "refuse")
15874
- throw guardRefusal(refusalHead(step.detail.number, answer2), lines2);
15209
+ throw guardRefusal(refusalHead(step.detail.number, answer2), lines);
15875
15210
  if (reaction === "ask")
15876
- return { report: report3, go: await askGuard(step, answer2, lines2) };
15877
- printLines(step, reaction, lines2);
15211
+ return { report: report3, go: await askGuard(step, answer2, lines) };
15212
+ printLines(step, reaction, lines);
15878
15213
  return { report: report3, go: true };
15879
15214
  }
15880
15215
 
@@ -15882,8 +15217,16 @@ async function guardBeforeMerge(step) {
15882
15217
  var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
15883
15218
  var WORKFLOWS_EXIST_WARNING = "CI may not have started (a path filter, a draft, Actions disabled, or it has not registered yet); this is probably not what you want";
15884
15219
  var UNCHECKED_MERGE_SENTENCE = "Merged with no checks reported, by rafa pr merge --skip-checks.";
15885
- function uncheckedCaseOf(workflowCount) {
15886
- return workflowCount === 0 ? "no-workflow" : "workflows-exist";
15220
+ function uncheckedCaseOf(workflowCount, baseWorkflows = null) {
15221
+ if (workflowCount === 0)
15222
+ return "no-workflow";
15223
+ if (workflowCount !== null && baseWorkflows !== null && !baseWorkflows.runsOnPullRequests) {
15224
+ return "no-pull-request-workflow";
15225
+ }
15226
+ return "workflows-exist";
15227
+ }
15228
+ function noPullRequestWorkflowLine(base) {
15229
+ return `no workflow runs on pull requests into ${base}`;
15887
15230
  }
15888
15231
  function workflowCountLine(workflowCount) {
15889
15232
  if (workflowCount === null)
@@ -15897,26 +15240,181 @@ function skipChecksCommand(number) {
15897
15240
  function uncheckedQuestion(number) {
15898
15241
  return `Merge #${number} with no checks? [y/N]`;
15899
15242
  }
15900
- function uncheckedComment(workflowCount) {
15901
- return `${UNCHECKED_MERGE_SENTENCE}
15243
+ function uncheckedComment(workflowCount, baseLine = null) {
15244
+ const countLine2 = workflowCountLine(workflowCount);
15245
+ const paragraphs = baseLine === null ? [UNCHECKED_MERGE_SENTENCE, countLine2] : [UNCHECKED_MERGE_SENTENCE, countLine2, baseLine];
15246
+ return paragraphs.join(`
15902
15247
 
15903
- ${workflowCountLine(workflowCount)}`;
15248
+ `);
15904
15249
  }
15905
- function readUnchecked(number, workflowCount) {
15906
- const which = uncheckedCaseOf(workflowCount);
15907
- const warning = which === "no-workflow" ? NO_WORKFLOW_WARNING : WORKFLOWS_EXIST_WARNING;
15250
+ function warningOf(which, baseWorkflows) {
15251
+ if (which === "no-workflow")
15252
+ return NO_WORKFLOW_WARNING;
15253
+ if (which === "no-pull-request-workflow" && baseWorkflows !== null)
15254
+ return noPullRequestWorkflowLine(baseWorkflows.base);
15255
+ return WORKFLOWS_EXIST_WARNING;
15256
+ }
15257
+ function readUnchecked(number, workflowCount, baseWorkflows = null) {
15258
+ const which = uncheckedCaseOf(workflowCount, baseWorkflows);
15259
+ const warning = warningOf(which, baseWorkflows);
15260
+ const isBaseCase = which === "no-pull-request-workflow";
15908
15261
  return Object.freeze({
15909
15262
  case: which,
15910
15263
  warning: Object.freeze([workflowCountLine(workflowCount), warning]),
15911
- yesMayAnswer: which === "no-workflow",
15264
+ yesMayAnswer: which !== "workflows-exist",
15912
15265
  question: uncheckedQuestion(number),
15913
- comment: uncheckedComment(workflowCount)
15266
+ comment: uncheckedComment(workflowCount, isBaseCase ? warning : null)
15914
15267
  });
15915
15268
  }
15916
15269
 
15270
+ // src/pr/workflow-triggers.ts
15271
+ var PULL_REQUEST_EVENT = "pull_request";
15272
+ var UNKNOWN = "unknown";
15273
+ var BRACKET_BODY = /^[A-Za-z0-9-]+$/;
15274
+ var REGEX_SPECIAL = /[.*+?^${}()|[\]\\/]/g;
15275
+ function workflowsRunOnPullRequestsInto(workflowTexts, base) {
15276
+ return workflowTexts.some((text) => workflowNamesBase(text, base));
15277
+ }
15278
+ function workflowNamesBase(workflowText, base) {
15279
+ let document;
15280
+ try {
15281
+ document = Bun.YAML.parse(workflowText);
15282
+ } catch {
15283
+ return true;
15284
+ }
15285
+ return readWorkflow(document, base) !== false;
15286
+ }
15287
+ function readWorkflow(document, base) {
15288
+ if (!isPlainObject(document) || !("on" in document))
15289
+ return UNKNOWN;
15290
+ const on = document.on;
15291
+ if (typeof on === "string")
15292
+ return on === PULL_REQUEST_EVENT;
15293
+ if (Array.isArray(on)) {
15294
+ if (!on.every((event) => typeof event === "string"))
15295
+ return UNKNOWN;
15296
+ return on.includes(PULL_REQUEST_EVENT);
15297
+ }
15298
+ if (!isPlainObject(on))
15299
+ return UNKNOWN;
15300
+ if (!(PULL_REQUEST_EVENT in on))
15301
+ return false;
15302
+ return readPullRequestTrigger(on[PULL_REQUEST_EVENT], base);
15303
+ }
15304
+ function readPullRequestTrigger(trigger, base) {
15305
+ if (trigger === null || trigger === undefined)
15306
+ return true;
15307
+ if (!isPlainObject(trigger))
15308
+ return UNKNOWN;
15309
+ const branches = trigger.branches;
15310
+ const ignored = trigger["branches-ignore"];
15311
+ if (branches !== undefined && ignored !== undefined)
15312
+ return UNKNOWN;
15313
+ if (branches !== undefined)
15314
+ return readBranches(branches, base);
15315
+ if (ignored !== undefined)
15316
+ return readBranchesIgnore(ignored, base);
15317
+ return true;
15318
+ }
15319
+ function readBranches(filter, base) {
15320
+ const patterns = patternListOf(filter);
15321
+ if (patterns === null)
15322
+ return UNKNOWN;
15323
+ let named2 = false;
15324
+ for (const pattern of patterns) {
15325
+ const negated = pattern.startsWith("!");
15326
+ const matches = branchPatternMatches(negated ? pattern.slice(1) : pattern, base);
15327
+ if (matches === null)
15328
+ return UNKNOWN;
15329
+ if (matches)
15330
+ named2 = !negated;
15331
+ }
15332
+ return named2;
15333
+ }
15334
+ function readBranchesIgnore(filter, base) {
15335
+ const patterns = patternListOf(filter);
15336
+ if (patterns === null || patterns.some((pattern) => pattern.startsWith("!")))
15337
+ return UNKNOWN;
15338
+ let anyMatch = false;
15339
+ for (const pattern of patterns) {
15340
+ const matches = branchPatternMatches(pattern, base);
15341
+ if (matches === null)
15342
+ return UNKNOWN;
15343
+ anyMatch = anyMatch || matches;
15344
+ }
15345
+ return !anyMatch;
15346
+ }
15347
+ function patternListOf(filter) {
15348
+ if (typeof filter === "string")
15349
+ return [filter];
15350
+ if (Array.isArray(filter) && filter.every((pattern) => typeof pattern === "string"))
15351
+ return filter;
15352
+ return null;
15353
+ }
15354
+ function branchPatternMatches(pattern, branch) {
15355
+ const source = regexSourceOf(pattern);
15356
+ if (source === null)
15357
+ return null;
15358
+ try {
15359
+ return new RegExp(`^${source}$`).test(branch);
15360
+ } catch {
15361
+ return null;
15362
+ }
15363
+ }
15364
+ function regexSourceOf(pattern) {
15365
+ let source = "";
15366
+ let quantifiable = false;
15367
+ let index = 0;
15368
+ while (index < pattern.length) {
15369
+ const char = pattern.charAt(index);
15370
+ if (char === "*") {
15371
+ const isDouble = pattern.charAt(index + 1) === "*";
15372
+ source += isDouble ? ".*" : "[^/]*";
15373
+ index += isDouble ? 2 : 1;
15374
+ quantifiable = false;
15375
+ } else if (char === "?" || char === "+") {
15376
+ if (!quantifiable)
15377
+ return null;
15378
+ source += char;
15379
+ index += 1;
15380
+ quantifiable = false;
15381
+ } else if (char === "[") {
15382
+ const close = pattern.indexOf("]", index + 1);
15383
+ if (close === -1)
15384
+ return null;
15385
+ const body = pattern.slice(index + 1, close);
15386
+ if (!BRACKET_BODY.test(body))
15387
+ return null;
15388
+ source += `[${body}]`;
15389
+ index = close + 1;
15390
+ quantifiable = true;
15391
+ } else if (char === "\\") {
15392
+ if (index + 1 >= pattern.length)
15393
+ return null;
15394
+ source += escapeRegex(pattern.charAt(index + 1));
15395
+ index += 2;
15396
+ quantifiable = true;
15397
+ } else {
15398
+ source += escapeRegex(char);
15399
+ index += 1;
15400
+ quantifiable = true;
15401
+ }
15402
+ }
15403
+ return source;
15404
+ }
15405
+ function escapeRegex(char) {
15406
+ return char.replace(REGEX_SPECIAL, "\\$&");
15407
+ }
15408
+ function isPlainObject(value) {
15409
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15410
+ }
15411
+
15917
15412
  // src/commands/pr/merge-unchecked.ts
15918
15413
  var INDENT4 = " ";
15919
15414
  var YES_ANSWERS3 = ["y", "yes"];
15415
+ var WORKFLOWS_DIR = ".github/workflows/";
15416
+ var WORKFLOW_ENDINGS = [".yml", ".yaml"];
15417
+ var LS_TREE_ENTRY = /^\d+ (\w+) ([0-9a-f]+)\t(.+)$/s;
15920
15418
  function refusal10(head, reading, summary, tail) {
15921
15419
  return new CommandExit(1, [
15922
15420
  `\u274C ${head}`,
@@ -15933,10 +15431,37 @@ async function workflowCountOf(pulls) {
15933
15431
  return null;
15934
15432
  }
15935
15433
  }
15434
+ function workflowBlobsOf(listing) {
15435
+ return listing.split("\x00").map((entry) => LS_TREE_ENTRY.exec(entry)).filter((match) => match !== null).filter(([, type, , path]) => type === "blob" && WORKFLOW_ENDINGS.some((ending) => path?.endsWith(ending) === true)).map(([, , oid]) => oid ?? "");
15436
+ }
15437
+ function readBaseWorkflows(git, base) {
15438
+ if (base === "" || base.startsWith("-"))
15439
+ return null;
15440
+ if (!git(["fetch", "origin", base]).ok)
15441
+ return null;
15442
+ const listed2 = git(["ls-tree", "--full-tree", "-z", `origin/${base}`, WORKFLOWS_DIR]);
15443
+ if (!listed2.ok)
15444
+ return null;
15445
+ const blobs = workflowBlobsOf(listed2.stdout);
15446
+ if (blobs.length === 0)
15447
+ return null;
15448
+ const shown = blobs.map((oid) => git(["cat-file", "blob", oid]));
15449
+ if (shown.some((result2) => !result2.ok))
15450
+ return null;
15451
+ const texts = shown.map((result2) => result2.stdout);
15452
+ return Object.freeze({ base, runsOnPullRequests: workflowsRunOnPullRequestsInto(texts, base) });
15453
+ }
15454
+ function baseWorkflowsOf(options, workflowCount) {
15455
+ const { git, base } = options;
15456
+ if (workflowCount === null || workflowCount === 0 || git === undefined || base === undefined)
15457
+ return null;
15458
+ return readBaseWorkflows(git, base);
15459
+ }
15936
15460
  async function readUncheckedMerge(options) {
15937
15461
  const { pulls, number, yes, summary } = options;
15938
15462
  const workflowCount = await workflowCountOf(pulls);
15939
- const reading = readUnchecked(number, workflowCount);
15463
+ const baseWorkflows = baseWorkflowsOf(options, workflowCount);
15464
+ const reading = readUnchecked(number, workflowCount, baseWorkflows);
15940
15465
  if (yes && !reading.yesMayAnswer) {
15941
15466
  throw refusal10(`rafa pr merge refuses --yes for #${number} with no checks: workflows exist, or their count could not be read.`, reading, summary, "That is more likely a fault than a choice, so a person has to answer. Run it again without --yes on a terminal.");
15942
15467
  }
@@ -15945,7 +15470,7 @@ async function readUncheckedMerge(options) {
15945
15470
  const tail = reading.yesMayAnswer ? "Merge it without the question with --yes." : "--yes is refused here, so run it on a terminal and answer it.";
15946
15471
  throw refusal10(`rafa pr merge asks before merging #${number} with no checks, and standard input is no terminal.`, reading, summary, tail);
15947
15472
  }
15948
- return Object.freeze({ workflowCount, reading, yes });
15473
+ return Object.freeze({ workflowCount, baseWorkflows, reading, yes });
15949
15474
  }
15950
15475
  function isYes(answer2) {
15951
15476
  return answer2 !== null && YES_ANSWERS3.includes(answer2.trim().toLowerCase());
@@ -15984,8 +15509,8 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
15984
15509
  var USAGE36 = PR_USAGE.merge;
15985
15510
  var YES_ANSWERS4 = ["y", "yes"];
15986
15511
  var DEFAULT_MERGE_SEAMS = Object.freeze({});
15987
- function refusal11(lines2) {
15988
- return new CommandExit(1, lines2.join(`
15512
+ function refusal11(lines) {
15513
+ return new CommandExit(1, lines.join(`
15989
15514
  `));
15990
15515
  }
15991
15516
  function readMethodFlag(flags, usage2) {
@@ -16127,8 +15652,8 @@ async function reportAfterCleanUp(context, pr, seams, detail) {
16127
15652
  return { unblocked: null, freed };
16128
15653
  }
16129
15654
  async function askToMerge(context, pr, seams, ask) {
16130
- const { number, summary, yes, skipChecks } = ask;
16131
- const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal }) : null;
15655
+ const { number, summary, yes, skipChecks, base, git } = ask;
15656
+ const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal, base, git }) : null;
16132
15657
  if (unchecked === null && !yes)
16133
15658
  requireTerminal(seams, summary);
16134
15659
  context.output.info(summary);
@@ -16193,7 +15718,7 @@ async function runMerge2(context, seams) {
16193
15718
  openPrompter: prompterOf(seams)
16194
15719
  });
16195
15720
  const summary = summaryLine2(detail, method);
16196
- const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks }) : { go: false, unchecked: null };
15721
+ const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks, base: detail.baseRefName, git }) : { go: false, unchecked: null };
16197
15722
  const answered3 = {
16198
15723
  number: detail.number,
16199
15724
  source: pick.source,
@@ -16257,7 +15782,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
16257
15782
  subject: "pr",
16258
15783
  action: "merge",
16259
15784
  summary: "merge a pull request and clean up both branches",
16260
- description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
15785
+ description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
16261
15786
  args: [
16262
15787
  {
16263
15788
  name: "n",
@@ -16273,7 +15798,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
16273
15798
  },
16274
15799
  {
16275
15800
  name: "skip-checks",
16276
- description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow or its workflow count could not be read.",
15801
+ description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow that may run on pull requests into the base, or its workflow count could not be read.",
16277
15802
  type: "boolean"
16278
15803
  },
16279
15804
  {
@@ -16359,9 +15884,9 @@ function parseFailedLog(text) {
16359
15884
  end -= 1;
16360
15885
  return raw.slice(0, end).map((line) => parseFailedLogLine(line));
16361
15886
  }
16362
- function readFailedStep(lines2) {
15887
+ function readFailedStep(lines) {
16363
15888
  let group;
16364
- for (const line of lines2) {
15889
+ for (const line of lines) {
16365
15890
  const head = groupStep(line.text);
16366
15891
  if (head !== undefined)
16367
15892
  group = head;
@@ -16372,14 +15897,14 @@ function readFailedStep(lines2) {
16372
15897
  if (group !== undefined)
16373
15898
  return { name: group, source: "group-marker" };
16374
15899
  }
16375
- const column = lines2.find((line) => line.step !== undefined)?.step;
15900
+ const column = lines.find((line) => line.step !== undefined)?.step;
16376
15901
  if (column !== undefined)
16377
15902
  return { name: column, source: "step-column" };
16378
15903
  return group === undefined ? undefined : { name: group, source: "group-marker" };
16379
15904
  }
16380
- function readJobs(lines2) {
15905
+ function readJobs(lines) {
16381
15906
  const jobs = [];
16382
- for (const line of lines2) {
15907
+ for (const line of lines) {
16383
15908
  if (line.job !== undefined && !jobs.includes(line.job))
16384
15909
  jobs.push(line.job);
16385
15910
  }
@@ -16395,14 +15920,14 @@ function readCap(value) {
16395
15920
  }
16396
15921
  function readFailedLog(text, options = {}) {
16397
15922
  const cap = readCap(options.maxLines);
16398
- const lines2 = parseFailedLog(text);
16399
- const kept = lines2.slice(Math.max(0, lines2.length - cap));
15923
+ const lines = parseFailedLog(text);
15924
+ const kept = lines.slice(Math.max(0, lines.length - cap));
16400
15925
  return {
16401
- jobs: readJobs(lines2),
16402
- step: readFailedStep(lines2),
15926
+ jobs: readJobs(lines),
15927
+ step: readFailedStep(lines),
16403
15928
  lines: kept.map((line) => line.text),
16404
- omitted: lines2.length - kept.length,
16405
- total: lines2.length
15929
+ omitted: lines.length - kept.length,
15930
+ total: lines.length
16406
15931
  };
16407
15932
  }
16408
15933
 
@@ -17277,7 +16802,7 @@ var TRIAGE_FRESH_WINDOW_MS = TRIAGE_FRESH_HOURS * HOUR_MS2;
17277
16802
  var MAX_TRIAGE_CANDIDATES = 3;
17278
16803
  var REFUSE_EXIT_CODE = 2;
17279
16804
  var DAYS_FROM_HOURS = 48;
17280
- var REFUSAL_PREFIX2 = "rafa pr triage --resolve refuses";
16805
+ var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
17281
16806
  var INDENT6 = " ";
17282
16807
  function ageMs(updatedAt, now) {
17283
16808
  const then = Date.parse(updatedAt);
@@ -17290,7 +16815,7 @@ function isFreshCandidate(updatedAt, now) {
17290
16815
  const age = ageMs(updatedAt, now);
17291
16816
  return age !== null && age <= TRIAGE_FRESH_WINDOW_MS;
17292
16817
  }
17293
- function plural4(count, singular, many) {
16818
+ function plural2(count, singular, many) {
17294
16819
  return count === 1 ? `1 ${singular}` : `${count} ${many}`;
17295
16820
  }
17296
16821
  function describeAge(updatedAt, now) {
@@ -17303,8 +16828,8 @@ function describeAge(updatedAt, now) {
17303
16828
  if (hours < 1)
17304
16829
  return "less than an hour ago";
17305
16830
  if (hours < DAYS_FROM_HOURS)
17306
- return `${plural4(hours, "hour", "hours")} ago`;
17307
- return `${plural4(Math.floor(age / DAY_MS2), "day", "days")} ago`;
16831
+ return `${plural2(hours, "hour", "hours")} ago`;
16832
+ return `${plural2(Math.floor(age / DAY_MS2), "day", "days")} ago`;
17308
16833
  }
17309
16834
  function triageCommand(number, resolve11 = false) {
17310
16835
  return resolve11 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
@@ -17343,24 +16868,24 @@ function plain(decision, assess, headline) {
17343
16868
  });
17344
16869
  }
17345
16870
  function listing(decision, assess, rest, headline, exitCode = 0) {
17346
- const lines2 = Object.freeze(rest.map((one) => skippedLine(one)));
16871
+ const lines = Object.freeze(rest.map((one) => skippedLine(one)));
17347
16872
  return Object.freeze({
17348
16873
  decision,
17349
16874
  assess,
17350
16875
  skipped: Object.freeze([...rest]),
17351
16876
  exitCode,
17352
16877
  headline,
17353
- lines: lines2,
17354
- message: [headline, ...lines2].join(`
16878
+ lines,
16879
+ message: [headline, ...lines].join(`
17355
16880
  `)
17356
16881
  });
17357
16882
  }
17358
16883
  function refusal12(rest, count) {
17359
- const headline = `${REFUSAL_PREFIX2}: ${plural4(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
16884
+ const headline = `${REFUSAL_PREFIX}: ${plural2(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
17360
16885
  return listing("refuse", Object.freeze([]), rest, headline, REFUSE_EXIT_CODE);
17361
16886
  }
17362
16887
  function someReading(fresh, stale, count) {
17363
- const older = stale.length === 0 ? "" : `; ${plural4(stale.length, "older one is", "older ones are")} skipped`;
16888
+ const older = stale.length === 0 ? "" : `; ${plural2(stale.length, "older one is", "older ones are")} skipped`;
17364
16889
  const headline = `${count} red: assessing ${joinNumbers(fresh)}${older}`;
17365
16890
  return listing("some", fresh, stale, headline);
17366
16891
  }
@@ -17376,9 +16901,9 @@ function selectTriagePullRequests(input) {
17376
16901
  }
17377
16902
  const candidates = byNumber2(input.candidates);
17378
16903
  const count = candidates.length;
17379
- const lines2 = candidates.map((pull2) => skipped(pull2, now, resolve11));
16904
+ const lines = candidates.map((pull2) => skipped(pull2, now, resolve11));
17380
16905
  if (resolve11 && count > 1)
17381
- return refusal12(lines2, count);
16906
+ return refusal12(lines, count);
17382
16907
  if (count === 0) {
17383
16908
  return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
17384
16909
  }
@@ -17387,9 +16912,9 @@ function selectTriagePullRequests(input) {
17387
16912
  return plain("single", Object.freeze([only]), `1 red: assessing #${only.number}, last moved ${describeAge(only.updatedAt, now)}`);
17388
16913
  }
17389
16914
  if (count > MAX_TRIAGE_CANDIDATES)
17390
- return listReading(lines2, count);
16915
+ return listReading(lines, count);
17391
16916
  const fresh = candidates.filter((pull2) => isFreshCandidate(pull2.updatedAt, now));
17392
- const stale = lines2.filter((one) => !one.fresh);
16917
+ const stale = lines.filter((one) => !one.fresh);
17393
16918
  if (fresh.length === 0)
17394
16919
  return listReading(stale, count);
17395
16920
  return someReading(Object.freeze(fresh), stale, count);
@@ -17462,7 +16987,7 @@ function readConflict(git, base, head) {
17462
16987
  // src/commands/pr/triage-read.ts
17463
16988
  var RUN_URL = /\/actions\/runs\/(\d+)(?:[/?#]|$)/;
17464
16989
  var CONFLICT_UNREAD = "unread";
17465
- var REMOTE3 = "origin";
16990
+ var REMOTE2 = "origin";
17466
16991
  function runIdOf(link) {
17467
16992
  return RUN_URL.exec(link)?.[1] ?? null;
17468
16993
  }
@@ -17510,10 +17035,10 @@ async function readWorkflowCount(pulls, rows) {
17510
17035
  }
17511
17036
  }
17512
17037
  function baseCandidates(refs2) {
17513
- return [`${REMOTE3}/${refs2.baseRefName}`, refs2.baseRefName];
17038
+ return [`${REMOTE2}/${refs2.baseRefName}`, refs2.baseRefName];
17514
17039
  }
17515
17040
  function headCandidates(refs2) {
17516
- return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${REMOTE3}/${refs2.headRefName}`];
17041
+ return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${REMOTE2}/${refs2.headRefName}`];
17517
17042
  }
17518
17043
  function resolvesRef(git, ref8) {
17519
17044
  return git(["rev-parse", "--verify", "--quiet", `${ref8}^{commit}`]).ok;
@@ -17737,8 +17262,8 @@ function withTaskBudget(plan2, usd) {
17737
17262
  }
17738
17263
 
17739
17264
  // src/pr/plans/load.ts
17740
- import { existsSync as existsSync10, readFileSync as readFileSync18 } from "fs";
17741
- import { dirname as dirname6, join as join31 } from "path";
17265
+ import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
17266
+ import { dirname as dirname6, join as join29 } from "path";
17742
17267
  import { fileURLToPath as fileURLToPath4 } from "url";
17743
17268
  var MODULE_DIR4 = dirname6(fileURLToPath4(import.meta.url));
17744
17269
  var PINNED_PLANS_DIRNAME = "plans";
@@ -17755,8 +17280,8 @@ function hasPinnedPlan(triageClass) {
17755
17280
  function pinnedPlanCandidates(moduleDir, triageClass) {
17756
17281
  const name = pinnedPlanFileName(triageClass);
17757
17282
  return [
17758
- join31(moduleDir, PINNED_PLANS_DIRNAME, name),
17759
- join31(moduleDir, name)
17283
+ join29(moduleDir, PINNED_PLANS_DIRNAME, name),
17284
+ join29(moduleDir, name)
17760
17285
  ];
17761
17286
  }
17762
17287
  function readPinnedPlan(triageClass, lookup = {}) {
@@ -17764,7 +17289,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
17764
17289
  throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
17765
17290
  }
17766
17291
  const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
17767
- const found = candidates.find((candidate) => existsSync10(candidate));
17292
+ const found = candidates.find((candidate) => existsSync9(candidate));
17768
17293
  if (found === undefined) {
17769
17294
  throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
17770
17295
  }
@@ -17909,19 +17434,19 @@ function readAttemptRepeat(input) {
17909
17434
 
17910
17435
  // src/commands/pr/resolve-loop.ts
17911
17436
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
17912
- import { join as join32 } from "path";
17913
- var RESOLVE_SUBDIR = join32(".rafa", "resolve");
17437
+ import { join as join30 } from "path";
17438
+ var RESOLVE_SUBDIR = join30(".rafa", "resolve");
17914
17439
  var RUN_PREFIX = "pr-";
17915
17440
  var ATTEMPT_PREFIX = "attempt-";
17916
17441
  var LOOP_WORDS = Object.freeze(["loop", "start"]);
17917
17442
  var CHILD_OUTPUT = "text";
17918
17443
  function resolveRunDir(home, number, attempt3) {
17919
- return join32(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
17444
+ return join30(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
17920
17445
  }
17921
17446
  function writeResolvePlan(write) {
17922
17447
  const dir = resolveRunDir(write.home, write.number, write.attempt);
17923
17448
  mkdirSync5(dir, { recursive: true });
17924
- const path = join32(dir, pinnedPlanFileName(write.triageClass));
17449
+ const path = join30(dir, pinnedPlanFileName(write.triageClass));
17925
17450
  writeFileSync6(path, write.plan, "utf8");
17926
17451
  return path;
17927
17452
  }
@@ -17933,10 +17458,10 @@ async function forwardLines(stream, onLine) {
17933
17458
  let pending = "";
17934
17459
  for await (const chunk of stream) {
17935
17460
  pending += decoder.decode(chunk, { stream: true });
17936
- const lines2 = pending.split(`
17461
+ const lines = pending.split(`
17937
17462
  `);
17938
- pending = lines2.pop() ?? "";
17939
- for (const line of lines2)
17463
+ pending = lines.pop() ?? "";
17464
+ for (const line of lines)
17940
17465
  onLine(line);
17941
17466
  }
17942
17467
  if (pending !== "")
@@ -17963,7 +17488,7 @@ async function runResolveLoop(run, seams = {}) {
17963
17488
 
17964
17489
  // src/pr/triage/version-convert.ts
17965
17490
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
17966
- import { dirname as dirname7, join as join33, posix } from "path";
17491
+ import { dirname as dirname7, join as join31, posix } from "path";
17967
17492
  var FALLBACK_PLAN_PREFIX = "pr-";
17968
17493
  var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
17969
17494
  var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
@@ -18075,7 +17600,7 @@ function planConversion(input, problems) {
18075
17600
  }
18076
17601
  let branchVersionText;
18077
17602
  try {
18078
- branchVersionText = readFileSync19(join33(input.worktree, settings.releaseVersionFile), "utf8");
17603
+ branchVersionText = readFileSync19(join31(input.worktree, settings.releaseVersionFile), "utf8");
18079
17604
  } catch (error) {
18080
17605
  return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
18081
17606
  }
@@ -18101,7 +17626,7 @@ function writeConversion(input, plan2) {
18101
17626
  [input.settings.releaseChangelog, plan2.changelogText]
18102
17627
  ];
18103
17628
  for (const [path, text] of writes) {
18104
- const target = join33(input.worktree, path);
17629
+ const target = join31(input.worktree, path);
18105
17630
  try {
18106
17631
  mkdirSync6(dirname7(target), { recursive: true });
18107
17632
  writeFileSync7(target, text);
@@ -18913,7 +18438,7 @@ var USAGE40 = PR_USAGE.wait;
18913
18438
  var TIMEOUT_FLAG = "timeout";
18914
18439
  var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
18915
18440
  var MS_PER_MIN = 60000;
18916
- var MS_PER_SECOND3 = 1000;
18441
+ var MS_PER_SECOND2 = 1000;
18917
18442
  var WAIT_EXIT_CODES = Object.freeze({
18918
18443
  green: 0,
18919
18444
  red: 1,
@@ -18942,7 +18467,7 @@ function pollWord(polls) {
18942
18467
  return polls === 1 ? "1 poll" : `${polls} polls`;
18943
18468
  }
18944
18469
  function secondsWord(elapsedMs) {
18945
- return `${Math.round(elapsedMs / MS_PER_SECOND3)}s`;
18470
+ return `${Math.round(elapsedMs / MS_PER_SECOND2)}s`;
18946
18471
  }
18947
18472
  function verdictWord(verdict) {
18948
18473
  return verdict === "timeout" || verdict === "pending" ? "still running" : verdict;
@@ -19333,9 +18858,9 @@ function tagSettle(worktree, mode, delivered) {
19333
18858
  }
19334
18859
 
19335
18860
  // src/release/settle-worktree.ts
19336
- import { existsSync as existsSync11, mkdtempSync as mkdtempSync2, realpathSync as realpathSync5, rmSync as rmSync2 } from "fs";
18861
+ import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync3, rmSync as rmSync2 } from "fs";
19337
18862
  import { tmpdir as tmpdir2 } from "os";
19338
- import { join as join34 } from "path";
18863
+ import { join as join32 } from "path";
19339
18864
  var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
19340
18865
  var WORKTREE_DIRECTORY = "base";
19341
18866
  function removeScratch(git, scratch, path) {
@@ -19352,14 +18877,14 @@ function removeScratch(git, scratch, path) {
19352
18877
  problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
19353
18878
  }
19354
18879
  }
19355
- if (problems.length === 0 && existsSync11(scratch)) {
18880
+ if (problems.length === 0 && existsSync10(scratch)) {
19356
18881
  problems.push(`the scratch directory ${scratch} is still there`);
19357
18882
  }
19358
18883
  return problems.length === 0 ? null : problems.join("; ");
19359
18884
  }
19360
18885
  function makeScratch(root) {
19361
18886
  try {
19362
- return realpathSync5(mkdtempSync2(join34(root, SETTLE_SCRATCH_PREFIX)));
18887
+ return realpathSync3(mkdtempSync2(join32(root, SETTLE_SCRATCH_PREFIX)));
19363
18888
  } catch (error) {
19364
18889
  return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
19365
18890
  }
@@ -19381,7 +18906,7 @@ function addWorktree(options) {
19381
18906
  const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
19382
18907
  if (typeof scratch !== "string")
19383
18908
  return scratch;
19384
- const path = join34(scratch, WORKTREE_DIRECTORY);
18909
+ const path = join32(scratch, WORKTREE_DIRECTORY);
19385
18910
  const added = git(["worktree", "add", "--detach", path, commit]);
19386
18911
  if (!added.ok) {
19387
18912
  return {
@@ -19435,26 +18960,26 @@ function short(hash) {
19435
18960
  return hash.slice(0, SHORT_HASH4);
19436
18961
  }
19437
18962
  function readingLines(reading, ref8) {
19438
- const lines2 = [`Strategy: ${reading.strategy}`];
18963
+ const lines = [`Strategy: ${reading.strategy}`];
19439
18964
  if (reading.outcome === "unread")
19440
- return lines2;
18965
+ return lines;
19441
18966
  if (reading.outcome === "malformed") {
19442
- return [...lines2, `Base: ${ref8} at ${short(reading.commit)}`];
18967
+ return [...lines, `Base: ${ref8} at ${short(reading.commit)}`];
19443
18968
  }
19444
- lines2.push(`Base: ${ref8} at ${short(reading.commit)}, version ${reading.baseVersion}`);
18969
+ lines.push(`Base: ${ref8} at ${short(reading.commit)}, version ${reading.baseVersion}`);
19445
18970
  if (reading.fragments.length === 0) {
19446
- lines2.push("Fragments: none waiting");
18971
+ lines.push("Fragments: none waiting");
19447
18972
  } else {
19448
- lines2.push("Fragments, in the order the base received them:");
18973
+ lines.push("Fragments, in the order the base received them:");
19449
18974
  reading.fragments.forEach((each, index) => {
19450
18975
  const { level, title } = each.fragment;
19451
- lines2.push(`${INDENT8}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
18976
+ lines.push(`${INDENT8}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
19452
18977
  });
19453
18978
  }
19454
18979
  if (reading.outcome === "folded" || reading.outcome === "built" || reading.outcome === "unbuilt") {
19455
- lines2.push(`Version: ${reading.baseVersion} \u2192 ${reading.version}`);
18980
+ lines.push(`Version: ${reading.baseVersion} \u2192 ${reading.version}`);
19456
18981
  }
19457
- return lines2;
18982
+ return lines;
19458
18983
  }
19459
18984
  function unwrittenEnding(reading) {
19460
18985
  switch (reading.outcome) {
@@ -19468,19 +18993,19 @@ function unwrittenEnding(reading) {
19468
18993
  return { line: "Nothing to settle: no fragment waits that ships a release.", refuses: false };
19469
18994
  }
19470
18995
  }
19471
- function resultOf5(base, lines2, problem2) {
18996
+ function resultOf5(base, lines, problem2) {
19472
18997
  const exitCode = problem2 === null ? 0 : 1;
19473
- return { ...base, lines: lines2, problem: problem2, exitCode };
18998
+ return { ...base, lines, problem: problem2, exitCode };
19474
18999
  }
19475
19000
  function dryRunResult(reading, ref8, branch) {
19476
19001
  const base = { dryRun: true, delivery: null, reading, delivered: null, tag: null };
19477
- const lines2 = [...readingLines(reading, ref8)];
19002
+ const lines = [...readingLines(reading, ref8)];
19478
19003
  if (reading.outcome === "folded") {
19479
- lines2.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
19480
- return resultOf5(base, lines2, null);
19004
+ lines.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
19005
+ return resultOf5(base, lines, null);
19481
19006
  }
19482
19007
  const ending = unwrittenEnding(reading);
19483
- return ending.refuses ? resultOf5(base, lines2, ending.line) : resultOf5(base, [...lines2, ending.line], null);
19008
+ return ending.refuses ? resultOf5(base, lines, ending.line) : resultOf5(base, [...lines, ending.line], null);
19484
19009
  }
19485
19010
  function deliveryEnding(delivered, branch) {
19486
19011
  const { outcome } = delivered;
@@ -19509,18 +19034,18 @@ function deliveryEnding(delivered, branch) {
19509
19034
  function settledResult(delivered, tag, ref8, branch) {
19510
19035
  const reading = delivered.outcome.build;
19511
19036
  const base = { dryRun: false, delivery: delivered.delivery, reading, delivered, tag };
19512
- const lines2 = [...readingLines(reading, ref8)];
19037
+ const lines = [...readingLines(reading, ref8)];
19513
19038
  const ending = deliveryEnding(delivered, branch);
19514
19039
  if (ending.refuses)
19515
- return resultOf5(base, lines2, ending.line);
19516
- lines2.push(ending.line);
19040
+ return resultOf5(base, lines, ending.line);
19041
+ lines.push(ending.line);
19517
19042
  if (tag.outcome === "failed")
19518
- return resultOf5(base, lines2, `\u274C ${tag.sentence}`);
19043
+ return resultOf5(base, lines, `\u274C ${tag.sentence}`);
19519
19044
  if (tag.outcome === "tagged")
19520
- lines2.push(`\u2705 ${tag.sentence}`);
19045
+ lines.push(`\u2705 ${tag.sentence}`);
19521
19046
  if (tag.outcome === "skipped" && tag.sentence !== null)
19522
- lines2.push(`Tag: ${tag.sentence}`);
19523
- return resultOf5(base, lines2, null);
19047
+ lines.push(`Tag: ${tag.sentence}`);
19048
+ return resultOf5(base, lines, null);
19524
19049
  }
19525
19050
  function providerFor(project, config, seams) {
19526
19051
  requireGhProvider(resolvePrProvider({ configured: config.prProvider, dir: project.root, readRemote: seams.readRemote }));
@@ -19604,7 +19129,7 @@ var settle_default = createReleaseSettleCommand();
19604
19129
 
19605
19130
  // src/commands/release/tag.ts
19606
19131
  import { readFileSync as readFileSync20 } from "fs";
19607
- import { join as join35 } from "path";
19132
+ import { join as join33 } from "path";
19608
19133
 
19609
19134
  // src/commands/release/release-commit.ts
19610
19135
  function unread(reason, message) {
@@ -19668,7 +19193,7 @@ function textOf(path) {
19668
19193
  }
19669
19194
  }
19670
19195
  function readVersion(root, configured) {
19671
- const resolved = join35(root, configured);
19196
+ const resolved = join33(root, configured);
19672
19197
  const text = textOf(resolved);
19673
19198
  if (text === null) {
19674
19199
  return {
@@ -19682,7 +19207,7 @@ function readVersion(root, configured) {
19682
19207
  return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
19683
19208
  }
19684
19209
  function readNewestRelease(root, configured) {
19685
- const resolved = join35(root, configured);
19210
+ const resolved = join33(root, configured);
19686
19211
  const text = textOf(resolved);
19687
19212
  if (text === null) {
19688
19213
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -19893,8 +19418,8 @@ function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
19893
19418
  context.output.result(result2);
19894
19419
  return;
19895
19420
  }
19896
- const lines2 = renderTagged(result2.written, result2.inputs.releaseBranch, result2.followUps);
19897
- for (const line of lines2)
19421
+ const lines = renderTagged(result2.written, result2.inputs.releaseBranch, result2.followUps);
19422
+ for (const line of lines)
19898
19423
  context.output.info(line);
19899
19424
  }
19900
19425
  };
@@ -19952,17 +19477,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
19952
19477
  var roadmap_default = createRoadmapCommand();
19953
19478
 
19954
19479
  // src/commands/skill/backfill.ts
19955
- import { statSync as statSync10 } from "fs";
19956
- import { join as join40, resolve as resolve13 } from "path";
19480
+ import { statSync as statSync9 } from "fs";
19481
+ import { join as join38, resolve as resolve13 } from "path";
19957
19482
 
19958
19483
  // src/backfill/backup.ts
19959
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
19960
- import { dirname as dirname8, isAbsolute as isAbsolute6, join as join37, relative as relative7, resolve as resolve11, sep as sep5 } from "path";
19484
+ import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
19485
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join35, relative as relative7, resolve as resolve11, sep as sep4 } from "path";
19961
19486
 
19962
19487
  // src/backfill/proposal-file.ts
19963
19488
  import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
19964
- import { join as join36 } from "path";
19965
- var BACKFILL_PATH = join36(".rafa", "backfill");
19489
+ import { join as join34 } from "path";
19490
+ var BACKFILL_PATH = join34(".rafa", "backfill");
19966
19491
  var PROPOSAL_FILE_PREFIX = "proposals-";
19967
19492
  var PROPOSAL_FILE_EXTENSION = ".yaml";
19968
19493
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
@@ -19988,7 +19513,7 @@ var PROPOSAL_FILE_HEADER = [
19988
19513
  ].join(`
19989
19514
  `);
19990
19515
  function proposalPath(dir, batch) {
19991
- return join36(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
19516
+ return join34(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
19992
19517
  }
19993
19518
  function isProposalFileName(name) {
19994
19519
  return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
@@ -20124,7 +19649,7 @@ function readProposalFiles(dir) {
20124
19649
  return [];
20125
19650
  }
20126
19651
  return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
20127
- const path = join36(dir, name);
19652
+ const path = join34(dir, name);
20128
19653
  try {
20129
19654
  return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
20130
19655
  } catch (error) {
@@ -20134,19 +19659,19 @@ function readProposalFiles(dir) {
20134
19659
  }
20135
19660
 
20136
19661
  // src/backfill/backup.ts
20137
- var BACKUP_PATH = join37(BACKFILL_PATH, "backup");
19662
+ var BACKUP_PATH = join35(BACKFILL_PATH, "backup");
20138
19663
  var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
20139
19664
  function backupDirectory(base) {
20140
- return join37(base, BACKUP_PATH);
19665
+ return join35(base, BACKUP_PATH);
20141
19666
  }
20142
19667
  function isUnder(root, path) {
20143
19668
  const step = relative7(resolve11(root), resolve11(path));
20144
- return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute6(step);
19669
+ return step === "" || !step.startsWith(`..${sep4}`) && step !== ".." && !isAbsolute6(step);
20145
19670
  }
20146
19671
  function backupPathFor(path, root, backupDir) {
20147
19672
  if (!isUnder(root, path))
20148
19673
  return null;
20149
- return join37(backupDir, relative7(resolve11(root), resolve11(path)));
19674
+ return join35(backupDir, relative7(resolve11(root), resolve11(path)));
20150
19675
  }
20151
19676
  function backupFile(path, options) {
20152
19677
  if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
@@ -20155,9 +19680,9 @@ function backupFile(path, options) {
20155
19680
  const backup = backupPathFor(path, options.root, options.backupDir);
20156
19681
  if (backup === null)
20157
19682
  return { path, kind: "outside", backup: null };
20158
- if (!existsSync12(path))
19683
+ if (!existsSync11(path))
20159
19684
  return { path, kind: "missing", backup: null };
20160
- if (existsSync12(backup))
19685
+ if (existsSync11(backup))
20161
19686
  return { path, kind: "kept", backup };
20162
19687
  mkdirSync7(dirname8(backup), { recursive: true });
20163
19688
  copyFileSync3(path, backup);
@@ -20516,12 +20041,12 @@ function readVerdict(cell) {
20516
20041
  function isEmptyCell(cell) {
20517
20042
  return cell === "" || cell === EMPTY_CELL;
20518
20043
  }
20519
- function findTable(lines2) {
20520
- for (let index = 0;index + 1 < lines2.length; index += 1) {
20521
- const header = splitTableRow(lines2[index] ?? "");
20044
+ function findTable(lines) {
20045
+ for (let index = 0;index + 1 < lines.length; index += 1) {
20046
+ const header = splitTableRow(lines[index] ?? "");
20522
20047
  if (header === null)
20523
20048
  continue;
20524
- const delimiter = splitTableRow(lines2[index + 1] ?? "");
20049
+ const delimiter = splitTableRow(lines[index + 1] ?? "");
20525
20050
  if (delimiter !== null && isDelimiterRow(delimiter)) {
20526
20051
  return { header, start: index };
20527
20052
  }
@@ -20582,11 +20107,11 @@ function parseDemotionReport(text) {
20582
20107
  const status = readStatus(document.data);
20583
20108
  if (typeof status !== "string")
20584
20109
  issues.push(status);
20585
- const lines2 = text.split(`
20110
+ const lines = text.split(`
20586
20111
  `).map((line) => line.replace(/\r$/, ""));
20587
- const bodyStart = lines2.length - document.body.split(`
20112
+ const bodyStart = lines.length - document.body.split(`
20588
20113
  `).length;
20589
- const table = findTable(lines2.slice(bodyStart));
20114
+ const table = findTable(lines.slice(bodyStart));
20590
20115
  if (table === null) {
20591
20116
  issues.push(issue("missing-table", "file", "the report holds no row table", 1));
20592
20117
  return { issues, report: null };
@@ -20599,8 +20124,8 @@ function parseDemotionReport(text) {
20599
20124
  }
20600
20125
  const rows = [];
20601
20126
  const seen = new Set;
20602
- for (let index = bodyStart + table.start + 2;index < lines2.length; index += 1) {
20603
- const cells = splitTableRow(lines2[index] ?? "");
20127
+ for (let index = bodyStart + table.start + 2;index < lines.length; index += 1) {
20128
+ const cells = splitTableRow(lines[index] ?? "");
20604
20129
  if (cells === null)
20605
20130
  break;
20606
20131
  if (cells.length !== DEMOTION_REPORT_COLUMNS.length) {
@@ -20789,7 +20314,7 @@ function parseSessionAnswer(stdout) {
20789
20314
 
20790
20315
  // src/backfill/propose.ts
20791
20316
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
20792
- import { join as join38, resolve as resolve12 } from "path";
20317
+ import { join as join36, resolve as resolve12 } from "path";
20793
20318
  var PROPOSAL_ACTION_KINDS = [
20794
20319
  "applied",
20795
20320
  "unchanged",
@@ -20938,7 +20463,7 @@ function writeRow(path, row2, original, options) {
20938
20463
  };
20939
20464
  }
20940
20465
  function applyRow(root, row2, options) {
20941
- const path = join38(root, ...row2.path.split("/"));
20466
+ const path = join36(root, ...row2.path.split("/"));
20942
20467
  if (row2.status === UNANSWERED_ROW) {
20943
20468
  return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
20944
20469
  }
@@ -20975,7 +20500,7 @@ function applyProposals(files, root, options) {
20975
20500
  for (const file of files) {
20976
20501
  const refusal13 = fileRefusal(file, root);
20977
20502
  for (const row2 of file.rows) {
20978
- const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join38(root, ...row2.path.split("/")), row2, "refused", refusal13);
20503
+ const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join36(root, ...row2.path.split("/")), row2, "refused", refusal13);
20979
20504
  actions.push(action);
20980
20505
  if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
20981
20506
  triggers[row2.name] = row2.trigger;
@@ -20986,17 +20511,17 @@ function applyProposals(files, root, options) {
20986
20511
  }
20987
20512
 
20988
20513
  // src/demote/select.ts
20989
- import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync6, statSync as statSync9 } from "fs";
20990
- import { basename as basename7, dirname as dirname9, join as join39 } from "path";
20514
+ import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync8 } from "fs";
20515
+ import { basename as basename7, dirname as dirname9, join as join37 } from "path";
20991
20516
  var SKILLS_DIRECTORY = "skills";
20992
20517
  var CLAUDE_DIRECTORY = ".claude";
20993
20518
  var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
20994
- var DEMOTED_PATH = join39(".rafa", "demoted");
20995
- var INSTINCTS_PATH = join39(".rafa", "instincts");
20519
+ var DEMOTED_PATH = join37(".rafa", "demoted");
20520
+ var INSTINCTS_PATH = join37(".rafa", "instincts");
20996
20521
  var REPORT_FILE = "report.md";
20997
20522
  function realOrSelf(path) {
20998
20523
  try {
20999
- return realpathSync6(path);
20524
+ return realpathSync4(path);
21000
20525
  } catch {
21001
20526
  return path;
21002
20527
  }
@@ -21015,19 +20540,19 @@ function resolveDemotionScope(dir, home) {
21015
20540
  if (basename7(claude) !== CLAUDE_DIRECTORY)
21016
20541
  return null;
21017
20542
  const base = dirname9(claude);
21018
- const demotedDir = join39(base, DEMOTED_PATH);
20543
+ const demotedDir = join37(base, DEMOTED_PATH);
21019
20544
  return {
21020
20545
  scope: samePath(base, home) ? "user" : "project",
21021
20546
  dir,
21022
20547
  base,
21023
20548
  demotedDir,
21024
- instinctsDir: join39(base, INSTINCTS_PATH),
21025
- reportPath: join39(demotedDir, REPORT_FILE)
20549
+ instinctsDir: join37(base, INSTINCTS_PATH),
20550
+ reportPath: join37(demotedDir, REPORT_FILE)
21026
20551
  };
21027
20552
  }
21028
20553
  function entryKind(path) {
21029
20554
  try {
21030
- const stats = statSync9(path);
20555
+ const stats = statSync8(path);
21031
20556
  if (stats.isFile())
21032
20557
  return "file";
21033
20558
  if (stats.isDirectory())
@@ -21049,7 +20574,7 @@ function readSelected(absolute, path, entries) {
21049
20574
  let mtime;
21050
20575
  try {
21051
20576
  text = readFileSync25(absolute, "utf8");
21052
- mtime = statSync9(absolute).mtime.toISOString();
20577
+ mtime = statSync8(absolute).mtime.toISOString();
21053
20578
  } catch {
21054
20579
  return null;
21055
20580
  }
@@ -21064,14 +20589,14 @@ function selectsLearned(text, scope) {
21064
20589
  return origin === AUTO_EXTRACTED_ORIGIN;
21065
20590
  }
21066
20591
  function selectLearned(scope) {
21067
- const dir = join39(scope.dir, LEARNED_DIRECTORY);
20592
+ const dir = join37(scope.dir, LEARNED_DIRECTORY);
21068
20593
  if (entryKind(dir) !== "directory")
21069
20594
  return [];
21070
20595
  const selected = [];
21071
20596
  for (const name of visibleNames(dir)) {
21072
20597
  if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
21073
20598
  continue;
21074
- const absolute = join39(dir, name);
20599
+ const absolute = join37(dir, name);
21075
20600
  if (entryKind(absolute) !== "file")
21076
20601
  continue;
21077
20602
  const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
@@ -21085,10 +20610,10 @@ function selectSkillFiles(scope) {
21085
20610
  for (const name of visibleNames(scope.dir)) {
21086
20611
  if (name === LEARNED_DIRECTORY)
21087
20612
  continue;
21088
- const skillDir = join39(scope.dir, name);
20613
+ const skillDir = join37(scope.dir, name);
21089
20614
  if (entryKind(skillDir) !== "directory")
21090
20615
  continue;
21091
- const absolute = join39(skillDir, SKILL_FILE);
20616
+ const absolute = join37(skillDir, SKILL_FILE);
21092
20617
  if (entryKind(absolute) !== "file")
21093
20618
  continue;
21094
20619
  const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
@@ -21149,7 +20674,7 @@ Usage: ${BACKFILL_USAGE}`);
21149
20674
  }
21150
20675
  function isDirectory3(path) {
21151
20676
  try {
21152
- return statSync10(path).isDirectory();
20677
+ return statSync9(path).isDirectory();
21153
20678
  } catch {
21154
20679
  return false;
21155
20680
  }
@@ -21198,12 +20723,12 @@ function actionLines(actions) {
21198
20723
  function checkOptions(context, projectRoot2) {
21199
20724
  return { projectRoot: projectRoot2, pathDirs: pathDirectories(context.env["PATH"]) };
21200
20725
  }
21201
- function report3(context, result2, lines2) {
20726
+ function report3(context, result2, lines) {
21202
20727
  if (context.outputMode === "json") {
21203
20728
  context.output.result(result2);
21204
20729
  return;
21205
20730
  }
21206
- for (const line of lines2)
20731
+ for (const line of lines)
21207
20732
  context.output.info(line);
21208
20733
  }
21209
20734
  function runPlan(context, scope, backfillDir, projectRoot2) {
@@ -21273,7 +20798,7 @@ function reviewedProposals(dir) {
21273
20798
  return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
21274
20799
  }
21275
20800
  function rowPaths(files, root) {
21276
- return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join40(root, ...row2.path.split("/"))));
20801
+ return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join38(root, ...row2.path.split("/"))));
21277
20802
  }
21278
20803
  function runApply(context, scope, backfillDir, projectRoot2) {
21279
20804
  const options = checkOptions(context, projectRoot2);
@@ -21300,7 +20825,7 @@ function runApply(context, scope, backfillDir, projectRoot2) {
21300
20825
  derivationCounts: countDerivations(derived.actions),
21301
20826
  backups: copied
21302
20827
  };
21303
- const lines2 = [
20828
+ const lines = [
21304
20829
  `${scope.scope} scope: ${scope.dir}`,
21305
20830
  ...actionLines(proposalActions),
21306
20831
  proposalLine2(proposals.actions.length, proposals.counts),
@@ -21310,16 +20835,16 @@ function runApply(context, scope, backfillDir, projectRoot2) {
21310
20835
  ];
21311
20836
  const refused2 = proposals.counts.refused + derived.counts.refused;
21312
20837
  if (refused2 > 0)
21313
- throw new CommandExit(Math.min(refused2, REFUSED_CAP), lines2.join(`
20838
+ throw new CommandExit(Math.min(refused2, REFUSED_CAP), lines.join(`
21314
20839
  `));
21315
- report3(context, result2, lines2);
20840
+ report3(context, result2, lines);
21316
20841
  }
21317
20842
  async function runBackfill(context, seams) {
21318
20843
  const mode = readMode(context.flags);
21319
20844
  const dir = readBackfillDirectory(context.args, seams.cwd);
21320
20845
  const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
21321
20846
  const scope = backfillScopeOf(dir, projectOf11(context).home);
21322
- const backfillDir = join40(scope.base, BACKFILL_PATH);
20847
+ const backfillDir = join38(scope.base, BACKFILL_PATH);
21323
20848
  if (mode === "propose") {
21324
20849
  await runPropose(context, scope, backfillDir, seams);
21325
20850
  return;
@@ -21433,13 +20958,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
21433
20958
  var check_default3 = createSkillCheckCommand();
21434
20959
 
21435
20960
  // src/commands/skill/demote.ts
21436
- import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync27, statSync as statSync12, writeFileSync as writeFileSync11 } from "fs";
20961
+ import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync27, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
21437
20962
  import { dirname as dirname11, resolve as resolve14 } from "path";
21438
20963
 
21439
20964
  // src/demote/apply.ts
21440
- import { copyFileSync as copyFileSync4, existsSync as existsSync13, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
20965
+ import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
21441
20966
  import { tmpdir as tmpdir3 } from "os";
21442
- import { dirname as dirname10, join as join41 } from "path";
20967
+ import { dirname as dirname10, join as join39 } from "path";
21443
20968
  var DEMOTION_ACTION_KINDS = [
21444
20969
  "demoted",
21445
20970
  "kept",
@@ -21460,13 +20985,13 @@ function learnedSkillName(path) {
21460
20985
  return base.slice(0, -MARKDOWN_EXTENSION.length);
21461
20986
  }
21462
20987
  function sourcePath(scope, path) {
21463
- return join41(scope.dir, ...path.split("/"));
20988
+ return join39(scope.dir, ...path.split("/"));
21464
20989
  }
21465
20990
  function demotedPath(scope, path) {
21466
- return join41(scope.demotedDir, ...path.split("/"));
20991
+ return join39(scope.demotedDir, ...path.split("/"));
21467
20992
  }
21468
20993
  function promotedPath(scope, path) {
21469
- return join41(scope.dir, learnedSkillName(path), SKILL_FILE);
20994
+ return join39(scope.dir, learnedSkillName(path), SKILL_FILE);
21470
20995
  }
21471
20996
  function takenInstinctIds(dir) {
21472
20997
  let names;
@@ -21479,7 +21004,7 @@ function takenInstinctIds(dir) {
21479
21004
  }
21480
21005
  function readText4(path) {
21481
21006
  try {
21482
- return statSync11(path).isFile() ? readFileSync26(path, "utf8") : null;
21007
+ return statSync10(path).isFile() ? readFileSync26(path, "utf8") : null;
21483
21008
  } catch {
21484
21009
  return null;
21485
21010
  }
@@ -21507,7 +21032,7 @@ function planKept(scope, row2, file) {
21507
21032
  return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
21508
21033
  }
21509
21034
  const to = promotedPath(scope, row2.path);
21510
- if (existsSync13(to) || existsSync13(dirname10(to))) {
21035
+ if (existsSync12(to) || existsSync12(dirname10(to))) {
21511
21036
  return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
21512
21037
  }
21513
21038
  return {
@@ -21521,7 +21046,7 @@ function planKept(scope, row2, file) {
21521
21046
  };
21522
21047
  }
21523
21048
  function checkCandidate(dir, id2, text, pathDirs) {
21524
- const path = join41(dir, `${id2}${MARKDOWN_EXTENSION}`);
21049
+ const path = join39(dir, `${id2}${MARKDOWN_EXTENSION}`);
21525
21050
  writeFileSync10(path, text, "utf8");
21526
21051
  const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
21527
21052
  return failureLines2(report4.issues);
@@ -21542,11 +21067,11 @@ function planDemoted(scope, row2, file, context) {
21542
21067
  if (failures.length > 0) {
21543
21068
  return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
21544
21069
  }
21545
- const destination = join41(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
21070
+ const destination = join39(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
21546
21071
  const kept = demotedPath(scope, row2.path);
21547
- if (existsSync13(destination))
21072
+ if (existsSync12(destination))
21548
21073
  return inert3(row2.path, "observation", "refused", `${destination} is already there`);
21549
- if (existsSync13(kept))
21074
+ if (existsSync12(kept))
21550
21075
  return inert3(row2.path, "observation", "refused", `${kept} is already there`);
21551
21076
  context.taken.push(id2);
21552
21077
  return {
@@ -21578,7 +21103,7 @@ function planRow(scope, row2, context) {
21578
21103
  entries: undefined,
21579
21104
  absolute,
21580
21105
  hash,
21581
- mtime: statSync11(absolute).mtime.toISOString()
21106
+ mtime: statSync10(absolute).mtime.toISOString()
21582
21107
  };
21583
21108
  return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
21584
21109
  }
@@ -21589,7 +21114,7 @@ function countActions(actions) {
21589
21114
  return counts;
21590
21115
  }
21591
21116
  function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
21592
- const temp = mkdtempSync3(join41(tmpdir3(), "rafa-demote-"));
21117
+ const temp = mkdtempSync3(join39(tmpdir3(), "rafa-demote-"));
21593
21118
  const taken = [...takenInstinctIds(scope.instinctsDir)];
21594
21119
  try {
21595
21120
  const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
@@ -21680,7 +21205,7 @@ Usage: ${DEMOTE_USAGE}`);
21680
21205
  }
21681
21206
  function isDirectory4(path) {
21682
21207
  try {
21683
- return statSync12(path).isDirectory();
21208
+ return statSync11(path).isDirectory();
21684
21209
  } catch {
21685
21210
  return false;
21686
21211
  }
@@ -21708,7 +21233,7 @@ function verdictLine2(report4) {
21708
21233
  return `${String(report4.rows.length)} file(s): ${parts.join(", ")}`;
21709
21234
  }
21710
21235
  function previousReport(path, context) {
21711
- if (!existsSync14(path))
21236
+ if (!existsSync13(path))
21712
21237
  return null;
21713
21238
  const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
21714
21239
  if (parsed.report !== null)
@@ -21743,13 +21268,13 @@ function runWrite(context, scope) {
21743
21268
  context.output.info(line);
21744
21269
  }
21745
21270
  function reviewedReport(scope) {
21746
- if (!existsSync14(scope.reportPath)) {
21271
+ if (!existsSync13(scope.reportPath)) {
21747
21272
  throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
21748
21273
  }
21749
21274
  const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
21750
21275
  if (parsed.report === null) {
21751
- const lines2 = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
21752
- throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines2].join(`
21276
+ const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
21277
+ throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
21753
21278
  `));
21754
21279
  }
21755
21280
  if (parsed.report.status !== REVIEWED_STATUS) {
@@ -21787,16 +21312,16 @@ function runApply2(context, scope) {
21787
21312
  actions: plan2.actions,
21788
21313
  counts: plan2.counts
21789
21314
  };
21790
- const lines2 = renderApply(result2);
21315
+ const lines = renderApply(result2);
21791
21316
  if (plan2.counts.refused > 0) {
21792
- throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP), lines2.join(`
21317
+ throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP), lines.join(`
21793
21318
  `));
21794
21319
  }
21795
21320
  if (context.outputMode === "json") {
21796
21321
  context.output.result(result2);
21797
21322
  return;
21798
21323
  }
21799
- for (const line of lines2)
21324
+ for (const line of lines)
21800
21325
  context.output.info(line);
21801
21326
  }
21802
21327
  function runDemote(context, seams) {
@@ -21990,8 +21515,8 @@ var VERDICT_WORDS = Object.freeze({
21990
21515
  pending: "checks pending",
21991
21516
  none: "no checks"
21992
21517
  });
21993
- function counted3(count, noun, plural5 = `${noun}s`) {
21994
- return `${String(count)} ${count === 1 ? noun : plural5}`;
21518
+ function counted3(count, noun, plural3 = `${noun}s`) {
21519
+ return `${String(count)} ${count === 1 ? noun : plural3}`;
21995
21520
  }
21996
21521
  function info2(text) {
21997
21522
  return { level: "info", text };
@@ -22119,7 +21644,7 @@ function statusData(sections) {
22119
21644
  }
22120
21645
 
22121
21646
  // src/status/sections.ts
22122
- import { existsSync as existsSync15 } from "fs";
21647
+ import { existsSync as existsSync14 } from "fs";
22123
21648
 
22124
21649
  // src/status/blocked-count.ts
22125
21650
  async function labelsCount(options) {
@@ -22379,7 +21904,7 @@ function nextOfPlace(current, epic, walk) {
22379
21904
  return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
22380
21905
  }
22381
21906
  function hasBoards(root, listing2) {
22382
- if (existsSync15(positionFilePath(root)))
21907
+ if (existsSync14(positionFilePath(root)))
22383
21908
  return true;
22384
21909
  return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
22385
21910
  }
@@ -22510,6 +22035,10 @@ async function runStatus2(context, seams) {
22510
22035
  const config = resolveProjectConfig(project, COMMAND_NAME14, (message) => {
22511
22036
  context.output.warn(message);
22512
22037
  });
22038
+ const warn3 = (message) => {
22039
+ context.output.warn(message);
22040
+ };
22041
+ await (seams.pull ?? pullBeforeRead)({ roots: { root: project.root, home: project.home }, warn: warn3 });
22513
22042
  const read3 = seams.read ?? ((input) => readStatusSections(input));
22514
22043
  const sections = await read3({ root: project.root, home: project.home, config });
22515
22044
  if (context.outputMode === "json") {