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