@open-tomato/rafa 0.23.0 → 0.24.1

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.1";
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-amyd4dse.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();
@@ -11254,7 +11496,7 @@ function readMergeRefusal(reading2) {
11254
11496
  return held.length === 0 ? null : worktreeRefusal(reading2.branch, held);
11255
11497
  }
11256
11498
  function cleanUpSteps(plan) {
11257
- const { base, branch, remote = DEFAULT_REMOTE2, remoteBranchPresent } = plan;
11499
+ const { base, branch, remote = DEFAULT_REMOTE2, localBranchPresent, remoteBranchPresent } = plan;
11258
11500
  const steps = [
11259
11501
  { id: "switch-base", label: `switch to ${base}`, argv: ["git", "switch", base] },
11260
11502
  {
@@ -11265,7 +11507,8 @@ function cleanUpSteps(plan) {
11265
11507
  {
11266
11508
  id: "delete-local",
11267
11509
  label: `delete the local branch ${branch}`,
11268
- argv: ["git", "branch", "-D", branch]
11510
+ argv: ["git", "branch", "-D", branch],
11511
+ ...localBranchPresent ? {} : { skip: `no local branch ${branch}; nothing to delete` }
11269
11512
  }
11270
11513
  ];
11271
11514
  if (remoteBranchPresent) {
@@ -11290,7 +11533,7 @@ function commandLine(step) {
11290
11533
  }
11291
11534
  function remainingFrom(steps, failed2) {
11292
11535
  const at = steps.findIndex((step) => step.id === failed2);
11293
- return at === -1 ? Object.freeze([]) : Object.freeze(steps.slice(at));
11536
+ return at === -1 ? Object.freeze([]) : Object.freeze(steps.slice(at).filter((step) => step.skip === undefined));
11294
11537
  }
11295
11538
  // src/pr/none.ts
11296
11539
  import { spawnSync as spawnSync2 } from "child_process";
@@ -11664,14 +11907,809 @@ function ghPullRequestsIn(cwd) {
11664
11907
  function ghAuthOkIn(cwd) {
11665
11908
  return ghAuthOk(createGhRunner({ cwd }));
11666
11909
  }
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) {
11910
+ // src/release/changelog.ts
11911
+ var RELEASE_HEADING_FIELDS = ["version", "date", "title"];
11912
+ var PLACEHOLDER = /\{([A-Za-z]+)\}/g;
11913
+ var WHITESPACE = /\s+/g;
11914
+ var ATX_HEADING = /^ {0,3}#{1,6}(?: |$)/;
11915
+ var CODE_FENCE = /^ {0,3}(?:```|~~~)/;
11916
+ var SEPARATORS = "\u2014\u2013-,;:\xB7|";
11917
+ function isSeparator(character) {
11918
+ return character.trim() === "" || SEPARATORS.includes(character);
11919
+ }
11920
+ function trimTrailingSeparators(text) {
11921
+ let end = text.length;
11922
+ while (end > 0 && isSeparator(text[end - 1] ?? ""))
11923
+ end -= 1;
11924
+ return text.slice(0, end);
11925
+ }
11926
+ function trimLeadingSeparators(text) {
11927
+ let start = 0;
11928
+ while (start < text.length && isSeparator(text[start] ?? ""))
11929
+ start += 1;
11930
+ return text.slice(start);
11931
+ }
11932
+ function isHeadingField(field5) {
11933
+ return RELEASE_HEADING_FIELDS.includes(field5);
11934
+ }
11935
+ function renderReleaseHeading(template, values) {
11936
+ let rendered = "";
11937
+ let afterGap = false;
11938
+ const add = (text) => {
11939
+ if (!afterGap) {
11940
+ rendered += text;
11941
+ return;
11942
+ }
11943
+ const kept = trimLeadingSeparators(text);
11944
+ if (kept === "")
11945
+ return;
11946
+ afterGap = false;
11947
+ rendered += kept;
11948
+ };
11949
+ let cursor = 0;
11950
+ for (const found of template.matchAll(PLACEHOLDER)) {
11951
+ const before = template.slice(cursor, found.index);
11952
+ cursor = found.index + found[0].length;
11953
+ const name2 = found[1] ?? "";
11954
+ if (!isHeadingField(name2)) {
11955
+ add(`${before}${found[0]}`);
11956
+ continue;
11957
+ }
11958
+ const value = values[name2].trim();
11959
+ if (value !== "") {
11960
+ add(before);
11961
+ add(value);
11962
+ continue;
11963
+ }
11964
+ add(before);
11965
+ rendered = `${trimTrailingSeparators(rendered)} `;
11966
+ afterGap = true;
11967
+ }
11968
+ add(template.slice(cursor));
11969
+ return rendered.replace(WHITESPACE, " ").trim();
11970
+ }
11971
+ function areaOf(note) {
11972
+ const area = note.area?.trim() ?? "";
11973
+ return area === "" ? null : area;
11974
+ }
11975
+ function groupChangeNotes(notes) {
11976
+ const named = new Map;
11977
+ const unnamed = [];
11978
+ const seen = new Set;
11979
+ let noneNotes = 0;
11980
+ let duplicateNotes = 0;
11981
+ let blankNotes = 0;
11982
+ for (const note of notes) {
11983
+ if (note.level === "none") {
11984
+ noneNotes += 1;
11985
+ continue;
11986
+ }
11987
+ const area = areaOf(note);
11988
+ const summary = note.summary.replace(WHITESPACE, " ").trim();
11989
+ if (summary === "") {
11990
+ blankNotes += 1;
11991
+ continue;
11992
+ }
11993
+ const key = `${area ?? ""}
11994
+ ${summary}`;
11995
+ if (seen.has(key)) {
11996
+ duplicateNotes += 1;
11997
+ continue;
11998
+ }
11999
+ seen.add(key);
12000
+ if (area === null) {
12001
+ unnamed.push(summary);
12002
+ continue;
12003
+ }
12004
+ const summaries = named.get(area);
12005
+ if (summaries === undefined)
12006
+ named.set(area, [summary]);
12007
+ else
12008
+ summaries.push(summary);
12009
+ }
12010
+ const groups = [...named].map(([area, summaries]) => ({ area, summaries }));
12011
+ return {
12012
+ groups: unnamed.length === 0 ? groups : [...groups, { area: null, summaries: unnamed }],
12013
+ noneNotes,
12014
+ duplicateNotes,
12015
+ blankNotes
12016
+ };
12017
+ }
12018
+ function renderNoteLines(grouping) {
12019
+ return grouping.groups.flatMap((group) => group.summaries.map((summary) => group.area === null ? `- ${summary}` : `- ${group.area}: ${summary}`));
12020
+ }
12021
+ function renderChangelogEntry(input) {
12022
+ const heading = renderReleaseHeading(input.template, input.values);
12023
+ const grouping = groupChangeNotes(input.notes);
12024
+ const lines = renderNoteLines(grouping);
12025
+ return {
12026
+ heading,
12027
+ lines,
12028
+ groups: grouping.groups,
12029
+ text: lines.length === 0 ? heading : `${heading}
12030
+
12031
+ ${lines.join(`
12032
+ `)}`,
12033
+ noneNotes: grouping.noneNotes,
12034
+ duplicateNotes: grouping.duplicateNotes,
12035
+ blankNotes: grouping.blankNotes
12036
+ };
12037
+ }
12038
+ function findHeading(lines, from) {
12039
+ let fenced = false;
12040
+ for (let index = 0;index < lines.length; index += 1) {
12041
+ const line = lines[index] ?? "";
12042
+ if (CODE_FENCE.test(line)) {
12043
+ fenced = !fenced;
12044
+ continue;
12045
+ }
12046
+ if (fenced || index < from)
12047
+ continue;
12048
+ if (ATX_HEADING.test(line))
12049
+ return index;
12050
+ }
12051
+ return null;
12052
+ }
12053
+ function endOfContent(lines) {
12054
+ let end = lines.length;
12055
+ while (end > 0 && (lines[end - 1] ?? "").trim() === "")
12056
+ end -= 1;
12057
+ return end;
12058
+ }
12059
+ function insertPoint(lines) {
12060
+ const first = findHeading(lines, 0);
12061
+ if (first === null)
12062
+ return { at: 0, point: "file-top" };
12063
+ const next = findHeading(lines, first + 1);
12064
+ if (next !== null)
12065
+ return { at: next, point: "before-next-heading" };
12066
+ return { at: endOfContent(lines), point: "file-end" };
12067
+ }
12068
+ function insertChangelogEntry(changelog, entry) {
12069
+ const lines = changelog.split(`
12070
+ `);
12071
+ const { at, point } = insertPoint(lines);
12072
+ const entryLines = entry.split(`
12073
+ `);
12074
+ const before = at === 0 || (lines[at - 1] ?? "").trim() === "" ? [] : [""];
12075
+ const after = at >= lines.length || (lines[at] ?? "").trim() === "" ? [] : [""];
12076
+ const inserted = [
12077
+ ...lines.slice(0, at),
12078
+ ...before,
12079
+ ...entryLines,
12080
+ ...after,
12081
+ ...lines.slice(at)
12082
+ ];
12083
+ return { text: inserted.join(`
12084
+ `), point, line: at + before.length + 1 };
12085
+ }
12086
+ function changelogDate(now) {
12087
+ const month = `${now.getMonth() + 1}`.padStart(2, "0");
12088
+ const day = `${now.getDate()}`.padStart(2, "0");
12089
+ return `${now.getFullYear()}-${month}-${day}`;
12090
+ }
12091
+
12092
+ // src/release/level.ts
12093
+ var RELEASE_LEVEL_RANK = {
12094
+ none: 0,
12095
+ patch: 1,
12096
+ minor: 2,
12097
+ major: 3
12098
+ };
12099
+ function highestChangeLevel(notes) {
12100
+ return notes.reduce((highest, note) => highest === null || RELEASE_LEVEL_RANK[note.level] > RELEASE_LEVEL_RANK[highest] ? note.level : highest, null);
12101
+ }
12102
+ function resolveReleaseLevel(declared, notes) {
12103
+ const notesLevel = highestChangeLevel(notes);
12104
+ if (declared !== null)
12105
+ return { level: declared, source: "plan", notesLevel };
12106
+ if (notesLevel !== null)
12107
+ return { level: notesLevel, source: "notes", notesLevel };
12108
+ return { level: "none", source: "default", notesLevel };
12109
+ }
12110
+
12111
+ // src/release/version.ts
12112
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
12113
+ var RELEASE_REMOTE = "origin";
12114
+ var RELEASE_BASE_BRANCH = "main";
12115
+ var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
12116
+ function parseSemanticVersion(raw) {
12117
+ const found = VERSION_SHAPE.exec(raw);
12118
+ if (found === null)
12119
+ return null;
12120
+ return {
12121
+ major: Number(found[1]),
12122
+ minor: Number(found[2]),
12123
+ patch: Number(found[3]),
12124
+ prerelease: found[4] ?? "",
12125
+ build: found[5] ?? ""
12126
+ };
12127
+ }
12128
+ function formatSemanticVersion(version2) {
12129
+ const prerelease = version2.prerelease === "" ? "" : `-${version2.prerelease}`;
12130
+ const build = version2.build === "" ? "" : `+${version2.build}`;
12131
+ return `${version2.major}.${version2.minor}.${version2.patch}${prerelease}${build}`;
12132
+ }
12133
+ function bumped(version2, level) {
12134
+ const onPrerelease = version2.prerelease !== "";
12135
+ const blank = { prerelease: "", build: "" };
12136
+ if (level === "major") {
12137
+ const stays = onPrerelease && version2.minor === 0 && version2.patch === 0;
12138
+ const major = stays ? version2.major : version2.major + 1;
12139
+ return { major, minor: 0, patch: 0, ...blank };
12140
+ }
12141
+ if (level === "minor") {
12142
+ const stays = onPrerelease && version2.patch === 0;
12143
+ const minor = stays ? version2.minor : version2.minor + 1;
12144
+ return { major: version2.major, minor, patch: 0, ...blank };
12145
+ }
12146
+ const patch = onPrerelease ? version2.patch : version2.patch + 1;
12147
+ return { major: version2.major, minor: version2.minor, patch, ...blank };
12148
+ }
12149
+ function nextVersion(base, level) {
12150
+ const parsed = parseSemanticVersion(base);
12151
+ if (parsed === null)
12152
+ return null;
12153
+ if (level === "none")
12154
+ return formatSemanticVersion(parsed);
12155
+ return formatSemanticVersion(bumped(parsed, level));
12156
+ }
12157
+ function manifestFields(text) {
12158
+ let parsed;
12159
+ try {
12160
+ parsed = JSON.parse(text);
12161
+ } catch {
12162
+ return null;
12163
+ }
12164
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
12165
+ return null;
12166
+ return parsed;
12167
+ }
12168
+ function readManifestVersion(text) {
12169
+ const fields = manifestFields(text);
12170
+ if (fields === null)
12171
+ return null;
12172
+ const value = fields["version"];
12173
+ if (typeof value !== "string")
12174
+ return null;
12175
+ const trimmed = value.trim();
12176
+ return trimmed === "" ? null : trimmed;
12177
+ }
12178
+ var VERSION_PAIR = /("version"\s*:\s*)"(?:[^"\\]|\\.)*"/g;
12179
+ function sameBesidesVersion(before, after) {
12180
+ const restored = { ...after, version: before["version"] };
12181
+ return JSON.stringify(restored) === JSON.stringify(before);
12182
+ }
12183
+ function replaceManifestVersion(text, next) {
12184
+ const previous = readManifestVersion(text);
12185
+ const before = manifestFields(text);
12186
+ if (previous === null || before === null)
12187
+ return null;
12188
+ for (const pair of text.matchAll(VERSION_PAIR)) {
12189
+ const start = pair.index;
12190
+ const candidate = `${text.slice(0, start)}${pair[1]}${JSON.stringify(next)}${text.slice(start + pair[0].length)}`;
12191
+ const after = manifestFields(candidate);
12192
+ if (after === null || after["version"] !== next)
12193
+ continue;
12194
+ if (!sameBesidesVersion(before, after))
12195
+ continue;
12196
+ return { text: candidate, previous };
12197
+ }
12198
+ return null;
12199
+ }
12200
+ function writeManifestVersion(path3, next) {
12201
+ const unwritten = { path: path3, written: false, previous: null, version: next };
12202
+ let text;
12203
+ try {
12204
+ text = readFileSync19(path3, "utf8");
12205
+ } catch (error) {
12206
+ return { ...unwritten, problem: `${path3} could not be read: ${messageOf(error)}` };
12207
+ }
12208
+ const rewritten = replaceManifestVersion(text, next);
12209
+ if (rewritten === null) {
12210
+ const previous = readManifestVersion(text);
12211
+ return {
12212
+ ...unwritten,
12213
+ previous,
12214
+ problem: previous === null ? `${path3} declares no version to write ${next} over` : `${path3} declares ${previous}, and the pair holding it could not be rewritten`
12215
+ };
12216
+ }
12217
+ try {
12218
+ writeFileSync9(path3, rewritten.text);
12219
+ } catch (error) {
12220
+ return { ...unwritten, previous: rewritten.previous, problem: `${path3} could not be written: ${messageOf(error)}` };
12221
+ }
12222
+ return { path: path3, written: true, previous: rewritten.previous, version: next, problem: null };
12223
+ }
12224
+ function gitPathOf(versionFile) {
12225
+ let path3 = versionFile;
12226
+ while (path3.startsWith("./"))
12227
+ path3 = path3.slice(2);
12228
+ return path3;
12229
+ }
12230
+ function readBaseVersion(git, versionFile, options = {}) {
12231
+ const remote = options.remote ?? RELEASE_REMOTE;
12232
+ const branch = options.branch ?? RELEASE_BASE_BRANCH;
12233
+ const ref3 = `${remote}/${branch}`;
12234
+ const path3 = gitPathOf(versionFile);
12235
+ const problems = [];
12236
+ const fetched = git(["fetch", remote, branch]);
12237
+ if (!fetched.ok) {
12238
+ problems.push(`${branch} could not be fetched from ${remote}, so the base version is whatever this clone already holds for ${ref3}: ${gitSaid(fetched)}`);
12239
+ }
12240
+ const shown = git(["show", `${ref3}:${path3}`]);
12241
+ if (!shown.ok) {
12242
+ problems.push(`${ref3}:${path3} could not be read: ${gitSaid(shown)}`);
12243
+ return { ref: ref3, path: path3, version: null, fetched: fetched.ok, problems };
12244
+ }
12245
+ const version2 = readManifestVersion(shown.stdout);
12246
+ if (version2 === null)
12247
+ problems.push(`${ref3}:${path3} declares no version`);
12248
+ return { ref: ref3, path: path3, version: version2, fetched: fetched.ok, problems };
12249
+ }
12250
+
12251
+ // src/commands/release/status.ts
12252
+ var RELEASE_STATUS_USAGE = "rafa release status [--plan=<stub>]";
12253
+ var UNREADABLE2 = "?";
12254
+ var NOTHING = "none";
12255
+ var INDENT2 = " ";
12256
+ var GAP = " ";
12257
+ var HEADING_VERSION = /(?<![0-9A-Za-z.+-])v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/g;
12258
+ var ATX_HEADING2 = /^ {0,3}#{1,6}(?: |$)/;
12259
+ var CODE_FENCE2 = /^ {0,3}(?:```|~~~)/;
12260
+ var RELEASE_TAG = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/;
12261
+ var NUMERIC_IDENTIFIER = /^(?:0|[1-9]\d*)$/;
12262
+ var DEFAULT_RELEASE_SEAMS = Object.freeze({});
12263
+ function compareIdentifiers(left, right) {
12264
+ const leftNumeric = NUMERIC_IDENTIFIER.test(left);
12265
+ const rightNumeric = NUMERIC_IDENTIFIER.test(right);
12266
+ if (leftNumeric && rightNumeric)
12267
+ return Number(left) - Number(right);
12268
+ if (leftNumeric)
12269
+ return -1;
12270
+ if (rightNumeric)
12271
+ return 1;
12272
+ if (left === right)
12273
+ return 0;
12274
+ return left < right ? -1 : 1;
12275
+ }
12276
+ function comparePrereleases(left, right) {
12277
+ if (left === right)
12278
+ return 0;
12279
+ if (left === "")
12280
+ return 1;
12281
+ if (right === "")
12282
+ return -1;
12283
+ const leftParts = left.split(".");
12284
+ const rightParts = right.split(".");
12285
+ const shared = Math.min(leftParts.length, rightParts.length);
12286
+ for (let index = 0;index < shared; index += 1) {
12287
+ const order = compareIdentifiers(leftParts[index] ?? "", rightParts[index] ?? "");
12288
+ if (order !== 0)
12289
+ return order;
12290
+ }
12291
+ return leftParts.length - rightParts.length;
12292
+ }
12293
+ function compareVersions(left, right) {
12294
+ if (left.major !== right.major)
12295
+ return left.major - right.major;
12296
+ if (left.minor !== right.minor)
12297
+ return left.minor - right.minor;
12298
+ if (left.patch !== right.patch)
12299
+ return left.patch - right.patch;
12300
+ return comparePrereleases(left.prerelease, right.prerelease);
12301
+ }
12302
+ function compareReleaseTags(left, right) {
12303
+ return compareVersions(right.parsed, left.parsed);
12304
+ }
12305
+ function releaseTagsOf(text) {
12306
+ const tags = [];
12307
+ for (const line of text.split(`
12308
+ `)) {
12309
+ const tag = line.trim();
12310
+ const found = RELEASE_TAG.exec(tag);
12311
+ const version2 = found?.[1];
12312
+ if (version2 === undefined)
12313
+ continue;
12314
+ const parsed = parseSemanticVersion(version2);
12315
+ if (parsed === null)
12316
+ continue;
12317
+ tags.push({ tag, version: version2, parsed });
12318
+ }
12319
+ return [...tags].sort(compareReleaseTags);
12320
+ }
12321
+ function changelogVersions(text) {
12322
+ const versions = [];
12323
+ const seen = new Set;
12324
+ let fenced = false;
12325
+ for (const line of text.split(`
12326
+ `)) {
12327
+ if (CODE_FENCE2.test(line)) {
12328
+ fenced = !fenced;
12329
+ continue;
12330
+ }
12331
+ if (fenced || !ATX_HEADING2.test(line))
12332
+ continue;
12333
+ for (const found of line.matchAll(HEADING_VERSION)) {
12334
+ const version2 = found[1] ?? "";
12335
+ if (parseSemanticVersion(version2) === null)
12336
+ continue;
12337
+ if (!seen.has(version2)) {
12338
+ seen.add(version2);
12339
+ versions.push(version2);
12340
+ }
12341
+ break;
12342
+ }
12343
+ }
12344
+ return versions;
12345
+ }
12346
+ function untaggedVersions(released, tags) {
12347
+ const tagged = new Set(tags.map((tag) => tag.version));
12348
+ return released.filter((version2) => !tagged.has(version2));
12349
+ }
12350
+ function textOf(path3) {
12351
+ try {
12352
+ return readFileSync20(path3, "utf8");
12353
+ } catch {
12354
+ return null;
12355
+ }
12356
+ }
12357
+ function readVersionFile(root, configured) {
12358
+ const resolved = join14(root, configured);
12359
+ const text = textOf(resolved);
12360
+ if (text === null) {
12361
+ return { path: configured, version: null, problem: `the version file could not be read at ${resolved}` };
12362
+ }
12363
+ const version2 = readManifestVersion(text);
12364
+ return version2 === null ? { path: configured, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, version: version2, problem: null };
12365
+ }
12366
+ function readTags(git) {
12367
+ const result = git(["tag", "--list"]);
12368
+ if (!result.ok) {
12369
+ return { tags: [], latest: null, problem: `the tags could not be listed: ${gitSaid(result)}` };
12370
+ }
12371
+ const tags = releaseTagsOf(result.stdout);
12372
+ return { tags, latest: tags[0] ?? null, problem: null };
12373
+ }
12374
+ function readUntagged(root, configured, tags) {
12375
+ const resolved = join14(root, configured);
12376
+ const text = textOf(resolved);
12377
+ if (text === null) {
12378
+ return {
12379
+ path: configured,
12380
+ released: [],
12381
+ versions: [],
12382
+ problem: `the changelog could not be read at ${resolved}`
12383
+ };
12384
+ }
12385
+ const released = changelogVersions(text);
12386
+ return { path: configured, released, versions: untaggedVersions(released, tags), problem: null };
12387
+ }
12388
+ function readBranch(git) {
12389
+ const result = git(["rev-parse", "--abbrev-ref", "HEAD"]);
12390
+ const branch = result.stdout.trim();
12391
+ return result.ok && branch !== "" && branch !== "HEAD" ? branch : null;
12392
+ }
12393
+ function planStubsUnder2(root, planDir) {
12394
+ const resolved = join14(root, planDir);
12395
+ if (!existsSync18(resolved))
12396
+ return [];
12397
+ try {
12398
+ return planStubsFromFileNames(readdirSync9(resolved));
12399
+ } catch {
12400
+ return [];
12401
+ }
12402
+ }
12403
+ function branchStubOf(branch) {
12404
+ const slashAt = branch.indexOf("/");
12405
+ if (slashAt <= 0)
12406
+ return null;
12407
+ const stub = branch.slice(slashAt + 1);
12408
+ return stub === "" || stub.includes("/") ? null : stub;
12409
+ }
12410
+ function pickPlan(asked, branch, stubs) {
12411
+ if (asked !== null)
12412
+ return { stub: asked, source: "flag", problem: null };
12413
+ if (branch === null) {
12414
+ return { stub: null, source: "none", problem: "the branch could not be read, so no plan was resolved" };
12415
+ }
12416
+ const branchStub = branchStubOf(branch);
12417
+ if (branchStub === null) {
12418
+ return { stub: null, source: "none", problem: `the branch "${branch}" names no plan stub` };
12419
+ }
12420
+ const resolution = resolvePlanStub(branchStub, stubs);
12421
+ if (resolution.stub !== null)
12422
+ return { stub: resolution.stub, source: "roster", problem: null };
12423
+ if (resolution.match === "ambiguous") {
12424
+ return {
12425
+ stub: null,
12426
+ source: "none",
12427
+ problem: `the branch stub "${branchStub}" reaches ${resolution.candidates.length} plans:` + ` ${resolution.candidates.join(", ")}`
12428
+ };
12429
+ }
12430
+ return { stub: branchStub, source: "branch", problem: null };
12431
+ }
12432
+ function readPending(root, asked, branch, stubs, readNotes) {
12433
+ const pick = pickPlan(asked, branch, stubs);
12434
+ const empty = { stub: pick.stub, source: pick.source, branch, notes: [], level: null };
12435
+ if (pick.stub === null)
12436
+ return { ...empty, problem: pick.problem };
12437
+ try {
12438
+ const notes = readNotes(root, pick.stub);
12439
+ return { ...empty, notes, level: highestChangeLevel(notes), problem: pick.problem };
12440
+ } catch (error) {
12441
+ return { ...empty, problem: `the change notes could not be read: ${messageOf(error)}` };
12442
+ }
12443
+ }
12444
+ function versionFileCell(reading2) {
12445
+ return `${reading2.path}: ${reading2.version ?? UNREADABLE2}`;
12446
+ }
12447
+ function tagCell(reading2) {
12448
+ if (reading2.problem !== null)
12449
+ return UNREADABLE2;
12450
+ const held = `of ${reading2.tags.length} release tags`;
12451
+ return reading2.latest === null ? `${NOTHING}, ${held}` : `${reading2.latest.tag}, ${held}`;
12452
+ }
12453
+ function untaggedCell(reading2) {
12454
+ if (reading2.problem !== null)
12455
+ return UNREADABLE2;
12456
+ if (reading2.released.length === 0)
12457
+ return `${NOTHING}, and the changelog names no release`;
12458
+ return reading2.versions.length === 0 ? NOTHING : reading2.versions.join(", ");
12459
+ }
12460
+ function levelPhrase(level) {
12461
+ return level === "none" ? "worth no release" : `worth a ${level} release`;
12462
+ }
12463
+ function pendingCell(reading2) {
12464
+ if (reading2.stub === null)
12465
+ return `${NOTHING}: no current plan`;
12466
+ const count = reading2.notes.length === 1 ? "1 note" : `${reading2.notes.length} notes`;
12467
+ if (reading2.notes.length === 0)
12468
+ return `${NOTHING} for ${reading2.stub}`;
12469
+ return `${count} for ${reading2.stub}, ${levelPhrase(reading2.level ?? "none")}`;
12470
+ }
12471
+ function pendingLines(reading2) {
12472
+ return renderNoteLines(groupChangeNotes(reading2.notes));
12473
+ }
12474
+ function problemsOf(reading2) {
12475
+ return [
12476
+ reading2.versionFile.problem,
12477
+ reading2.tags.problem,
12478
+ reading2.untagged.problem,
12479
+ reading2.plan.problem
12480
+ ].filter((problem) => problem !== null);
12481
+ }
12482
+ var LABELS = ["version file", "latest tag", "untagged", "pending"];
12483
+ function renderStatus(reading2) {
12484
+ const width = Math.max(...LABELS.map((label) => label.length));
12485
+ const cells = [
12486
+ versionFileCell(reading2.versionFile),
12487
+ tagCell(reading2.tags),
12488
+ untaggedCell(reading2.untagged),
12489
+ pendingCell(reading2.plan)
12490
+ ];
12491
+ const lines = LABELS.map((label, index) => `${INDENT2}${label.padEnd(width)}${GAP}${cells[index] ?? ""}`);
12492
+ const notes = pendingLines(reading2.plan).map((line) => `${INDENT2}${" ".repeat(width)}${GAP}${line}`);
12493
+ const problems = problemsOf(reading2).map((problem) => `${INDENT2}${problem}`);
12494
+ const block = [...lines, ...notes].join(`
12495
+ `);
12496
+ return problems.length === 0 ? block : [block, problems.join(`
12497
+ `)].join(`
12498
+
12499
+ `);
12500
+ }
12501
+ function projectOf(context) {
12502
+ if (context.project === null)
12503
+ throw new Error("rafa release runs inside a project, and was handed none");
12504
+ return context.project;
12505
+ }
12506
+ function statusConfig(project, warn) {
12507
+ try {
12508
+ const { config } = loadConfig({ root: project.root, home: project.home }, {}, warn);
12509
+ return {
12510
+ versionFile: config.releaseVersionFile,
12511
+ changelog: config.releaseChangelog,
12512
+ planDir: config.planDir
12513
+ };
12514
+ } catch (error) {
12515
+ if (!(error instanceof ConfigError))
12516
+ throw error;
12517
+ throw new CommandExit(1, ["\u274C The config cannot be used:", ...error.problems.map((problem) => ` ${problem}`)].join(`
12518
+ `));
12519
+ }
12520
+ }
12521
+ function readPlanFlag(context) {
12522
+ const value = context.flags["plan"];
12523
+ if (value === undefined)
12524
+ return null;
12525
+ const stub = typeof value === "string" ? value.trim() : "";
12526
+ if (stub === "") {
12527
+ throw new CommandExit(1, `\u274C --plan takes a plan stub, as --plan=rafa-21-changelog-and-release
12528
+ Usage: ${RELEASE_STATUS_USAGE}`);
12529
+ }
12530
+ return stub;
12531
+ }
12532
+ function expectNoArguments(args) {
12533
+ if (args.length === 0)
12534
+ return;
12535
+ throw new CommandExit(1, `\u274C Expected no arguments, got ${args.length}: ${args.join(" ")}
12536
+ Usage: ${RELEASE_STATUS_USAGE}`);
12537
+ }
12538
+ function readStatus(context, seams = DEFAULT_RELEASE_SEAMS) {
12539
+ expectNoArguments(context.args);
12540
+ const asked = readPlanFlag(context);
12541
+ const project = projectOf(context);
12542
+ const config = statusConfig(project, (message) => {
12543
+ context.output.warn(message);
12544
+ });
12545
+ const git = (seams.git ?? createGitRunner)(project.root);
12546
+ const readNotes = seams.readNotes ?? readPlanChanges;
12547
+ const tags = readTags(git);
12548
+ const reading2 = {
12549
+ versionFile: readVersionFile(project.root, config.versionFile),
12550
+ tags,
12551
+ untagged: readUntagged(project.root, config.changelog, tags.tags),
12552
+ plan: readPending(project.root, asked, readBranch(git), planStubsUnder2(project.root, config.planDir), readNotes)
12553
+ };
12554
+ return { reading: reading2, text: renderStatus(reading2) };
12555
+ }
12556
+ function createReleaseStatusCommand(seams = DEFAULT_RELEASE_SEAMS) {
12557
+ const command = {
12558
+ name: "release status",
12559
+ subject: "release",
12560
+ action: "status",
12561
+ summary: "show the version, the latest tag, the untagged releases and the pending change notes",
12562
+ 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.",
12563
+ args: [],
12564
+ flags: [
12565
+ {
12566
+ name: "plan",
12567
+ description: "the plan stub the pending change notes are read for, instead of the branch's",
12568
+ type: "string"
12569
+ }
12570
+ ],
12571
+ examples: [
12572
+ {
12573
+ cmd: "rafa release status",
12574
+ note: "Shows the version, the latest tag, the untagged releases and the notes pending for this branch's plan."
12575
+ },
12576
+ {
12577
+ cmd: "rafa release status --plan=rafa-21-changelog-and-release",
12578
+ note: "Reads the pending change notes for that plan rather than the one the branch names."
12579
+ }
12580
+ ],
12581
+ outputs: ["text", "json"],
12582
+ run: async (context) => {
12583
+ const status = readStatus(context, seams);
12584
+ if (context.outputMode === "json") {
12585
+ context.output.result(status);
12586
+ return;
12587
+ }
12588
+ context.output.info(status.text);
12589
+ }
12590
+ };
12591
+ return Object.freeze(command);
12592
+ }
12593
+ var status_default = createReleaseStatusCommand();
12594
+
12595
+ // src/commands/plan/list.ts
12596
+ import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
12597
+ import { join as join15 } from "path";
12598
+ var USAGE3 = "rafa plan list";
12599
+ function listing(dir, stub) {
12600
+ const plan = join15(dir, planFileName(stub, false));
12601
+ const trackerPath = join15(dir, planFileName(stub, true));
12602
+ const tracker = isFile(trackerPath) ? trackerPath : null;
12603
+ const planModel = parsePlan(readFileSync21(plan, "utf8"));
12604
+ const counted2 = tracker === null ? planModel : parsePlan(readFileSync21(tracker, "utf8"));
12605
+ return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
12606
+ }
12607
+ function listPlans(plans) {
12608
+ const dir = plans.path;
12609
+ if (!existsSync19(dir))
12610
+ return { dir, plans: [] };
12611
+ const stubs = readdirSync10(dir).filter((name2) => isFile(join15(dir, name2))).map((name2) => stubOfPlanFile(name2)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
12612
+ return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
12613
+ }
12614
+ function rowNotes(plan) {
12615
+ const notes = [formatCounts(plan.tasks)];
12616
+ if (plan.tracker === null)
12617
+ notes.push("no tracker");
12618
+ if (plan.issues > 0)
12619
+ notes.push(plural(plan.issues, "issue"));
12620
+ return notes.join("; ");
12621
+ }
12622
+ function renderPlanList(list, dirLabel) {
12623
+ const label = `${dirLabel}/`;
12624
+ if (list.plans.length === 0)
12625
+ return [`No plans in ${label}.`];
12626
+ const width = Math.max(...list.plans.map((plan) => plan.stub.length));
12627
+ return [`Plans in ${label}:`, ...list.plans.map((plan) => ` ${plan.stub.padEnd(width)} ${rowNotes(plan)}`)];
12628
+ }
12629
+ var planListCommand = {
12630
+ name: "plan list",
12631
+ subject: "plan",
12632
+ action: "list",
12633
+ summary: "list the plans, with the tasks done in each",
12634
+ 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.",
12635
+ args: [],
12636
+ flags: [],
12637
+ examples: [
12638
+ {
12639
+ cmd: "rafa plan list",
12640
+ note: "Prints one row per plan: its stub, then its tasks done, blocked and open."
12641
+ },
12642
+ {
12643
+ cmd: "rafa plan list --output=json",
12644
+ note: "Writes a start event, then a result event whose data is the list."
12645
+ }
12646
+ ],
12647
+ outputs: ["text", "json"],
12648
+ run: async (context) => {
12649
+ expectNoArgument(context.args, USAGE3);
12650
+ const plans = resolvePlansDir(requireProject(context, USAGE3), USAGE3, (message) => {
12651
+ context.output.warn(message);
12652
+ });
12653
+ const list = listPlans(plans);
12654
+ if (context.outputMode === "json") {
12655
+ context.output.result(list);
12656
+ return;
12657
+ }
12658
+ for (const line of renderPlanList(list, plans.label))
12659
+ context.output.info(line);
12660
+ }
12661
+ };
12662
+ var list_default = Object.freeze(planListCommand);
12663
+
12664
+ // src/cli/prompt/confirm.ts
12665
+ import { createInterface as createInterface2 } from "readline";
12666
+ function createLinePrompter(input, output) {
12667
+ const buffered = [];
12668
+ const waiting = [];
12669
+ let ended = false;
12670
+ const lines = createInterface2({ input, terminal: false, crlfDelay: Infinity });
12671
+ lines.on("line", (line) => {
12672
+ const reader = waiting.shift();
12673
+ if (reader === undefined)
12674
+ buffered.push(line);
12675
+ else
12676
+ reader(line);
12677
+ });
12678
+ lines.on("close", () => {
12679
+ ended = true;
12680
+ for (const reader of waiting.splice(0))
12681
+ reader(null);
12682
+ });
12683
+ return {
12684
+ say: (text) => {
12685
+ output.write(`${text}
12686
+ `);
12687
+ },
12688
+ ask: async (question) => {
12689
+ output.write(question);
12690
+ const next = buffered.shift();
12691
+ if (next !== undefined)
12692
+ return next;
12693
+ if (ended)
12694
+ return null;
12695
+ return new Promise((settle) => {
12696
+ waiting.push(settle);
12697
+ });
12698
+ },
12699
+ close: () => {
12700
+ lines.close();
12701
+ }
12702
+ };
12703
+ }
12704
+
12705
+ // src/start/branch-decision.ts
12706
+ var REMOTE = "origin";
12707
+ var BRANCH_PREFIX2 = "feat/";
12708
+ var INDENT3 = " ";
12709
+ var UNTRACKED_PREFIX = "??";
12710
+ var MAX_LISTED_CHANGES2 = 10;
12711
+ var COUNT = /^\d+$/;
12712
+ function branchNameFor(planStub2) {
11675
12713
  return `${BRANCH_PREFIX2}${planStub2}`;
11676
12714
  }
11677
12715
  function localRef(branch) {
@@ -11720,9 +12758,9 @@ function plural3(count, singular, many) {
11720
12758
  return count === 1 ? `1 ${singular}` : `${count} ${many}`;
11721
12759
  }
11722
12760
  function listChanges2(entries) {
11723
- const shown = entries.slice(0, MAX_LISTED_CHANGES2).map((entry) => `${INDENT2}${entry}`);
12761
+ const shown = entries.slice(0, MAX_LISTED_CHANGES2).map((entry) => `${INDENT3}${entry}`);
11724
12762
  const hidden = entries.length - shown.length;
11725
- return hidden > 0 ? [...shown, `${INDENT2}... and ${hidden} more`] : shown;
12763
+ return hidden > 0 ? [...shown, `${INDENT3}... and ${hidden} more`] : shown;
11726
12764
  }
11727
12765
  function trackedChanges(tree) {
11728
12766
  return Object.freeze(tree.entries.filter((entry) => !entry.startsWith(UNTRACKED_PREFIX)));
@@ -11734,7 +12772,7 @@ function treeRefusal(tree, plan) {
11734
12772
  return [
11735
12773
  `\u274C Refusing to leave ${plan.base} for ${plan.branch}: ` + `the working tree has ${plural3(changes.length, "change", "changes")} to tracked files.`,
11736
12774
  ...listChanges2(changes),
11737
- `${INDENT2}Commit or stash them, then run again. Untracked files are left alone.`
12775
+ `${INDENT3}Commit or stash them, then run again. Untracked files are left alone.`
11738
12776
  ].join(`
11739
12777
  `);
11740
12778
  }
@@ -11797,10 +12835,10 @@ function refuse4(lines) {
11797
12835
  }
11798
12836
  function quotedLines(said) {
11799
12837
  return said === "" ? [] : said.split(`
11800
- `).map((line) => `${INDENT2}${line}`);
12838
+ `).map((line) => `${INDENT3}${line}`);
11801
12839
  }
11802
12840
  function stillOn(plan) {
11803
- return `${INDENT2}The run is still on ${plan.base}.`;
12841
+ return `${INDENT3}The run is still on ${plan.base}.`;
11804
12842
  }
11805
12843
  function failureHead(step, plan) {
11806
12844
  if (step.id === "fetch") {
@@ -11816,8 +12854,8 @@ function divergedRefusal(standing, plan) {
11816
12854
  const behind = plural3(standing.behind, "commit", "commits");
11817
12855
  return refuse4([
11818
12856
  `\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.`
12857
+ `${INDENT3}${plan.base} is ${ahead} ahead of ${REMOTE}/${plan.base} and ${behind} behind it.`,
12858
+ `${INDENT3}Push, rebase or reset ${plan.base}, then run again.`
11821
12859
  ]);
11822
12860
  }
11823
12861
  function readStepOutcome(step, result, plan) {
@@ -11830,7 +12868,7 @@ function readStepOutcome(step, result, plan) {
11830
12868
  if (standing === null) {
11831
12869
  return refuse4([
11832
12870
  `\u274C Could not ${step.label}.`,
11833
- `${INDENT2}git answered ${JSON.stringify(result.stdout)}, which is no pair of counts.`,
12871
+ `${INDENT3}git answered ${JSON.stringify(result.stdout)}, which is no pair of counts.`,
11834
12872
  stillOn(plan)
11835
12873
  ]);
11836
12874
  }
@@ -11920,11 +12958,11 @@ function parseArgs(argv, spec = {}) {
11920
12958
 
11921
12959
  // src/commands/loop/loop-sessions.ts
11922
12960
  import { execFileSync as execFileSync2 } from "child_process";
11923
- import { readFileSync as readFileSync20 } from "fs";
11924
- import { join as join15, relative as relative3 } from "path";
12961
+ import { readFileSync as readFileSync22 } from "fs";
12962
+ import { join as join16, relative as relative3 } from "path";
11925
12963
 
11926
12964
  // src/effort/store/task-finishes.ts
11927
- import { existsSync as existsSync18 } from "fs";
12965
+ import { existsSync as existsSync20 } from "fs";
11928
12966
  var SELECT_FINISHES = `
11929
12967
  SELECT collected_at FROM task_reports
11930
12968
  WHERE plan_stub IS ? AND outcome = 'done' AND collected_at >= ?
@@ -11935,7 +12973,7 @@ var SELECT_FINISHES = `
11935
12973
  `;
11936
12974
  function readTaskFinishes(repoRoot, query) {
11937
12975
  const path3 = sqliteStorePath(repoRoot);
11938
- if (!existsSync18(path3))
12976
+ if (!existsSync20(path3))
11939
12977
  return [];
11940
12978
  const bound = [query.planStub, query.since, query.planStub, query.since];
11941
12979
  const rows = withSqliteStore(path3, false, (db) => db.query(SELECT_FINISHES).all(...bound));
@@ -12019,9 +13057,9 @@ function readRecords(root, seams) {
12019
13057
  throw refusal2(`The session records under ${runsLabel(root)} cannot be read.`, messageOf(error));
12020
13058
  }
12021
13059
  }
12022
- function readBranchOrRefuse(root, readBranch) {
13060
+ function readBranchOrRefuse(root, readBranch2) {
12023
13061
  try {
12024
- return readBranch(root);
13062
+ return readBranch2(root);
12025
13063
  } catch (error) {
12026
13064
  throw refusal2("The branch checked out at the project root cannot be read, so no session is paired with it.", messageOf(error).split(`
12027
13065
  `)[0] ?? "", `Name the session with --${SESSION_ID_FLAG}; \`rafa loop list\` lists the running ones.`);
@@ -12065,11 +13103,11 @@ function writeSession(root, record, change) {
12065
13103
  }
12066
13104
  }
12067
13105
  function readSessionChecklist(root, record) {
12068
- const plan = join15(root, record.plan);
13106
+ const plan = join16(root, record.plan);
12069
13107
  const file = [trackerPathFor(plan), plan].find((path3) => isFile(path3));
12070
13108
  if (file === undefined)
12071
13109
  return null;
12072
- const content = readFileSync20(file, "utf8");
13110
+ const content = readFileSync22(file, "utf8");
12073
13111
  return { file, tasks: parsePlan(content).tasks, lines: content.split(`
12074
13112
  `) };
12075
13113
  }
@@ -12106,6 +13144,60 @@ function etaLine(eta) {
12106
13144
  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
13145
  }
12108
13146
 
13147
+ // src/next/epic-end.ts
13148
+ var CLOSE_MARK = "\uD83D\uDC49";
13149
+ var ROADMAP_MARK = "\uD83D\uDDFA";
13150
+ var RELEASE_MARK = "\uD83C\uDFF7";
13151
+ var EPIC_END_ROADMAP_COMMAND = "rafa roadmap";
13152
+ var EPIC_END_RELEASE_COMMAND = "rafa release tag";
13153
+ function epicCloseCommand(epic) {
13154
+ return `rafa epic close ${String(epic.number)}`;
13155
+ }
13156
+ function readEpicEndRelease(git, root, changelog) {
13157
+ const tags = readTags(git);
13158
+ if (tags.problem !== null)
13159
+ return { changelog, versions: [], problem: tags.problem };
13160
+ const untagged = readUntagged(root, changelog, tags.tags);
13161
+ return untagged.problem === null ? { changelog, versions: untagged.versions, problem: null } : { changelog, versions: [], problem: untagged.problem };
13162
+ }
13163
+ function namedVersions(versions) {
13164
+ if (versions.length === 1)
13165
+ return `${versions[0] ?? ""} is`;
13166
+ const head = versions.slice(0, -1).join(", ");
13167
+ return `${head} and ${versions[versions.length - 1] ?? ""} are`;
13168
+ }
13169
+ function epicEndLines(epic, release) {
13170
+ const lines = [
13171
+ `${CLOSE_MARK} epic #${String(epic.number)} ${epic.title} has run dry: close it through its gate` + ` \u2014 ${epicCloseCommand(epic)}`,
13172
+ `${ROADMAP_MARK} list the board's epics with their state and progress \u2014 ${EPIC_END_ROADMAP_COMMAND}`
13173
+ ];
13174
+ if (release === null || release.versions.length === 0)
13175
+ return Object.freeze(lines);
13176
+ return Object.freeze([
13177
+ ...lines,
13178
+ `${RELEASE_MARK} ${namedVersions(release.versions)} in ${release.changelog} with no tag: tag the release` + ` \u2014 ${EPIC_END_RELEASE_COMMAND}`
13179
+ ]);
13180
+ }
13181
+ function watchDryEpic() {
13182
+ let last = null;
13183
+ return {
13184
+ watch: (sources) => {
13185
+ last = null;
13186
+ const { board } = sources;
13187
+ const watched = {
13188
+ ...board,
13189
+ next: async () => {
13190
+ const reading2 = await board.next();
13191
+ last = reading2.dryEpic ?? null;
13192
+ return reading2;
13193
+ }
13194
+ };
13195
+ return { ...sources, board: watched };
13196
+ },
13197
+ last: () => last
13198
+ };
13199
+ }
13200
+
12109
13201
  // src/next/sources.ts
12110
13202
  var PREFIX14 = "rafa next";
12111
13203
  var DEFAULT_BASE_BRANCH = "main";
@@ -12125,6 +13217,13 @@ function listOnce3(read) {
12125
13217
  return kept;
12126
13218
  };
12127
13219
  }
13220
+ async function cancelledNotices(listing2) {
13221
+ try {
13222
+ return cancelledEpicNoticeLines(await listing2());
13223
+ } catch {
13224
+ return [];
13225
+ }
13226
+ }
12128
13227
  async function walkEpic(epic, listing2, readings) {
12129
13228
  const rows = await listing2();
12130
13229
  const read = readEpics({ issues: rows, claims: new Set, today: new Date(0) }).epics.find((candidate) => candidate.number === epic);
@@ -12133,11 +13232,20 @@ async function walkEpic(epic, listing2, readings) {
12133
13232
  throw new Error(`${PREFIX14}: epic #${String(epic)}, the current place, is not on the board listing, so its members cannot be read`);
12134
13233
  }
12135
13234
  const pick = await pickNextRoadmapLine(epicLines(read, row).lines, readings);
12136
- return { line: pick.line, passed: pick.skipped.length };
13235
+ return {
13236
+ line: pick.line,
13237
+ passed: pick.skipped.length,
13238
+ dry: pick.line === null ? { number: read.number, title: read.title } : null
13239
+ };
12137
13240
  }
12138
13241
  function ghNextBoard(options) {
12139
13242
  const { gh, git, configured, remote, root } = options;
12140
- const listing2 = listOnce3(options.listing ?? createGhBoardListing({ gh }));
13243
+ const listed = options.listing ?? createGhBoardListing({ gh });
13244
+ let asked = false;
13245
+ const listing2 = listOnce3(() => {
13246
+ asked = true;
13247
+ return listed();
13248
+ });
12141
13249
  const issues = memoiseIssues2(createGhSpecIssueReader({ gh }));
12142
13250
  return Object.freeze({
12143
13251
  next: async () => {
@@ -12156,11 +13264,13 @@ function ghNextBoard(options) {
12156
13264
  pullRequests: createGhOpenPullRequests({ gh })
12157
13265
  });
12158
13266
  const walk = epic === null ? await walkBoard(roadmap, { issues, readings, listing: listing2 }) : await walkEpic(epic, listing2, readings);
13267
+ const cancelled = options.noticeCancelled === true && asked ? await cancelledNotices(listing2) : [];
12159
13268
  return {
12160
13269
  roadmap,
12161
13270
  line: walk.line,
12162
13271
  passed: walk.passed,
12163
- problems: [...current?.notices ?? [], ...branches.problems]
13272
+ problems: [...current?.notices ?? [], ...cancelled, ...branches.problems],
13273
+ ...walk.dry === null ? {} : { dryEpic: walk.dry }
12164
13274
  };
12165
13275
  },
12166
13276
  blocking: async (issue3) => readBlockedLine(await issues(issue3), blockerStatesOf(issues)),
@@ -12169,8 +13279,12 @@ function ghNextBoard(options) {
12169
13279
  }
12170
13280
  async function walkBoard(roadmap, seams) {
12171
13281
  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 };
13282
+ const { descent, pick, dry } = await pickDescendedLine(parseRoadmapBody(read.body), seams);
13283
+ return {
13284
+ line: pick.line,
13285
+ passed: descent.passed.length + pick.skipped.length,
13286
+ dry: dry && descent.epic !== null ? { number: descent.epic.number, title: descent.epic.title } : null
13287
+ };
12174
13288
  }
12175
13289
  function nextProject(context) {
12176
13290
  if (context.project === null)
@@ -12203,7 +13317,7 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
12203
13317
  const git = (seams.openGit ?? createGitRunner)(project.root);
12204
13318
  const gh = (seams.openGh ?? ((root) => createGhRunner({ cwd: root })))(project.root);
12205
13319
  const loop = resolveLoopSeams({ isAlive: seams.isAlive });
12206
- const board = () => ghNextBoard({ gh, git, configured: config.roadmapIssue, root: project.root });
13320
+ const board = () => ghNextBoard({ gh, git, configured: config.roadmapIssue, root: project.root, noticeCancelled: true });
12207
13321
  const held = {
12208
13322
  base: config.prBase ?? DEFAULT_BASE_BRANCH,
12209
13323
  plans: plansDirAt(project.root, config.planDir),
@@ -12214,7 +13328,8 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
12214
13328
  return Object.freeze({
12215
13329
  ...held,
12216
13330
  board: board(),
12217
- answer: () => Object.freeze({ ...held, board: board() })
13331
+ answer: () => Object.freeze({ ...held, board: board() }),
13332
+ release: () => readEpicEndRelease(git, project.root, config.releaseChangelog)
12218
13333
  });
12219
13334
  }
12220
13335
 
@@ -12228,7 +13343,7 @@ function once2(read) {
12228
13343
  return held.value;
12229
13344
  };
12230
13345
  }
12231
- function readBranch(sources, note) {
13346
+ function readBranch2(sources, note) {
12232
13347
  const result = sources.git(["rev-parse", "--abbrev-ref", "HEAD"]);
12233
13348
  if (!result.ok) {
12234
13349
  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 +13423,7 @@ function hasRun(records, stub) {
12308
13423
  }
12309
13424
  function hasBranch(refs, stub) {
12310
13425
  const branch = `${BRANCH_PREFIX2}${stub}`;
12311
- return refs.some((ref) => ref === branch || ref.endsWith(`/${branch}`));
13426
+ return refs.some((ref3) => ref3 === branch || ref3.endsWith(`/${branch}`));
12312
13427
  }
12313
13428
  function openWorld(sources) {
12314
13429
  let problems = [];
@@ -12317,7 +13432,7 @@ function openWorld(sources) {
12317
13432
  };
12318
13433
  const remote = sources.remote ?? REMOTE;
12319
13434
  const runs = once2(() => sources.runs());
12320
- const branch = once2(() => readBranch(sources, note));
13435
+ const branch = once2(() => readBranch2(sources, note));
12321
13436
  const plans = once2(() => readPlans(sources, note));
12322
13437
  const refs = once2(() => readRefs(sources, remote, note));
12323
13438
  const roadmap = once2(async () => {
@@ -13021,13 +14136,13 @@ function versionLine() {
13021
14136
  }
13022
14137
 
13023
14138
  // 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";
14139
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
14140
+ import { dirname as dirname11, join as join17 } from "path";
13026
14141
  var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
13027
14142
  var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
13028
14143
  var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
13029
14144
  function noticesPath(home) {
13030
- return join16(home, ".rafa", "notices.json");
14145
+ return join17(home, ".rafa", "notices.json");
13031
14146
  }
13032
14147
  function isNoticeId(value) {
13033
14148
  return typeof value === "string" && NOTICE_IDS.includes(value);
@@ -13035,7 +14150,7 @@ function isNoticeId(value) {
13035
14150
  function readDismissed(home) {
13036
14151
  let raw;
13037
14152
  try {
13038
- raw = readFileSync21(noticesPath(home), "utf8");
14153
+ raw = readFileSync23(noticesPath(home), "utf8");
13039
14154
  } catch {
13040
14155
  return [];
13041
14156
  }
@@ -13055,7 +14170,7 @@ function writeDismissed(home, ids) {
13055
14170
  const all = NOTICE_IDS.filter((id2) => ids.includes(id2) || readDismissed(home).includes(id2));
13056
14171
  const path3 = noticesPath(home);
13057
14172
  mkdirSync8(dirname11(path3), { recursive: true });
13058
- writeFileSync9(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
14173
+ writeFileSync10(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
13059
14174
  `);
13060
14175
  }
13061
14176
  function pendingNotices(dismissed) {
@@ -13130,10 +14245,10 @@ async function requireNoticesAnswered() {
13130
14245
  }
13131
14246
 
13132
14247
  // src/start/branch.ts
13133
- var INDENT3 = " ";
14248
+ var INDENT4 = " ";
13134
14249
  var DEFAULT_BRANCH_SEAMS = Object.freeze({});
13135
- function refExists(git, ref) {
13136
- return git(["show-ref", "--verify", "--quiet", ref]).ok;
14250
+ function refExists(git, ref3) {
14251
+ return git(["show-ref", "--verify", "--quiet", ref3]).ok;
13137
14252
  }
13138
14253
  function terminalReading(seams) {
13139
14254
  return seams.isTerminal ?? (() => process.stdin.isTTY === true);
@@ -13166,8 +14281,8 @@ function readWorkingTree(git, plan) {
13166
14281
  return parseWorkingTree(result.stdout);
13167
14282
  throw new CommandExit(1, [
13168
14283
  `\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}.`
14284
+ `${INDENT4}${gitSaid(result)}`,
14285
+ `${INDENT4}The run is still on ${plan.base}.`
13171
14286
  ].join(`
13172
14287
  `));
13173
14288
  }
@@ -13188,9 +14303,9 @@ ${refused}`);
13188
14303
  if (outcome.kind === "refuse")
13189
14304
  throw new CommandExit(1, `
13190
14305
  ${outcome.message}`);
13191
- activeOutput().info(`${INDENT3}${step.label}: done`);
14306
+ activeOutput().info(`${INDENT4}${step.label}: done`);
13192
14307
  }
13193
- activeOutput().info(`${INDENT3}The run is on ${plan.branch}.`);
14308
+ activeOutput().info(`${INDENT4}The run is on ${plan.branch}.`);
13194
14309
  return Object.freeze({ kind: "moved", branch: plan.branch, route, steps });
13195
14310
  }
13196
14311
  async function offerRunBranch(request, seams = DEFAULT_BRANCH_SEAMS) {
@@ -14001,8 +15116,8 @@ function reportLessons(record, context, now) {
14001
15116
  }
14002
15117
 
14003
15118
  // src/effort/store/absences.ts
14004
- import { randomUUID as randomUUID4 } from "crypto";
14005
- import { existsSync as existsSync19 } from "fs";
15119
+ import { randomUUID as randomUUID5 } from "crypto";
15120
+ import { existsSync as existsSync21 } from "fs";
14006
15121
  var ABSENCE_REASONS = {
14007
15122
  "no-block": true,
14008
15123
  "unclosed-block": true,
@@ -14022,141 +15137,47 @@ function refusedWrite7(reason) {
14022
15137
  return new Error(`effort store: report absence write ${reason}; nothing written`);
14023
15138
  }
14024
15139
  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);
15140
+ const { reason, text, block } = absence;
15141
+ if (!REPORT_ABSENCE_REASONS.includes(reason)) {
15142
+ const expected2 = REPORT_ABSENCE_REASONS.join(", ");
15143
+ throw refusedWrite7(`has reason ${describeValue3(reason)}, not one of ${expected2}`);
15144
+ }
15145
+ if (typeof text !== "string") {
15146
+ throw refusedWrite7(`has detail ${describeValue3(text)}, not a string`);
15147
+ }
15148
+ const detailProblem = textProblem(text);
15149
+ if (detailProblem !== null)
15150
+ throw refusedWrite7(`has a detail that ${detailProblem}`);
15151
+ if (block === null)
15152
+ return;
15153
+ if (typeof block.body !== "string") {
15154
+ throw refusedWrite7(`has block body ${describeValue3(block.body)}, not a string`);
15155
+ }
15156
+ if (LONE_SURROGATE.test(block.body)) {
15157
+ throw refusedWrite7("has a block body holding a lone UTF-16 surrogate");
14134
15158
  }
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
15159
  }
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) {
15160
+ function writeReportAbsence(repoRoot, write, seams = {}) {
15161
+ checkDispatch("report absence", write.dispatch, write.outcome);
15162
+ checkAbsence(write.absence);
14148
15163
  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
- }));
15164
+ const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
15165
+ const { reason, text, block } = write.absence;
15166
+ const collectedAt = (seams.now ?? (() => new Date))().toISOString();
15167
+ const id2 = (seams.newId ?? randomUUID5)();
15168
+ const values = [
15169
+ id2,
15170
+ sessionId,
15171
+ planStub2,
15172
+ taskLine,
15173
+ reason,
15174
+ text,
15175
+ block?.body ?? null,
15176
+ write.outcome,
15177
+ collectedAt
15178
+ ];
15179
+ const appended2 = withSqliteStore(path3, !existsSync21(path3), (db) => db.query(INSERT_ABSENCE).run(...values).changes);
15180
+ return { path: path3, appended: appended2, skipped: 1 - appended2 };
14160
15181
  }
14161
15182
 
14162
15183
  // src/effort/store/triage.ts
@@ -14416,8 +15437,8 @@ function renderLessonsSection(lessons) {
14416
15437
  }
14417
15438
 
14418
15439
  // src/utils/progress.ts
14419
- import { existsSync as existsSync21, writeFileSync as writeFileSync10 } from "fs";
14420
- import { join as join17 } from "path";
15440
+ import { existsSync as existsSync22, writeFileSync as writeFileSync11 } from "fs";
15441
+ import { join as join18 } from "path";
14421
15442
  var PROGRESS_FILE_NAME = "progress.txt";
14422
15443
  var PROGRESS_CAP_BYTES = 16000;
14423
15444
  var LISTED_FIELDS = [
@@ -14459,7 +15480,7 @@ var ESCAPED_CODE_POINTS = new Set([
14459
15480
  8233
14460
15481
  ]);
14461
15482
  function progressFilePath(repoRoot) {
14462
- return join17(repoRoot, PROGRESS_FILE_NAME);
15483
+ return join18(repoRoot, PROGRESS_FILE_NAME);
14463
15484
  }
14464
15485
  function escapeValue(value) {
14465
15486
  let escaped = "";
@@ -14520,7 +15541,7 @@ function renderProgressText(findings, capBytes = PROGRESS_CAP_BYTES) {
14520
15541
  }
14521
15542
  function readProgressFindings(repoRoot, planStub2) {
14522
15543
  const path3 = sqliteStorePath(repoRoot);
14523
- if (!existsSync21(path3))
15544
+ if (!existsSync22(path3))
14524
15545
  return [];
14525
15546
  const rows = withSqliteStore(path3, false, (db) => {
14526
15547
  const query = db.query(SELECT_FINDINGS2);
@@ -14541,12 +15562,12 @@ function readProgressFindings(repoRoot, planStub2) {
14541
15562
  function writeProgress(repoRoot, planStub2) {
14542
15563
  const render = renderProgressText(readProgressFindings(repoRoot, planStub2));
14543
15564
  const path3 = progressFilePath(repoRoot);
14544
- writeFileSync10(path3, render.text, "utf8");
15565
+ writeFileSync11(path3, render.text, "utf8");
14545
15566
  return { ...render, path: path3, storePath: sqliteStorePath(repoRoot) };
14546
15567
  }
14547
15568
 
14548
15569
  // src/inventory/search/rank.ts
14549
- import { readFileSync as readFileSync22 } from "fs";
15570
+ import { readFileSync as readFileSync24 } from "fs";
14550
15571
  var SEARCH_LIMIT = 12;
14551
15572
  var MIN_WORD_LENGTH = 2;
14552
15573
  var MIN_PREFIX_LENGTH = 4;
@@ -14668,7 +15689,7 @@ function rankCandidates(question, candidates, limit = SEARCH_LIMIT) {
14668
15689
  return [];
14669
15690
  return candidates.map((candidate) => scoreCandidate(candidate, words)).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
14670
15691
  }
14671
- function readRankCandidate(record, read = (path3) => readFileSync22(path3, "utf8")) {
15692
+ function readRankCandidate(record, read = (path3) => readFileSync24(path3, "utf8")) {
14672
15693
  let text;
14673
15694
  try {
14674
15695
  text = read(record.path);
@@ -14897,16 +15918,16 @@ async function handOut(handout, task, resolution, repoRoot) {
14897
15918
 
14898
15919
  // src/start/preflight.ts
14899
15920
  import { randomUUID as randomUUID7 } from "crypto";
14900
- import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
15921
+ import { existsSync as existsSync23, readFileSync as readFileSync26 } from "fs";
14901
15922
  import { homedir as homedir4 } from "os";
14902
15923
  import { basename as basename7 } from "path";
14903
15924
 
14904
15925
  // src/preflight/first-dispatch.ts
14905
- import { readFileSync as readFileSync23 } from "fs";
15926
+ import { readFileSync as readFileSync25 } from "fs";
14906
15927
  var TICKED_TASK = /^- \[[xX]\] (.+)/;
14907
15928
  function readIfReadable(path3) {
14908
15929
  try {
14909
- return readFileSync23(path3, "utf8");
15930
+ return readFileSync25(path3, "utf8");
14910
15931
  } catch {
14911
15932
  return null;
14912
15933
  }
@@ -14939,11 +15960,11 @@ function knownMissingNotice(lines) {
14939
15960
  }
14940
15961
  function agentSourcePath(planPath) {
14941
15962
  const tracker = trackerPathFor(planPath);
14942
- return existsSync22(tracker) ? tracker : planPath;
15963
+ return existsSync23(tracker) ? tracker : planPath;
14943
15964
  }
14944
15965
  function readIfReadable2(path3) {
14945
15966
  try {
14946
- return readFileSync24(path3, "utf8");
15967
+ return readFileSync26(path3, "utf8");
14947
15968
  } catch {
14948
15969
  return null;
14949
15970
  }
@@ -15332,14 +16353,14 @@ async function holdWhilePaused(options) {
15332
16353
  }
15333
16354
 
15334
16355
  // src/start/plan-path.ts
15335
- import { existsSync as existsSync23 } from "fs";
16356
+ import { existsSync as existsSync24 } from "fs";
15336
16357
  import { resolve as resolve11 } from "path";
15337
16358
  var DEFAULT_PLAN_FILE = "PLAN.md";
15338
16359
  function resolvePlanPath(repoRoot, planDir, planArg) {
15339
16360
  if (planArg)
15340
16361
  return resolve11(repoRoot, planArg);
15341
16362
  const inPlanDir = resolve11(repoRoot, planDir, DEFAULT_PLAN_FILE);
15342
- return existsSync23(inPlanDir) ? inPlanDir : resolve11(repoRoot, DEFAULT_PLAN_FILE);
16363
+ return existsSync24(inPlanDir) ? inPlanDir : resolve11(repoRoot, DEFAULT_PLAN_FILE);
15343
16364
  }
15344
16365
 
15345
16366
  // src/utils/git.ts
@@ -15462,250 +16483,68 @@ function pollChecks(io, prNumber, timeoutMs) {
15462
16483
  now: io.now,
15463
16484
  sleep: io.sleep,
15464
16485
  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;
16486
+ const secs = Math.round(elapsedMs / 1000);
16487
+ activeOutput().info(` [${secs}s] ${verdict} \u2014 ${rows.length} check(s)`);
15662
16488
  }
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;
16489
+ });
15675
16490
  }
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" };
16491
+ function reportSettledVerdict(prNumber, result) {
16492
+ if (result.verdict === "green") {
16493
+ activeOutput().info(`
16494
+ \u2705 CI green on PR #${prNumber}:`);
16495
+ activeOutput().info(formatRows(result.rows));
16496
+ return true;
16497
+ }
16498
+ if (result.verdict === "timeout") {
16499
+ activeOutput().warn(`
16500
+ \u26A0\uFE0F CI still running after ${Math.round(result.elapsedMs / 1000)}s. Not waiting further.`);
16501
+ activeOutput().warn(formatRows(result.rows));
16502
+ activeOutput().warn(` Check it yourself: gh pr checks ${prNumber}`);
16503
+ return true;
16504
+ }
16505
+ return false;
15684
16506
  }
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 };
16507
+ async function handleNoChecks(io, branch, prNumber, merge) {
16508
+ if (merge?.state === "merged") {
16509
+ activeOutput().info(`
16510
+ \u2705 PR #${prNumber} is already merged.`);
16511
+ return "stop";
16512
+ }
16513
+ if (merge !== null && merge.mergeStateStatus !== "DIRTY") {
16514
+ activeOutput().warn(`
16515
+ \u26A0\uFE0F PR #${prNumber} reports no checks and is not conflicting`);
16516
+ activeOutput().warn(` (GitHub says it is ${merge.mergeable}, merge state ${merge.mergeStateStatus}).`);
16517
+ activeOutput().warn(" Most likely no workflow matches the changed paths. Nothing to repair.");
16518
+ return "stop";
16519
+ }
16520
+ activeOutput().warn(`
16521
+ \u274C PR #${prNumber} has no checks \u2014 it does not merge cleanly, so GitHub scheduled no run.`);
16522
+ 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);
16523
+ if (exitCode !== 0) {
16524
+ activeOutput().error(`
16525
+ \u274C Conflict-repair session failed (exit ${exitCode}).`);
16526
+ return "stop";
16527
+ }
16528
+ return "next";
15702
16529
  }
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}`;
16530
+ async function handleRedChecks(io, branch, prNumber, rows) {
16531
+ activeOutput().warn(`
16532
+ \u274C CI red on PR #${prNumber}:`);
16533
+ activeOutput().warn(formatRows(rows));
16534
+ const failed3 = failingRows(rows);
16535
+ const exitCode = await repairPullRequest(prNumber, branch, "its CI checks failed.", ["The failing checks are:", formatRows(failed3)].join(`
16536
+ `), io.runRepair);
16537
+ if (exitCode !== 0) {
16538
+ activeOutput().error(`
16539
+ \u274C CI-repair session failed (exit ${exitCode}).`);
16540
+ return "stop";
16541
+ }
16542
+ return "next";
15707
16543
  }
15708
16544
 
16545
+ // src/release/prepare.ts
16546
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync12 } from "fs";
16547
+
15709
16548
  // src/release/enabled.ts
15710
16549
  import { statSync as statSync11 } from "fs";
15711
16550
  import { resolve as resolve12 } from "path";
@@ -15734,165 +16573,6 @@ function resolveReleaseEnabled(settings, repoRoot) {
15734
16573
  return { enabled: settings.releaseEnabled, source: "config", versionFile, changelog };
15735
16574
  }
15736
16575
 
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
16576
  // src/release/prepare.ts
15897
16577
  function levelFields(reading2) {
15898
16578
  return { level: reading2.level, levelSource: reading2.source, notesLevel: reading2.notesLevel };
@@ -15947,9 +16627,9 @@ function planVersion(input, versionFile, level) {
15947
16627
  }
15948
16628
  return { refused: false, version: version2, baseVersion: base.version, fetched: base.fetched, problems: base.problems };
15949
16629
  }
15950
- function textOf(path3) {
16630
+ function textOf2(path3) {
15951
16631
  try {
15952
- return readFileSync26(path3, "utf8");
16632
+ return readFileSync27(path3, "utf8");
15953
16633
  } catch {
15954
16634
  return null;
15955
16635
  }
@@ -15963,14 +16643,14 @@ function writeText(path3, text) {
15963
16643
  }
15964
16644
  }
15965
16645
  function writeVersionFile(file, version2) {
15966
- const before = textOf(file.resolved);
16646
+ const before = textOf2(file.resolved);
15967
16647
  if (before === null) {
15968
16648
  return { edit: null, problem: `${file.path} could not be read, so ${version2} was not written to it` };
15969
16649
  }
15970
16650
  const written = writeManifestVersion(file.resolved, version2);
15971
16651
  if (written.problem !== null)
15972
16652
  return { edit: null, problem: written.problem };
15973
- const after = textOf(file.resolved);
16653
+ const after = textOf2(file.resolved);
15974
16654
  if (after === null) {
15975
16655
  return { edit: null, problem: `${file.path} could not be read back after ${version2} was written to it` };
15976
16656
  }
@@ -16011,7 +16691,7 @@ function prepareRelease(input) {
16011
16691
  if (!files.changelog.present) {
16012
16692
  return skip("changelog-missing", `no version bump and no changelog entry: ${files.changelog.path} is not there`, reading2);
16013
16693
  }
16014
- const changelogBefore = textOf(files.changelog.resolved);
16694
+ const changelogBefore = textOf2(files.changelog.resolved);
16015
16695
  if (changelogBefore === null) {
16016
16696
  return skip("changelog-unreadable", `no version bump and no changelog entry: ${files.changelog.path} could not be read`, reading2);
16017
16697
  }
@@ -16062,7 +16742,7 @@ function changelogFailure(failed3, versionEdit, restored) {
16062
16742
  }
16063
16743
 
16064
16744
  // src/release/verify.ts
16065
- import { readFileSync as readFileSync27, writeFileSync as writeFileSync13 } from "fs";
16745
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "fs";
16066
16746
  function changelogInsertionSpan(before, after) {
16067
16747
  const beforeLines = before.split(`
16068
16748
  `);
@@ -16077,9 +16757,9 @@ function changelogInsertionSpan(before, after) {
16077
16757
  suffix += 1;
16078
16758
  return { prefix, suffix, inserted: afterLines.slice(prefix, afterLines.length - suffix) };
16079
16759
  }
16080
- function textOf2(path3) {
16760
+ function textOf3(path3) {
16081
16761
  try {
16082
- return readFileSync27(path3, "utf8");
16762
+ return readFileSync28(path3, "utf8");
16083
16763
  } catch {
16084
16764
  return null;
16085
16765
  }
@@ -16147,7 +16827,7 @@ function checkVersion(prepared) {
16147
16827
  const file = prepared.versionFile;
16148
16828
  if (file === null)
16149
16829
  return null;
16150
- const text = textOf2(file.resolved);
16830
+ const text = textOf3(file.resolved);
16151
16831
  if (text === null) {
16152
16832
  return { reason: "version-unreadable", because: `${file.path} could not be read back after the wrap-up session` };
16153
16833
  }
@@ -16165,7 +16845,7 @@ function checkVersion(prepared) {
16165
16845
  }
16166
16846
  function verifyRelease(prepared) {
16167
16847
  const changelog = prepared.changelog;
16168
- const current = textOf2(changelog.resolved);
16848
+ const current = textOf3(changelog.resolved);
16169
16849
  if (current === null) {
16170
16850
  return refuse5(prepared, "changelog-unreadable", `${changelog.path} could not be read back after the wrap-up session`);
16171
16851
  }
@@ -16414,11 +17094,11 @@ async function finishRelease(input, seams = {}) {
16414
17094
  }
16415
17095
 
16416
17096
  // src/start/risk-total.ts
16417
- import { readFileSync as readFileSync29 } from "fs";
17097
+ import { readFileSync as readFileSync30 } from "fs";
16418
17098
 
16419
17099
  // src/plan/risk.ts
16420
- import { readFileSync as readFileSync28 } from "fs";
16421
- import { isAbsolute as isAbsolute4, join as join18, relative as relative4 } from "path";
17100
+ import { readFileSync as readFileSync29 } from "fs";
17101
+ import { isAbsolute as isAbsolute4, join as join19, relative as relative4 } from "path";
16422
17102
 
16423
17103
  // src/plan/risk/accounts.ts
16424
17104
  var PUSH_REMOTE = "origin";
@@ -16960,7 +17640,7 @@ function toolsFinding(task, plan, seams) {
16960
17640
  return null;
16961
17641
  }
16962
17642
  function displayPath(path3, seams) {
16963
- const absolute = isAbsolute4(path3) ? path3 : join18(seams.repoRoot, path3);
17643
+ const absolute = isAbsolute4(path3) ? path3 : join19(seams.repoRoot, path3);
16964
17644
  const within = (root) => {
16965
17645
  const step = relative4(root, absolute);
16966
17646
  return step === "" || step.startsWith("..") || isAbsolute4(step) ? null : step;
@@ -16977,7 +17657,7 @@ function located(finding2, file, task) {
16977
17657
  }
16978
17658
  function readText5(path3) {
16979
17659
  try {
16980
- return readFileSync28(path3, "utf8");
17660
+ return readFileSync29(path3, "utf8");
16981
17661
  } catch {
16982
17662
  return null;
16983
17663
  }
@@ -16985,7 +17665,7 @@ function readText5(path3) {
16985
17665
  function findSkillFile(name2, seams) {
16986
17666
  const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
16987
17667
  for (const { dir } of tiers) {
16988
- const path3 = join18(dir, name2, "SKILL.md");
17668
+ const path3 = join19(dir, name2, "SKILL.md");
16989
17669
  if (readText5(path3) !== null)
16990
17670
  return path3;
16991
17671
  }
@@ -17079,7 +17759,7 @@ function spawningRunners(root) {
17079
17759
  }
17080
17760
  async function readRiskTotalLine(input, seams = {}) {
17081
17761
  const makeRunners = seams.runners ?? spawningRunners;
17082
- const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync29(input.planPath, "utf8") }, {
17762
+ const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync30(input.planPath, "utf8") }, {
17083
17763
  repoRoot: input.repoRoot,
17084
17764
  home: input.home,
17085
17765
  settingSources: input.config.settingSources,
@@ -17255,11 +17935,11 @@ function announcePlanIssues(planContent) {
17255
17935
  }
17256
17936
 
17257
17937
  // src/start/runtime.ts
17258
- import { existsSync as existsSync24, readdirSync as readdirSync10, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
17938
+ import { existsSync as existsSync25, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync12 } from "fs";
17259
17939
  import { homedir as homedir5 } from "os";
17260
- import { isAbsolute as isAbsolute5, join as join19, relative as relative6, resolve as resolve13, sep } from "path";
17940
+ import { isAbsolute as isAbsolute5, join as join20, relative as relative6, resolve as resolve13, sep } from "path";
17261
17941
  var RUNTIME_FLAG = "--runtime";
17262
- var RUNTIME_SUBDIR = join19(".rafa", "runtime");
17942
+ var RUNTIME_SUBDIR = join20(".rafa", "runtime");
17263
17943
  var RUNTIME_ENTRY = "cli.js";
17264
17944
  var SOURCE_DIR = "src";
17265
17945
  var RUNTIME_COMMAND = ["start"];
@@ -17286,7 +17966,7 @@ function resolveRuntime(value, places) {
17286
17966
  throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
17287
17967
  }
17288
17968
  const isVersion = VERSION_SHAPE2.test(value);
17289
- const named = isVersion ? join19(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve13(places.cwd, value);
17969
+ const named = isVersion ? join20(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve13(places.cwd, value);
17290
17970
  refuseInsideSource(value, named, places);
17291
17971
  const entry = entryOf2(value, named, isVersion, places.home);
17292
17972
  const real = realpathSync3(entry);
@@ -17297,7 +17977,7 @@ function entryOf2(value, named, isVersion, home) {
17297
17977
  if (isVersion) {
17298
17978
  if (isFile2(named))
17299
17979
  return named;
17300
- const root = join19(home, RUNTIME_SUBDIR);
17980
+ const root = join20(home, RUNTIME_SUBDIR);
17301
17981
  const installed = installedVersions(root);
17302
17982
  throw refusal3([
17303
17983
  `\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
@@ -17306,7 +17986,7 @@ function entryOf2(value, named, isVersion, home) {
17306
17986
  }
17307
17987
  if (isFile2(named))
17308
17988
  return named;
17309
- const inside = join19(named, RUNTIME_ENTRY);
17989
+ const inside = join20(named, RUNTIME_ENTRY);
17310
17990
  if (isDirectory(named) && isFile2(inside))
17311
17991
  return inside;
17312
17992
  throw refusal3([
@@ -17324,8 +18004,8 @@ function refuseInsideSource(value, path3, places) {
17324
18004
  ]);
17325
18005
  }
17326
18006
  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));
18007
+ const dirs = [join20(cwd, SOURCE_DIR), join20(root, SOURCE_DIR)];
18008
+ const real = dirs.filter((dir) => existsSync25(dir)).map((dir) => realpathSync3(dir));
17329
18009
  return [...new Set([...dirs, ...real])];
17330
18010
  }
17331
18011
  function isInside2(path3, dir) {
@@ -17334,7 +18014,7 @@ function isInside2(path3, dir) {
17334
18014
  }
17335
18015
  function installedVersions(root) {
17336
18016
  try {
17337
- return readdirSync10(root).filter((name2) => isFile2(join19(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
18017
+ return readdirSync11(root).filter((name2) => isFile2(join20(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
17338
18018
  } catch {
17339
18019
  return [];
17340
18020
  }
@@ -17465,11 +18145,11 @@ function eventOf(line) {
17465
18145
  }
17466
18146
 
17467
18147
  // src/triage/triage.ts
17468
- import { basename as basename8, join as join20 } from "path";
18148
+ import { basename as basename8, join as join21 } from "path";
17469
18149
 
17470
18150
  // src/effort/store/tracker-refs.ts
17471
18151
  import { randomUUID as randomUUID10 } from "crypto";
17472
- import { existsSync as existsSync25 } from "fs";
18152
+ import { existsSync as existsSync26 } from "fs";
17473
18153
  var SELECT_HELD2 = `
17474
18154
  SELECT seq, tracker_ref FROM findings
17475
18155
  WHERE session_id = ? AND artifact = ?
@@ -17535,17 +18215,17 @@ function storedRefOf(value) {
17535
18215
  if (problem !== null)
17536
18216
  return `whose ${field5} ${problem}`;
17537
18217
  }
17538
- const ref = {
18218
+ const ref3 = {
17539
18219
  opt: fields.opt,
17540
18220
  kind: fields.kind,
17541
18221
  externalId: fields.externalId,
17542
18222
  url: fields.url
17543
18223
  };
17544
18224
  if (typeof fields.module === "string")
17545
- ref.module = fields.module;
18225
+ ref3.module = fields.module;
17546
18226
  if (typeof fields.repo === "string")
17547
- ref.repo = fields.repo;
17548
- return { ref, json: JSON.stringify(ref) };
18227
+ ref3.repo = fields.repo;
18228
+ return { ref: ref3, json: JSON.stringify(ref3) };
17549
18229
  }
17550
18230
  function heldRefOf(path3, seq, text) {
17551
18231
  let parsed;
@@ -17592,7 +18272,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
17592
18272
  wanted.json,
17593
18273
  collectedAt
17594
18274
  ];
17595
- const [action, stored] = withSqliteStore(path3, !existsSync25(path3), (db) => {
18275
+ const [action, stored] = withSqliteStore(path3, !existsSync26(path3), (db) => {
17596
18276
  const place = db.transaction(() => placeRef(db, path3, write, wanted, insertValues));
17597
18277
  return place.immediate();
17598
18278
  });
@@ -17601,7 +18281,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
17601
18281
  function readTrackerRef(repoRoot, artifact) {
17602
18282
  checkArtifact("read", artifact);
17603
18283
  const path3 = sqliteStorePath(repoRoot);
17604
- if (!existsSync25(path3))
18284
+ if (!existsSync26(path3))
17605
18285
  return null;
17606
18286
  const oldest = withSqliteStore(path3, false, (db) => db.query(SELECT_OLDEST).get(artifact));
17607
18287
  return oldest === null ? null : heldRefOf(path3, oldest.seq, oldest.tracker_ref).ref;
@@ -17613,28 +18293,28 @@ var ARTIFACT_REF_KINDS = new Set(["path", "symbol"]);
17613
18293
  var UNREAD_STAMP = "unread";
17614
18294
  var REFS_INTRO = "The paths and symbols the artifact names, each with its target's" + " fingerprint when this bug was filed.";
17615
18295
  function artifactRefs(artifact) {
17616
- return extractRefs(artifact).filter((ref) => ARTIFACT_REF_KINDS.has(ref.kind));
18296
+ return extractRefs(artifact).filter((ref3) => ARTIFACT_REF_KINDS.has(ref3.kind));
17617
18297
  }
17618
18298
  function stampWord(live) {
17619
18299
  return live.kind === "unreadable" ? UNREAD_STAMP : fingerprintText(live);
17620
18300
  }
17621
18301
  async function stampArtifactRefs(artifact, verify) {
17622
18302
  const stamps = [];
17623
- for (const ref of artifactRefs(artifact)) {
18303
+ for (const ref3 of artifactRefs(artifact)) {
17624
18304
  let stamp;
17625
18305
  try {
17626
- stamp = stampWord(await verify({ kind: ref.kind, text: ref.text }));
18306
+ stamp = stampWord(await verify({ kind: ref3.kind, text: ref3.text }));
17627
18307
  } catch {
17628
18308
  stamp = UNREAD_STAMP;
17629
18309
  }
17630
- stamps.push(Object.freeze({ kind: ref.kind, text: ref.text, stamp }));
18310
+ stamps.push(Object.freeze({ kind: ref3.kind, text: ref3.text, stamp }));
17631
18311
  }
17632
18312
  return Object.freeze(stamps);
17633
18313
  }
17634
18314
  function refsSection(stamps) {
17635
18315
  if (stamps.length === 0)
17636
18316
  return null;
17637
- const lines = stamps.map((ref) => `- ${ref.kind} \`${ref.text}\`: \`${ref.stamp}\``);
18317
+ const lines = stamps.map((ref3) => `- ${ref3.kind} \`${ref3.text}\`: \`${ref3.stamp}\``);
17638
18318
  return [`## ${REFS_HEADING}`, REFS_INTRO, lines.join(`
17639
18319
  `)].join(`
17640
18320
 
@@ -17657,19 +18337,19 @@ async function noIssues() {
17657
18337
  }
17658
18338
  function createArtifactRefsVerifier(repoRoot, env) {
17659
18339
  let made = null;
17660
- return async (ref) => {
18340
+ return async (ref3) => {
17661
18341
  made ??= createRefVerifier({
17662
18342
  issues: noIssues,
17663
18343
  git: createGitRunner(repoRoot),
17664
18344
  outline: tsSymbolsOutliner({ cwd: repoRoot, env }),
17665
18345
  roster: EMPTY_ROSTER
17666
18346
  });
17667
- return made(ref);
18347
+ return made(ref3);
17668
18348
  };
17669
18349
  }
17670
18350
 
17671
18351
  // src/triage/triage.ts
17672
- var PRIVATE_TRIAGE_DIR = join20(".rafa", "triage", "private");
18352
+ var PRIVATE_TRIAGE_DIR = join21(".rafa", "triage", "private");
17673
18353
  var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
17674
18354
  var TRIAGE_MODULE = "unassigned";
17675
18355
  var BLOCKER_SEPARATOR = "; ";
@@ -17680,7 +18360,7 @@ var MIN_FENCE_LENGTH = 3;
17680
18360
  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
18361
  var COMMENT_OPENING = "Reported again by a rafa task session.";
17682
18362
  function privateTriageDir(repoRoot) {
17683
- return join20(repoRoot, PRIVATE_TRIAGE_DIR);
18363
+ return join21(repoRoot, PRIVATE_TRIAGE_DIR);
17684
18364
  }
17685
18365
  function createPrivateTriageTracker(repoRoot, options = {}) {
17686
18366
  return createLocalTracker({
@@ -17754,7 +18434,7 @@ function fenced(value) {
17754
18434
  return `${fence}
17755
18435
  ${body}${fence}`;
17756
18436
  }
17757
- function issueTitle(redactedWhat) {
18437
+ function issueTitle2(redactedWhat) {
17758
18438
  const line = oneLine5(redactedWhat);
17759
18439
  const points = Array.from(line);
17760
18440
  if (points.length <= TITLE_MAX_LENGTH)
@@ -17799,7 +18479,7 @@ async function filingFor(what, bug, options, redact, verifyRefs) {
17799
18479
  return {
17800
18480
  draft: {
17801
18481
  opt: 0,
17802
- title: issueTitle(redact(what)),
18482
+ title: issueTitle2(redact(what)),
17803
18483
  body: issueText(ISSUE_OPENING, values, redact),
17804
18484
  type: "bug",
17805
18485
  module: TRIAGE_MODULE,
@@ -17831,23 +18511,23 @@ function resultOf(run, action, fields = {}) {
17831
18511
  ...fields
17832
18512
  };
17833
18513
  }
17834
- async function settle(run, action, ref, foundBy) {
18514
+ async function settle(run, action, ref3, foundBy) {
17835
18515
  const { store } = run.route;
17836
18516
  const { key } = run.filing;
17837
18517
  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 });
18518
+ return resultOf(run, action, { ref: ref3, foundBy });
18519
+ const stored = await step(run, "storing the reference", () => store(key, ref3));
18520
+ return stored.ok ? resultOf(run, action, { ref: ref3, foundBy, stored: stored.value }) : resultOf(run, action, { ref: ref3, foundBy, problem: stored.problem });
17841
18521
  }
17842
18522
  async function fileNew(run) {
17843
18523
  const created = await step(run, `the ${run.route.channel} tracker create`, () => run.route.tracker.create(run.filing.draft));
17844
18524
  return created.ok ? settle(run, "filed", created.value, null) : resultOf(run, "failed", { problem: created.problem });
17845
18525
  }
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));
18526
+ async function commentOn(run, ref3, foundBy) {
18527
+ const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(ref3, run.filing.comment));
17848
18528
  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);
18529
+ return resultOf(run, "failed", { ref: ref3, foundBy, problem: commented.problem });
18530
+ return foundBy === "store" ? resultOf(run, "commented", { ref: ref3, foundBy }) : settle(run, "commented", ref3, foundBy);
17851
18531
  }
17852
18532
  async function triageBug(run) {
17853
18533
  const { route, filing } = run;
@@ -17895,11 +18575,11 @@ async function triageReport(options) {
17895
18575
  channel: "public",
17896
18576
  tracker: options.tracker,
17897
18577
  readStored: (key) => readTrackerRef(repoRoot, key),
17898
- store: (key, ref) => writeTrackerRef(repoRoot, {
18578
+ store: (key, ref3) => writeTrackerRef(repoRoot, {
17899
18579
  dispatch: options.dispatch,
17900
18580
  outcome: options.outcome,
17901
18581
  artifact: key,
17902
- ref
18582
+ ref: ref3
17903
18583
  }, options.seams).action
17904
18584
  },
17905
18585
  private: { channel: "private", tracker: privateTracker, readStored: null, store: null }
@@ -17926,7 +18606,7 @@ async function triageReport(options) {
17926
18606
  // src/start/triage.ts
17927
18607
  var NOT_RESOLVED_REASON = "start triage: no public tracker is resolved for a report with no public bug";
17928
18608
  var LINE_PREFIX = "Triage: ";
17929
- var INDENT4 = " ";
18609
+ var INDENT5 = " ";
17930
18610
  var FOUND_BY = {
17931
18611
  store: "its stored reference",
17932
18612
  find: "a tracker find"
@@ -17950,8 +18630,8 @@ function filesPublicly(bug) {
17950
18630
  function hasTriage(report2) {
17951
18631
  return blockerTextOf(report2.blockers) !== null || report2.outOfScopeBugs.length > 0;
17952
18632
  }
17953
- function issueName2(ref) {
17954
- return ref.url ?? `${ref.kind} issue ${ref.externalId}`;
18633
+ function issueName2(ref3) {
18634
+ return ref3.url ?? `${ref3.kind} issue ${ref3.externalId}`;
17955
18635
  }
17956
18636
  function bugLines(bug) {
17957
18637
  const name2 = `out_of_scope_bugs[${bug.index}]`;
@@ -17989,11 +18669,11 @@ async function resolvePublicTracker(options, redact) {
17989
18669
  });
17990
18670
  const passed2 = attempts.filter((attempt2) => !attempt2.ok).map((attempt2) => `\`${attempt2.kind}\``);
17991
18671
  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}.`);
18672
+ activeOutput().info(`${INDENT5}${LINE_PREFIX}public bugs of this run go to the \`${tracker.kind}\` tracker${fallback}.`);
17993
18673
  return tracker;
17994
18674
  } catch (error) {
17995
18675
  const reason = messageOf(error);
17996
- activeOutput().warn(`${INDENT4}${LINE_PREFIX}no tracker resolved, so no public bug of this run is filed: ${redact(reason)}`);
18676
+ activeOutput().warn(`${INDENT5}${LINE_PREFIX}no tracker resolved, so no public bug of this run is filed: ${redact(reason)}`);
17997
18677
  return unresolvedTracker(options.config.trackerDefault, reason);
17998
18678
  }
17999
18679
  }
@@ -18031,16 +18711,16 @@ function createStartTriage(options) {
18031
18711
  });
18032
18712
  const { notes, warnings } = describeStartTriage(result);
18033
18713
  for (const note of notes)
18034
- activeOutput().info(`${INDENT4}${note}`);
18714
+ activeOutput().info(`${INDENT5}${note}`);
18035
18715
  for (const warning of warnings)
18036
- activeOutput().warn(`${INDENT4}${warning}`);
18716
+ activeOutput().warn(`${INDENT5}${warning}`);
18037
18717
  return result;
18038
18718
  };
18039
18719
  return async (input) => {
18040
18720
  try {
18041
18721
  return await triageTask(input);
18042
18722
  } catch (error) {
18043
- activeOutput().warn(`${INDENT4}${LINE_PREFIX}nothing more was triaged for this task: ${redact(messageOf(error))}`);
18723
+ activeOutput().warn(`${INDENT5}${LINE_PREFIX}nothing more was triaged for this task: ${redact(messageOf(error))}`);
18044
18724
  return null;
18045
18725
  }
18046
18726
  };
@@ -19255,4 +19935,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
19255
19935
  }
19256
19936
  }
19257
19937
 
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 };
19938
+ 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 };