@open-tomato/rafa 0.26.0 → 0.28.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 +35 -1
- package/dist/cli.js +4235 -1503
- package/dist/effort/store/index.js +1 -1
- package/dist/{index-azptzvp3.js → index-4rwvr6t4.js} +593 -92
- package/dist/{index-59x3zsh4.js → index-6jkj0dcx.js} +1570 -284
- package/dist/{index-2yd8bber.js → index-em1vd7kd.js} +1424 -596
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
writeTrackerBlocker
|
|
15
15
|
} from "./index-p1265xgj.js";
|
|
16
16
|
import {
|
|
17
|
+
ACROSS_STORES_ORDER,
|
|
17
18
|
CORE_ADAPTER_REGISTRY,
|
|
18
19
|
ClaudePlannerError,
|
|
19
20
|
DEVELOPMENT_MARK,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
SKILL_FILE,
|
|
34
35
|
SQLITE_MIGRATIONS,
|
|
35
36
|
SQLITE_STORE_FILE_NAME,
|
|
37
|
+
STAMPED_COLUMNS,
|
|
36
38
|
SessionConflictError,
|
|
37
39
|
SessionRecordError,
|
|
38
40
|
SessionStateError,
|
|
@@ -40,13 +42,17 @@ import {
|
|
|
40
42
|
beginSession,
|
|
41
43
|
checkUsage,
|
|
42
44
|
createGhRunner,
|
|
45
|
+
createGitRunner,
|
|
43
46
|
createLocalTracker,
|
|
44
47
|
declaredNameIssue,
|
|
45
48
|
effortStoreDir,
|
|
46
49
|
errorCode,
|
|
47
50
|
getClaudeUsagePercent,
|
|
48
51
|
gitRemoteUrl,
|
|
52
|
+
gitSaid,
|
|
53
|
+
goHome,
|
|
49
54
|
guardTestProcess,
|
|
55
|
+
hopFilePath,
|
|
50
56
|
interruptClaudeSessions,
|
|
51
57
|
isPidAlive,
|
|
52
58
|
isSessionId,
|
|
@@ -56,8 +62,12 @@ import {
|
|
|
56
62
|
parseInstinct,
|
|
57
63
|
planFilePath,
|
|
58
64
|
planSchema,
|
|
65
|
+
positionAt,
|
|
66
|
+
positionFilePath,
|
|
59
67
|
readFrontmatter,
|
|
60
68
|
readFrontmatterDocument,
|
|
69
|
+
readHopRecord,
|
|
70
|
+
readPositionFile,
|
|
61
71
|
readScopes,
|
|
62
72
|
readSession,
|
|
63
73
|
readSessions,
|
|
@@ -67,21 +77,24 @@ import {
|
|
|
67
77
|
runClaudeCaptured,
|
|
68
78
|
runsDir,
|
|
69
79
|
scanLayout,
|
|
70
|
-
scopeAt,
|
|
71
80
|
selectEffortStore,
|
|
72
81
|
sessionFilePath,
|
|
73
82
|
setActiveStoreSettings,
|
|
74
83
|
sqliteCatalogue,
|
|
75
84
|
sqliteStorePath,
|
|
85
|
+
staleAgainst,
|
|
86
|
+
stampedValues,
|
|
76
87
|
typeOfLabels,
|
|
77
88
|
updateSession,
|
|
78
89
|
version,
|
|
79
90
|
withSqliteStore,
|
|
80
91
|
writeFrontmatter,
|
|
92
|
+
writeHopRecord,
|
|
81
93
|
writeInstinct,
|
|
82
94
|
writeNames,
|
|
95
|
+
writePositionFile,
|
|
83
96
|
writeSqliteStore
|
|
84
|
-
} from "./index-
|
|
97
|
+
} from "./index-4rwvr6t4.js";
|
|
85
98
|
import {
|
|
86
99
|
CHANGE_LEVELS,
|
|
87
100
|
CONFIG_DEFAULTS,
|
|
@@ -967,9 +980,10 @@ var INSERT_FINDING = `
|
|
|
967
980
|
INSERT INTO findings (
|
|
968
981
|
id, session_id, plan_stub, task_line,
|
|
969
982
|
kind, trigger, what, cause, resolution, artifact, signal,
|
|
970
|
-
outcome, tracker_ref, collected_at
|
|
983
|
+
outcome, tracker_ref, collected_at,
|
|
984
|
+
${STAMPED_COLUMNS}
|
|
971
985
|
)
|
|
972
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL,
|
|
986
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ${stampedValues("findings")})
|
|
973
987
|
ON CONFLICT (session_id, artifact) WHERE artifact IS NOT NULL DO NOTHING
|
|
974
988
|
ON CONFLICT (session_id, trigger, what) WHERE artifact IS NULL DO NOTHING
|
|
975
989
|
`;
|
|
@@ -1096,8 +1110,8 @@ function writeFindings(repoRoot, write, seams = {}) {
|
|
|
1096
1110
|
// src/effort/store/skill-invocations.ts
|
|
1097
1111
|
var UNKNOWN_SKILL_COUNT = "unknown";
|
|
1098
1112
|
var INSERT_INVOCATION = `
|
|
1099
|
-
INSERT INTO skill_invocations (session_id, name, sidechain, count)
|
|
1100
|
-
VALUES (?, ?, ?,
|
|
1113
|
+
INSERT INTO skill_invocations (session_id, name, sidechain, count, ${STAMPED_COLUMNS})
|
|
1114
|
+
VALUES (?, ?, ?, ?, ${stampedValues("skill_invocations")})
|
|
1101
1115
|
`;
|
|
1102
1116
|
var SELECT_HELD = "SELECT 1 AS held FROM skill_invocations WHERE session_id = ? LIMIT 1";
|
|
1103
1117
|
var SELECT_ALL = "SELECT session_id, name, sidechain, count FROM skill_invocations ORDER BY seq";
|
|
@@ -3812,8 +3826,8 @@ async function waitForChecks(options) {
|
|
|
3812
3826
|
// src/effort/store/plan-ci.ts
|
|
3813
3827
|
var PLAN_CI_VERDICTS = ["green", "red", "none"];
|
|
3814
3828
|
var INSERT_PLAN_CI = `
|
|
3815
|
-
INSERT INTO plan_ci (plan_stub, pr, head_sha, verdict, failing, read_at)
|
|
3816
|
-
VALUES (?, ?, ?, ?, ?,
|
|
3829
|
+
INSERT INTO plan_ci (plan_stub, pr, head_sha, verdict, failing, read_at, ${STAMPED_COLUMNS})
|
|
3830
|
+
VALUES (?, ?, ?, ?, ?, ?, ${stampedValues("plan_ci")})
|
|
3817
3831
|
ON CONFLICT (pr, head_sha, read_at) DO NOTHING
|
|
3818
3832
|
`;
|
|
3819
3833
|
var SELECT_COLUMNS = "SELECT plan_stub, pr, head_sha, verdict, failing, read_at FROM plan_ci";
|
|
@@ -3928,9 +3942,10 @@ var INSERT_REPORT = `
|
|
|
3928
3942
|
id, session_id, plan_stub, task_line,
|
|
3929
3943
|
status,
|
|
3930
3944
|
outcome, collected_at,
|
|
3931
|
-
skills_used
|
|
3945
|
+
skills_used,
|
|
3946
|
+
${STAMPED_COLUMNS}
|
|
3932
3947
|
)
|
|
3933
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
3948
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("task_reports")})
|
|
3934
3949
|
ON CONFLICT (session_id) DO NOTHING
|
|
3935
3950
|
`;
|
|
3936
3951
|
function refusedWrite4(reason) {
|
|
@@ -3995,7 +4010,7 @@ function readTaskReportTallies(repoRoot) {
|
|
|
3995
4010
|
var SELECT_REPORTED_SKILLS = `
|
|
3996
4011
|
SELECT session_id, plan_stub, task_line, outcome, skills_used
|
|
3997
4012
|
FROM task_reports
|
|
3998
|
-
ORDER BY
|
|
4013
|
+
ORDER BY ${ACROSS_STORES_ORDER}
|
|
3999
4014
|
`;
|
|
4000
4015
|
function parseSkillsUsed(sessionId, json) {
|
|
4001
4016
|
if (json === null)
|
|
@@ -4020,14 +4035,14 @@ function readReportedSkills(repoRoot) {
|
|
|
4020
4035
|
}
|
|
4021
4036
|
|
|
4022
4037
|
// src/effort/skill-facts.ts
|
|
4023
|
-
var SELECT_OFFERS =
|
|
4038
|
+
var SELECT_OFFERS = `SELECT session_id, resolver, skills_offered, lessons_offered FROM dispatches ORDER BY ${ACROSS_STORES_ORDER}`;
|
|
4024
4039
|
var SELECT_FINDINGS = `
|
|
4025
4040
|
SELECT session_id, kind, trigger, what, cause, resolution, artifact, signal
|
|
4026
4041
|
FROM findings
|
|
4027
|
-
ORDER BY
|
|
4042
|
+
ORDER BY ${ACROSS_STORES_ORDER}
|
|
4028
4043
|
`;
|
|
4029
4044
|
function selectEntries(table) {
|
|
4030
|
-
return `SELECT session_id, what, artifact FROM ${table} ORDER BY
|
|
4045
|
+
return `SELECT session_id, what, artifact FROM ${table} ORDER BY ${ACROSS_STORES_ORDER}`;
|
|
4031
4046
|
}
|
|
4032
4047
|
function parseNames(column, sessionId, json) {
|
|
4033
4048
|
if (json === null)
|
|
@@ -4329,9 +4344,10 @@ var INSERT_DISPATCH = `
|
|
|
4329
4344
|
INSERT INTO dispatches (
|
|
4330
4345
|
session_id, plan_stub, task_line,
|
|
4331
4346
|
declaration, agent, model, effort, budget_usd, tools,
|
|
4332
|
-
flags, resolver, skills_offered, lessons_offered, collected_at
|
|
4347
|
+
flags, resolver, skills_offered, lessons_offered, collected_at,
|
|
4348
|
+
${STAMPED_COLUMNS}
|
|
4333
4349
|
)
|
|
4334
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
4350
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("dispatches")})
|
|
4335
4351
|
ON CONFLICT (session_id) DO NOTHING
|
|
4336
4352
|
`;
|
|
4337
4353
|
function refusedWrite5(reason) {
|
|
@@ -4472,9 +4488,10 @@ var INSERT_CHECK = `
|
|
|
4472
4488
|
run_id, position,
|
|
4473
4489
|
tier, kind, item, probe,
|
|
4474
4490
|
outcome, duration_ms, failure,
|
|
4475
|
-
collected_at
|
|
4491
|
+
collected_at,
|
|
4492
|
+
${STAMPED_COLUMNS}
|
|
4476
4493
|
)
|
|
4477
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
4494
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("preflight")})
|
|
4478
4495
|
`;
|
|
4479
4496
|
function refusedWrite6(reason) {
|
|
4480
4497
|
return new Error(`effort store: preflight write ${reason}; nothing written`);
|
|
@@ -6297,6 +6314,7 @@ var SPEC_FLAG = "--spec";
|
|
|
6297
6314
|
var ISSUE_FLAG = "--issue";
|
|
6298
6315
|
var NEXT_FLAG = "--next";
|
|
6299
6316
|
var REFRESH_FLAG = "--refresh";
|
|
6317
|
+
var ROADMAP_FLAG = "--roadmap";
|
|
6300
6318
|
var DRY_RUN_FLAG = "--dry-run";
|
|
6301
6319
|
var SKIP_REVIEW_FLAG = "--skip-review";
|
|
6302
6320
|
var NO_COMMENT_FLAG = "--no-comment";
|
|
@@ -8627,26 +8645,6 @@ var GLOBAL_FLAGS = Object.freeze([
|
|
|
8627
8645
|
]);
|
|
8628
8646
|
var SPENDS_LEGEND = `${SPENDS_GLYPH} starts Claude Code sessions, which spend your Claude usage`;
|
|
8629
8647
|
|
|
8630
|
-
// src/pr/git.ts
|
|
8631
|
-
import { spawnSync } from "child_process";
|
|
8632
|
-
function createGitRunner(cwd) {
|
|
8633
|
-
return (args) => {
|
|
8634
|
-
const result = spawnSync("git", [...args], {
|
|
8635
|
-
cwd,
|
|
8636
|
-
encoding: "utf8",
|
|
8637
|
-
env: { ...process.env, LC_ALL: "C" }
|
|
8638
|
-
});
|
|
8639
|
-
if (result.error !== undefined) {
|
|
8640
|
-
return { ok: false, stdout: "", stderr: `could not run git in ${cwd}: ${messageOf(result.error)}` };
|
|
8641
|
-
}
|
|
8642
|
-
return { ok: result.status === 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
8643
|
-
};
|
|
8644
|
-
}
|
|
8645
|
-
function gitSaid(result) {
|
|
8646
|
-
return [result.stderr, result.stdout].map((stream) => stream.trim()).filter((stream) => stream !== "").join(`
|
|
8647
|
-
`);
|
|
8648
|
-
}
|
|
8649
|
-
|
|
8650
8648
|
// src/refs/verify.ts
|
|
8651
8649
|
class RefVerifyError extends Error {
|
|
8652
8650
|
constructor(problem) {
|
|
@@ -9406,9 +9404,81 @@ function dryEpicSentence(epic) {
|
|
|
9406
9404
|
const { done, total } = epic.progress;
|
|
9407
9405
|
return `epic #${String(epic.number)} ${epic.title} has run dry: every line of its checklist and every open member it labels` + ` is done or taken, though the epic is not done (${String(done)}/${String(total)});` + " the walk stops here and does not move on to another epic";
|
|
9408
9406
|
}
|
|
9407
|
+
function rowOnListing(number, listing, what) {
|
|
9408
|
+
const row = listing.find((issue3) => issue3.number === number);
|
|
9409
|
+
if (row === undefined) {
|
|
9410
|
+
throw new Error(`${PREFIX9}: ${what} #${String(number)} is not on the board listing, so the next ${NOW_HORIZON_LABEL} epic` + " cannot be read; the listing reads the newest issues only, and a line it leaves out is not passed as if it were none");
|
|
9411
|
+
}
|
|
9412
|
+
return row;
|
|
9413
|
+
}
|
|
9414
|
+
async function hasPickableLine(epic, row, readings) {
|
|
9415
|
+
const pick = await pickNextRoadmapLine(epicLines(epic, row).lines, readings);
|
|
9416
|
+
return pick.line !== null;
|
|
9417
|
+
}
|
|
9418
|
+
async function nowEpicOf(line, listing, readings) {
|
|
9419
|
+
if (line.ticked)
|
|
9420
|
+
return null;
|
|
9421
|
+
const row = rowOnListing(line.issue, listing, "checklist line");
|
|
9422
|
+
if (row.type !== "epic" || row.state !== "OPEN" || !isNowEpic(row.labels))
|
|
9423
|
+
return null;
|
|
9424
|
+
const { epic } = epicOnListing(line.issue, listing);
|
|
9425
|
+
if (epic.state === "done")
|
|
9426
|
+
return null;
|
|
9427
|
+
return await hasPickableLine(epic, row, readings) ? epic : null;
|
|
9428
|
+
}
|
|
9429
|
+
async function nextNowEpic(request) {
|
|
9430
|
+
const { after, board, listing, readings } = request;
|
|
9431
|
+
const lines = parseRoadmapBody(rowOnListing(board, listing, "board").body);
|
|
9432
|
+
const at = lines.findIndex((line) => line.issue === after);
|
|
9433
|
+
if (at === -1)
|
|
9434
|
+
return null;
|
|
9435
|
+
for (const line of lines.slice(at + 1)) {
|
|
9436
|
+
const epic = await nowEpicOf(line, listing, readings);
|
|
9437
|
+
if (epic === null)
|
|
9438
|
+
continue;
|
|
9439
|
+
return Object.freeze({
|
|
9440
|
+
number: epic.number,
|
|
9441
|
+
title: epic.title,
|
|
9442
|
+
slug: epic.slug,
|
|
9443
|
+
board,
|
|
9444
|
+
line,
|
|
9445
|
+
progress: epic.progress
|
|
9446
|
+
});
|
|
9447
|
+
}
|
|
9448
|
+
return null;
|
|
9449
|
+
}
|
|
9409
9450
|
|
|
9410
9451
|
// src/board/spec-source-roadmap.ts
|
|
9411
9452
|
var PREFIX10 = "board spec source roadmap";
|
|
9453
|
+
function hopHeaderLine(away) {
|
|
9454
|
+
const { record, target } = away;
|
|
9455
|
+
return `\uD83E\uDDED Away on a hop: issue #${String(target)}, the blocker of #${String(record.blocked)},` + ` in epic #${String(record.targetEpic)} on board #${String(record.targetBoard)}`;
|
|
9456
|
+
}
|
|
9457
|
+
function hopPickLine(target) {
|
|
9458
|
+
return `\u25B6 Next on the hop: issue #${String(target)}`;
|
|
9459
|
+
}
|
|
9460
|
+
function hopBlockedMessage(target) {
|
|
9461
|
+
return `issue #${String(target)} is the hop's target and still has an open blocker, so nothing is planned;` + " a hop follows one blocker, never a second, and `rafa next --roadmap` goes home from here";
|
|
9462
|
+
}
|
|
9463
|
+
function unfollowedHopNotice(detail) {
|
|
9464
|
+
return `${detail}, so no hop is followed and --next picks as it does without --roadmap`;
|
|
9465
|
+
}
|
|
9466
|
+
function readAwayHop(root) {
|
|
9467
|
+
const hop = readHopRecord(root);
|
|
9468
|
+
if (!hop.set) {
|
|
9469
|
+
return {
|
|
9470
|
+
away: null,
|
|
9471
|
+
notices: hop.reason === "absent" ? [] : [unfollowedHopNotice(hop.detail)]
|
|
9472
|
+
};
|
|
9473
|
+
}
|
|
9474
|
+
const { record } = hop;
|
|
9475
|
+
const placed = readPositionFile(root);
|
|
9476
|
+
if (!placed.set || staleAgainst(record, placed.position))
|
|
9477
|
+
return { away: null, notices: [] };
|
|
9478
|
+
if (record.state !== "away" || record.kind !== "blocker" || record.target === null)
|
|
9479
|
+
return { away: null, notices: [] };
|
|
9480
|
+
return { away: Object.freeze({ record, target: record.target }), notices: [] };
|
|
9481
|
+
}
|
|
9412
9482
|
function roadmapHeaderLine(roadmap) {
|
|
9413
9483
|
return `\uD83D\uDDFA Reading the roadmap, issue #${String(roadmap)}, for the next spec...`;
|
|
9414
9484
|
}
|
|
@@ -9487,6 +9557,12 @@ async function walkPlaceEpic(number, listing, readings) {
|
|
|
9487
9557
|
}
|
|
9488
9558
|
async function pickRoadmapIssue(options) {
|
|
9489
9559
|
const { seams, issues, output } = options;
|
|
9560
|
+
if (options.followHop === true) {
|
|
9561
|
+
const hop = readAwayHop(options.root);
|
|
9562
|
+
hop.notices.forEach((notice) => output.warn(notice));
|
|
9563
|
+
if (hop.away !== null)
|
|
9564
|
+
return await pickHopTarget(hop.away, issues, output);
|
|
9565
|
+
}
|
|
9490
9566
|
const listing = listOnce2(seams.listing);
|
|
9491
9567
|
const start = await startingPlace(options, listing);
|
|
9492
9568
|
start.notices.forEach((notice) => output.warn(notice));
|
|
@@ -9515,6 +9591,17 @@ async function pickRoadmapIssue(options) {
|
|
|
9515
9591
|
output.info(pickLine(pick.line));
|
|
9516
9592
|
return settlePick(pick.line, { lines: descent.lines, issues, readings, seams, output });
|
|
9517
9593
|
}
|
|
9594
|
+
async function pickHopTarget(away, issues, output) {
|
|
9595
|
+
output.info(hopHeaderLine(away));
|
|
9596
|
+
const blocked2 = await readBlockedLine(await issues(away.target), blockerStatesOf(issues));
|
|
9597
|
+
if (blocked2 !== null) {
|
|
9598
|
+
output.info(blockedPickLine(blocked2));
|
|
9599
|
+
output.info(hopBlockedMessage(away.target));
|
|
9600
|
+
return { stop: "blocked" };
|
|
9601
|
+
}
|
|
9602
|
+
output.info(hopPickLine(away.target));
|
|
9603
|
+
return { issue: away.target };
|
|
9604
|
+
}
|
|
9518
9605
|
function printDescent(walked, output) {
|
|
9519
9606
|
const { descent, pick } = walked;
|
|
9520
9607
|
descent.passed.forEach((pass) => output.info(descentPassLine(pass)));
|
|
@@ -9565,6 +9652,7 @@ async function settleBlockedPick(options) {
|
|
|
9565
9652
|
var PREFIX11 = "board spec source";
|
|
9566
9653
|
var SOURCE_FLAGS = [SPEC_FLAG, ISSUE_FLAG, NEXT_FLAG];
|
|
9567
9654
|
var SOURCE_REFUSAL_EXIT = 1;
|
|
9655
|
+
var ROADMAP_REFUSAL_EXIT2 = 2;
|
|
9568
9656
|
function severalSourcesMessage(given) {
|
|
9569
9657
|
const named = given.join(" and ");
|
|
9570
9658
|
return `${named} each name a spec, and a run plans from one; drop all but one of them`;
|
|
@@ -9572,6 +9660,9 @@ function severalSourcesMessage(given) {
|
|
|
9572
9660
|
function noSourceMessage(specsDir) {
|
|
9573
9661
|
return `no spec was named: pass ${SPEC_FLAG}=<file>.md, read against the project root` + ` or under specs.dir (${specsDir}), ${ISSUE_FLAG}=<n> to plan from an issue,` + ` or ${NEXT_FLAG} to take the first undone line of the roadmap`;
|
|
9574
9662
|
}
|
|
9663
|
+
function roadmapWithoutNextMessage() {
|
|
9664
|
+
return `${ROADMAP_FLAG} follows a hop to the spec ${NEXT_FLAG} would plan, and this line gives no ${NEXT_FLAG};` + ` write ${NEXT_FLAG} ${ROADMAP_FLAG}, or drop ${ROADMAP_FLAG}`;
|
|
9665
|
+
}
|
|
9575
9666
|
function missingValueMessage(flag, wants) {
|
|
9576
9667
|
return `${flag} was given no value; write ${flag}=${wants}`;
|
|
9577
9668
|
}
|
|
@@ -9607,6 +9698,14 @@ function requestOf(flag, word) {
|
|
|
9607
9698
|
}
|
|
9608
9699
|
return flag === SPEC_FLAG ? { kind: "spec", spec: word.value } : { kind: "issue", issue: issueNumberOf(word.value, ISSUE_FLAG) };
|
|
9609
9700
|
}
|
|
9701
|
+
function withRoadmap(args, request) {
|
|
9702
|
+
if (!readFlagWord(args, ROADMAP_FLAG).given)
|
|
9703
|
+
return request;
|
|
9704
|
+
if (request?.kind !== "next") {
|
|
9705
|
+
throw new CommandExit(ROADMAP_REFUSAL_EXIT2, roadmapWithoutNextMessage());
|
|
9706
|
+
}
|
|
9707
|
+
return { ...request, followHop: true };
|
|
9708
|
+
}
|
|
9610
9709
|
function readSpecSourceFlags(args) {
|
|
9611
9710
|
const words = SOURCE_FLAGS.map((flag) => ({ flag, word: readFlagWord(args, flag) }));
|
|
9612
9711
|
const given = words.filter((entry) => entry.word.given);
|
|
@@ -9614,8 +9713,9 @@ function readSpecSourceFlags(args) {
|
|
|
9614
9713
|
throw new CommandExit(SOURCE_REFUSAL_EXIT, severalSourcesMessage(given.map((entry) => entry.flag)));
|
|
9615
9714
|
}
|
|
9616
9715
|
const [only] = given;
|
|
9716
|
+
const request = only === undefined ? null : requestOf(only.flag, only.word);
|
|
9617
9717
|
return Object.freeze({
|
|
9618
|
-
request:
|
|
9718
|
+
request: withRoadmap(args, request),
|
|
9619
9719
|
refresh: args.includes(REFRESH_FLAG),
|
|
9620
9720
|
dryRun: args.includes(DRY_RUN_FLAG)
|
|
9621
9721
|
});
|
|
@@ -9670,7 +9770,8 @@ async function resolveSpecSource(options) {
|
|
|
9670
9770
|
seams: requireRoadmapSeams(options.roadmap),
|
|
9671
9771
|
root: repoRoot,
|
|
9672
9772
|
issues,
|
|
9673
|
-
output
|
|
9773
|
+
output,
|
|
9774
|
+
...request.followHop === true ? { followHop: true } : {}
|
|
9674
9775
|
});
|
|
9675
9776
|
if ("stop" in picked)
|
|
9676
9777
|
return stopped(picked.stop);
|
|
@@ -9954,7 +10055,7 @@ function withOutlineCache(outline, root) {
|
|
|
9954
10055
|
import path2 from "path";
|
|
9955
10056
|
async function coreRoster() {
|
|
9956
10057
|
const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
|
|
9957
|
-
import("./index-
|
|
10058
|
+
import("./index-em1vd7kd.js"),
|
|
9958
10059
|
import("./describe-vbwqxj1f.js")
|
|
9959
10060
|
]);
|
|
9960
10061
|
return describeRegistry(CORE_REGISTRY, version);
|
|
@@ -10185,78 +10286,6 @@ function renderDoctorRefs(reading2) {
|
|
|
10185
10286
|
return [headLine(reading2), ...reading2.copies.flatMap(copyLine)];
|
|
10186
10287
|
}
|
|
10187
10288
|
|
|
10188
|
-
// src/project/position.ts
|
|
10189
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
10190
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync19, renameSync as renameSync4, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
10191
|
-
import { dirname as dirname9, join as join13 } from "path";
|
|
10192
|
-
var POSITION_FILE = "position.json";
|
|
10193
|
-
function positionFilePath(root) {
|
|
10194
|
-
return join13(scopeAt(root).dir, POSITION_FILE);
|
|
10195
|
-
}
|
|
10196
|
-
function isRecord(value) {
|
|
10197
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10198
|
-
}
|
|
10199
|
-
function isIssueNumber(value) {
|
|
10200
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
10201
|
-
}
|
|
10202
|
-
function asPlace(value) {
|
|
10203
|
-
if (!isRecord(value) || !isIssueNumber(value.board))
|
|
10204
|
-
return null;
|
|
10205
|
-
if (value.epic !== null && !isIssueNumber(value.epic))
|
|
10206
|
-
return null;
|
|
10207
|
-
return { board: value.board, epic: value.epic };
|
|
10208
|
-
}
|
|
10209
|
-
function asPosition(value) {
|
|
10210
|
-
if (!isRecord(value))
|
|
10211
|
-
return null;
|
|
10212
|
-
const current = asPlace(value.current);
|
|
10213
|
-
const home = asPlace(value.home);
|
|
10214
|
-
const previous = value.previous === null ? null : asPlace(value.previous);
|
|
10215
|
-
if (current === null || home === null)
|
|
10216
|
-
return null;
|
|
10217
|
-
if (previous === null && value.previous !== null)
|
|
10218
|
-
return null;
|
|
10219
|
-
return { current, previous, home };
|
|
10220
|
-
}
|
|
10221
|
-
function readPositionFile(root) {
|
|
10222
|
-
const file = positionFilePath(root);
|
|
10223
|
-
let text;
|
|
10224
|
-
try {
|
|
10225
|
-
text = readFileSync19(file, "utf8");
|
|
10226
|
-
} catch (error) {
|
|
10227
|
-
const code = error.code;
|
|
10228
|
-
return code === "ENOENT" ? { set: false, reason: "absent", detail: `No position file at ${file}` } : { set: false, reason: "unreadable", detail: `Cannot read ${file}: ${messageOf(error)}` };
|
|
10229
|
-
}
|
|
10230
|
-
let parsed;
|
|
10231
|
-
try {
|
|
10232
|
-
parsed = JSON.parse(text);
|
|
10233
|
-
} catch (error) {
|
|
10234
|
-
return { set: false, reason: "invalid-json", detail: `${file} holds no JSON: ${messageOf(error)}` };
|
|
10235
|
-
}
|
|
10236
|
-
const position = asPosition(parsed);
|
|
10237
|
-
return position === null ? { set: false, reason: "wrong-shape", detail: `${file} does not hold a current, previous and home place` } : { set: true, position };
|
|
10238
|
-
}
|
|
10239
|
-
function writePositionFile(root, position) {
|
|
10240
|
-
const file = positionFilePath(root);
|
|
10241
|
-
mkdirSync8(dirname9(file), { recursive: true });
|
|
10242
|
-
const temporary = `${file}.${String(process.pid)}.${randomUUID3()}.tmp`;
|
|
10243
|
-
const { current, previous, home } = position;
|
|
10244
|
-
try {
|
|
10245
|
-
writeFileSync9(temporary, `${JSON.stringify({ current, previous, home }, null, 2)}
|
|
10246
|
-
`);
|
|
10247
|
-
renameSync4(temporary, file);
|
|
10248
|
-
} catch (error) {
|
|
10249
|
-
rmSync2(temporary, { force: true });
|
|
10250
|
-
throw error;
|
|
10251
|
-
}
|
|
10252
|
-
}
|
|
10253
|
-
function rehome(position, place) {
|
|
10254
|
-
return { current: place, previous: position.current, home: place };
|
|
10255
|
-
}
|
|
10256
|
-
function hop(position, place) {
|
|
10257
|
-
return { current: place, previous: position.current, home: position.home };
|
|
10258
|
-
}
|
|
10259
|
-
|
|
10260
10289
|
// src/adapters/tracker/resolve.ts
|
|
10261
10290
|
var PREFIX12 = "tracker chain";
|
|
10262
10291
|
var FAILURE_SEPARATOR = "; ";
|
|
@@ -11636,19 +11665,20 @@ function createIssueListCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
11636
11665
|
var list_default = createIssueListCommand();
|
|
11637
11666
|
|
|
11638
11667
|
// src/commands/release/status.ts
|
|
11639
|
-
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as
|
|
11640
|
-
import { join as
|
|
11668
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync20 } from "fs";
|
|
11669
|
+
import { join as join13 } from "path";
|
|
11641
11670
|
|
|
11642
11671
|
// src/effort/store/changes.ts
|
|
11643
|
-
import { randomUUID as
|
|
11672
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11644
11673
|
import { existsSync as existsSync16 } from "fs";
|
|
11645
11674
|
var INSERT_CHANGE = `
|
|
11646
11675
|
INSERT INTO changes (
|
|
11647
11676
|
id, session_id, plan_stub, task_line,
|
|
11648
11677
|
level, area, summary,
|
|
11649
|
-
collected_at
|
|
11678
|
+
collected_at,
|
|
11679
|
+
${STAMPED_COLUMNS}
|
|
11650
11680
|
)
|
|
11651
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
11681
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("changes")})
|
|
11652
11682
|
ON CONFLICT (session_id, level, ifnull(area, ''), summary) DO NOTHING
|
|
11653
11683
|
`;
|
|
11654
11684
|
function optionalText(value) {
|
|
@@ -11707,7 +11737,7 @@ function writeChanges(repoRoot, write, seams = {}) {
|
|
|
11707
11737
|
rejected.push(rejection);
|
|
11708
11738
|
}
|
|
11709
11739
|
const collectedAt = (seams.now ?? (() => new Date))().toISOString();
|
|
11710
|
-
const newId = seams.newId ??
|
|
11740
|
+
const newId = seams.newId ?? randomUUID3;
|
|
11711
11741
|
const rows = stored.map((change) => rowValues2(change, write.dispatch, newId(), collectedAt));
|
|
11712
11742
|
const appended2 = writeSqliteStore(path3, rows.length, 0, (db) => insertRows3(db, rows));
|
|
11713
11743
|
return { path: path3, appended: appended2, skipped: stored.length - appended2, rejected };
|
|
@@ -11716,7 +11746,7 @@ var SELECT_PLAN_CHANGES = `
|
|
|
11716
11746
|
SELECT session_id, task_line, level, area, summary, collected_at
|
|
11717
11747
|
FROM changes
|
|
11718
11748
|
WHERE plan_stub IS ?
|
|
11719
|
-
ORDER BY
|
|
11749
|
+
ORDER BY ${ACROSS_STORES_ORDER}
|
|
11720
11750
|
`;
|
|
11721
11751
|
function readPlanChanges(repoRoot, planStub2) {
|
|
11722
11752
|
const path3 = sqliteStorePath(repoRoot);
|
|
@@ -11973,7 +12003,7 @@ function remainingFrom(steps, failed2) {
|
|
|
11973
12003
|
return at === -1 ? Object.freeze([]) : Object.freeze(steps.slice(at).filter((step) => step.skip === undefined));
|
|
11974
12004
|
}
|
|
11975
12005
|
// src/pr/none.ts
|
|
11976
|
-
import { spawnSync
|
|
12006
|
+
import { spawnSync } from "child_process";
|
|
11977
12007
|
|
|
11978
12008
|
// src/pr/provider.ts
|
|
11979
12009
|
var GITHUB_HOST = "github.com";
|
|
@@ -12014,7 +12044,7 @@ function requireGhProvider(reading2) {
|
|
|
12014
12044
|
// src/pr/none.ts
|
|
12015
12045
|
var COMPARE_QUERY = "?expand=1";
|
|
12016
12046
|
function pushBranch(dir, branch) {
|
|
12017
|
-
const result =
|
|
12047
|
+
const result = spawnSync("git", ["push", "--set-upstream", "origin", branch], {
|
|
12018
12048
|
cwd: dir,
|
|
12019
12049
|
encoding: "utf8",
|
|
12020
12050
|
env: { ...process.env, LC_ALL: "C" }
|
|
@@ -12048,6 +12078,8 @@ var SUMMARY_FIELDS = "author,baseRefName,headRefName,isCrossRepository,number,st
|
|
|
12048
12078
|
var DETAIL_FIELDS = `${SUMMARY_FIELDS},body,headRefOid,labels,mergeStateStatus,mergeable`;
|
|
12049
12079
|
var CHECK_FIELDS = "name,state,link";
|
|
12050
12080
|
var MERGED_FIELDS = "headRefName,headRefOid,mergedAt,number";
|
|
12081
|
+
var FILES_FIELDS = "changedFiles,files";
|
|
12082
|
+
var REVIEW_FIELDS = "reviews";
|
|
12051
12083
|
var WORKFLOWS_PATH = `repos/${REPO_PATH3}/actions/workflows`;
|
|
12052
12084
|
var LIST_LIMIT = 30;
|
|
12053
12085
|
var MERGED_LIMIT = 100;
|
|
@@ -12182,6 +12214,33 @@ function readComment2(value, command, where) {
|
|
|
12182
12214
|
url: readString2(comment["html_url"], command, `${where}.html_url`)
|
|
12183
12215
|
};
|
|
12184
12216
|
}
|
|
12217
|
+
function readCount(value, command, where) {
|
|
12218
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
12219
|
+
refuse3(command, `${where} is ${describeValue(value)}, expected a whole number of zero or more`);
|
|
12220
|
+
}
|
|
12221
|
+
return value;
|
|
12222
|
+
}
|
|
12223
|
+
function readChangedFiles(value, command) {
|
|
12224
|
+
const pull = readMapping(value, command, "its output");
|
|
12225
|
+
const count = readCount(pull["changedFiles"], command, "changedFiles");
|
|
12226
|
+
const paths = readList(pull["files"], command, "files").map((file, index) => {
|
|
12227
|
+
const named = readMapping(file, command, `files[${index}]`);
|
|
12228
|
+
return readString2(named["path"], command, `files[${index}].path`);
|
|
12229
|
+
});
|
|
12230
|
+
if (paths.length !== count) {
|
|
12231
|
+
refuse3(command, `${paths.length} paths for ${count} changed files, so the list is not the whole of it`);
|
|
12232
|
+
}
|
|
12233
|
+
return paths;
|
|
12234
|
+
}
|
|
12235
|
+
function readReview(value, command, where) {
|
|
12236
|
+
const review = readMapping(value, command, where);
|
|
12237
|
+
const author = readMapping(review["author"], command, `${where}.author`);
|
|
12238
|
+
return {
|
|
12239
|
+
login: readString2(author["login"], command, `${where}.author.login`),
|
|
12240
|
+
state: readString2(review["state"], command, `${where}.state`),
|
|
12241
|
+
submittedAt: readString2(review["submittedAt"], command, `${where}.submittedAt`)
|
|
12242
|
+
};
|
|
12243
|
+
}
|
|
12185
12244
|
function readWorkflowCount(stdout) {
|
|
12186
12245
|
let payload;
|
|
12187
12246
|
try {
|
|
@@ -12277,6 +12336,20 @@ function createGhPullRequests(options) {
|
|
|
12277
12336
|
const rows = parseChecks(result.stdout);
|
|
12278
12337
|
return { rows, verdict: verdictOf(rows) };
|
|
12279
12338
|
},
|
|
12339
|
+
changedFiles: async (number) => {
|
|
12340
|
+
const target = pullNumber(number, "changedFiles");
|
|
12341
|
+
const command = `gh pr view ${target} --json ${FILES_FIELDS}`;
|
|
12342
|
+
const stdout = await succeed(["pr", "view", target, "--json", FILES_FIELDS], command);
|
|
12343
|
+
return readChangedFiles(parseJson2(stdout, command), command);
|
|
12344
|
+
},
|
|
12345
|
+
reviews: async (number) => {
|
|
12346
|
+
const target = pullNumber(number, "reviews");
|
|
12347
|
+
const command = `gh pr view ${target} --json ${REVIEW_FIELDS}`;
|
|
12348
|
+
const stdout = await succeed(["pr", "view", target, "--json", REVIEW_FIELDS], command);
|
|
12349
|
+
const pull = readMapping(parseJson2(stdout, command), command, "its output");
|
|
12350
|
+
const rows = readList(pull["reviews"], command, "reviews");
|
|
12351
|
+
return rows.map((row, index) => readReview(row, command, `reviews[${index}]`));
|
|
12352
|
+
},
|
|
12280
12353
|
browse: async (number) => {
|
|
12281
12354
|
const target = pullNumber(number, "browse");
|
|
12282
12355
|
await succeed(["pr", "view", target, "--web"], `gh pr view ${target} --web`);
|
|
@@ -12546,7 +12619,7 @@ function resolveReleaseLevel(declared, notes) {
|
|
|
12546
12619
|
}
|
|
12547
12620
|
|
|
12548
12621
|
// src/release/version.ts
|
|
12549
|
-
import { readFileSync as
|
|
12622
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
|
|
12550
12623
|
var RELEASE_REMOTE = "origin";
|
|
12551
12624
|
var RELEASE_BASE_BRANCH = "main";
|
|
12552
12625
|
var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
@@ -12638,7 +12711,7 @@ function writeManifestVersion(path3, next) {
|
|
|
12638
12711
|
const unwritten = { path: path3, written: false, previous: null, version: next };
|
|
12639
12712
|
let text;
|
|
12640
12713
|
try {
|
|
12641
|
-
text =
|
|
12714
|
+
text = readFileSync19(path3, "utf8");
|
|
12642
12715
|
} catch (error) {
|
|
12643
12716
|
return { ...unwritten, problem: `${path3} could not be read: ${messageOf(error)}` };
|
|
12644
12717
|
}
|
|
@@ -12652,7 +12725,7 @@ function writeManifestVersion(path3, next) {
|
|
|
12652
12725
|
};
|
|
12653
12726
|
}
|
|
12654
12727
|
try {
|
|
12655
|
-
|
|
12728
|
+
writeFileSync9(path3, rewritten.text);
|
|
12656
12729
|
} catch (error) {
|
|
12657
12730
|
return { ...unwritten, previous: rewritten.previous, problem: `${path3} could not be written: ${messageOf(error)}` };
|
|
12658
12731
|
}
|
|
@@ -12786,13 +12859,13 @@ function untaggedVersions(released, tags) {
|
|
|
12786
12859
|
}
|
|
12787
12860
|
function textOf(path3) {
|
|
12788
12861
|
try {
|
|
12789
|
-
return
|
|
12862
|
+
return readFileSync20(path3, "utf8");
|
|
12790
12863
|
} catch {
|
|
12791
12864
|
return null;
|
|
12792
12865
|
}
|
|
12793
12866
|
}
|
|
12794
12867
|
function readVersionFile(root, configured) {
|
|
12795
|
-
const resolved =
|
|
12868
|
+
const resolved = join13(root, configured);
|
|
12796
12869
|
const text = textOf(resolved);
|
|
12797
12870
|
if (text === null) {
|
|
12798
12871
|
return { path: configured, version: null, problem: `the version file could not be read at ${resolved}` };
|
|
@@ -12809,7 +12882,7 @@ function readTags(git) {
|
|
|
12809
12882
|
return { tags, latest: tags[0] ?? null, problem: null };
|
|
12810
12883
|
}
|
|
12811
12884
|
function readUntagged(root, configured, tags) {
|
|
12812
|
-
const resolved =
|
|
12885
|
+
const resolved = join13(root, configured);
|
|
12813
12886
|
const text = textOf(resolved);
|
|
12814
12887
|
if (text === null) {
|
|
12815
12888
|
return {
|
|
@@ -12828,7 +12901,7 @@ function readBranch(git) {
|
|
|
12828
12901
|
return result.ok && branch !== "" && branch !== "HEAD" ? branch : null;
|
|
12829
12902
|
}
|
|
12830
12903
|
function planStubsUnder2(root, planDir) {
|
|
12831
|
-
const resolved =
|
|
12904
|
+
const resolved = join13(root, planDir);
|
|
12832
12905
|
if (!existsSync17(resolved))
|
|
12833
12906
|
return [];
|
|
12834
12907
|
try {
|
|
@@ -13030,23 +13103,23 @@ function createReleaseStatusCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
13030
13103
|
var status_default = createReleaseStatusCommand();
|
|
13031
13104
|
|
|
13032
13105
|
// src/commands/plan/list.ts
|
|
13033
|
-
import { existsSync as existsSync18, readdirSync as readdirSync9, readFileSync as
|
|
13034
|
-
import { join as
|
|
13106
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
|
|
13107
|
+
import { join as join14 } from "path";
|
|
13035
13108
|
var USAGE4 = "rafa plan list";
|
|
13036
13109
|
var OPEN_HINT = "Type it bare: rafa plan list --open";
|
|
13037
13110
|
function listing(dir, stub) {
|
|
13038
|
-
const plan =
|
|
13039
|
-
const trackerPath =
|
|
13111
|
+
const plan = join14(dir, planFileName(stub, false));
|
|
13112
|
+
const trackerPath = join14(dir, planFileName(stub, true));
|
|
13040
13113
|
const tracker = isFile(trackerPath) ? trackerPath : null;
|
|
13041
|
-
const planModel = parsePlan(
|
|
13042
|
-
const counted2 = tracker === null ? planModel : parsePlan(
|
|
13114
|
+
const planModel = parsePlan(readFileSync21(plan, "utf8"));
|
|
13115
|
+
const counted2 = tracker === null ? planModel : parsePlan(readFileSync21(tracker, "utf8"));
|
|
13043
13116
|
return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
|
|
13044
13117
|
}
|
|
13045
13118
|
function listPlans(plans) {
|
|
13046
13119
|
const dir = plans.path;
|
|
13047
13120
|
if (!existsSync18(dir))
|
|
13048
13121
|
return { dir, plans: [] };
|
|
13049
|
-
const stubs = readdirSync9(dir).filter((name) => isFile(
|
|
13122
|
+
const stubs = readdirSync9(dir).filter((name) => isFile(join14(dir, name))).map((name) => stubOfPlanFile(name)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
|
|
13050
13123
|
return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
|
|
13051
13124
|
}
|
|
13052
13125
|
function isOpen(plan) {
|
|
@@ -13415,8 +13488,8 @@ function parseArgs(argv, spec = {}) {
|
|
|
13415
13488
|
|
|
13416
13489
|
// src/commands/loop/loop-sessions.ts
|
|
13417
13490
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13418
|
-
import { readFileSync as
|
|
13419
|
-
import { join as
|
|
13491
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
13492
|
+
import { join as join15, relative as relative3 } from "path";
|
|
13420
13493
|
|
|
13421
13494
|
// src/effort/store/task-finishes.ts
|
|
13422
13495
|
import { existsSync as existsSync19 } from "fs";
|
|
@@ -13560,11 +13633,11 @@ function writeSession(root, record, change) {
|
|
|
13560
13633
|
}
|
|
13561
13634
|
}
|
|
13562
13635
|
function readSessionChecklist(root, record) {
|
|
13563
|
-
const plan =
|
|
13636
|
+
const plan = join15(root, record.plan);
|
|
13564
13637
|
const file = [trackerPathFor(plan), plan].find((path3) => isFile(path3));
|
|
13565
13638
|
if (file === undefined)
|
|
13566
13639
|
return null;
|
|
13567
|
-
const content =
|
|
13640
|
+
const content = readFileSync22(file, "utf8");
|
|
13568
13641
|
return { file, tasks: parsePlan(content).tasks, lines: content.split(`
|
|
13569
13642
|
`) };
|
|
13570
13643
|
}
|
|
@@ -13655,6 +13728,668 @@ function watchDryEpic() {
|
|
|
13655
13728
|
};
|
|
13656
13729
|
}
|
|
13657
13730
|
|
|
13731
|
+
// src/board/epic-board.ts
|
|
13732
|
+
function openBoards(listing2) {
|
|
13733
|
+
return listing2.filter((issue3) => issue3.state === "OPEN" && issue3.labels.includes(ROADMAP_LABEL)).sort((a, b) => a.number - b.number);
|
|
13734
|
+
}
|
|
13735
|
+
function boardListsEpic(number, epic, view) {
|
|
13736
|
+
const row = view.rows.get(number);
|
|
13737
|
+
if (row?.state !== "OPEN")
|
|
13738
|
+
return false;
|
|
13739
|
+
return parseRoadmapBody(row.body).some((line) => line.issue === epic);
|
|
13740
|
+
}
|
|
13741
|
+
async function boardOfEpic(epic, current, view) {
|
|
13742
|
+
if (boardListsEpic(current.board, epic, view))
|
|
13743
|
+
return current.board;
|
|
13744
|
+
const fallback = await view.defaultBoard();
|
|
13745
|
+
if (boardListsEpic(fallback, epic, view))
|
|
13746
|
+
return fallback;
|
|
13747
|
+
return openBoards(view.listing).find((row) => boardListsEpic(row.number, epic, view))?.number ?? fallback;
|
|
13748
|
+
}
|
|
13749
|
+
|
|
13750
|
+
// src/board/blocker-epic.ts
|
|
13751
|
+
var REASON_SENTENCES = Object.freeze({
|
|
13752
|
+
"cross-repository": "is on another repository, which the board listing does not read",
|
|
13753
|
+
"not-on-listing": "is not on the board listing",
|
|
13754
|
+
"no-epic-label": `carries no ${EPIC_LABEL_PREFIX}<slug> label, so no epic holds it`,
|
|
13755
|
+
"same-epic": "is in the home epic, so there is no other epic to hop to",
|
|
13756
|
+
"no-epic": "carries an epic label no epic issue owns",
|
|
13757
|
+
"closed-epic": "is in a closed epic",
|
|
13758
|
+
"no-open-board": "is in an epic no open board lists"
|
|
13759
|
+
});
|
|
13760
|
+
function noHopSentence(answer2) {
|
|
13761
|
+
const name = typeof answer2.blocker === "number" ? `#${String(answer2.blocker)}` : answer2.blocker;
|
|
13762
|
+
const epic = answer2.epic === null ? "" : ` (epic #${String(answer2.epic)})`;
|
|
13763
|
+
return `${name}${epic} ${REASON_SENTENCES[answer2.reason]}`;
|
|
13764
|
+
}
|
|
13765
|
+
function epicOfSlug(slug, listing2) {
|
|
13766
|
+
return listing2.filter((row) => row.type === "epic" && epicSlugsOf(row.labels)[0] === slug).sort((a, b) => a.number - b.number)[0];
|
|
13767
|
+
}
|
|
13768
|
+
function noHop(blocker, reason, epic = null) {
|
|
13769
|
+
return Object.freeze({ kind: "no-hop", blocker, reason, epic });
|
|
13770
|
+
}
|
|
13771
|
+
async function locateBlockerEpic(request) {
|
|
13772
|
+
const { blocker, home, view } = request;
|
|
13773
|
+
if (typeof blocker === "string")
|
|
13774
|
+
return noHop(blocker, "cross-repository");
|
|
13775
|
+
const row = view.rows.get(blocker);
|
|
13776
|
+
if (row === undefined)
|
|
13777
|
+
return noHop(blocker, "not-on-listing");
|
|
13778
|
+
const slugs = epicSlugsOf(row.labels);
|
|
13779
|
+
if (slugs.length === 0)
|
|
13780
|
+
return noHop(blocker, "no-epic-label");
|
|
13781
|
+
const epics = slugs.map((slug2) => ({ slug: slug2, epic: epicOfSlug(slug2, view.listing) }));
|
|
13782
|
+
if (home.epic !== null && epics.some(({ epic: epic2 }) => epic2?.number === home.epic)) {
|
|
13783
|
+
return noHop(blocker, "same-epic", home.epic);
|
|
13784
|
+
}
|
|
13785
|
+
const [first] = epics;
|
|
13786
|
+
if (first?.epic === undefined)
|
|
13787
|
+
return noHop(blocker, "no-epic");
|
|
13788
|
+
const { slug, epic } = first;
|
|
13789
|
+
if (epic.state === "CLOSED")
|
|
13790
|
+
return noHop(blocker, "closed-epic", epic.number);
|
|
13791
|
+
const board = await boardOfEpic(epic.number, home, view);
|
|
13792
|
+
if (!boardListsEpic(board, epic.number, view))
|
|
13793
|
+
return noHop(blocker, "no-open-board", epic.number);
|
|
13794
|
+
return Object.freeze({ kind: "located", blocker, epic: epic.number, slug, board });
|
|
13795
|
+
}
|
|
13796
|
+
|
|
13797
|
+
// src/next/hop-chain.ts
|
|
13798
|
+
function oneHopAllows(from, to, home) {
|
|
13799
|
+
return samePlace(from, home) || samePlace(to, home);
|
|
13800
|
+
}
|
|
13801
|
+
function chainText(chain) {
|
|
13802
|
+
const head = `#${String(chain.blocked)} \u2190 #${String(chain.blocker)}`;
|
|
13803
|
+
return chain.next.length === 0 ? head : `${head} \u2190 ${chain.next.map((id2) => `#${String(id2)}`).join(", ")}`;
|
|
13804
|
+
}
|
|
13805
|
+
function placeText(place) {
|
|
13806
|
+
return place.epic === null ? `board #${String(place.board)}` : `epic #${String(place.epic)} on board #${String(place.board)}`;
|
|
13807
|
+
}
|
|
13808
|
+
function leftText(place) {
|
|
13809
|
+
return place.epic === null ? `board #${String(place.board)}` : `epic #${String(place.epic)}`;
|
|
13810
|
+
}
|
|
13811
|
+
function hopMoveSentence(move) {
|
|
13812
|
+
return `hop from ${leftText(move.from)}: #${String(move.blocked)} blocked by #${String(move.blocker)}, in epic #${String(move.epic)}`;
|
|
13813
|
+
}
|
|
13814
|
+
function haltSentence(halt) {
|
|
13815
|
+
const chain = `halt: ${chainText(halt.chain)}`;
|
|
13816
|
+
if (halt.reason === "both-away") {
|
|
13817
|
+
return `${chain}: the move from ${placeText(halt.from)} to ${placeText(halt.to)} has neither end at home`;
|
|
13818
|
+
}
|
|
13819
|
+
if (halt.fault !== null)
|
|
13820
|
+
return `${chain}: ${halt.fault}`;
|
|
13821
|
+
return halt.chain.mutual ? `${chain}: #${String(halt.chain.blocked)} and #${String(halt.chain.blocker)} block each other` : `${chain}: #${String(halt.chain.blocker)} is blocked in turn`;
|
|
13822
|
+
}
|
|
13823
|
+
function takenText(taken) {
|
|
13824
|
+
return taken.by === "branch" ? `branch ${taken.branch} exists` : `PR #${String(taken.pullRequest)} open`;
|
|
13825
|
+
}
|
|
13826
|
+
function hopDecisionSentence(decision) {
|
|
13827
|
+
if (decision.kind === "hop")
|
|
13828
|
+
return hopMoveSentence(decision);
|
|
13829
|
+
if (decision.kind === "wait") {
|
|
13830
|
+
return `#${String(decision.blocked)} waits on #${String(decision.blocker)}, taken: ${takenText(decision.taken)}`;
|
|
13831
|
+
}
|
|
13832
|
+
if (decision.kind === "halt")
|
|
13833
|
+
return haltSentence(decision);
|
|
13834
|
+
return `#${String(decision.blocked)} stays: ${noHopSentence(decision.located)}`;
|
|
13835
|
+
}
|
|
13836
|
+
async function takenBy(blocker, taken) {
|
|
13837
|
+
const branch = taken.branchFor(blocker);
|
|
13838
|
+
if (branch !== null)
|
|
13839
|
+
return { by: "branch", branch };
|
|
13840
|
+
const pullRequest = await taken.pullRequestFor(blocker);
|
|
13841
|
+
return pullRequest === null ? null : { by: "pull-request", pullRequest };
|
|
13842
|
+
}
|
|
13843
|
+
function blockerLine(blocker, view) {
|
|
13844
|
+
const row = view.rows.get(blocker);
|
|
13845
|
+
if (row === undefined)
|
|
13846
|
+
return Promise.resolve(null);
|
|
13847
|
+
const states = (issue3) => Promise.resolve(view.rows.get(issue3)?.state ?? null);
|
|
13848
|
+
return readBlockedLine({ ...row, author: "" }, states);
|
|
13849
|
+
}
|
|
13850
|
+
function chainOf(blocked2, blocker, next) {
|
|
13851
|
+
return Object.freeze({ blocked: blocked2, blocker, next: Object.freeze([...next]), mutual: next.includes(blocked2) });
|
|
13852
|
+
}
|
|
13853
|
+
function halt(reason, chain, from, to, fault = null) {
|
|
13854
|
+
return Object.freeze({ kind: "halt", reason, chain, from, to, fault });
|
|
13855
|
+
}
|
|
13856
|
+
async function decideHop(request) {
|
|
13857
|
+
const { blocked: blocked2, blocker, position, view } = request;
|
|
13858
|
+
const from = position.current;
|
|
13859
|
+
const located = await locateBlockerEpic({ blocker, home: from, view });
|
|
13860
|
+
if (located.kind === "no-hop")
|
|
13861
|
+
return Object.freeze({ kind: "stay", blocked: blocked2, located });
|
|
13862
|
+
const to = Object.freeze({ board: located.board, epic: located.epic });
|
|
13863
|
+
if (!oneHopAllows(from, to, position.home)) {
|
|
13864
|
+
return halt("both-away", chainOf(blocked2, located.blocker, []), from, to);
|
|
13865
|
+
}
|
|
13866
|
+
const taken = await takenBy(located.blocker, request.taken);
|
|
13867
|
+
if (taken !== null) {
|
|
13868
|
+
return Object.freeze({
|
|
13869
|
+
kind: "wait",
|
|
13870
|
+
blocked: blocked2,
|
|
13871
|
+
blocker: located.blocker,
|
|
13872
|
+
epic: located.epic,
|
|
13873
|
+
board: located.board,
|
|
13874
|
+
taken: Object.freeze(taken)
|
|
13875
|
+
});
|
|
13876
|
+
}
|
|
13877
|
+
const line = await blockerLine(located.blocker, view);
|
|
13878
|
+
if (line !== null) {
|
|
13879
|
+
const held = line.blockers.filter((id2) => line.open.includes(id2) || line.unread.includes(id2));
|
|
13880
|
+
const chain = chainOf(blocked2, located.blocker, held);
|
|
13881
|
+
return halt("blocked-blocker", chain, from, to, line.fault);
|
|
13882
|
+
}
|
|
13883
|
+
return Object.freeze({
|
|
13884
|
+
kind: "hop",
|
|
13885
|
+
blocked: blocked2,
|
|
13886
|
+
blocker: located.blocker,
|
|
13887
|
+
epic: located.epic,
|
|
13888
|
+
slug: located.slug,
|
|
13889
|
+
board: located.board,
|
|
13890
|
+
from,
|
|
13891
|
+
to
|
|
13892
|
+
});
|
|
13893
|
+
}
|
|
13894
|
+
|
|
13895
|
+
// src/board/board-body.ts
|
|
13896
|
+
var OWNER_FIELD = "Owner";
|
|
13897
|
+
var BOARD_OWNS_FIELD = "Owns";
|
|
13898
|
+
var LOGIN = "[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}";
|
|
13899
|
+
var TEAM = "[A-Za-z0-9][A-Za-z0-9_.-]*";
|
|
13900
|
+
var HANDLE = new RegExp(`^@${LOGIN}(?:/${TEAM})?$`, "u");
|
|
13901
|
+
function isOwnerHandle(text) {
|
|
13902
|
+
return HANDLE.test(text);
|
|
13903
|
+
}
|
|
13904
|
+
var OWNS_SEPARATOR2 = /[\s,]+/u;
|
|
13905
|
+
function undressed(text) {
|
|
13906
|
+
return text.replaceAll("`", " ").trim();
|
|
13907
|
+
}
|
|
13908
|
+
function normaliseFolder(folder) {
|
|
13909
|
+
let path3 = folder;
|
|
13910
|
+
while (path3.startsWith("./"))
|
|
13911
|
+
path3 = path3.slice(2);
|
|
13912
|
+
while (path3.endsWith("/"))
|
|
13913
|
+
path3 = path3.slice(0, -1);
|
|
13914
|
+
return path3;
|
|
13915
|
+
}
|
|
13916
|
+
function isRepoRelative(path3) {
|
|
13917
|
+
if (path3 === "" || path3 === "." || path3.startsWith("/"))
|
|
13918
|
+
return false;
|
|
13919
|
+
return !path3.split("/").includes("..");
|
|
13920
|
+
}
|
|
13921
|
+
function ownedFolders2(text) {
|
|
13922
|
+
const named = undressed(text).split(OWNS_SEPARATOR2).filter((folder) => folder !== "");
|
|
13923
|
+
const owns = named.map(normaliseFolder).filter(isRepoRelative);
|
|
13924
|
+
const rejected = named.filter((folder) => !isRepoRelative(normaliseFolder(folder)));
|
|
13925
|
+
return { owns: [...new Set(owns)], rejected };
|
|
13926
|
+
}
|
|
13927
|
+
function readBoardBody(body) {
|
|
13928
|
+
const lines = bodyLines(body);
|
|
13929
|
+
const ownerLine = findField(lines, OWNER_FIELD);
|
|
13930
|
+
const ownsLine = findField(lines, BOARD_OWNS_FIELD);
|
|
13931
|
+
const handle = ownerLine === null ? null : undressed(ownerLine.text);
|
|
13932
|
+
const isOwned = handle !== null && isOwnerHandle(handle);
|
|
13933
|
+
const folders = ownsLine === null ? { owns: [], rejected: [] } : ownedFolders2(ownsLine.text);
|
|
13934
|
+
const problems = [
|
|
13935
|
+
ownerLine === null || isOwned ? null : { kind: "malformed-owner", line: ownerLine.number, text: ownerLine.text, rejected: Object.freeze([]) },
|
|
13936
|
+
ownsLine === null || folders.rejected.length === 0 ? null : { kind: "malformed-owns", line: ownsLine.number, text: ownsLine.text, rejected: Object.freeze(folders.rejected) }
|
|
13937
|
+
];
|
|
13938
|
+
return Object.freeze({
|
|
13939
|
+
lines: parseRoadmapBody(body),
|
|
13940
|
+
owner: isOwned ? handle : null,
|
|
13941
|
+
owns: Object.freeze(folders.owns),
|
|
13942
|
+
problems: Object.freeze(problems.filter((problem) => problem !== null))
|
|
13943
|
+
});
|
|
13944
|
+
}
|
|
13945
|
+
|
|
13946
|
+
// src/board/codeowners.ts
|
|
13947
|
+
import { readFileSync as readFileSync23, statSync as statSync11 } from "fs";
|
|
13948
|
+
import { join as join16 } from "path";
|
|
13949
|
+
var CODEOWNERS_LOCATIONS = Object.freeze([
|
|
13950
|
+
".github/CODEOWNERS",
|
|
13951
|
+
"CODEOWNERS",
|
|
13952
|
+
"docs/CODEOWNERS"
|
|
13953
|
+
]);
|
|
13954
|
+
var HANDLE2 = /^@[^\s@/]+(?:\/[^\s@/]+)?$/u;
|
|
13955
|
+
var EMAIL = /^[^\s@]+@[^\s@]+$/u;
|
|
13956
|
+
var WORDS = /\s+/u;
|
|
13957
|
+
var REGEX_SYNTAX = /[.*+?^${}()|[\]\\/]/gu;
|
|
13958
|
+
function isOwner(owner) {
|
|
13959
|
+
return HANDLE2.test(owner) || EMAIL.test(owner);
|
|
13960
|
+
}
|
|
13961
|
+
function skipReason(pattern, owners) {
|
|
13962
|
+
if (pattern.startsWith("!"))
|
|
13963
|
+
return "negation";
|
|
13964
|
+
if (pattern.includes("[") || pattern.includes("]"))
|
|
13965
|
+
return "character-range";
|
|
13966
|
+
if (pattern.includes("***"))
|
|
13967
|
+
return "triple-asterisk";
|
|
13968
|
+
if (!owners.every(isOwner))
|
|
13969
|
+
return "malformed-owner";
|
|
13970
|
+
return null;
|
|
13971
|
+
}
|
|
13972
|
+
function parseCodeowners(text) {
|
|
13973
|
+
const rules = [];
|
|
13974
|
+
const skipped2 = [];
|
|
13975
|
+
text.split(/\r?\n/u).forEach((raw, index) => {
|
|
13976
|
+
const trimmed = raw.trim();
|
|
13977
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
13978
|
+
return;
|
|
13979
|
+
const words = trimmed.split(WORDS);
|
|
13980
|
+
const comment = words.findIndex((word) => word.startsWith("#"));
|
|
13981
|
+
const [pattern = "", ...owners] = comment === -1 ? words : words.slice(0, comment);
|
|
13982
|
+
const line = index + 1;
|
|
13983
|
+
const reason = skipReason(pattern, owners);
|
|
13984
|
+
if (reason === null) {
|
|
13985
|
+
rules.push(Object.freeze({ pattern, owners: Object.freeze(owners), line }));
|
|
13986
|
+
} else {
|
|
13987
|
+
skipped2.push(Object.freeze({ line, text: trimmed, reason }));
|
|
13988
|
+
}
|
|
13989
|
+
});
|
|
13990
|
+
return { rules: Object.freeze(rules), skipped: Object.freeze(skipped2) };
|
|
13991
|
+
}
|
|
13992
|
+
function isFile2(path3) {
|
|
13993
|
+
try {
|
|
13994
|
+
return statSync11(path3).isFile();
|
|
13995
|
+
} catch {
|
|
13996
|
+
return false;
|
|
13997
|
+
}
|
|
13998
|
+
}
|
|
13999
|
+
function findCodeowners(root) {
|
|
14000
|
+
return CODEOWNERS_LOCATIONS.find((location) => isFile2(join16(root, location))) ?? null;
|
|
14001
|
+
}
|
|
14002
|
+
function readCodeowners(root) {
|
|
14003
|
+
const path3 = findCodeowners(root);
|
|
14004
|
+
if (path3 === null)
|
|
14005
|
+
return null;
|
|
14006
|
+
let text;
|
|
14007
|
+
try {
|
|
14008
|
+
text = readFileSync23(join16(root, path3), "utf8");
|
|
14009
|
+
} catch (error) {
|
|
14010
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
14011
|
+
throw new Error(`Cannot read ${path3}: ${why}`, { cause: error });
|
|
14012
|
+
}
|
|
14013
|
+
return Object.freeze({ path: path3, ...parseCodeowners(text) });
|
|
14014
|
+
}
|
|
14015
|
+
function segmentSource(segment) {
|
|
14016
|
+
let source = "";
|
|
14017
|
+
let isEscaped = false;
|
|
14018
|
+
for (const char of segment) {
|
|
14019
|
+
if (isEscaped) {
|
|
14020
|
+
source += char.replace(REGEX_SYNTAX, "\\$&");
|
|
14021
|
+
isEscaped = false;
|
|
14022
|
+
} else if (char === "\\") {
|
|
14023
|
+
isEscaped = true;
|
|
14024
|
+
} else if (char === "*") {
|
|
14025
|
+
source += "[^/]*";
|
|
14026
|
+
} else if (char === "?") {
|
|
14027
|
+
source += "[^/]";
|
|
14028
|
+
} else {
|
|
14029
|
+
source += char.replace(REGEX_SYNTAX, "\\$&");
|
|
14030
|
+
}
|
|
14031
|
+
}
|
|
14032
|
+
return source;
|
|
14033
|
+
}
|
|
14034
|
+
function patternSegments(pattern) {
|
|
14035
|
+
const split = pattern.split("/");
|
|
14036
|
+
const isSingle = split.length === 1 || split.length === 2 && split[1] === "";
|
|
14037
|
+
const anchored = split[0] === "" ? split.slice(1) : split;
|
|
14038
|
+
const floating = split[0] !== "" && isSingle && split[0] !== "**" ? ["**", ...anchored] : anchored;
|
|
14039
|
+
return floating.length > 1 && floating.at(-1) === "" ? [...floating.slice(0, -1), "**"] : floating;
|
|
14040
|
+
}
|
|
14041
|
+
function globstarSource(index, last) {
|
|
14042
|
+
if (index === 0) {
|
|
14043
|
+
return index === last ? ".+" : "(?:.+/)?";
|
|
14044
|
+
}
|
|
14045
|
+
return index === last ? "/.*" : "(?:/.+)?";
|
|
14046
|
+
}
|
|
14047
|
+
function patternRegex(pattern) {
|
|
14048
|
+
if (pattern === "/")
|
|
14049
|
+
return /^$/u;
|
|
14050
|
+
const segments = patternSegments(pattern);
|
|
14051
|
+
const last = segments.length - 1;
|
|
14052
|
+
let source = "";
|
|
14053
|
+
let needsSlash = false;
|
|
14054
|
+
segments.forEach((segment, index) => {
|
|
14055
|
+
if (segment === "**") {
|
|
14056
|
+
source += globstarSource(index, last);
|
|
14057
|
+
needsSlash = index !== 0;
|
|
14058
|
+
return;
|
|
14059
|
+
}
|
|
14060
|
+
const slash = needsSlash ? "/" : "";
|
|
14061
|
+
if (segment === "*") {
|
|
14062
|
+
source += `${slash}[^/]+`;
|
|
14063
|
+
} else {
|
|
14064
|
+
const under = index === last ? "(?:/.*)?" : "";
|
|
14065
|
+
source += `${slash}${segmentSource(segment)}${under}`;
|
|
14066
|
+
}
|
|
14067
|
+
needsSlash = true;
|
|
14068
|
+
});
|
|
14069
|
+
return new RegExp(`^${source}$`, "u");
|
|
14070
|
+
}
|
|
14071
|
+
function repoRelative(path3) {
|
|
14072
|
+
let relative4 = path3;
|
|
14073
|
+
while (relative4.startsWith("./") || relative4.startsWith("/")) {
|
|
14074
|
+
relative4 = relative4.startsWith("/") ? relative4.slice(1) : relative4.slice(2);
|
|
14075
|
+
}
|
|
14076
|
+
return relative4;
|
|
14077
|
+
}
|
|
14078
|
+
function matchesPattern(pattern, path3) {
|
|
14079
|
+
return pattern !== "" && patternRegex(pattern).test(repoRelative(path3));
|
|
14080
|
+
}
|
|
14081
|
+
function lastMatchingRule(file, path3) {
|
|
14082
|
+
const matching = file.rules.filter((rule) => matchesPattern(rule.pattern, path3));
|
|
14083
|
+
return matching.at(-1) ?? null;
|
|
14084
|
+
}
|
|
14085
|
+
function rulesNaming(file, owner) {
|
|
14086
|
+
const wanted = owner.toLowerCase();
|
|
14087
|
+
return Object.freeze(file.rules.filter((rule) => rule.owners.some((named) => named.toLowerCase() === wanted)));
|
|
14088
|
+
}
|
|
14089
|
+
|
|
14090
|
+
// src/board/board-owns.ts
|
|
14091
|
+
function ownedBoard(issue3) {
|
|
14092
|
+
const { owner, owns } = readBoardBody(issue3.body);
|
|
14093
|
+
return Object.freeze({ number: issue3.number, owner, owns });
|
|
14094
|
+
}
|
|
14095
|
+
function boardFolders(board, codeowners) {
|
|
14096
|
+
if (codeowners !== null && board.owner !== null) {
|
|
14097
|
+
const folders = rulesNaming(codeowners, board.owner).map((rule) => rule.pattern);
|
|
14098
|
+
return Object.freeze({ board: board.number, source: "codeowners", folders: Object.freeze(folders) });
|
|
14099
|
+
}
|
|
14100
|
+
if (codeowners === null && board.owns.length > 0) {
|
|
14101
|
+
return Object.freeze({ board: board.number, source: "owns", folders: Object.freeze([...board.owns]) });
|
|
14102
|
+
}
|
|
14103
|
+
return Object.freeze({ board: board.number, source: "repository", folders: Object.freeze([]) });
|
|
14104
|
+
}
|
|
14105
|
+
function repoPath(path3) {
|
|
14106
|
+
let relative4 = path3;
|
|
14107
|
+
while (relative4.startsWith("./") || relative4.startsWith("/")) {
|
|
14108
|
+
relative4 = relative4.startsWith("/") ? relative4.slice(1) : relative4.slice(2);
|
|
14109
|
+
}
|
|
14110
|
+
while (relative4.endsWith("/"))
|
|
14111
|
+
relative4 = relative4.slice(0, -1);
|
|
14112
|
+
return relative4;
|
|
14113
|
+
}
|
|
14114
|
+
function holds(folder, path3) {
|
|
14115
|
+
return path3 === folder || path3.startsWith(`${folder}/`);
|
|
14116
|
+
}
|
|
14117
|
+
function byNumber2(boards) {
|
|
14118
|
+
return [...boards].sort((a, b) => a.board - b.board);
|
|
14119
|
+
}
|
|
14120
|
+
function codeownersOwner(path3, boards, folders, codeowners) {
|
|
14121
|
+
const rule = lastMatchingRule(codeowners, path3);
|
|
14122
|
+
if (rule === null)
|
|
14123
|
+
return null;
|
|
14124
|
+
const ranked = byNumber2(folders.filter((entry) => entry.source === "codeowners"));
|
|
14125
|
+
const ownerOf = new Map(boards.map((board) => [board.number, board.owner?.toLowerCase() ?? null]));
|
|
14126
|
+
for (const named of rule.owners) {
|
|
14127
|
+
const wanted = named.toLowerCase();
|
|
14128
|
+
const board = ranked.find((entry) => ownerOf.get(entry.board) === wanted);
|
|
14129
|
+
if (board !== undefined)
|
|
14130
|
+
return Object.freeze({ board: board.board, source: "codeowners", match: rule.pattern });
|
|
14131
|
+
}
|
|
14132
|
+
return null;
|
|
14133
|
+
}
|
|
14134
|
+
function ownsOwner(path3, folders) {
|
|
14135
|
+
let best = null;
|
|
14136
|
+
for (const entry of byNumber2(folders.filter((candidate) => candidate.source === "owns"))) {
|
|
14137
|
+
for (const folder of entry.folders) {
|
|
14138
|
+
const depth = folder.split("/").length;
|
|
14139
|
+
if (holds(folder, path3) && (best === null || depth > best.depth)) {
|
|
14140
|
+
best = { board: entry.board, folder, depth };
|
|
14141
|
+
}
|
|
14142
|
+
}
|
|
14143
|
+
}
|
|
14144
|
+
return best === null ? null : Object.freeze({ board: best.board, source: "owns", match: best.folder });
|
|
14145
|
+
}
|
|
14146
|
+
function owningBoard(path3, boards, codeowners) {
|
|
14147
|
+
const relative4 = repoPath(path3);
|
|
14148
|
+
const folders = boards.map((board) => boardFolders(board, codeowners));
|
|
14149
|
+
const specific = codeowners === null ? ownsOwner(relative4, folders) : codeownersOwner(relative4, boards, folders, codeowners);
|
|
14150
|
+
if (specific !== null)
|
|
14151
|
+
return specific;
|
|
14152
|
+
const [whole] = byNumber2(folders.filter((entry) => entry.source === "repository"));
|
|
14153
|
+
return whole === undefined ? null : Object.freeze({ board: whole.board, source: "repository", match: null });
|
|
14154
|
+
}
|
|
14155
|
+
|
|
14156
|
+
// src/board/owner-resolve.ts
|
|
14157
|
+
var NOT_FOUND = "(HTTP 404)";
|
|
14158
|
+
function ownerLookupArgs(handle) {
|
|
14159
|
+
if (!isOwnerHandle(handle)) {
|
|
14160
|
+
return null;
|
|
14161
|
+
}
|
|
14162
|
+
const [account = "", slug] = handle.slice(1).split("/");
|
|
14163
|
+
return slug === undefined ? ["api", `users/${account}`] : ["api", `orgs/${account}/teams/${slug}`];
|
|
14164
|
+
}
|
|
14165
|
+
function failureReason(result, command) {
|
|
14166
|
+
const written = result.stderr.trim() || result.stdout.trim();
|
|
14167
|
+
return written === "" ? `${command} failed and wrote nothing` : `${command} failed: ${written}`;
|
|
14168
|
+
}
|
|
14169
|
+
async function lookUp(gh, handle) {
|
|
14170
|
+
const args = ownerLookupArgs(handle);
|
|
14171
|
+
if (args === null) {
|
|
14172
|
+
return { handle, state: "unknown", reason: `${JSON.stringify(handle)} is not an @login or @org/team handle, so nothing was asked` };
|
|
14173
|
+
}
|
|
14174
|
+
const result = await gh(args);
|
|
14175
|
+
if (result.ok) {
|
|
14176
|
+
return { handle, state: "resolved" };
|
|
14177
|
+
}
|
|
14178
|
+
return result.stderr.includes(NOT_FOUND) ? { handle, state: "unresolved" } : { handle, state: "unknown", reason: failureReason(result, `gh ${args.join(" ")}`) };
|
|
14179
|
+
}
|
|
14180
|
+
function createOwnerResolver(options) {
|
|
14181
|
+
const { gh } = options;
|
|
14182
|
+
const answers = new Map;
|
|
14183
|
+
return (handle) => {
|
|
14184
|
+
const key = handle.toLowerCase();
|
|
14185
|
+
const known = answers.get(key);
|
|
14186
|
+
if (known !== undefined) {
|
|
14187
|
+
return known.then((answer2) => ({ ...answer2, handle }));
|
|
14188
|
+
}
|
|
14189
|
+
const pending = lookUp(gh, handle);
|
|
14190
|
+
answers.set(key, pending);
|
|
14191
|
+
return pending;
|
|
14192
|
+
};
|
|
14193
|
+
}
|
|
14194
|
+
|
|
14195
|
+
// src/pr/owner-approval.ts
|
|
14196
|
+
var APPROVED = "APPROVED";
|
|
14197
|
+
var NOT_FOUND2 = "(HTTP 404)";
|
|
14198
|
+
var SEVERITY = Object.freeze(["unresolved", "unknown", "waiting"]);
|
|
14199
|
+
function messageOf5(error) {
|
|
14200
|
+
return error instanceof Error ? error.message : String(error);
|
|
14201
|
+
}
|
|
14202
|
+
function folded(handle) {
|
|
14203
|
+
return handle === null ? null : handle.toLowerCase();
|
|
14204
|
+
}
|
|
14205
|
+
function teamMembershipArgs(team, login) {
|
|
14206
|
+
const isTeam = isOwnerHandle(team) && team.includes("/");
|
|
14207
|
+
const isLogin = isOwnerHandle(`@${login}`) && !login.includes("/");
|
|
14208
|
+
if (!isTeam || !isLogin)
|
|
14209
|
+
return null;
|
|
14210
|
+
const [org = "", slug = ""] = team.slice(1).split("/");
|
|
14211
|
+
return ["api", `orgs/${org}/teams/${slug}/memberships/${login}`];
|
|
14212
|
+
}
|
|
14213
|
+
function failureReason2(result, command) {
|
|
14214
|
+
const written = result.stderr.trim() || result.stdout.trim();
|
|
14215
|
+
return written === "" ? `${command} failed and wrote nothing` : `${command} failed: ${written}`;
|
|
14216
|
+
}
|
|
14217
|
+
function readMembership(stdout, command) {
|
|
14218
|
+
let parsed;
|
|
14219
|
+
try {
|
|
14220
|
+
parsed = JSON.parse(stdout);
|
|
14221
|
+
} catch {
|
|
14222
|
+
return { state: "unknown", reason: `${command} wrote something that is not JSON` };
|
|
14223
|
+
}
|
|
14224
|
+
const state = typeof parsed === "object" && parsed !== null ? parsed["state"] : undefined;
|
|
14225
|
+
if (state === "active" || state === "pending")
|
|
14226
|
+
return { state };
|
|
14227
|
+
return { state: "unknown", reason: `${command} answered membership state ${JSON.stringify(state) ?? "undefined"}` };
|
|
14228
|
+
}
|
|
14229
|
+
function createGhTeamMembership(options) {
|
|
14230
|
+
const { gh } = options;
|
|
14231
|
+
return async (team, login) => {
|
|
14232
|
+
const args = teamMembershipArgs(team, login);
|
|
14233
|
+
if (args === null) {
|
|
14234
|
+
return {
|
|
14235
|
+
state: "unknown",
|
|
14236
|
+
reason: `${JSON.stringify(login)} in team ${JSON.stringify(team)} is not a login and a team handle, so nothing was asked`
|
|
14237
|
+
};
|
|
14238
|
+
}
|
|
14239
|
+
const command = `gh ${args.join(" ")}`;
|
|
14240
|
+
const result = await gh(args);
|
|
14241
|
+
if (result.ok)
|
|
14242
|
+
return readMembership(result.stdout, command);
|
|
14243
|
+
return result.stderr.includes(NOT_FOUND2) ? { state: "not-member" } : { state: "unknown", reason: failureReason2(result, command) };
|
|
14244
|
+
};
|
|
14245
|
+
}
|
|
14246
|
+
function ref3(number) {
|
|
14247
|
+
return `#${String(number)}`;
|
|
14248
|
+
}
|
|
14249
|
+
function ownerName(owner) {
|
|
14250
|
+
const boards = owner.boards.map(ref3).join(", ");
|
|
14251
|
+
const noun = owner.boards.length === 1 ? "board" : "boards";
|
|
14252
|
+
return owner.handle === null ? `${noun} ${boards}` : `owner ${owner.handle} of ${noun} ${boards}`;
|
|
14253
|
+
}
|
|
14254
|
+
function gatedOwners(paths, request) {
|
|
14255
|
+
const byNumber3 = new Map(request.boards.map((board) => [board.number, board]));
|
|
14256
|
+
const foreign = new Set;
|
|
14257
|
+
for (const path3 of paths) {
|
|
14258
|
+
const owner = owningBoard(path3, request.boards, request.codeowners);
|
|
14259
|
+
if (owner !== null && owner.board !== request.home)
|
|
14260
|
+
foreign.add(owner.board);
|
|
14261
|
+
}
|
|
14262
|
+
if (foreign.size === 0)
|
|
14263
|
+
return [];
|
|
14264
|
+
const home = byNumber3.get(request.home);
|
|
14265
|
+
if (home === undefined)
|
|
14266
|
+
return null;
|
|
14267
|
+
const groups = new Map;
|
|
14268
|
+
for (const number of [...foreign].sort((a, b) => a - b)) {
|
|
14269
|
+
const handle = byNumber3.get(number)?.owner ?? null;
|
|
14270
|
+
if (folded(handle) === folded(home.owner))
|
|
14271
|
+
continue;
|
|
14272
|
+
const key = folded(handle) ?? `board ${String(number)}`;
|
|
14273
|
+
const group = groups.get(key) ?? { handle, boards: [] };
|
|
14274
|
+
groups.set(key, { handle: group.handle, boards: [...group.boards, number] });
|
|
14275
|
+
}
|
|
14276
|
+
return [...groups.values()].map((group) => Object.freeze({ handle: group.handle, boards: Object.freeze(group.boards) }));
|
|
14277
|
+
}
|
|
14278
|
+
function blocked2(state, reason, owners) {
|
|
14279
|
+
return Object.freeze({ state, reason, owners: Object.freeze([...owners]) });
|
|
14280
|
+
}
|
|
14281
|
+
function mostSevere(answers) {
|
|
14282
|
+
const refusals = answers.filter((answer2) => answer2 !== null && answer2.state !== "approved");
|
|
14283
|
+
for (const state of SEVERITY) {
|
|
14284
|
+
const found = refusals.find((refusal3) => refusal3.state === state);
|
|
14285
|
+
if (found !== undefined)
|
|
14286
|
+
return found;
|
|
14287
|
+
}
|
|
14288
|
+
return null;
|
|
14289
|
+
}
|
|
14290
|
+
function latestReviews(reviews) {
|
|
14291
|
+
const ordered = [...reviews].sort((a, b) => {
|
|
14292
|
+
const difference = Date.parse(a.submittedAt) - Date.parse(b.submittedAt);
|
|
14293
|
+
return Number.isNaN(difference) ? 0 : difference;
|
|
14294
|
+
});
|
|
14295
|
+
return new Map(ordered.map((review) => [review.login.toLowerCase(), review]));
|
|
14296
|
+
}
|
|
14297
|
+
async function resolutionRefusal(owner, seams) {
|
|
14298
|
+
const name = ownerName(owner);
|
|
14299
|
+
if (owner.handle === null) {
|
|
14300
|
+
return { state: "unresolved", reason: `${name} names no Owner:, so nobody can approve its paths` };
|
|
14301
|
+
}
|
|
14302
|
+
const resolution = await seams.resolveOwner(owner.handle);
|
|
14303
|
+
if (resolution.state === "unresolved") {
|
|
14304
|
+
return { state: "unresolved", reason: `${name} does not resolve to an account or team GitHub knows` };
|
|
14305
|
+
}
|
|
14306
|
+
return resolution.state === "unknown" ? { state: "unknown", reason: `could not resolve ${name}: ${resolution.reason}` } : null;
|
|
14307
|
+
}
|
|
14308
|
+
async function ownerApproval(owner, latest, membership, pullRequest) {
|
|
14309
|
+
const name = ownerName(owner);
|
|
14310
|
+
if (!owner.handle.includes("/")) {
|
|
14311
|
+
const review = latest.get(owner.handle.slice(1).toLowerCase());
|
|
14312
|
+
return review?.state === APPROVED ? { state: "approved", approvedBy: review.login } : { state: "waiting", reason: `${name} has not approved ${ref3(pullRequest)}` };
|
|
14313
|
+
}
|
|
14314
|
+
const failures = [];
|
|
14315
|
+
for (const review of [...latest.values()].filter((latestReview) => latestReview.state === APPROVED)) {
|
|
14316
|
+
const answer2 = await membership(owner.handle, review.login);
|
|
14317
|
+
if (answer2.state === "active")
|
|
14318
|
+
return { state: "approved", approvedBy: review.login };
|
|
14319
|
+
if (answer2.state === "unknown")
|
|
14320
|
+
failures.push(answer2.reason);
|
|
14321
|
+
}
|
|
14322
|
+
return failures.length === 0 ? { state: "waiting", reason: `no active member of ${name} has approved ${ref3(pullRequest)}` } : { state: "unknown", reason: `could not tell whether ${name} approved ${ref3(pullRequest)}: ${failures.join("; ")}` };
|
|
14323
|
+
}
|
|
14324
|
+
function isHandled(owner) {
|
|
14325
|
+
return owner.handle !== null;
|
|
14326
|
+
}
|
|
14327
|
+
async function readOwnerApproval(request, seams) {
|
|
14328
|
+
const pull = ref3(request.pullRequest);
|
|
14329
|
+
let paths;
|
|
14330
|
+
try {
|
|
14331
|
+
paths = await seams.pullRequests.changedFiles(request.pullRequest);
|
|
14332
|
+
} catch (error) {
|
|
14333
|
+
return blocked2("unknown", `could not read the changed files of ${pull}: ${messageOf5(error)}`, []);
|
|
14334
|
+
}
|
|
14335
|
+
const owners = gatedOwners(paths, request);
|
|
14336
|
+
if (owners === null) {
|
|
14337
|
+
return blocked2("unknown", `home board ${ref3(request.home)} is not among the boards read, so its owner is unknown`, []);
|
|
14338
|
+
}
|
|
14339
|
+
if (owners.length === 0)
|
|
14340
|
+
return Object.freeze({ state: "not-gated" });
|
|
14341
|
+
const unresolved = mostSevere(await Promise.all(owners.map((owner) => resolutionRefusal(owner, seams))));
|
|
14342
|
+
if (unresolved !== null)
|
|
14343
|
+
return blocked2(unresolved.state, unresolved.reason, owners);
|
|
14344
|
+
let latest;
|
|
14345
|
+
try {
|
|
14346
|
+
latest = latestReviews(await seams.pullRequests.reviews(request.pullRequest));
|
|
14347
|
+
} catch (error) {
|
|
14348
|
+
return blocked2("unknown", `could not read the reviews of ${pull}: ${messageOf5(error)}`, owners);
|
|
14349
|
+
}
|
|
14350
|
+
const handled = owners.filter(isHandled);
|
|
14351
|
+
const answers = await Promise.all(handled.map((owner) => ownerApproval(owner, latest, seams.membership, request.pullRequest)));
|
|
14352
|
+
const refused = mostSevere(answers);
|
|
14353
|
+
if (refused !== null)
|
|
14354
|
+
return blocked2(refused.state, refused.reason, owners);
|
|
14355
|
+
const given = handled.flatMap((owner, index) => {
|
|
14356
|
+
const answer2 = answers[index];
|
|
14357
|
+
return answer2?.state === "approved" ? [Object.freeze({ ...owner, approvedBy: answer2.approvedBy })] : [];
|
|
14358
|
+
});
|
|
14359
|
+
return Object.freeze({ state: "approved", owners: Object.freeze(given) });
|
|
14360
|
+
}
|
|
14361
|
+
|
|
14362
|
+
// src/next/owner-gate.ts
|
|
14363
|
+
async function homeBoard(options) {
|
|
14364
|
+
const reading2 = readPositionFile(options.root);
|
|
14365
|
+
if (reading2.set)
|
|
14366
|
+
return reading2.position.home.board;
|
|
14367
|
+
const { gh, configured } = options;
|
|
14368
|
+
const board = await resolveDefaultBoard({
|
|
14369
|
+
configured,
|
|
14370
|
+
listBoards: createGhBoardLister({ gh }),
|
|
14371
|
+
search: createGhRoadmapSearch({ gh })
|
|
14372
|
+
});
|
|
14373
|
+
return board.number;
|
|
14374
|
+
}
|
|
14375
|
+
function gateBoards(listing2, home) {
|
|
14376
|
+
const labelled = openBoards(listing2);
|
|
14377
|
+
const unlabelledHome = labelled.some((board) => board.number === home) ? undefined : listing2.find((issue3) => issue3.number === home && issue3.state === "OPEN");
|
|
14378
|
+
const boards = unlabelledHome === undefined ? labelled : [...labelled, unlabelledHome].sort((a, b) => a.number - b.number);
|
|
14379
|
+
return Object.freeze(boards.map(ownedBoard));
|
|
14380
|
+
}
|
|
14381
|
+
function nextOwnerGate(options) {
|
|
14382
|
+
const { gh, root, pulls } = options;
|
|
14383
|
+
const listing2 = options.listing ?? createGhBoardListing({ gh });
|
|
14384
|
+
const resolveOwner = createOwnerResolver({ gh });
|
|
14385
|
+
const membership = createGhTeamMembership({ gh });
|
|
14386
|
+
return async (pullRequest) => {
|
|
14387
|
+
const home = await homeBoard(options);
|
|
14388
|
+
const boards = gateBoards(await listing2(), home);
|
|
14389
|
+
return readOwnerApproval({ pullRequest, home, boards, codeowners: readCodeowners(root) }, { pullRequests: pulls, resolveOwner, membership });
|
|
14390
|
+
};
|
|
14391
|
+
}
|
|
14392
|
+
|
|
13658
14393
|
// src/next/sources.ts
|
|
13659
14394
|
var PREFIX14 = "rafa next";
|
|
13660
14395
|
var DEFAULT_BASE_BRANCH = "main";
|
|
@@ -13681,20 +14416,143 @@ async function cancelledNotices(listing2) {
|
|
|
13681
14416
|
return [];
|
|
13682
14417
|
}
|
|
13683
14418
|
}
|
|
13684
|
-
async
|
|
14419
|
+
var plainPick = async (lines, readings) => {
|
|
14420
|
+
const pick = await pickNextRoadmapLine(lines, readings);
|
|
14421
|
+
return { line: pick.line, passed: pick.skipped.length, waiting: [] };
|
|
14422
|
+
};
|
|
14423
|
+
async function everyBlockerTaken(blocked3, readings) {
|
|
14424
|
+
if (blocked3 === null || blocked3.fault !== null)
|
|
14425
|
+
return null;
|
|
14426
|
+
const held = blocked3.blockers.filter((id2) => blocked3.open.includes(id2) || blocked3.unread.includes(id2));
|
|
14427
|
+
if (held.length === 0)
|
|
14428
|
+
return null;
|
|
14429
|
+
let taken = [];
|
|
14430
|
+
for (const issue3 of held) {
|
|
14431
|
+
const by = await takenBy(issue3, readings);
|
|
14432
|
+
if (by === null)
|
|
14433
|
+
return null;
|
|
14434
|
+
taken = [...taken, Object.freeze({ issue: issue3, taken: Object.freeze(by) })];
|
|
14435
|
+
}
|
|
14436
|
+
return Object.freeze(taken);
|
|
14437
|
+
}
|
|
14438
|
+
function waitingPick(blocking) {
|
|
14439
|
+
const pick = async (lines, readings, before) => {
|
|
14440
|
+
const next = await pickNextRoadmapLine(lines, readings);
|
|
14441
|
+
const passed2 = before.passed + next.skipped.length;
|
|
14442
|
+
if (next.line === null)
|
|
14443
|
+
return { line: null, passed: passed2, waiting: before.waiting };
|
|
14444
|
+
const blockers = await everyBlockerTaken(await blocking(next.line.issue), readings);
|
|
14445
|
+
if (blockers === null)
|
|
14446
|
+
return { line: next.line, passed: passed2, waiting: before.waiting };
|
|
14447
|
+
const waiting = [...before.waiting, Object.freeze({ line: next.line, blockers })];
|
|
14448
|
+
return pick(lines.slice(lines.indexOf(next.line) + 1), readings, { line: null, passed: passed2 + 1, waiting });
|
|
14449
|
+
};
|
|
14450
|
+
return (lines, readings) => pick(lines, readings, { line: null, passed: 0, waiting: [] });
|
|
14451
|
+
}
|
|
14452
|
+
async function walkEpic(epic, listing2, readings, picker) {
|
|
13685
14453
|
const rows = await listing2();
|
|
13686
14454
|
const read = readEpics({ issues: rows, claims: new Set, today: new Date(0) }).epics.find((candidate) => candidate.number === epic);
|
|
13687
14455
|
const row = rows.find((issue3) => issue3.number === epic);
|
|
13688
14456
|
if (read === undefined || row === undefined) {
|
|
13689
14457
|
throw new Error(`${PREFIX14}: epic #${String(epic)}, the current place, is not on the board listing, so its members cannot be read`);
|
|
13690
14458
|
}
|
|
13691
|
-
const pick = await
|
|
14459
|
+
const pick = await picker(epicLines(read, row).lines, readings);
|
|
13692
14460
|
return {
|
|
13693
14461
|
line: pick.line,
|
|
13694
|
-
passed: pick.
|
|
13695
|
-
dry: pick.line === null ? { number: read.number, title: read.title } : null
|
|
14462
|
+
passed: pick.passed,
|
|
14463
|
+
dry: pick.line === null ? { number: read.number, title: read.title } : null,
|
|
14464
|
+
epic,
|
|
14465
|
+
waiting: pick.waiting
|
|
14466
|
+
};
|
|
14467
|
+
}
|
|
14468
|
+
function followHopRecord(root) {
|
|
14469
|
+
if (root === undefined)
|
|
14470
|
+
return { record: null, stale: null, position: null, problems: [] };
|
|
14471
|
+
const placed = readPositionFile(root);
|
|
14472
|
+
const position = placed.set ? placed.position : null;
|
|
14473
|
+
const hop = readHopRecord(root);
|
|
14474
|
+
if (!hop.set) {
|
|
14475
|
+
const problems = hop.reason === "absent" ? [] : [`${hop.detail}, so no hop is followed`];
|
|
14476
|
+
return { record: null, stale: null, position, problems };
|
|
14477
|
+
}
|
|
14478
|
+
return position === null || staleAgainst(hop.record, position) ? { record: null, stale: hop.record, position, problems: [] } : { record: hop.record, stale: null, position, problems: [] };
|
|
14479
|
+
}
|
|
14480
|
+
function awayTargetOf(record) {
|
|
14481
|
+
if (record?.state !== "away" || record.kind !== "blocker")
|
|
14482
|
+
return null;
|
|
14483
|
+
return record.target;
|
|
14484
|
+
}
|
|
14485
|
+
async function walkTarget(record, target, readings) {
|
|
14486
|
+
const closed = await readings.isClosed(target);
|
|
14487
|
+
const pullRequest = closed ? null : await readings.pullRequestFor(target);
|
|
14488
|
+
const line = closed || pullRequest !== null ? null : Object.freeze({ issue: target, ticked: false, why: "", lineNumber: 0 });
|
|
14489
|
+
return {
|
|
14490
|
+
walk: { line, passed: 0, dry: null, epic: record.targetEpic, waiting: [] },
|
|
14491
|
+
target: Object.freeze({ issue: target, closed, pullRequest })
|
|
14492
|
+
};
|
|
14493
|
+
}
|
|
14494
|
+
var NOTHING_TAKEN = Object.freeze({
|
|
14495
|
+
branchFor: () => null,
|
|
14496
|
+
pullRequestFor: () => Promise.resolve(null)
|
|
14497
|
+
});
|
|
14498
|
+
async function boardView(seams) {
|
|
14499
|
+
const listing2 = await seams.listing();
|
|
14500
|
+
return {
|
|
14501
|
+
listing: listing2,
|
|
14502
|
+
rows: new Map(listing2.map((row) => [row.number, row])),
|
|
14503
|
+
defaultBoard: () => Promise.resolve(seams.fallback)
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
async function decideAtHome(line, places, seams) {
|
|
14507
|
+
const blocked3 = await seams.blocking(line.issue);
|
|
14508
|
+
if (blocked3 === null || blocked3.fault !== null)
|
|
14509
|
+
return null;
|
|
14510
|
+
const held = blocked3.blockers.filter((id2) => blocked3.open.includes(id2) || blocked3.unread.includes(id2));
|
|
14511
|
+
const view = await boardView(seams);
|
|
14512
|
+
let last = null;
|
|
14513
|
+
for (const blocker of held) {
|
|
14514
|
+
last = await decideHop({ blocked: line.issue, blocker, position: places, view, taken: seams.readings });
|
|
14515
|
+
if (last.kind !== "wait")
|
|
14516
|
+
return last;
|
|
14517
|
+
}
|
|
14518
|
+
return last;
|
|
14519
|
+
}
|
|
14520
|
+
async function decideAway(record, target, seams) {
|
|
14521
|
+
if (record.blocked === null || await seams.blocking(target) === null)
|
|
14522
|
+
return null;
|
|
14523
|
+
return decideHop({
|
|
14524
|
+
blocked: record.blocked,
|
|
14525
|
+
blocker: target,
|
|
14526
|
+
position: { current: record.from, home: record.home },
|
|
14527
|
+
view: await boardView(seams),
|
|
14528
|
+
taken: NOTHING_TAKEN
|
|
14529
|
+
});
|
|
14530
|
+
}
|
|
14531
|
+
function placesOf(walk, roadmap, position) {
|
|
14532
|
+
const current = { board: roadmap, epic: walk.epic };
|
|
14533
|
+
const away = position !== null && !samePlace(position.current, position.home);
|
|
14534
|
+
return {
|
|
14535
|
+
current,
|
|
14536
|
+
home: away ? position.home : current
|
|
13696
14537
|
};
|
|
13697
14538
|
}
|
|
14539
|
+
async function decisionFor(walk, followed, seams) {
|
|
14540
|
+
if (walk.line === null)
|
|
14541
|
+
return null;
|
|
14542
|
+
const target = awayTargetOf(followed.record);
|
|
14543
|
+
if (followed.record !== null && target !== null)
|
|
14544
|
+
return decideAway(followed.record, target, seams);
|
|
14545
|
+
return decideAtHome(walk.line, placesOf(walk, seams.roadmap, followed.position), seams);
|
|
14546
|
+
}
|
|
14547
|
+
async function readHopAnswer(walk, followed, seams) {
|
|
14548
|
+
try {
|
|
14549
|
+
const decision = await decisionFor(walk, followed, seams);
|
|
14550
|
+
const nextEpic = walk.dry === null ? null : await nextNowEpic({ after: walk.dry.number, board: seams.roadmap, listing: await seams.listing(), readings: seams.readings });
|
|
14551
|
+
return { decision, nextEpic, problems: [] };
|
|
14552
|
+
} catch (error) {
|
|
14553
|
+
return { decision: null, nextEpic: null, problems: [`the hop could not be read, so none is proposed: ${messageOf(error)}`] };
|
|
14554
|
+
}
|
|
14555
|
+
}
|
|
13698
14556
|
function ghNextBoard(options) {
|
|
13699
14557
|
const { gh, git, configured, remote, root } = options;
|
|
13700
14558
|
const listed = options.listing ?? createGhBoardListing({ gh });
|
|
@@ -13704,6 +14562,10 @@ function ghNextBoard(options) {
|
|
|
13704
14562
|
return listed();
|
|
13705
14563
|
});
|
|
13706
14564
|
const issues = memoiseIssues2(createGhSpecIssueReader({ gh }));
|
|
14565
|
+
const board = {
|
|
14566
|
+
blocking: async (issue3) => readBlockedLine(await issues(issue3), blockerStatesOf(issues)),
|
|
14567
|
+
isReady: async (issue3) => hasSpecReadyLabel((await issues(issue3)).labels)
|
|
14568
|
+
};
|
|
13707
14569
|
return Object.freeze({
|
|
13708
14570
|
next: async () => {
|
|
13709
14571
|
const { number: fallback } = await resolveDefaultBoard({
|
|
@@ -13720,27 +14582,47 @@ function ghNextBoard(options) {
|
|
|
13720
14582
|
branches,
|
|
13721
14583
|
pullRequests: createGhOpenPullRequests({ gh })
|
|
13722
14584
|
});
|
|
13723
|
-
const
|
|
14585
|
+
const followed = options.roadmap === true ? followHopRecord(root) : null;
|
|
14586
|
+
const picker = followed === null ? plainPick : waitingPick(board.blocking);
|
|
14587
|
+
const record = followed?.record ?? null;
|
|
14588
|
+
const target = awayTargetOf(record);
|
|
14589
|
+
const away = record !== null && target !== null ? await walkTarget(record, target, readings) : null;
|
|
14590
|
+
const walk = away?.walk ?? (epic === null ? await walkBoard(roadmap, { issues, readings, listing: listing2 }, picker) : await walkEpic(epic, listing2, readings, picker));
|
|
14591
|
+
const answer2 = followed === null ? null : await readHopAnswer(walk, followed, { blocking: board.blocking, readings, listing: listing2, fallback, roadmap });
|
|
13724
14592
|
const cancelled = options.noticeCancelled === true && asked ? await cancelledNotices(listing2) : [];
|
|
13725
14593
|
return {
|
|
13726
14594
|
roadmap,
|
|
13727
14595
|
line: walk.line,
|
|
13728
14596
|
passed: walk.passed,
|
|
13729
|
-
problems: [...current?.notices ?? [], ...cancelled, ...branches.problems],
|
|
13730
|
-
...walk.dry === null ? {} : { dryEpic: walk.dry }
|
|
14597
|
+
problems: [...current?.notices ?? [], ...cancelled, ...branches.problems, ...followed?.problems ?? [], ...answer2?.problems ?? []],
|
|
14598
|
+
...walk.dry === null ? {} : { dryEpic: walk.dry },
|
|
14599
|
+
...followed === null || answer2 === null ? {} : {
|
|
14600
|
+
hop: Object.freeze({
|
|
14601
|
+
record: followed.record,
|
|
14602
|
+
stale: followed.stale,
|
|
14603
|
+
position: followed.position,
|
|
14604
|
+
target: away?.target ?? null,
|
|
14605
|
+
waiting: walk.waiting,
|
|
14606
|
+
decision: answer2.decision,
|
|
14607
|
+
nextEpic: answer2.nextEpic
|
|
14608
|
+
})
|
|
14609
|
+
}
|
|
13731
14610
|
};
|
|
13732
14611
|
},
|
|
13733
|
-
blocking:
|
|
13734
|
-
isReady:
|
|
14612
|
+
blocking: board.blocking,
|
|
14613
|
+
isReady: board.isReady
|
|
13735
14614
|
});
|
|
13736
14615
|
}
|
|
13737
|
-
async function walkBoard(roadmap, seams) {
|
|
14616
|
+
async function walkBoard(roadmap, seams, picker) {
|
|
13738
14617
|
const read = await seams.issues(roadmap);
|
|
13739
|
-
const
|
|
14618
|
+
const descent = await descendRoadmap(parseRoadmapBody(read.body), seams);
|
|
14619
|
+
const pick = await picker(descent.lines, seams.readings);
|
|
13740
14620
|
return {
|
|
13741
14621
|
line: pick.line,
|
|
13742
|
-
passed: descent.passed.length + pick.
|
|
13743
|
-
dry:
|
|
14622
|
+
passed: descent.passed.length + pick.passed,
|
|
14623
|
+
dry: descent.epic !== null && pick.line === null ? { number: descent.epic.number, title: descent.epic.title } : null,
|
|
14624
|
+
epic: descent.epic?.number ?? null,
|
|
14625
|
+
waiting: pick.waiting
|
|
13744
14626
|
};
|
|
13745
14627
|
}
|
|
13746
14628
|
function nextProject(context) {
|
|
@@ -13761,7 +14643,7 @@ function nextConfig(project, warn) {
|
|
|
13761
14643
|
`));
|
|
13762
14644
|
}
|
|
13763
14645
|
}
|
|
13764
|
-
function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
|
|
14646
|
+
function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS, options = {}) {
|
|
13765
14647
|
const project = nextProject(context);
|
|
13766
14648
|
const config = nextConfig(project, (message) => {
|
|
13767
14649
|
context.output.warn(message);
|
|
@@ -13774,13 +14656,23 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
|
|
|
13774
14656
|
const git = (seams.openGit ?? createGitRunner)(project.root);
|
|
13775
14657
|
const gh = (seams.openGh ?? ((root) => createGhRunner({ cwd: root })))(project.root);
|
|
13776
14658
|
const loop = resolveLoopSeams({ isAlive: seams.isAlive });
|
|
13777
|
-
const
|
|
14659
|
+
const roadmap = options.roadmap === true;
|
|
14660
|
+
const board = () => ghNextBoard({
|
|
14661
|
+
gh,
|
|
14662
|
+
git,
|
|
14663
|
+
configured: config.roadmapIssue,
|
|
14664
|
+
root: project.root,
|
|
14665
|
+
noticeCancelled: true,
|
|
14666
|
+
...roadmap ? { roadmap } : {}
|
|
14667
|
+
});
|
|
14668
|
+
const pulls = (seams.pullRequests ?? ghPullRequestsIn)(project.root);
|
|
13778
14669
|
const held = {
|
|
13779
14670
|
base: config.prBase ?? DEFAULT_BASE_BRANCH,
|
|
13780
14671
|
plans: plansDirAt(project.root, config.planDir),
|
|
13781
14672
|
runs: () => readRecords(project.root, loop),
|
|
13782
14673
|
git,
|
|
13783
|
-
pulls
|
|
14674
|
+
pulls,
|
|
14675
|
+
...roadmap ? { roadmap: Object.freeze({ ownerApproval: nextOwnerGate({ gh, root: project.root, pulls, configured: config.roadmapIssue }) }) } : {}
|
|
13784
14676
|
};
|
|
13785
14677
|
return Object.freeze({
|
|
13786
14678
|
...held,
|
|
@@ -13880,7 +14772,7 @@ function hasRun(records, stub) {
|
|
|
13880
14772
|
}
|
|
13881
14773
|
function hasBranch(refs, stub) {
|
|
13882
14774
|
const branch = `${BRANCH_PREFIX2}${stub}`;
|
|
13883
|
-
return refs.some((
|
|
14775
|
+
return refs.some((ref4) => ref4 === branch || ref4.endsWith(`/${branch}`));
|
|
13884
14776
|
}
|
|
13885
14777
|
function openWorld(sources) {
|
|
13886
14778
|
let problems = [];
|
|
@@ -13931,6 +14823,221 @@ function branchLabel(world) {
|
|
|
13931
14823
|
return branch === "" ? "this checkout" : `\`${branch}\``;
|
|
13932
14824
|
}
|
|
13933
14825
|
|
|
14826
|
+
// src/next/hop-rows.ts
|
|
14827
|
+
function oneLine2(text) {
|
|
14828
|
+
return text.replace(/\s+/g, " ").trim();
|
|
14829
|
+
}
|
|
14830
|
+
function ref4(issue3) {
|
|
14831
|
+
return `#${String(issue3)}`;
|
|
14832
|
+
}
|
|
14833
|
+
function placeText2(place) {
|
|
14834
|
+
return place.epic === null ? `board ${ref4(place.board)}` : `epic ${ref4(place.epic)} on board ${ref4(place.board)}`;
|
|
14835
|
+
}
|
|
14836
|
+
function dryHopSentence(dry, next) {
|
|
14837
|
+
return `hop from epic ${ref4(dry.number)}: it ran dry, next \`now\` epic ${ref4(next.number)} on board ${ref4(next.board)}`;
|
|
14838
|
+
}
|
|
14839
|
+
async function hopView(world) {
|
|
14840
|
+
if (!onBase(world))
|
|
14841
|
+
return null;
|
|
14842
|
+
const reading2 = await world.roadmap();
|
|
14843
|
+
return reading2.hop === undefined ? null : { reading: reading2, hop: reading2.hop };
|
|
14844
|
+
}
|
|
14845
|
+
function awayRecord(hop) {
|
|
14846
|
+
return hop.record?.state === "away" ? hop.record : null;
|
|
14847
|
+
}
|
|
14848
|
+
function homeOf(view) {
|
|
14849
|
+
return view.hop.position?.home ?? { board: view.reading.roadmap, epic: null };
|
|
14850
|
+
}
|
|
14851
|
+
function homeAnswer(id2, reading2, step, issue3) {
|
|
14852
|
+
return {
|
|
14853
|
+
id: id2,
|
|
14854
|
+
action: "home",
|
|
14855
|
+
reading: reading2,
|
|
14856
|
+
proposal: `go back home to ${placeText2(step.home)}`,
|
|
14857
|
+
...issue3 === null ? {} : { issue: issue3 },
|
|
14858
|
+
...step.pullRequest === null ? {} : { pullRequest: step.pullRequest },
|
|
14859
|
+
hop: Object.freeze(step)
|
|
14860
|
+
};
|
|
14861
|
+
}
|
|
14862
|
+
async function readGate(roadmap, pullRequest) {
|
|
14863
|
+
try {
|
|
14864
|
+
return await roadmap.ownerApproval(pullRequest);
|
|
14865
|
+
} catch (error) {
|
|
14866
|
+
return {
|
|
14867
|
+
state: "unknown",
|
|
14868
|
+
reason: `could not read the owner gate of ${ref4(pullRequest)}: ${oneLine2(messageOf(error))}`,
|
|
14869
|
+
owners: []
|
|
14870
|
+
};
|
|
14871
|
+
}
|
|
14872
|
+
}
|
|
14873
|
+
async function waitingIssue(world, pullRequest) {
|
|
14874
|
+
let hop;
|
|
14875
|
+
try {
|
|
14876
|
+
hop = (await world.roadmap()).hop;
|
|
14877
|
+
} catch {
|
|
14878
|
+
return null;
|
|
14879
|
+
}
|
|
14880
|
+
const record = hop?.record ?? null;
|
|
14881
|
+
if (record?.target === null || record === null)
|
|
14882
|
+
return null;
|
|
14883
|
+
const named = record.pullRequest === pullRequest || hop?.target?.pullRequest === pullRequest;
|
|
14884
|
+
return named ? record.target : null;
|
|
14885
|
+
}
|
|
14886
|
+
async function readPrOwnerReview(world) {
|
|
14887
|
+
const { roadmap } = world.sources;
|
|
14888
|
+
if (roadmap === undefined)
|
|
14889
|
+
return null;
|
|
14890
|
+
const open = await world.openPull();
|
|
14891
|
+
if (open === null || open.mergeable !== "mergeable")
|
|
14892
|
+
return null;
|
|
14893
|
+
if (open.verdict !== "green" && open.verdict !== "none")
|
|
14894
|
+
return null;
|
|
14895
|
+
const pullRequest = open.summary.number;
|
|
14896
|
+
const gate = await readGate(roadmap, pullRequest);
|
|
14897
|
+
if (gate.state === "not-gated" || gate.state === "approved")
|
|
14898
|
+
return null;
|
|
14899
|
+
const issue3 = await waitingIssue(world, pullRequest);
|
|
14900
|
+
return {
|
|
14901
|
+
id: "pr-owner-review",
|
|
14902
|
+
action: "none",
|
|
14903
|
+
reading: `waiting on ${ref4(issue3 ?? pullRequest)} (owner review)`,
|
|
14904
|
+
proposal: `leave ${ref4(pullRequest)} to its owner's review, which rafa does not merge past: ${oneLine2(gate.reason)}`,
|
|
14905
|
+
pullRequest,
|
|
14906
|
+
...issue3 === null ? {} : { issue: issue3 }
|
|
14907
|
+
};
|
|
14908
|
+
}
|
|
14909
|
+
function dryEnded(view, record) {
|
|
14910
|
+
const { dryEpic, line } = view.reading;
|
|
14911
|
+
if (line !== null || dryEpic === undefined)
|
|
14912
|
+
return null;
|
|
14913
|
+
const step = { action: "home", home: record.home, closing: "halted", pullRequest: null };
|
|
14914
|
+
const reading2 = `epic ${ref4(dryEpic.number)}, where the dry hop went, has no line left, and a second hop would leave home behind`;
|
|
14915
|
+
return homeAnswer("away-ended", reading2, step, null);
|
|
14916
|
+
}
|
|
14917
|
+
async function notReadyEnded(world, record, target) {
|
|
14918
|
+
const picked = await world.picked();
|
|
14919
|
+
if (picked === null || picked.line.issue !== target || picked.ready || picked.blocked !== null)
|
|
14920
|
+
return null;
|
|
14921
|
+
const step = { action: "home", home: record.home, closing: "halted", pullRequest: null };
|
|
14922
|
+
const reading2 = `${ref4(target)}, the hop's target in epic ${ref4(record.targetEpic)}, carries no \`${SPEC_READY_LABEL}\`, so its spec is refused as not ready`;
|
|
14923
|
+
return homeAnswer("away-ended", reading2, step, target);
|
|
14924
|
+
}
|
|
14925
|
+
async function readAwayEnded(world) {
|
|
14926
|
+
const view = await hopView(world);
|
|
14927
|
+
const record = view === null ? null : awayRecord(view.hop);
|
|
14928
|
+
if (view === null || record === null)
|
|
14929
|
+
return null;
|
|
14930
|
+
if (record.kind === "dry")
|
|
14931
|
+
return dryEnded(view, record);
|
|
14932
|
+
const target = view.hop.target;
|
|
14933
|
+
if (target === null)
|
|
14934
|
+
return null;
|
|
14935
|
+
const where = `${ref4(target.issue)}, the hop's target in epic ${ref4(record.targetEpic)}`;
|
|
14936
|
+
if (target.closed) {
|
|
14937
|
+
const step = { action: "home", home: record.home, closing: "merged", pullRequest: null };
|
|
14938
|
+
return homeAnswer("away-ended", `${where}, is closed`, step, target.issue);
|
|
14939
|
+
}
|
|
14940
|
+
if (target.pullRequest !== null) {
|
|
14941
|
+
const step = { action: "home", home: record.home, closing: "waiting", pullRequest: target.pullRequest };
|
|
14942
|
+
return homeAnswer("away-ended", `${where}, has pull request ${ref4(target.pullRequest)} open`, step, target.issue);
|
|
14943
|
+
}
|
|
14944
|
+
return notReadyEnded(world, record, target.issue);
|
|
14945
|
+
}
|
|
14946
|
+
function decisionOf(view, kind) {
|
|
14947
|
+
const decision = view?.hop.decision ?? null;
|
|
14948
|
+
return decision?.kind === kind ? decision : null;
|
|
14949
|
+
}
|
|
14950
|
+
async function readHopHalt(world) {
|
|
14951
|
+
const view = await hopView(world);
|
|
14952
|
+
const halt2 = decisionOf(view, "halt");
|
|
14953
|
+
if (view === null || halt2 === null)
|
|
14954
|
+
return null;
|
|
14955
|
+
const home = view.hop.record?.home ?? view.hop.position?.home ?? halt2.from;
|
|
14956
|
+
const step = { action: "home", home, closing: "halted", pullRequest: null };
|
|
14957
|
+
return homeAnswer("hop-halt", hopDecisionSentence(halt2), step, halt2.chain.blocked);
|
|
14958
|
+
}
|
|
14959
|
+
async function readHopBlocked(world) {
|
|
14960
|
+
const view = await hopView(world);
|
|
14961
|
+
const move = decisionOf(view, "hop");
|
|
14962
|
+
if (view === null || move === null || awayRecord(view.hop) !== null)
|
|
14963
|
+
return null;
|
|
14964
|
+
const opening = {
|
|
14965
|
+
kind: "blocker",
|
|
14966
|
+
home: homeOf(view),
|
|
14967
|
+
from: move.from,
|
|
14968
|
+
blocked: move.blocked,
|
|
14969
|
+
target: move.blocker,
|
|
14970
|
+
targetEpic: move.epic,
|
|
14971
|
+
targetBoard: move.board
|
|
14972
|
+
};
|
|
14973
|
+
return {
|
|
14974
|
+
id: "hop-blocked",
|
|
14975
|
+
action: "hop",
|
|
14976
|
+
reading: hopDecisionSentence(move),
|
|
14977
|
+
proposal: `hop to ${placeText2(move.to)} and work ${ref4(move.blocker)}, keeping home`,
|
|
14978
|
+
issue: move.blocker,
|
|
14979
|
+
hop: Object.freeze({ action: "hop", opening: Object.freeze(opening) })
|
|
14980
|
+
};
|
|
14981
|
+
}
|
|
14982
|
+
function dryPlaces(view, dry) {
|
|
14983
|
+
const from = { board: view.reading.roadmap, epic: dry.number };
|
|
14984
|
+
const { position } = view.hop;
|
|
14985
|
+
return {
|
|
14986
|
+
from,
|
|
14987
|
+
home: position === null || samePlace(position.current, position.home) ? from : position.home
|
|
14988
|
+
};
|
|
14989
|
+
}
|
|
14990
|
+
async function readHopDry(world) {
|
|
14991
|
+
const view = await hopView(world);
|
|
14992
|
+
if (view === null || awayRecord(view.hop) !== null)
|
|
14993
|
+
return null;
|
|
14994
|
+
const { dryEpic, line } = view.reading;
|
|
14995
|
+
const next = view.hop.nextEpic;
|
|
14996
|
+
if (line !== null || dryEpic === undefined || next === null)
|
|
14997
|
+
return null;
|
|
14998
|
+
const { from, home } = dryPlaces(view, dryEpic);
|
|
14999
|
+
const to = { board: next.board, epic: next.number };
|
|
15000
|
+
if (!oneHopAllows(from, to, home))
|
|
15001
|
+
return null;
|
|
15002
|
+
const opening = {
|
|
15003
|
+
kind: "dry",
|
|
15004
|
+
home: homeOf(view),
|
|
15005
|
+
from,
|
|
15006
|
+
blocked: null,
|
|
15007
|
+
target: null,
|
|
15008
|
+
targetEpic: next.number,
|
|
15009
|
+
targetBoard: next.board
|
|
15010
|
+
};
|
|
15011
|
+
return {
|
|
15012
|
+
id: "hop-dry",
|
|
15013
|
+
action: "hop",
|
|
15014
|
+
reading: dryHopSentence(dryEpic, next),
|
|
15015
|
+
proposal: `hop to ${placeText2(to)}, keeping home`,
|
|
15016
|
+
hop: Object.freeze({ action: "hop", opening: Object.freeze(opening) })
|
|
15017
|
+
};
|
|
15018
|
+
}
|
|
15019
|
+
function loopEnding(record, target) {
|
|
15020
|
+
const home = (closing, pullRequest) => ({ action: "home", home: record.home, closing, pullRequest });
|
|
15021
|
+
const epic = ref4(record.targetEpic);
|
|
15022
|
+
if (record.kind === "dry" || record.target === null) {
|
|
15023
|
+
return { reading: `the loop has run in epic ${epic}, where the dry hop went`, step: home("halted", null) };
|
|
15024
|
+
}
|
|
15025
|
+
const where = `${ref4(record.target)}, the hop's target in epic ${epic},`;
|
|
15026
|
+
if (target?.closed === true)
|
|
15027
|
+
return { reading: `${where} is closed`, step: home("merged", null) };
|
|
15028
|
+
if (target !== null && target.pullRequest !== null) {
|
|
15029
|
+
return { reading: `${where} has pull request ${ref4(target.pullRequest)} open`, step: home("waiting", target.pullRequest) };
|
|
15030
|
+
}
|
|
15031
|
+
return { reading: `${where} has no pull request open after the loop`, step: home("halted", null) };
|
|
15032
|
+
}
|
|
15033
|
+
function homeAfterLoop(reading2) {
|
|
15034
|
+
const record = reading2.hop === undefined ? null : awayRecord(reading2.hop);
|
|
15035
|
+
if (record === null)
|
|
15036
|
+
return null;
|
|
15037
|
+
const ending = loopEnding(record, reading2.hop?.target ?? null);
|
|
15038
|
+
return homeAnswer("away-ended", ending.reading, ending.step, record.target);
|
|
15039
|
+
}
|
|
15040
|
+
|
|
13934
15041
|
// src/next/state.ts
|
|
13935
15042
|
var PREFIX15 = "rafa next";
|
|
13936
15043
|
var MAX_NAMED_FILES = 5;
|
|
@@ -13943,7 +15050,7 @@ function nameFiles(paths) {
|
|
|
13943
15050
|
const hidden = paths.length - shown.length;
|
|
13944
15051
|
return hidden > 0 ? `${shown.join(", ")} and ${hidden} more` : shown.join(", ");
|
|
13945
15052
|
}
|
|
13946
|
-
function
|
|
15053
|
+
function oneLine3(text) {
|
|
13947
15054
|
return text.replace(/\s+/g, " ").trim();
|
|
13948
15055
|
}
|
|
13949
15056
|
function readTreeModified(world) {
|
|
@@ -13965,7 +15072,7 @@ async function readPullsUnusable(world) {
|
|
|
13965
15072
|
return {
|
|
13966
15073
|
id: "pulls-unusable",
|
|
13967
15074
|
action: "none",
|
|
13968
|
-
reading: `the \`${world.sources.pulls.kind}\` pull request provider could not be asked: ${
|
|
15075
|
+
reading: `the \`${world.sources.pulls.kind}\` pull request provider could not be asked: ${oneLine3(messageOf(error))}`,
|
|
13969
15076
|
proposal: "run `rafa doctor` to see what the provider needs, then read the state again"
|
|
13970
15077
|
};
|
|
13971
15078
|
}
|
|
@@ -14182,6 +15289,14 @@ var ROWS = Object.freeze([
|
|
|
14182
15289
|
{ id: "nothing-left", read: readNothingLeft }
|
|
14183
15290
|
]);
|
|
14184
15291
|
var NEXT_STATES = Object.freeze(ROWS.map((row) => row.id));
|
|
15292
|
+
var HOP_ROWS_AHEAD_OF = Object.freeze({
|
|
15293
|
+
"pr-no-checks": [{ id: "pr-owner-review", read: readPrOwnerReview, pulls: true }],
|
|
15294
|
+
"plan-unstarted": [{ id: "away-ended", read: readAwayEnded }],
|
|
15295
|
+
"issue-blocked": [{ id: "hop-halt", read: readHopHalt }, { id: "hop-blocked", read: readHopBlocked }],
|
|
15296
|
+
"nothing-left": [{ id: "hop-dry", read: readHopDry }]
|
|
15297
|
+
});
|
|
15298
|
+
var ROADMAP_ROWS = Object.freeze(ROWS.flatMap((row) => [...HOP_ROWS_AHEAD_OF[row.id] ?? [], row]));
|
|
15299
|
+
var NEXT_ROADMAP_STATES = Object.freeze(ROADMAP_ROWS.map((row) => row.id));
|
|
14185
15300
|
function answer2(row, problems) {
|
|
14186
15301
|
return Object.freeze({
|
|
14187
15302
|
id: row.id,
|
|
@@ -14192,15 +15307,17 @@ function answer2(row, problems) {
|
|
|
14192
15307
|
issue: row.issue ?? null,
|
|
14193
15308
|
planStub: row.planStub ?? null,
|
|
14194
15309
|
planPath: row.planPath ?? null,
|
|
14195
|
-
problems: Object.freeze([...problems])
|
|
15310
|
+
problems: Object.freeze([...problems]),
|
|
15311
|
+
...row.hop === undefined ? {} : { hop: row.hop }
|
|
14196
15312
|
});
|
|
14197
15313
|
}
|
|
14198
15314
|
async function readNextState(sources) {
|
|
14199
15315
|
const world = openWorld(sources);
|
|
15316
|
+
const rows = sources.roadmap === undefined ? ROWS : ROADMAP_ROWS;
|
|
14200
15317
|
const modified = readTreeModified(world);
|
|
14201
15318
|
if (modified !== null)
|
|
14202
15319
|
return answer2(modified, world.problems());
|
|
14203
|
-
for (const row of
|
|
15320
|
+
for (const row of rows) {
|
|
14204
15321
|
if (row.pulls === true) {
|
|
14205
15322
|
const unusable = await readPullsUnusable(world);
|
|
14206
15323
|
if (unusable !== null)
|
|
@@ -14212,6 +15329,13 @@ async function readNextState(sources) {
|
|
|
14212
15329
|
}
|
|
14213
15330
|
throw new Error(`${PREFIX15}: no row of the table answered, and the last row answers for every reading`);
|
|
14214
15331
|
}
|
|
15332
|
+
async function readHomeAfterLoop(sources) {
|
|
15333
|
+
if (sources.roadmap === undefined)
|
|
15334
|
+
return null;
|
|
15335
|
+
const reading2 = await sources.board.next();
|
|
15336
|
+
const found = homeAfterLoop(reading2);
|
|
15337
|
+
return found === null ? null : answer2(found, reading2.problems);
|
|
15338
|
+
}
|
|
14215
15339
|
|
|
14216
15340
|
// src/next/hint.ts
|
|
14217
15341
|
var HINT_FLAG = "hint";
|
|
@@ -14290,8 +15414,108 @@ async function nextStepHint(context, seams = DEFAULT_HINT_SEAMS) {
|
|
|
14290
15414
|
}
|
|
14291
15415
|
}
|
|
14292
15416
|
|
|
15417
|
+
// src/next/hop-action.ts
|
|
15418
|
+
import { rmSync as rmSync2 } from "fs";
|
|
15419
|
+
var NO_REHOME = "--no-rehome";
|
|
15420
|
+
function hopLogLine(opening) {
|
|
15421
|
+
const { from, blocked: blocked3, target, targetEpic, targetBoard } = opening;
|
|
15422
|
+
if (opening.kind === "dry" || blocked3 === null || target === null) {
|
|
15423
|
+
return dryHopSentence({ number: from.epic ?? from.board }, { number: targetEpic, board: targetBoard });
|
|
15424
|
+
}
|
|
15425
|
+
return hopMoveSentence({ from, blocked: blocked3, blocker: target, epic: targetEpic });
|
|
15426
|
+
}
|
|
15427
|
+
function homeLogLine(home) {
|
|
15428
|
+
return `back home: ${placeText2(home)}`;
|
|
15429
|
+
}
|
|
15430
|
+
function openedRecord(opening, now) {
|
|
15431
|
+
return {
|
|
15432
|
+
...opening,
|
|
15433
|
+
state: "away",
|
|
15434
|
+
pullRequest: null,
|
|
15435
|
+
startedAt: now.toISOString()
|
|
15436
|
+
};
|
|
15437
|
+
}
|
|
15438
|
+
function writeRecord(root, record) {
|
|
15439
|
+
try {
|
|
15440
|
+
writeHopRecord(root, record);
|
|
15441
|
+
} catch (error) {
|
|
15442
|
+
throw new CommandExit(1, `\u274C Could not write ${hopFilePath(root)}: ${messageOf(error)}`);
|
|
15443
|
+
}
|
|
15444
|
+
}
|
|
15445
|
+
function restoreRecord(root, before) {
|
|
15446
|
+
if (before.set)
|
|
15447
|
+
writeHopRecord(root, before.record);
|
|
15448
|
+
else
|
|
15449
|
+
rmSync2(hopFilePath(root), { force: true });
|
|
15450
|
+
}
|
|
15451
|
+
async function runHop(world, step) {
|
|
15452
|
+
const { opening } = step;
|
|
15453
|
+
const before = readHopRecord(world.root);
|
|
15454
|
+
const record = openedRecord(opening, world.now());
|
|
15455
|
+
writeRecord(world.root, record);
|
|
15456
|
+
try {
|
|
15457
|
+
await world.switchTo([String(opening.targetEpic), NO_REHOME]);
|
|
15458
|
+
} catch (error) {
|
|
15459
|
+
restoreRecord(world.root, before);
|
|
15460
|
+
throw error;
|
|
15461
|
+
}
|
|
15462
|
+
world.info(hopLogLine(opening));
|
|
15463
|
+
return record;
|
|
15464
|
+
}
|
|
15465
|
+
function writePosition(root, position) {
|
|
15466
|
+
try {
|
|
15467
|
+
writePositionFile(root, position);
|
|
15468
|
+
} catch (error) {
|
|
15469
|
+
throw new CommandExit(1, `\u274C Could not write ${positionFilePath(root)}: ${messageOf(error)}`);
|
|
15470
|
+
}
|
|
15471
|
+
}
|
|
15472
|
+
function movedHome(world, step) {
|
|
15473
|
+
const reading2 = readPositionFile(world.root);
|
|
15474
|
+
if (reading2.set) {
|
|
15475
|
+
if (samePlace(reading2.position.current, reading2.position.home))
|
|
15476
|
+
return { now: reading2.position, written: false };
|
|
15477
|
+
return { now: goHome(reading2.position), written: true };
|
|
15478
|
+
}
|
|
15479
|
+
if (reading2.reason === "absent")
|
|
15480
|
+
return { now: null, written: false };
|
|
15481
|
+
world.warn(`${reading2.detail}, so it is written afresh at home`);
|
|
15482
|
+
return { now: positionAt(step.home), written: true };
|
|
15483
|
+
}
|
|
15484
|
+
function awayToClose(world, position) {
|
|
15485
|
+
const reading2 = readHopRecord(world.root);
|
|
15486
|
+
if (!reading2.set) {
|
|
15487
|
+
if (reading2.reason !== "absent")
|
|
15488
|
+
world.warn(`${reading2.detail}, so no hop record is closed`);
|
|
15489
|
+
return null;
|
|
15490
|
+
}
|
|
15491
|
+
const { record } = reading2;
|
|
15492
|
+
if (record.state !== "away" || position === null)
|
|
15493
|
+
return null;
|
|
15494
|
+
return staleAgainst(record, position) ? null : record;
|
|
15495
|
+
}
|
|
15496
|
+
function runHome(world, step) {
|
|
15497
|
+
const moved = movedHome(world, step);
|
|
15498
|
+
if (moved.written && moved.now !== null)
|
|
15499
|
+
writePosition(world.root, moved.now);
|
|
15500
|
+
const away = awayToClose(world, moved.now);
|
|
15501
|
+
const closed = away === null ? null : { ...away, state: step.closing, pullRequest: step.pullRequest ?? away.pullRequest };
|
|
15502
|
+
if (closed !== null)
|
|
15503
|
+
writeRecord(world.root, closed);
|
|
15504
|
+
const line = homeLogLine(moved.now?.home ?? step.home);
|
|
15505
|
+
world.info(line);
|
|
15506
|
+
return Object.freeze({
|
|
15507
|
+
position: moved.written ? moved.now : null,
|
|
15508
|
+
record: closed,
|
|
15509
|
+
line
|
|
15510
|
+
});
|
|
15511
|
+
}
|
|
15512
|
+
|
|
14293
15513
|
// src/next/actions.ts
|
|
14294
15514
|
var PREFIX16 = "rafa next";
|
|
15515
|
+
var NEXT_IN_PROCESS_ACTIONS = Object.freeze(["sync", "hop", "home"]);
|
|
15516
|
+
var SWITCH_COMMAND = Object.freeze({ subject: "switch", action: "switch" });
|
|
15517
|
+
var ROADMAP_WORD = "--roadmap";
|
|
15518
|
+
var ROADMAP_PASSED_ACTIONS = new Set(["plan", "start", "resume"]);
|
|
14295
15519
|
function needed(state, value, what) {
|
|
14296
15520
|
if (value === null) {
|
|
14297
15521
|
throw new Error(`${PREFIX16}: state "${state.id}" proposes "${state.action}" and names no ${what}`);
|
|
@@ -14322,14 +15546,15 @@ var NEXT_COMMAND_ACTIONS = Object.freeze(Object.keys(ACTION_COMMANDS));
|
|
|
14322
15546
|
function runsCommand(action) {
|
|
14323
15547
|
return NEXT_COMMAND_ACTIONS.includes(action);
|
|
14324
15548
|
}
|
|
14325
|
-
function actionInvocation(state) {
|
|
15549
|
+
function actionInvocation(state, options = {}) {
|
|
14326
15550
|
if (!runsCommand(state.action))
|
|
14327
15551
|
return null;
|
|
14328
15552
|
const spec = ACTION_COMMANDS[state.action];
|
|
15553
|
+
const passes = options.roadmap === true && ROADMAP_PASSED_ACTIONS.has(state.action);
|
|
14329
15554
|
return Object.freeze({
|
|
14330
15555
|
action: state.action,
|
|
14331
15556
|
command: commandSpelling(spec),
|
|
14332
|
-
argv: Object.freeze(spec.argv(state))
|
|
15557
|
+
argv: Object.freeze(passes ? [...spec.argv(state), ROADMAP_WORD] : spec.argv(state))
|
|
14333
15558
|
});
|
|
14334
15559
|
}
|
|
14335
15560
|
function actionContext(caller, command, argv) {
|
|
@@ -14341,17 +15566,52 @@ function actionContext(caller, command, argv) {
|
|
|
14341
15566
|
argv: Object.freeze([...argv])
|
|
14342
15567
|
});
|
|
14343
15568
|
}
|
|
14344
|
-
async function
|
|
14345
|
-
const
|
|
15569
|
+
async function runRegistered(caller, name, argv, step) {
|
|
15570
|
+
const command = caller.registry.find(name.subject, name.action);
|
|
15571
|
+
if (command === undefined) {
|
|
15572
|
+
throw new CommandExit(1, `\u274C ${PREFIX16}: the "${step}" step runs "rafa ${commandSpelling(name)}", which is registered by nothing`);
|
|
15573
|
+
}
|
|
15574
|
+
await command.run(actionContext(caller, command, argv));
|
|
15575
|
+
}
|
|
15576
|
+
function hopWorld(caller, state) {
|
|
15577
|
+
if (caller.project === null) {
|
|
15578
|
+
throw new Error(`${PREFIX16}: the action "${state.action}" of state "${state.id}" runs inside a project, and was handed none`);
|
|
15579
|
+
}
|
|
15580
|
+
return {
|
|
15581
|
+
root: caller.project.root,
|
|
15582
|
+
switchTo: (argv) => runRegistered(caller, SWITCH_COMMAND, argv, state.action),
|
|
15583
|
+
info: (line) => {
|
|
15584
|
+
caller.output.info(line);
|
|
15585
|
+
},
|
|
15586
|
+
warn: (line) => {
|
|
15587
|
+
caller.output.warn(line);
|
|
15588
|
+
},
|
|
15589
|
+
now: () => new Date
|
|
15590
|
+
};
|
|
15591
|
+
}
|
|
15592
|
+
function noStep(state) {
|
|
15593
|
+
return new Error(`${PREFIX16}: state "${state.id}" proposes "${state.action}" and carries no ${state.action} step`);
|
|
15594
|
+
}
|
|
15595
|
+
async function runHopStep(caller, state) {
|
|
15596
|
+
const step = state.hop;
|
|
15597
|
+
if (step?.action !== state.action)
|
|
15598
|
+
throw noStep(state);
|
|
15599
|
+
if (step.action === "hop") {
|
|
15600
|
+
await runHop(hopWorld(caller, state), step);
|
|
15601
|
+
return;
|
|
15602
|
+
}
|
|
15603
|
+
runHome(hopWorld(caller, state), step);
|
|
15604
|
+
}
|
|
15605
|
+
async function runAction(caller, state, options = {}) {
|
|
15606
|
+
if (state.action === "hop" || state.action === "home") {
|
|
15607
|
+
await runHopStep(caller, state);
|
|
15608
|
+
return;
|
|
15609
|
+
}
|
|
15610
|
+
const invocation = actionInvocation(state, options);
|
|
14346
15611
|
if (invocation === null) {
|
|
14347
15612
|
throw new Error(`${PREFIX16}: the action "${state.action}" of state "${state.id}" runs no registered command`);
|
|
14348
15613
|
}
|
|
14349
|
-
|
|
14350
|
-
const command = caller.registry.find(spec.subject, spec.action);
|
|
14351
|
-
if (command === undefined) {
|
|
14352
|
-
throw new CommandExit(1, `\u274C ${PREFIX16}: the "${invocation.action}" step runs "rafa ${invocation.command}", which is registered by nothing`);
|
|
14353
|
-
}
|
|
14354
|
-
await command.run(actionContext(caller, command, invocation.argv));
|
|
15614
|
+
await runRegistered(caller, ACTION_COMMANDS[invocation.action], invocation.argv, invocation.action);
|
|
14355
15615
|
}
|
|
14356
15616
|
|
|
14357
15617
|
// src/next/ending.ts
|
|
@@ -14587,8 +15847,8 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
14587
15847
|
import { homedir as homedir3 } from "os";
|
|
14588
15848
|
|
|
14589
15849
|
// src/notices/notices.ts
|
|
14590
|
-
import { mkdirSync as
|
|
14591
|
-
import { dirname as
|
|
15850
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "fs";
|
|
15851
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
14592
15852
|
var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
|
|
14593
15853
|
var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
|
|
14594
15854
|
var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
|
|
@@ -14620,8 +15880,8 @@ function readDismissed(home) {
|
|
|
14620
15880
|
function writeDismissed(home, ids) {
|
|
14621
15881
|
const all = NOTICE_IDS.filter((id2) => ids.includes(id2) || readDismissed(home).includes(id2));
|
|
14622
15882
|
const path3 = noticesPath(home);
|
|
14623
|
-
|
|
14624
|
-
|
|
15883
|
+
mkdirSync8(dirname9(path3), { recursive: true });
|
|
15884
|
+
writeFileSync10(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
|
|
14625
15885
|
`);
|
|
14626
15886
|
}
|
|
14627
15887
|
function pendingNotices(dismissed) {
|
|
@@ -14698,8 +15958,8 @@ async function requireNoticesAnswered() {
|
|
|
14698
15958
|
// src/start/branch.ts
|
|
14699
15959
|
var INDENT4 = " ";
|
|
14700
15960
|
var DEFAULT_BRANCH_SEAMS = Object.freeze({});
|
|
14701
|
-
function refExists(git,
|
|
14702
|
-
return git(["show-ref", "--verify", "--quiet",
|
|
15961
|
+
function refExists(git, ref5) {
|
|
15962
|
+
return git(["show-ref", "--verify", "--quiet", ref5]).ok;
|
|
14703
15963
|
}
|
|
14704
15964
|
function terminalReading(seams) {
|
|
14705
15965
|
return seams.isTerminal ?? (() => process.stdin.isTTY === true);
|
|
@@ -14802,7 +16062,7 @@ function markBudgetExit(options) {
|
|
|
14802
16062
|
}
|
|
14803
16063
|
|
|
14804
16064
|
// src/utils/commit.ts
|
|
14805
|
-
import { spawnSync as
|
|
16065
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
14806
16066
|
var DEFAULT_COMMIT_TYPE = "chore";
|
|
14807
16067
|
var TEST_NOUN_WINDOW = 40;
|
|
14808
16068
|
var FIX_VERBS = ["fix", "repair", "correct", "resolve"];
|
|
@@ -15014,7 +16274,7 @@ function failed2(step, subject, result) {
|
|
|
15014
16274
|
}
|
|
15015
16275
|
function spawnGit(cwd) {
|
|
15016
16276
|
return (args) => {
|
|
15017
|
-
const result =
|
|
16277
|
+
const result = spawnSync2("git", args, {
|
|
15018
16278
|
cwd,
|
|
15019
16279
|
encoding: "utf8",
|
|
15020
16280
|
maxBuffer: MAX_GIT_BUFFER
|
|
@@ -15079,24 +16339,24 @@ function indentBlock(text) {
|
|
|
15079
16339
|
}
|
|
15080
16340
|
function holdsOf(reading2) {
|
|
15081
16341
|
const { report: report2, issues } = reading2;
|
|
15082
|
-
const
|
|
16342
|
+
const holds2 = [];
|
|
15083
16343
|
if (report2.status === "blocked")
|
|
15084
|
-
|
|
16344
|
+
holds2.push("status: blocked");
|
|
15085
16345
|
for (const blocker of report2.blockers) {
|
|
15086
|
-
|
|
16346
|
+
holds2.push(`blocker: ${blocker.what ?? "an entry with no usable what"}`);
|
|
15087
16347
|
}
|
|
15088
16348
|
for (const issue3 of issues) {
|
|
15089
16349
|
if (!DROPPED_BLOCKER_FIELD.test(issue3.field))
|
|
15090
16350
|
continue;
|
|
15091
|
-
|
|
16351
|
+
holds2.push(`blocker: ${issue3.field}, an entry the report parser dropped`);
|
|
15092
16352
|
}
|
|
15093
|
-
return
|
|
16353
|
+
return holds2;
|
|
15094
16354
|
}
|
|
15095
16355
|
function leftNothingBehind(attempt2, reported) {
|
|
15096
16356
|
return !reported && attempt2.outcome === "nothing-to-commit";
|
|
15097
16357
|
}
|
|
15098
|
-
function holdsAfterCommit(
|
|
15099
|
-
return silent ? [...
|
|
16358
|
+
function holdsAfterCommit(holds2, silent) {
|
|
16359
|
+
return silent ? [...holds2, NO_REPORT_HOLD, NO_COMMIT_HOLD] : holds2;
|
|
15100
16360
|
}
|
|
15101
16361
|
function commitFinishedTask(options) {
|
|
15102
16362
|
const { taskInfo, trackerPath } = options;
|
|
@@ -15117,8 +16377,8 @@ function commitFinishedTask(options) {
|
|
|
15117
16377
|
return attempt2;
|
|
15118
16378
|
}
|
|
15119
16379
|
const silent = leftNothingBehind(attempt2, reported);
|
|
15120
|
-
const
|
|
15121
|
-
const held =
|
|
16380
|
+
const holds2 = holdsAfterCommit(reportHolds, silent);
|
|
16381
|
+
const held = holds2.length > 0;
|
|
15122
16382
|
const marked = silent && writeTrackerBlocker(trackerPath, taskInfo.lineNum, NOTHING_REPORTED_OR_COMMITTED);
|
|
15123
16383
|
if (!marked) {
|
|
15124
16384
|
updateTrackerLine(trackerPath, taskInfo.lineNum, held ? "blocked" : "done");
|
|
@@ -15127,7 +16387,7 @@ function commitFinishedTask(options) {
|
|
|
15127
16387
|
out.error(silent ? `
|
|
15128
16388
|
\u26D4 Task held: its session left neither a report nor a commit: ${taskText}` : `
|
|
15129
16389
|
\u26D4 Task blocked by its own report: ${taskText}`);
|
|
15130
|
-
for (const hold of
|
|
16390
|
+
for (const hold of holds2)
|
|
15131
16391
|
out.error(indentBlock(hold));
|
|
15132
16392
|
} else {
|
|
15133
16393
|
out.info(`\u2705 Task done: ${taskText}`);
|
|
@@ -15149,17 +16409,17 @@ function finishCleanExit(options) {
|
|
|
15149
16409
|
const reported = reading2.present;
|
|
15150
16410
|
const reportHolds = reported ? holdsOf(reading2) : [];
|
|
15151
16411
|
const attempt2 = commitFinishedTask({ ...task, holds: reportHolds, reported });
|
|
15152
|
-
const
|
|
15153
|
-
const
|
|
16412
|
+
const holds2 = holdsAfterCommit(reportHolds, leftNothingBehind(attempt2, reported));
|
|
16413
|
+
const blocked3 = attempt2.outcome === "failed" || holds2.length > 0;
|
|
15154
16414
|
return {
|
|
15155
16415
|
attempt: attempt2,
|
|
15156
|
-
holds,
|
|
15157
|
-
outcome:
|
|
16416
|
+
holds: holds2,
|
|
16417
|
+
outcome: blocked3 ? "blocked" : "done"
|
|
15158
16418
|
};
|
|
15159
16419
|
}
|
|
15160
16420
|
|
|
15161
16421
|
// src/start/dispatch.ts
|
|
15162
|
-
import { randomUUID as
|
|
16422
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
15163
16423
|
|
|
15164
16424
|
// src/report/lessons.ts
|
|
15165
16425
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -15567,7 +16827,7 @@ function reportLessons(record, context, now) {
|
|
|
15567
16827
|
}
|
|
15568
16828
|
|
|
15569
16829
|
// src/effort/store/absences.ts
|
|
15570
|
-
import { randomUUID as
|
|
16830
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
15571
16831
|
import { existsSync as existsSync20 } from "fs";
|
|
15572
16832
|
var ABSENCE_REASONS = {
|
|
15573
16833
|
"no-block": true,
|
|
@@ -15579,9 +16839,10 @@ var INSERT_ABSENCE = `
|
|
|
15579
16839
|
INSERT INTO report_absences (
|
|
15580
16840
|
id, session_id, plan_stub, task_line,
|
|
15581
16841
|
reason, detail, block_body,
|
|
15582
|
-
outcome, collected_at
|
|
16842
|
+
outcome, collected_at,
|
|
16843
|
+
${STAMPED_COLUMNS}
|
|
15583
16844
|
)
|
|
15584
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
16845
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("report_absences")})
|
|
15585
16846
|
ON CONFLICT (session_id) DO NOTHING
|
|
15586
16847
|
`;
|
|
15587
16848
|
function refusedWrite7(reason) {
|
|
@@ -15615,7 +16876,7 @@ function writeReportAbsence(repoRoot, write, seams = {}) {
|
|
|
15615
16876
|
const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
|
|
15616
16877
|
const { reason, text, block } = write.absence;
|
|
15617
16878
|
const collectedAt = (seams.now ?? (() => new Date))().toISOString();
|
|
15618
|
-
const id2 = (seams.newId ??
|
|
16879
|
+
const id2 = (seams.newId ?? randomUUID4)();
|
|
15619
16880
|
const values = [
|
|
15620
16881
|
id2,
|
|
15621
16882
|
sessionId,
|
|
@@ -15632,7 +16893,7 @@ function writeReportAbsence(repoRoot, write, seams = {}) {
|
|
|
15632
16893
|
}
|
|
15633
16894
|
|
|
15634
16895
|
// src/effort/store/triage.ts
|
|
15635
|
-
import { randomUUID as
|
|
16896
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
15636
16897
|
|
|
15637
16898
|
// src/triage/machine-fault.ts
|
|
15638
16899
|
var BUILD_FAILURE = /\b(?:abort(?:ed|s)?|broke|broken|crash(?:ed|es)?|error|errors|fail(?:ed|ing|s|ure|ures)?|unusable)\b/iu;
|
|
@@ -15714,9 +16975,10 @@ var BLOCKERS = {
|
|
|
15714
16975
|
INSERT INTO blockers (
|
|
15715
16976
|
id, session_id, plan_stub, task_line,
|
|
15716
16977
|
what, artifact,
|
|
15717
|
-
outcome, collected_at
|
|
16978
|
+
outcome, collected_at,
|
|
16979
|
+
${STAMPED_COLUMNS}
|
|
15718
16980
|
)
|
|
15719
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
16981
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("blockers")})
|
|
15720
16982
|
ON CONFLICT (session_id, what, ifnull(artifact, '')) DO NOTHING
|
|
15721
16983
|
`,
|
|
15722
16984
|
values: ({ what, artifact }) => [what, artifact]
|
|
@@ -15731,9 +16993,10 @@ var OUT_OF_SCOPE_BUGS = {
|
|
|
15731
16993
|
INSERT INTO out_of_scope_bugs (
|
|
15732
16994
|
id, session_id, plan_stub, task_line,
|
|
15733
16995
|
what, artifact, security, scope,
|
|
15734
|
-
outcome, collected_at
|
|
16996
|
+
outcome, collected_at,
|
|
16997
|
+
${STAMPED_COLUMNS}
|
|
15735
16998
|
)
|
|
15736
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
16999
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("out_of_scope_bugs")})
|
|
15737
17000
|
ON CONFLICT (session_id, what, ifnull(artifact, ''), ifnull(security, -1)) DO NOTHING
|
|
15738
17001
|
`,
|
|
15739
17002
|
values: (entry) => [
|
|
@@ -15797,7 +17060,7 @@ function writeTriage(repoRoot, write, seams = {}) {
|
|
|
15797
17060
|
const blockers = checkList(BLOCKERS, write.blockers);
|
|
15798
17061
|
const bugs = checkList(OUT_OF_SCOPE_BUGS, write.outOfScopeBugs);
|
|
15799
17062
|
const collectedAt = (seams.now ?? (() => new Date))().toISOString();
|
|
15800
|
-
const newId = seams.newId ??
|
|
17063
|
+
const newId = seams.newId ?? randomUUID5;
|
|
15801
17064
|
const blockerRows = rowsOf2(BLOCKERS, blockers.stored, write, newId, collectedAt);
|
|
15802
17065
|
const bugRows = rowsOf2(OUT_OF_SCOPE_BUGS, bugs.stored, write, newId, collectedAt);
|
|
15803
17066
|
const [blockersAdded, bugsAdded] = writeSqliteStore(path3, blockerRows.length + bugRows.length, [0, 0], (db) => insertBoth(db, blockerRows, bugRows));
|
|
@@ -15854,12 +17117,12 @@ function describeTaskReportRecord(record) {
|
|
|
15854
17117
|
var SKILLS_HEADING = "## Skills for this task";
|
|
15855
17118
|
var SKILLS_INSTRUCTION = "Invoke each with the Skill tool before you change anything it covers.";
|
|
15856
17119
|
var LESSONS_HEADING = "## Lessons from earlier tasks";
|
|
15857
|
-
function
|
|
17120
|
+
function oneLine4(text) {
|
|
15858
17121
|
return text.replace(/\s+/g, " ").trim();
|
|
15859
17122
|
}
|
|
15860
17123
|
function skillLine(skill, delivery = SKILL_DELIVERY) {
|
|
15861
17124
|
const name = `\`${sessionSkillName(skill.name, delivery)}\``;
|
|
15862
|
-
const description =
|
|
17125
|
+
const description = oneLine4(skill.description);
|
|
15863
17126
|
return description === "" ? `- ${name}` : `- ${name}: ${description}`;
|
|
15864
17127
|
}
|
|
15865
17128
|
function renderSkillsSection(skills, delivery = SKILL_DELIVERY) {
|
|
@@ -15869,7 +17132,7 @@ function renderSkillsSection(skills, delivery = SKILL_DELIVERY) {
|
|
|
15869
17132
|
`);
|
|
15870
17133
|
}
|
|
15871
17134
|
function triggerClause(trigger) {
|
|
15872
|
-
return
|
|
17135
|
+
return oneLine4(trigger).replace(/^when\b\s*/i, "");
|
|
15873
17136
|
}
|
|
15874
17137
|
function lessonTaskCount(lesson) {
|
|
15875
17138
|
return lesson.sources === undefined ? lesson.usage_count : lesson.sources.length;
|
|
@@ -15878,7 +17141,7 @@ function lessonLine(lesson) {
|
|
|
15878
17141
|
const count = lessonTaskCount(lesson);
|
|
15879
17142
|
const tasks = count === 1 ? "1 task" : `${count} tasks`;
|
|
15880
17143
|
const evidence = `confidence ${lesson.confidence.toFixed(2)}, from ${tasks}, lesson ${lesson.id}`;
|
|
15881
|
-
return `- When ${triggerClause(lesson.trigger)}: ${
|
|
17144
|
+
return `- When ${triggerClause(lesson.trigger)}: ${oneLine4(lesson.action)} (${evidence})`;
|
|
15882
17145
|
}
|
|
15883
17146
|
function renderLessonsSection(lessons) {
|
|
15884
17147
|
if (lessons.length === 0)
|
|
@@ -15888,7 +17151,7 @@ function renderLessonsSection(lessons) {
|
|
|
15888
17151
|
}
|
|
15889
17152
|
|
|
15890
17153
|
// src/utils/progress.ts
|
|
15891
|
-
import { existsSync as existsSync21, writeFileSync as
|
|
17154
|
+
import { existsSync as existsSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
15892
17155
|
import { join as join18 } from "path";
|
|
15893
17156
|
var PROGRESS_FILE_NAME = "progress.txt";
|
|
15894
17157
|
var PROGRESS_CAP_BYTES = 16000;
|
|
@@ -16013,7 +17276,7 @@ function readProgressFindings(repoRoot, planStub2) {
|
|
|
16013
17276
|
function writeProgress(repoRoot, planStub2) {
|
|
16014
17277
|
const render = renderProgressText(readProgressFindings(repoRoot, planStub2));
|
|
16015
17278
|
const path3 = progressFilePath(repoRoot);
|
|
16016
|
-
|
|
17279
|
+
writeFileSync11(path3, render.text, "utf8");
|
|
16017
17280
|
return { ...render, path: path3, storePath: sqliteStorePath(repoRoot) };
|
|
16018
17281
|
}
|
|
16019
17282
|
|
|
@@ -16166,7 +17429,7 @@ var SEPARATOR = " \u2014 ";
|
|
|
16166
17429
|
function moreLine(dropped) {
|
|
16167
17430
|
return `${dropped} more: rafa skill list`;
|
|
16168
17431
|
}
|
|
16169
|
-
function
|
|
17432
|
+
function oneLine5(text) {
|
|
16170
17433
|
return text.replace(/\s+/g, " ").trim();
|
|
16171
17434
|
}
|
|
16172
17435
|
function cutLine(line) {
|
|
@@ -16183,9 +17446,9 @@ function isDisabled(row) {
|
|
|
16183
17446
|
}
|
|
16184
17447
|
function skillIndexLine(item) {
|
|
16185
17448
|
const fields = fieldsOf2(item.winner);
|
|
16186
|
-
const tags = (fields.tags ?? []).map(
|
|
16187
|
-
const prevents =
|
|
16188
|
-
const about = prevents === "" ?
|
|
17449
|
+
const tags = (fields.tags ?? []).map(oneLine5).filter((tag) => tag !== "").join(", ");
|
|
17450
|
+
const prevents = oneLine5(fields.prevents ?? "");
|
|
17451
|
+
const about = prevents === "" ? oneLine5(fields.summary ?? "") : prevents;
|
|
16189
17452
|
const parts = [item.name, tags, about].filter((part) => part !== "");
|
|
16190
17453
|
return cutLine(parts.join(SEPARATOR));
|
|
16191
17454
|
}
|
|
@@ -16341,14 +17604,14 @@ function taskInputFor(taskInfo, text, declaration, planContent) {
|
|
|
16341
17604
|
stageContext: stageContextOf(taskInfo, planContent)
|
|
16342
17605
|
};
|
|
16343
17606
|
}
|
|
16344
|
-
function
|
|
17607
|
+
function messageOf6(error) {
|
|
16345
17608
|
return error instanceof Error ? error.message : String(error);
|
|
16346
17609
|
}
|
|
16347
17610
|
async function pullBundle(learning, repoRoot) {
|
|
16348
17611
|
try {
|
|
16349
17612
|
return await taskLearningAdapter(learning, repoRoot).pullBlessed();
|
|
16350
17613
|
} catch (error) {
|
|
16351
|
-
activeOutput().warn(` Lessons: none handed to this task: the \`${learning.kind}\` learning adapter answered no blessed set: ${
|
|
17614
|
+
activeOutput().warn(` Lessons: none handed to this task: the \`${learning.kind}\` learning adapter answered no blessed set: ${messageOf6(error)}`);
|
|
16352
17615
|
activeOutput().warn(" The task is not failed for it: it is dispatched without a lessons section.");
|
|
16353
17616
|
return null;
|
|
16354
17617
|
}
|
|
@@ -16368,7 +17631,7 @@ async function handOut(handout, task, resolution, repoRoot) {
|
|
|
16368
17631
|
}
|
|
16369
17632
|
|
|
16370
17633
|
// src/start/preflight.ts
|
|
16371
|
-
import { randomUUID as
|
|
17634
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
16372
17635
|
import { existsSync as existsSync23, readFileSync as readFileSync27 } from "fs";
|
|
16373
17636
|
import { homedir as homedir4 } from "os";
|
|
16374
17637
|
import { basename as basename7, join as join19 } from "path";
|
|
@@ -16678,10 +17941,10 @@ function storeChecks(options, runId, report2) {
|
|
|
16678
17941
|
return messageOf(error);
|
|
16679
17942
|
}
|
|
16680
17943
|
}
|
|
16681
|
-
function refusalOf(runId,
|
|
16682
|
-
if (
|
|
17944
|
+
function refusalOf(runId, halt2, storeProblem) {
|
|
17945
|
+
if (halt2 === null && storeProblem === null)
|
|
16683
17946
|
return null;
|
|
16684
|
-
if (
|
|
17947
|
+
if (halt2 === null) {
|
|
16685
17948
|
return [
|
|
16686
17949
|
`\u274C The preflight checks of run ${runId} could not be stored: ${storeProblem}`,
|
|
16687
17950
|
" Nothing was dispatched. Make the store writable, then run again."
|
|
@@ -16690,11 +17953,11 @@ function refusalOf(runId, halt, storeProblem) {
|
|
|
16690
17953
|
}
|
|
16691
17954
|
const where = storeProblem === null ? ` Nothing was dispatched. The checks are stored under run ${runId},
|
|
16692
17955
|
and \`rafa effort report\` lists the halt.` : ` Nothing was dispatched. The checks of run ${runId} were not stored: ${storeProblem}`;
|
|
16693
|
-
return `\u274C ${
|
|
17956
|
+
return `\u274C ${halt2}
|
|
16694
17957
|
${where}`;
|
|
16695
17958
|
}
|
|
16696
17959
|
async function runStartPreflight(options) {
|
|
16697
|
-
const runId = (options.newRunId ??
|
|
17960
|
+
const runId = (options.newRunId ?? randomUUID6)();
|
|
16698
17961
|
refuseUnresolvableAgents(options);
|
|
16699
17962
|
refuseStoreRuleBreaks(options.planPath);
|
|
16700
17963
|
const items = await loadItems(options);
|
|
@@ -16798,7 +18061,7 @@ async function dispatchTask(options) {
|
|
|
16798
18061
|
const handed = await handOut(options.handout, task, resolution, options.repoRoot);
|
|
16799
18062
|
const prompt = withStamp(buildTaskPrompt(taskText, options.promptContent, injection.text, options.knownMissing, taskInfo.blocker ?? null, { skills: renderSkillsSection(handed.skills), lessons: renderLessonsSection(handed.lessons) }));
|
|
16800
18063
|
const served = serveForSession(options.serving, resolution);
|
|
16801
|
-
const sessionId = (options.newSessionId ??
|
|
18064
|
+
const sessionId = (options.newSessionId ?? randomUUID7)();
|
|
16802
18065
|
const session = await run(prompt, flags, sessionId, options.settingSources, served);
|
|
16803
18066
|
return {
|
|
16804
18067
|
taskText,
|
|
@@ -16823,7 +18086,7 @@ function serveForSession(serving, resolution) {
|
|
|
16823
18086
|
activeOutput().warn(` ${skipped2.message}`);
|
|
16824
18087
|
return served.flags;
|
|
16825
18088
|
}
|
|
16826
|
-
function
|
|
18089
|
+
function messageOf7(error) {
|
|
16827
18090
|
return error instanceof Error ? error.message : String(error);
|
|
16828
18091
|
}
|
|
16829
18092
|
function renderProgressForDispatch(repoRoot, planStub2) {
|
|
@@ -16836,7 +18099,7 @@ function renderProgressForDispatch(repoRoot, planStub2) {
|
|
|
16836
18099
|
return true;
|
|
16837
18100
|
} catch (error) {
|
|
16838
18101
|
activeOutput().error(`
|
|
16839
|
-
\u274C progress.txt could not be rendered from the findings store: ${
|
|
18102
|
+
\u274C progress.txt could not be rendered from the findings store: ${messageOf7(error)}`);
|
|
16840
18103
|
activeOutput().error(" Nothing was dispatched. Make the store readable, then run again.");
|
|
16841
18104
|
return false;
|
|
16842
18105
|
}
|
|
@@ -16860,7 +18123,7 @@ async function pushTaskLessons(options, record) {
|
|
|
16860
18123
|
const rules = result.decisions.map((decision) => decision.rule).join(", ");
|
|
16861
18124
|
activeOutput().info(` Pushed ${count} lesson(s) to the \`${learning.kind}\` learning adapter: ${rules}.`);
|
|
16862
18125
|
} catch (error) {
|
|
16863
|
-
activeOutput().warn(` ${count} lesson(s) of session ${dispatch.sessionId} were not pushed to the \`${learning.kind}\` learning adapter: ${
|
|
18126
|
+
activeOutput().warn(` ${count} lesson(s) of session ${dispatch.sessionId} were not pushed to the \`${learning.kind}\` learning adapter: ${messageOf7(error)}`);
|
|
16864
18127
|
activeOutput().warn(" The task is not failed for it: its report is stored, findings and all.");
|
|
16865
18128
|
}
|
|
16866
18129
|
}
|
|
@@ -16894,7 +18157,7 @@ async function storeTaskReport(options) {
|
|
|
16894
18157
|
activeOutput().warn(` ${warning}`);
|
|
16895
18158
|
} catch (error) {
|
|
16896
18159
|
activeOutput().error(`
|
|
16897
|
-
\u274C The report of session ${dispatch.sessionId} was not stored: ${
|
|
18160
|
+
\u274C The report of session ${dispatch.sessionId} was not stored: ${messageOf7(error)}`);
|
|
16898
18161
|
activeOutput().error(" The session printed it above as it ran.");
|
|
16899
18162
|
return false;
|
|
16900
18163
|
}
|
|
@@ -17141,14 +18404,14 @@ async function handleRedChecks(io, branch, prNumber, rows) {
|
|
|
17141
18404
|
}
|
|
17142
18405
|
|
|
17143
18406
|
// src/release/prepare.ts
|
|
17144
|
-
import { readFileSync as readFileSync28, writeFileSync as
|
|
18407
|
+
import { readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
|
|
17145
18408
|
|
|
17146
18409
|
// src/release/enabled.ts
|
|
17147
|
-
import { statSync as
|
|
18410
|
+
import { statSync as statSync12 } from "fs";
|
|
17148
18411
|
import { resolve as resolve13 } from "path";
|
|
17149
18412
|
function isRegularFile(full) {
|
|
17150
18413
|
try {
|
|
17151
|
-
return
|
|
18414
|
+
return statSync12(full).isFile();
|
|
17152
18415
|
} catch {
|
|
17153
18416
|
return false;
|
|
17154
18417
|
}
|
|
@@ -17234,7 +18497,7 @@ function textOf2(path3) {
|
|
|
17234
18497
|
}
|
|
17235
18498
|
function writeText(path3, text) {
|
|
17236
18499
|
try {
|
|
17237
|
-
|
|
18500
|
+
writeFileSync12(path3, text);
|
|
17238
18501
|
return null;
|
|
17239
18502
|
} catch (error) {
|
|
17240
18503
|
return `${path3} could not be written: ${messageOf(error)}`;
|
|
@@ -17340,7 +18603,7 @@ function changelogFailure(failed3, versionEdit, restored) {
|
|
|
17340
18603
|
}
|
|
17341
18604
|
|
|
17342
18605
|
// src/release/verify.ts
|
|
17343
|
-
import { readFileSync as readFileSync29, writeFileSync as
|
|
18606
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync13 } from "fs";
|
|
17344
18607
|
function changelogInsertionSpan(before, after) {
|
|
17345
18608
|
const beforeLines = before.split(`
|
|
17346
18609
|
`);
|
|
@@ -17388,7 +18651,7 @@ function changedOutside(current, before, span) {
|
|
|
17388
18651
|
}
|
|
17389
18652
|
function restoreFile(file) {
|
|
17390
18653
|
try {
|
|
17391
|
-
|
|
18654
|
+
writeFileSync13(file.resolved, file.after);
|
|
17392
18655
|
return { path: file.path, restored: true, problem: null };
|
|
17393
18656
|
} catch (error) {
|
|
17394
18657
|
return {
|
|
@@ -18382,7 +19645,7 @@ async function announceRiskTotal(input, seams = {}) {
|
|
|
18382
19645
|
}
|
|
18383
19646
|
|
|
18384
19647
|
// src/start/session.ts
|
|
18385
|
-
import { randomUUID as
|
|
19648
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
18386
19649
|
import { relative as relative5 } from "path";
|
|
18387
19650
|
var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
|
|
18388
19651
|
function recordLabel(repoRoot, sessionId) {
|
|
@@ -18446,16 +19709,37 @@ function sessionHandle(repoRoot, sessionId) {
|
|
|
18446
19709
|
})
|
|
18447
19710
|
});
|
|
18448
19711
|
}
|
|
19712
|
+
function awayHopOf(options) {
|
|
19713
|
+
if (options.roadmap !== true)
|
|
19714
|
+
return {};
|
|
19715
|
+
const hop = readAwayHopStamp(options.repoRoot);
|
|
19716
|
+
return hop === null ? {} : { hop };
|
|
19717
|
+
}
|
|
19718
|
+
function readAwayHopStamp(repoRoot) {
|
|
19719
|
+
const reading2 = readHopRecord(repoRoot);
|
|
19720
|
+
if (!reading2.set) {
|
|
19721
|
+
if (reading2.reason !== "absent") {
|
|
19722
|
+
activeOutput().warn(`\u26A0\uFE0F --roadmap: no hop is stamped on the session record: ${reading2.detail}`);
|
|
19723
|
+
}
|
|
19724
|
+
return null;
|
|
19725
|
+
}
|
|
19726
|
+
const { record } = reading2;
|
|
19727
|
+
if (record.state !== "away")
|
|
19728
|
+
return null;
|
|
19729
|
+
const placed = readPositionFile(repoRoot);
|
|
19730
|
+
return placed.set && !staleAgainst(record, placed.position) ? record : null;
|
|
19731
|
+
}
|
|
18449
19732
|
function openRunSession(options) {
|
|
18450
19733
|
const { repoRoot } = options;
|
|
18451
19734
|
const seams = options.seams ?? {};
|
|
18452
19735
|
const draft = {
|
|
18453
|
-
sessionId: (seams.newSessionId ??
|
|
19736
|
+
sessionId: (seams.newSessionId ?? randomUUID8)(),
|
|
18454
19737
|
planStub: options.planStub,
|
|
18455
19738
|
plan: relative5(repoRoot, options.planPath),
|
|
18456
19739
|
branch: options.branch,
|
|
18457
19740
|
pid: seams.pid ?? process.pid,
|
|
18458
|
-
startedAt: (seams.now ?? (() => new Date))().toISOString()
|
|
19741
|
+
startedAt: (seams.now ?? (() => new Date))().toISOString(),
|
|
19742
|
+
...awayHopOf(options)
|
|
18459
19743
|
};
|
|
18460
19744
|
try {
|
|
18461
19745
|
beginSession(repoRoot, draft, { isAlive: seams.isAlive });
|
|
@@ -18543,7 +19827,7 @@ function announcePlanIssues(planContent) {
|
|
|
18543
19827
|
}
|
|
18544
19828
|
|
|
18545
19829
|
// src/start/runtime.ts
|
|
18546
|
-
import { existsSync as existsSync25, readdirSync as readdirSync10, realpathSync as realpathSync2, statSync as
|
|
19830
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, realpathSync as realpathSync2, statSync as statSync13 } from "fs";
|
|
18547
19831
|
import { homedir as homedir5 } from "os";
|
|
18548
19832
|
import { isAbsolute as isAbsolute4, join as join21, relative as relative6, resolve as resolve14, sep } from "path";
|
|
18549
19833
|
var RUNTIME_FLAG = "--runtime";
|
|
@@ -18583,7 +19867,7 @@ function resolveRuntime(value, places) {
|
|
|
18583
19867
|
}
|
|
18584
19868
|
function entryOf2(value, named, isVersion, home) {
|
|
18585
19869
|
if (isVersion) {
|
|
18586
|
-
if (
|
|
19870
|
+
if (isFile3(named))
|
|
18587
19871
|
return named;
|
|
18588
19872
|
const root = join21(home, RUNTIME_SUBDIR);
|
|
18589
19873
|
const installed = installedVersions(root);
|
|
@@ -18592,10 +19876,10 @@ function entryOf2(value, named, isVersion, home) {
|
|
|
18592
19876
|
installed.length === 0 ? ` ${root} holds no version.` : ` ${root} holds ${installed.join(", ")}.`
|
|
18593
19877
|
]);
|
|
18594
19878
|
}
|
|
18595
|
-
if (
|
|
19879
|
+
if (isFile3(named))
|
|
18596
19880
|
return named;
|
|
18597
19881
|
const inside = join21(named, RUNTIME_ENTRY);
|
|
18598
|
-
if (isDirectory(named) &&
|
|
19882
|
+
if (isDirectory(named) && isFile3(inside))
|
|
18599
19883
|
return inside;
|
|
18600
19884
|
throw refusal3([
|
|
18601
19885
|
`\u274C Refusing to start: \`--runtime=${value}\` names no runtime: ${named} is neither a file nor a directory holding \`${RUNTIME_ENTRY}\`.`,
|
|
@@ -18622,21 +19906,21 @@ function isInside2(path3, dir) {
|
|
|
18622
19906
|
}
|
|
18623
19907
|
function installedVersions(root) {
|
|
18624
19908
|
try {
|
|
18625
|
-
return readdirSync10(root).filter((name) =>
|
|
19909
|
+
return readdirSync10(root).filter((name) => isFile3(join21(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
|
|
18626
19910
|
} catch {
|
|
18627
19911
|
return [];
|
|
18628
19912
|
}
|
|
18629
19913
|
}
|
|
18630
|
-
function
|
|
19914
|
+
function isFile3(path3) {
|
|
18631
19915
|
try {
|
|
18632
|
-
return
|
|
19916
|
+
return statSync13(path3).isFile();
|
|
18633
19917
|
} catch {
|
|
18634
19918
|
return false;
|
|
18635
19919
|
}
|
|
18636
19920
|
}
|
|
18637
19921
|
function isDirectory(path3) {
|
|
18638
19922
|
try {
|
|
18639
|
-
return
|
|
19923
|
+
return statSync13(path3).isDirectory();
|
|
18640
19924
|
} catch {
|
|
18641
19925
|
return false;
|
|
18642
19926
|
}
|
|
@@ -18756,7 +20040,7 @@ function eventOf(line) {
|
|
|
18756
20040
|
import { basename as basename8, join as join22 } from "path";
|
|
18757
20041
|
|
|
18758
20042
|
// src/effort/store/tracker-refs.ts
|
|
18759
|
-
import { randomUUID as
|
|
20043
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
18760
20044
|
import { existsSync as existsSync26 } from "fs";
|
|
18761
20045
|
var SELECT_HELD2 = `
|
|
18762
20046
|
SELECT seq, tracker_ref FROM findings
|
|
@@ -18766,9 +20050,10 @@ var INSERT_REF = `
|
|
|
18766
20050
|
INSERT INTO findings (
|
|
18767
20051
|
id, session_id, plan_stub, task_line,
|
|
18768
20052
|
artifact,
|
|
18769
|
-
outcome, tracker_ref, collected_at
|
|
20053
|
+
outcome, tracker_ref, collected_at,
|
|
20054
|
+
${STAMPED_COLUMNS}
|
|
18770
20055
|
)
|
|
18771
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
20056
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${stampedValues("findings")})
|
|
18772
20057
|
`;
|
|
18773
20058
|
var ATTACH_REF = "UPDATE findings SET tracker_ref = ? WHERE seq = ? AND tracker_ref IS NULL";
|
|
18774
20059
|
var SELECT_OLDEST = `
|
|
@@ -18823,17 +20108,17 @@ function storedRefOf(value) {
|
|
|
18823
20108
|
if (problem !== null)
|
|
18824
20109
|
return `whose ${field4} ${problem}`;
|
|
18825
20110
|
}
|
|
18826
|
-
const
|
|
20111
|
+
const ref5 = {
|
|
18827
20112
|
opt: fields.opt,
|
|
18828
20113
|
kind: fields.kind,
|
|
18829
20114
|
externalId: fields.externalId,
|
|
18830
20115
|
url: fields.url
|
|
18831
20116
|
};
|
|
18832
20117
|
if (typeof fields.module === "string")
|
|
18833
|
-
|
|
20118
|
+
ref5.module = fields.module;
|
|
18834
20119
|
if (typeof fields.repo === "string")
|
|
18835
|
-
|
|
18836
|
-
return { ref:
|
|
20120
|
+
ref5.repo = fields.repo;
|
|
20121
|
+
return { ref: ref5, json: JSON.stringify(ref5) };
|
|
18837
20122
|
}
|
|
18838
20123
|
function heldRefOf(path3, seq, text) {
|
|
18839
20124
|
let parsed;
|
|
@@ -18869,7 +20154,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
18869
20154
|
const path3 = sqliteStorePath(repoRoot);
|
|
18870
20155
|
const { sessionId, planStub: planStub2, taskLine } = write.dispatch;
|
|
18871
20156
|
const collectedAt = (seams.now ?? (() => new Date))().toISOString();
|
|
18872
|
-
const newId = seams.newId ??
|
|
20157
|
+
const newId = seams.newId ?? randomUUID9;
|
|
18873
20158
|
const insertValues = () => [
|
|
18874
20159
|
newId(),
|
|
18875
20160
|
sessionId,
|
|
@@ -18901,28 +20186,28 @@ var ARTIFACT_REF_KINDS = new Set(["path", "symbol"]);
|
|
|
18901
20186
|
var UNREAD_STAMP = "unread";
|
|
18902
20187
|
var REFS_INTRO = "The paths and symbols the artifact names, each with its target's" + " fingerprint when this bug was filed.";
|
|
18903
20188
|
function artifactRefs(artifact) {
|
|
18904
|
-
return extractRefs(artifact).filter((
|
|
20189
|
+
return extractRefs(artifact).filter((ref5) => ARTIFACT_REF_KINDS.has(ref5.kind));
|
|
18905
20190
|
}
|
|
18906
20191
|
function stampWord(live) {
|
|
18907
20192
|
return live.kind === "unreadable" ? UNREAD_STAMP : fingerprintText(live);
|
|
18908
20193
|
}
|
|
18909
20194
|
async function stampArtifactRefs(artifact, verify) {
|
|
18910
20195
|
const stamps = [];
|
|
18911
|
-
for (const
|
|
20196
|
+
for (const ref5 of artifactRefs(artifact)) {
|
|
18912
20197
|
let stamp;
|
|
18913
20198
|
try {
|
|
18914
|
-
stamp = stampWord(await verify({ kind:
|
|
20199
|
+
stamp = stampWord(await verify({ kind: ref5.kind, text: ref5.text }));
|
|
18915
20200
|
} catch {
|
|
18916
20201
|
stamp = UNREAD_STAMP;
|
|
18917
20202
|
}
|
|
18918
|
-
stamps.push(Object.freeze({ kind:
|
|
20203
|
+
stamps.push(Object.freeze({ kind: ref5.kind, text: ref5.text, stamp }));
|
|
18919
20204
|
}
|
|
18920
20205
|
return Object.freeze(stamps);
|
|
18921
20206
|
}
|
|
18922
20207
|
function refsSection(stamps) {
|
|
18923
20208
|
if (stamps.length === 0)
|
|
18924
20209
|
return null;
|
|
18925
|
-
const lines = stamps.map((
|
|
20210
|
+
const lines = stamps.map((ref5) => `- ${ref5.kind} \`${ref5.text}\`: \`${ref5.stamp}\``);
|
|
18926
20211
|
return [`## ${REFS_HEADING}`, REFS_INTRO, lines.join(`
|
|
18927
20212
|
`)].join(`
|
|
18928
20213
|
|
|
@@ -18945,14 +20230,14 @@ async function noIssues() {
|
|
|
18945
20230
|
}
|
|
18946
20231
|
function createArtifactRefsVerifier(repoRoot, env) {
|
|
18947
20232
|
let made = null;
|
|
18948
|
-
return async (
|
|
20233
|
+
return async (ref5) => {
|
|
18949
20234
|
made ??= createRefVerifier({
|
|
18950
20235
|
issues: noIssues,
|
|
18951
20236
|
git: createGitRunner(repoRoot),
|
|
18952
20237
|
outline: tsSymbolsOutliner({ cwd: repoRoot, env }),
|
|
18953
20238
|
roster: EMPTY_ROSTER
|
|
18954
20239
|
});
|
|
18955
|
-
return made(
|
|
20240
|
+
return made(ref5);
|
|
18956
20241
|
};
|
|
18957
20242
|
}
|
|
18958
20243
|
|
|
@@ -19027,11 +20312,11 @@ function triageBlockers(options) {
|
|
|
19027
20312
|
function artifactOf(bug) {
|
|
19028
20313
|
return hasText2(bug.artifact) && textProblem(bug.artifact) === null ? bug.artifact : null;
|
|
19029
20314
|
}
|
|
19030
|
-
function
|
|
20315
|
+
function oneLine6(text) {
|
|
19031
20316
|
return text.replace(/\s+/g, " ").trim();
|
|
19032
20317
|
}
|
|
19033
20318
|
function bugKeyOf(trackerPath, artifact) {
|
|
19034
|
-
return `${basename8(trackerPath)}${KEY_SEPARATOR}${
|
|
20319
|
+
return `${basename8(trackerPath)}${KEY_SEPARATOR}${oneLine6(artifact)}`;
|
|
19035
20320
|
}
|
|
19036
20321
|
function fenced(value) {
|
|
19037
20322
|
const runs = Array.from(value.matchAll(/`+/g), (run) => run[0].length);
|
|
@@ -19043,7 +20328,7 @@ function fenced(value) {
|
|
|
19043
20328
|
${body}${fence}`;
|
|
19044
20329
|
}
|
|
19045
20330
|
function issueTitle2(redactedWhat) {
|
|
19046
|
-
const line =
|
|
20331
|
+
const line = oneLine6(redactedWhat);
|
|
19047
20332
|
const points = Array.from(line);
|
|
19048
20333
|
if (points.length <= TITLE_MAX_LENGTH)
|
|
19049
20334
|
return line;
|
|
@@ -19119,23 +20404,23 @@ function resultOf(run, action, fields = {}) {
|
|
|
19119
20404
|
...fields
|
|
19120
20405
|
};
|
|
19121
20406
|
}
|
|
19122
|
-
async function settle(run, action,
|
|
20407
|
+
async function settle(run, action, ref5, foundBy) {
|
|
19123
20408
|
const { store } = run.route;
|
|
19124
20409
|
const { key } = run.filing;
|
|
19125
20410
|
if (store === null || key === null)
|
|
19126
|
-
return resultOf(run, action, { ref:
|
|
19127
|
-
const stored = await step(run, "storing the reference", () => store(key,
|
|
19128
|
-
return stored.ok ? resultOf(run, action, { ref:
|
|
20411
|
+
return resultOf(run, action, { ref: ref5, foundBy });
|
|
20412
|
+
const stored = await step(run, "storing the reference", () => store(key, ref5));
|
|
20413
|
+
return stored.ok ? resultOf(run, action, { ref: ref5, foundBy, stored: stored.value }) : resultOf(run, action, { ref: ref5, foundBy, problem: stored.problem });
|
|
19129
20414
|
}
|
|
19130
20415
|
async function fileNew(run) {
|
|
19131
20416
|
const created = await step(run, `the ${run.route.channel} tracker create`, () => run.route.tracker.create(run.filing.draft));
|
|
19132
20417
|
return created.ok ? settle(run, "filed", created.value, null) : resultOf(run, "failed", { problem: created.problem });
|
|
19133
20418
|
}
|
|
19134
|
-
async function commentOn(run,
|
|
19135
|
-
const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(
|
|
20419
|
+
async function commentOn(run, ref5, foundBy) {
|
|
20420
|
+
const commented = await step(run, `the ${run.route.channel} tracker comment`, () => run.route.tracker.comment(ref5, run.filing.comment));
|
|
19136
20421
|
if (!commented.ok)
|
|
19137
|
-
return resultOf(run, "failed", { ref:
|
|
19138
|
-
return foundBy === "store" ? resultOf(run, "commented", { ref:
|
|
20422
|
+
return resultOf(run, "failed", { ref: ref5, foundBy, problem: commented.problem });
|
|
20423
|
+
return foundBy === "store" ? resultOf(run, "commented", { ref: ref5, foundBy }) : settle(run, "commented", ref5, foundBy);
|
|
19139
20424
|
}
|
|
19140
20425
|
async function triageBug(run) {
|
|
19141
20426
|
const { route, filing } = run;
|
|
@@ -19183,11 +20468,11 @@ async function triageReport(options) {
|
|
|
19183
20468
|
channel: "public",
|
|
19184
20469
|
tracker: options.tracker,
|
|
19185
20470
|
readStored: (key) => readTrackerRef(repoRoot, key),
|
|
19186
|
-
store: (key,
|
|
20471
|
+
store: (key, ref5) => writeTrackerRef(repoRoot, {
|
|
19187
20472
|
dispatch: options.dispatch,
|
|
19188
20473
|
outcome: options.outcome,
|
|
19189
20474
|
artifact: key,
|
|
19190
|
-
ref:
|
|
20475
|
+
ref: ref5
|
|
19191
20476
|
}, options.seams).action
|
|
19192
20477
|
},
|
|
19193
20478
|
private: { channel: "private", tracker: privateTracker, readStored: null, store: null }
|
|
@@ -19238,8 +20523,8 @@ function filesPublicly(bug) {
|
|
|
19238
20523
|
function hasTriage(report2) {
|
|
19239
20524
|
return blockerTextOf(report2.blockers) !== null || report2.outOfScopeBugs.length > 0;
|
|
19240
20525
|
}
|
|
19241
|
-
function issueName2(
|
|
19242
|
-
return
|
|
20526
|
+
function issueName2(ref5) {
|
|
20527
|
+
return ref5.url ?? `${ref5.kind} issue ${ref5.externalId}`;
|
|
19243
20528
|
}
|
|
19244
20529
|
function bugLines(bug) {
|
|
19245
20530
|
const name = `out_of_scope_bugs[${bug.index}]`;
|
|
@@ -19431,7 +20716,7 @@ function changedSince(git, head) {
|
|
|
19431
20716
|
const paths = result.stdout.split("\x00").filter((path3) => path3 !== "");
|
|
19432
20717
|
return { ok: true, head, paths: new Set(paths) };
|
|
19433
20718
|
}
|
|
19434
|
-
function
|
|
20719
|
+
function repoPath2(path3, repoRoot) {
|
|
19435
20720
|
const inside = isAbsolute5(path3) ? relative7(repoRoot, path3) : path3;
|
|
19436
20721
|
const normal = posix2.normalize(inside.replaceAll("\\", "/"));
|
|
19437
20722
|
if (normal === "." || normal === ".." || normal.startsWith("../") || isAbsolute5(normal))
|
|
@@ -19441,7 +20726,7 @@ function repoPath(path3, repoRoot) {
|
|
|
19441
20726
|
function promotedOutcome(lesson, named, changed, repoRoot) {
|
|
19442
20727
|
if (!changed.ok)
|
|
19443
20728
|
return { kind: "unchecked", id: lesson.id, path: named, reason: changed.reason };
|
|
19444
|
-
const path3 =
|
|
20729
|
+
const path3 = repoPath2(named, repoRoot);
|
|
19445
20730
|
return path3 !== null && changed.paths.has(path3) ? { lesson, path: path3 } : { kind: "unchanged", id: lesson.id, path: named };
|
|
19446
20731
|
}
|
|
19447
20732
|
function checkPromoted(lessons, reading2, changed, repoRoot) {
|
|
@@ -19584,15 +20869,15 @@ function buildWrapUpPrompt(branch, planContent, openPullRequest = null, release
|
|
|
19584
20869
|
].join(`
|
|
19585
20870
|
`);
|
|
19586
20871
|
}
|
|
19587
|
-
function
|
|
20872
|
+
function oneLine7(text) {
|
|
19588
20873
|
return text.trim().replace(/\s+/g, " ");
|
|
19589
20874
|
}
|
|
19590
20875
|
function lessonLines(lesson) {
|
|
19591
|
-
const artifact = lesson.artifact === undefined ||
|
|
20876
|
+
const artifact = lesson.artifact === undefined || oneLine7(lesson.artifact) === "" ? "none" : oneLine7(lesson.artifact);
|
|
19592
20877
|
return [
|
|
19593
20878
|
`- \`${lesson.id}\``,
|
|
19594
|
-
` - trigger: ${
|
|
19595
|
-
` - action: ${
|
|
20879
|
+
` - trigger: ${oneLine7(lesson.trigger)}`,
|
|
20880
|
+
` - action: ${oneLine7(lesson.action)}`,
|
|
19596
20881
|
` - artifact: ${artifact}`
|
|
19597
20882
|
];
|
|
19598
20883
|
}
|
|
@@ -19661,7 +20946,7 @@ async function openPullRequestNumber(branch) {
|
|
|
19661
20946
|
return null;
|
|
19662
20947
|
}
|
|
19663
20948
|
}
|
|
19664
|
-
function
|
|
20949
|
+
function messageOf8(error) {
|
|
19665
20950
|
return error instanceof Error ? error.message : String(error);
|
|
19666
20951
|
}
|
|
19667
20952
|
function wrapUpAdapter(learning) {
|
|
@@ -19682,7 +20967,7 @@ async function lessonsToPromote(learning) {
|
|
|
19682
20967
|
minConfidence: learning.promoteMinConfidence
|
|
19683
20968
|
});
|
|
19684
20969
|
} catch (error) {
|
|
19685
|
-
activeOutput().warn(` The wrap-up lists no lesson to promote: the \`${learning.kind}\` learning adapter answered no blessed set: ${
|
|
20970
|
+
activeOutput().warn(` The wrap-up lists no lesson to promote: the \`${learning.kind}\` learning adapter answered no blessed set: ${messageOf8(error)}`);
|
|
19686
20971
|
return [];
|
|
19687
20972
|
}
|
|
19688
20973
|
}
|
|
@@ -19753,6 +21038,7 @@ var interrupted = false;
|
|
|
19753
21038
|
var DEFAULT_BRANCHES = ["main", "master"];
|
|
19754
21039
|
var ANY_BRANCH_FLAG = "--any-branch";
|
|
19755
21040
|
var CREATE_BRANCH_FLAG = "--create-branch";
|
|
21041
|
+
var ROADMAP_FLAG2 = "--roadmap";
|
|
19756
21042
|
var UNNAMED_PLAN = "this-plan";
|
|
19757
21043
|
async function resolveRunBranch(request, seams = DEFAULT_BRANCH_SEAMS) {
|
|
19758
21044
|
const { args, base } = request;
|
|
@@ -19854,7 +21140,7 @@ async function start(args, repoRoot) {
|
|
|
19854
21140
|
});
|
|
19855
21141
|
await requireNoticesAnswered();
|
|
19856
21142
|
setActivePlanStub(planStub2);
|
|
19857
|
-
const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch });
|
|
21143
|
+
const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch, roadmap: args.includes(ROADMAP_FLAG2) });
|
|
19858
21144
|
try {
|
|
19859
21145
|
const planContent = fs.readFileSync(planPath, "utf8");
|
|
19860
21146
|
const promptContent = fs.readFileSync(promptPath, "utf8");
|
|
@@ -20544,4 +21830,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
20544
21830
|
}
|
|
20545
21831
|
}
|
|
20546
21832
|
|
|
20547
|
-
export { readConfigFile, loadConfig, SERVE_CLI_VERSION, 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, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, SPEC_EXTENSION, boardId, notesFileName, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches,
|
|
21833
|
+
export { readConfigFile, loadConfig, SERVE_CLI_VERSION, 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, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, SPEC_EXTENSION, boardId, notesFileName, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, 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, 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, renderDoctorRefs, readEpicDependents, REASON_FLAG, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, HORIZONS, horizonOf, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, createPlanDirNames, hasPlanFor, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, resolvePrProvider, requireGhProvider, isFirstDispatch, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, 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, nextOwnerGate, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, createLinePrompter, REMOTE, parseBaseStanding, hasDiverged, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter, ready_default, 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 };
|