@open-tomato/rafa 0.23.0 → 0.24.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.
@@ -49,7 +49,7 @@ import {
49
49
  writeFrontmatter,
50
50
  writeInstinct,
51
51
  writeSqliteStore
52
- } from "./index-ae290g0m.js";
52
+ } from "./index-7qgng1rf.js";
53
53
  import {
54
54
  CHANGE_LEVELS,
55
55
  CONFIG_DEFAULTS,
@@ -81,12 +81,12 @@ import {
81
81
  DESCRIBE_BINARY,
82
82
  DESCRIBE_SCHEMA_VERSION,
83
83
  pluralOf
84
- } from "./index-4a45ffqh.js";
84
+ } from "./index-w1dnwja1.js";
85
85
  import {
86
86
  CommandExit,
87
87
  SPENDS_GLYPH,
88
88
  commandSpelling
89
- } from "./index-28jzpyks.js";
89
+ } from "./index-4fwgqveh.js";
90
90
  import {
91
91
  BUDGET_FLAG,
92
92
  BUNDLED_SKILLS_DIR,
@@ -5132,7 +5132,7 @@ async function report(args, repoRoot) {
5132
5132
 
5133
5133
  // package.json
5134
5134
  var name = "@open-tomato/rafa";
5135
- var version = "0.23.0";
5135
+ var version = "0.24.0";
5136
5136
 
5137
5137
  // src/modules/manifest.ts
5138
5138
  var MANIFEST_VERSION = 1;
@@ -8063,6 +8063,202 @@ function readEpics(input) {
8063
8063
  });
8064
8064
  }
8065
8065
 
8066
+ // src/board/epic-dependents.ts
8067
+ function membersOf(slug, issues) {
8068
+ const members = slug === null ? [] : groupByEpicLabel(issues).get(slug) ?? [];
8069
+ return {
8070
+ open: members.filter((member) => member.state === "OPEN").map((member) => member.number),
8071
+ all: new Set(members.map((member) => member.number))
8072
+ };
8073
+ }
8074
+ function readEpicDependents(issues, epic) {
8075
+ const epicIssue = issues.find((issue3) => issue3.number === epic && issue3.type === "epic");
8076
+ if (epicIssue === undefined)
8077
+ return null;
8078
+ const slug = epicSlugsOf(epicIssue.labels)[0] ?? null;
8079
+ const members = membersOf(slug, issues);
8080
+ const open = new Set(members.open);
8081
+ const known = new Set(issues.map((issue3) => issue3.number));
8082
+ const outsiders = issues.filter((issue3) => issue3.state === "OPEN" && issue3.number !== epic && !members.all.has(issue3.number)).sort((left, right) => left.number - right.number);
8083
+ const dependents = [];
8084
+ const problems = [];
8085
+ for (const issue3 of outsiders) {
8086
+ const reading2 = readBlockedBy(issue3.number, issue3.body, known);
8087
+ const names = reading2.blockers.filter((id) => open.has(id));
8088
+ if (names.length === 0)
8089
+ continue;
8090
+ if (reading2.kind === "blocked") {
8091
+ dependents.push(Object.freeze({ issue: issue3, waitsOn: Object.freeze(names) }));
8092
+ continue;
8093
+ }
8094
+ problems.push(Object.freeze({
8095
+ issue: issue3.number,
8096
+ reading: reading2,
8097
+ names: Object.freeze(names),
8098
+ message: blockedFaultMessage(reading2)
8099
+ }));
8100
+ }
8101
+ return Object.freeze({
8102
+ epic,
8103
+ slug,
8104
+ openMembers: Object.freeze([...members.open]),
8105
+ dependents: Object.freeze(dependents),
8106
+ problems: Object.freeze(problems)
8107
+ });
8108
+ }
8109
+
8110
+ // src/board/epic-trail.ts
8111
+ var REASON_FLAG = "reason";
8112
+ var WHITESPACE_RUN = /\s+/gu;
8113
+ var BACKTICK_RUN = /`+/gu;
8114
+ function ref(issue3) {
8115
+ return `#${String(issue3)}`;
8116
+ }
8117
+ function wordList(words) {
8118
+ if (words.length < 2)
8119
+ return words.join("");
8120
+ return `${words.slice(0, -1).join(", ")} and ${words[words.length - 1] ?? ""}`;
8121
+ }
8122
+ function refList(issues) {
8123
+ return wordList(issues.map(ref));
8124
+ }
8125
+ function codeSpan(text) {
8126
+ const longest = (text.match(BACKTICK_RUN) ?? []).reduce((most, run) => Math.max(most, run.length), 0);
8127
+ const fence = "`".repeat(longest + 1);
8128
+ const padding = text.startsWith("`") || text.endsWith("`") ? " " : "";
8129
+ return `${fence}${padding}${text}${padding}${fence}`;
8130
+ }
8131
+ function normaliseReason(reason) {
8132
+ return reason.trim().replace(WHITESPACE_RUN, " ");
8133
+ }
8134
+ function renderHorizonComment(change, reason) {
8135
+ return `Moved ${change.from} \u2192 ${change.to}: ${normaliseReason(reason)}`;
8136
+ }
8137
+ function renderParkedPullRequestComment(change, reason) {
8138
+ return `Closed: epic ${ref(change.epic)} was deferred ${change.from} \u2192 ${change.to}: ${normaliseReason(reason)}` + `
8139
+
8140
+ The branch is kept; reopen this pull request to take the work up again.`;
8141
+ }
8142
+ function openWorkPhrase(work) {
8143
+ const branches = work.branches.map((branch) => `branch ${codeSpan(branch)}`);
8144
+ const pullRequests = work.pullRequests.map((pr) => `pull request ${ref(pr)}`);
8145
+ return [...branches, ...pullRequests].join(", ");
8146
+ }
8147
+ function renderMoveComment(change, reason, work) {
8148
+ const head = `Moved from epic ${ref(change.from)} to ${ref(change.to)}: ${normaliseReason(reason)}`;
8149
+ const named = work === undefined ? "" : openWorkPhrase(work);
8150
+ return named === "" ? head : `${head}
8151
+
8152
+ Open work stays as it is: ${named}.`;
8153
+ }
8154
+ function criteriaCount(count) {
8155
+ return count === 1 ? "1 acceptance criterion" : `${String(count)} acceptance criteria`;
8156
+ }
8157
+ function renderCloseComment(trail) {
8158
+ const head = `Closed through the closing gate: every member is closed, and ${criteriaCount(trail.passed.length)}` + " passed against main.";
8159
+ const lines = [head];
8160
+ if (trail.passed.length > 0) {
8161
+ lines.push("", ...trail.passed.map((criterion) => `- [x] ${normaliseReason(criterion)}`));
8162
+ }
8163
+ if (trail.unchecked.length > 0) {
8164
+ lines.push("", `Closed over ${criteriaCount(trail.unchecked.length)} left unchecked, with --accept-unchecked:`, "", ...trail.unchecked.map(({ criterion, reason }) => `- [ ] ${normaliseReason(criterion)} \u2014 ${normaliseReason(reason)}`));
8165
+ }
8166
+ return lines.join(`
8167
+ `);
8168
+ }
8169
+ function dependentLine(outcome) {
8170
+ const { answer } = outcome;
8171
+ switch (answer.kind) {
8172
+ case "moved": {
8173
+ return `- ${ref(outcome.issue)} moved to epic ${ref(answer.to)}`;
8174
+ }
8175
+ case "unblocked": {
8176
+ return `- ${ref(outcome.issue)} unblocked`;
8177
+ }
8178
+ case "cancelled": {
8179
+ return `- ${ref(outcome.issue)} closed as not planned`;
8180
+ }
8181
+ }
8182
+ }
8183
+ function renderCancelComment(reason, dependents) {
8184
+ const said = reason === null ? "" : normaliseReason(reason);
8185
+ const head = said === "" ? "Cancelled." : `Cancelled: ${said}`;
8186
+ if (dependents.length === 0)
8187
+ return `${head}
8188
+
8189
+ No issue in another epic waited on its open members.`;
8190
+ return [head, "", "Issues in other epics that waited on its open members:", "", ...dependents.map(dependentLine)].join(`
8191
+ `);
8192
+ }
8193
+ function cancelMoveReason(epic) {
8194
+ return `epic ${ref(epic)}, which it was blocked by, was cancelled`;
8195
+ }
8196
+ function renderDependentComment(action, epic, waitedOn) {
8197
+ const why = `epic ${ref(epic)} was cancelled, so ${refList(waitedOn)}`;
8198
+ const verb = waitedOn.length === 1 ? "blocks" : "block";
8199
+ return action === "unblocked" ? `Unblocked: ${why} no longer ${verb} this issue.` : `Closed as not planned: ${why}, which this issue waited on, will not land.`;
8200
+ }
8201
+ function renderUnblockNote(day, epic, dropped, still) {
8202
+ const verb = dropped.length === 1 ? "blocks" : "block";
8203
+ const rest = still.length === 0 ? "nothing else blocks it." : `it is still blocked by ${wordList(still)}.`;
8204
+ return `**Updated ${day}, epic ${ref(epic)} cancelled:** ${refList(dropped)} no longer ${verb} this issue; ${rest}`;
8205
+ }
8206
+ function carriesUnblockNote(body, epic) {
8207
+ return new RegExp(String.raw`\*\*Updated \d{4}-\d{2}-\d{2}, epic #${String(epic)} cancelled:\*\*`, "u").test(body);
8208
+ }
8209
+ function reasonQuestion(change) {
8210
+ return change.kind === "horizon" ? `Why move epic ${ref(change.epic)} from ${change.from} to ${change.to}? ` : `Why move ${ref(change.issue)} from epic ${ref(change.from)} to ${ref(change.to)}? `;
8211
+ }
8212
+ async function readReason(change, flag, ask) {
8213
+ if (flag !== null)
8214
+ return readingOf(flag);
8215
+ const question = reasonQuestion(change);
8216
+ if (ask === null)
8217
+ return { status: "unasked", question };
8218
+ const answer = await ask(question);
8219
+ return answer === null ? { status: "blank" } : readingOf(answer);
8220
+ }
8221
+ function readingOf(text) {
8222
+ const reason = normaliseReason(text);
8223
+ return reason === "" ? { status: "blank" } : { status: "given", reason };
8224
+ }
8225
+ function unaskedReasonMessage(question) {
8226
+ return `No terminal to ask on, so nothing changed. It would have asked: ${question.trim()}` + ` Pass --${REASON_FLAG} "<why>" to answer it.`;
8227
+ }
8228
+ function blankReasonMessage() {
8229
+ return "No reason was given, so nothing changed: every change to an epic leaves its reason on the issue." + ` Pass --${REASON_FLAG} "<why>" or answer the question.`;
8230
+ }
8231
+
8232
+ // src/board/epic-cancel-notice.ts
8233
+ function ref2(issue3) {
8234
+ return `#${String(issue3)}`;
8235
+ }
8236
+ function refList2(issues) {
8237
+ const refs = issues.map(ref2);
8238
+ if (refs.length < 2)
8239
+ return refs.join("");
8240
+ return `${refs.slice(0, -1).join(", ")} and ${refs[refs.length - 1] ?? ""}`;
8241
+ }
8242
+ function readCancelledEpicNotices(issues) {
8243
+ const cancelled = issues.filter((issue3) => issue3.type === "epic" && isNotPlanned(issue3)).map((issue3) => issue3.number).sort((left, right) => left - right);
8244
+ const notices = [];
8245
+ for (const epic of cancelled) {
8246
+ const read = readEpicDependents(issues, epic);
8247
+ const waiting = (read?.dependents ?? []).filter(({ issue: issue3 }) => !carriesUnblockNote(issue3.body, epic)).map(({ issue: issue3 }) => issue3.number);
8248
+ if (waiting.length > 0)
8249
+ notices.push(Object.freeze({ epic, dependents: Object.freeze(waiting) }));
8250
+ }
8251
+ return Object.freeze(notices);
8252
+ }
8253
+ function renderCancelledEpicNotice(notice) {
8254
+ const { epic, dependents } = notice;
8255
+ const [verb, whom] = dependents.length === 1 ? ["waits", "it"] : ["wait", "each"];
8256
+ return `Epic ${ref2(epic)} was closed as not planned, and ${refList2(dependents)} still ${verb} on its open members;` + ` rafa epic cancel ${String(epic)} asks what becomes of ${whom}.`;
8257
+ }
8258
+ function cancelledEpicNoticeLines(issues) {
8259
+ return Object.freeze(readCancelledEpicNotices(issues).map(renderCancelledEpicNotice));
8260
+ }
8261
+
8066
8262
  // src/board/epic-problems.ts
8067
8263
  var HORIZON_LABEL_PREFIX = "horizon:";
8068
8264
  var EPIC_PROBLEM_KINDS = Object.freeze([
@@ -8516,16 +8712,16 @@ function firstStamp(live) {
8516
8712
  }
8517
8713
  async function liveReadings(body, verify) {
8518
8714
  const read = [];
8519
- for (const ref of extractRefs(body))
8520
- read.push([ref, await verify(ref)]);
8715
+ for (const ref3 of extractRefs(body))
8716
+ read.push([ref3, await verify(ref3)]);
8521
8717
  return read;
8522
8718
  }
8523
- function rowOf2(ref, live, stamp, heldTo, issue3) {
8524
- const { state, changedHeadings: changedHeadings2 } = compareToStamp({ live, stamp, blocker: ref.blocker });
8719
+ function rowOf2(ref3, live, stamp, heldTo, issue3) {
8720
+ const { state, changedHeadings: changedHeadings2 } = compareToStamp({ live, stamp, blocker: ref3.blocker });
8525
8721
  return Object.freeze({
8526
- kind: ref.kind,
8527
- text: ref.text,
8528
- line: ref.line,
8722
+ kind: ref3.kind,
8723
+ text: ref3.text,
8724
+ line: ref3.line,
8529
8725
  state,
8530
8726
  fingerprint: live,
8531
8727
  stamp: heldTo,
@@ -8540,12 +8736,12 @@ function answer(rows, copy, body, stamps, write) {
8540
8736
  async function readRefsText(options) {
8541
8737
  const { stamps, body } = readRefsBlock(options.copy);
8542
8738
  const added = [];
8543
- const rows = (await liveReadings(body, options.verify)).map(([ref, live]) => {
8544
- const stamp = findStamp(stamps, ref);
8739
+ const rows = (await liveReadings(body, options.verify)).map(([ref3, live]) => {
8740
+ const stamp = findStamp(stamps, ref3);
8545
8741
  const fresh = stamp === null ? firstStamp(live) : null;
8546
8742
  if (fresh !== null)
8547
- added.push(Object.freeze({ kind: ref.kind, text: ref.text, fingerprint: fresh }));
8548
- return rowOf2(ref, live, stamp, stamp ?? fresh, options.issue);
8743
+ added.push(Object.freeze({ kind: ref3.kind, text: ref3.text, fingerprint: fresh }));
8744
+ return rowOf2(ref3, live, stamp, stamp ?? fresh, options.issue);
8549
8745
  });
8550
8746
  const kept = added.length === 0 ? stamps : Object.freeze([...stamps ?? [], ...added]);
8551
8747
  return answer(rows, options.copy, body, kept, added.length > 0);
@@ -8553,11 +8749,11 @@ async function readRefsText(options) {
8553
8749
  async function restampRefsText(options) {
8554
8750
  const { stamps: old, body } = readRefsBlock(options.copy);
8555
8751
  const stamps = [];
8556
- const rows = (await liveReadings(body, options.verify)).map(([ref, live]) => {
8557
- const stamp = live.kind === "unreadable" ? findStamp(old, ref) : live;
8752
+ const rows = (await liveReadings(body, options.verify)).map(([ref3, live]) => {
8753
+ const stamp = live.kind === "unreadable" ? findStamp(old, ref3) : live;
8558
8754
  if (stamp !== null)
8559
- stamps.push(Object.freeze({ kind: ref.kind, text: ref.text, fingerprint: stamp }));
8560
- return rowOf2(ref, live, stamp, stamp, options.issue);
8755
+ stamps.push(Object.freeze({ kind: ref3.kind, text: ref3.text, fingerprint: stamp }));
8756
+ return rowOf2(ref3, live, stamp, stamp, options.issue);
8561
8757
  });
8562
8758
  return answer(rows, options.copy, body, Object.freeze(stamps), true);
8563
8759
  }
@@ -8791,26 +8987,26 @@ function createRefVerifier(seams) {
8791
8987
  tracked ??= trackedFiles(seams.git);
8792
8988
  return tracked;
8793
8989
  };
8794
- return async (ref) => {
8795
- switch (ref.kind) {
8990
+ return async (ref3) => {
8991
+ switch (ref3.kind) {
8796
8992
  case "issue":
8797
- return verifyIssue(ref.text, seams.issues);
8993
+ return verifyIssue(ref3.text, seams.issues);
8798
8994
  case "cross-issue":
8799
- return verifyCrossIssue(ref.text, seams.issues);
8995
+ return verifyCrossIssue(ref3.text, seams.issues);
8800
8996
  case "path": {
8801
- const target = pathTarget(ref.text, listed());
8997
+ const target = pathTarget(ref3.text, listed());
8802
8998
  if (target.kind === "file")
8803
8999
  return fileReading(target.path, seams.git);
8804
9000
  return presence(target.kind === "present");
8805
9001
  }
8806
9002
  case "symbol":
8807
- return verifySymbol(ref.text, seams.git, seams.outline);
9003
+ return verifySymbol(ref3.text, seams.git, seams.outline);
8808
9004
  case "command":
8809
- return presence(isRosterCommand(ref.text, seams.roster));
9005
+ return presence(isRosterCommand(ref3.text, seams.roster));
8810
9006
  case "flag":
8811
- return presence(isRosterFlag(ref.text, seams.roster));
9007
+ return presence(isRosterFlag(ref3.text, seams.roster));
8812
9008
  case "key":
8813
- return presence(isSettingKey(ref.text));
9009
+ return presence(isSettingKey(ref3.text));
8814
9010
  }
8815
9011
  };
8816
9012
  }
@@ -8819,6 +9015,7 @@ function createRefVerifier(seams) {
8819
9015
  var PREFIX8 = "board issue";
8820
9016
  var REPO_PATH2 = "{owner}/{repo}";
8821
9017
  var COMMENT_ID = /^[1-9]\d*$/;
9018
+ var CLOSE_REASONS = Object.freeze(["completed", "not planned"]);
8822
9019
  function detailOf7(result, command) {
8823
9020
  const written = result.stderr.trim() || result.stdout.trim();
8824
9021
  return written === "" ? `${command} failed and wrote nothing` : `${command} failed: ${written}`;
@@ -8844,12 +9041,39 @@ function commentBody2(value, member) {
8844
9041
  }
8845
9042
  return value;
8846
9043
  }
9044
+ function closeReason(value) {
9045
+ if (!CLOSE_REASONS.includes(value)) {
9046
+ throw new TypeError(`${PREFIX8}: closeIssue refused reason ${describeValue(value)}, expected one of ${CLOSE_REASONS.join(", ")}`);
9047
+ }
9048
+ return value;
9049
+ }
9050
+ function closeComment(value, member = "closeIssue") {
9051
+ if (commentBody2(value, member).trim() === "") {
9052
+ throw new TypeError(`${PREFIX8}: ${member} refused an empty comment, every close leaves one`);
9053
+ }
9054
+ return value;
9055
+ }
8847
9056
  function labelName(value, member, what) {
8848
9057
  if (typeof value !== "string" || value === "" || value.startsWith("-")) {
8849
9058
  throw new TypeError(`${PREFIX8}: ${member} refused ${what} ${describeValue(value)},` + " expected a non-empty name that does not open with a hyphen");
8850
9059
  }
8851
9060
  return value;
8852
9061
  }
9062
+ function issueTitle(value) {
9063
+ if (typeof value !== "string" || value.trim() === "" || /[\r\n]/u.test(value)) {
9064
+ throw new TypeError(`${PREFIX8}: createIssue refused the title ${describeValue(value)}, expected one line holding some text`);
9065
+ }
9066
+ return value;
9067
+ }
9068
+ function createdIssue(stdout) {
9069
+ const url = stdout.trim().split(`
9070
+ `).at(-1)?.trim() ?? "";
9071
+ const number = CREATED_ISSUE_URL.exec(url)?.[1];
9072
+ if (number === undefined) {
9073
+ throw new Error(`${PREFIX8}: gh issue create exited 0 and printed no issue URL, so the issue may exist` + ` unrecorded; it wrote ${describeValue(stdout)}`);
9074
+ }
9075
+ return Object.freeze({ number: Number(number), url });
9076
+ }
8853
9077
  function parseJson(stdout, command) {
8854
9078
  try {
8855
9079
  return JSON.parse(stdout);
@@ -8920,6 +9144,29 @@ function createGhIssueBoard(options) {
8920
9144
  const number = issueNumber2(issue3, "removeLabel");
8921
9145
  const off = labelName(label, "removeLabel", "the label");
8922
9146
  await succeed(["issue", "edit", number, "--remove-label", off], `gh issue edit ${number} --remove-label ${off}`);
9147
+ },
9148
+ closeIssue: async (issue3, reason, comment) => {
9149
+ const number = issueNumber2(issue3, "closeIssue");
9150
+ const why = closeReason(reason);
9151
+ const text = closeComment(comment);
9152
+ await succeed(["issue", "close", number, `--reason=${why}`, `--comment=${text}`], `gh issue close ${number} --reason=${why}`);
9153
+ },
9154
+ createLabel: async (name2, description) => {
9155
+ const label = labelName(name2, "createLabel", "the label");
9156
+ const text = commentBody2(description, "createLabel");
9157
+ await succeed(["label", "create", label, `--description=${text}`], `gh label create ${label}`);
9158
+ },
9159
+ createIssue: async (title, body, labels) => {
9160
+ const heading = issueTitle(title);
9161
+ const text = commentBody2(body, "createIssue");
9162
+ const names = labels.map((label) => labelName(label, "createIssue", "the label"));
9163
+ const stdout = await succeed(["issue", "create", `--title=${heading}`, `--body=${text}`, ...names.map((name2) => `--label=${name2}`)], "gh issue create");
9164
+ return createdIssue(stdout);
9165
+ },
9166
+ closePullRequest: async (pullRequest, comment) => {
9167
+ const number = issueNumber2(pullRequest, "closePullRequest");
9168
+ const text = closeComment(comment, "closePullRequest");
9169
+ await succeed(["pr", "close", number, `--comment=${text}`], `gh pr close ${number}`);
8923
9170
  }
8924
9171
  };
8925
9172
  return Object.freeze(board);
@@ -9639,12 +9886,12 @@ function listPassingRows(rows, output) {
9639
9886
  }
9640
9887
  function memoiseVerifier(verify) {
9641
9888
  const read = new Map;
9642
- return (ref) => {
9643
- const key = `${ref.kind}\x00${ref.text}`;
9889
+ return (ref3) => {
9890
+ const key = `${ref3.kind}\x00${ref3.text}`;
9644
9891
  const known = read.get(key);
9645
9892
  if (known !== undefined)
9646
9893
  return known;
9647
- const reading2 = verify(ref);
9894
+ const reading2 = verify(ref3);
9648
9895
  read.set(key, reading2);
9649
9896
  return reading2;
9650
9897
  };
@@ -9683,8 +9930,8 @@ async function enforceRefsGate(options) {
9683
9930
  import path2 from "path";
9684
9931
  async function coreRoster() {
9685
9932
  const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
9686
- import("./index-rw7tb99r.js"),
9687
- import("./describe-0tefrkk7.js")
9933
+ import("./index-zzvc0gkb.js"),
9934
+ import("./describe-66gth329.js")
9688
9935
  ]);
9689
9936
  return describeRegistry(CORE_REGISTRY, version);
9690
9937
  }
@@ -9698,9 +9945,9 @@ function createPlanRefsVerifier(repoRoot) {
9698
9945
  outline: tsSymbolsOutliner({ cwd: repoRoot }),
9699
9946
  roster: await coreRoster()
9700
9947
  });
9701
- return async (ref) => {
9948
+ return async (ref3) => {
9702
9949
  made ??= verifier();
9703
- return (await made)(ref);
9950
+ return (await made)(ref3);
9704
9951
  };
9705
9952
  }
9706
9953
  function isBoardRequest(request) {
@@ -9779,11 +10026,11 @@ async function defaultVerifier(input, issues) {
9779
10026
  });
9780
10027
  }
9781
10028
  function boardUnknown(verify) {
9782
- return async (ref) => {
10029
+ return async (ref3) => {
9783
10030
  try {
9784
- return await verify(ref);
10031
+ return await verify(ref3);
9785
10032
  } catch (error) {
9786
- if (ref.kind === "issue" && error instanceof RefVerifyError)
10033
+ if (ref3.kind === "issue" && error instanceof RefVerifyError)
9787
10034
  return UNREADABLE;
9788
10035
  throw error;
9789
10036
  }
@@ -10207,8 +10454,8 @@ function hasPlanFor(names, issue3) {
10207
10454
  }
10208
10455
  function readHasColumn(issue3, sources) {
10209
10456
  const plan = hasPlanFor(sources.planNames, issue3) ? [{ kind: "plan" }] : [];
10210
- const ref = sources.refs.find((candidate) => branchClaims(candidate, issue3));
10211
- const branch = ref === undefined ? [] : [{ kind: "branch", ref }];
10457
+ const ref3 = sources.refs.find((candidate) => branchClaims(candidate, issue3));
10458
+ const branch = ref3 === undefined ? [] : [{ kind: "branch", ref: ref3 }];
10212
10459
  const pulls = sources.pulls.filter((pull) => closedIssuesIn(pull.body).includes(issue3)).map((pull) => ({ kind: "pr", number: pull.number }));
10213
10460
  return Object.freeze([...plan, ...branch, ...pulls]);
10214
10461
  }
@@ -10357,7 +10604,7 @@ async function claimsOf(issues, seams) {
10357
10604
  const refs = scanClaimBranches(seams.git, seams.remote).refs;
10358
10605
  const pulls = await orEmpty(seams.pullRequests, []);
10359
10606
  const closing = new Set(pulls.flatMap((pull) => closedIssuesIn(pull.body)));
10360
- const claimed = issues.map((issue3) => issue3.number).filter((number) => hasPlanFor(names, number) || closing.has(number) || refs.some((ref) => branchClaims(ref, number)));
10607
+ const claimed = issues.map((issue3) => issue3.number).filter((number) => hasPlanFor(names, number) || closing.has(number) || refs.some((ref3) => branchClaims(ref3, number)));
10361
10608
  return new Set(claimed);
10362
10609
  }
10363
10610
  function epicRowOf(row, byNumber2) {
@@ -10389,6 +10636,7 @@ async function readRoadmapEpicRows(options) {
10389
10636
  const { epicRows, specs } = splitRows(read.rows, epics);
10390
10637
  const shown = epicRows.filter((row) => options.all === true || row.horizon === "now");
10391
10638
  const problems = epicRows.length === 0 || listed.issues === null ? [] : readEpicProblems(listed.issues);
10639
+ const cancelled = listed.issues === null ? [] : cancelledEpicNoticeLines(listed.issues);
10392
10640
  return Object.freeze({
10393
10641
  roadmap: read.roadmap,
10394
10642
  groups: groupByHorizon(shown),
@@ -10397,7 +10645,7 @@ async function readRoadmapEpicRows(options) {
10397
10645
  epics,
10398
10646
  unknown: epics.unknown,
10399
10647
  problems: Object.freeze([...problems]),
10400
- warnings: Object.freeze([...read.warnings, ...problems.map(epicProblemMessage)])
10648
+ warnings: Object.freeze([...read.warnings, ...problems.map(epicProblemMessage), ...cancelled])
10401
10649
  });
10402
10650
  }
10403
10651
 
@@ -10527,11 +10775,11 @@ async function resolveIssueTracker(context, seams) {
10527
10775
  function issueRef(tracker, id2) {
10528
10776
  return { opt: 0, kind: tracker.kind, externalId: id2, url: null };
10529
10777
  }
10530
- function issueName(ref) {
10531
- return `${ref.kind} issue ${ref.externalId}`;
10778
+ function issueName(ref3) {
10779
+ return `${ref3.kind} issue ${ref3.externalId}`;
10532
10780
  }
10533
- function urlLines(ref) {
10534
- return ref.url === null ? [] : [`URL: ${ref.url}`];
10781
+ function urlLines(ref3) {
10782
+ return ref3.url === null ? [] : [`URL: ${ref3.url}`];
10535
10783
  }
10536
10784
  async function onTracker(tracker, doing, call) {
10537
10785
  try {
@@ -10750,7 +10998,7 @@ function renderEpicTable(rows, width, full = false) {
10750
10998
  return [...epicGroups, ...unknown, ...hidden, ...specs].flatMap((group, index) => index === 0 ? group : ["", ...group]);
10751
10999
  }
10752
11000
 
10753
- // src/commands/epics.ts
11001
+ // src/commands/epic/show.ts
10754
11002
  var USAGE2 = "rafa epics [<n>]";
10755
11003
  var ISSUE_NUMBER = /^[1-9]\d*$/u;
10756
11004
  function readEpicArgument(args) {
@@ -10880,12 +11128,16 @@ async function readEpicsView(context, seams) {
10880
11128
  }
10881
11129
  const epics = readEpics({ issues: listing, claims: await claimsOf(listing, read), today: new Date });
10882
11130
  const epic = asked === null ? placeEpic(roadmap, listing, epics) : askedEpic(asked, listing, epics);
11131
+ const cancelled = cancelledEpicNoticeLines(listing);
10883
11132
  const row = listing.find((issue3) => issue3.number === epic?.number);
10884
- if (epic === null || row === undefined)
10885
- return Object.freeze({ ...empty, epic: null, unknown: null });
11133
+ if (epic === null || row === undefined) {
11134
+ for (const line of cancelled)
11135
+ warn(line);
11136
+ return Object.freeze({ ...empty, epic: null, unknown: null, warnings: Object.freeze([...notices, ...cancelled]) });
11137
+ }
10886
11138
  const rows = await readLineRows(epicLines(epic, row).lines, read);
10887
11139
  const problems = readEpicProblems(listing).filter((problem) => isProblemOf(problem, epic));
10888
- const warnings = [...rows.warnings, ...problems.map(epicProblemMessage)];
11140
+ const warnings = [...rows.warnings, ...problems.map(epicProblemMessage), ...cancelled];
10889
11141
  for (const warning of warnings)
10890
11142
  warn(warning);
10891
11143
  return Object.freeze({
@@ -10907,13 +11159,13 @@ async function runEpics(context, seams) {
10907
11159
  for (const text of renderEpics(result, width))
10908
11160
  context.output.info(text);
10909
11161
  }
10910
- function createEpicsCommand(seams = DEFAULT_ISSUE_SEAMS) {
11162
+ function createEpicShowCommand(seams = DEFAULT_ISSUE_SEAMS) {
10911
11163
  const command = {
10912
- name: "epics",
10913
- subject: "epics",
10914
- action: "epics",
11164
+ name: "epic show",
11165
+ subject: "epic",
11166
+ action: "show",
10915
11167
  summary: "list one epic's issues as the Roadmap table: the epic numbered, or the first now epic not done",
10916
- description: "Prints one epic's issues as `rafa roadmap` prints its spec lines, with the spec, blocked by," + " has and refs columns: the unticked lines of the epic's checklist in its order, then its open members" + " missing from the checklist by number. The epic is the type:epic issue numbered, or, with no number," + " the current place's epic, the one `rafa switch` moved to; a place naming a board alone, or no" + " position file, takes the first epic that board (with no position file `roadmap.issue`, else the" + " lowest-numbered open type:roadmap board, else the open issue titled Roadmap) names that is open," + " horizon:now and not done, the one `rafa next` walks into. The table is headed by the epic's title," + " computed state and done/total, and a stored state disagreeing with the computed one is printed" + " under it. No issue of another epic is named. Each label problem about the epic and each reading that" + " failed is warned about; an unreachable board prints the epic unknown with the reason. With" + " `--output=json` the epic, its rows, its problems and the warnings are the data of the terminal result" + " event.",
11168
+ description: "Prints one epic's issues as `rafa roadmap` prints its spec lines, with the spec, blocked by," + " has and refs columns: the unticked lines of the epic's checklist in its order, then its open members" + " missing from the checklist by number. The epic is the type:epic issue numbered, or, with no number," + " the current place's epic, the one `rafa switch` moved to; a place naming a board alone, or no" + " position file, takes the first epic that board (with no position file `roadmap.issue`, else the" + " lowest-numbered open type:roadmap board, else the open issue titled Roadmap) names that is open," + " horizon:now and not done, the one `rafa next` walks into. The table is headed by the epic's title," + " computed state and done/total, and a stored state disagreeing with the computed one is printed" + " under it. No issue of another epic is named. Each label problem about the epic and each reading that" + " failed is warned about; an unreachable board prints the epic unknown with the reason. With" + " `--output=json` the epic, its rows, its problems and the warnings are the data of the terminal result" + " event. `rafa epics` and `rafa epic` are the same command, typed by the subject alone.",
10917
11169
  args: [
10918
11170
  {
10919
11171
  name: "n",
@@ -10925,133 +11177,123 @@ function createEpicsCommand(seams = DEFAULT_ISSUE_SEAMS) {
10925
11177
  flags: [],
10926
11178
  examples: [
10927
11179
  {
10928
- cmd: "rafa epics",
10929
- note: "Prints the issues of the epic `rafa next` is walking: the first now epic on the Roadmap not done."
11180
+ cmd: "rafa epic show",
11181
+ note: "Prints the issues of the epic `rafa next` is walking: the first now epic on the Roadmap not done." + " `rafa epics` prints the same."
10930
11182
  },
10931
11183
  {
10932
- cmd: "rafa epics 252",
10933
- note: "Prints the issues of epic #252, whatever its horizon, in its checklist's order."
11184
+ cmd: "rafa epic show 252",
11185
+ note: "Prints the issues of epic #252, whatever its horizon, in its checklist's order, as `rafa epics 252` does."
10934
11186
  },
10935
11187
  {
10936
- cmd: "rafa epics 252 --output=json",
11188
+ cmd: "rafa epic show 252 --output=json",
10937
11189
  note: "Writes a result event whose data holds epic #252 as computed, its rows and its label problems."
10938
11190
  }
10939
11191
  ],
10940
11192
  outputs: ["text", "json"],
11193
+ aliases: ["epic"],
11194
+ lastingAliases: ["epic"],
10941
11195
  run: (context) => runEpics(context, seams)
10942
11196
  };
10943
11197
  return Object.freeze(command);
10944
11198
  }
10945
- var epics_default = createEpicsCommand();
11199
+ var show_default = createEpicShowCommand();
10946
11200
 
10947
- // src/commands/plan/list.ts
10948
- import { existsSync as existsSync17, readdirSync as readdirSync9, readFileSync as readFileSync19 } from "fs";
11201
+ // src/commands/release/status.ts
11202
+ import { existsSync as existsSync18, readdirSync as readdirSync9, readFileSync as readFileSync20 } from "fs";
10949
11203
  import { join as join14 } from "path";
10950
- var USAGE3 = "rafa plan list";
10951
- function listing(dir, stub) {
10952
- const plan = join14(dir, planFileName(stub, false));
10953
- const trackerPath = join14(dir, planFileName(stub, true));
10954
- const tracker = isFile(trackerPath) ? trackerPath : null;
10955
- const planModel = parsePlan(readFileSync19(plan, "utf8"));
10956
- const counted2 = tracker === null ? planModel : parsePlan(readFileSync19(tracker, "utf8"));
10957
- return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
10958
- }
10959
- function listPlans(plans) {
10960
- const dir = plans.path;
10961
- if (!existsSync17(dir))
10962
- return { dir, plans: [] };
10963
- const stubs = readdirSync9(dir).filter((name2) => isFile(join14(dir, name2))).map((name2) => stubOfPlanFile(name2)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
10964
- return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
11204
+
11205
+ // src/effort/store/changes.ts
11206
+ import { randomUUID as randomUUID4 } from "crypto";
11207
+ import { existsSync as existsSync17 } from "fs";
11208
+ var INSERT_CHANGE = `
11209
+ INSERT INTO changes (
11210
+ id, session_id, plan_stub, task_line,
11211
+ level, area, summary,
11212
+ collected_at
11213
+ )
11214
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11215
+ ON CONFLICT (session_id, level, ifnull(area, ''), summary) DO NOTHING
11216
+ `;
11217
+ function optionalText(value) {
11218
+ const problem = textProblem(value);
11219
+ return problem === null ? null : { reason: "unstorable-field", problem };
10965
11220
  }
10966
- function rowNotes(plan) {
10967
- const notes = [formatCounts(plan.tasks)];
10968
- if (plan.tracker === null)
10969
- notes.push("no tracker");
10970
- if (plan.issues > 0)
10971
- notes.push(plural(plan.issues, "issue"));
10972
- return notes.join("; ");
11221
+ function requiredText(value) {
11222
+ return value === null ? { reason: "missing-field", problem: "is missing" } : optionalText(value);
10973
11223
  }
10974
- function renderPlanList(list, dirLabel) {
10975
- const label = `${dirLabel}/`;
10976
- if (list.plans.length === 0)
10977
- return [`No plans in ${label}.`];
10978
- const width = Math.max(...list.plans.map((plan) => plan.stub.length));
10979
- return [`Plans in ${label}:`, ...list.plans.map((plan) => ` ${plan.stub.padEnd(width)} ${rowNotes(plan)}`)];
11224
+ function requiredLevel(value) {
11225
+ if (value === null)
11226
+ return { reason: "missing-field", problem: "is missing" };
11227
+ if (CHANGE_LEVELS.includes(value))
11228
+ return null;
11229
+ const expected2 = CHANGE_LEVELS.join(", ");
11230
+ return {
11231
+ reason: "unstorable-field",
11232
+ problem: `is ${describeValue3(value)}, not one of ${expected2}`
11233
+ };
10980
11234
  }
10981
- var planListCommand = {
10982
- name: "plan list",
10983
- subject: "plan",
10984
- action: "list",
10985
- summary: "list the plans, with the tasks done in each",
10986
- description: "Lists each `PLAN-<stub>.md` in the plans directory `plan.dir` names, in stub" + " order, with its tasks counted by checkbox: from its tracker, `PLAN_TRACKER-<stub>.md`, once" + " there is one, and from the plan itself before then. A plan with no tracker is marked `no" + " tracker`, and a plan the plan parser did not read as written shows how many issues `rafa plan" + " validate` reports for it. The directory is read under the project root, and is `.rafa/plans`" + " unless a config names another. With `--output=json` the list is the data of the terminal result" + " event, each path absolute.",
10987
- args: [],
10988
- flags: [],
10989
- examples: [
10990
- {
10991
- cmd: "rafa plan list",
10992
- note: "Prints one row per plan: its stub, then its tasks done, blocked and open."
10993
- },
10994
- {
10995
- cmd: "rafa plan list --output=json",
10996
- note: "Writes a start event, then a result event whose data is the list."
10997
- }
10998
- ],
10999
- outputs: ["text", "json"],
11000
- run: async (context) => {
11001
- expectNoArgument(context.args, USAGE3);
11002
- const plans = resolvePlansDir(requireProject(context, USAGE3), USAGE3, (message) => {
11003
- context.output.warn(message);
11004
- });
11005
- const list = listPlans(plans);
11006
- if (context.outputMode === "json") {
11007
- context.output.result(list);
11008
- return;
11235
+ var FIELD_CHECKS2 = [
11236
+ ["level", requiredLevel],
11237
+ ["area", optionalText],
11238
+ ["summary", requiredText]
11239
+ ];
11240
+ function rejectionOf2(change, index) {
11241
+ for (const [field5, check] of FIELD_CHECKS2) {
11242
+ const found = check(change[field5]);
11243
+ if (found !== null) {
11244
+ const text = `changes[${index}].${field5} ${found.problem}; not written`;
11245
+ return { index, reason: found.reason, field: field5, text };
11009
11246
  }
11010
- for (const line of renderPlanList(list, plans.label))
11011
- context.output.info(line);
11012
11247
  }
11013
- };
11014
- var list_default = Object.freeze(planListCommand);
11015
-
11016
- // src/cli/prompt/confirm.ts
11017
- import { createInterface as createInterface2 } from "readline";
11018
- function createLinePrompter(input, output) {
11019
- const buffered = [];
11020
- const waiting = [];
11021
- let ended = false;
11022
- const lines = createInterface2({ input, terminal: false, crlfDelay: Infinity });
11023
- lines.on("line", (line) => {
11024
- const reader = waiting.shift();
11025
- if (reader === undefined)
11026
- buffered.push(line);
11248
+ return null;
11249
+ }
11250
+ function rowValues2(change, dispatch, id2, collectedAt) {
11251
+ const { sessionId, planStub: planStub2, taskLine } = dispatch;
11252
+ const { level, area, summary } = change;
11253
+ return [id2, sessionId, planStub2, taskLine, level, area, summary, collectedAt];
11254
+ }
11255
+ function insertRows3(db, rows) {
11256
+ const insert = db.query(INSERT_CHANGE);
11257
+ const insertAll = db.transaction(() => rows.reduce((appended2, values) => appended2 + insert.run(...values).changes, 0));
11258
+ return insertAll.immediate();
11259
+ }
11260
+ function writeChanges(repoRoot, write, seams = {}) {
11261
+ checkDispatch("changes", write.dispatch, null);
11262
+ const path3 = sqliteStorePath(repoRoot);
11263
+ const rejected = [];
11264
+ const stored = [];
11265
+ for (const [index, change] of write.changes.entries()) {
11266
+ const rejection = rejectionOf2(change, index);
11267
+ if (rejection === null)
11268
+ stored.push(change);
11027
11269
  else
11028
- reader(line);
11029
- });
11030
- lines.on("close", () => {
11031
- ended = true;
11032
- for (const reader of waiting.splice(0))
11033
- reader(null);
11034
- });
11035
- return {
11036
- say: (text) => {
11037
- output.write(`${text}
11038
- `);
11039
- },
11040
- ask: async (question) => {
11041
- output.write(question);
11042
- const next = buffered.shift();
11043
- if (next !== undefined)
11044
- return next;
11045
- if (ended)
11046
- return null;
11047
- return new Promise((settle) => {
11048
- waiting.push(settle);
11049
- });
11050
- },
11051
- close: () => {
11052
- lines.close();
11053
- }
11054
- };
11270
+ rejected.push(rejection);
11271
+ }
11272
+ const collectedAt = (seams.now ?? (() => new Date))().toISOString();
11273
+ const newId = seams.newId ?? randomUUID4;
11274
+ const rows = stored.map((change) => rowValues2(change, write.dispatch, newId(), collectedAt));
11275
+ const appended2 = writeSqliteStore(path3, rows.length, 0, (db) => insertRows3(db, rows));
11276
+ return { path: path3, appended: appended2, skipped: stored.length - appended2, rejected };
11277
+ }
11278
+ var SELECT_PLAN_CHANGES = `
11279
+ SELECT session_id, task_line, level, area, summary, collected_at
11280
+ FROM changes
11281
+ WHERE plan_stub IS ?
11282
+ ORDER BY seq
11283
+ `;
11284
+ function readPlanChanges(repoRoot, planStub2) {
11285
+ const path3 = sqliteStorePath(repoRoot);
11286
+ if (!existsSync17(path3))
11287
+ return [];
11288
+ const rows = withSqliteStore(path3, false, (db) => db.query(SELECT_PLAN_CHANGES).all(planStub2));
11289
+ return rows.map((row) => ({
11290
+ sessionId: row.session_id,
11291
+ taskLine: row.task_line,
11292
+ level: row.level,
11293
+ area: row.area,
11294
+ summary: row.summary,
11295
+ collectedAt: row.collected_at
11296
+ }));
11055
11297
  }
11056
11298
  // src/pr/preflight-items.ts
11057
11299
  var DEFAULT_GH_HOST = "github.com";
@@ -11130,8 +11372,8 @@ function parseWorktrees(stdout) {
11130
11372
  continue;
11131
11373
  }
11132
11374
  if (line.startsWith("branch ")) {
11133
- const ref = line.slice("branch ".length).trim();
11134
- branch = ref.startsWith(BRANCH_REF_PREFIX) ? ref.slice(BRANCH_REF_PREFIX.length) : ref;
11375
+ const ref3 = line.slice("branch ".length).trim();
11376
+ branch = ref3.startsWith(BRANCH_REF_PREFIX) ? ref3.slice(BRANCH_REF_PREFIX.length) : ref3;
11135
11377
  }
11136
11378
  }
11137
11379
  flush();
@@ -11664,14 +11906,809 @@ function ghPullRequestsIn(cwd) {
11664
11906
  function ghAuthOkIn(cwd) {
11665
11907
  return ghAuthOk(createGhRunner({ cwd }));
11666
11908
  }
11667
- // src/start/branch-decision.ts
11668
- var REMOTE = "origin";
11669
- var BRANCH_PREFIX2 = "feat/";
11670
- var INDENT2 = " ";
11671
- var UNTRACKED_PREFIX = "??";
11672
- var MAX_LISTED_CHANGES2 = 10;
11673
- var COUNT = /^\d+$/;
11674
- function branchNameFor(planStub2) {
11909
+ // src/release/changelog.ts
11910
+ var RELEASE_HEADING_FIELDS = ["version", "date", "title"];
11911
+ var PLACEHOLDER = /\{([A-Za-z]+)\}/g;
11912
+ var WHITESPACE = /\s+/g;
11913
+ var ATX_HEADING = /^ {0,3}#{1,6}(?: |$)/;
11914
+ var CODE_FENCE = /^ {0,3}(?:```|~~~)/;
11915
+ var SEPARATORS = "\u2014\u2013-,;:\xB7|";
11916
+ function isSeparator(character) {
11917
+ return character.trim() === "" || SEPARATORS.includes(character);
11918
+ }
11919
+ function trimTrailingSeparators(text) {
11920
+ let end = text.length;
11921
+ while (end > 0 && isSeparator(text[end - 1] ?? ""))
11922
+ end -= 1;
11923
+ return text.slice(0, end);
11924
+ }
11925
+ function trimLeadingSeparators(text) {
11926
+ let start = 0;
11927
+ while (start < text.length && isSeparator(text[start] ?? ""))
11928
+ start += 1;
11929
+ return text.slice(start);
11930
+ }
11931
+ function isHeadingField(field5) {
11932
+ return RELEASE_HEADING_FIELDS.includes(field5);
11933
+ }
11934
+ function renderReleaseHeading(template, values) {
11935
+ let rendered = "";
11936
+ let afterGap = false;
11937
+ const add = (text) => {
11938
+ if (!afterGap) {
11939
+ rendered += text;
11940
+ return;
11941
+ }
11942
+ const kept = trimLeadingSeparators(text);
11943
+ if (kept === "")
11944
+ return;
11945
+ afterGap = false;
11946
+ rendered += kept;
11947
+ };
11948
+ let cursor = 0;
11949
+ for (const found of template.matchAll(PLACEHOLDER)) {
11950
+ const before = template.slice(cursor, found.index);
11951
+ cursor = found.index + found[0].length;
11952
+ const name2 = found[1] ?? "";
11953
+ if (!isHeadingField(name2)) {
11954
+ add(`${before}${found[0]}`);
11955
+ continue;
11956
+ }
11957
+ const value = values[name2].trim();
11958
+ if (value !== "") {
11959
+ add(before);
11960
+ add(value);
11961
+ continue;
11962
+ }
11963
+ add(before);
11964
+ rendered = `${trimTrailingSeparators(rendered)} `;
11965
+ afterGap = true;
11966
+ }
11967
+ add(template.slice(cursor));
11968
+ return rendered.replace(WHITESPACE, " ").trim();
11969
+ }
11970
+ function areaOf(note) {
11971
+ const area = note.area?.trim() ?? "";
11972
+ return area === "" ? null : area;
11973
+ }
11974
+ function groupChangeNotes(notes) {
11975
+ const named = new Map;
11976
+ const unnamed = [];
11977
+ const seen = new Set;
11978
+ let noneNotes = 0;
11979
+ let duplicateNotes = 0;
11980
+ let blankNotes = 0;
11981
+ for (const note of notes) {
11982
+ if (note.level === "none") {
11983
+ noneNotes += 1;
11984
+ continue;
11985
+ }
11986
+ const area = areaOf(note);
11987
+ const summary = note.summary.replace(WHITESPACE, " ").trim();
11988
+ if (summary === "") {
11989
+ blankNotes += 1;
11990
+ continue;
11991
+ }
11992
+ const key = `${area ?? ""}
11993
+ ${summary}`;
11994
+ if (seen.has(key)) {
11995
+ duplicateNotes += 1;
11996
+ continue;
11997
+ }
11998
+ seen.add(key);
11999
+ if (area === null) {
12000
+ unnamed.push(summary);
12001
+ continue;
12002
+ }
12003
+ const summaries = named.get(area);
12004
+ if (summaries === undefined)
12005
+ named.set(area, [summary]);
12006
+ else
12007
+ summaries.push(summary);
12008
+ }
12009
+ const groups = [...named].map(([area, summaries]) => ({ area, summaries }));
12010
+ return {
12011
+ groups: unnamed.length === 0 ? groups : [...groups, { area: null, summaries: unnamed }],
12012
+ noneNotes,
12013
+ duplicateNotes,
12014
+ blankNotes
12015
+ };
12016
+ }
12017
+ function renderNoteLines(grouping) {
12018
+ return grouping.groups.flatMap((group) => group.summaries.map((summary) => group.area === null ? `- ${summary}` : `- ${group.area}: ${summary}`));
12019
+ }
12020
+ function renderChangelogEntry(input) {
12021
+ const heading = renderReleaseHeading(input.template, input.values);
12022
+ const grouping = groupChangeNotes(input.notes);
12023
+ const lines = renderNoteLines(grouping);
12024
+ return {
12025
+ heading,
12026
+ lines,
12027
+ groups: grouping.groups,
12028
+ text: lines.length === 0 ? heading : `${heading}
12029
+
12030
+ ${lines.join(`
12031
+ `)}`,
12032
+ noneNotes: grouping.noneNotes,
12033
+ duplicateNotes: grouping.duplicateNotes,
12034
+ blankNotes: grouping.blankNotes
12035
+ };
12036
+ }
12037
+ function findHeading(lines, from) {
12038
+ let fenced = false;
12039
+ for (let index = 0;index < lines.length; index += 1) {
12040
+ const line = lines[index] ?? "";
12041
+ if (CODE_FENCE.test(line)) {
12042
+ fenced = !fenced;
12043
+ continue;
12044
+ }
12045
+ if (fenced || index < from)
12046
+ continue;
12047
+ if (ATX_HEADING.test(line))
12048
+ return index;
12049
+ }
12050
+ return null;
12051
+ }
12052
+ function endOfContent(lines) {
12053
+ let end = lines.length;
12054
+ while (end > 0 && (lines[end - 1] ?? "").trim() === "")
12055
+ end -= 1;
12056
+ return end;
12057
+ }
12058
+ function insertPoint(lines) {
12059
+ const first = findHeading(lines, 0);
12060
+ if (first === null)
12061
+ return { at: 0, point: "file-top" };
12062
+ const next = findHeading(lines, first + 1);
12063
+ if (next !== null)
12064
+ return { at: next, point: "before-next-heading" };
12065
+ return { at: endOfContent(lines), point: "file-end" };
12066
+ }
12067
+ function insertChangelogEntry(changelog, entry) {
12068
+ const lines = changelog.split(`
12069
+ `);
12070
+ const { at, point } = insertPoint(lines);
12071
+ const entryLines = entry.split(`
12072
+ `);
12073
+ const before = at === 0 || (lines[at - 1] ?? "").trim() === "" ? [] : [""];
12074
+ const after = at >= lines.length || (lines[at] ?? "").trim() === "" ? [] : [""];
12075
+ const inserted = [
12076
+ ...lines.slice(0, at),
12077
+ ...before,
12078
+ ...entryLines,
12079
+ ...after,
12080
+ ...lines.slice(at)
12081
+ ];
12082
+ return { text: inserted.join(`
12083
+ `), point, line: at + before.length + 1 };
12084
+ }
12085
+ function changelogDate(now) {
12086
+ const month = `${now.getMonth() + 1}`.padStart(2, "0");
12087
+ const day = `${now.getDate()}`.padStart(2, "0");
12088
+ return `${now.getFullYear()}-${month}-${day}`;
12089
+ }
12090
+
12091
+ // src/release/level.ts
12092
+ var RELEASE_LEVEL_RANK = {
12093
+ none: 0,
12094
+ patch: 1,
12095
+ minor: 2,
12096
+ major: 3
12097
+ };
12098
+ function highestChangeLevel(notes) {
12099
+ return notes.reduce((highest, note) => highest === null || RELEASE_LEVEL_RANK[note.level] > RELEASE_LEVEL_RANK[highest] ? note.level : highest, null);
12100
+ }
12101
+ function resolveReleaseLevel(declared, notes) {
12102
+ const notesLevel = highestChangeLevel(notes);
12103
+ if (declared !== null)
12104
+ return { level: declared, source: "plan", notesLevel };
12105
+ if (notesLevel !== null)
12106
+ return { level: notesLevel, source: "notes", notesLevel };
12107
+ return { level: "none", source: "default", notesLevel };
12108
+ }
12109
+
12110
+ // src/release/version.ts
12111
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
12112
+ var RELEASE_REMOTE = "origin";
12113
+ var RELEASE_BASE_BRANCH = "main";
12114
+ var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
12115
+ function parseSemanticVersion(raw) {
12116
+ const found = VERSION_SHAPE.exec(raw);
12117
+ if (found === null)
12118
+ return null;
12119
+ return {
12120
+ major: Number(found[1]),
12121
+ minor: Number(found[2]),
12122
+ patch: Number(found[3]),
12123
+ prerelease: found[4] ?? "",
12124
+ build: found[5] ?? ""
12125
+ };
12126
+ }
12127
+ function formatSemanticVersion(version2) {
12128
+ const prerelease = version2.prerelease === "" ? "" : `-${version2.prerelease}`;
12129
+ const build = version2.build === "" ? "" : `+${version2.build}`;
12130
+ return `${version2.major}.${version2.minor}.${version2.patch}${prerelease}${build}`;
12131
+ }
12132
+ function bumped(version2, level) {
12133
+ const onPrerelease = version2.prerelease !== "";
12134
+ const blank = { prerelease: "", build: "" };
12135
+ if (level === "major") {
12136
+ const stays = onPrerelease && version2.minor === 0 && version2.patch === 0;
12137
+ const major = stays ? version2.major : version2.major + 1;
12138
+ return { major, minor: 0, patch: 0, ...blank };
12139
+ }
12140
+ if (level === "minor") {
12141
+ const stays = onPrerelease && version2.patch === 0;
12142
+ const minor = stays ? version2.minor : version2.minor + 1;
12143
+ return { major: version2.major, minor, patch: 0, ...blank };
12144
+ }
12145
+ const patch = onPrerelease ? version2.patch : version2.patch + 1;
12146
+ return { major: version2.major, minor: version2.minor, patch, ...blank };
12147
+ }
12148
+ function nextVersion(base, level) {
12149
+ const parsed = parseSemanticVersion(base);
12150
+ if (parsed === null)
12151
+ return null;
12152
+ if (level === "none")
12153
+ return formatSemanticVersion(parsed);
12154
+ return formatSemanticVersion(bumped(parsed, level));
12155
+ }
12156
+ function manifestFields(text) {
12157
+ let parsed;
12158
+ try {
12159
+ parsed = JSON.parse(text);
12160
+ } catch {
12161
+ return null;
12162
+ }
12163
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
12164
+ return null;
12165
+ return parsed;
12166
+ }
12167
+ function readManifestVersion(text) {
12168
+ const fields = manifestFields(text);
12169
+ if (fields === null)
12170
+ return null;
12171
+ const value = fields["version"];
12172
+ if (typeof value !== "string")
12173
+ return null;
12174
+ const trimmed = value.trim();
12175
+ return trimmed === "" ? null : trimmed;
12176
+ }
12177
+ var VERSION_PAIR = /("version"\s*:\s*)"(?:[^"\\]|\\.)*"/g;
12178
+ function sameBesidesVersion(before, after) {
12179
+ const restored = { ...after, version: before["version"] };
12180
+ return JSON.stringify(restored) === JSON.stringify(before);
12181
+ }
12182
+ function replaceManifestVersion(text, next) {
12183
+ const previous = readManifestVersion(text);
12184
+ const before = manifestFields(text);
12185
+ if (previous === null || before === null)
12186
+ return null;
12187
+ for (const pair of text.matchAll(VERSION_PAIR)) {
12188
+ const start = pair.index;
12189
+ const candidate = `${text.slice(0, start)}${pair[1]}${JSON.stringify(next)}${text.slice(start + pair[0].length)}`;
12190
+ const after = manifestFields(candidate);
12191
+ if (after === null || after["version"] !== next)
12192
+ continue;
12193
+ if (!sameBesidesVersion(before, after))
12194
+ continue;
12195
+ return { text: candidate, previous };
12196
+ }
12197
+ return null;
12198
+ }
12199
+ function writeManifestVersion(path3, next) {
12200
+ const unwritten = { path: path3, written: false, previous: null, version: next };
12201
+ let text;
12202
+ try {
12203
+ text = readFileSync19(path3, "utf8");
12204
+ } catch (error) {
12205
+ return { ...unwritten, problem: `${path3} could not be read: ${messageOf(error)}` };
12206
+ }
12207
+ const rewritten = replaceManifestVersion(text, next);
12208
+ if (rewritten === null) {
12209
+ const previous = readManifestVersion(text);
12210
+ return {
12211
+ ...unwritten,
12212
+ previous,
12213
+ problem: previous === null ? `${path3} declares no version to write ${next} over` : `${path3} declares ${previous}, and the pair holding it could not be rewritten`
12214
+ };
12215
+ }
12216
+ try {
12217
+ writeFileSync9(path3, rewritten.text);
12218
+ } catch (error) {
12219
+ return { ...unwritten, previous: rewritten.previous, problem: `${path3} could not be written: ${messageOf(error)}` };
12220
+ }
12221
+ return { path: path3, written: true, previous: rewritten.previous, version: next, problem: null };
12222
+ }
12223
+ function gitPathOf(versionFile) {
12224
+ let path3 = versionFile;
12225
+ while (path3.startsWith("./"))
12226
+ path3 = path3.slice(2);
12227
+ return path3;
12228
+ }
12229
+ function readBaseVersion(git, versionFile, options = {}) {
12230
+ const remote = options.remote ?? RELEASE_REMOTE;
12231
+ const branch = options.branch ?? RELEASE_BASE_BRANCH;
12232
+ const ref3 = `${remote}/${branch}`;
12233
+ const path3 = gitPathOf(versionFile);
12234
+ const problems = [];
12235
+ const fetched = git(["fetch", remote, branch]);
12236
+ if (!fetched.ok) {
12237
+ problems.push(`${branch} could not be fetched from ${remote}, so the base version is whatever this clone already holds for ${ref3}: ${gitSaid(fetched)}`);
12238
+ }
12239
+ const shown = git(["show", `${ref3}:${path3}`]);
12240
+ if (!shown.ok) {
12241
+ problems.push(`${ref3}:${path3} could not be read: ${gitSaid(shown)}`);
12242
+ return { ref: ref3, path: path3, version: null, fetched: fetched.ok, problems };
12243
+ }
12244
+ const version2 = readManifestVersion(shown.stdout);
12245
+ if (version2 === null)
12246
+ problems.push(`${ref3}:${path3} declares no version`);
12247
+ return { ref: ref3, path: path3, version: version2, fetched: fetched.ok, problems };
12248
+ }
12249
+
12250
+ // src/commands/release/status.ts
12251
+ var RELEASE_STATUS_USAGE = "rafa release status [--plan=<stub>]";
12252
+ var UNREADABLE2 = "?";
12253
+ var NOTHING = "none";
12254
+ var INDENT2 = " ";
12255
+ var GAP = " ";
12256
+ var HEADING_VERSION = /(?<![0-9A-Za-z.+-])v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/g;
12257
+ var ATX_HEADING2 = /^ {0,3}#{1,6}(?: |$)/;
12258
+ var CODE_FENCE2 = /^ {0,3}(?:```|~~~)/;
12259
+ var RELEASE_TAG = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/;
12260
+ var NUMERIC_IDENTIFIER = /^(?:0|[1-9]\d*)$/;
12261
+ var DEFAULT_RELEASE_SEAMS = Object.freeze({});
12262
+ function compareIdentifiers(left, right) {
12263
+ const leftNumeric = NUMERIC_IDENTIFIER.test(left);
12264
+ const rightNumeric = NUMERIC_IDENTIFIER.test(right);
12265
+ if (leftNumeric && rightNumeric)
12266
+ return Number(left) - Number(right);
12267
+ if (leftNumeric)
12268
+ return -1;
12269
+ if (rightNumeric)
12270
+ return 1;
12271
+ if (left === right)
12272
+ return 0;
12273
+ return left < right ? -1 : 1;
12274
+ }
12275
+ function comparePrereleases(left, right) {
12276
+ if (left === right)
12277
+ return 0;
12278
+ if (left === "")
12279
+ return 1;
12280
+ if (right === "")
12281
+ return -1;
12282
+ const leftParts = left.split(".");
12283
+ const rightParts = right.split(".");
12284
+ const shared = Math.min(leftParts.length, rightParts.length);
12285
+ for (let index = 0;index < shared; index += 1) {
12286
+ const order = compareIdentifiers(leftParts[index] ?? "", rightParts[index] ?? "");
12287
+ if (order !== 0)
12288
+ return order;
12289
+ }
12290
+ return leftParts.length - rightParts.length;
12291
+ }
12292
+ function compareVersions(left, right) {
12293
+ if (left.major !== right.major)
12294
+ return left.major - right.major;
12295
+ if (left.minor !== right.minor)
12296
+ return left.minor - right.minor;
12297
+ if (left.patch !== right.patch)
12298
+ return left.patch - right.patch;
12299
+ return comparePrereleases(left.prerelease, right.prerelease);
12300
+ }
12301
+ function compareReleaseTags(left, right) {
12302
+ return compareVersions(right.parsed, left.parsed);
12303
+ }
12304
+ function releaseTagsOf(text) {
12305
+ const tags = [];
12306
+ for (const line of text.split(`
12307
+ `)) {
12308
+ const tag = line.trim();
12309
+ const found = RELEASE_TAG.exec(tag);
12310
+ const version2 = found?.[1];
12311
+ if (version2 === undefined)
12312
+ continue;
12313
+ const parsed = parseSemanticVersion(version2);
12314
+ if (parsed === null)
12315
+ continue;
12316
+ tags.push({ tag, version: version2, parsed });
12317
+ }
12318
+ return [...tags].sort(compareReleaseTags);
12319
+ }
12320
+ function changelogVersions(text) {
12321
+ const versions = [];
12322
+ const seen = new Set;
12323
+ let fenced = false;
12324
+ for (const line of text.split(`
12325
+ `)) {
12326
+ if (CODE_FENCE2.test(line)) {
12327
+ fenced = !fenced;
12328
+ continue;
12329
+ }
12330
+ if (fenced || !ATX_HEADING2.test(line))
12331
+ continue;
12332
+ for (const found of line.matchAll(HEADING_VERSION)) {
12333
+ const version2 = found[1] ?? "";
12334
+ if (parseSemanticVersion(version2) === null)
12335
+ continue;
12336
+ if (!seen.has(version2)) {
12337
+ seen.add(version2);
12338
+ versions.push(version2);
12339
+ }
12340
+ break;
12341
+ }
12342
+ }
12343
+ return versions;
12344
+ }
12345
+ function untaggedVersions(released, tags) {
12346
+ const tagged = new Set(tags.map((tag) => tag.version));
12347
+ return released.filter((version2) => !tagged.has(version2));
12348
+ }
12349
+ function textOf(path3) {
12350
+ try {
12351
+ return readFileSync20(path3, "utf8");
12352
+ } catch {
12353
+ return null;
12354
+ }
12355
+ }
12356
+ function readVersionFile(root, configured) {
12357
+ const resolved = join14(root, configured);
12358
+ const text = textOf(resolved);
12359
+ if (text === null) {
12360
+ return { path: configured, version: null, problem: `the version file could not be read at ${resolved}` };
12361
+ }
12362
+ const version2 = readManifestVersion(text);
12363
+ return version2 === null ? { path: configured, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, version: version2, problem: null };
12364
+ }
12365
+ function readTags(git) {
12366
+ const result = git(["tag", "--list"]);
12367
+ if (!result.ok) {
12368
+ return { tags: [], latest: null, problem: `the tags could not be listed: ${gitSaid(result)}` };
12369
+ }
12370
+ const tags = releaseTagsOf(result.stdout);
12371
+ return { tags, latest: tags[0] ?? null, problem: null };
12372
+ }
12373
+ function readUntagged(root, configured, tags) {
12374
+ const resolved = join14(root, configured);
12375
+ const text = textOf(resolved);
12376
+ if (text === null) {
12377
+ return {
12378
+ path: configured,
12379
+ released: [],
12380
+ versions: [],
12381
+ problem: `the changelog could not be read at ${resolved}`
12382
+ };
12383
+ }
12384
+ const released = changelogVersions(text);
12385
+ return { path: configured, released, versions: untaggedVersions(released, tags), problem: null };
12386
+ }
12387
+ function readBranch(git) {
12388
+ const result = git(["rev-parse", "--abbrev-ref", "HEAD"]);
12389
+ const branch = result.stdout.trim();
12390
+ return result.ok && branch !== "" && branch !== "HEAD" ? branch : null;
12391
+ }
12392
+ function planStubsUnder2(root, planDir) {
12393
+ const resolved = join14(root, planDir);
12394
+ if (!existsSync18(resolved))
12395
+ return [];
12396
+ try {
12397
+ return planStubsFromFileNames(readdirSync9(resolved));
12398
+ } catch {
12399
+ return [];
12400
+ }
12401
+ }
12402
+ function branchStubOf(branch) {
12403
+ const slashAt = branch.indexOf("/");
12404
+ if (slashAt <= 0)
12405
+ return null;
12406
+ const stub = branch.slice(slashAt + 1);
12407
+ return stub === "" || stub.includes("/") ? null : stub;
12408
+ }
12409
+ function pickPlan(asked, branch, stubs) {
12410
+ if (asked !== null)
12411
+ return { stub: asked, source: "flag", problem: null };
12412
+ if (branch === null) {
12413
+ return { stub: null, source: "none", problem: "the branch could not be read, so no plan was resolved" };
12414
+ }
12415
+ const branchStub = branchStubOf(branch);
12416
+ if (branchStub === null) {
12417
+ return { stub: null, source: "none", problem: `the branch "${branch}" names no plan stub` };
12418
+ }
12419
+ const resolution = resolvePlanStub(branchStub, stubs);
12420
+ if (resolution.stub !== null)
12421
+ return { stub: resolution.stub, source: "roster", problem: null };
12422
+ if (resolution.match === "ambiguous") {
12423
+ return {
12424
+ stub: null,
12425
+ source: "none",
12426
+ problem: `the branch stub "${branchStub}" reaches ${resolution.candidates.length} plans:` + ` ${resolution.candidates.join(", ")}`
12427
+ };
12428
+ }
12429
+ return { stub: branchStub, source: "branch", problem: null };
12430
+ }
12431
+ function readPending(root, asked, branch, stubs, readNotes) {
12432
+ const pick = pickPlan(asked, branch, stubs);
12433
+ const empty = { stub: pick.stub, source: pick.source, branch, notes: [], level: null };
12434
+ if (pick.stub === null)
12435
+ return { ...empty, problem: pick.problem };
12436
+ try {
12437
+ const notes = readNotes(root, pick.stub);
12438
+ return { ...empty, notes, level: highestChangeLevel(notes), problem: pick.problem };
12439
+ } catch (error) {
12440
+ return { ...empty, problem: `the change notes could not be read: ${messageOf(error)}` };
12441
+ }
12442
+ }
12443
+ function versionFileCell(reading2) {
12444
+ return `${reading2.path}: ${reading2.version ?? UNREADABLE2}`;
12445
+ }
12446
+ function tagCell(reading2) {
12447
+ if (reading2.problem !== null)
12448
+ return UNREADABLE2;
12449
+ const held = `of ${reading2.tags.length} release tags`;
12450
+ return reading2.latest === null ? `${NOTHING}, ${held}` : `${reading2.latest.tag}, ${held}`;
12451
+ }
12452
+ function untaggedCell(reading2) {
12453
+ if (reading2.problem !== null)
12454
+ return UNREADABLE2;
12455
+ if (reading2.released.length === 0)
12456
+ return `${NOTHING}, and the changelog names no release`;
12457
+ return reading2.versions.length === 0 ? NOTHING : reading2.versions.join(", ");
12458
+ }
12459
+ function levelPhrase(level) {
12460
+ return level === "none" ? "worth no release" : `worth a ${level} release`;
12461
+ }
12462
+ function pendingCell(reading2) {
12463
+ if (reading2.stub === null)
12464
+ return `${NOTHING}: no current plan`;
12465
+ const count = reading2.notes.length === 1 ? "1 note" : `${reading2.notes.length} notes`;
12466
+ if (reading2.notes.length === 0)
12467
+ return `${NOTHING} for ${reading2.stub}`;
12468
+ return `${count} for ${reading2.stub}, ${levelPhrase(reading2.level ?? "none")}`;
12469
+ }
12470
+ function pendingLines(reading2) {
12471
+ return renderNoteLines(groupChangeNotes(reading2.notes));
12472
+ }
12473
+ function problemsOf(reading2) {
12474
+ return [
12475
+ reading2.versionFile.problem,
12476
+ reading2.tags.problem,
12477
+ reading2.untagged.problem,
12478
+ reading2.plan.problem
12479
+ ].filter((problem) => problem !== null);
12480
+ }
12481
+ var LABELS = ["version file", "latest tag", "untagged", "pending"];
12482
+ function renderStatus(reading2) {
12483
+ const width = Math.max(...LABELS.map((label) => label.length));
12484
+ const cells = [
12485
+ versionFileCell(reading2.versionFile),
12486
+ tagCell(reading2.tags),
12487
+ untaggedCell(reading2.untagged),
12488
+ pendingCell(reading2.plan)
12489
+ ];
12490
+ const lines = LABELS.map((label, index) => `${INDENT2}${label.padEnd(width)}${GAP}${cells[index] ?? ""}`);
12491
+ const notes = pendingLines(reading2.plan).map((line) => `${INDENT2}${" ".repeat(width)}${GAP}${line}`);
12492
+ const problems = problemsOf(reading2).map((problem) => `${INDENT2}${problem}`);
12493
+ const block = [...lines, ...notes].join(`
12494
+ `);
12495
+ return problems.length === 0 ? block : [block, problems.join(`
12496
+ `)].join(`
12497
+
12498
+ `);
12499
+ }
12500
+ function projectOf(context) {
12501
+ if (context.project === null)
12502
+ throw new Error("rafa release runs inside a project, and was handed none");
12503
+ return context.project;
12504
+ }
12505
+ function statusConfig(project, warn) {
12506
+ try {
12507
+ const { config } = loadConfig({ root: project.root, home: project.home }, {}, warn);
12508
+ return {
12509
+ versionFile: config.releaseVersionFile,
12510
+ changelog: config.releaseChangelog,
12511
+ planDir: config.planDir
12512
+ };
12513
+ } catch (error) {
12514
+ if (!(error instanceof ConfigError))
12515
+ throw error;
12516
+ throw new CommandExit(1, ["\u274C The config cannot be used:", ...error.problems.map((problem) => ` ${problem}`)].join(`
12517
+ `));
12518
+ }
12519
+ }
12520
+ function readPlanFlag(context) {
12521
+ const value = context.flags["plan"];
12522
+ if (value === undefined)
12523
+ return null;
12524
+ const stub = typeof value === "string" ? value.trim() : "";
12525
+ if (stub === "") {
12526
+ throw new CommandExit(1, `\u274C --plan takes a plan stub, as --plan=rafa-21-changelog-and-release
12527
+ Usage: ${RELEASE_STATUS_USAGE}`);
12528
+ }
12529
+ return stub;
12530
+ }
12531
+ function expectNoArguments(args) {
12532
+ if (args.length === 0)
12533
+ return;
12534
+ throw new CommandExit(1, `\u274C Expected no arguments, got ${args.length}: ${args.join(" ")}
12535
+ Usage: ${RELEASE_STATUS_USAGE}`);
12536
+ }
12537
+ function readStatus(context, seams = DEFAULT_RELEASE_SEAMS) {
12538
+ expectNoArguments(context.args);
12539
+ const asked = readPlanFlag(context);
12540
+ const project = projectOf(context);
12541
+ const config = statusConfig(project, (message) => {
12542
+ context.output.warn(message);
12543
+ });
12544
+ const git = (seams.git ?? createGitRunner)(project.root);
12545
+ const readNotes = seams.readNotes ?? readPlanChanges;
12546
+ const tags = readTags(git);
12547
+ const reading2 = {
12548
+ versionFile: readVersionFile(project.root, config.versionFile),
12549
+ tags,
12550
+ untagged: readUntagged(project.root, config.changelog, tags.tags),
12551
+ plan: readPending(project.root, asked, readBranch(git), planStubsUnder2(project.root, config.planDir), readNotes)
12552
+ };
12553
+ return { reading: reading2, text: renderStatus(reading2) };
12554
+ }
12555
+ function createReleaseStatusCommand(seams = DEFAULT_RELEASE_SEAMS) {
12556
+ const command = {
12557
+ name: "release status",
12558
+ subject: "release",
12559
+ action: "status",
12560
+ summary: "show the version, the latest tag, the untagged releases and the pending change notes",
12561
+ description: "Reads four things about the release state of the project and writes nothing: the version" + " `release.versionFile` declares, the latest release tag of the repository by semantic version" + " precedence, the versions `release.changelog` calls released that carry no tag, and the change notes" + " the current plan's sessions have stored for its next release. The current plan is the one" + " `--plan=<stub>` names, and otherwise the one the checked-out branch's `<type>/<stub>` names," + " resolved against the plans in `plan.dir`. A reading that could not be made leaves its cell as `?`" + " and says why on a line under the block rather than refusing the other three. With `--output=json`" + " the four readings and the rendered block are the data of the terminal result event.",
12562
+ args: [],
12563
+ flags: [
12564
+ {
12565
+ name: "plan",
12566
+ description: "the plan stub the pending change notes are read for, instead of the branch's",
12567
+ type: "string"
12568
+ }
12569
+ ],
12570
+ examples: [
12571
+ {
12572
+ cmd: "rafa release status",
12573
+ note: "Shows the version, the latest tag, the untagged releases and the notes pending for this branch's plan."
12574
+ },
12575
+ {
12576
+ cmd: "rafa release status --plan=rafa-21-changelog-and-release",
12577
+ note: "Reads the pending change notes for that plan rather than the one the branch names."
12578
+ }
12579
+ ],
12580
+ outputs: ["text", "json"],
12581
+ run: async (context) => {
12582
+ const status = readStatus(context, seams);
12583
+ if (context.outputMode === "json") {
12584
+ context.output.result(status);
12585
+ return;
12586
+ }
12587
+ context.output.info(status.text);
12588
+ }
12589
+ };
12590
+ return Object.freeze(command);
12591
+ }
12592
+ var status_default = createReleaseStatusCommand();
12593
+
12594
+ // src/commands/plan/list.ts
12595
+ import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
12596
+ import { join as join15 } from "path";
12597
+ var USAGE3 = "rafa plan list";
12598
+ function listing(dir, stub) {
12599
+ const plan = join15(dir, planFileName(stub, false));
12600
+ const trackerPath = join15(dir, planFileName(stub, true));
12601
+ const tracker = isFile(trackerPath) ? trackerPath : null;
12602
+ const planModel = parsePlan(readFileSync21(plan, "utf8"));
12603
+ const counted2 = tracker === null ? planModel : parsePlan(readFileSync21(tracker, "utf8"));
12604
+ return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
12605
+ }
12606
+ function listPlans(plans) {
12607
+ const dir = plans.path;
12608
+ if (!existsSync19(dir))
12609
+ return { dir, plans: [] };
12610
+ const stubs = readdirSync10(dir).filter((name2) => isFile(join15(dir, name2))).map((name2) => stubOfPlanFile(name2)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
12611
+ return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
12612
+ }
12613
+ function rowNotes(plan) {
12614
+ const notes = [formatCounts(plan.tasks)];
12615
+ if (plan.tracker === null)
12616
+ notes.push("no tracker");
12617
+ if (plan.issues > 0)
12618
+ notes.push(plural(plan.issues, "issue"));
12619
+ return notes.join("; ");
12620
+ }
12621
+ function renderPlanList(list, dirLabel) {
12622
+ const label = `${dirLabel}/`;
12623
+ if (list.plans.length === 0)
12624
+ return [`No plans in ${label}.`];
12625
+ const width = Math.max(...list.plans.map((plan) => plan.stub.length));
12626
+ return [`Plans in ${label}:`, ...list.plans.map((plan) => ` ${plan.stub.padEnd(width)} ${rowNotes(plan)}`)];
12627
+ }
12628
+ var planListCommand = {
12629
+ name: "plan list",
12630
+ subject: "plan",
12631
+ action: "list",
12632
+ summary: "list the plans, with the tasks done in each",
12633
+ description: "Lists each `PLAN-<stub>.md` in the plans directory `plan.dir` names, in stub" + " order, with its tasks counted by checkbox: from its tracker, `PLAN_TRACKER-<stub>.md`, once" + " there is one, and from the plan itself before then. A plan with no tracker is marked `no" + " tracker`, and a plan the plan parser did not read as written shows how many issues `rafa plan" + " validate` reports for it. The directory is read under the project root, and is `.rafa/plans`" + " unless a config names another. With `--output=json` the list is the data of the terminal result" + " event, each path absolute.",
12634
+ args: [],
12635
+ flags: [],
12636
+ examples: [
12637
+ {
12638
+ cmd: "rafa plan list",
12639
+ note: "Prints one row per plan: its stub, then its tasks done, blocked and open."
12640
+ },
12641
+ {
12642
+ cmd: "rafa plan list --output=json",
12643
+ note: "Writes a start event, then a result event whose data is the list."
12644
+ }
12645
+ ],
12646
+ outputs: ["text", "json"],
12647
+ run: async (context) => {
12648
+ expectNoArgument(context.args, USAGE3);
12649
+ const plans = resolvePlansDir(requireProject(context, USAGE3), USAGE3, (message) => {
12650
+ context.output.warn(message);
12651
+ });
12652
+ const list = listPlans(plans);
12653
+ if (context.outputMode === "json") {
12654
+ context.output.result(list);
12655
+ return;
12656
+ }
12657
+ for (const line of renderPlanList(list, plans.label))
12658
+ context.output.info(line);
12659
+ }
12660
+ };
12661
+ var list_default = Object.freeze(planListCommand);
12662
+
12663
+ // src/cli/prompt/confirm.ts
12664
+ import { createInterface as createInterface2 } from "readline";
12665
+ function createLinePrompter(input, output) {
12666
+ const buffered = [];
12667
+ const waiting = [];
12668
+ let ended = false;
12669
+ const lines = createInterface2({ input, terminal: false, crlfDelay: Infinity });
12670
+ lines.on("line", (line) => {
12671
+ const reader = waiting.shift();
12672
+ if (reader === undefined)
12673
+ buffered.push(line);
12674
+ else
12675
+ reader(line);
12676
+ });
12677
+ lines.on("close", () => {
12678
+ ended = true;
12679
+ for (const reader of waiting.splice(0))
12680
+ reader(null);
12681
+ });
12682
+ return {
12683
+ say: (text) => {
12684
+ output.write(`${text}
12685
+ `);
12686
+ },
12687
+ ask: async (question) => {
12688
+ output.write(question);
12689
+ const next = buffered.shift();
12690
+ if (next !== undefined)
12691
+ return next;
12692
+ if (ended)
12693
+ return null;
12694
+ return new Promise((settle) => {
12695
+ waiting.push(settle);
12696
+ });
12697
+ },
12698
+ close: () => {
12699
+ lines.close();
12700
+ }
12701
+ };
12702
+ }
12703
+
12704
+ // src/start/branch-decision.ts
12705
+ var REMOTE = "origin";
12706
+ var BRANCH_PREFIX2 = "feat/";
12707
+ var INDENT3 = " ";
12708
+ var UNTRACKED_PREFIX = "??";
12709
+ var MAX_LISTED_CHANGES2 = 10;
12710
+ var COUNT = /^\d+$/;
12711
+ function branchNameFor(planStub2) {
11675
12712
  return `${BRANCH_PREFIX2}${planStub2}`;
11676
12713
  }
11677
12714
  function localRef(branch) {
@@ -11720,9 +12757,9 @@ function plural3(count, singular, many) {
11720
12757
  return count === 1 ? `1 ${singular}` : `${count} ${many}`;
11721
12758
  }
11722
12759
  function listChanges2(entries) {
11723
- const shown = entries.slice(0, MAX_LISTED_CHANGES2).map((entry) => `${INDENT2}${entry}`);
12760
+ const shown = entries.slice(0, MAX_LISTED_CHANGES2).map((entry) => `${INDENT3}${entry}`);
11724
12761
  const hidden = entries.length - shown.length;
11725
- return hidden > 0 ? [...shown, `${INDENT2}... and ${hidden} more`] : shown;
12762
+ return hidden > 0 ? [...shown, `${INDENT3}... and ${hidden} more`] : shown;
11726
12763
  }
11727
12764
  function trackedChanges(tree) {
11728
12765
  return Object.freeze(tree.entries.filter((entry) => !entry.startsWith(UNTRACKED_PREFIX)));
@@ -11734,7 +12771,7 @@ function treeRefusal(tree, plan) {
11734
12771
  return [
11735
12772
  `\u274C Refusing to leave ${plan.base} for ${plan.branch}: ` + `the working tree has ${plural3(changes.length, "change", "changes")} to tracked files.`,
11736
12773
  ...listChanges2(changes),
11737
- `${INDENT2}Commit or stash them, then run again. Untracked files are left alone.`
12774
+ `${INDENT3}Commit or stash them, then run again. Untracked files are left alone.`
11738
12775
  ].join(`
11739
12776
  `);
11740
12777
  }
@@ -11797,10 +12834,10 @@ function refuse4(lines) {
11797
12834
  }
11798
12835
  function quotedLines(said) {
11799
12836
  return said === "" ? [] : said.split(`
11800
- `).map((line) => `${INDENT2}${line}`);
12837
+ `).map((line) => `${INDENT3}${line}`);
11801
12838
  }
11802
12839
  function stillOn(plan) {
11803
- return `${INDENT2}The run is still on ${plan.base}.`;
12840
+ return `${INDENT3}The run is still on ${plan.base}.`;
11804
12841
  }
11805
12842
  function failureHead(step, plan) {
11806
12843
  if (step.id === "fetch") {
@@ -11816,8 +12853,8 @@ function divergedRefusal(standing, plan) {
11816
12853
  const behind = plural3(standing.behind, "commit", "commits");
11817
12854
  return refuse4([
11818
12855
  `\u274C Refusing to create ${plan.branch}: ${plan.base} has diverged from ${REMOTE}/${plan.base}.`,
11819
- `${INDENT2}${plan.base} is ${ahead} ahead of ${REMOTE}/${plan.base} and ${behind} behind it.`,
11820
- `${INDENT2}Push, rebase or reset ${plan.base}, then run again.`
12856
+ `${INDENT3}${plan.base} is ${ahead} ahead of ${REMOTE}/${plan.base} and ${behind} behind it.`,
12857
+ `${INDENT3}Push, rebase or reset ${plan.base}, then run again.`
11821
12858
  ]);
11822
12859
  }
11823
12860
  function readStepOutcome(step, result, plan) {
@@ -11830,7 +12867,7 @@ function readStepOutcome(step, result, plan) {
11830
12867
  if (standing === null) {
11831
12868
  return refuse4([
11832
12869
  `\u274C Could not ${step.label}.`,
11833
- `${INDENT2}git answered ${JSON.stringify(result.stdout)}, which is no pair of counts.`,
12870
+ `${INDENT3}git answered ${JSON.stringify(result.stdout)}, which is no pair of counts.`,
11834
12871
  stillOn(plan)
11835
12872
  ]);
11836
12873
  }
@@ -11920,11 +12957,11 @@ function parseArgs(argv, spec = {}) {
11920
12957
 
11921
12958
  // src/commands/loop/loop-sessions.ts
11922
12959
  import { execFileSync as execFileSync2 } from "child_process";
11923
- import { readFileSync as readFileSync20 } from "fs";
11924
- import { join as join15, relative as relative3 } from "path";
12960
+ import { readFileSync as readFileSync22 } from "fs";
12961
+ import { join as join16, relative as relative3 } from "path";
11925
12962
 
11926
12963
  // src/effort/store/task-finishes.ts
11927
- import { existsSync as existsSync18 } from "fs";
12964
+ import { existsSync as existsSync20 } from "fs";
11928
12965
  var SELECT_FINISHES = `
11929
12966
  SELECT collected_at FROM task_reports
11930
12967
  WHERE plan_stub IS ? AND outcome = 'done' AND collected_at >= ?
@@ -11935,7 +12972,7 @@ var SELECT_FINISHES = `
11935
12972
  `;
11936
12973
  function readTaskFinishes(repoRoot, query) {
11937
12974
  const path3 = sqliteStorePath(repoRoot);
11938
- if (!existsSync18(path3))
12975
+ if (!existsSync20(path3))
11939
12976
  return [];
11940
12977
  const bound = [query.planStub, query.since, query.planStub, query.since];
11941
12978
  const rows = withSqliteStore(path3, false, (db) => db.query(SELECT_FINISHES).all(...bound));
@@ -12019,9 +13056,9 @@ function readRecords(root, seams) {
12019
13056
  throw refusal2(`The session records under ${runsLabel(root)} cannot be read.`, messageOf(error));
12020
13057
  }
12021
13058
  }
12022
- function readBranchOrRefuse(root, readBranch) {
13059
+ function readBranchOrRefuse(root, readBranch2) {
12023
13060
  try {
12024
- return readBranch(root);
13061
+ return readBranch2(root);
12025
13062
  } catch (error) {
12026
13063
  throw refusal2("The branch checked out at the project root cannot be read, so no session is paired with it.", messageOf(error).split(`
12027
13064
  `)[0] ?? "", `Name the session with --${SESSION_ID_FLAG}; \`rafa loop list\` lists the running ones.`);
@@ -12065,11 +13102,11 @@ function writeSession(root, record, change) {
12065
13102
  }
12066
13103
  }
12067
13104
  function readSessionChecklist(root, record) {
12068
- const plan = join15(root, record.plan);
13105
+ const plan = join16(root, record.plan);
12069
13106
  const file = [trackerPathFor(plan), plan].find((path3) => isFile(path3));
12070
13107
  if (file === undefined)
12071
13108
  return null;
12072
- const content = readFileSync20(file, "utf8");
13109
+ const content = readFileSync22(file, "utf8");
12073
13110
  return { file, tasks: parsePlan(content).tasks, lines: content.split(`
12074
13111
  `) };
12075
13112
  }
@@ -12106,6 +13143,60 @@ function etaLine(eta) {
12106
13143
  return `ETA: ${total2} for ${plural(eta.left, "task")} left,` + ` at ${formatDuration(eta.secondsPerTask)} per task over the ${plural(eta.finished, "task")} this session finished`;
12107
13144
  }
12108
13145
 
13146
+ // src/next/epic-end.ts
13147
+ var CLOSE_MARK = "\uD83D\uDC49";
13148
+ var ROADMAP_MARK = "\uD83D\uDDFA";
13149
+ var RELEASE_MARK = "\uD83C\uDFF7";
13150
+ var EPIC_END_ROADMAP_COMMAND = "rafa roadmap";
13151
+ var EPIC_END_RELEASE_COMMAND = "rafa release tag";
13152
+ function epicCloseCommand(epic) {
13153
+ return `rafa epic close ${String(epic.number)}`;
13154
+ }
13155
+ function readEpicEndRelease(git, root, changelog) {
13156
+ const tags = readTags(git);
13157
+ if (tags.problem !== null)
13158
+ return { changelog, versions: [], problem: tags.problem };
13159
+ const untagged = readUntagged(root, changelog, tags.tags);
13160
+ return untagged.problem === null ? { changelog, versions: untagged.versions, problem: null } : { changelog, versions: [], problem: untagged.problem };
13161
+ }
13162
+ function namedVersions(versions) {
13163
+ if (versions.length === 1)
13164
+ return `${versions[0] ?? ""} is`;
13165
+ const head = versions.slice(0, -1).join(", ");
13166
+ return `${head} and ${versions[versions.length - 1] ?? ""} are`;
13167
+ }
13168
+ function epicEndLines(epic, release) {
13169
+ const lines = [
13170
+ `${CLOSE_MARK} epic #${String(epic.number)} ${epic.title} has run dry: close it through its gate` + ` \u2014 ${epicCloseCommand(epic)}`,
13171
+ `${ROADMAP_MARK} list the board's epics with their state and progress \u2014 ${EPIC_END_ROADMAP_COMMAND}`
13172
+ ];
13173
+ if (release === null || release.versions.length === 0)
13174
+ return Object.freeze(lines);
13175
+ return Object.freeze([
13176
+ ...lines,
13177
+ `${RELEASE_MARK} ${namedVersions(release.versions)} in ${release.changelog} with no tag: tag the release` + ` \u2014 ${EPIC_END_RELEASE_COMMAND}`
13178
+ ]);
13179
+ }
13180
+ function watchDryEpic() {
13181
+ let last = null;
13182
+ return {
13183
+ watch: (sources) => {
13184
+ last = null;
13185
+ const { board } = sources;
13186
+ const watched = {
13187
+ ...board,
13188
+ next: async () => {
13189
+ const reading2 = await board.next();
13190
+ last = reading2.dryEpic ?? null;
13191
+ return reading2;
13192
+ }
13193
+ };
13194
+ return { ...sources, board: watched };
13195
+ },
13196
+ last: () => last
13197
+ };
13198
+ }
13199
+
12109
13200
  // src/next/sources.ts
12110
13201
  var PREFIX14 = "rafa next";
12111
13202
  var DEFAULT_BASE_BRANCH = "main";
@@ -12125,6 +13216,13 @@ function listOnce3(read) {
12125
13216
  return kept;
12126
13217
  };
12127
13218
  }
13219
+ async function cancelledNotices(listing2) {
13220
+ try {
13221
+ return cancelledEpicNoticeLines(await listing2());
13222
+ } catch {
13223
+ return [];
13224
+ }
13225
+ }
12128
13226
  async function walkEpic(epic, listing2, readings) {
12129
13227
  const rows = await listing2();
12130
13228
  const read = readEpics({ issues: rows, claims: new Set, today: new Date(0) }).epics.find((candidate) => candidate.number === epic);
@@ -12133,11 +13231,20 @@ async function walkEpic(epic, listing2, readings) {
12133
13231
  throw new Error(`${PREFIX14}: epic #${String(epic)}, the current place, is not on the board listing, so its members cannot be read`);
12134
13232
  }
12135
13233
  const pick = await pickNextRoadmapLine(epicLines(read, row).lines, readings);
12136
- return { line: pick.line, passed: pick.skipped.length };
13234
+ return {
13235
+ line: pick.line,
13236
+ passed: pick.skipped.length,
13237
+ dry: pick.line === null ? { number: read.number, title: read.title } : null
13238
+ };
12137
13239
  }
12138
13240
  function ghNextBoard(options) {
12139
13241
  const { gh, git, configured, remote, root } = options;
12140
- const listing2 = listOnce3(options.listing ?? createGhBoardListing({ gh }));
13242
+ const listed = options.listing ?? createGhBoardListing({ gh });
13243
+ let asked = false;
13244
+ const listing2 = listOnce3(() => {
13245
+ asked = true;
13246
+ return listed();
13247
+ });
12141
13248
  const issues = memoiseIssues2(createGhSpecIssueReader({ gh }));
12142
13249
  return Object.freeze({
12143
13250
  next: async () => {
@@ -12156,11 +13263,13 @@ function ghNextBoard(options) {
12156
13263
  pullRequests: createGhOpenPullRequests({ gh })
12157
13264
  });
12158
13265
  const walk = epic === null ? await walkBoard(roadmap, { issues, readings, listing: listing2 }) : await walkEpic(epic, listing2, readings);
13266
+ const cancelled = options.noticeCancelled === true && asked ? await cancelledNotices(listing2) : [];
12159
13267
  return {
12160
13268
  roadmap,
12161
13269
  line: walk.line,
12162
13270
  passed: walk.passed,
12163
- problems: [...current?.notices ?? [], ...branches.problems]
13271
+ problems: [...current?.notices ?? [], ...cancelled, ...branches.problems],
13272
+ ...walk.dry === null ? {} : { dryEpic: walk.dry }
12164
13273
  };
12165
13274
  },
12166
13275
  blocking: async (issue3) => readBlockedLine(await issues(issue3), blockerStatesOf(issues)),
@@ -12169,8 +13278,12 @@ function ghNextBoard(options) {
12169
13278
  }
12170
13279
  async function walkBoard(roadmap, seams) {
12171
13280
  const read = await seams.issues(roadmap);
12172
- const { descent, pick } = await pickDescendedLine(parseRoadmapBody(read.body), seams);
12173
- return { line: pick.line, passed: descent.passed.length + pick.skipped.length };
13281
+ const { descent, pick, dry } = await pickDescendedLine(parseRoadmapBody(read.body), seams);
13282
+ return {
13283
+ line: pick.line,
13284
+ passed: descent.passed.length + pick.skipped.length,
13285
+ dry: dry && descent.epic !== null ? { number: descent.epic.number, title: descent.epic.title } : null
13286
+ };
12174
13287
  }
12175
13288
  function nextProject(context) {
12176
13289
  if (context.project === null)
@@ -12203,7 +13316,7 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
12203
13316
  const git = (seams.openGit ?? createGitRunner)(project.root);
12204
13317
  const gh = (seams.openGh ?? ((root) => createGhRunner({ cwd: root })))(project.root);
12205
13318
  const loop = resolveLoopSeams({ isAlive: seams.isAlive });
12206
- const board = () => ghNextBoard({ gh, git, configured: config.roadmapIssue, root: project.root });
13319
+ const board = () => ghNextBoard({ gh, git, configured: config.roadmapIssue, root: project.root, noticeCancelled: true });
12207
13320
  const held = {
12208
13321
  base: config.prBase ?? DEFAULT_BASE_BRANCH,
12209
13322
  plans: plansDirAt(project.root, config.planDir),
@@ -12214,7 +13327,8 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
12214
13327
  return Object.freeze({
12215
13328
  ...held,
12216
13329
  board: board(),
12217
- answer: () => Object.freeze({ ...held, board: board() })
13330
+ answer: () => Object.freeze({ ...held, board: board() }),
13331
+ release: () => readEpicEndRelease(git, project.root, config.releaseChangelog)
12218
13332
  });
12219
13333
  }
12220
13334
 
@@ -12228,7 +13342,7 @@ function once2(read) {
12228
13342
  return held.value;
12229
13343
  };
12230
13344
  }
12231
- function readBranch(sources, note) {
13345
+ function readBranch2(sources, note) {
12232
13346
  const result = sources.git(["rev-parse", "--abbrev-ref", "HEAD"]);
12233
13347
  if (!result.ok) {
12234
13348
  note(`the branch at the project root could not be read, so no branch names a plan or a pull request: ${gitSaid(result)}`);
@@ -12308,7 +13422,7 @@ function hasRun(records, stub) {
12308
13422
  }
12309
13423
  function hasBranch(refs, stub) {
12310
13424
  const branch = `${BRANCH_PREFIX2}${stub}`;
12311
- return refs.some((ref) => ref === branch || ref.endsWith(`/${branch}`));
13425
+ return refs.some((ref3) => ref3 === branch || ref3.endsWith(`/${branch}`));
12312
13426
  }
12313
13427
  function openWorld(sources) {
12314
13428
  let problems = [];
@@ -12317,7 +13431,7 @@ function openWorld(sources) {
12317
13431
  };
12318
13432
  const remote = sources.remote ?? REMOTE;
12319
13433
  const runs = once2(() => sources.runs());
12320
- const branch = once2(() => readBranch(sources, note));
13434
+ const branch = once2(() => readBranch2(sources, note));
12321
13435
  const plans = once2(() => readPlans(sources, note));
12322
13436
  const refs = once2(() => readRefs(sources, remote, note));
12323
13437
  const roadmap = once2(async () => {
@@ -13021,13 +14135,13 @@ function versionLine() {
13021
14135
  }
13022
14136
 
13023
14137
  // src/notices/notices.ts
13024
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync9 } from "fs";
13025
- import { dirname as dirname11, join as join16 } from "path";
14138
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
14139
+ import { dirname as dirname11, join as join17 } from "path";
13026
14140
  var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
13027
14141
  var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
13028
14142
  var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
13029
14143
  function noticesPath(home) {
13030
- return join16(home, ".rafa", "notices.json");
14144
+ return join17(home, ".rafa", "notices.json");
13031
14145
  }
13032
14146
  function isNoticeId(value) {
13033
14147
  return typeof value === "string" && NOTICE_IDS.includes(value);
@@ -13035,7 +14149,7 @@ function isNoticeId(value) {
13035
14149
  function readDismissed(home) {
13036
14150
  let raw;
13037
14151
  try {
13038
- raw = readFileSync21(noticesPath(home), "utf8");
14152
+ raw = readFileSync23(noticesPath(home), "utf8");
13039
14153
  } catch {
13040
14154
  return [];
13041
14155
  }
@@ -13055,7 +14169,7 @@ function writeDismissed(home, ids) {
13055
14169
  const all = NOTICE_IDS.filter((id2) => ids.includes(id2) || readDismissed(home).includes(id2));
13056
14170
  const path3 = noticesPath(home);
13057
14171
  mkdirSync8(dirname11(path3), { recursive: true });
13058
- writeFileSync9(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
14172
+ writeFileSync10(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
13059
14173
  `);
13060
14174
  }
13061
14175
  function pendingNotices(dismissed) {
@@ -13130,10 +14244,10 @@ async function requireNoticesAnswered() {
13130
14244
  }
13131
14245
 
13132
14246
  // src/start/branch.ts
13133
- var INDENT3 = " ";
14247
+ var INDENT4 = " ";
13134
14248
  var DEFAULT_BRANCH_SEAMS = Object.freeze({});
13135
- function refExists(git, ref) {
13136
- return git(["show-ref", "--verify", "--quiet", ref]).ok;
14249
+ function refExists(git, ref3) {
14250
+ return git(["show-ref", "--verify", "--quiet", ref3]).ok;
13137
14251
  }
13138
14252
  function terminalReading(seams) {
13139
14253
  return seams.isTerminal ?? (() => process.stdin.isTTY === true);
@@ -13166,8 +14280,8 @@ function readWorkingTree(git, plan) {
13166
14280
  return parseWorkingTree(result.stdout);
13167
14281
  throw new CommandExit(1, [
13168
14282
  `\u274C Could not read the working tree before leaving ${plan.base} for ${plan.branch}.`,
13169
- `${INDENT3}${gitSaid(result)}`,
13170
- `${INDENT3}The run is still on ${plan.base}.`
14283
+ `${INDENT4}${gitSaid(result)}`,
14284
+ `${INDENT4}The run is still on ${plan.base}.`
13171
14285
  ].join(`
13172
14286
  `));
13173
14287
  }
@@ -13188,9 +14302,9 @@ ${refused}`);
13188
14302
  if (outcome.kind === "refuse")
13189
14303
  throw new CommandExit(1, `
13190
14304
  ${outcome.message}`);
13191
- activeOutput().info(`${INDENT3}${step.label}: done`);
14305
+ activeOutput().info(`${INDENT4}${step.label}: done`);
13192
14306
  }
13193
- activeOutput().info(`${INDENT3}The run is on ${plan.branch}.`);
14307
+ activeOutput().info(`${INDENT4}The run is on ${plan.branch}.`);
13194
14308
  return Object.freeze({ kind: "moved", branch: plan.branch, route, steps });
13195
14309
  }
13196
14310
  async function offerRunBranch(request, seams = DEFAULT_BRANCH_SEAMS) {
@@ -14001,8 +15115,8 @@ function reportLessons(record, context, now) {
14001
15115
  }
14002
15116
 
14003
15117
  // src/effort/store/absences.ts
14004
- import { randomUUID as randomUUID4 } from "crypto";
14005
- import { existsSync as existsSync19 } from "fs";
15118
+ import { randomUUID as randomUUID5 } from "crypto";
15119
+ import { existsSync as existsSync21 } from "fs";
14006
15120
  var ABSENCE_REASONS = {
14007
15121
  "no-block": true,
14008
15122
  "unclosed-block": true,
@@ -14022,141 +15136,47 @@ function refusedWrite7(reason) {
14022
15136
  return new Error(`effort store: report absence write ${reason}; nothing written`);
14023
15137
  }
14024
15138
  function checkAbsence(absence) {
14025
- const { reason, text, block } = absence;
14026
- if (!REPORT_ABSENCE_REASONS.includes(reason)) {
14027
- const expected2 = REPORT_ABSENCE_REASONS.join(", ");
14028
- throw refusedWrite7(`has reason ${describeValue3(reason)}, not one of ${expected2}`);
14029
- }
14030
- if (typeof text !== "string") {
14031
- throw refusedWrite7(`has detail ${describeValue3(text)}, not a string`);
14032
- }
14033
- const detailProblem = textProblem(text);
14034
- if (detailProblem !== null)
14035
- throw refusedWrite7(`has a detail that ${detailProblem}`);
14036
- if (block === null)
14037
- return;
14038
- if (typeof block.body !== "string") {
14039
- throw refusedWrite7(`has block body ${describeValue3(block.body)}, not a string`);
14040
- }
14041
- if (LONE_SURROGATE.test(block.body)) {
14042
- throw refusedWrite7("has a block body holding a lone UTF-16 surrogate");
14043
- }
14044
- }
14045
- function writeReportAbsence(repoRoot, write, seams = {}) {
14046
- checkDispatch("report absence", write.dispatch, write.outcome);
14047
- checkAbsence(write.absence);
14048
- const path3 = sqliteStorePath(repoRoot);
14049
- const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
14050
- const { reason, text, block } = write.absence;
14051
- const collectedAt = (seams.now ?? (() => new Date))().toISOString();
14052
- const id2 = (seams.newId ?? randomUUID4)();
14053
- const values = [
14054
- id2,
14055
- sessionId,
14056
- planStub2,
14057
- taskLine,
14058
- reason,
14059
- text,
14060
- block?.body ?? null,
14061
- write.outcome,
14062
- collectedAt
14063
- ];
14064
- const appended2 = withSqliteStore(path3, !existsSync19(path3), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
14065
- return { path: path3, appended: appended2, skipped: 1 - appended2 };
14066
- }
14067
-
14068
- // src/effort/store/changes.ts
14069
- import { randomUUID as randomUUID5 } from "crypto";
14070
- import { existsSync as existsSync20 } from "fs";
14071
- var INSERT_CHANGE = `
14072
- INSERT INTO changes (
14073
- id, session_id, plan_stub, task_line,
14074
- level, area, summary,
14075
- collected_at
14076
- )
14077
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
14078
- ON CONFLICT (session_id, level, ifnull(area, ''), summary) DO NOTHING
14079
- `;
14080
- function optionalText(value) {
14081
- const problem = textProblem(value);
14082
- return problem === null ? null : { reason: "unstorable-field", problem };
14083
- }
14084
- function requiredText(value) {
14085
- return value === null ? { reason: "missing-field", problem: "is missing" } : optionalText(value);
14086
- }
14087
- function requiredLevel(value) {
14088
- if (value === null)
14089
- return { reason: "missing-field", problem: "is missing" };
14090
- if (CHANGE_LEVELS.includes(value))
14091
- return null;
14092
- const expected2 = CHANGE_LEVELS.join(", ");
14093
- return {
14094
- reason: "unstorable-field",
14095
- problem: `is ${describeValue3(value)}, not one of ${expected2}`
14096
- };
14097
- }
14098
- var FIELD_CHECKS2 = [
14099
- ["level", requiredLevel],
14100
- ["area", optionalText],
14101
- ["summary", requiredText]
14102
- ];
14103
- function rejectionOf2(change, index) {
14104
- for (const [field5, check] of FIELD_CHECKS2) {
14105
- const found = check(change[field5]);
14106
- if (found !== null) {
14107
- const text = `changes[${index}].${field5} ${found.problem}; not written`;
14108
- return { index, reason: found.reason, field: field5, text };
14109
- }
14110
- }
14111
- return null;
14112
- }
14113
- function rowValues2(change, dispatch, id2, collectedAt) {
14114
- const { sessionId, planStub: planStub2, taskLine } = dispatch;
14115
- const { level, area, summary } = change;
14116
- return [id2, sessionId, planStub2, taskLine, level, area, summary, collectedAt];
14117
- }
14118
- function insertRows3(db, rows) {
14119
- const insert = db.query(INSERT_CHANGE);
14120
- const insertAll = db.transaction(() => rows.reduce((appended2, values) => appended2 + insert.run(...values).changes, 0));
14121
- return insertAll.immediate();
14122
- }
14123
- function writeChanges(repoRoot, write, seams = {}) {
14124
- checkDispatch("changes", write.dispatch, null);
14125
- const path3 = sqliteStorePath(repoRoot);
14126
- const rejected = [];
14127
- const stored = [];
14128
- for (const [index, change] of write.changes.entries()) {
14129
- const rejection = rejectionOf2(change, index);
14130
- if (rejection === null)
14131
- stored.push(change);
14132
- else
14133
- rejected.push(rejection);
15139
+ const { reason, text, block } = absence;
15140
+ if (!REPORT_ABSENCE_REASONS.includes(reason)) {
15141
+ const expected2 = REPORT_ABSENCE_REASONS.join(", ");
15142
+ throw refusedWrite7(`has reason ${describeValue3(reason)}, not one of ${expected2}`);
15143
+ }
15144
+ if (typeof text !== "string") {
15145
+ throw refusedWrite7(`has detail ${describeValue3(text)}, not a string`);
15146
+ }
15147
+ const detailProblem = textProblem(text);
15148
+ if (detailProblem !== null)
15149
+ throw refusedWrite7(`has a detail that ${detailProblem}`);
15150
+ if (block === null)
15151
+ return;
15152
+ if (typeof block.body !== "string") {
15153
+ throw refusedWrite7(`has block body ${describeValue3(block.body)}, not a string`);
15154
+ }
15155
+ if (LONE_SURROGATE.test(block.body)) {
15156
+ throw refusedWrite7("has a block body holding a lone UTF-16 surrogate");
14134
15157
  }
14135
- const collectedAt = (seams.now ?? (() => new Date))().toISOString();
14136
- const newId = seams.newId ?? randomUUID5;
14137
- const rows = stored.map((change) => rowValues2(change, write.dispatch, newId(), collectedAt));
14138
- const appended2 = writeSqliteStore(path3, rows.length, 0, (db) => insertRows3(db, rows));
14139
- return { path: path3, appended: appended2, skipped: stored.length - appended2, rejected };
14140
15158
  }
14141
- var SELECT_PLAN_CHANGES = `
14142
- SELECT session_id, task_line, level, area, summary, collected_at
14143
- FROM changes
14144
- WHERE plan_stub IS ?
14145
- ORDER BY seq
14146
- `;
14147
- function readPlanChanges(repoRoot, planStub2) {
15159
+ function writeReportAbsence(repoRoot, write, seams = {}) {
15160
+ checkDispatch("report absence", write.dispatch, write.outcome);
15161
+ checkAbsence(write.absence);
14148
15162
  const path3 = sqliteStorePath(repoRoot);
14149
- if (!existsSync20(path3))
14150
- return [];
14151
- const rows = withSqliteStore(path3, false, (db) => db.query(SELECT_PLAN_CHANGES).all(planStub2));
14152
- return rows.map((row) => ({
14153
- sessionId: row.session_id,
14154
- taskLine: row.task_line,
14155
- level: row.level,
14156
- area: row.area,
14157
- summary: row.summary,
14158
- collectedAt: row.collected_at
14159
- }));
15163
+ const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
15164
+ const { reason, text, block } = write.absence;
15165
+ const collectedAt = (seams.now ?? (() => new Date))().toISOString();
15166
+ const id2 = (seams.newId ?? randomUUID5)();
15167
+ const values = [
15168
+ id2,
15169
+ sessionId,
15170
+ planStub2,
15171
+ taskLine,
15172
+ reason,
15173
+ text,
15174
+ block?.body ?? null,
15175
+ write.outcome,
15176
+ collectedAt
15177
+ ];
15178
+ const appended2 = withSqliteStore(path3, !existsSync21(path3), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
15179
+ return { path: path3, appended: appended2, skipped: 1 - appended2 };
14160
15180
  }
14161
15181
 
14162
15182
  // src/effort/store/triage.ts
@@ -14416,8 +15436,8 @@ function renderLessonsSection(lessons) {
14416
15436
  }
14417
15437
 
14418
15438
  // src/utils/progress.ts
14419
- import { existsSync as existsSync21, writeFileSync as writeFileSync10 } from "fs";
14420
- import { join as join17 } from "path";
15439
+ import { existsSync as existsSync22, writeFileSync as writeFileSync11 } from "fs";
15440
+ import { join as join18 } from "path";
14421
15441
  var PROGRESS_FILE_NAME = "progress.txt";
14422
15442
  var PROGRESS_CAP_BYTES = 16000;
14423
15443
  var LISTED_FIELDS = [
@@ -14459,7 +15479,7 @@ var ESCAPED_CODE_POINTS = new Set([
14459
15479
  8233
14460
15480
  ]);
14461
15481
  function progressFilePath(repoRoot) {
14462
- return join17(repoRoot, PROGRESS_FILE_NAME);
15482
+ return join18(repoRoot, PROGRESS_FILE_NAME);
14463
15483
  }
14464
15484
  function escapeValue(value) {
14465
15485
  let escaped = "";
@@ -14520,7 +15540,7 @@ function renderProgressText(findings, capBytes = PROGRESS_CAP_BYTES) {
14520
15540
  }
14521
15541
  function readProgressFindings(repoRoot, planStub2) {
14522
15542
  const path3 = sqliteStorePath(repoRoot);
14523
- if (!existsSync21(path3))
15543
+ if (!existsSync22(path3))
14524
15544
  return [];
14525
15545
  const rows = withSqliteStore(path3, false, (db) => {
14526
15546
  const query = db.query(SELECT_FINDINGS2);
@@ -14541,12 +15561,12 @@ function readProgressFindings(repoRoot, planStub2) {
14541
15561
  function writeProgress(repoRoot, planStub2) {
14542
15562
  const render = renderProgressText(readProgressFindings(repoRoot, planStub2));
14543
15563
  const path3 = progressFilePath(repoRoot);
14544
- writeFileSync10(path3, render.text, "utf8");
15564
+ writeFileSync11(path3, render.text, "utf8");
14545
15565
  return { ...render, path: path3, storePath: sqliteStorePath(repoRoot) };
14546
15566
  }
14547
15567
 
14548
15568
  // src/inventory/search/rank.ts
14549
- import { readFileSync as readFileSync22 } from "fs";
15569
+ import { readFileSync as readFileSync24 } from "fs";
14550
15570
  var SEARCH_LIMIT = 12;
14551
15571
  var MIN_WORD_LENGTH = 2;
14552
15572
  var MIN_PREFIX_LENGTH = 4;
@@ -14668,7 +15688,7 @@ function rankCandidates(question, candidates, limit = SEARCH_LIMIT) {
14668
15688
  return [];
14669
15689
  return candidates.map((candidate) => scoreCandidate(candidate, words)).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
14670
15690
  }
14671
- function readRankCandidate(record, read = (path3) => readFileSync22(path3, "utf8")) {
15691
+ function readRankCandidate(record, read = (path3) => readFileSync24(path3, "utf8")) {
14672
15692
  let text;
14673
15693
  try {
14674
15694
  text = read(record.path);
@@ -14897,16 +15917,16 @@ async function handOut(handout, task, resolution, repoRoot) {
14897
15917
 
14898
15918
  // src/start/preflight.ts
14899
15919
  import { randomUUID as randomUUID7 } from "crypto";
14900
- import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
15920
+ import { existsSync as existsSync23, readFileSync as readFileSync26 } from "fs";
14901
15921
  import { homedir as homedir4 } from "os";
14902
15922
  import { basename as basename7 } from "path";
14903
15923
 
14904
15924
  // src/preflight/first-dispatch.ts
14905
- import { readFileSync as readFileSync23 } from "fs";
15925
+ import { readFileSync as readFileSync25 } from "fs";
14906
15926
  var TICKED_TASK = /^- \[[xX]\] (.+)/;
14907
15927
  function readIfReadable(path3) {
14908
15928
  try {
14909
- return readFileSync23(path3, "utf8");
15929
+ return readFileSync25(path3, "utf8");
14910
15930
  } catch {
14911
15931
  return null;
14912
15932
  }
@@ -14939,11 +15959,11 @@ function knownMissingNotice(lines) {
14939
15959
  }
14940
15960
  function agentSourcePath(planPath) {
14941
15961
  const tracker = trackerPathFor(planPath);
14942
- return existsSync22(tracker) ? tracker : planPath;
15962
+ return existsSync23(tracker) ? tracker : planPath;
14943
15963
  }
14944
15964
  function readIfReadable2(path3) {
14945
15965
  try {
14946
- return readFileSync24(path3, "utf8");
15966
+ return readFileSync26(path3, "utf8");
14947
15967
  } catch {
14948
15968
  return null;
14949
15969
  }
@@ -15332,14 +16352,14 @@ async function holdWhilePaused(options) {
15332
16352
  }
15333
16353
 
15334
16354
  // src/start/plan-path.ts
15335
- import { existsSync as existsSync23 } from "fs";
16355
+ import { existsSync as existsSync24 } from "fs";
15336
16356
  import { resolve as resolve11 } from "path";
15337
16357
  var DEFAULT_PLAN_FILE = "PLAN.md";
15338
16358
  function resolvePlanPath(repoRoot, planDir, planArg) {
15339
16359
  if (planArg)
15340
16360
  return resolve11(repoRoot, planArg);
15341
16361
  const inPlanDir = resolve11(repoRoot, planDir, DEFAULT_PLAN_FILE);
15342
- return existsSync23(inPlanDir) ? inPlanDir : resolve11(repoRoot, DEFAULT_PLAN_FILE);
16362
+ return existsSync24(inPlanDir) ? inPlanDir : resolve11(repoRoot, DEFAULT_PLAN_FILE);
15343
16363
  }
15344
16364
 
15345
16365
  // src/utils/git.ts
@@ -15462,250 +16482,68 @@ function pollChecks(io, prNumber, timeoutMs) {
15462
16482
  now: io.now,
15463
16483
  sleep: io.sleep,
15464
16484
  onPoll: (rows, verdict, elapsedMs) => {
15465
- const secs = Math.round(elapsedMs / 1000);
15466
- activeOutput().info(` [${secs}s] ${verdict} \u2014 ${rows.length} check(s)`);
15467
- }
15468
- });
15469
- }
15470
- function reportSettledVerdict(prNumber, result) {
15471
- if (result.verdict === "green") {
15472
- activeOutput().info(`
15473
- \u2705 CI green on PR #${prNumber}:`);
15474
- activeOutput().info(formatRows(result.rows));
15475
- return true;
15476
- }
15477
- if (result.verdict === "timeout") {
15478
- activeOutput().warn(`
15479
- \u26A0\uFE0F CI still running after ${Math.round(result.elapsedMs / 1000)}s. Not waiting further.`);
15480
- activeOutput().warn(formatRows(result.rows));
15481
- activeOutput().warn(` Check it yourself: gh pr checks ${prNumber}`);
15482
- return true;
15483
- }
15484
- return false;
15485
- }
15486
- async function handleNoChecks(io, branch, prNumber, merge) {
15487
- if (merge?.state === "merged") {
15488
- activeOutput().info(`
15489
- \u2705 PR #${prNumber} is already merged.`);
15490
- return "stop";
15491
- }
15492
- if (merge !== null && merge.mergeStateStatus !== "DIRTY") {
15493
- activeOutput().warn(`
15494
- \u26A0\uFE0F PR #${prNumber} reports no checks and is not conflicting`);
15495
- activeOutput().warn(` (GitHub says it is ${merge.mergeable}, merge state ${merge.mergeStateStatus}).`);
15496
- activeOutput().warn(" Most likely no workflow matches the changed paths. Nothing to repair.");
15497
- return "stop";
15498
- }
15499
- activeOutput().warn(`
15500
- \u274C PR #${prNumber} has no checks \u2014 it does not merge cleanly, so GitHub scheduled no run.`);
15501
- const exitCode = await repairPullRequest(prNumber, branch, "it conflicts with the base branch, so GitHub scheduled no CI run at all.", "Merge `origin/main` into this branch and resolve the conflicts, then push. Mechanical conflicts (versions, lockfiles, complementary additions) are yours to resolve; a genuine semantic conflict is not.", io.runRepair);
15502
- if (exitCode !== 0) {
15503
- activeOutput().error(`
15504
- \u274C Conflict-repair session failed (exit ${exitCode}).`);
15505
- return "stop";
15506
- }
15507
- return "next";
15508
- }
15509
- async function handleRedChecks(io, branch, prNumber, rows) {
15510
- activeOutput().warn(`
15511
- \u274C CI red on PR #${prNumber}:`);
15512
- activeOutput().warn(formatRows(rows));
15513
- const failed3 = failingRows(rows);
15514
- const exitCode = await repairPullRequest(prNumber, branch, "its CI checks failed.", ["The failing checks are:", formatRows(failed3)].join(`
15515
- `), io.runRepair);
15516
- if (exitCode !== 0) {
15517
- activeOutput().error(`
15518
- \u274C CI-repair session failed (exit ${exitCode}).`);
15519
- return "stop";
15520
- }
15521
- return "next";
15522
- }
15523
-
15524
- // src/release/prepare.ts
15525
- import { readFileSync as readFileSync26, writeFileSync as writeFileSync12 } from "fs";
15526
-
15527
- // src/release/changelog.ts
15528
- var RELEASE_HEADING_FIELDS = ["version", "date", "title"];
15529
- var PLACEHOLDER = /\{([A-Za-z]+)\}/g;
15530
- var WHITESPACE = /\s+/g;
15531
- var ATX_HEADING = /^ {0,3}#{1,6}(?: |$)/;
15532
- var CODE_FENCE = /^ {0,3}(?:```|~~~)/;
15533
- var SEPARATORS = "\u2014\u2013-,;:\xB7|";
15534
- function isSeparator(character) {
15535
- return character.trim() === "" || SEPARATORS.includes(character);
15536
- }
15537
- function trimTrailingSeparators(text) {
15538
- let end = text.length;
15539
- while (end > 0 && isSeparator(text[end - 1] ?? ""))
15540
- end -= 1;
15541
- return text.slice(0, end);
15542
- }
15543
- function trimLeadingSeparators(text) {
15544
- let start = 0;
15545
- while (start < text.length && isSeparator(text[start] ?? ""))
15546
- start += 1;
15547
- return text.slice(start);
15548
- }
15549
- function isHeadingField(field5) {
15550
- return RELEASE_HEADING_FIELDS.includes(field5);
15551
- }
15552
- function renderReleaseHeading(template, values) {
15553
- let rendered = "";
15554
- let afterGap = false;
15555
- const add = (text) => {
15556
- if (!afterGap) {
15557
- rendered += text;
15558
- return;
15559
- }
15560
- const kept = trimLeadingSeparators(text);
15561
- if (kept === "")
15562
- return;
15563
- afterGap = false;
15564
- rendered += kept;
15565
- };
15566
- let cursor = 0;
15567
- for (const found of template.matchAll(PLACEHOLDER)) {
15568
- const before = template.slice(cursor, found.index);
15569
- cursor = found.index + found[0].length;
15570
- const name2 = found[1] ?? "";
15571
- if (!isHeadingField(name2)) {
15572
- add(`${before}${found[0]}`);
15573
- continue;
15574
- }
15575
- const value = values[name2].trim();
15576
- if (value !== "") {
15577
- add(before);
15578
- add(value);
15579
- continue;
15580
- }
15581
- add(before);
15582
- rendered = `${trimTrailingSeparators(rendered)} `;
15583
- afterGap = true;
15584
- }
15585
- add(template.slice(cursor));
15586
- return rendered.replace(WHITESPACE, " ").trim();
15587
- }
15588
- function areaOf(note) {
15589
- const area = note.area?.trim() ?? "";
15590
- return area === "" ? null : area;
15591
- }
15592
- function groupChangeNotes(notes) {
15593
- const named = new Map;
15594
- const unnamed = [];
15595
- const seen = new Set;
15596
- let noneNotes = 0;
15597
- let duplicateNotes = 0;
15598
- let blankNotes = 0;
15599
- for (const note of notes) {
15600
- if (note.level === "none") {
15601
- noneNotes += 1;
15602
- continue;
15603
- }
15604
- const area = areaOf(note);
15605
- const summary = note.summary.replace(WHITESPACE, " ").trim();
15606
- if (summary === "") {
15607
- blankNotes += 1;
15608
- continue;
15609
- }
15610
- const key = `${area ?? ""}
15611
- ${summary}`;
15612
- if (seen.has(key)) {
15613
- duplicateNotes += 1;
15614
- continue;
15615
- }
15616
- seen.add(key);
15617
- if (area === null) {
15618
- unnamed.push(summary);
15619
- continue;
15620
- }
15621
- const summaries = named.get(area);
15622
- if (summaries === undefined)
15623
- named.set(area, [summary]);
15624
- else
15625
- summaries.push(summary);
15626
- }
15627
- const groups = [...named].map(([area, summaries]) => ({ area, summaries }));
15628
- return {
15629
- groups: unnamed.length === 0 ? groups : [...groups, { area: null, summaries: unnamed }],
15630
- noneNotes,
15631
- duplicateNotes,
15632
- blankNotes
15633
- };
15634
- }
15635
- function renderNoteLines(grouping) {
15636
- return grouping.groups.flatMap((group) => group.summaries.map((summary) => group.area === null ? `- ${summary}` : `- ${group.area}: ${summary}`));
15637
- }
15638
- function renderChangelogEntry(input) {
15639
- const heading = renderReleaseHeading(input.template, input.values);
15640
- const grouping = groupChangeNotes(input.notes);
15641
- const lines = renderNoteLines(grouping);
15642
- return {
15643
- heading,
15644
- lines,
15645
- groups: grouping.groups,
15646
- text: lines.length === 0 ? heading : `${heading}
15647
-
15648
- ${lines.join(`
15649
- `)}`,
15650
- noneNotes: grouping.noneNotes,
15651
- duplicateNotes: grouping.duplicateNotes,
15652
- blankNotes: grouping.blankNotes
15653
- };
15654
- }
15655
- function findHeading(lines, from) {
15656
- let fenced = false;
15657
- for (let index = 0;index < lines.length; index += 1) {
15658
- const line = lines[index] ?? "";
15659
- if (CODE_FENCE.test(line)) {
15660
- fenced = !fenced;
15661
- continue;
16485
+ const secs = Math.round(elapsedMs / 1000);
16486
+ activeOutput().info(` [${secs}s] ${verdict} \u2014 ${rows.length} check(s)`);
15662
16487
  }
15663
- if (fenced || index < from)
15664
- continue;
15665
- if (ATX_HEADING.test(line))
15666
- return index;
15667
- }
15668
- return null;
15669
- }
15670
- function endOfContent(lines) {
15671
- let end = lines.length;
15672
- while (end > 0 && (lines[end - 1] ?? "").trim() === "")
15673
- end -= 1;
15674
- return end;
16488
+ });
15675
16489
  }
15676
- function insertPoint(lines) {
15677
- const first = findHeading(lines, 0);
15678
- if (first === null)
15679
- return { at: 0, point: "file-top" };
15680
- const next = findHeading(lines, first + 1);
15681
- if (next !== null)
15682
- return { at: next, point: "before-next-heading" };
15683
- return { at: endOfContent(lines), point: "file-end" };
16490
+ function reportSettledVerdict(prNumber, result) {
16491
+ if (result.verdict === "green") {
16492
+ activeOutput().info(`
16493
+ \u2705 CI green on PR #${prNumber}:`);
16494
+ activeOutput().info(formatRows(result.rows));
16495
+ return true;
16496
+ }
16497
+ if (result.verdict === "timeout") {
16498
+ activeOutput().warn(`
16499
+ \u26A0\uFE0F CI still running after ${Math.round(result.elapsedMs / 1000)}s. Not waiting further.`);
16500
+ activeOutput().warn(formatRows(result.rows));
16501
+ activeOutput().warn(` Check it yourself: gh pr checks ${prNumber}`);
16502
+ return true;
16503
+ }
16504
+ return false;
15684
16505
  }
15685
- function insertChangelogEntry(changelog, entry) {
15686
- const lines = changelog.split(`
15687
- `);
15688
- const { at, point } = insertPoint(lines);
15689
- const entryLines = entry.split(`
15690
- `);
15691
- const before = at === 0 || (lines[at - 1] ?? "").trim() === "" ? [] : [""];
15692
- const after = at >= lines.length || (lines[at] ?? "").trim() === "" ? [] : [""];
15693
- const inserted = [
15694
- ...lines.slice(0, at),
15695
- ...before,
15696
- ...entryLines,
15697
- ...after,
15698
- ...lines.slice(at)
15699
- ];
15700
- return { text: inserted.join(`
15701
- `), point, line: at + before.length + 1 };
16506
+ async function handleNoChecks(io, branch, prNumber, merge) {
16507
+ if (merge?.state === "merged") {
16508
+ activeOutput().info(`
16509
+ \u2705 PR #${prNumber} is already merged.`);
16510
+ return "stop";
16511
+ }
16512
+ if (merge !== null && merge.mergeStateStatus !== "DIRTY") {
16513
+ activeOutput().warn(`
16514
+ \u26A0\uFE0F PR #${prNumber} reports no checks and is not conflicting`);
16515
+ activeOutput().warn(` (GitHub says it is ${merge.mergeable}, merge state ${merge.mergeStateStatus}).`);
16516
+ activeOutput().warn(" Most likely no workflow matches the changed paths. Nothing to repair.");
16517
+ return "stop";
16518
+ }
16519
+ activeOutput().warn(`
16520
+ \u274C PR #${prNumber} has no checks \u2014 it does not merge cleanly, so GitHub scheduled no run.`);
16521
+ const exitCode = await repairPullRequest(prNumber, branch, "it conflicts with the base branch, so GitHub scheduled no CI run at all.", "Merge `origin/main` into this branch and resolve the conflicts, then push. Mechanical conflicts (versions, lockfiles, complementary additions) are yours to resolve; a genuine semantic conflict is not.", io.runRepair);
16522
+ if (exitCode !== 0) {
16523
+ activeOutput().error(`
16524
+ \u274C Conflict-repair session failed (exit ${exitCode}).`);
16525
+ return "stop";
16526
+ }
16527
+ return "next";
15702
16528
  }
15703
- function changelogDate(now) {
15704
- const month = `${now.getMonth() + 1}`.padStart(2, "0");
15705
- const day = `${now.getDate()}`.padStart(2, "0");
15706
- return `${now.getFullYear()}-${month}-${day}`;
16529
+ async function handleRedChecks(io, branch, prNumber, rows) {
16530
+ activeOutput().warn(`
16531
+ \u274C CI red on PR #${prNumber}:`);
16532
+ activeOutput().warn(formatRows(rows));
16533
+ const failed3 = failingRows(rows);
16534
+ const exitCode = await repairPullRequest(prNumber, branch, "its CI checks failed.", ["The failing checks are:", formatRows(failed3)].join(`
16535
+ `), io.runRepair);
16536
+ if (exitCode !== 0) {
16537
+ activeOutput().error(`
16538
+ \u274C CI-repair session failed (exit ${exitCode}).`);
16539
+ return "stop";
16540
+ }
16541
+ return "next";
15707
16542
  }
15708
16543
 
16544
+ // src/release/prepare.ts
16545
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync12 } from "fs";
16546
+
15709
16547
  // src/release/enabled.ts
15710
16548
  import { statSync as statSync11 } from "fs";
15711
16549
  import { resolve as resolve12 } from "path";
@@ -15734,165 +16572,6 @@ function resolveReleaseEnabled(settings, repoRoot) {
15734
16572
  return { enabled: settings.releaseEnabled, source: "config", versionFile, changelog };
15735
16573
  }
15736
16574
 
15737
- // src/release/level.ts
15738
- var RELEASE_LEVEL_RANK = {
15739
- none: 0,
15740
- patch: 1,
15741
- minor: 2,
15742
- major: 3
15743
- };
15744
- function highestChangeLevel(notes) {
15745
- return notes.reduce((highest, note) => highest === null || RELEASE_LEVEL_RANK[note.level] > RELEASE_LEVEL_RANK[highest] ? note.level : highest, null);
15746
- }
15747
- function resolveReleaseLevel(declared, notes) {
15748
- const notesLevel = highestChangeLevel(notes);
15749
- if (declared !== null)
15750
- return { level: declared, source: "plan", notesLevel };
15751
- if (notesLevel !== null)
15752
- return { level: notesLevel, source: "notes", notesLevel };
15753
- return { level: "none", source: "default", notesLevel };
15754
- }
15755
-
15756
- // src/release/version.ts
15757
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync11 } from "fs";
15758
- var RELEASE_REMOTE = "origin";
15759
- var RELEASE_BASE_BRANCH = "main";
15760
- var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
15761
- function parseSemanticVersion(raw) {
15762
- const found = VERSION_SHAPE.exec(raw);
15763
- if (found === null)
15764
- return null;
15765
- return {
15766
- major: Number(found[1]),
15767
- minor: Number(found[2]),
15768
- patch: Number(found[3]),
15769
- prerelease: found[4] ?? "",
15770
- build: found[5] ?? ""
15771
- };
15772
- }
15773
- function formatSemanticVersion(version2) {
15774
- const prerelease = version2.prerelease === "" ? "" : `-${version2.prerelease}`;
15775
- const build = version2.build === "" ? "" : `+${version2.build}`;
15776
- return `${version2.major}.${version2.minor}.${version2.patch}${prerelease}${build}`;
15777
- }
15778
- function bumped(version2, level) {
15779
- const onPrerelease = version2.prerelease !== "";
15780
- const blank = { prerelease: "", build: "" };
15781
- if (level === "major") {
15782
- const stays = onPrerelease && version2.minor === 0 && version2.patch === 0;
15783
- const major = stays ? version2.major : version2.major + 1;
15784
- return { major, minor: 0, patch: 0, ...blank };
15785
- }
15786
- if (level === "minor") {
15787
- const stays = onPrerelease && version2.patch === 0;
15788
- const minor = stays ? version2.minor : version2.minor + 1;
15789
- return { major: version2.major, minor, patch: 0, ...blank };
15790
- }
15791
- const patch = onPrerelease ? version2.patch : version2.patch + 1;
15792
- return { major: version2.major, minor: version2.minor, patch, ...blank };
15793
- }
15794
- function nextVersion(base, level) {
15795
- const parsed = parseSemanticVersion(base);
15796
- if (parsed === null)
15797
- return null;
15798
- if (level === "none")
15799
- return formatSemanticVersion(parsed);
15800
- return formatSemanticVersion(bumped(parsed, level));
15801
- }
15802
- function manifestFields(text) {
15803
- let parsed;
15804
- try {
15805
- parsed = JSON.parse(text);
15806
- } catch {
15807
- return null;
15808
- }
15809
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
15810
- return null;
15811
- return parsed;
15812
- }
15813
- function readManifestVersion(text) {
15814
- const fields = manifestFields(text);
15815
- if (fields === null)
15816
- return null;
15817
- const value = fields["version"];
15818
- if (typeof value !== "string")
15819
- return null;
15820
- const trimmed = value.trim();
15821
- return trimmed === "" ? null : trimmed;
15822
- }
15823
- var VERSION_PAIR = /("version"\s*:\s*)"(?:[^"\\]|\\.)*"/g;
15824
- function sameBesidesVersion(before, after) {
15825
- const restored = { ...after, version: before["version"] };
15826
- return JSON.stringify(restored) === JSON.stringify(before);
15827
- }
15828
- function replaceManifestVersion(text, next) {
15829
- const previous = readManifestVersion(text);
15830
- const before = manifestFields(text);
15831
- if (previous === null || before === null)
15832
- return null;
15833
- for (const pair of text.matchAll(VERSION_PAIR)) {
15834
- const start = pair.index;
15835
- const candidate = `${text.slice(0, start)}${pair[1]}${JSON.stringify(next)}${text.slice(start + pair[0].length)}`;
15836
- const after = manifestFields(candidate);
15837
- if (after === null || after["version"] !== next)
15838
- continue;
15839
- if (!sameBesidesVersion(before, after))
15840
- continue;
15841
- return { text: candidate, previous };
15842
- }
15843
- return null;
15844
- }
15845
- function writeManifestVersion(path3, next) {
15846
- const unwritten = { path: path3, written: false, previous: null, version: next };
15847
- let text;
15848
- try {
15849
- text = readFileSync25(path3, "utf8");
15850
- } catch (error) {
15851
- return { ...unwritten, problem: `${path3} could not be read: ${messageOf(error)}` };
15852
- }
15853
- const rewritten = replaceManifestVersion(text, next);
15854
- if (rewritten === null) {
15855
- const previous = readManifestVersion(text);
15856
- return {
15857
- ...unwritten,
15858
- previous,
15859
- problem: previous === null ? `${path3} declares no version to write ${next} over` : `${path3} declares ${previous}, and the pair holding it could not be rewritten`
15860
- };
15861
- }
15862
- try {
15863
- writeFileSync11(path3, rewritten.text);
15864
- } catch (error) {
15865
- return { ...unwritten, previous: rewritten.previous, problem: `${path3} could not be written: ${messageOf(error)}` };
15866
- }
15867
- return { path: path3, written: true, previous: rewritten.previous, version: next, problem: null };
15868
- }
15869
- function gitPathOf(versionFile) {
15870
- let path3 = versionFile;
15871
- while (path3.startsWith("./"))
15872
- path3 = path3.slice(2);
15873
- return path3;
15874
- }
15875
- function readBaseVersion(git, versionFile, options = {}) {
15876
- const remote = options.remote ?? RELEASE_REMOTE;
15877
- const branch = options.branch ?? RELEASE_BASE_BRANCH;
15878
- const ref = `${remote}/${branch}`;
15879
- const path3 = gitPathOf(versionFile);
15880
- const problems = [];
15881
- const fetched = git(["fetch", remote, branch]);
15882
- if (!fetched.ok) {
15883
- problems.push(`${branch} could not be fetched from ${remote}, so the base version is whatever this clone already holds for ${ref}: ${gitSaid(fetched)}`);
15884
- }
15885
- const shown = git(["show", `${ref}:${path3}`]);
15886
- if (!shown.ok) {
15887
- problems.push(`${ref}:${path3} could not be read: ${gitSaid(shown)}`);
15888
- return { ref, path: path3, version: null, fetched: fetched.ok, problems };
15889
- }
15890
- const version2 = readManifestVersion(shown.stdout);
15891
- if (version2 === null)
15892
- problems.push(`${ref}:${path3} declares no version`);
15893
- return { ref, path: path3, version: version2, fetched: fetched.ok, problems };
15894
- }
15895
-
15896
16575
  // src/release/prepare.ts
15897
16576
  function levelFields(reading2) {
15898
16577
  return { level: reading2.level, levelSource: reading2.source, notesLevel: reading2.notesLevel };
@@ -15947,9 +16626,9 @@ function planVersion(input, versionFile, level) {
15947
16626
  }
15948
16627
  return { refused: false, version: version2, baseVersion: base.version, fetched: base.fetched, problems: base.problems };
15949
16628
  }
15950
- function textOf(path3) {
16629
+ function textOf2(path3) {
15951
16630
  try {
15952
- return readFileSync26(path3, "utf8");
16631
+ return readFileSync27(path3, "utf8");
15953
16632
  } catch {
15954
16633
  return null;
15955
16634
  }
@@ -15963,14 +16642,14 @@ function writeText(path3, text) {
15963
16642
  }
15964
16643
  }
15965
16644
  function writeVersionFile(file, version2) {
15966
- const before = textOf(file.resolved);
16645
+ const before = textOf2(file.resolved);
15967
16646
  if (before === null) {
15968
16647
  return { edit: null, problem: `${file.path} could not be read, so ${version2} was not written to it` };
15969
16648
  }
15970
16649
  const written = writeManifestVersion(file.resolved, version2);
15971
16650
  if (written.problem !== null)
15972
16651
  return { edit: null, problem: written.problem };
15973
- const after = textOf(file.resolved);
16652
+ const after = textOf2(file.resolved);
15974
16653
  if (after === null) {
15975
16654
  return { edit: null, problem: `${file.path} could not be read back after ${version2} was written to it` };
15976
16655
  }
@@ -16011,7 +16690,7 @@ function prepareRelease(input) {
16011
16690
  if (!files.changelog.present) {
16012
16691
  return skip("changelog-missing", `no version bump and no changelog entry: ${files.changelog.path} is not there`, reading2);
16013
16692
  }
16014
- const changelogBefore = textOf(files.changelog.resolved);
16693
+ const changelogBefore = textOf2(files.changelog.resolved);
16015
16694
  if (changelogBefore === null) {
16016
16695
  return skip("changelog-unreadable", `no version bump and no changelog entry: ${files.changelog.path} could not be read`, reading2);
16017
16696
  }
@@ -16062,7 +16741,7 @@ function changelogFailure(failed3, versionEdit, restored) {
16062
16741
  }
16063
16742
 
16064
16743
  // src/release/verify.ts
16065
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync13 } from "fs";
16744
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "fs";
16066
16745
  function changelogInsertionSpan(before, after) {
16067
16746
  const beforeLines = before.split(`
16068
16747
  `);
@@ -16077,9 +16756,9 @@ function changelogInsertionSpan(before, after) {
16077
16756
  suffix += 1;
16078
16757
  return { prefix, suffix, inserted: afterLines.slice(prefix, afterLines.length - suffix) };
16079
16758
  }
16080
- function textOf2(path3) {
16759
+ function textOf3(path3) {
16081
16760
  try {
16082
- return readFileSync27(path3, "utf8");
16761
+ return readFileSync28(path3, "utf8");
16083
16762
  } catch {
16084
16763
  return null;
16085
16764
  }
@@ -16147,7 +16826,7 @@ function checkVersion(prepared) {
16147
16826
  const file = prepared.versionFile;
16148
16827
  if (file === null)
16149
16828
  return null;
16150
- const text = textOf2(file.resolved);
16829
+ const text = textOf3(file.resolved);
16151
16830
  if (text === null) {
16152
16831
  return { reason: "version-unreadable", because: `${file.path} could not be read back after the wrap-up session` };
16153
16832
  }
@@ -16165,7 +16844,7 @@ function checkVersion(prepared) {
16165
16844
  }
16166
16845
  function verifyRelease(prepared) {
16167
16846
  const changelog = prepared.changelog;
16168
- const current = textOf2(changelog.resolved);
16847
+ const current = textOf3(changelog.resolved);
16169
16848
  if (current === null) {
16170
16849
  return refuse5(prepared, "changelog-unreadable", `${changelog.path} could not be read back after the wrap-up session`);
16171
16850
  }
@@ -16414,11 +17093,11 @@ async function finishRelease(input, seams = {}) {
16414
17093
  }
16415
17094
 
16416
17095
  // src/start/risk-total.ts
16417
- import { readFileSync as readFileSync29 } from "fs";
17096
+ import { readFileSync as readFileSync30 } from "fs";
16418
17097
 
16419
17098
  // src/plan/risk.ts
16420
- import { readFileSync as readFileSync28 } from "fs";
16421
- import { isAbsolute as isAbsolute4, join as join18, relative as relative4 } from "path";
17099
+ import { readFileSync as readFileSync29 } from "fs";
17100
+ import { isAbsolute as isAbsolute4, join as join19, relative as relative4 } from "path";
16422
17101
 
16423
17102
  // src/plan/risk/accounts.ts
16424
17103
  var PUSH_REMOTE = "origin";
@@ -16960,7 +17639,7 @@ function toolsFinding(task, plan, seams) {
16960
17639
  return null;
16961
17640
  }
16962
17641
  function displayPath(path3, seams) {
16963
- const absolute = isAbsolute4(path3) ? path3 : join18(seams.repoRoot, path3);
17642
+ const absolute = isAbsolute4(path3) ? path3 : join19(seams.repoRoot, path3);
16964
17643
  const within = (root) => {
16965
17644
  const step = relative4(root, absolute);
16966
17645
  return step === "" || step.startsWith("..") || isAbsolute4(step) ? null : step;
@@ -16977,7 +17656,7 @@ function located(finding2, file, task) {
16977
17656
  }
16978
17657
  function readText5(path3) {
16979
17658
  try {
16980
- return readFileSync28(path3, "utf8");
17659
+ return readFileSync29(path3, "utf8");
16981
17660
  } catch {
16982
17661
  return null;
16983
17662
  }
@@ -16985,7 +17664,7 @@ function readText5(path3) {
16985
17664
  function findSkillFile(name2, seams) {
16986
17665
  const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
16987
17666
  for (const { dir } of tiers) {
16988
- const path3 = join18(dir, name2, "SKILL.md");
17667
+ const path3 = join19(dir, name2, "SKILL.md");
16989
17668
  if (readText5(path3) !== null)
16990
17669
  return path3;
16991
17670
  }
@@ -17079,7 +17758,7 @@ function spawningRunners(root) {
17079
17758
  }
17080
17759
  async function readRiskTotalLine(input, seams = {}) {
17081
17760
  const makeRunners = seams.runners ?? spawningRunners;
17082
- const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync29(input.planPath, "utf8") }, {
17761
+ const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync30(input.planPath, "utf8") }, {
17083
17762
  repoRoot: input.repoRoot,
17084
17763
  home: input.home,
17085
17764
  settingSources: input.config.settingSources,
@@ -17255,11 +17934,11 @@ function announcePlanIssues(planContent) {
17255
17934
  }
17256
17935
 
17257
17936
  // src/start/runtime.ts
17258
- import { existsSync as existsSync24, readdirSync as readdirSync10, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
17937
+ import { existsSync as existsSync25, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
17259
17938
  import { homedir as homedir5 } from "os";
17260
- import { isAbsolute as isAbsolute5, join as join19, relative as relative6, resolve as resolve13, sep } from "path";
17939
+ import { isAbsolute as isAbsolute5, join as join20, relative as relative6, resolve as resolve13, sep } from "path";
17261
17940
  var RUNTIME_FLAG = "--runtime";
17262
- var RUNTIME_SUBDIR = join19(".rafa", "runtime");
17941
+ var RUNTIME_SUBDIR = join20(".rafa", "runtime");
17263
17942
  var RUNTIME_ENTRY = "cli.js";
17264
17943
  var SOURCE_DIR = "src";
17265
17944
  var RUNTIME_COMMAND = ["start"];
@@ -17286,7 +17965,7 @@ function resolveRuntime(value, places) {
17286
17965
  throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
17287
17966
  }
17288
17967
  const isVersion = VERSION_SHAPE2.test(value);
17289
- const named = isVersion ? join19(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve13(places.cwd, value);
17968
+ const named = isVersion ? join20(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve13(places.cwd, value);
17290
17969
  refuseInsideSource(value, named, places);
17291
17970
  const entry = entryOf2(value, named, isVersion, places.home);
17292
17971
  const real = realpathSync3(entry);
@@ -17297,7 +17976,7 @@ function entryOf2(value, named, isVersion, home) {
17297
17976
  if (isVersion) {
17298
17977
  if (isFile2(named))
17299
17978
  return named;
17300
- const root = join19(home, RUNTIME_SUBDIR);
17979
+ const root = join20(home, RUNTIME_SUBDIR);
17301
17980
  const installed = installedVersions(root);
17302
17981
  throw refusal3([
17303
17982
  `\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
@@ -17306,7 +17985,7 @@ function entryOf2(value, named, isVersion, home) {
17306
17985
  }
17307
17986
  if (isFile2(named))
17308
17987
  return named;
17309
- const inside = join19(named, RUNTIME_ENTRY);
17988
+ const inside = join20(named, RUNTIME_ENTRY);
17310
17989
  if (isDirectory(named) && isFile2(inside))
17311
17990
  return inside;
17312
17991
  throw refusal3([
@@ -17324,8 +18003,8 @@ function refuseInsideSource(value, path3, places) {
17324
18003
  ]);
17325
18004
  }
17326
18005
  function sourceDirs({ cwd, root }) {
17327
- const dirs = [join19(cwd, SOURCE_DIR), join19(root, SOURCE_DIR)];
17328
- const real = dirs.filter((dir) => existsSync24(dir)).map((dir) => realpathSync3(dir));
18006
+ const dirs = [join20(cwd, SOURCE_DIR), join20(root, SOURCE_DIR)];
18007
+ const real = dirs.filter((dir) => existsSync25(dir)).map((dir) => realpathSync3(dir));
17329
18008
  return [...new Set([...dirs, ...real])];
17330
18009
  }
17331
18010
  function isInside2(path3, dir) {
@@ -17334,7 +18013,7 @@ function isInside2(path3, dir) {
17334
18013
  }
17335
18014
  function installedVersions(root) {
17336
18015
  try {
17337
- return readdirSync10(root).filter((name2) => isFile2(join19(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
18016
+ return readdirSync11(root).filter((name2) => isFile2(join20(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
17338
18017
  } catch {
17339
18018
  return [];
17340
18019
  }
@@ -17465,11 +18144,11 @@ function eventOf(line) {
17465
18144
  }
17466
18145
 
17467
18146
  // src/triage/triage.ts
17468
- import { basename as basename8, join as join20 } from "path";
18147
+ import { basename as basename8, join as join21 } from "path";
17469
18148
 
17470
18149
  // src/effort/store/tracker-refs.ts
17471
18150
  import { randomUUID as randomUUID10 } from "crypto";
17472
- import { existsSync as existsSync25 } from "fs";
18151
+ import { existsSync as existsSync26 } from "fs";
17473
18152
  var SELECT_HELD2 = `
17474
18153
  SELECT seq, tracker_ref FROM findings
17475
18154
  WHERE session_id = ? AND artifact = ?
@@ -17535,17 +18214,17 @@ function storedRefOf(value) {
17535
18214
  if (problem !== null)
17536
18215
  return `whose ${field5} ${problem}`;
17537
18216
  }
17538
- const ref = {
18217
+ const ref3 = {
17539
18218
  opt: fields.opt,
17540
18219
  kind: fields.kind,
17541
18220
  externalId: fields.externalId,
17542
18221
  url: fields.url
17543
18222
  };
17544
18223
  if (typeof fields.module === "string")
17545
- ref.module = fields.module;
18224
+ ref3.module = fields.module;
17546
18225
  if (typeof fields.repo === "string")
17547
- ref.repo = fields.repo;
17548
- return { ref, json: JSON.stringify(ref) };
18226
+ ref3.repo = fields.repo;
18227
+ return { ref: ref3, json: JSON.stringify(ref3) };
17549
18228
  }
17550
18229
  function heldRefOf(path3, seq, text) {
17551
18230
  let parsed;
@@ -17592,7 +18271,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
17592
18271
  wanted.json,
17593
18272
  collectedAt
17594
18273
  ];
17595
- const [action, stored] = withSqliteStore(path3, !existsSync25(path3), (db) => {
18274
+ const [action, stored] = withSqliteStore(path3, !existsSync26(path3), (db) => {
17596
18275
  const place = db.transaction(() => placeRef(db, path3, write, wanted, insertValues));
17597
18276
  return place.immediate();
17598
18277
  });
@@ -17601,7 +18280,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
17601
18280
  function readTrackerRef(repoRoot, artifact) {
17602
18281
  checkArtifact("read", artifact);
17603
18282
  const path3 = sqliteStorePath(repoRoot);
17604
- if (!existsSync25(path3))
18283
+ if (!existsSync26(path3))
17605
18284
  return null;
17606
18285
  const oldest = withSqliteStore(path3, false, (db) => db.query(SELECT_OLDEST).get(artifact));
17607
18286
  return oldest === null ? null : heldRefOf(path3, oldest.seq, oldest.tracker_ref).ref;
@@ -17613,28 +18292,28 @@ var ARTIFACT_REF_KINDS = new Set(["path", "symbol"]);
17613
18292
  var UNREAD_STAMP = "unread";
17614
18293
  var REFS_INTRO = "The paths and symbols the artifact names, each with its target's" + " fingerprint when this bug was filed.";
17615
18294
  function artifactRefs(artifact) {
17616
- return extractRefs(artifact).filter((ref) => ARTIFACT_REF_KINDS.has(ref.kind));
18295
+ return extractRefs(artifact).filter((ref3) => ARTIFACT_REF_KINDS.has(ref3.kind));
17617
18296
  }
17618
18297
  function stampWord(live) {
17619
18298
  return live.kind === "unreadable" ? UNREAD_STAMP : fingerprintText(live);
17620
18299
  }
17621
18300
  async function stampArtifactRefs(artifact, verify) {
17622
18301
  const stamps = [];
17623
- for (const ref of artifactRefs(artifact)) {
18302
+ for (const ref3 of artifactRefs(artifact)) {
17624
18303
  let stamp;
17625
18304
  try {
17626
- stamp = stampWord(await verify({ kind: ref.kind, text: ref.text }));
18305
+ stamp = stampWord(await verify({ kind: ref3.kind, text: ref3.text }));
17627
18306
  } catch {
17628
18307
  stamp = UNREAD_STAMP;
17629
18308
  }
17630
- stamps.push(Object.freeze({ kind: ref.kind, text: ref.text, stamp }));
18309
+ stamps.push(Object.freeze({ kind: ref3.kind, text: ref3.text, stamp }));
17631
18310
  }
17632
18311
  return Object.freeze(stamps);
17633
18312
  }
17634
18313
  function refsSection(stamps) {
17635
18314
  if (stamps.length === 0)
17636
18315
  return null;
17637
- const lines = stamps.map((ref) => `- ${ref.kind} \`${ref.text}\`: \`${ref.stamp}\``);
18316
+ const lines = stamps.map((ref3) => `- ${ref3.kind} \`${ref3.text}\`: \`${ref3.stamp}\``);
17638
18317
  return [`## ${REFS_HEADING}`, REFS_INTRO, lines.join(`
17639
18318
  `)].join(`
17640
18319
 
@@ -17657,19 +18336,19 @@ async function noIssues() {
17657
18336
  }
17658
18337
  function createArtifactRefsVerifier(repoRoot, env) {
17659
18338
  let made = null;
17660
- return async (ref) => {
18339
+ return async (ref3) => {
17661
18340
  made ??= createRefVerifier({
17662
18341
  issues: noIssues,
17663
18342
  git: createGitRunner(repoRoot),
17664
18343
  outline: tsSymbolsOutliner({ cwd: repoRoot, env }),
17665
18344
  roster: EMPTY_ROSTER
17666
18345
  });
17667
- return made(ref);
18346
+ return made(ref3);
17668
18347
  };
17669
18348
  }
17670
18349
 
17671
18350
  // src/triage/triage.ts
17672
- var PRIVATE_TRIAGE_DIR = join20(".rafa", "triage", "private");
18351
+ var PRIVATE_TRIAGE_DIR = join21(".rafa", "triage", "private");
17673
18352
  var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
17674
18353
  var TRIAGE_MODULE = "unassigned";
17675
18354
  var BLOCKER_SEPARATOR = "; ";
@@ -17680,7 +18359,7 @@ var MIN_FENCE_LENGTH = 3;
17680
18359
  var ISSUE_OPENING = "An out-of-scope bug a rafa task session reported. The loop filed it and" + " dispatches no task for it: a plan that wants it fixed declares a task.";
17681
18360
  var COMMENT_OPENING = "Reported again by a rafa task session.";
17682
18361
  function privateTriageDir(repoRoot) {
17683
- return join20(repoRoot, PRIVATE_TRIAGE_DIR);
18362
+ return join21(repoRoot, PRIVATE_TRIAGE_DIR);
17684
18363
  }
17685
18364
  function createPrivateTriageTracker(repoRoot, options = {}) {
17686
18365
  return createLocalTracker({
@@ -17754,7 +18433,7 @@ function fenced(value) {
17754
18433
  return `${fence}
17755
18434
  ${body}${fence}`;
17756
18435
  }
17757
- function issueTitle(redactedWhat) {
18436
+ function issueTitle2(redactedWhat) {
17758
18437
  const line = oneLine5(redactedWhat);
17759
18438
  const points = Array.from(line);
17760
18439
  if (points.length <= TITLE_MAX_LENGTH)
@@ -17799,7 +18478,7 @@ async function filingFor(what, bug, options, redact, verifyRefs) {
17799
18478
  return {
17800
18479
  draft: {
17801
18480
  opt: 0,
17802
- title: issueTitle(redact(what)),
18481
+ title: issueTitle2(redact(what)),
17803
18482
  body: issueText(ISSUE_OPENING, values, redact),
17804
18483
  type: "bug",
17805
18484
  module: TRIAGE_MODULE,
@@ -17831,23 +18510,23 @@ function resultOf(run, action, fields = {}) {
17831
18510
  ...fields
17832
18511
  };
17833
18512
  }
17834
- async function settle(run, action, ref, foundBy) {
18513
+ async function settle(run, action, ref3, foundBy) {
17835
18514
  const { store } = run.route;
17836
18515
  const { key } = run.filing;
17837
18516
  if (store === null || key === null)
17838
- return resultOf(run, action, { ref, foundBy });
17839
- const stored = await step(run, "storing the reference", () => store(key, ref));
17840
- return stored.ok ? resultOf(run, action, { ref, foundBy, stored: stored.value }) : resultOf(run, action, { ref, foundBy, problem: stored.problem });
18517
+ return resultOf(run, action, { ref: ref3, foundBy });
18518
+ const stored = await step(run, "storing the reference", () => store(key, ref3));
18519
+ return stored.ok ? resultOf(run, action, { ref: ref3, foundBy, stored: stored.value }) : resultOf(run, action, { ref: ref3, foundBy, problem: stored.problem });
17841
18520
  }
17842
18521
  async function fileNew(run) {
17843
18522
  const created = await step(run, `the ${run.route.channel} tracker create`, () => run.route.tracker.create(run.filing.draft));
17844
18523
  return created.ok ? settle(run, "filed", created.value, null) : resultOf(run, "failed", { problem: created.problem });
17845
18524
  }
17846
- async function commentOn(run, ref, foundBy) {
17847
- const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(ref, run.filing.comment));
18525
+ async function commentOn(run, ref3, foundBy) {
18526
+ const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(ref3, run.filing.comment));
17848
18527
  if (!commented.ok)
17849
- return resultOf(run, "failed", { ref, foundBy, problem: commented.problem });
17850
- return foundBy === "store" ? resultOf(run, "commented", { ref, foundBy }) : settle(run, "commented", ref, foundBy);
18528
+ return resultOf(run, "failed", { ref: ref3, foundBy, problem: commented.problem });
18529
+ return foundBy === "store" ? resultOf(run, "commented", { ref: ref3, foundBy }) : settle(run, "commented", ref3, foundBy);
17851
18530
  }
17852
18531
  async function triageBug(run) {
17853
18532
  const { route, filing } = run;
@@ -17895,11 +18574,11 @@ async function triageReport(options) {
17895
18574
  channel: "public",
17896
18575
  tracker: options.tracker,
17897
18576
  readStored: (key) => readTrackerRef(repoRoot, key),
17898
- store: (key, ref) => writeTrackerRef(repoRoot, {
18577
+ store: (key, ref3) => writeTrackerRef(repoRoot, {
17899
18578
  dispatch: options.dispatch,
17900
18579
  outcome: options.outcome,
17901
18580
  artifact: key,
17902
- ref
18581
+ ref: ref3
17903
18582
  }, options.seams).action
17904
18583
  },
17905
18584
  private: { channel: "private", tracker: privateTracker, readStored: null, store: null }
@@ -17926,7 +18605,7 @@ async function triageReport(options) {
17926
18605
  // src/start/triage.ts
17927
18606
  var NOT_RESOLVED_REASON = "start triage: no public tracker is resolved for a report with no public bug";
17928
18607
  var LINE_PREFIX = "Triage: ";
17929
- var INDENT4 = " ";
18608
+ var INDENT5 = " ";
17930
18609
  var FOUND_BY = {
17931
18610
  store: "its stored reference",
17932
18611
  find: "a tracker find"
@@ -17950,8 +18629,8 @@ function filesPublicly(bug) {
17950
18629
  function hasTriage(report2) {
17951
18630
  return blockerTextOf(report2.blockers) !== null || report2.outOfScopeBugs.length > 0;
17952
18631
  }
17953
- function issueName2(ref) {
17954
- return ref.url ?? `${ref.kind} issue ${ref.externalId}`;
18632
+ function issueName2(ref3) {
18633
+ return ref3.url ?? `${ref3.kind} issue ${ref3.externalId}`;
17955
18634
  }
17956
18635
  function bugLines(bug) {
17957
18636
  const name2 = `out_of_scope_bugs[${bug.index}]`;
@@ -17989,11 +18668,11 @@ async function resolvePublicTracker(options, redact) {
17989
18668
  });
17990
18669
  const passed2 = attempts.filter((attempt2) => !attempt2.ok).map((attempt2) => `\`${attempt2.kind}\``);
17991
18670
  const fallback = passed2.length === 0 ? "" : `, having passed over ${passed2.join(", ")}`;
17992
- activeOutput().info(`${INDENT4}${LINE_PREFIX}public bugs of this run go to the \`${tracker.kind}\` tracker${fallback}.`);
18671
+ activeOutput().info(`${INDENT5}${LINE_PREFIX}public bugs of this run go to the \`${tracker.kind}\` tracker${fallback}.`);
17993
18672
  return tracker;
17994
18673
  } catch (error) {
17995
18674
  const reason = messageOf(error);
17996
- activeOutput().warn(`${INDENT4}${LINE_PREFIX}no tracker resolved, so no public bug of this run is filed: ${redact(reason)}`);
18675
+ activeOutput().warn(`${INDENT5}${LINE_PREFIX}no tracker resolved, so no public bug of this run is filed: ${redact(reason)}`);
17997
18676
  return unresolvedTracker(options.config.trackerDefault, reason);
17998
18677
  }
17999
18678
  }
@@ -18031,16 +18710,16 @@ function createStartTriage(options) {
18031
18710
  });
18032
18711
  const { notes, warnings } = describeStartTriage(result);
18033
18712
  for (const note of notes)
18034
- activeOutput().info(`${INDENT4}${note}`);
18713
+ activeOutput().info(`${INDENT5}${note}`);
18035
18714
  for (const warning of warnings)
18036
- activeOutput().warn(`${INDENT4}${warning}`);
18715
+ activeOutput().warn(`${INDENT5}${warning}`);
18037
18716
  return result;
18038
18717
  };
18039
18718
  return async (input) => {
18040
18719
  try {
18041
18720
  return await triageTask(input);
18042
18721
  } catch (error) {
18043
- activeOutput().warn(`${INDENT4}${LINE_PREFIX}nothing more was triaged for this task: ${redact(messageOf(error))}`);
18722
+ activeOutput().warn(`${INDENT5}${LINE_PREFIX}nothing more was triaged for this task: ${redact(messageOf(error))}`);
18044
18723
  return null;
18045
18724
  }
18046
18725
  };
@@ -19255,4 +19934,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
19255
19934
  }
19256
19935
  }
19257
19936
 
19258
- export { readConfigFile, loadConfig, planStubsFromFileNames, resolvePlanStub, SERVE_CLI_VERSION, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, SessionRecordError, runsDir, errorCode, readSessions, readSession, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, report, name, version, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, SPEC_EXTENSION, boardId, notesFileName, ROADMAP_TITLE, ROADMAP_SETTING, ROADMAP_REFUSAL_EXIT, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, bodyLines, findField, SPEC_BLOCKED_LABEL, hasSpecBlockedLabel, readBlockedBy, blockedFaultMessage, EPIC_LABEL_PREFIX, epicSlugsOf, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, validate_default, createGhPermissions, readAuthorTrust, trustRefusalClause, requireTrustedAuthor, RefsBlockError, fingerprintText, readRefsText, readCopyRefs, restampCopyRefs, createGitRunner, gitSaid, RefVerifyError, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, anythingAt, writeShippedFile, setUpBoard, createGhBoardLister, resolveDefaultBoard, unlabelledRoadmapMessage, createGhIssueBoard, blockedLineSentence, readEpicProblems, epicProblemMessage, readDoctorRefs, roadmapRefsCells, renderDoctorRefs, POSITION_FILE, positionFilePath, readPositionFile, writePositionFile, rehome, hop, hasEpicLines, horizonOf, readRoadmapEpicRows, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, renderRoadmapTable, renderEpicTable, firstNowEpic, epics_default, samePlace, resolvePlace, createPlanDirNames, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, readPlanChanges, rankCandidates, readRankCandidate, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, resolvePrProvider, requireGhProvider, isFirstDispatch, SESSION_ID_FLAG2 as SESSION_ID_FLAG, resolveLoopSeams, sessionIdFlag, refusal2 as refusal, isLive, planLabel, sessionLine, projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, checkboxAt, estimateEta, readSessionFinishes, etaLine, parseWorkingTree, parseWorktrees, readMergeRefusal, cleanUpSteps, commandLine, remainingFrom, createGhPullRequests, ghPullRequestsIn, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, createLinePrompter, REMOTE, parseBaseStanding, hasDiverged, list_default, readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter, ready_default, versionLine, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, resolveCreateSpec, resolveReleaseEnabled, TRIAGE_MODULE, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, RUNTIME_SUBDIR, RUNTIME_ENTRY, VERSION_SHAPE2 as VERSION_SHAPE, refuseMisplacedRuntime, groupChangeNotes, renderNoteLines, highestChangeLevel, parseSemanticVersion, readManifestVersion, MECHANICAL_CONFLICT_SENTENCE, start, usage, createPlanRefsVerifier, plan };
19937
+ export { readConfigFile, loadConfig, SERVE_CLI_VERSION, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, SessionRecordError, runsDir, errorCode, readSessions, readSession, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, minutesBetween, sessionLogDir, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, formatCount, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, report, name, version, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, SPEC_EXTENSION, boardId, notesFileName, ROADMAP_TITLE, ROADMAP_SETTING, ROADMAP_REFUSAL_EXIT, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, bodyLines, findField, readEpicBody, SPEC_BLOCKED_LABEL, hasSpecBlockedLabel, readBlockedBy, blockedFaultMessage, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, EPIC_TYPE_LABEL, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, validate_default, createGhPermissions, readAuthorTrust, trustRefusalClause, requireTrustedAuthor, RefsBlockError, fingerprintText, readRefsText, readCopyRefs, restampCopyRefs, createGitRunner, gitSaid, RefVerifyError, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, anythingAt, writeShippedFile, setUpBoard, createGhBoardLister, resolveDefaultBoard, unlabelledRoadmapMessage, createGhIssueBoard, blockedLineSentence, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readDoctorRefs, roadmapRefsCells, renderDoctorRefs, POSITION_FILE, positionFilePath, readPositionFile, writePositionFile, rehome, hop, readEpicDependents, REASON_FLAG, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, HORIZONS, hasEpicLines, horizonOf, readRoadmapEpicRows, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, renderRoadmapTable, renderEpicTable, firstNowEpic, show_default, samePlace, resolvePlace, createPlanDirNames, hasPlanFor, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, resolvePrProvider, requireGhProvider, isFirstDispatch, SESSION_ID_FLAG2 as SESSION_ID_FLAG, resolveLoopSeams, sessionIdFlag, refusal2 as refusal, isLive, planLabel, sessionLine, projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, parseWorkingTree, parseWorktrees, readMergeRefusal, cleanUpSteps, commandLine, remainingFrom, createGhPullRequests, ghPullRequestsIn, readManifestVersion, DEFAULT_RELEASE_SEAMS, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, createLinePrompter, REMOTE, parseBaseStanding, hasDiverged, list_default, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter, ready_default, versionLine, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, resolveCreateSpec, TRIAGE_MODULE, namedSecrets, triageReport, unresolvedTracker, resolveReleaseEnabled, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, RUNTIME_SUBDIR, RUNTIME_ENTRY, VERSION_SHAPE2 as VERSION_SHAPE, refuseMisplacedRuntime, MECHANICAL_CONFLICT_SENTENCE, start, usage, createPlanRefsVerifier, plan };