@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
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
NEXT_COMMAND_ACTIONS,
|
|
26
26
|
NO_NEXT_STEP,
|
|
27
27
|
ORIGIN_FIELD,
|
|
28
|
-
POSITION_FILE,
|
|
29
28
|
PREVIOUS_COPY_WARN_ABOVE,
|
|
30
29
|
PROVENANCE_FIELD,
|
|
31
30
|
REASON_FLAG,
|
|
@@ -61,7 +60,7 @@ import {
|
|
|
61
60
|
blockedLineSentence,
|
|
62
61
|
boardChanged,
|
|
63
62
|
boardId,
|
|
64
|
-
|
|
63
|
+
boardOfEpic,
|
|
65
64
|
bodySections,
|
|
66
65
|
branchClaims,
|
|
67
66
|
bundledAgentsDirectory,
|
|
@@ -92,8 +91,8 @@ import {
|
|
|
92
91
|
createGhPullRequests,
|
|
93
92
|
createGhRoadmapSearch,
|
|
94
93
|
createGhSpecIssueReader,
|
|
95
|
-
createGitRunner,
|
|
96
94
|
createLinePrompter,
|
|
95
|
+
createOwnerResolver,
|
|
97
96
|
createPlanDirNames,
|
|
98
97
|
createPlanRefsVerifier,
|
|
99
98
|
developmentApplied,
|
|
@@ -109,7 +108,6 @@ import {
|
|
|
109
108
|
expectOneArgument,
|
|
110
109
|
expectTwoArguments,
|
|
111
110
|
failingRows,
|
|
112
|
-
findField,
|
|
113
111
|
fingerprintText,
|
|
114
112
|
firstNowEpic,
|
|
115
113
|
firstSentence,
|
|
@@ -124,12 +122,10 @@ import {
|
|
|
124
122
|
ghPreflightItems,
|
|
125
123
|
ghPullRequestsIn,
|
|
126
124
|
ghUnauthenticatedMessage,
|
|
127
|
-
gitSaid,
|
|
128
125
|
groupByEpicLabel,
|
|
129
126
|
hasDiverged,
|
|
130
127
|
hasPlanFor,
|
|
131
128
|
hasSpecBlockedLabel,
|
|
132
|
-
hop,
|
|
133
129
|
horizonOf,
|
|
134
130
|
isFile,
|
|
135
131
|
isFirstDispatch,
|
|
@@ -153,9 +149,11 @@ import {
|
|
|
153
149
|
missingBoardLabels,
|
|
154
150
|
missingPlanAgents,
|
|
155
151
|
namedSecrets,
|
|
152
|
+
nextOwnerGate,
|
|
156
153
|
nextQuestion,
|
|
157
154
|
notesFileName,
|
|
158
155
|
onTracker,
|
|
156
|
+
openBoards,
|
|
159
157
|
openNextSources,
|
|
160
158
|
parseBaseStanding,
|
|
161
159
|
parseRoadmapBody,
|
|
@@ -168,7 +166,6 @@ import {
|
|
|
168
166
|
planLabel,
|
|
169
167
|
plansDirAt,
|
|
170
168
|
plural,
|
|
171
|
-
positionFilePath,
|
|
172
169
|
previousDir,
|
|
173
170
|
projectRoot,
|
|
174
171
|
provenanceBlock,
|
|
@@ -176,6 +173,7 @@ import {
|
|
|
176
173
|
readAgentDirectory,
|
|
177
174
|
readAuthorTrust,
|
|
178
175
|
readBlockedBy,
|
|
176
|
+
readBoardBody,
|
|
179
177
|
readBranch,
|
|
180
178
|
readBranchPlan,
|
|
181
179
|
readChoice,
|
|
@@ -186,13 +184,13 @@ import {
|
|
|
186
184
|
readEpicDependents,
|
|
187
185
|
readEpicProblems,
|
|
188
186
|
readEpics,
|
|
187
|
+
readHomeAfterLoop,
|
|
189
188
|
readManifestVersion,
|
|
190
189
|
readMergeRefusal,
|
|
191
190
|
readNextState,
|
|
192
191
|
readNonBlankFlag,
|
|
193
192
|
readOpenPull,
|
|
194
193
|
readPlans,
|
|
195
|
-
readPositionFile,
|
|
196
194
|
readRankCandidate,
|
|
197
195
|
readReason,
|
|
198
196
|
readRecords,
|
|
@@ -210,7 +208,6 @@ import {
|
|
|
210
208
|
recordPlanCi,
|
|
211
209
|
refusal,
|
|
212
210
|
refuseMisplacedRuntime,
|
|
213
|
-
rehome,
|
|
214
211
|
remainingFrom,
|
|
215
212
|
renderCancelComment,
|
|
216
213
|
renderCloseComment,
|
|
@@ -271,10 +268,9 @@ import {
|
|
|
271
268
|
verdictOf,
|
|
272
269
|
waitForChecks,
|
|
273
270
|
watchDryEpic,
|
|
274
|
-
writePositionFile,
|
|
275
271
|
writeSession,
|
|
276
272
|
writeShippedFile
|
|
277
|
-
} from "./index-
|
|
273
|
+
} from "./index-6jkj0dcx.js";
|
|
278
274
|
import {
|
|
279
275
|
PROBE_TIMEOUT_MS,
|
|
280
276
|
findNextTask,
|
|
@@ -297,16 +293,20 @@ import {
|
|
|
297
293
|
DISK_FILE_SYSTEM,
|
|
298
294
|
DevelopmentBuildRefusedError,
|
|
299
295
|
EFFORT_DIR_VARIABLE,
|
|
296
|
+
EFFORT_KEY_PROJECTIONS,
|
|
300
297
|
EFFORT_STORE_DIR,
|
|
298
|
+
HOP_FILE,
|
|
301
299
|
ISSUE_PRIORITIES,
|
|
302
300
|
ISSUE_STATES,
|
|
303
301
|
ISSUE_TYPES,
|
|
304
302
|
MARKDOWN_EXTENSION,
|
|
305
303
|
MIGRATION_LOG_TABLE,
|
|
304
|
+
POSITION_FILE,
|
|
306
305
|
SCOPE_DIR,
|
|
307
306
|
SKILL_FILE,
|
|
308
307
|
SQLITE_MIGRATIONS,
|
|
309
308
|
SQLITE_STORE_FILE_NAME,
|
|
309
|
+
SchemaRefusedError,
|
|
310
310
|
SessionRecordError,
|
|
311
311
|
activeStoreSettings,
|
|
312
312
|
allRecords,
|
|
@@ -314,26 +314,37 @@ import {
|
|
|
314
314
|
bringForward,
|
|
315
315
|
claudeArgs,
|
|
316
316
|
createGhRunner,
|
|
317
|
+
createGitRunner,
|
|
317
318
|
effortStoreDir,
|
|
318
319
|
effortStoreFileName,
|
|
319
320
|
errorCode,
|
|
320
321
|
findRecords,
|
|
321
322
|
gitRemoteUrl,
|
|
323
|
+
gitSaid,
|
|
322
324
|
guardTestProcess,
|
|
325
|
+
hop,
|
|
323
326
|
instinctProject,
|
|
324
327
|
isPidAlive,
|
|
325
328
|
mergeFrontmatter,
|
|
326
329
|
name,
|
|
327
330
|
normalizeRemote,
|
|
331
|
+
openNdjsonStore,
|
|
332
|
+
openSqliteStore,
|
|
328
333
|
planSchema,
|
|
334
|
+
positionFilePath,
|
|
329
335
|
readFrontmatter,
|
|
330
336
|
readFrontmatterDocument,
|
|
337
|
+
readHopRecord,
|
|
338
|
+
readPositionFile,
|
|
339
|
+
readProjectIdentity,
|
|
331
340
|
readRuntimeIdentity,
|
|
332
341
|
readScopes,
|
|
333
342
|
readSession,
|
|
334
343
|
readSessions,
|
|
344
|
+
readStoreMeta,
|
|
335
345
|
readStoreSchema,
|
|
336
346
|
refuseUnownedDevelopmentWrite,
|
|
347
|
+
rehome,
|
|
337
348
|
renderFrontmatter,
|
|
338
349
|
runClaudeCaptured,
|
|
339
350
|
runsDir,
|
|
@@ -345,13 +356,15 @@ import {
|
|
|
345
356
|
spawnClaudeCaptured,
|
|
346
357
|
sqliteCatalogue,
|
|
347
358
|
sqliteStorePath,
|
|
359
|
+
staleAgainst,
|
|
348
360
|
storeProjectRoot,
|
|
349
361
|
toHeldRecords,
|
|
350
362
|
version,
|
|
351
363
|
versionLine,
|
|
352
364
|
writeFrontmatter,
|
|
353
|
-
writeNames
|
|
354
|
-
|
|
365
|
+
writeNames,
|
|
366
|
+
writePositionFile
|
|
367
|
+
} from "./index-4rwvr6t4.js";
|
|
355
368
|
import {
|
|
356
369
|
CONFIG_DEFAULTS,
|
|
357
370
|
CONFIG_FILE,
|
|
@@ -2545,7 +2558,7 @@ function headingOf(search, question, projectRoot2) {
|
|
|
2545
2558
|
const how = search.status === "ranked" ? "; keyword ranking, no session" : "";
|
|
2546
2559
|
return `${plural2} for "${question}" (project: ${projectRoot2}${how}):`;
|
|
2547
2560
|
}
|
|
2548
|
-
function
|
|
2561
|
+
function bodyLines(search) {
|
|
2549
2562
|
switch (search.status) {
|
|
2550
2563
|
case "no-candidates":
|
|
2551
2564
|
return [` (no ${search.kind} ranks for these words)`];
|
|
@@ -2570,7 +2583,7 @@ function searchWarnings(search) {
|
|
|
2570
2583
|
return [...notice, ...issues, ...effort];
|
|
2571
2584
|
}
|
|
2572
2585
|
function renderKindSearch(search, question, projectRoot2) {
|
|
2573
|
-
return [headingOf(search, question, projectRoot2), ...
|
|
2586
|
+
return [headingOf(search, question, projectRoot2), ...bodyLines(search)];
|
|
2574
2587
|
}
|
|
2575
2588
|
function projectOf3(context, kind) {
|
|
2576
2589
|
if (context.project === null)
|
|
@@ -2945,96 +2958,6 @@ function createAgentVendorCommand(now = () => new Date, entry = () => Bun.main)
|
|
|
2945
2958
|
}
|
|
2946
2959
|
var vendor_default = createAgentVendorCommand();
|
|
2947
2960
|
|
|
2948
|
-
// src/board/board-body.ts
|
|
2949
|
-
var OWNER_FIELD = "Owner";
|
|
2950
|
-
var BOARD_OWNS_FIELD = "Owns";
|
|
2951
|
-
var LOGIN = "[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}";
|
|
2952
|
-
var TEAM = "[A-Za-z0-9][A-Za-z0-9_.-]*";
|
|
2953
|
-
var HANDLE = new RegExp(`^@${LOGIN}(?:/${TEAM})?$`, "u");
|
|
2954
|
-
function isOwnerHandle(text) {
|
|
2955
|
-
return HANDLE.test(text);
|
|
2956
|
-
}
|
|
2957
|
-
var OWNS_SEPARATOR = /[\s,]+/u;
|
|
2958
|
-
function undressed(text) {
|
|
2959
|
-
return text.replaceAll("`", " ").trim();
|
|
2960
|
-
}
|
|
2961
|
-
function normaliseFolder(folder) {
|
|
2962
|
-
let path = folder;
|
|
2963
|
-
while (path.startsWith("./"))
|
|
2964
|
-
path = path.slice(2);
|
|
2965
|
-
while (path.endsWith("/"))
|
|
2966
|
-
path = path.slice(0, -1);
|
|
2967
|
-
return path;
|
|
2968
|
-
}
|
|
2969
|
-
function isRepoRelative(path) {
|
|
2970
|
-
if (path === "" || path === "." || path.startsWith("/"))
|
|
2971
|
-
return false;
|
|
2972
|
-
return !path.split("/").includes("..");
|
|
2973
|
-
}
|
|
2974
|
-
function ownedFolders(text) {
|
|
2975
|
-
const named = undressed(text).split(OWNS_SEPARATOR).filter((folder) => folder !== "");
|
|
2976
|
-
const owns = named.map(normaliseFolder).filter(isRepoRelative);
|
|
2977
|
-
const rejected = named.filter((folder) => !isRepoRelative(normaliseFolder(folder)));
|
|
2978
|
-
return { owns: [...new Set(owns)], rejected };
|
|
2979
|
-
}
|
|
2980
|
-
function readBoardBody(body) {
|
|
2981
|
-
const lines = bodyLines(body);
|
|
2982
|
-
const ownerLine = findField(lines, OWNER_FIELD);
|
|
2983
|
-
const ownsLine = findField(lines, BOARD_OWNS_FIELD);
|
|
2984
|
-
const handle = ownerLine === null ? null : undressed(ownerLine.text);
|
|
2985
|
-
const isOwned = handle !== null && isOwnerHandle(handle);
|
|
2986
|
-
const folders = ownsLine === null ? { owns: [], rejected: [] } : ownedFolders(ownsLine.text);
|
|
2987
|
-
const problems = [
|
|
2988
|
-
ownerLine === null || isOwned ? null : { kind: "malformed-owner", line: ownerLine.number, text: ownerLine.text, rejected: Object.freeze([]) },
|
|
2989
|
-
ownsLine === null || folders.rejected.length === 0 ? null : { kind: "malformed-owns", line: ownsLine.number, text: ownsLine.text, rejected: Object.freeze(folders.rejected) }
|
|
2990
|
-
];
|
|
2991
|
-
return Object.freeze({
|
|
2992
|
-
lines: parseRoadmapBody(body),
|
|
2993
|
-
owner: isOwned ? handle : null,
|
|
2994
|
-
owns: Object.freeze(folders.owns),
|
|
2995
|
-
problems: Object.freeze(problems.filter((problem) => problem !== null))
|
|
2996
|
-
});
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
// src/board/owner-resolve.ts
|
|
3000
|
-
var NOT_FOUND = "(HTTP 404)";
|
|
3001
|
-
function ownerLookupArgs(handle) {
|
|
3002
|
-
if (!isOwnerHandle(handle)) {
|
|
3003
|
-
return null;
|
|
3004
|
-
}
|
|
3005
|
-
const [account = "", slug] = handle.slice(1).split("/");
|
|
3006
|
-
return slug === undefined ? ["api", `users/${account}`] : ["api", `orgs/${account}/teams/${slug}`];
|
|
3007
|
-
}
|
|
3008
|
-
function failureReason(result, command) {
|
|
3009
|
-
const written = result.stderr.trim() || result.stdout.trim();
|
|
3010
|
-
return written === "" ? `${command} failed and wrote nothing` : `${command} failed: ${written}`;
|
|
3011
|
-
}
|
|
3012
|
-
async function lookUp(gh, handle) {
|
|
3013
|
-
const args = ownerLookupArgs(handle);
|
|
3014
|
-
if (args === null) {
|
|
3015
|
-
return { handle, state: "unknown", reason: `${JSON.stringify(handle)} is not an @login or @org/team handle, so nothing was asked` };
|
|
3016
|
-
}
|
|
3017
|
-
const result = await gh(args);
|
|
3018
|
-
if (result.ok) {
|
|
3019
|
-
return { handle, state: "resolved" };
|
|
3020
|
-
}
|
|
3021
|
-
return result.stderr.includes(NOT_FOUND) ? { handle, state: "unresolved" } : { handle, state: "unknown", reason: failureReason(result, `gh ${args.join(" ")}`) };
|
|
3022
|
-
}
|
|
3023
|
-
function createOwnerResolver(options) {
|
|
3024
|
-
const { gh } = options;
|
|
3025
|
-
const answers = new Map;
|
|
3026
|
-
return (handle) => {
|
|
3027
|
-
const key = handle.toLowerCase();
|
|
3028
|
-
const known = answers.get(key);
|
|
3029
|
-
if (known !== undefined) {
|
|
3030
|
-
return known.then((answer) => ({ ...answer, handle }));
|
|
3031
|
-
}
|
|
3032
|
-
const pending = lookUp(gh, handle);
|
|
3033
|
-
answers.set(key, pending);
|
|
3034
|
-
return pending;
|
|
3035
|
-
};
|
|
3036
|
-
}
|
|
3037
|
-
|
|
3038
2961
|
// src/commands/switch.ts
|
|
3039
2962
|
var USAGE5 = "rafa switch <n | -> [--no-rehome]";
|
|
3040
2963
|
var SWITCH_REFUSAL_EXIT = 2;
|
|
@@ -3068,9 +2991,6 @@ function readRehome(flags) {
|
|
|
3068
2991
|
return value;
|
|
3069
2992
|
throw lineRefusal(`--${REHOME_FLAG} takes no value, and read "${value}" as one; type the target before the flags`, USAGE5);
|
|
3070
2993
|
}
|
|
3071
|
-
function openBoards(listing) {
|
|
3072
|
-
return listing.filter((issue) => issue.state === "OPEN" && issue.labels.includes(ROADMAP_LABEL)).sort((a, b) => a.number - b.number);
|
|
3073
|
-
}
|
|
3074
2994
|
async function kindOf(number, board) {
|
|
3075
2995
|
const row = board.rows.get(number);
|
|
3076
2996
|
if (row === undefined) {
|
|
@@ -3087,20 +3007,6 @@ async function kindOf(number, board) {
|
|
|
3087
3007
|
return { why: `${id(number)} is neither a board nor an epic: it carries neither ${ROADMAP_LABEL} nor type:epic` };
|
|
3088
3008
|
return row.state === "CLOSED" ? { why: `${id(number)} is a closed ${kind}` } : { kind };
|
|
3089
3009
|
}
|
|
3090
|
-
function lists(number, epic, board) {
|
|
3091
|
-
const row = board.rows.get(number);
|
|
3092
|
-
if (row?.state !== "OPEN")
|
|
3093
|
-
return false;
|
|
3094
|
-
return parseRoadmapBody(row.body).some((line) => line.issue === epic);
|
|
3095
|
-
}
|
|
3096
|
-
async function boardOfEpic(epic, current, board) {
|
|
3097
|
-
if (lists(current.board, epic, board))
|
|
3098
|
-
return current.board;
|
|
3099
|
-
const fallback = await board.defaultBoard();
|
|
3100
|
-
if (lists(fallback, epic, board))
|
|
3101
|
-
return fallback;
|
|
3102
|
-
return openBoards(board.listing).find((row) => lists(row.number, epic, board))?.number ?? fallback;
|
|
3103
|
-
}
|
|
3104
3010
|
function boardPlace(number, board, epics) {
|
|
3105
3011
|
const row = board.rows.get(number);
|
|
3106
3012
|
const epic = row === undefined ? null : firstNowEpic(parseRoadmapBody(row.body), board.listing, epics)?.number ?? null;
|
|
@@ -7559,12 +7465,14 @@ function refuseInFlight(path) {
|
|
|
7559
7465
|
}
|
|
7560
7466
|
}
|
|
7561
7467
|
}
|
|
7562
|
-
function checkedCounts(db, tables, original) {
|
|
7468
|
+
function checkedCounts(db, tables, original, added = {}) {
|
|
7563
7469
|
return tables.map((table) => {
|
|
7564
7470
|
const rows = count(db, `SELECT count(*) AS n FROM main.${quoted(table)}`);
|
|
7565
7471
|
const originalRows = count(db, `SELECT count(*) AS n FROM ${original}.${quoted(table)}`);
|
|
7566
|
-
|
|
7567
|
-
|
|
7472
|
+
const addedRows = added[table] ?? 0;
|
|
7473
|
+
if (rows !== originalRows + addedRows) {
|
|
7474
|
+
const plus = addedRows === 0 ? "" : ` plus ${String(addedRows)} added`;
|
|
7475
|
+
throw new RebuildRefusal(`the rebuilt ${table} holds ${String(rows)} rows, the store ${String(originalRows)}${plus}`);
|
|
7568
7476
|
}
|
|
7569
7477
|
return { table, rows };
|
|
7570
7478
|
});
|
|
@@ -7978,8 +7886,291 @@ function createCopyCommand(seams = {}) {
|
|
|
7978
7886
|
}
|
|
7979
7887
|
var copy_default = createCopyCommand();
|
|
7980
7888
|
|
|
7981
|
-
// src/commands/effort/
|
|
7982
|
-
import { join as
|
|
7889
|
+
// src/commands/effort/merge.ts
|
|
7890
|
+
import { join as join21, resolve as resolve8 } from "path";
|
|
7891
|
+
|
|
7892
|
+
// src/effort/store/merge-store.ts
|
|
7893
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7894
|
+
import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "fs";
|
|
7895
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7896
|
+
import { dirname as dirname3, join as join20 } from "path";
|
|
7897
|
+
import { Database as Database4, SQLiteError as SQLiteError2 } from "bun:sqlite";
|
|
7898
|
+
|
|
7899
|
+
// src/effort/store/merge-commit-gaps.ts
|
|
7900
|
+
var GAP_FIELD = "minutesSincePrevious";
|
|
7901
|
+
var UPDATE_GAP = "UPDATE commits SET row_json = ? WHERE seq = ?";
|
|
7902
|
+
function parsedObject(text) {
|
|
7903
|
+
try {
|
|
7904
|
+
const value = JSON.parse(text);
|
|
7905
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7906
|
+
} catch {
|
|
7907
|
+
return null;
|
|
7908
|
+
}
|
|
7909
|
+
}
|
|
7910
|
+
function withoutGap(rowJson) {
|
|
7911
|
+
const row2 = parsedObject(rowJson);
|
|
7912
|
+
if (row2 === null)
|
|
7913
|
+
return rowJson;
|
|
7914
|
+
const rest = Object.fromEntries(Object.entries(row2).filter(([key]) => key !== GAP_FIELD));
|
|
7915
|
+
return JSON.stringify(rest);
|
|
7916
|
+
}
|
|
7917
|
+
function readCommits(db) {
|
|
7918
|
+
return db.query("SELECT seq, sha, row_json FROM commits").all().map(({ seq, sha, row_json: rowJson }) => {
|
|
7919
|
+
const row2 = parsedObject(rowJson);
|
|
7920
|
+
const timestamp = typeof row2?.timestamp === "string" ? row2.timestamp : null;
|
|
7921
|
+
return { seq, sha, row: row2, timestamp, epoch: timestamp === null ? Number.NaN : Date.parse(timestamp) };
|
|
7922
|
+
});
|
|
7923
|
+
}
|
|
7924
|
+
function timeOrder(entries) {
|
|
7925
|
+
return entries.filter((entry) => !Number.isNaN(entry.epoch)).sort((left, right) => left.epoch - right.epoch || compareText(left.sha, right.sha));
|
|
7926
|
+
}
|
|
7927
|
+
function compareText(left, right) {
|
|
7928
|
+
if (left === right)
|
|
7929
|
+
return 0;
|
|
7930
|
+
return left < right ? -1 : 1;
|
|
7931
|
+
}
|
|
7932
|
+
function storedGap(entry) {
|
|
7933
|
+
const value = entry.row?.[GAP_FIELD];
|
|
7934
|
+
return typeof value === "number" ? value : null;
|
|
7935
|
+
}
|
|
7936
|
+
function namedRows(inserted, positions, order) {
|
|
7937
|
+
const position = positions.get(inserted.seq);
|
|
7938
|
+
if (position === undefined)
|
|
7939
|
+
return [inserted.seq];
|
|
7940
|
+
const next = order[position + 1];
|
|
7941
|
+
return next === undefined ? [inserted.seq] : [inserted.seq, next.seq];
|
|
7942
|
+
}
|
|
7943
|
+
function recomputedGap(entry, positions, order) {
|
|
7944
|
+
const position = positions.get(entry.seq);
|
|
7945
|
+
if (position === undefined || position === 0)
|
|
7946
|
+
return null;
|
|
7947
|
+
const previous = order[position - 1];
|
|
7948
|
+
if (previous?.timestamp == null || entry.timestamp === null)
|
|
7949
|
+
return null;
|
|
7950
|
+
return minutesBetween(previous.timestamp, entry.timestamp);
|
|
7951
|
+
}
|
|
7952
|
+
function recomputeCommitGaps(db, inserted) {
|
|
7953
|
+
return db.transaction(() => {
|
|
7954
|
+
const entries = readCommits(db);
|
|
7955
|
+
const bySeq = new Map(entries.map((entry) => [entry.seq, entry]));
|
|
7956
|
+
const order = timeOrder(entries);
|
|
7957
|
+
const positions = new Map(order.map((entry, position) => [entry.seq, position]));
|
|
7958
|
+
const named = new Set(inserted.flatMap((seq) => {
|
|
7959
|
+
const entry = bySeq.get(seq);
|
|
7960
|
+
return entry === undefined ? [] : namedRows(entry, positions, order);
|
|
7961
|
+
}));
|
|
7962
|
+
const targets = [...named].map((seq) => bySeq.get(seq)).filter((entry) => entry?.row != null).sort((left, right) => (positions.get(left.seq) ?? -1) - (positions.get(right.seq) ?? -1));
|
|
7963
|
+
return targets.flatMap((entry) => {
|
|
7964
|
+
const before = storedGap(entry);
|
|
7965
|
+
const after = recomputedGap(entry, positions, order);
|
|
7966
|
+
if (before === after && entry.row !== null && Object.hasOwn(entry.row, GAP_FIELD))
|
|
7967
|
+
return [];
|
|
7968
|
+
db.query(UPDATE_GAP).run(JSON.stringify({ ...entry.row, [GAP_FIELD]: after }), entry.seq);
|
|
7969
|
+
return [{ seq: entry.seq, sha: entry.sha, before, after }];
|
|
7970
|
+
});
|
|
7971
|
+
})();
|
|
7972
|
+
}
|
|
7973
|
+
|
|
7974
|
+
// src/effort/store/merge-rules.ts
|
|
7975
|
+
var SET_ONCE = "set-once";
|
|
7976
|
+
var RECOMPUTED = "recomputed";
|
|
7977
|
+
var APPEND_ONLY = {};
|
|
7978
|
+
function appendOnly(...identity) {
|
|
7979
|
+
return { scope: "merged", identity, edited: APPEND_ONLY };
|
|
7980
|
+
}
|
|
7981
|
+
var MERGE_RULES = {
|
|
7982
|
+
sessions: appendOnly("session_id"),
|
|
7983
|
+
commits: { scope: "merged", identity: ["sha"], edited: { row_json: RECOMPUTED } },
|
|
7984
|
+
findings: { scope: "merged", identity: ["id"], edited: { tracker_ref: SET_ONCE } },
|
|
7985
|
+
blockers: appendOnly("id"),
|
|
7986
|
+
out_of_scope_bugs: appendOnly("id"),
|
|
7987
|
+
report_absences: appendOnly("id"),
|
|
7988
|
+
task_reports: appendOnly("id"),
|
|
7989
|
+
preflight: appendOnly("run_id", "position"),
|
|
7990
|
+
dispatches: appendOnly("session_id"),
|
|
7991
|
+
changes: appendOnly("id"),
|
|
7992
|
+
skill_invocations: appendOnly("session_id", "name", "sidechain"),
|
|
7993
|
+
plan_ci: appendOnly("pr", "head_sha", "read_at"),
|
|
7994
|
+
schema_migrations: { scope: "local" },
|
|
7995
|
+
store_meta: { scope: "local" },
|
|
7996
|
+
merges: { scope: "local" },
|
|
7997
|
+
merge_conflicts: { scope: "local" }
|
|
7998
|
+
};
|
|
7999
|
+
|
|
8000
|
+
// src/effort/store/merge-union.ts
|
|
8001
|
+
import { SQLiteError } from "bun:sqlite";
|
|
8002
|
+
class UnionSchemaMismatch extends Error {
|
|
8003
|
+
constructor(message) {
|
|
8004
|
+
super(message);
|
|
8005
|
+
this.name = "UnionSchemaMismatch";
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
var UNIQUE_REFUSAL = "SQLITE_CONSTRAINT_UNIQUE";
|
|
8009
|
+
function mergedTables() {
|
|
8010
|
+
return Object.entries(MERGE_RULES).flatMap(([table, rule]) => rule.scope === "merged" ? [{ table, identity: rule.identity }] : []);
|
|
8011
|
+
}
|
|
8012
|
+
function carriedColumns(db, table) {
|
|
8013
|
+
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name: name2 }) => name2).filter((name2) => name2 !== "seq");
|
|
8014
|
+
}
|
|
8015
|
+
function refuseColumnMismatch(local, other, tables) {
|
|
8016
|
+
const faults = tables.flatMap(({ table }) => {
|
|
8017
|
+
const here = new Set(carriedColumns(local, table));
|
|
8018
|
+
const there = new Set(carriedColumns(other, table));
|
|
8019
|
+
const onlyHere = [...here].filter((column) => !there.has(column)).map((column) => `${table}.${column} only here`);
|
|
8020
|
+
const onlyThere = [...there].filter((column) => !here.has(column)).map((column) => `${table}.${column} only there`);
|
|
8021
|
+
return [...onlyHere, ...onlyThere];
|
|
8022
|
+
});
|
|
8023
|
+
if (faults.length > 0) {
|
|
8024
|
+
throw new UnionSchemaMismatch(`the two stores hold different columns (${faults.join(", ")}); bring both to the same migrations first`);
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
function valueOf(row2, column) {
|
|
8028
|
+
return row2[column] ?? null;
|
|
8029
|
+
}
|
|
8030
|
+
function byOrigin(local, table, row2) {
|
|
8031
|
+
const store = valueOf(row2, "origin_store");
|
|
8032
|
+
const seq = valueOf(row2, "origin_seq");
|
|
8033
|
+
if (store === null || seq === null)
|
|
8034
|
+
return null;
|
|
8035
|
+
return local.query(`SELECT seq FROM ${quoted(table)} WHERE origin_store = ? AND origin_seq = ?`).get(store, seq)?.seq ?? null;
|
|
8036
|
+
}
|
|
8037
|
+
function byIdentity(local, { table, identity }, row2) {
|
|
8038
|
+
const where = identity.map((column) => `${quoted(column)} IS ?`).join(" AND ");
|
|
8039
|
+
return local.query(`SELECT seq FROM ${quoted(table)} WHERE ${where} ORDER BY seq LIMIT 1`).get(...identity.map((column) => valueOf(row2, column)))?.seq ?? null;
|
|
8040
|
+
}
|
|
8041
|
+
function insertRow(local, table, columns2, row2, incomingSeq) {
|
|
8042
|
+
const columnList = columns2.map(quoted).join(", ");
|
|
8043
|
+
const placeholders = columns2.map(() => "?").join(", ");
|
|
8044
|
+
const nextSeq = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${quoted(table)})`;
|
|
8045
|
+
try {
|
|
8046
|
+
const inserted = local.query(`INSERT INTO ${quoted(table)} (seq, ${columnList}) VALUES (${nextSeq}, ${placeholders}) RETURNING seq`).get(...columns2.map((column) => valueOf(row2, column)));
|
|
8047
|
+
if (inserted === null)
|
|
8048
|
+
throw new Error(`the insert into ${table} returned no seq`);
|
|
8049
|
+
return { kind: "added", localSeq: inserted.seq };
|
|
8050
|
+
} catch (error) {
|
|
8051
|
+
if (!(error instanceof SQLiteError) || error.code !== UNIQUE_REFUSAL)
|
|
8052
|
+
throw error;
|
|
8053
|
+
return { kind: "collided", collision: { incomingSeq, reason: error.message } };
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
function placeRow(local, merged2, columns2, row2) {
|
|
8057
|
+
const incomingSeq = Number(valueOf(row2, "seq"));
|
|
8058
|
+
const origin = byOrigin(local, merged2.table, row2);
|
|
8059
|
+
if (origin !== null)
|
|
8060
|
+
return { kind: "matched", match: { localSeq: origin, incomingSeq, by: "origin" } };
|
|
8061
|
+
const identity = byIdentity(local, merged2, row2);
|
|
8062
|
+
if (identity !== null)
|
|
8063
|
+
return { kind: "matched", match: { localSeq: identity, incomingSeq, by: "identity" } };
|
|
8064
|
+
return insertRow(local, merged2.table, columns2, row2, incomingSeq);
|
|
8065
|
+
}
|
|
8066
|
+
function unionTable(local, other, merged2) {
|
|
8067
|
+
const columns2 = carriedColumns(local, merged2.table);
|
|
8068
|
+
const incoming = other.query(`SELECT seq, ${columns2.map(quoted).join(", ")} FROM ${quoted(merged2.table)} ORDER BY seq`).all();
|
|
8069
|
+
const outcomes = incoming.map((row2) => placeRow(local, merged2, columns2, row2));
|
|
8070
|
+
return {
|
|
8071
|
+
table: merged2.table,
|
|
8072
|
+
added: outcomes.flatMap((outcome) => outcome.kind === "added" ? [outcome.localSeq] : []),
|
|
8073
|
+
matched: outcomes.flatMap((outcome) => outcome.kind === "matched" ? [outcome.match] : []),
|
|
8074
|
+
collided: outcomes.flatMap((outcome) => outcome.kind === "collided" ? [outcome.collision] : [])
|
|
8075
|
+
};
|
|
8076
|
+
}
|
|
8077
|
+
function unionStores(local, other) {
|
|
8078
|
+
const tables = mergedTables();
|
|
8079
|
+
refuseColumnMismatch(local, other, tables);
|
|
8080
|
+
return local.transaction(() => tables.map((merged2) => unionTable(local, other, merged2)))();
|
|
8081
|
+
}
|
|
8082
|
+
|
|
8083
|
+
// src/effort/store/merge-conflicts.ts
|
|
8084
|
+
var SET_ONCE_FILLS = {
|
|
8085
|
+
"findings.tracker_ref": "UPDATE findings SET tracker_ref = ? WHERE seq = ? AND tracker_ref IS NULL"
|
|
8086
|
+
};
|
|
8087
|
+
var RECOMPUTED_COMPARISONS = {
|
|
8088
|
+
"commits.row_json": withoutGap
|
|
8089
|
+
};
|
|
8090
|
+
var NOT_CONTENT = new Set(["seq", "origin_store", "origin_seq"]);
|
|
8091
|
+
var INSERT_CONFLICT = `INSERT INTO merge_conflicts (merge_id, table_name, local_seq, field, incoming, recorded_at)
|
|
8092
|
+
VALUES (?, ?, ?, ?, ?, ?)`;
|
|
8093
|
+
function rowAt(db, table, columns2, seq, side) {
|
|
8094
|
+
const row2 = db.query(`SELECT seq, ${columns2.map(quoted).join(", ")} FROM ${quoted(table)} WHERE seq = ?`).get(seq);
|
|
8095
|
+
if (row2 === null)
|
|
8096
|
+
throw new Error(`the union matched ${table} row ${seq} ${side}, which that store does not hold`);
|
|
8097
|
+
return row2;
|
|
8098
|
+
}
|
|
8099
|
+
function sameValue(left, right) {
|
|
8100
|
+
if (left instanceof Uint8Array && right instanceof Uint8Array)
|
|
8101
|
+
return Buffer.from(left).equals(right);
|
|
8102
|
+
return (left ?? null) === (right ?? null);
|
|
8103
|
+
}
|
|
8104
|
+
function fillStatement(table, field) {
|
|
8105
|
+
const statement = SET_ONCE_FILLS[`${table}.${field}`];
|
|
8106
|
+
if (statement === undefined) {
|
|
8107
|
+
throw new Error(`${table}.${field} is set-once in MERGE_RULES and has no statement in SET_ONCE_FILLS`);
|
|
8108
|
+
}
|
|
8109
|
+
return statement;
|
|
8110
|
+
}
|
|
8111
|
+
function settleSetOnce(local, table, field, here, there) {
|
|
8112
|
+
const incoming = there[field] ?? null;
|
|
8113
|
+
if (incoming === null)
|
|
8114
|
+
return { skipped: true, filled: [], conflictFields: [] };
|
|
8115
|
+
if ((here[field] ?? null) !== null)
|
|
8116
|
+
return { skipped: false, filled: [], conflictFields: [field] };
|
|
8117
|
+
local.query(fillStatement(table, field)).run(incoming, Number(here.seq));
|
|
8118
|
+
return { skipped: true, filled: [field], conflictFields: [] };
|
|
8119
|
+
}
|
|
8120
|
+
function settleRecomputed(table, field, here, there) {
|
|
8121
|
+
const comparable = RECOMPUTED_COMPARISONS[`${table}.${field}`];
|
|
8122
|
+
if (comparable === undefined) {
|
|
8123
|
+
throw new Error(`${table}.${field} is recomputed in MERGE_RULES and has no entry in RECOMPUTED_COMPARISONS`);
|
|
8124
|
+
}
|
|
8125
|
+
const hereValue = here[field];
|
|
8126
|
+
const thereValue = there[field];
|
|
8127
|
+
const same = typeof hereValue === "string" && typeof thereValue === "string" && comparable(hereValue) === comparable(thereValue);
|
|
8128
|
+
return same ? { skipped: true, filled: [], conflictFields: [] } : { skipped: false, filled: [], conflictFields: [null] };
|
|
8129
|
+
}
|
|
8130
|
+
function settlePair(local, table, edited, here, there) {
|
|
8131
|
+
const differing = Object.keys(here).filter((column) => !NOT_CONTENT.has(column) && !sameValue(here[column], there[column]));
|
|
8132
|
+
if (differing.length === 0)
|
|
8133
|
+
return { skipped: true, filled: [], conflictFields: [] };
|
|
8134
|
+
if (differing.some((column) => !Object.hasOwn(edited, column)))
|
|
8135
|
+
return { skipped: false, filled: [], conflictFields: [null] };
|
|
8136
|
+
const outcomes = differing.map((field) => {
|
|
8137
|
+
if (edited[field] === SET_ONCE)
|
|
8138
|
+
return settleSetOnce(local, table, field, here, there);
|
|
8139
|
+
if (edited[field] === RECOMPUTED)
|
|
8140
|
+
return settleRecomputed(table, field, here, there);
|
|
8141
|
+
throw new Error(`${table}.${field} has the rule ${edited[field]}, which the merge does not apply`);
|
|
8142
|
+
});
|
|
8143
|
+
return {
|
|
8144
|
+
skipped: outcomes.every((outcome) => outcome.skipped),
|
|
8145
|
+
filled: outcomes.flatMap((outcome) => outcome.filled),
|
|
8146
|
+
conflictFields: outcomes.flatMap((outcome) => outcome.conflictFields)
|
|
8147
|
+
};
|
|
8148
|
+
}
|
|
8149
|
+
function settleTable(local, other, union, trail) {
|
|
8150
|
+
const rule = MERGE_RULES[union.table];
|
|
8151
|
+
if (rule?.scope !== "merged")
|
|
8152
|
+
throw new Error(`${union.table} is not a merged table in MERGE_RULES`);
|
|
8153
|
+
const columns2 = carriedColumns(local, union.table);
|
|
8154
|
+
const settled = union.matched.map((match) => {
|
|
8155
|
+
const here = rowAt(local, union.table, columns2, match.localSeq, "here");
|
|
8156
|
+
const there = rowAt(other, union.table, columns2, match.incomingSeq, "in the other store");
|
|
8157
|
+
const outcome = settlePair(local, union.table, rule.edited, here, there);
|
|
8158
|
+
const conflicts = outcome.conflictFields.map((field) => {
|
|
8159
|
+
local.query(INSERT_CONFLICT).run(trail.mergeId, union.table, match.localSeq, field, JSON.stringify(there), trail.recordedAt);
|
|
8160
|
+
return { localSeq: match.localSeq, incomingSeq: match.incomingSeq, field };
|
|
8161
|
+
});
|
|
8162
|
+
return { match, outcome, conflicts };
|
|
8163
|
+
});
|
|
8164
|
+
return {
|
|
8165
|
+
table: union.table,
|
|
8166
|
+
skipped: settled.flatMap(({ match, outcome }) => outcome.skipped ? [match.localSeq] : []),
|
|
8167
|
+
filled: settled.flatMap(({ match, outcome }) => outcome.filled.map((field) => ({ localSeq: match.localSeq, field }))),
|
|
8168
|
+
conflicts: settled.flatMap(({ conflicts }) => conflicts)
|
|
8169
|
+
};
|
|
8170
|
+
}
|
|
8171
|
+
function settleMatches(local, other, unions, trail) {
|
|
8172
|
+
return local.transaction(() => unions.map((union) => settleTable(local, other, union, trail)))();
|
|
8173
|
+
}
|
|
7983
8174
|
|
|
7984
8175
|
// src/effort/store/migrate.ts
|
|
7985
8176
|
import { existsSync as existsSync8 } from "fs";
|
|
@@ -7988,7 +8179,7 @@ var LIVE2 = "live";
|
|
|
7988
8179
|
function asidePaths2(path, first, stamp) {
|
|
7989
8180
|
return { parallelPath: `${path}.migrate-${stamp}`, backupPath: `${path}.before-${first}-${stamp}.bak` };
|
|
7990
8181
|
}
|
|
7991
|
-
function liveLoop(path, options) {
|
|
8182
|
+
function liveLoop(path, options, nothingDone = "Nothing was migrated.") {
|
|
7992
8183
|
const root = storeProjectRoot(path);
|
|
7993
8184
|
if (root === null)
|
|
7994
8185
|
return;
|
|
@@ -7996,7 +8187,7 @@ function liveLoop(path, options) {
|
|
|
7996
8187
|
try {
|
|
7997
8188
|
records = readSessions(root, { isAlive: options.isAlive ?? isPidAlive });
|
|
7998
8189
|
} catch (error) {
|
|
7999
|
-
throw new RebuildRefusal(`the loop's run records cannot be read, so a live loop on ${path} cannot be ruled out` + ` (${messageOf(error)}).
|
|
8190
|
+
throw new RebuildRefusal(`the loop's run records cannot be read, so a live loop on ${path} cannot be ruled out` + ` (${messageOf(error)}). ${nothingDone}`);
|
|
8000
8191
|
}
|
|
8001
8192
|
return records.find((record) => record.state === "running" || record.state === "paused");
|
|
8002
8193
|
}
|
|
@@ -8065,109 +8256,629 @@ function migrateStore(options) {
|
|
|
8065
8256
|
const status = dryRun ? "would-migrate" : "migrated";
|
|
8066
8257
|
return { ...found, ...planned, ...built, status, backupPath };
|
|
8067
8258
|
}
|
|
8068
|
-
|
|
8069
|
-
// src/
|
|
8070
|
-
var
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8259
|
+
|
|
8260
|
+
// src/effort/store/merge-store.ts
|
|
8261
|
+
var MOVE_TO_SQLITE = "rafa effort move --to=sqlite";
|
|
8262
|
+
|
|
8263
|
+
class MergeRefusal extends RebuildRefusal {
|
|
8264
|
+
reason;
|
|
8265
|
+
constructor(reason, message) {
|
|
8266
|
+
super(message);
|
|
8267
|
+
this.name = "MergeRefusal";
|
|
8268
|
+
this.reason = reason;
|
|
8269
|
+
}
|
|
8270
|
+
}
|
|
8271
|
+
var LIVE3 = "live";
|
|
8272
|
+
var MERGES_TABLE = "merges";
|
|
8273
|
+
var INSERT_MERGE = `INSERT INTO merges (id, other_store, merged_at, rows_added, rows_skipped, rows_in_conflict)
|
|
8274
|
+
VALUES (?, ?, ?, ?, ?, ?)`;
|
|
8275
|
+
var NOTHING_MERGED = "Nothing was merged.";
|
|
8276
|
+
function refuseNdjson(backend) {
|
|
8277
|
+
if (backend !== "ndjson")
|
|
8278
|
+
return;
|
|
8279
|
+
throw new MergeRefusal("ndjson", `REFUSED \u2014 this project keeps its sessions and commits as NDJSON (store: ndjson), and a merge needs the SQLite store. ${NOTHING_MERGED}` + ` Next safe step: ${MOVE_TO_SQLITE}`);
|
|
8280
|
+
}
|
|
8281
|
+
function refuseMissing(path, which) {
|
|
8282
|
+
if (existsSync9(path))
|
|
8283
|
+
return;
|
|
8284
|
+
throw new MergeRefusal("missing", `REFUSED \u2014 ${which} ${path} is not there. ${NOTHING_MERGED}`);
|
|
8285
|
+
}
|
|
8286
|
+
function refuseDamaged(path) {
|
|
8287
|
+
let answers;
|
|
8288
|
+
try {
|
|
8289
|
+
const db = new Database4(path, { readonly: true });
|
|
8290
|
+
try {
|
|
8291
|
+
answers = db.query("PRAGMA integrity_check").all().map((row2) => row2.integrity_check);
|
|
8292
|
+
} finally {
|
|
8293
|
+
db.close();
|
|
8294
|
+
}
|
|
8295
|
+
} catch (error) {
|
|
8296
|
+
if (!(error instanceof SQLiteError2))
|
|
8297
|
+
throw error;
|
|
8298
|
+
throw new MergeRefusal("damaged", `REFUSED \u2014 ${path} cannot be read as a SQLite store (${error.message}). ${NOTHING_MERGED}` + " Take a consistent copy on its device with rafa effort copy, and merge that.");
|
|
8299
|
+
}
|
|
8300
|
+
if (answers.length === 1 && answers[0] === "ok")
|
|
8301
|
+
return;
|
|
8302
|
+
throw new MergeRefusal("damaged", `REFUSED \u2014 ${path} failed integrity_check: ${answers.join("; ")}. ${NOTHING_MERGED}` + " Take a consistent copy on its device with rafa effort copy, and merge that.");
|
|
8303
|
+
}
|
|
8304
|
+
function writablePlan(path, migrations) {
|
|
8305
|
+
const plan2 = planSchema(readOnlySchema(path), sqliteCatalogue(migrations), "write", "open");
|
|
8306
|
+
if (plan2.verdict === "refuse")
|
|
8307
|
+
throw new MergeRefusal("schema", `REFUSED \u2014 ${path}: ${plan2.message}`);
|
|
8308
|
+
return plan2;
|
|
8309
|
+
}
|
|
8310
|
+
function recordedMeta(path) {
|
|
8311
|
+
const db = new Database4(path, { readonly: true });
|
|
8312
|
+
try {
|
|
8313
|
+
const hasTable = db.query("SELECT count(*) AS n FROM sqlite_master WHERE type = 'table' AND name = 'store_meta'").get()?.n === 1;
|
|
8314
|
+
return hasTable ? readStoreMeta(db) : null;
|
|
8315
|
+
} finally {
|
|
8316
|
+
db.close();
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
function refuseOtherProject(options) {
|
|
8320
|
+
const other = recordedMeta(options.otherPath);
|
|
8321
|
+
const here = recordedMeta(options.path)?.projectRootCommit ?? (options.readProject ?? readProjectIdentity)(dirname3(options.path)).rootCommit;
|
|
8322
|
+
const there = other?.projectRootCommit ?? null;
|
|
8323
|
+
if (here !== null && there !== null && here !== there) {
|
|
8324
|
+
throw new MergeRefusal("other-project", `REFUSED \u2014 ${options.otherPath} is the effort store of another project (root commit ${there});` + ` this store's project has root commit ${here}. ${NOTHING_MERGED}`);
|
|
8325
|
+
}
|
|
8326
|
+
return other?.storeId ?? null;
|
|
8327
|
+
}
|
|
8328
|
+
function refuseLiveLoop3(path, options) {
|
|
8329
|
+
const loop = liveLoop(path, options, NOTHING_MERGED);
|
|
8330
|
+
if (loop === undefined)
|
|
8331
|
+
return;
|
|
8332
|
+
throw new MergeRefusal("live-loop", `REFUSED \u2014 loop ${loop.sessionId} (pid ${String(loop.pid)}, plan ${loop.planStub ?? loop.plan}) is running on this store,` + ` and swapping the merged store in under it would lose what it writes. ${NOTHING_MERGED}` + " Finish or stop that loop, then run it again.");
|
|
8333
|
+
}
|
|
8334
|
+
function openOther(otherPath, plan2, stamp) {
|
|
8335
|
+
const needs = writeNames(plan2);
|
|
8336
|
+
if (needs.length === 0) {
|
|
8337
|
+
const db = new Database4(otherPath, { readonly: true });
|
|
8338
|
+
return { db, broughtForward: [], release: () => db.close() };
|
|
8339
|
+
}
|
|
8340
|
+
const dir = mkdtempSync2(join20(tmpdir2(), "rafa-merge-other-"));
|
|
8341
|
+
const release = (db) => {
|
|
8342
|
+
db?.close();
|
|
8343
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
8344
|
+
};
|
|
8345
|
+
try {
|
|
8346
|
+
const copyPath = join20(dir, "other.sqlite");
|
|
8347
|
+
vacuumInto(otherPath, copyPath);
|
|
8348
|
+
const db = new Database4(copyPath, { readwrite: true });
|
|
8349
|
+
try {
|
|
8350
|
+
bringForward(db, copyPath, "write", "open", { ...stamp, builtAside: true });
|
|
8351
|
+
} catch (error) {
|
|
8352
|
+
release(db);
|
|
8353
|
+
throw error;
|
|
8354
|
+
}
|
|
8355
|
+
return { db, broughtForward: needs, release: () => release(db) };
|
|
8356
|
+
} catch (error) {
|
|
8357
|
+
release();
|
|
8358
|
+
throw error;
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8361
|
+
function mergeInto(db, other, mergeId, otherStore, mergedAt) {
|
|
8362
|
+
return db.transaction(() => {
|
|
8363
|
+
const unions = unionStores(db, other);
|
|
8364
|
+
const settlements = settleMatches(db, other, unions, { mergeId, recordedAt: mergedAt });
|
|
8365
|
+
const gaps = recomputeCommitGaps(db, unions.find(({ table }) => table === "commits")?.added ?? []);
|
|
8366
|
+
const tables = unions.map((union, index) => {
|
|
8367
|
+
const settled = settlements[index];
|
|
8368
|
+
return {
|
|
8369
|
+
table: union.table,
|
|
8370
|
+
added: union.added.length,
|
|
8371
|
+
skipped: settled?.skipped.length ?? 0,
|
|
8372
|
+
inConflict: settled?.conflicts.length ?? 0,
|
|
8373
|
+
filled: settled?.filled.length ?? 0,
|
|
8374
|
+
collided: union.collided.length
|
|
8375
|
+
};
|
|
8376
|
+
});
|
|
8377
|
+
const sum = (field) => tables.reduce((total, entry) => total + entry[field], 0);
|
|
8378
|
+
const totals = { rowsAdded: sum("added"), rowsSkipped: sum("skipped"), rowsInConflict: sum("inConflict") };
|
|
8379
|
+
db.query(INSERT_MERGE).run(mergeId, otherStore, mergedAt, totals.rowsAdded, totals.rowsSkipped, totals.rowsInConflict);
|
|
8380
|
+
return { tables, ...totals, gapsRewritten: gaps.length };
|
|
8381
|
+
})();
|
|
8382
|
+
}
|
|
8383
|
+
function addedRows(merged2) {
|
|
8384
|
+
return {
|
|
8385
|
+
...Object.fromEntries(merged2.tables.map(({ table, added }) => [table, added])),
|
|
8386
|
+
[MERGES_TABLE]: 1,
|
|
8387
|
+
merge_conflicts: merged2.rowsInConflict
|
|
8388
|
+
};
|
|
8389
|
+
}
|
|
8390
|
+
function buildMerged(path, parallelPath, other, merge) {
|
|
8391
|
+
vacuumInto(path, parallelPath);
|
|
8392
|
+
const db = new Database4(parallelPath, { readwrite: true });
|
|
8393
|
+
try {
|
|
8394
|
+
bringForward(db, parallelPath, "write", "open", { ...merge.stamp, builtAside: true });
|
|
8395
|
+
const merged2 = mergeInto(db, other, merge.id, merge.otherStore, merge.stamp.now().toISOString());
|
|
8396
|
+
db.run(`ATTACH DATABASE ? AS ${LIVE3}`, [path]);
|
|
8397
|
+
const live = new Set(tableNames(db, LIVE3));
|
|
8398
|
+
checkedCounts(db, tableNames(db, "main").filter((table) => live.has(table)), LIVE3, addedRows(merged2));
|
|
8399
|
+
db.run(`DETACH DATABASE ${LIVE3}`);
|
|
8400
|
+
refuseCorrupt(db);
|
|
8401
|
+
refuseUnusable(db, parallelPath, merge.stamp.migrations);
|
|
8402
|
+
return merged2;
|
|
8403
|
+
} finally {
|
|
8404
|
+
db.close();
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
function refuseUnmergeable(options, migrations) {
|
|
8408
|
+
const { path, otherPath } = options;
|
|
8409
|
+
refuseNdjson(options.backend);
|
|
8410
|
+
refuseMissing(path, "this store");
|
|
8411
|
+
refuseMissing(otherPath, "the other store");
|
|
8412
|
+
refuseInFlight(path);
|
|
8413
|
+
refuseInFlight(otherPath);
|
|
8414
|
+
refuseDamaged(otherPath);
|
|
8415
|
+
const otherPlan = writablePlan(otherPath, migrations);
|
|
8416
|
+
const localPlan = writablePlan(path, migrations);
|
|
8417
|
+
const otherStore = refuseOtherProject(options);
|
|
8418
|
+
if (!options.dryRun) {
|
|
8419
|
+
refuseUnownedDevelopmentWrite(path, [...writeNames(localPlan), MERGES_TABLE], options);
|
|
8420
|
+
refuseLiveLoop3(path, options);
|
|
8421
|
+
}
|
|
8422
|
+
return { otherPlan, otherStore };
|
|
8423
|
+
}
|
|
8424
|
+
function mergeStore(options) {
|
|
8425
|
+
const { path, otherPath, dryRun, stamp } = options;
|
|
8426
|
+
guardTestProcess(path);
|
|
8427
|
+
guardTestProcess(otherPath);
|
|
8428
|
+
const migrations = options.migrations ?? SQLITE_MIGRATIONS;
|
|
8429
|
+
const { otherPlan, otherStore } = refuseUnmergeable(options, migrations);
|
|
8430
|
+
const mergeStamp = {
|
|
8431
|
+
migrations,
|
|
8432
|
+
appliedBy: appliedByName(options.identity ?? readRuntimeIdentity()),
|
|
8433
|
+
now: options.now ?? (() => new Date)
|
|
8434
|
+
};
|
|
8435
|
+
const mergeId = (options.newMergeId ?? randomUUID2)();
|
|
8436
|
+
const other = openOther(otherPath, otherPlan, mergeStamp);
|
|
8437
|
+
try {
|
|
8438
|
+
const { built, backupPath } = rebuildAside({
|
|
8439
|
+
path,
|
|
8440
|
+
parallelPath: `${path}.merge-${stamp}`,
|
|
8441
|
+
backupPath: `${path}.before-merge-${stamp}.bak`,
|
|
8442
|
+
dryRun,
|
|
8443
|
+
build: (parallelPath) => buildMerged(path, parallelPath, other.db, { id: mergeId, otherStore, stamp: mergeStamp })
|
|
8444
|
+
});
|
|
8445
|
+
const status = dryRun ? "would-merge" : "merged";
|
|
8446
|
+
return { path, otherPath, status, mergeId, otherStore, otherBroughtForward: other.broughtForward, ...built, backupPath };
|
|
8447
|
+
} finally {
|
|
8448
|
+
other.release();
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
|
|
8452
|
+
// src/commands/effort/merge.ts
|
|
8453
|
+
var COMMAND_NAME7 = "rafa effort merge";
|
|
8454
|
+
var MERGE_USAGE = "rafa effort merge <file> [--dry-run]";
|
|
8455
|
+
var DRY_RUN_FLAG3 = "dry-run";
|
|
8456
|
+
var REFUSED_EXIT2 = 1;
|
|
8457
|
+
var UNMERGEABLE_EXIT = 2;
|
|
8458
|
+
var UNMERGEABLE_REASONS = new Set(["other-project", "ndjson"]);
|
|
8459
|
+
function tableLine(entry) {
|
|
8460
|
+
const extra = [
|
|
8461
|
+
...entry.filled === 0 ? [] : [`${String(entry.filled)} filled`],
|
|
8462
|
+
...entry.collided === 0 ? [] : [`${String(entry.collided)} refused by another unique key, kept in the other file only`]
|
|
8463
|
+
];
|
|
8464
|
+
const counts = [
|
|
8465
|
+
`${String(entry.added)} added`,
|
|
8466
|
+
`${String(entry.skipped)} skipped`,
|
|
8467
|
+
`${String(entry.inConflict)} in conflict`,
|
|
8468
|
+
...extra
|
|
8469
|
+
];
|
|
8470
|
+
return ` ${entry.table}: ${counts.join(", ")}`;
|
|
8471
|
+
}
|
|
8472
|
+
function closingLine(result) {
|
|
8473
|
+
if (result.status === "would-merge") {
|
|
8474
|
+
return "\uD83D\uDD0D Dry run: the merged store was built beside it, checked (row counts, integrity_check, this rafa's" + ` schema plan) and deleted. Run \`${COMMAND_NAME7} ${result.otherPath}\` to swap it in; the original would be` + ` kept whole at ${result.path}.before-merge-<stamp>.bak.`;
|
|
8475
|
+
}
|
|
8476
|
+
return `\u2705 Merged. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`;
|
|
8477
|
+
}
|
|
8478
|
+
function renderMerge(result) {
|
|
8479
|
+
const other = result.otherStore === null ? "a store that names no origin" : `store ${result.otherStore}`;
|
|
8480
|
+
const forward = result.otherBroughtForward.length === 0 ? [] : [`A copy of the other store was brought forward through ${result.otherBroughtForward.join(", ")}; the file itself was only read.`];
|
|
8481
|
+
return [
|
|
8482
|
+
`Merges ${result.otherPath} (${other}) into ${result.path}, as merge ${result.mergeId}.`,
|
|
8483
|
+
...forward,
|
|
8484
|
+
...result.tables.map(tableLine),
|
|
8485
|
+
`Total: ${String(result.rowsAdded)} added, ${String(result.rowsSkipped)} skipped,` + ` ${String(result.rowsInConflict)} in conflict; ${String(result.gapsRewritten)} commit gaps recomputed.`,
|
|
8486
|
+
closingLine(result)
|
|
8487
|
+
];
|
|
8488
|
+
}
|
|
8489
|
+
function storePath(context, root) {
|
|
8490
|
+
try {
|
|
8491
|
+
return join21(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8492
|
+
} catch (error) {
|
|
8493
|
+
throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${messageOf(error)}`);
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
function refusalExit(error) {
|
|
8497
|
+
if (error instanceof MergeRefusal) {
|
|
8498
|
+
const code = UNMERGEABLE_REASONS.has(error.reason) ? UNMERGEABLE_EXIT : REFUSED_EXIT2;
|
|
8499
|
+
return new CommandExit(code, `\u274C ${COMMAND_NAME7}: ${error.message}`);
|
|
8500
|
+
}
|
|
8501
|
+
const known = error instanceof RebuildRefusal || error instanceof DevelopmentBuildRefusedError || error instanceof UnionSchemaMismatch;
|
|
8502
|
+
return known ? new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${error.message}`) : null;
|
|
8503
|
+
}
|
|
8504
|
+
function probeSeams(seams) {
|
|
8505
|
+
return {
|
|
8506
|
+
...seams.isAlive === undefined ? {} : { isAlive: seams.isAlive },
|
|
8507
|
+
...seams.identity === undefined ? {} : { identity: seams.identity },
|
|
8508
|
+
...seams.migrations === undefined ? {} : { migrations: seams.migrations },
|
|
8509
|
+
...seams.newMergeId === undefined ? {} : { newMergeId: seams.newMergeId },
|
|
8510
|
+
...seams.readProject === undefined ? {} : { readProject: seams.readProject }
|
|
8511
|
+
};
|
|
8512
|
+
}
|
|
8513
|
+
function runMerge(context, seams) {
|
|
8514
|
+
const file = expectOneArgument(context.args, MERGE_USAGE);
|
|
8515
|
+
const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MERGE_USAGE}`);
|
|
8516
|
+
const project = requireProject(context, COMMAND_NAME7);
|
|
8517
|
+
const path = storePath(context, project.root);
|
|
8518
|
+
const backend = resolveProjectConfig(project, COMMAND_NAME7, (message) => context.output.warn(message)).store;
|
|
8519
|
+
const now = seams.now ?? (() => new Date);
|
|
8520
|
+
const otherPath = resolve8((seams.cwd ?? (() => process.cwd()))(), file);
|
|
8521
|
+
let result;
|
|
8522
|
+
try {
|
|
8523
|
+
result = mergeStore({ path, otherPath, backend, dryRun, stamp: fileStamp(now()), now, env: context.env, ...probeSeams(seams) });
|
|
8524
|
+
} catch (error) {
|
|
8525
|
+
throw refusalExit(error) ?? error;
|
|
8526
|
+
}
|
|
8527
|
+
if (context.outputMode === "json") {
|
|
8528
|
+
context.output.result(result);
|
|
8529
|
+
return;
|
|
8530
|
+
}
|
|
8531
|
+
for (const line of renderMerge(result))
|
|
8532
|
+
context.output.info(line);
|
|
8533
|
+
}
|
|
8534
|
+
function createMergeCommand(seams = {}) {
|
|
8535
|
+
const command = {
|
|
8536
|
+
name: "effort merge",
|
|
8537
|
+
subject: "effort",
|
|
8538
|
+
action: "merge",
|
|
8539
|
+
summary: "merge another device's effort store file into this one, keeping every row and the original",
|
|
8540
|
+
description: "Adds to `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, every row another" + " device's store file holds that this one lacks. A row matches on its origin pair, or on its table's" + " identity columns when either side has no origin; an equal pair is skipped, a set-once field NULL here" + " is filled, and any other difference is kept on both sides and recorded in `merge_conflicts`. The gap" + " of each commit brought in, and of the commit after it, is recomputed. The other file is only read." + " The merged store is built beside this one, checked (each table's count plus the rows added," + " integrity_check, the schema plan), and swapped in with the original kept whole as" + " `effort.sqlite.before-merge-<stamp>.bak`. It prints the rows added, skipped and in conflict per table" + " and the backup's name. Exit code 2 for another project's store, and for a `store: ndjson` project," + " naming `rafa effort move --to=sqlite`; exit code 1, changing nothing, for a missing or damaged file," + " a write in flight, a loop session running or paused, and a development build over a store it does not" + " own. With `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8541
|
+
args: [
|
|
8542
|
+
{
|
|
8543
|
+
name: "file",
|
|
8544
|
+
description: "The other device's `effort.sqlite`, a consistent copy such as `rafa effort copy` makes;" + " a relative path is read from the directory the command runs in.",
|
|
8545
|
+
type: "string",
|
|
8546
|
+
required: true
|
|
8547
|
+
}
|
|
8548
|
+
],
|
|
8549
|
+
flags: [
|
|
8550
|
+
{
|
|
8551
|
+
name: DRY_RUN_FLAG3,
|
|
8552
|
+
description: "Build and check the merged store, print what it would add, then delete it. This store is" + " only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8553
|
+
type: "boolean"
|
|
8554
|
+
}
|
|
8555
|
+
],
|
|
8556
|
+
examples: [
|
|
8557
|
+
{
|
|
8558
|
+
cmd: "rafa effort merge ../laptop-effort/effort.sqlite --dry-run",
|
|
8559
|
+
note: "Prints what the merge would add, skip and record as conflicts, changing nothing."
|
|
8560
|
+
},
|
|
8561
|
+
{
|
|
8562
|
+
cmd: "rafa effort merge ../laptop-effort/effort.sqlite",
|
|
8563
|
+
note: "Swaps the merged store in and prints where the original was kept."
|
|
8564
|
+
}
|
|
8565
|
+
],
|
|
8566
|
+
outputs: ["text", "json"],
|
|
8567
|
+
run: async (context) => {
|
|
8568
|
+
runMerge(context, seams);
|
|
8569
|
+
await Promise.resolve();
|
|
8570
|
+
}
|
|
8571
|
+
};
|
|
8572
|
+
return Object.freeze(command);
|
|
8573
|
+
}
|
|
8574
|
+
var merge_default = createMergeCommand();
|
|
8575
|
+
|
|
8576
|
+
// src/commands/effort/migrate.ts
|
|
8577
|
+
import { join as join22 } from "path";
|
|
8578
|
+
var COMMAND_NAME8 = "rafa effort migrate";
|
|
8579
|
+
var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
|
|
8580
|
+
var DRY_RUN_FLAG4 = "dry-run";
|
|
8581
|
+
var REFUSED_EXIT3 = 1;
|
|
8582
|
+
function builtLines(result) {
|
|
8583
|
+
const rows = result.counted.reduce((sum, table) => sum + table.rows, 0);
|
|
8584
|
+
const breaking = result.breaking.length === 0 ? "Every one is additive." : `${result.breaking.join(", ")} breaks older runtimes: a rafa that does not know it refuses the store once it runs.`;
|
|
8585
|
+
return [
|
|
8586
|
+
`Applies ${result.applying.join(", ")} to ${result.path}, logged as applied by ${result.appliedBy}. ${breaking}`,
|
|
8587
|
+
`Row counts match in ${String(result.counted.length)} tables, ${String(rows)} rows; integrity_check ok;` + ` schema version ${String(result.userVersion)} after.`
|
|
8588
|
+
];
|
|
8589
|
+
}
|
|
8590
|
+
function renderMigrate(result) {
|
|
8591
|
+
switch (result.status) {
|
|
8592
|
+
case "missing":
|
|
8593
|
+
return [`No effort store at ${result.path}: nothing to migrate.`];
|
|
8594
|
+
case "current":
|
|
8595
|
+
return [`\u2705 ${result.path} is current: no migration is pending. Nothing to migrate.`];
|
|
8596
|
+
case "would-migrate":
|
|
8597
|
+
return [
|
|
8598
|
+
...builtLines(result),
|
|
8599
|
+
"\uD83D\uDD0D Dry run: the migrated store was built beside it, checked (row counts, integrity_check, this rafa's" + " schema plan) and deleted. Run `rafa effort migrate` to swap it in."
|
|
8600
|
+
];
|
|
8601
|
+
case "migrated":
|
|
8602
|
+
return [
|
|
8603
|
+
...builtLines(result),
|
|
8604
|
+
`\u2705 Migrated. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`
|
|
8605
|
+
];
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
function storePath2(context, root) {
|
|
8609
|
+
try {
|
|
8610
|
+
return join22(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8611
|
+
} catch (error) {
|
|
8612
|
+
throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8}: ${messageOf(error)}`);
|
|
8613
|
+
}
|
|
8614
|
+
}
|
|
8615
|
+
function runMigrate(context, seams) {
|
|
8616
|
+
expectNoArgument(context.args, MIGRATE_USAGE);
|
|
8617
|
+
const dryRun = readSwitch(DRY_RUN_FLAG4, context.flags[DRY_RUN_FLAG4], `Usage: ${MIGRATE_USAGE}`);
|
|
8618
|
+
const path = storePath2(context, requireProject(context, COMMAND_NAME8).root);
|
|
8619
|
+
const now = seams.now ?? (() => new Date);
|
|
8620
|
+
let result;
|
|
8621
|
+
try {
|
|
8622
|
+
result = migrateStore({
|
|
8623
|
+
path,
|
|
8624
|
+
dryRun,
|
|
8625
|
+
stamp: fileStamp(now()),
|
|
8626
|
+
now,
|
|
8627
|
+
env: context.env,
|
|
8628
|
+
...seams.identity === undefined ? {} : { identity: seams.identity },
|
|
8629
|
+
...seams.isAlive === undefined ? {} : { isAlive: seams.isAlive },
|
|
8630
|
+
...seams.migrations === undefined ? {} : { migrations: seams.migrations }
|
|
8631
|
+
});
|
|
8632
|
+
} catch (error) {
|
|
8633
|
+
if (error instanceof RebuildRefusal || error instanceof DevelopmentBuildRefusedError) {
|
|
8634
|
+
throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8}: ${error.message}`);
|
|
8635
|
+
}
|
|
8636
|
+
throw error;
|
|
8637
|
+
}
|
|
8638
|
+
if (context.outputMode === "json") {
|
|
8639
|
+
context.output.result(result);
|
|
8640
|
+
return;
|
|
8641
|
+
}
|
|
8642
|
+
for (const line of renderMigrate(result))
|
|
8643
|
+
context.output.info(line);
|
|
8644
|
+
}
|
|
8645
|
+
function createMigrateCommand(seams = {}) {
|
|
8646
|
+
const command = {
|
|
8647
|
+
name: "effort migrate",
|
|
8648
|
+
subject: "effort",
|
|
8649
|
+
action: "migrate",
|
|
8650
|
+
summary: "apply the effort store's pending migrations, one that breaks older runtimes included, keeping the original",
|
|
8651
|
+
description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then renames the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8652
|
+
args: [],
|
|
8653
|
+
flags: [
|
|
8654
|
+
{
|
|
8655
|
+
name: DRY_RUN_FLAG4,
|
|
8656
|
+
description: "Build and check the migrated store, print what it applies, then delete it. The live store" + " is only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8657
|
+
type: "boolean"
|
|
8658
|
+
}
|
|
8659
|
+
],
|
|
8660
|
+
examples: [
|
|
8661
|
+
{
|
|
8662
|
+
cmd: "rafa effort migrate --dry-run",
|
|
8663
|
+
note: "Prints what the migration applies and how it checked, changing nothing."
|
|
8664
|
+
},
|
|
8665
|
+
{
|
|
8666
|
+
cmd: "rafa effort migrate",
|
|
8667
|
+
note: "Swaps the migrated store in and prints where the original was kept."
|
|
8668
|
+
}
|
|
8669
|
+
],
|
|
8670
|
+
outputs: ["text", "json"],
|
|
8671
|
+
run: async (context) => {
|
|
8672
|
+
runMigrate(context, seams);
|
|
8673
|
+
await Promise.resolve();
|
|
8674
|
+
}
|
|
8675
|
+
};
|
|
8676
|
+
return Object.freeze(command);
|
|
8677
|
+
}
|
|
8678
|
+
var migrate_default = createMigrateCommand();
|
|
8679
|
+
|
|
8680
|
+
// src/effort/store/move.ts
|
|
8681
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
8682
|
+
var MOVED_KINDS = Object.freeze(["sessions", "commits"]);
|
|
8683
|
+
var MOVE_TARGET = "sqlite";
|
|
8684
|
+
var STORE_LINE = `store: ${MOVE_TARGET}`;
|
|
8685
|
+
var STORE_KEY = /^store\s*:/u;
|
|
8686
|
+
var COMMENTED_STORE_KEY = /^#\s*store\s*:/u;
|
|
8687
|
+
|
|
8688
|
+
class MoveRefusal extends Error {
|
|
8689
|
+
reason;
|
|
8690
|
+
constructor(reason, message) {
|
|
8691
|
+
super(message);
|
|
8692
|
+
this.name = "MoveRefusal";
|
|
8693
|
+
this.reason = reason;
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
function namesStore(text) {
|
|
8697
|
+
let document;
|
|
8698
|
+
try {
|
|
8699
|
+
document = Bun.YAML.parse(text);
|
|
8700
|
+
} catch {
|
|
8701
|
+
return false;
|
|
8702
|
+
}
|
|
8703
|
+
return isMapping(document) && Object.hasOwn(document, "store");
|
|
8704
|
+
}
|
|
8705
|
+
function withSqliteStoreSetting(text) {
|
|
8706
|
+
const lines2 = text.split(`
|
|
8707
|
+
`);
|
|
8708
|
+
const at = lines2.findIndex((line) => STORE_KEY.test(line));
|
|
8709
|
+
const commented = lines2.findIndex((line) => COMMENTED_STORE_KEY.test(line));
|
|
8710
|
+
const target = at >= 0 ? at : commented;
|
|
8711
|
+
if (target >= 0)
|
|
8712
|
+
return [...lines2.slice(0, target), STORE_LINE, ...lines2.slice(target + 1)].join(`
|
|
8713
|
+
`);
|
|
8714
|
+
if (namesStore(text))
|
|
8715
|
+
return null;
|
|
8716
|
+
const body = lines2.at(-1) === "" ? lines2.slice(0, -1) : lines2;
|
|
8717
|
+
return [...body, STORE_LINE, ""].join(`
|
|
8718
|
+
`);
|
|
8719
|
+
}
|
|
8720
|
+
function plannedConfig(path) {
|
|
8721
|
+
let text;
|
|
8722
|
+
try {
|
|
8723
|
+
text = readFileSync8(path, "utf8");
|
|
8724
|
+
} catch (error) {
|
|
8725
|
+
throw new MoveRefusal("config", `${path} could not be read: ${messageOf(error)}. Nothing was moved.`);
|
|
8726
|
+
}
|
|
8727
|
+
const refuse = (why) => new MoveRefusal("config", `${path} ${why}, so this move cannot set ${STORE_LINE}. Nothing was moved. Set it by hand, then run the move again.`);
|
|
8728
|
+
try {
|
|
8729
|
+
if (parseConfigText(text, path).values.store === MOVE_TARGET)
|
|
8730
|
+
return null;
|
|
8731
|
+
} catch (error) {
|
|
8732
|
+
throw refuse(`cannot be read as a config (${messageOf(error)})`);
|
|
8733
|
+
}
|
|
8734
|
+
const written = withSqliteStoreSetting(text);
|
|
8735
|
+
if (written === null)
|
|
8736
|
+
throw refuse("spells store in a shape this command does not edit");
|
|
8737
|
+
let read;
|
|
8738
|
+
try {
|
|
8739
|
+
read = parseConfigText(written, path).values.store;
|
|
8740
|
+
} catch (error) {
|
|
8741
|
+
throw refuse(`would not parse once edited (${messageOf(error)})`);
|
|
8742
|
+
}
|
|
8743
|
+
if (read !== MOVE_TARGET)
|
|
8744
|
+
throw refuse(`would read store as ${String(read)} once edited`);
|
|
8745
|
+
return written;
|
|
8746
|
+
}
|
|
8747
|
+
function keyedRows(kind, rows) {
|
|
8748
|
+
const project = EFFORT_KEY_PROJECTIONS[kind];
|
|
8749
|
+
const keyed = rows.filter((row2) => project(row2) !== null);
|
|
8750
|
+
return { keyed, unkeyed: rows.length - keyed.length };
|
|
8751
|
+
}
|
|
8752
|
+
function checkCounts(kind, before, added, read, after) {
|
|
8753
|
+
const missing = read.filter((key) => !after.has(key));
|
|
8754
|
+
if (after.size === before + added && missing.length === 0)
|
|
8755
|
+
return;
|
|
8756
|
+
throw new MoveRefusal("count", `the ${kind} check failed: the SQLite store held ${String(before)}, ${String(added)} were appended, and it now holds` + ` ${String(after.size)}, missing ${String(missing.length)} of the keys read. The config was not changed, so the` + " project still reads its NDJSON files.");
|
|
8757
|
+
}
|
|
8758
|
+
function moveKind(kind, source, target) {
|
|
8759
|
+
const reading = source.readRows(kind);
|
|
8760
|
+
const { keyed, unkeyed } = keyedRows(kind, reading.rows);
|
|
8761
|
+
const project = EFFORT_KEY_PROJECTIONS[kind];
|
|
8762
|
+
const before = target.keys(kind).size;
|
|
8763
|
+
const appended = target.append(kind, keyed);
|
|
8764
|
+
const keys = keyed.map((row2) => project(row2) ?? "");
|
|
8765
|
+
checkCounts(kind, before, appended.appended, keys, target.keys(kind));
|
|
8766
|
+
return {
|
|
8767
|
+
kind,
|
|
8768
|
+
from: source.path(kind),
|
|
8769
|
+
read: keyed.length,
|
|
8770
|
+
added: appended.appended,
|
|
8771
|
+
skipped: appended.skipped,
|
|
8772
|
+
unkeyed,
|
|
8773
|
+
unparsed: reading.unparsedLineCount
|
|
8774
|
+
};
|
|
8775
|
+
}
|
|
8776
|
+
function writeConfig(path, text) {
|
|
8777
|
+
try {
|
|
8778
|
+
writeFileSync2(path, text, "utf8");
|
|
8779
|
+
} catch (error) {
|
|
8780
|
+
throw new MoveRefusal("config", `${path} could not be written (${messageOf(error)}). The rows were moved and checked; set ${STORE_LINE}` + " there by hand, or run the move again, which adds nothing.");
|
|
8781
|
+
}
|
|
8782
|
+
}
|
|
8783
|
+
function moveToSqlite(options) {
|
|
8784
|
+
const source = options.source ?? openNdjsonStore(options.root);
|
|
8785
|
+
const target = options.target ?? openSqliteStore(options.root);
|
|
8786
|
+
const configPath = configFilePath(options.root);
|
|
8787
|
+
const config = plannedConfig(configPath);
|
|
8788
|
+
const kinds = MOVED_KINDS.map((kind) => moveKind(kind, source, target));
|
|
8789
|
+
if (config !== null)
|
|
8790
|
+
writeConfig(configPath, config);
|
|
8791
|
+
return { to: target.path("sessions"), kinds, configPath, configChanged: config !== null };
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
// src/commands/effort/move.ts
|
|
8795
|
+
var COMMAND_NAME9 = "rafa effort move";
|
|
8796
|
+
var MOVE_USAGE = MOVE_TO_SQLITE;
|
|
8797
|
+
var TO_FLAG2 = "to";
|
|
8798
|
+
var REFUSED_EXIT4 = 1;
|
|
8799
|
+
function readTarget2(context) {
|
|
8800
|
+
const value = context.flags[TO_FLAG2];
|
|
8801
|
+
if (value === MOVE_TARGET)
|
|
8802
|
+
return;
|
|
8803
|
+
const found = value === undefined ? "no --to" : `--${TO_FLAG2}=${String(value)}`;
|
|
8804
|
+
throw new CommandExit(REFUSED_EXIT4, `\u274C ${COMMAND_NAME9}: ${found}; a move writes to ${MOVE_TARGET} and nothing else. Nothing was moved.
|
|
8805
|
+
Usage: ${MOVE_USAGE}`);
|
|
8806
|
+
}
|
|
8807
|
+
function kindLine(move3) {
|
|
8808
|
+
const extra = [
|
|
8809
|
+
...move3.unkeyed === 0 ? [] : [`${String(move3.unkeyed)} without a key, not moved`],
|
|
8810
|
+
...move3.unparsed === 0 ? [] : [`${String(move3.unparsed)} unparsed lines, not moved`]
|
|
8811
|
+
];
|
|
8812
|
+
const counts = [`${String(move3.read)} read`, `${String(move3.added)} added`, `${String(move3.skipped)} already held`, ...extra];
|
|
8813
|
+
return ` ${move3.kind}: ${counts.join(", ")} (from ${move3.from})`;
|
|
8814
|
+
}
|
|
8815
|
+
function renderMove(result) {
|
|
8816
|
+
const config = result.configChanged ? `\u2705 Set store: ${MOVE_TARGET} in ${result.configPath}.` : `\u2705 ${result.configPath} already reads store: ${MOVE_TARGET}; left as it was.`;
|
|
8077
8817
|
return [
|
|
8078
|
-
`
|
|
8079
|
-
|
|
8818
|
+
`Moves the NDJSON sessions and commits into ${result.to}.`,
|
|
8819
|
+
...result.kinds.map(kindLine),
|
|
8820
|
+
"Checked: the SQLite store holds every key read, and its count is the old count plus the rows added.",
|
|
8821
|
+
config,
|
|
8822
|
+
"The NDJSON files are left where they are; running the move again adds nothing."
|
|
8080
8823
|
];
|
|
8081
8824
|
}
|
|
8082
|
-
function
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
return [`No effort store at ${result.path}: nothing to migrate.`];
|
|
8086
|
-
case "current":
|
|
8087
|
-
return [`\u2705 ${result.path} is current: no migration is pending. Nothing to migrate.`];
|
|
8088
|
-
case "would-migrate":
|
|
8089
|
-
return [
|
|
8090
|
-
...builtLines(result),
|
|
8091
|
-
"\uD83D\uDD0D Dry run: the migrated store was built beside it, checked (row counts, integrity_check, this rafa's" + " schema plan) and deleted. Run `rafa effort migrate` to swap it in."
|
|
8092
|
-
];
|
|
8093
|
-
case "migrated":
|
|
8094
|
-
return [
|
|
8095
|
-
...builtLines(result),
|
|
8096
|
-
`\u2705 Migrated. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`
|
|
8097
|
-
];
|
|
8098
|
-
}
|
|
8825
|
+
function refusalExit2(error) {
|
|
8826
|
+
const known = error instanceof MoveRefusal || error instanceof SchemaRefusedError || error instanceof DevelopmentBuildRefusedError;
|
|
8827
|
+
return known ? new CommandExit(REFUSED_EXIT4, `\u274C ${COMMAND_NAME9}: ${error.message}`) : null;
|
|
8099
8828
|
}
|
|
8100
|
-
function
|
|
8829
|
+
function runMove(context) {
|
|
8830
|
+
expectNoArgument(context.args, MOVE_USAGE);
|
|
8831
|
+
readTarget2(context);
|
|
8832
|
+
const project = requireProject(context, COMMAND_NAME9);
|
|
8833
|
+
resolveProjectConfig(project, COMMAND_NAME9, (message) => context.output.warn(message));
|
|
8101
8834
|
try {
|
|
8102
|
-
|
|
8835
|
+
effortStoreDir(project.root, context.env);
|
|
8103
8836
|
} catch (error) {
|
|
8104
|
-
throw new CommandExit(
|
|
8837
|
+
throw new CommandExit(REFUSED_EXIT4, `\u274C ${COMMAND_NAME9}: ${messageOf(error)}`);
|
|
8105
8838
|
}
|
|
8106
|
-
}
|
|
8107
|
-
function runMigrate(context, seams) {
|
|
8108
|
-
expectNoArgument(context.args, MIGRATE_USAGE);
|
|
8109
|
-
const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MIGRATE_USAGE}`);
|
|
8110
|
-
const path = storePath(context, requireProject(context, COMMAND_NAME7).root);
|
|
8111
|
-
const now = seams.now ?? (() => new Date);
|
|
8112
8839
|
let result;
|
|
8113
8840
|
try {
|
|
8114
|
-
result =
|
|
8115
|
-
path,
|
|
8116
|
-
dryRun,
|
|
8117
|
-
stamp: fileStamp(now()),
|
|
8118
|
-
now,
|
|
8119
|
-
env: context.env,
|
|
8120
|
-
...seams.identity === undefined ? {} : { identity: seams.identity },
|
|
8121
|
-
...seams.isAlive === undefined ? {} : { isAlive: seams.isAlive },
|
|
8122
|
-
...seams.migrations === undefined ? {} : { migrations: seams.migrations }
|
|
8123
|
-
});
|
|
8841
|
+
result = moveToSqlite({ root: project.root });
|
|
8124
8842
|
} catch (error) {
|
|
8125
|
-
|
|
8126
|
-
throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${error.message}`);
|
|
8127
|
-
}
|
|
8128
|
-
throw error;
|
|
8843
|
+
throw refusalExit2(error) ?? error;
|
|
8129
8844
|
}
|
|
8130
8845
|
if (context.outputMode === "json") {
|
|
8131
8846
|
context.output.result(result);
|
|
8132
8847
|
return;
|
|
8133
8848
|
}
|
|
8134
|
-
for (const line of
|
|
8849
|
+
for (const line of renderMove(result))
|
|
8135
8850
|
context.output.info(line);
|
|
8136
8851
|
}
|
|
8137
|
-
function
|
|
8852
|
+
function createMoveCommand() {
|
|
8138
8853
|
const command = {
|
|
8139
|
-
name: "effort
|
|
8854
|
+
name: "effort move",
|
|
8140
8855
|
subject: "effort",
|
|
8141
|
-
action: "
|
|
8142
|
-
summary: "
|
|
8143
|
-
description: "
|
|
8856
|
+
action: "move",
|
|
8857
|
+
summary: "move a store: ndjson project's sessions and commits into the SQLite store and switch to it",
|
|
8858
|
+
description: "Appends every session and commit row the NDJSON files under `.rafa/effort/`, or under" + " `RAFA_EFFORT_DIR`, hold to the SQLite store beside them, through the store port, so each row is" + " deduplicated by its key as `rafa effort collect` would. It then checks that the SQLite store holds" + " every key read and that its count is the old count plus the rows added, and only then sets" + " `store: sqlite` in `.rafa/config.yaml`, keeping every other line. The NDJSON files are left where" + " they are, and a move run again adds nothing, so it can be repeated once a loop started before it" + " has ended. Exit code 1, with nothing moved, for a `--to` other than `sqlite`, a config it cannot" + " edit, and a development build over a store it does not own; a failed count check is exit code 1" + " with the config left as it was. With `--output=json` the outcome is the data of the terminal" + " result event. Starts no session.",
|
|
8144
8859
|
args: [],
|
|
8145
8860
|
flags: [
|
|
8146
8861
|
{
|
|
8147
|
-
name:
|
|
8148
|
-
description: "
|
|
8149
|
-
type: "
|
|
8862
|
+
name: TO_FLAG2,
|
|
8863
|
+
description: "The store to move to; `sqlite` is the one value it takes, and it is required.",
|
|
8864
|
+
type: "string"
|
|
8150
8865
|
}
|
|
8151
8866
|
],
|
|
8152
8867
|
examples: [
|
|
8153
8868
|
{
|
|
8154
|
-
cmd:
|
|
8155
|
-
note: "
|
|
8156
|
-
},
|
|
8157
|
-
{
|
|
8158
|
-
cmd: "rafa effort migrate",
|
|
8159
|
-
note: "Swaps the migrated store in and prints where the original was kept."
|
|
8869
|
+
cmd: MOVE_USAGE,
|
|
8870
|
+
note: "Moves the NDJSON rows into the SQLite store, checks the counts and sets store: sqlite."
|
|
8160
8871
|
}
|
|
8161
8872
|
],
|
|
8162
8873
|
outputs: ["text", "json"],
|
|
8163
8874
|
run: async (context) => {
|
|
8164
|
-
|
|
8875
|
+
runMove(context);
|
|
8165
8876
|
await Promise.resolve();
|
|
8166
8877
|
}
|
|
8167
8878
|
};
|
|
8168
8879
|
return Object.freeze(command);
|
|
8169
8880
|
}
|
|
8170
|
-
var
|
|
8881
|
+
var move_default = createMoveCommand();
|
|
8171
8882
|
|
|
8172
8883
|
// src/commands/effort/report.ts
|
|
8173
8884
|
var report_default = wrapPhaseZeroCommand({
|
|
@@ -8223,11 +8934,11 @@ var report_default = wrapPhaseZeroCommand({
|
|
|
8223
8934
|
}, report);
|
|
8224
8935
|
|
|
8225
8936
|
// src/commands/effort/schema.ts
|
|
8226
|
-
import { join as
|
|
8227
|
-
var
|
|
8937
|
+
import { join as join23 } from "path";
|
|
8938
|
+
var COMMAND_NAME10 = "rafa effort schema";
|
|
8228
8939
|
var SCHEMA_USAGE = "rafa effort schema [--check]";
|
|
8229
8940
|
var CHECK_FLAG = "check";
|
|
8230
|
-
var
|
|
8941
|
+
var REFUSED_EXIT5 = 1;
|
|
8231
8942
|
var FAILED_EXIT2 = 2;
|
|
8232
8943
|
function listLine(name2, items) {
|
|
8233
8944
|
const shown = items.length === 0 ? "none" : items.join(", ");
|
|
@@ -8267,29 +8978,29 @@ function renderSchema(report2) {
|
|
|
8267
8978
|
`Next safe step: ${report2.nextStep}`
|
|
8268
8979
|
];
|
|
8269
8980
|
}
|
|
8270
|
-
function
|
|
8981
|
+
function storePath3(context, root) {
|
|
8271
8982
|
try {
|
|
8272
|
-
return
|
|
8983
|
+
return join23(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8273
8984
|
} catch (error) {
|
|
8274
|
-
throw new CommandExit(
|
|
8985
|
+
throw new CommandExit(REFUSED_EXIT5, `\u274C ${COMMAND_NAME10}: ${messageOf(error)}`);
|
|
8275
8986
|
}
|
|
8276
8987
|
}
|
|
8277
8988
|
function runSchema(context, seams) {
|
|
8278
8989
|
expectNoArgument(context.args, SCHEMA_USAGE);
|
|
8279
8990
|
const check = readSwitch(CHECK_FLAG, context.flags[CHECK_FLAG], `Usage: ${SCHEMA_USAGE}`);
|
|
8280
|
-
const path =
|
|
8991
|
+
const path = storePath3(context, requireProject(context, COMMAND_NAME10).root);
|
|
8281
8992
|
let report2;
|
|
8282
8993
|
try {
|
|
8283
8994
|
report2 = readSchemaReport(path, seams);
|
|
8284
8995
|
} catch (error) {
|
|
8285
|
-
throw new CommandExit(FAILED_EXIT2, `\u274C ${
|
|
8996
|
+
throw new CommandExit(FAILED_EXIT2, `\u274C ${COMMAND_NAME10}: ${path} cannot be read as an effort store: ${messageOf(error)}`);
|
|
8286
8997
|
}
|
|
8287
8998
|
if (context.outputMode !== "json") {
|
|
8288
8999
|
for (const line of renderSchema(report2))
|
|
8289
9000
|
context.output.info(line);
|
|
8290
9001
|
}
|
|
8291
9002
|
if (check && report2.refused) {
|
|
8292
|
-
throw new CommandExit(
|
|
9003
|
+
throw new CommandExit(REFUSED_EXIT5, `\u274C ${COMMAND_NAME10} --check: this rafa refuses ${path} (${report2.status}). Next safe step: ${report2.nextStep}`);
|
|
8293
9004
|
}
|
|
8294
9005
|
if (context.outputMode === "json")
|
|
8295
9006
|
context.output.result(report2);
|
|
@@ -8695,7 +9406,7 @@ async function applyHorizonChange(board, reading, reason) {
|
|
|
8695
9406
|
}
|
|
8696
9407
|
|
|
8697
9408
|
// src/commands/epic/horizon-change.ts
|
|
8698
|
-
var
|
|
9409
|
+
var TO_FLAG3 = "to";
|
|
8699
9410
|
var ISSUE_NUMBER2 = /^[1-9]\d*$/u;
|
|
8700
9411
|
var NO_ANSWERS = ["n", "no"];
|
|
8701
9412
|
var DEFER_ACTION = Object.freeze({
|
|
@@ -8721,9 +9432,9 @@ function readHorizonLine(context, action) {
|
|
|
8721
9432
|
if (!ISSUE_NUMBER2.test(word)) {
|
|
8722
9433
|
throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, action.usage);
|
|
8723
9434
|
}
|
|
8724
|
-
const to = readChoiceFlag(flags,
|
|
9435
|
+
const to = readChoiceFlag(flags, TO_FLAG3, action.targets, action.usage);
|
|
8725
9436
|
if (to === undefined)
|
|
8726
|
-
throw lineRefusal(`--${
|
|
9437
|
+
throw lineRefusal(`--${TO_FLAG3} is required: --${TO_FLAG3}=<horizon>`, action.usage);
|
|
8727
9438
|
const reason = readTextFlag(flags, REASON_FLAG, action.usage) ?? null;
|
|
8728
9439
|
return Object.freeze({ epic: Number(word), to, reason });
|
|
8729
9440
|
}
|
|
@@ -8736,7 +9447,7 @@ function wrongDirection(action, change) {
|
|
|
8736
9447
|
return null;
|
|
8737
9448
|
const other = action.name === "defer" ? "promote" : "defer";
|
|
8738
9449
|
const verb = later ? "a deferral" : "a promotion";
|
|
8739
|
-
return new CommandExit(EPIC_HORIZON_REFUSAL_EXIT, `\u274C Epic ${ref2(change.epic)} stands on ${change.from}, so ${change.from} \u2192 ${change.to} is ${verb};` + ` run rafa epic ${other} ${String(change.epic)} --${
|
|
9450
|
+
return new CommandExit(EPIC_HORIZON_REFUSAL_EXIT, `\u274C Epic ${ref2(change.epic)} stands on ${change.from}, so ${change.from} \u2192 ${change.to} is ${verb};` + ` run rafa epic ${other} ${String(change.epic)} --${TO_FLAG3}=${change.to}. Nothing was changed`);
|
|
8740
9451
|
}
|
|
8741
9452
|
function workPhrase(work) {
|
|
8742
9453
|
return [
|
|
@@ -8923,9 +9634,9 @@ function readMoveLine(context) {
|
|
|
8923
9634
|
throw lineRefusal(`Expected one issue number, got ${got}`, USAGE8);
|
|
8924
9635
|
}
|
|
8925
9636
|
const issue = issueNumber(word, "issue number");
|
|
8926
|
-
const target = readTextFlag(flags,
|
|
9637
|
+
const target = readTextFlag(flags, TO_FLAG3, USAGE8);
|
|
8927
9638
|
if (target === undefined)
|
|
8928
|
-
throw lineRefusal(`--${
|
|
9639
|
+
throw lineRefusal(`--${TO_FLAG3} is required: --${TO_FLAG3}=<epic>`, USAGE8);
|
|
8929
9640
|
const to = issueNumber(target, "epic number");
|
|
8930
9641
|
const reason = readTextFlag(flags, REASON_FLAG, USAGE8) ?? null;
|
|
8931
9642
|
return Object.freeze({ issue, to, reason });
|
|
@@ -9184,7 +9895,7 @@ function createEpicMoveCommand(seams = {}) {
|
|
|
9184
9895
|
],
|
|
9185
9896
|
flags: [
|
|
9186
9897
|
{
|
|
9187
|
-
name:
|
|
9898
|
+
name: TO_FLAG3,
|
|
9188
9899
|
description: "The number of the open epic it moves to. Required.",
|
|
9189
9900
|
type: "string",
|
|
9190
9901
|
required: true
|
|
@@ -9206,7 +9917,7 @@ function createEpicMoveCommand(seams = {}) {
|
|
|
9206
9917
|
};
|
|
9207
9918
|
return Object.freeze(command);
|
|
9208
9919
|
}
|
|
9209
|
-
var
|
|
9920
|
+
var move_default2 = createEpicMoveCommand();
|
|
9210
9921
|
|
|
9211
9922
|
// src/commands/epic/cancel.ts
|
|
9212
9923
|
var EPIC_CANCEL_REFUSAL_EXIT = 2;
|
|
@@ -9612,28 +10323,28 @@ function createEpicCancelCommand(seams = {}) {
|
|
|
9612
10323
|
var cancel_default = createEpicCancelCommand();
|
|
9613
10324
|
|
|
9614
10325
|
// src/commands/epic/close.ts
|
|
9615
|
-
import { randomUUID as
|
|
9616
|
-
import { join as
|
|
10326
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
10327
|
+
import { join as join27 } from "path";
|
|
9617
10328
|
|
|
9618
10329
|
// src/board/epic-template.ts
|
|
9619
|
-
import { existsSync as
|
|
9620
|
-
import { dirname as
|
|
10330
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
10331
|
+
import { dirname as dirname4, join as join24 } from "path";
|
|
9621
10332
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9622
|
-
var MODULE_DIR2 =
|
|
10333
|
+
var MODULE_DIR2 = dirname4(fileURLToPath2(import.meta.url));
|
|
9623
10334
|
var EPIC_TEMPLATE_FILE = "epic.md";
|
|
9624
10335
|
var CRITERIA_PLACEHOLDERS = Object.freeze([
|
|
9625
10336
|
"- Placeholder: one line per thing that makes this epic finished," + " written so a check against main can tell whether it holds."
|
|
9626
10337
|
]);
|
|
9627
10338
|
var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
|
|
9628
10339
|
function epicTemplateSource(moduleDir = MODULE_DIR2) {
|
|
9629
|
-
return
|
|
10340
|
+
return join24(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
|
|
9630
10341
|
}
|
|
9631
10342
|
function readEpicTemplate(moduleDir = MODULE_DIR2) {
|
|
9632
10343
|
const source = epicTemplateSource(moduleDir);
|
|
9633
|
-
if (!
|
|
10344
|
+
if (!existsSync10(source)) {
|
|
9634
10345
|
throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
|
|
9635
10346
|
}
|
|
9636
|
-
return
|
|
10347
|
+
return readFileSync9(source, "utf8");
|
|
9637
10348
|
}
|
|
9638
10349
|
function placeholderCriteria(criteria) {
|
|
9639
10350
|
if (criteria === null)
|
|
@@ -9724,10 +10435,10 @@ function renderEpicCost(cost, estimate) {
|
|
|
9724
10435
|
}
|
|
9725
10436
|
|
|
9726
10437
|
// src/epic/verify-plan.ts
|
|
9727
|
-
import { existsSync as
|
|
9728
|
-
import { dirname as
|
|
10438
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
10439
|
+
import { dirname as dirname5, join as join25 } from "path";
|
|
9729
10440
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9730
|
-
var MODULE_DIR3 =
|
|
10441
|
+
var MODULE_DIR3 = dirname5(fileURLToPath3(import.meta.url));
|
|
9731
10442
|
var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
|
|
9732
10443
|
var VERIFY_BLOCK_FENCE = "rafa:verify";
|
|
9733
10444
|
var VERIFY_PROMPT_SLOTS = ["EPIC_NUMBER", "EPIC_TITLE", "CRITERIA"];
|
|
@@ -9782,15 +10493,15 @@ function criteriaToAsk(criteria) {
|
|
|
9782
10493
|
return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
|
|
9783
10494
|
}
|
|
9784
10495
|
function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
|
|
9785
|
-
return [
|
|
10496
|
+
return [join25(moduleDir, VERIFY_PROMPT_FILE), join25(dirname5(moduleDir), VERIFY_PROMPT_FILE)];
|
|
9786
10497
|
}
|
|
9787
10498
|
function readVerifyPrompt(moduleDir = MODULE_DIR3) {
|
|
9788
10499
|
const candidates = verifyPromptCandidates(moduleDir);
|
|
9789
|
-
const found = candidates.find((candidate) =>
|
|
10500
|
+
const found = candidates.find((candidate) => existsSync11(candidate));
|
|
9790
10501
|
if (found === undefined) {
|
|
9791
10502
|
throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
|
|
9792
10503
|
}
|
|
9793
|
-
return
|
|
10504
|
+
return readFileSync10(found, "utf8");
|
|
9794
10505
|
}
|
|
9795
10506
|
function fenceFor2(text) {
|
|
9796
10507
|
const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
|
|
@@ -9926,8 +10637,8 @@ function parseVerifyPlan(output, criteria) {
|
|
|
9926
10637
|
}
|
|
9927
10638
|
|
|
9928
10639
|
// src/epic/verify-run.ts
|
|
9929
|
-
import { randomUUID as
|
|
9930
|
-
import { join as
|
|
10640
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
10641
|
+
import { join as join26 } from "path";
|
|
9931
10642
|
var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
|
|
9932
10643
|
var DEFAULT_VERIFY_REMOTE = "origin";
|
|
9933
10644
|
var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
|
|
@@ -9936,7 +10647,7 @@ var CHECK_RESULTS = ["pass", "fail"];
|
|
|
9936
10647
|
var WORKTREE_PREFIX2 = "epic-";
|
|
9937
10648
|
var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
|
|
9938
10649
|
function verifyWorktreePath(home, epic) {
|
|
9939
|
-
return
|
|
10650
|
+
return join26(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX2}${String(epic)}`);
|
|
9940
10651
|
}
|
|
9941
10652
|
function fetchStep(remote) {
|
|
9942
10653
|
return { id: "fetch", args: ["fetch", remote] };
|
|
@@ -10073,7 +10784,7 @@ function readCheck(check, sessionId, session) {
|
|
|
10073
10784
|
}
|
|
10074
10785
|
async function runChecks(options, worktree, prompt) {
|
|
10075
10786
|
const spawn = options.spawn ?? spawnClaudeCaptured;
|
|
10076
|
-
const pickId = options.sessionId ??
|
|
10787
|
+
const pickId = options.sessionId ?? randomUUID3;
|
|
10077
10788
|
const results = [];
|
|
10078
10789
|
for (const check of options.checks) {
|
|
10079
10790
|
const sessionId = pickId();
|
|
@@ -10196,7 +10907,7 @@ async function planVerification(input) {
|
|
|
10196
10907
|
return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
|
|
10197
10908
|
}
|
|
10198
10909
|
function closeTriageFile(root, epic) {
|
|
10199
|
-
return
|
|
10910
|
+
return join27(root, ".rafa", `epic-${String(epic)}-close`);
|
|
10200
10911
|
}
|
|
10201
10912
|
function failedCheckArtifact(epic, criterion) {
|
|
10202
10913
|
return `epic ${ref5(epic)} acceptance criterion: ${oneLine(criterion.text)}`;
|
|
@@ -10365,7 +11076,7 @@ async function closeEpic(context, seams) {
|
|
|
10365
11076
|
config,
|
|
10366
11077
|
seams,
|
|
10367
11078
|
spawn: seams.spawn ?? spawnClaudeCaptured,
|
|
10368
|
-
sessionId: seams.sessionId ??
|
|
11079
|
+
sessionId: seams.sessionId ?? randomUUID4
|
|
10369
11080
|
};
|
|
10370
11081
|
const gh = seams.gh ?? createGhRunner({ cwd: project.root });
|
|
10371
11082
|
let listing;
|
|
@@ -10472,7 +11183,7 @@ function createEpicDeferCommand(seams = {}) {
|
|
|
10472
11183
|
],
|
|
10473
11184
|
flags: [
|
|
10474
11185
|
{
|
|
10475
|
-
name:
|
|
11186
|
+
name: TO_FLAG3,
|
|
10476
11187
|
description: `The horizon it moves to: one of ${DEFER_ACTION.targets.join(", ")}. Required.`,
|
|
10477
11188
|
type: "string",
|
|
10478
11189
|
required: true
|
|
@@ -10708,7 +11419,7 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
10708
11419
|
],
|
|
10709
11420
|
flags: [
|
|
10710
11421
|
{
|
|
10711
|
-
name:
|
|
11422
|
+
name: TO_FLAG3,
|
|
10712
11423
|
description: `The horizon it moves to: one of ${PROMOTE_ACTION.targets.join(", ")}. Required.`,
|
|
10713
11424
|
type: "string",
|
|
10714
11425
|
required: true
|
|
@@ -10733,13 +11444,13 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
10733
11444
|
var promote_default = createEpicPromoteCommand();
|
|
10734
11445
|
|
|
10735
11446
|
// src/commands/init.ts
|
|
10736
|
-
import { existsSync as
|
|
11447
|
+
import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
|
|
10737
11448
|
import { homedir } from "os";
|
|
10738
|
-
import { join as
|
|
11449
|
+
import { join as join32, relative as relative6, sep as sep4 } from "path";
|
|
10739
11450
|
|
|
10740
11451
|
// src/agents/vendorable.ts
|
|
10741
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
10742
|
-
import { isAbsolute as isAbsolute5, join as
|
|
11452
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync11 } from "fs";
|
|
11453
|
+
import { isAbsolute as isAbsolute5, join as join28 } from "path";
|
|
10743
11454
|
function planFiles(dir) {
|
|
10744
11455
|
let entries;
|
|
10745
11456
|
try {
|
|
@@ -10751,7 +11462,7 @@ function planFiles(dir) {
|
|
|
10751
11462
|
}
|
|
10752
11463
|
function readText2(path) {
|
|
10753
11464
|
try {
|
|
10754
|
-
return
|
|
11465
|
+
return readFileSync11(path, "utf8");
|
|
10755
11466
|
} catch {
|
|
10756
11467
|
return null;
|
|
10757
11468
|
}
|
|
@@ -10761,7 +11472,7 @@ function rafaHolds(roster, name2) {
|
|
|
10761
11472
|
return item?.holders.some((holder) => holder.source === "rafa") === true;
|
|
10762
11473
|
}
|
|
10763
11474
|
function vendorableAgents(scan) {
|
|
10764
|
-
const dir = isAbsolute5(scan.planDir) ? scan.planDir :
|
|
11475
|
+
const dir = isAbsolute5(scan.planDir) ? scan.planDir : join28(scan.repoRoot, scan.planDir);
|
|
10765
11476
|
const roots = {
|
|
10766
11477
|
repoRoot: scan.repoRoot,
|
|
10767
11478
|
home: scan.home,
|
|
@@ -10770,7 +11481,7 @@ function vendorableAgents(scan) {
|
|
|
10770
11481
|
const roster = resolveAgentRoster(roots, scan.settings);
|
|
10771
11482
|
const found = [];
|
|
10772
11483
|
for (const file of planFiles(dir)) {
|
|
10773
|
-
const path =
|
|
11484
|
+
const path = join28(dir, file);
|
|
10774
11485
|
const markdown = readText2(path);
|
|
10775
11486
|
if (markdown === null)
|
|
10776
11487
|
continue;
|
|
@@ -10796,13 +11507,13 @@ function vendorableAgentWarnings(agents, root) {
|
|
|
10796
11507
|
|
|
10797
11508
|
// src/project/gitignore.ts
|
|
10798
11509
|
import { createHash } from "crypto";
|
|
10799
|
-
import { existsSync as
|
|
10800
|
-
import { dirname as
|
|
11510
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
|
|
11511
|
+
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join29 } from "path";
|
|
10801
11512
|
var GITIGNORE_FILE = ".gitignore";
|
|
10802
11513
|
var DIGEST_NAME = "tracking.digest";
|
|
10803
11514
|
var PRIVATE_TRIAGE = "triage/private";
|
|
10804
11515
|
var CACHE_DIR = "cache";
|
|
10805
|
-
var TRACKING_DIGEST_FILE =
|
|
11516
|
+
var TRACKING_DIGEST_FILE = join29(SCOPE_DIR, DIGEST_NAME);
|
|
10806
11517
|
var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
|
|
10807
11518
|
var BLOCK_END = "# <<< rafa tracking";
|
|
10808
11519
|
var SUB_PATHS = [
|
|
@@ -10823,6 +11534,7 @@ function trackingEntry(flags) {
|
|
|
10823
11534
|
`${SCOPE_DIR}/${PRIVATE_TRIAGE}/`,
|
|
10824
11535
|
`${SCOPE_DIR}/${DIGEST_NAME}`,
|
|
10825
11536
|
`${SCOPE_DIR}/${POSITION_FILE}`,
|
|
11537
|
+
`${SCOPE_DIR}/${HOP_FILE}`,
|
|
10826
11538
|
`${SCOPE_DIR}/${CACHE_DIR}/`
|
|
10827
11539
|
];
|
|
10828
11540
|
}
|
|
@@ -10890,19 +11602,19 @@ function attempt(file, failure3, act) {
|
|
|
10890
11602
|
}
|
|
10891
11603
|
}
|
|
10892
11604
|
function readIfPresent2(file) {
|
|
10893
|
-
if (!
|
|
11605
|
+
if (!existsSync12(file))
|
|
10894
11606
|
return null;
|
|
10895
|
-
return attempt(file, "cannot be read", () =>
|
|
11607
|
+
return attempt(file, "cannot be read", () => readFileSync12(file, "utf8"));
|
|
10896
11608
|
}
|
|
10897
11609
|
function writeTrackingGitignore(root, flags) {
|
|
10898
11610
|
requireAbsolute(root);
|
|
10899
|
-
const file =
|
|
11611
|
+
const file = join29(root, GITIGNORE_FILE);
|
|
10900
11612
|
const text = readIfPresent2(file);
|
|
10901
11613
|
const next = withTrackingBlock(text, flags, file);
|
|
10902
11614
|
if (next === text)
|
|
10903
11615
|
return { file, change: "unchanged" };
|
|
10904
11616
|
attempt(file, "cannot be written", () => {
|
|
10905
|
-
|
|
11617
|
+
writeFileSync3(file, next);
|
|
10906
11618
|
});
|
|
10907
11619
|
const change = text === null ? "created" : "updated";
|
|
10908
11620
|
return { file, change };
|
|
@@ -10918,7 +11630,7 @@ function trackingDigest(flags) {
|
|
|
10918
11630
|
}
|
|
10919
11631
|
function exposureNotice(root) {
|
|
10920
11632
|
return [
|
|
10921
|
-
`tracking.all is true, so ${
|
|
11633
|
+
`tracking.all is true, so ${join29(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
|
|
10922
11634
|
" - unpatched issues described in specs and plans",
|
|
10923
11635
|
" - effort rows with model names and token counts",
|
|
10924
11636
|
" - instincts that may quote error output",
|
|
@@ -10931,7 +11643,7 @@ function printWarning(message) {
|
|
|
10931
11643
|
}
|
|
10932
11644
|
function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
10933
11645
|
requireAbsolute(root);
|
|
10934
|
-
const digestFile =
|
|
11646
|
+
const digestFile = join29(root, TRACKING_DIGEST_FILE);
|
|
10935
11647
|
const recorded = `${trackingDigest(flags)}
|
|
10936
11648
|
`;
|
|
10937
11649
|
if (readIfPresent2(digestFile) === recorded) {
|
|
@@ -10940,8 +11652,8 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
|
10940
11652
|
if (flags.trackingAll)
|
|
10941
11653
|
print2(exposureNotice(root));
|
|
10942
11654
|
attempt(digestFile, "cannot be written", () => {
|
|
10943
|
-
mkdirSync4(
|
|
10944
|
-
|
|
11655
|
+
mkdirSync4(dirname6(digestFile), { recursive: true });
|
|
11656
|
+
writeFileSync3(digestFile, recorded);
|
|
10945
11657
|
});
|
|
10946
11658
|
return { digestFile, changed: true, printed: flags.trackingAll };
|
|
10947
11659
|
}
|
|
@@ -10951,12 +11663,12 @@ function applyTracking(root, flags, print2 = printWarning) {
|
|
|
10951
11663
|
}
|
|
10952
11664
|
|
|
10953
11665
|
// src/project/root-choice.ts
|
|
10954
|
-
import { resolve as
|
|
11666
|
+
import { resolve as resolve10 } from "path";
|
|
10955
11667
|
|
|
10956
11668
|
// src/project/roots.ts
|
|
10957
11669
|
import { spawnSync } from "child_process";
|
|
10958
|
-
import { accessSync, constants as constants2, readFileSync as
|
|
10959
|
-
import { isAbsolute as isAbsolute7, join as
|
|
11670
|
+
import { accessSync, constants as constants2, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
11671
|
+
import { isAbsolute as isAbsolute7, join as join30, resolve as resolve9 } from "path";
|
|
10960
11672
|
var REFUSED_ROOTS = Object.freeze([
|
|
10961
11673
|
{ path: "/", why: "is the filesystem root" },
|
|
10962
11674
|
{ path: "/var", why: "holds system state" },
|
|
@@ -10976,7 +11688,7 @@ var PACKAGE_JSON = "package.json";
|
|
|
10976
11688
|
var NOT_A_REPOSITORY = "fatal: not a git repository (or any ";
|
|
10977
11689
|
var DISK_ROOTS_FILE_SYSTEM = Object.freeze({
|
|
10978
11690
|
...DISK_FILE_SYSTEM,
|
|
10979
|
-
readFile: (path) =>
|
|
11691
|
+
readFile: (path) => readFileSync13(path, "utf8"),
|
|
10980
11692
|
isDirectory: (path) => statSync8(path).isDirectory(),
|
|
10981
11693
|
assertWritable: (path) => accessSync(path, constants2.W_OK)
|
|
10982
11694
|
});
|
|
@@ -11019,7 +11731,7 @@ function realOrSpelled(path, fs) {
|
|
|
11019
11731
|
try {
|
|
11020
11732
|
return fs.realpath(path);
|
|
11021
11733
|
} catch {
|
|
11022
|
-
return
|
|
11734
|
+
return resolve9(path);
|
|
11023
11735
|
}
|
|
11024
11736
|
}
|
|
11025
11737
|
function namedRoots(home, fs) {
|
|
@@ -11061,7 +11773,7 @@ function writeRefusal(given, real, fs) {
|
|
|
11061
11773
|
}
|
|
11062
11774
|
function refusalOf(given, named2, fs) {
|
|
11063
11775
|
const resolved = resolveReal(given, fs);
|
|
11064
|
-
const byName2 = namedRefusal(given, resolved.real ??
|
|
11776
|
+
const byName2 = namedRefusal(given, resolved.real ?? resolve9(given), named2);
|
|
11065
11777
|
if (byName2 !== null)
|
|
11066
11778
|
return byName2;
|
|
11067
11779
|
if (resolved.real === null) {
|
|
@@ -11089,8 +11801,8 @@ function readManifest2(file, fs) {
|
|
|
11089
11801
|
}
|
|
11090
11802
|
}
|
|
11091
11803
|
function readDir(dir, fs) {
|
|
11092
|
-
const manifest = readManifest2(
|
|
11093
|
-
const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(
|
|
11804
|
+
const manifest = readManifest2(join30(dir, PACKAGE_JSON), fs);
|
|
11805
|
+
const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(join30(dir, name2)));
|
|
11094
11806
|
const markers = manifest.marks ? [PACKAGE_JSON, ...files] : files;
|
|
11095
11807
|
return { ...manifest, dir, markers };
|
|
11096
11808
|
}
|
|
@@ -11160,7 +11872,7 @@ function firstCandidate(found) {
|
|
|
11160
11872
|
return first.refusal === null ? chosen(first.path, first.source) : problem(first.refusal.reason);
|
|
11161
11873
|
}
|
|
11162
11874
|
function namedRoot(typed, start2, source, seams) {
|
|
11163
|
-
const path =
|
|
11875
|
+
const path = resolve10(start2, typed);
|
|
11164
11876
|
const refusal9 = rootRefusal(path, seams);
|
|
11165
11877
|
if (refusal9 !== null)
|
|
11166
11878
|
return problem(refusal9.reason);
|
|
@@ -11201,8 +11913,8 @@ async function promptForRoot(found, prompter, seams) {
|
|
|
11201
11913
|
}
|
|
11202
11914
|
|
|
11203
11915
|
// src/project/scaffold.ts
|
|
11204
|
-
import { existsSync as
|
|
11205
|
-
import { join as
|
|
11916
|
+
import { existsSync as existsSync13, lstatSync, mkdirSync as mkdirSync5, statSync as statSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
11917
|
+
import { join as join31 } from "path";
|
|
11206
11918
|
var PROJECT_TREE = Object.freeze(["specs", "plans", "runs", "effort", "instincts"]);
|
|
11207
11919
|
var USER_TREE = Object.freeze(["instincts"]);
|
|
11208
11920
|
var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
@@ -11305,7 +12017,7 @@ class ScaffoldError extends Error {
|
|
|
11305
12017
|
}
|
|
11306
12018
|
function layoutOf2(base, tree) {
|
|
11307
12019
|
const scope = scopeAt(base);
|
|
11308
|
-
return { dirs: [scope.dir, ...tree.map((name2) =>
|
|
12020
|
+
return { dirs: [scope.dir, ...tree.map((name2) => join31(scope.dir, name2))], configFile: configFilePath(base) };
|
|
11309
12021
|
}
|
|
11310
12022
|
function occupied(path) {
|
|
11311
12023
|
return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
@@ -11316,7 +12028,7 @@ function isDirectory2(path) {
|
|
|
11316
12028
|
function directoryConflict(dir) {
|
|
11317
12029
|
if (!occupied(dir) || isDirectory2(dir))
|
|
11318
12030
|
return null;
|
|
11319
|
-
return
|
|
12031
|
+
return existsSync13(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
|
|
11320
12032
|
}
|
|
11321
12033
|
function layoutConflicts(layout) {
|
|
11322
12034
|
const [scopeDir, ...tree] = layout.dirs;
|
|
@@ -11324,7 +12036,7 @@ function layoutConflicts(layout) {
|
|
|
11324
12036
|
if (opening !== null)
|
|
11325
12037
|
return [opening];
|
|
11326
12038
|
const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
|
|
11327
|
-
const file = occupied(layout.configFile) && !
|
|
12039
|
+
const file = occupied(layout.configFile) && !existsSync13(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
|
|
11328
12040
|
return [...dirs, ...file];
|
|
11329
12041
|
}
|
|
11330
12042
|
function scaffoldConflicts(root, home) {
|
|
@@ -11338,7 +12050,7 @@ function attempt2(path, failure3, act) {
|
|
|
11338
12050
|
}
|
|
11339
12051
|
}
|
|
11340
12052
|
function ensureDirectory(path) {
|
|
11341
|
-
if (
|
|
12053
|
+
if (existsSync13(path))
|
|
11342
12054
|
return { path, kind: "directory", change: "unchanged" };
|
|
11343
12055
|
attempt2(path, "cannot be created", () => {
|
|
11344
12056
|
mkdirSync5(path, { recursive: true });
|
|
@@ -11346,10 +12058,10 @@ function ensureDirectory(path) {
|
|
|
11346
12058
|
return { path, kind: "directory", change: "created" };
|
|
11347
12059
|
}
|
|
11348
12060
|
function ensureFile(path, text) {
|
|
11349
|
-
if (
|
|
12061
|
+
if (existsSync13(path))
|
|
11350
12062
|
return { path, kind: "file", change: "unchanged" };
|
|
11351
12063
|
attempt2(path, "cannot be written", () => {
|
|
11352
|
-
|
|
12064
|
+
writeFileSync4(path, text, { encoding: "utf8", flag: "wx" });
|
|
11353
12065
|
});
|
|
11354
12066
|
return { path, kind: "file", change: "created" };
|
|
11355
12067
|
}
|
|
@@ -11366,10 +12078,10 @@ function writeUserScope(home) {
|
|
|
11366
12078
|
}
|
|
11367
12079
|
|
|
11368
12080
|
// src/commands/init-release.ts
|
|
11369
|
-
import { writeFileSync as
|
|
12081
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
11370
12082
|
|
|
11371
12083
|
// src/release/setting.ts
|
|
11372
|
-
import { readFileSync as
|
|
12084
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
11373
12085
|
var RELEASE_ENABLED_SETTING = "release.enabled";
|
|
11374
12086
|
function releaseEnabledIn(text, path) {
|
|
11375
12087
|
return parseConfigText(text, path).values.releaseEnabled ?? null;
|
|
@@ -11378,7 +12090,7 @@ function readReleaseSetting(root) {
|
|
|
11378
12090
|
const path = configFilePath(root);
|
|
11379
12091
|
let text;
|
|
11380
12092
|
try {
|
|
11381
|
-
text =
|
|
12093
|
+
text = readFileSync14(path, "utf8");
|
|
11382
12094
|
} catch (error) {
|
|
11383
12095
|
return { enabled: null, text: "", problem: `${path} could not be read: ${messageOf(error)}` };
|
|
11384
12096
|
}
|
|
@@ -11499,7 +12211,7 @@ function writeSetting(root, text, enabled) {
|
|
|
11499
12211
|
return `the file this run would have written reads ${RELEASE_ENABLED_SETTING} as ${describeValue(read)}`;
|
|
11500
12212
|
}
|
|
11501
12213
|
try {
|
|
11502
|
-
|
|
12214
|
+
writeFileSync5(path, written, "utf8");
|
|
11503
12215
|
} catch (error) {
|
|
11504
12216
|
return `it could not be written: ${messageOf(error)}`;
|
|
11505
12217
|
}
|
|
@@ -11662,9 +12374,9 @@ function resolvedConfig2(root, home, warn3) {
|
|
|
11662
12374
|
}
|
|
11663
12375
|
}
|
|
11664
12376
|
function checkGitignore(root, flags) {
|
|
11665
|
-
const file =
|
|
12377
|
+
const file = join32(root, GITIGNORE_FILE);
|
|
11666
12378
|
try {
|
|
11667
|
-
const text =
|
|
12379
|
+
const text = existsSync14(file) ? readFileSync15(file, "utf8") : null;
|
|
11668
12380
|
withTrackingBlock(text, flags, file);
|
|
11669
12381
|
} catch (error) {
|
|
11670
12382
|
const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
|
|
@@ -11695,8 +12407,8 @@ function initialise(root, start2, home, context, entry) {
|
|
|
11695
12407
|
checkConflicts(root.path, home);
|
|
11696
12408
|
const config = resolvedConfig2(root.path, home, warn3);
|
|
11697
12409
|
checkGitignore(root.path, config);
|
|
11698
|
-
const configExisted =
|
|
11699
|
-
const digestExisted =
|
|
12410
|
+
const configExisted = existsSync14(configFilePath(root.path));
|
|
12411
|
+
const digestExisted = existsSync14(join32(root.path, TRACKING_DIGEST_FILE));
|
|
11700
12412
|
const project = writeProjectScope(root.path);
|
|
11701
12413
|
const tracking = applyTracking(root.path, config, warn3);
|
|
11702
12414
|
const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
|
|
@@ -11874,7 +12586,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
11874
12586
|
var init_default = createInitCommand();
|
|
11875
12587
|
|
|
11876
12588
|
// src/commands/check-report.ts
|
|
11877
|
-
import { resolve as
|
|
12589
|
+
import { resolve as resolve11 } from "path";
|
|
11878
12590
|
var DEFAULT_CHECK_SEAMS = Object.freeze({
|
|
11879
12591
|
cwd: () => process.cwd()
|
|
11880
12592
|
});
|
|
@@ -11890,7 +12602,7 @@ Usage: ${usage2}`);
|
|
|
11890
12602
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
11891
12603
|
Usage: ${usage2}`);
|
|
11892
12604
|
}
|
|
11893
|
-
return
|
|
12605
|
+
return resolve11(cwd(), dir);
|
|
11894
12606
|
}
|
|
11895
12607
|
function readFix(value, usage2) {
|
|
11896
12608
|
if (value === undefined || value === false || value === "false")
|
|
@@ -11907,7 +12619,7 @@ function readProjectRoot(value, usage2, cwd) {
|
|
|
11907
12619
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
11908
12620
|
Usage: ${usage2}`);
|
|
11909
12621
|
}
|
|
11910
|
-
return
|
|
12622
|
+
return resolve11(cwd(), value);
|
|
11911
12623
|
}
|
|
11912
12624
|
function reportMarker(report2) {
|
|
11913
12625
|
if (report2.failed)
|
|
@@ -12543,8 +13255,8 @@ function createInstinctShowCommand() {
|
|
|
12543
13255
|
var show_default3 = createInstinctShowCommand();
|
|
12544
13256
|
|
|
12545
13257
|
// src/commands/issue/check.ts
|
|
12546
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
12547
|
-
import { join as
|
|
13258
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync10 } from "fs";
|
|
13259
|
+
import { join as join33, resolve as resolve12 } from "path";
|
|
12548
13260
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
12549
13261
|
var STAMP_FLAG = "stamp";
|
|
12550
13262
|
var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
|
|
@@ -12576,22 +13288,22 @@ function copyNames(dir, issue) {
|
|
|
12576
13288
|
}
|
|
12577
13289
|
const prefix = `${boardId(issue)}-`;
|
|
12578
13290
|
const notes = notesFileName(issue);
|
|
12579
|
-
return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync10(
|
|
13291
|
+
return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync10(join33(dir, name2)).isFile()).sort();
|
|
12580
13292
|
}
|
|
12581
13293
|
function findSavedCopy(root, specsDir, issue) {
|
|
12582
|
-
const names = copyNames(
|
|
13294
|
+
const names = copyNames(resolve12(root, specsDir), issue);
|
|
12583
13295
|
const [only] = names;
|
|
12584
13296
|
if (only === undefined)
|
|
12585
13297
|
throw new CommandExit(1, missingCopyMessage(issue, specsDir));
|
|
12586
13298
|
if (names.length > 1) {
|
|
12587
13299
|
throw new CommandExit(1, [
|
|
12588
13300
|
`\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
|
|
12589
|
-
...names.map((name2) => ` ${
|
|
13301
|
+
...names.map((name2) => ` ${join33(specsDir, name2)}`)
|
|
12590
13302
|
].join(`
|
|
12591
13303
|
`));
|
|
12592
13304
|
}
|
|
12593
|
-
const path =
|
|
12594
|
-
return { path, absolute:
|
|
13305
|
+
const path = join33(specsDir, only);
|
|
13306
|
+
return { path, absolute: resolve12(root, path) };
|
|
12595
13307
|
}
|
|
12596
13308
|
function liveText(live) {
|
|
12597
13309
|
return live.kind === "unreadable" ? "unreadable" : fingerprintText(live);
|
|
@@ -12639,7 +13351,7 @@ async function readReferences(absolute, issue, verify, stamp) {
|
|
|
12639
13351
|
return rows.map((row2) => checkedRef(row2, row2.stamp));
|
|
12640
13352
|
}
|
|
12641
13353
|
const once2 = memoiseVerifier(verify);
|
|
12642
|
-
const before = await readRefsText({ copy:
|
|
13354
|
+
const before = await readRefsText({ copy: readFileSync16(absolute, "utf8"), issue, verify: once2 });
|
|
12643
13355
|
const after = await restampCopyRefs({ path: absolute, issue, verify: once2 });
|
|
12644
13356
|
return before.rows.map((row2, index) => checkedRef(row2, after.rows[index]?.stamp ?? null));
|
|
12645
13357
|
}
|
|
@@ -12920,7 +13632,7 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
12920
13632
|
};
|
|
12921
13633
|
return Object.freeze(command);
|
|
12922
13634
|
}
|
|
12923
|
-
var
|
|
13635
|
+
var move_default3 = createIssueMoveCommand();
|
|
12924
13636
|
|
|
12925
13637
|
// src/commands/issue/show.ts
|
|
12926
13638
|
var USAGE19 = "rafa issue show <id>";
|
|
@@ -13494,7 +14206,7 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
13494
14206
|
subject: "loop",
|
|
13495
14207
|
action: "start",
|
|
13496
14208
|
summary: "run a plan task by task, then wrap up, open the PR and wait for CI",
|
|
13497
|
-
description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
|
|
14209
|
+
description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task, and under `--roadmap`" + " the hop away, when one is. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
|
|
13498
14210
|
args: [],
|
|
13499
14211
|
flags: [
|
|
13500
14212
|
{
|
|
@@ -13550,6 +14262,11 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
13550
14262
|
description: "Runs on `main` or `master`, which the loop otherwise refuses.",
|
|
13551
14263
|
type: "boolean"
|
|
13552
14264
|
},
|
|
14265
|
+
{
|
|
14266
|
+
name: "roadmap",
|
|
14267
|
+
description: "Stamps the hop `rafa next --roadmap` is away on, when one is, on the session record as" + " its `hop`: the hop record `.rafa/hop.json` holds, still `away` and its home still the" + " position's. What `rafa next --roadmap` passes to the loop it starts.",
|
|
14268
|
+
type: "boolean"
|
|
14269
|
+
},
|
|
13553
14270
|
{
|
|
13554
14271
|
name: "detached",
|
|
13555
14272
|
description: "Runs the loop in the background. Refused until phase 6, before anything is read:" + " until then a run holds the terminal it starts in.",
|
|
@@ -13904,15 +14621,18 @@ function isAlwaysAsked(action) {
|
|
|
13904
14621
|
return ALWAYS_ASKED.has(action);
|
|
13905
14622
|
}
|
|
13906
14623
|
var CEILING_REFUSAL_EXIT = 2;
|
|
14624
|
+
var ROADMAP_ACTIONS = new Set(["hop", "home"]);
|
|
13907
14625
|
var YES_ACTIONS = Object.freeze([
|
|
13908
14626
|
"sync",
|
|
13909
|
-
...NEXT_COMMAND_ACTIONS.filter((action) => !ALWAYS_ASKED.has(action))
|
|
14627
|
+
...NEXT_COMMAND_ACTIONS.filter((action) => !ALWAYS_ASKED.has(action)),
|
|
14628
|
+
...ROADMAP_ACTIONS
|
|
13910
14629
|
]);
|
|
13911
14630
|
var BARE_YES_ACTIONS = Object.freeze([
|
|
13912
14631
|
"sync",
|
|
13913
14632
|
"wait",
|
|
13914
14633
|
"unblock",
|
|
13915
|
-
"plan"
|
|
14634
|
+
"plan",
|
|
14635
|
+
"home"
|
|
13916
14636
|
]);
|
|
13917
14637
|
function ceilingRefusal(problem2, usage2) {
|
|
13918
14638
|
return new CommandExit(CEILING_REFUSAL_EXIT, `\u274C ${problem2}
|
|
@@ -13941,6 +14661,85 @@ function allowedUnasked(action, ceiling) {
|
|
|
13941
14661
|
return ceiling.includes(action);
|
|
13942
14662
|
}
|
|
13943
14663
|
|
|
14664
|
+
// src/next/lines.ts
|
|
14665
|
+
var NEXT_USAGE = "rafa next [--dry-run] [--roadmap] [--yes[=<action ids>]]";
|
|
14666
|
+
var DRY_RUN_FLAG5 = "dry-run";
|
|
14667
|
+
var ROADMAP_FLAG = "roadmap";
|
|
14668
|
+
var STATE_MARK = "\uD83D\uDCCD";
|
|
14669
|
+
var PROPOSAL_MARK = "\uD83D\uDC49";
|
|
14670
|
+
var STOP_MARK = "\u23F9";
|
|
14671
|
+
var MAX_ACTIONS = 12;
|
|
14672
|
+
function stateLine(state) {
|
|
14673
|
+
return `${STATE_MARK} ${state.reading}.`;
|
|
14674
|
+
}
|
|
14675
|
+
function proposalLine(state, invocation) {
|
|
14676
|
+
const runs = invocation === null ? "" : ` \u2014 rafa ${commandWords(invocation)}`;
|
|
14677
|
+
return `${PROPOSAL_MARK} ${state.proposal}${runs}`;
|
|
14678
|
+
}
|
|
14679
|
+
function shownIds(ids, roadmap) {
|
|
14680
|
+
return roadmap ? ids : ids.filter((action) => !ROADMAP_ACTIONS.has(action));
|
|
14681
|
+
}
|
|
14682
|
+
function namedIds(ceiling, roadmap) {
|
|
14683
|
+
const shown = shownIds(ceiling, roadmap);
|
|
14684
|
+
return shown.length === 0 ? "no action" : shown.join(", ");
|
|
14685
|
+
}
|
|
14686
|
+
function dryRunLine(dryRun, roadmap) {
|
|
14687
|
+
if (dryRun === "no-terminal") {
|
|
14688
|
+
return `${STOP_MARK} There is no terminal to answer on, so nothing ran; run rafa next where you can answer,` + ` or type --${YES_FLAG}=${shownIds(BARE_YES_ACTIONS, roadmap).join(",")} to allow those steps unasked.`;
|
|
14689
|
+
}
|
|
14690
|
+
return `${STOP_MARK} --${DRY_RUN_FLAG5}: nothing ran.`;
|
|
14691
|
+
}
|
|
14692
|
+
function stopLine(stop, state, ceiling, dryRun, roadmap = false) {
|
|
14693
|
+
if (stop === "dry-run")
|
|
14694
|
+
return dryRunLine(dryRun, roadmap);
|
|
14695
|
+
if (stop === "declined")
|
|
14696
|
+
return `${STOP_MARK} Nothing ran.`;
|
|
14697
|
+
if (stop === "unasked") {
|
|
14698
|
+
const allows = `${STOP_MARK} --${YES_FLAG} allows ${namedIds(ceiling ?? [], roadmap)}, and this step is ${state.action},` + " so nothing ran;";
|
|
14699
|
+
if (ALWAYS_ASKED.has(state.action)) {
|
|
14700
|
+
return `${allows} no --${YES_FLAG} list allows it, so drop --${YES_FLAG} to be asked.`;
|
|
14701
|
+
}
|
|
14702
|
+
return `${allows} type --${YES_FLAG}=${[...shownIds(ceiling ?? [], roadmap), state.action].join(",")} to allow it,` + ` or drop --${YES_FLAG} to be asked.`;
|
|
14703
|
+
}
|
|
14704
|
+
if (stop === "loop-started") {
|
|
14705
|
+
return `${STOP_MARK} The loop has run; rafa next reads where it left the project.`;
|
|
14706
|
+
}
|
|
14707
|
+
if (stop === "unchanged") {
|
|
14708
|
+
return `${STOP_MARK} That last step left the project where it was, so the chain stops rather than repeating it.`;
|
|
14709
|
+
}
|
|
14710
|
+
if (stop === "capped") {
|
|
14711
|
+
return `${STOP_MARK} ${MAX_ACTIONS} actions have run, which is as many as one chain runs; run rafa next again.`;
|
|
14712
|
+
}
|
|
14713
|
+
return null;
|
|
14714
|
+
}
|
|
14715
|
+
function lineRefusal2(problem2) {
|
|
14716
|
+
return new CommandExit(1, `\u274C ${problem2}
|
|
14717
|
+
Usage: ${NEXT_USAGE}`);
|
|
14718
|
+
}
|
|
14719
|
+
function readDryRun(flags) {
|
|
14720
|
+
return readBareFlag(flags, DRY_RUN_FLAG5);
|
|
14721
|
+
}
|
|
14722
|
+
function readRoadmap(flags) {
|
|
14723
|
+
return readBareFlag(flags, ROADMAP_FLAG);
|
|
14724
|
+
}
|
|
14725
|
+
function readBareFlag(flags, name2) {
|
|
14726
|
+
const value = flags[name2];
|
|
14727
|
+
if (value === undefined)
|
|
14728
|
+
return false;
|
|
14729
|
+
if (typeof value === "boolean")
|
|
14730
|
+
return value;
|
|
14731
|
+
if (value === "true" || value === "false")
|
|
14732
|
+
return value === "true";
|
|
14733
|
+
throw lineRefusal2(`--${name2} takes no value, and read "${value}" as one`);
|
|
14734
|
+
}
|
|
14735
|
+
function dryRunOf(flag, ceiling, hasTerminal) {
|
|
14736
|
+
if (flag)
|
|
14737
|
+
return "flag";
|
|
14738
|
+
if (ceiling === null && !hasTerminal)
|
|
14739
|
+
return "no-terminal";
|
|
14740
|
+
return null;
|
|
14741
|
+
}
|
|
14742
|
+
|
|
13944
14743
|
// src/next/sync.ts
|
|
13945
14744
|
var INDENT = " ";
|
|
13946
14745
|
function refuse(lines2) {
|
|
@@ -13995,53 +14794,8 @@ function fastForwardBase(git, plan2) {
|
|
|
13995
14794
|
}
|
|
13996
14795
|
|
|
13997
14796
|
// src/commands/next.ts
|
|
13998
|
-
var NEXT_USAGE = "rafa next [--dry-run] [--yes[=<action ids>]]";
|
|
13999
|
-
var DRY_RUN_FLAG4 = "dry-run";
|
|
14000
|
-
var STATE_MARK = "\uD83D\uDCCD";
|
|
14001
|
-
var PROPOSAL_MARK = "\uD83D\uDC49";
|
|
14002
|
-
var STOP_MARK = "\u23F9";
|
|
14003
|
-
var MAX_ACTIONS = 12;
|
|
14004
14797
|
var LOOP_ACTIONS = Object.freeze(["start", "resume"]);
|
|
14005
14798
|
var QUESTION_HANDED_OVER = new Set(["merge-unchecked"]);
|
|
14006
|
-
function stateLine(state) {
|
|
14007
|
-
return `${STATE_MARK} ${state.reading}.`;
|
|
14008
|
-
}
|
|
14009
|
-
function proposalLine(state, invocation) {
|
|
14010
|
-
const runs = invocation === null ? "" : ` \u2014 rafa ${commandWords(invocation)}`;
|
|
14011
|
-
return `${PROPOSAL_MARK} ${state.proposal}${runs}`;
|
|
14012
|
-
}
|
|
14013
|
-
function namedIds(ceiling) {
|
|
14014
|
-
return ceiling.length === 0 ? "no action" : ceiling.join(", ");
|
|
14015
|
-
}
|
|
14016
|
-
function dryRunLine(dryRun) {
|
|
14017
|
-
if (dryRun === "no-terminal") {
|
|
14018
|
-
return `${STOP_MARK} There is no terminal to answer on, so nothing ran; run rafa next where you can answer,` + ` or type --${YES_FLAG}=${BARE_YES_ACTIONS.join(",")} to allow those steps unasked.`;
|
|
14019
|
-
}
|
|
14020
|
-
return `${STOP_MARK} --${DRY_RUN_FLAG4}: nothing ran.`;
|
|
14021
|
-
}
|
|
14022
|
-
function stopLine(stop, state, ceiling, dryRun) {
|
|
14023
|
-
if (stop === "dry-run")
|
|
14024
|
-
return dryRunLine(dryRun);
|
|
14025
|
-
if (stop === "declined")
|
|
14026
|
-
return `${STOP_MARK} Nothing ran.`;
|
|
14027
|
-
if (stop === "unasked") {
|
|
14028
|
-
const allows = `${STOP_MARK} --${YES_FLAG} allows ${namedIds(ceiling ?? [])}, and this step is ${state.action},` + " so nothing ran;";
|
|
14029
|
-
if (ALWAYS_ASKED.has(state.action)) {
|
|
14030
|
-
return `${allows} no --${YES_FLAG} list allows it, so drop --${YES_FLAG} to be asked.`;
|
|
14031
|
-
}
|
|
14032
|
-
return `${allows} type --${YES_FLAG}=${[...ceiling ?? [], state.action].join(",")} to allow it,` + ` or drop --${YES_FLAG} to be asked.`;
|
|
14033
|
-
}
|
|
14034
|
-
if (stop === "loop-started") {
|
|
14035
|
-
return `${STOP_MARK} The loop has run; rafa next reads where it left the project.`;
|
|
14036
|
-
}
|
|
14037
|
-
if (stop === "unchanged") {
|
|
14038
|
-
return `${STOP_MARK} That last step left the project where it was, so the chain stops rather than repeating it.`;
|
|
14039
|
-
}
|
|
14040
|
-
if (stop === "capped") {
|
|
14041
|
-
return `${STOP_MARK} ${MAX_ACTIONS} actions have run, which is as many as one chain runs; run rafa next again.`;
|
|
14042
|
-
}
|
|
14043
|
-
return null;
|
|
14044
|
-
}
|
|
14045
14799
|
function repeats(previous, state) {
|
|
14046
14800
|
if (previous === null)
|
|
14047
14801
|
return false;
|
|
@@ -14057,82 +14811,74 @@ function stepOf2(state, invocation, over = {}) {
|
|
|
14057
14811
|
...over
|
|
14058
14812
|
});
|
|
14059
14813
|
}
|
|
14060
|
-
function
|
|
14061
|
-
|
|
14814
|
+
async function playTurn(options, state, previous) {
|
|
14815
|
+
const { run, ask, handOver, ceiling, dryRun, info: info2, warn: warn3, roadmap } = options;
|
|
14816
|
+
for (const problem2 of state.problems)
|
|
14817
|
+
warn3(problem2);
|
|
14818
|
+
const invocation = actionInvocation(state, { roadmap: roadmap !== undefined });
|
|
14819
|
+
info2(stateLine(state));
|
|
14820
|
+
info2(proposalLine(state, invocation));
|
|
14821
|
+
const halt = (stop, over = {}) => ({ stop, step: stepOf2(state, invocation, over) });
|
|
14822
|
+
if (dryRun !== null)
|
|
14823
|
+
return halt("dry-run");
|
|
14824
|
+
if (state.action === "none")
|
|
14825
|
+
return halt("nothing-to-run");
|
|
14826
|
+
if (repeats(previous, state))
|
|
14827
|
+
return halt("unchanged");
|
|
14828
|
+
const unasked = allowedUnasked(state.action, ceiling);
|
|
14829
|
+
if (!unasked && ceiling !== null)
|
|
14830
|
+
return halt("unasked");
|
|
14831
|
+
const handedOver = QUESTION_HANDED_OVER.has(state.action);
|
|
14832
|
+
const asks = !unasked && !handedOver;
|
|
14833
|
+
if (asks && !await ask(nextQuestion(state)))
|
|
14834
|
+
return halt("declined", { asked: true });
|
|
14835
|
+
if (handedOver)
|
|
14836
|
+
handOver?.();
|
|
14837
|
+
await run(state);
|
|
14838
|
+
return { stop: null, step: stepOf2(state, invocation, { asked: asks, ran: true }) };
|
|
14062
14839
|
}
|
|
14063
14840
|
async function runNextChain(options) {
|
|
14064
|
-
const { read,
|
|
14841
|
+
const { read, ceiling, dryRun, info: info2, roadmap } = options;
|
|
14065
14842
|
let steps = [];
|
|
14843
|
+
let hops = [];
|
|
14066
14844
|
let previous = null;
|
|
14845
|
+
const report2 = (stop) => Object.freeze({
|
|
14846
|
+
steps: Object.freeze([...steps]),
|
|
14847
|
+
stop,
|
|
14848
|
+
...roadmap === undefined ? {} : { hops: Object.freeze([...hops]) }
|
|
14849
|
+
});
|
|
14850
|
+
const stopOn = (stop, state) => {
|
|
14851
|
+
const line = stopLine(stop, state, ceiling, dryRun, roadmap !== undefined);
|
|
14852
|
+
if (line !== null)
|
|
14853
|
+
info2(line);
|
|
14854
|
+
return report2(stop);
|
|
14855
|
+
};
|
|
14856
|
+
const turn = async (state, before) => {
|
|
14857
|
+
const end = await playTurn(options, state, before);
|
|
14858
|
+
steps = [...steps, end.step];
|
|
14859
|
+
if (end.stop === null && state.hop !== undefined)
|
|
14860
|
+
hops = [...hops, state.hop];
|
|
14861
|
+
return end.stop;
|
|
14862
|
+
};
|
|
14067
14863
|
for (;; ) {
|
|
14068
14864
|
const state = await read();
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
info2(line);
|
|
14078
|
-
};
|
|
14079
|
-
const stopHere = (stop, over = {}) => {
|
|
14080
|
-
announce(stop);
|
|
14081
|
-
return ended(steps, stepOf2(state, invocation, over), stop);
|
|
14082
|
-
};
|
|
14083
|
-
const stopAfter = (stop) => {
|
|
14084
|
-
announce(stop);
|
|
14085
|
-
return Object.freeze({ steps, stop });
|
|
14086
|
-
};
|
|
14087
|
-
if (dryRun !== null)
|
|
14088
|
-
return stopHere("dry-run");
|
|
14089
|
-
if (state.action === "none")
|
|
14090
|
-
return stopHere("nothing-to-run");
|
|
14091
|
-
if (repeats(previous, state))
|
|
14092
|
-
return stopHere("unchanged");
|
|
14093
|
-
const unasked = allowedUnasked(state.action, ceiling);
|
|
14094
|
-
if (!unasked && ceiling !== null)
|
|
14095
|
-
return stopHere("unasked");
|
|
14096
|
-
const handedOver = QUESTION_HANDED_OVER.has(state.action);
|
|
14097
|
-
const asks = !unasked && !handedOver;
|
|
14098
|
-
if (asks && !await ask(nextQuestion(state)))
|
|
14099
|
-
return stopHere("declined", { asked: true });
|
|
14100
|
-
if (handedOver)
|
|
14101
|
-
handOver?.();
|
|
14102
|
-
await run(state);
|
|
14103
|
-
steps = [...steps, stepOf2(state, invocation, { asked: asks, ran: true })];
|
|
14104
|
-
if (LOOP_ACTIONS.includes(state.action))
|
|
14105
|
-
return stopAfter("loop-started");
|
|
14865
|
+
const stop = await turn(state, previous);
|
|
14866
|
+
if (stop !== null)
|
|
14867
|
+
return stopOn(stop, state);
|
|
14868
|
+
if (LOOP_ACTIONS.includes(state.action)) {
|
|
14869
|
+
const home = roadmap === undefined ? null : await roadmap.afterLoop();
|
|
14870
|
+
const homeStop = home === null ? null : await turn(home, null);
|
|
14871
|
+
return homeStop === null ? stopOn("loop-started", state) : stopOn(homeStop, home ?? state);
|
|
14872
|
+
}
|
|
14106
14873
|
if (steps.length >= MAX_ACTIONS)
|
|
14107
|
-
return
|
|
14874
|
+
return stopOn("capped", state);
|
|
14108
14875
|
previous = state;
|
|
14109
14876
|
}
|
|
14110
14877
|
}
|
|
14111
|
-
function lineRefusal2(problem2) {
|
|
14112
|
-
return new CommandExit(1, `\u274C ${problem2}
|
|
14113
|
-
Usage: ${NEXT_USAGE}`);
|
|
14114
|
-
}
|
|
14115
|
-
function readDryRun(flags) {
|
|
14116
|
-
const value = flags[DRY_RUN_FLAG4];
|
|
14117
|
-
if (value === undefined)
|
|
14118
|
-
return false;
|
|
14119
|
-
if (typeof value === "boolean")
|
|
14120
|
-
return value;
|
|
14121
|
-
if (value === "true" || value === "false")
|
|
14122
|
-
return value === "true";
|
|
14123
|
-
throw lineRefusal2(`--${DRY_RUN_FLAG4} takes no value, and read "${value}" as one`);
|
|
14124
|
-
}
|
|
14125
|
-
function dryRunOf(flag, ceiling, hasTerminal) {
|
|
14126
|
-
if (flag)
|
|
14127
|
-
return "flag";
|
|
14128
|
-
if (ceiling === null && !hasTerminal)
|
|
14129
|
-
return "no-terminal";
|
|
14130
|
-
return null;
|
|
14131
|
-
}
|
|
14132
14878
|
var DEFAULT_NEXT_SEAMS = Object.freeze({});
|
|
14133
14879
|
async function runStateAction(context, sources, state) {
|
|
14134
14880
|
if (state.action !== "sync") {
|
|
14135
|
-
await runAction({ ...context, output: actionOutput(context.output) }, state);
|
|
14881
|
+
await runAction({ ...context, output: actionOutput(context.output) }, state, { roadmap: sources.roadmap !== undefined });
|
|
14136
14882
|
return;
|
|
14137
14883
|
}
|
|
14138
14884
|
const outcome2 = fastForwardBase(sources.git, { base: sources.base, remote: sources.remote ?? REMOTE });
|
|
@@ -14150,13 +14896,22 @@ function endOfEpic(context, sources, report2, epic) {
|
|
|
14150
14896
|
for (const line of epicEndLines(epic, release))
|
|
14151
14897
|
context.output.info(line);
|
|
14152
14898
|
}
|
|
14899
|
+
async function homeAfterLoop(context, sources) {
|
|
14900
|
+
try {
|
|
14901
|
+
return await readHomeAfterLoop(sources.answer());
|
|
14902
|
+
} catch (error) {
|
|
14903
|
+
context.output.warn(`the hop could not be read after the loop, so rafa next does not go home: ${messageOf(error)}`);
|
|
14904
|
+
return null;
|
|
14905
|
+
}
|
|
14906
|
+
}
|
|
14153
14907
|
async function runNext(context, seams) {
|
|
14154
14908
|
expectNoArgument(context.args, NEXT_USAGE);
|
|
14155
14909
|
const flagged = readDryRun(context.flags);
|
|
14910
|
+
const roadmap = readRoadmap(context.flags);
|
|
14156
14911
|
const ceiling = readYesCeiling(context.flags, NEXT_USAGE);
|
|
14157
14912
|
const isTerminal = seams.isTerminal ?? (() => process.stdin.isTTY === true);
|
|
14158
14913
|
const dryRun = dryRunOf(flagged, ceiling, isTerminal());
|
|
14159
|
-
const sources = openNextSources(context, seams);
|
|
14914
|
+
const sources = openNextSources(context, seams, roadmap ? { roadmap } : {});
|
|
14160
14915
|
const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
|
|
14161
14916
|
const prompter = lazyPrompter(openPrompter);
|
|
14162
14917
|
const dry = watchDryEpic();
|
|
@@ -14173,7 +14928,8 @@ async function runNext(context, seams) {
|
|
|
14173
14928
|
},
|
|
14174
14929
|
warn: (line) => {
|
|
14175
14930
|
context.output.warn(line);
|
|
14176
|
-
}
|
|
14931
|
+
},
|
|
14932
|
+
...roadmap ? { roadmap: { afterLoop: () => homeAfterLoop(context, sources) } } : {}
|
|
14177
14933
|
});
|
|
14178
14934
|
endOfEpic(context, sources, report2, dry.last());
|
|
14179
14935
|
return report2;
|
|
@@ -14184,10 +14940,15 @@ async function runNext(context, seams) {
|
|
|
14184
14940
|
var YES_ID_LIST = YES_ACTIONS.join(", ");
|
|
14185
14941
|
var NEXT_FLAGS = Object.freeze([
|
|
14186
14942
|
{
|
|
14187
|
-
name:
|
|
14943
|
+
name: DRY_RUN_FLAG5,
|
|
14188
14944
|
description: "Print where the project stands and what to do about it, and stop without asking or running it." + " A run with no terminal to answer on and no --yes does this whether or not the flag is typed.",
|
|
14189
14945
|
type: "boolean"
|
|
14190
14946
|
},
|
|
14947
|
+
{
|
|
14948
|
+
name: ROADMAP_FLAG,
|
|
14949
|
+
description: "Follow one blocker into another epic or board and come home: where the walk stops at an issue" + " blocked by one in another epic, or at an epic with no line left, propose the hop there, pass" + " --roadmap to plan create and loop start, and go home once the loop has run. A blocker that is" + " itself blocked halts with the chain instead, and a pull request whose owner has not approved is" + " not merged.",
|
|
14950
|
+
type: "boolean"
|
|
14951
|
+
},
|
|
14191
14952
|
{
|
|
14192
14953
|
name: YES_FLAG,
|
|
14193
14954
|
description: "Run the actions named without asking, as a comma list of action ids, and stop at the first" + ` action the list leaves out. Bare it names ${BARE_YES_ACTIONS.join(", ")}. The ids are ${YES_ID_LIST};` + ` a list naming ${[...ALWAYS_ASKED].join(" or ")}, which are always asked, or a word that is no id at all` + " is refused.",
|
|
@@ -14200,7 +14961,7 @@ function createNextCommand(seams = DEFAULT_NEXT_SEAMS) {
|
|
|
14200
14961
|
subject: "next",
|
|
14201
14962
|
action: "next",
|
|
14202
14963
|
summary: "say where the project stands, propose the one next step, and run it on a yes",
|
|
14203
|
-
description: "Reads the project once \u2014 the running loops, the branch and its base, the plans and their" + " trackers, the open pull request and its checks, and the roadmap \u2014 and prints where it stands on one" + " line and the one thing to do about it on the next. On a yes it runs that action by calling the" + " command that does it, then reads the project again and proposes what follows. It stops when the" + " answer is no, when an action fails, when there is nothing to run, and once a loop has started. A" + " working tree with changes to tracked files and a pull request provider that cannot be asked are" + " reported in place of a proposal. A pull request no check has reported on is proposed as" + " `rafa pr merge <n> --skip-checks`, which asks its own question after the warning it prints, so" + " rafa next asks none before it. `--dry-run` prints the two lines and stops, and so does a run" + " with no terminal to answer on and no `--yes`, since there is nobody to put the question to." + " With `--output=json`" + " the steps and why the chain stopped are the data of the terminal result event.",
|
|
14964
|
+
description: "Reads the project once \u2014 the running loops, the branch and its base, the plans and their" + " trackers, the open pull request and its checks, and the roadmap \u2014 and prints where it stands on one" + " line and the one thing to do about it on the next. On a yes it runs that action by calling the" + " command that does it, then reads the project again and proposes what follows. It stops when the" + " answer is no, when an action fails, when there is nothing to run, and once a loop has started. A" + " working tree with changes to tracked files and a pull request provider that cannot be asked are" + " reported in place of a proposal. A pull request no check has reported on is proposed as" + " `rafa pr merge <n> --skip-checks`, which asks its own question after the warning it prints, so" + " rafa next asks none before it. `--dry-run` prints the two lines and stops, and so does a run" + " with no terminal to answer on and no `--yes`, since there is nobody to put the question to." + " With `--roadmap` it follows one blocker into another epic or board, works it up to its pull" + " request and comes home, halting where that blocker is blocked in turn." + " With `--output=json`" + " the steps and why the chain stopped are the data of the terminal result event, and under" + " `--roadmap` the hops that ran as well.",
|
|
14204
14965
|
args: [],
|
|
14205
14966
|
flags: [...NEXT_FLAGS],
|
|
14206
14967
|
examples: [
|
|
@@ -14215,6 +14976,10 @@ function createNextCommand(seams = DEFAULT_NEXT_SEAMS) {
|
|
|
14215
14976
|
{
|
|
14216
14977
|
cmd: "rafa next --yes=sync,plan",
|
|
14217
14978
|
note: "Fast-forwards the base and creates the next plan unasked, stopping at any other step."
|
|
14979
|
+
},
|
|
14980
|
+
{
|
|
14981
|
+
cmd: "rafa next --roadmap --yes=hop,plan,start,home",
|
|
14982
|
+
note: "Hops to the epic holding the blocker, plans and runs it, and comes home unasked."
|
|
14218
14983
|
}
|
|
14219
14984
|
],
|
|
14220
14985
|
outputs: ["text", "json"],
|
|
@@ -14253,6 +15018,11 @@ var wrapped2 = wrapPhaseZeroCommand({
|
|
|
14253
15018
|
description: "Plans the first line of the roadmap issue that is neither done nor taken, then as" + " `--issue`. The roadmap is `roadmap.issue` in the config, else the pinned issue titled `Roadmap`;" + " `--next=<n>` reads issue `<n>` as the roadmap instead.",
|
|
14254
15019
|
type: "boolean"
|
|
14255
15020
|
},
|
|
15021
|
+
{
|
|
15022
|
+
name: "roadmap",
|
|
15023
|
+
description: "With `--next`, plans the target of the `rafa next --roadmap` hop under way, the blocker it" + " went to another epic or board for, through the same checks as any line; with no hop away it picks" + " as `--next` does. Refused with exit 2 without `--next`.",
|
|
15024
|
+
type: "boolean"
|
|
15025
|
+
},
|
|
14256
15026
|
{
|
|
14257
15027
|
name: "refresh",
|
|
14258
15028
|
description: "Rebuilds a snapshot that no longer matches the issue without asking; the old copy moves under" + " `previous/`.",
|
|
@@ -14309,6 +15079,10 @@ var wrapped2 = wrapPhaseZeroCommand({
|
|
|
14309
15079
|
cmd: "rafa plan create --next --dry-run",
|
|
14310
15080
|
note: "Prints the first roadmap line that is neither done nor taken, with what it skipped to reach" + " it, and stops without writing a snapshot or starting a session."
|
|
14311
15081
|
},
|
|
15082
|
+
{
|
|
15083
|
+
cmd: "rafa plan create --next --roadmap",
|
|
15084
|
+
note: "While a rafa next --roadmap hop is away, plans the blocker it went for on that blocker's own" + " board; with none away, plans as rafa plan create --next does."
|
|
15085
|
+
},
|
|
14312
15086
|
{
|
|
14313
15087
|
cmd: "rafa plan create --issue=20 --refresh --skip-review --no-comment",
|
|
14314
15088
|
note: "Takes issue 20 as it reads now over the snapshot already there, plans without the planner" + " judging the spec first, and keeps any gaps off the board."
|
|
@@ -14321,8 +15095,8 @@ var wrapped2 = wrapPhaseZeroCommand({
|
|
|
14321
15095
|
var create_default2 = endingWith(wrapped2);
|
|
14322
15096
|
|
|
14323
15097
|
// src/commands/plan/risk.ts
|
|
14324
|
-
import { readFileSync as
|
|
14325
|
-
import { resolve as
|
|
15098
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
15099
|
+
import { resolve as resolve13 } from "path";
|
|
14326
15100
|
var COMMAND2 = "rafa plan risk";
|
|
14327
15101
|
var USAGE27 = "rafa plan risk [<plan>] [--strict]";
|
|
14328
15102
|
var STRICT_HINT = `Type the plan first: ${COMMAND2} <plan> --strict`;
|
|
@@ -14335,7 +15109,7 @@ function riskPlanPath(root, planDir, typed) {
|
|
|
14335
15109
|
return path;
|
|
14336
15110
|
if (typed !== null)
|
|
14337
15111
|
throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
|
|
14338
|
-
const lookedFor = [...new Set([
|
|
15112
|
+
const lookedFor = [...new Set([resolve13(root, planDir, DEFAULT_PLAN_FILE), resolve13(root, DEFAULT_PLAN_FILE)])];
|
|
14339
15113
|
throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
|
|
14340
15114
|
Usage: ${USAGE27}`);
|
|
14341
15115
|
}
|
|
@@ -14349,7 +15123,7 @@ function strictRefusal(report2) {
|
|
|
14349
15123
|
}
|
|
14350
15124
|
async function readRisk(path, project, config, context, seams) {
|
|
14351
15125
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
14352
|
-
return assessPlanRisk({ path, text:
|
|
15126
|
+
return assessPlanRisk({ path, text: readFileSync17(path, "utf8") }, {
|
|
14353
15127
|
repoRoot: project.root,
|
|
14354
15128
|
home: project.home,
|
|
14355
15129
|
settingSources: config.settingSources,
|
|
@@ -14432,8 +15206,8 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
14432
15206
|
var risk_default = createPlanRiskCommand();
|
|
14433
15207
|
|
|
14434
15208
|
// src/commands/plan/show.ts
|
|
14435
|
-
import { readFileSync as
|
|
14436
|
-
import { join as
|
|
15209
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
15210
|
+
import { join as join34 } from "path";
|
|
14437
15211
|
var USAGE28 = "rafa plan show <stub> [--tracker]";
|
|
14438
15212
|
var COMMAND3 = "rafa plan show";
|
|
14439
15213
|
function readTrackerFlag(value) {
|
|
@@ -14451,10 +15225,10 @@ function showPlan(plans, stub, tracker) {
|
|
|
14451
15225
|
throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
|
|
14452
15226
|
}
|
|
14453
15227
|
const name2 = planFileName(stub, tracker);
|
|
14454
|
-
const file =
|
|
15228
|
+
const file = join34(plans.path, name2);
|
|
14455
15229
|
if (!isFile(file))
|
|
14456
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
14457
|
-
const model = parsePlan(
|
|
15230
|
+
throw new CommandExit(1, missingText(stub, tracker, join34(plans.label, name2)));
|
|
15231
|
+
const model = parsePlan(readFileSync18(file, "utf8"));
|
|
14458
15232
|
const { header } = model;
|
|
14459
15233
|
return {
|
|
14460
15234
|
stub,
|
|
@@ -14477,7 +15251,7 @@ function taskLine(task) {
|
|
|
14477
15251
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
14478
15252
|
}
|
|
14479
15253
|
function renderShownPlan(shown, dirLabel) {
|
|
14480
|
-
const relative7 =
|
|
15254
|
+
const relative7 = join34(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
14481
15255
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
14482
15256
|
const lines2 = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
|
|
14483
15257
|
if (shown.header.issue !== null)
|
|
@@ -14897,24 +15671,24 @@ function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
14897
15671
|
var list_default9 = createPrListCommand();
|
|
14898
15672
|
|
|
14899
15673
|
// src/commands/pr/merge.ts
|
|
14900
|
-
import { existsSync as
|
|
14901
|
-
import { join as
|
|
15674
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
15675
|
+
import { join as join36 } from "path";
|
|
14902
15676
|
|
|
14903
15677
|
// src/runtime/install.ts
|
|
14904
15678
|
import {
|
|
14905
15679
|
chmodSync,
|
|
14906
15680
|
copyFileSync as copyFileSync4,
|
|
14907
|
-
existsSync as
|
|
15681
|
+
existsSync as existsSync15,
|
|
14908
15682
|
mkdirSync as mkdirSync6,
|
|
14909
15683
|
readdirSync as readdirSync4,
|
|
14910
|
-
readFileSync as
|
|
15684
|
+
readFileSync as readFileSync19,
|
|
14911
15685
|
realpathSync as realpathSync5,
|
|
14912
15686
|
renameSync as renameSync2,
|
|
14913
|
-
rmSync as
|
|
15687
|
+
rmSync as rmSync5,
|
|
14914
15688
|
statSync as statSync11,
|
|
14915
15689
|
symlinkSync
|
|
14916
15690
|
} from "fs";
|
|
14917
|
-
import { basename as basename6, delimiter as delimiter2, dirname as
|
|
15691
|
+
import { basename as basename6, delimiter as delimiter2, dirname as dirname7, join as join35, relative as relative7, resolve as resolve14 } from "path";
|
|
14918
15692
|
var LINK_NAME = "rafa";
|
|
14919
15693
|
var FORCE_FLAG = "--force";
|
|
14920
15694
|
var EXIT_DONE = 0;
|
|
@@ -14975,20 +15749,20 @@ function runtimeRefusalLines(runtimeDir, version2) {
|
|
|
14975
15749
|
];
|
|
14976
15750
|
}
|
|
14977
15751
|
function listTrackers(repoRoot, planDir) {
|
|
14978
|
-
const dir =
|
|
14979
|
-
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) =>
|
|
15752
|
+
const dir = resolve14(repoRoot, planDir);
|
|
15753
|
+
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) => join35(dir, entry)).filter((path) => statSync11(path).isFile()).map((path) => relative7(repoRoot, path));
|
|
14980
15754
|
}
|
|
14981
15755
|
function openTrackers(repoRoot, planDir) {
|
|
14982
15756
|
return listTrackers(repoRoot, planDir).flatMap((path) => {
|
|
14983
|
-
const task = findNextTask(
|
|
15757
|
+
const task = findNextTask(readFileSync19(join35(repoRoot, path), "utf8"));
|
|
14984
15758
|
return task ? [{ path, task }] : [];
|
|
14985
15759
|
});
|
|
14986
15760
|
}
|
|
14987
15761
|
function readManifestVersion2(repoRoot) {
|
|
14988
|
-
const manifest = JSON.parse(
|
|
15762
|
+
const manifest = JSON.parse(readFileSync19(join35(repoRoot, "package.json"), "utf8"));
|
|
14989
15763
|
const record = typeof manifest === "object" && manifest !== null ? manifest : {};
|
|
14990
15764
|
if (record["name"] !== name) {
|
|
14991
|
-
throw new Error(`${
|
|
15765
|
+
throw new Error(`${join35(repoRoot, "package.json")} names ${JSON.stringify(record["name"])}, not ${name}: this is no rafa checkout`);
|
|
14992
15766
|
}
|
|
14993
15767
|
const version2 = record["version"];
|
|
14994
15768
|
if (typeof version2 !== "string" || !VERSION_SHAPE.test(version2)) {
|
|
@@ -15001,8 +15775,8 @@ function copyTree(from, to) {
|
|
|
15001
15775
|
let copied = 0;
|
|
15002
15776
|
const entries = readdirSync4(from, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
15003
15777
|
for (const entry of entries) {
|
|
15004
|
-
const source =
|
|
15005
|
-
const target =
|
|
15778
|
+
const source = join35(from, entry.name);
|
|
15779
|
+
const target = join35(to, entry.name);
|
|
15006
15780
|
if (entry.isDirectory()) {
|
|
15007
15781
|
copied += copyTree(source, target);
|
|
15008
15782
|
} else if (entry.isFile()) {
|
|
@@ -15016,23 +15790,23 @@ function copyTree(from, to) {
|
|
|
15016
15790
|
}
|
|
15017
15791
|
function replaceTree(from, to) {
|
|
15018
15792
|
const staging = temporaryName(to);
|
|
15019
|
-
|
|
15793
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
15020
15794
|
try {
|
|
15021
15795
|
const copied = copyTree(from, staging);
|
|
15022
15796
|
swapIn2(staging, to);
|
|
15023
15797
|
return copied;
|
|
15024
15798
|
} catch (err) {
|
|
15025
|
-
|
|
15799
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
15026
15800
|
throw err;
|
|
15027
15801
|
}
|
|
15028
15802
|
}
|
|
15029
15803
|
function swapIn2(staging, to) {
|
|
15030
|
-
if (!
|
|
15804
|
+
if (!existsSync15(to)) {
|
|
15031
15805
|
renameSync2(staging, to);
|
|
15032
15806
|
return;
|
|
15033
15807
|
}
|
|
15034
15808
|
const outgoing = `${temporaryName(to)}.outgoing`;
|
|
15035
|
-
|
|
15809
|
+
rmSync5(outgoing, { recursive: true, force: true });
|
|
15036
15810
|
renameSync2(to, outgoing);
|
|
15037
15811
|
try {
|
|
15038
15812
|
renameSync2(staging, to);
|
|
@@ -15040,17 +15814,17 @@ function swapIn2(staging, to) {
|
|
|
15040
15814
|
renameSync2(outgoing, to);
|
|
15041
15815
|
throw err;
|
|
15042
15816
|
}
|
|
15043
|
-
|
|
15817
|
+
rmSync5(outgoing, { recursive: true, force: true });
|
|
15044
15818
|
}
|
|
15045
15819
|
function relink(linkPath, target) {
|
|
15046
|
-
mkdirSync6(
|
|
15820
|
+
mkdirSync6(dirname7(linkPath), { recursive: true });
|
|
15047
15821
|
const temporary = temporaryName(linkPath);
|
|
15048
|
-
|
|
15822
|
+
rmSync5(temporary, { force: true });
|
|
15049
15823
|
try {
|
|
15050
15824
|
symlinkSync(target, temporary);
|
|
15051
15825
|
renameSync2(temporary, linkPath);
|
|
15052
15826
|
} catch (err) {
|
|
15053
|
-
|
|
15827
|
+
rmSync5(temporary, { force: true });
|
|
15054
15828
|
throw err;
|
|
15055
15829
|
}
|
|
15056
15830
|
const resolved = realpathSync5(linkPath);
|
|
@@ -15066,7 +15840,7 @@ function runBuild(repoRoot, lines2) {
|
|
|
15066
15840
|
cwd: repoRoot,
|
|
15067
15841
|
env: {
|
|
15068
15842
|
...process.env,
|
|
15069
|
-
PATH: [
|
|
15843
|
+
PATH: [dirname7(process.execPath), process.env["PATH"] ?? ""].join(delimiter2)
|
|
15070
15844
|
},
|
|
15071
15845
|
stdin: "ignore",
|
|
15072
15846
|
stdout: stdio,
|
|
@@ -15097,20 +15871,20 @@ function installRuntime(seams, options = {}) {
|
|
|
15097
15871
|
}
|
|
15098
15872
|
function runInstall(seams, options) {
|
|
15099
15873
|
const { repoRoot, home, log } = seams;
|
|
15100
|
-
const runtimeRoot =
|
|
15101
|
-
const linkPath =
|
|
15874
|
+
const runtimeRoot = join35(home, RUNTIME_SUBDIR);
|
|
15875
|
+
const linkPath = join35(home, RAFA_BIN_DIR, LINK_NAME);
|
|
15102
15876
|
log(`repo root: ${repoRoot}`);
|
|
15103
15877
|
log(`runtime root: ${runtimeRoot}`);
|
|
15104
15878
|
log(`link: ${linkPath}`);
|
|
15105
15879
|
const version2 = inStage("manifest", () => readManifestVersion2(repoRoot));
|
|
15106
|
-
const planDir = inStage("config", () =>
|
|
15880
|
+
const planDir = inStage("config", () => resolve14(repoRoot, loadConfig({ root: repoRoot, home }, {}, seams.warn).config.planDir));
|
|
15107
15881
|
log(`plan dir: ${planDir}`);
|
|
15108
15882
|
const open = inStage("trackers", () => openTrackers(repoRoot, planDir));
|
|
15109
15883
|
if (open.length > 0)
|
|
15110
15884
|
return { kind: "refused", reason: "trackers", planDir, trackers: open };
|
|
15111
|
-
const distDir =
|
|
15112
|
-
const runtimeDir =
|
|
15113
|
-
const installed =
|
|
15885
|
+
const distDir = join35(repoRoot, "dist");
|
|
15886
|
+
const runtimeDir = join35(runtimeRoot, version2);
|
|
15887
|
+
const installed = existsSync15(runtimeDir);
|
|
15114
15888
|
if (installed && options.force !== true) {
|
|
15115
15889
|
return { kind: "refused", reason: "runtime-exists", version: version2, runtimeDir };
|
|
15116
15890
|
}
|
|
@@ -15118,13 +15892,13 @@ function runInstall(seams, options) {
|
|
|
15118
15892
|
const code = inStage("build", () => seams.build(repoRoot));
|
|
15119
15893
|
if (code !== 0)
|
|
15120
15894
|
throw new StageFailure("build", `the build exited ${code}`);
|
|
15121
|
-
if (!
|
|
15122
|
-
throw new StageFailure("build", `the build exited 0 and left no ${
|
|
15895
|
+
if (!existsSync15(join35(distDir, RUNTIME_ENTRY))) {
|
|
15896
|
+
throw new StageFailure("build", `the build exited 0 and left no ${join35(distDir, RUNTIME_ENTRY)}`);
|
|
15123
15897
|
}
|
|
15124
15898
|
log(installed ? `copying ${distDir} over ${runtimeDir}, replacing it whole` : `copying ${distDir} into ${runtimeDir}`);
|
|
15125
15899
|
const copied = inStage("copy", () => replaceTree(distDir, runtimeDir));
|
|
15126
15900
|
log(`copied ${copied} file(s)`);
|
|
15127
|
-
const resolved = inStage("link", () => relink(linkPath,
|
|
15901
|
+
const resolved = inStage("link", () => relink(linkPath, join35(runtimeDir, RUNTIME_ENTRY)));
|
|
15128
15902
|
log(`${linkPath} resolves to ${resolved}`);
|
|
15129
15903
|
return { kind: "done", version: version2, runtimeDir, linkPath, resolved, copied };
|
|
15130
15904
|
}
|
|
@@ -15153,12 +15927,12 @@ function replaceFile(source, target) {
|
|
|
15153
15927
|
chmodSync(temporary, statSync11(source).mode & 4095);
|
|
15154
15928
|
renameSync2(temporary, target);
|
|
15155
15929
|
} catch (err) {
|
|
15156
|
-
|
|
15930
|
+
rmSync5(temporary, { force: true });
|
|
15157
15931
|
throw err;
|
|
15158
15932
|
}
|
|
15159
15933
|
}
|
|
15160
15934
|
function temporaryName(path) {
|
|
15161
|
-
return
|
|
15935
|
+
return join35(dirname7(path), `.${basename6(path)}.snapshot-${process.pid}`);
|
|
15162
15936
|
}
|
|
15163
15937
|
|
|
15164
15938
|
// src/commands/pr/merge-followups.ts
|
|
@@ -15555,13 +16329,13 @@ function localHoldsBranch(git, branch, warn3) {
|
|
|
15555
16329
|
}
|
|
15556
16330
|
function textOf(path) {
|
|
15557
16331
|
try {
|
|
15558
|
-
return
|
|
16332
|
+
return readFileSync20(path, "utf8");
|
|
15559
16333
|
} catch {
|
|
15560
16334
|
return "";
|
|
15561
16335
|
}
|
|
15562
16336
|
}
|
|
15563
16337
|
function followUpsFor(pr, git) {
|
|
15564
|
-
const facts = readPackageFacts(textOf(
|
|
16338
|
+
const facts = readPackageFacts(textOf(join36(pr.project.root, "package.json")));
|
|
15565
16339
|
if (facts.version === null)
|
|
15566
16340
|
return [];
|
|
15567
16341
|
const tags = git(["tag", "--list", versionTag(facts.version)]);
|
|
@@ -15569,7 +16343,7 @@ function followUpsFor(pr, git) {
|
|
|
15569
16343
|
version: facts.version,
|
|
15570
16344
|
tagged: tags.ok && tags.stdout.trim() !== "",
|
|
15571
16345
|
rafaCheckout: facts.rafaCheckout,
|
|
15572
|
-
runtimeInstalled:
|
|
16346
|
+
runtimeInstalled: existsSync16(join36(pr.project.home, RUNTIME_SUBDIR, facts.version))
|
|
15573
16347
|
});
|
|
15574
16348
|
}
|
|
15575
16349
|
function openGh(pr, seams) {
|
|
@@ -15699,7 +16473,7 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
|
|
|
15699
16473
|
context.output.info(`Commented on #${number} that it was merged with no checks: ${posted.url}`);
|
|
15700
16474
|
return posted.url;
|
|
15701
16475
|
}
|
|
15702
|
-
async function
|
|
16476
|
+
async function runMerge2(context, seams) {
|
|
15703
16477
|
const yes = readBooleanFlag(context.flags, "yes", USAGE31);
|
|
15704
16478
|
const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE31);
|
|
15705
16479
|
const wanted = readMethodFlag(context.flags, USAGE31);
|
|
@@ -15834,7 +16608,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15834
16608
|
],
|
|
15835
16609
|
outputs: ["text", "json"],
|
|
15836
16610
|
run: async (context) => {
|
|
15837
|
-
const merged2 = await
|
|
16611
|
+
const merged2 = await runMerge2(context, seams);
|
|
15838
16612
|
if (context.outputMode === "json")
|
|
15839
16613
|
context.output.result(merged2);
|
|
15840
16614
|
if (merged2.merged)
|
|
@@ -15843,7 +16617,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15843
16617
|
};
|
|
15844
16618
|
return Object.freeze(command);
|
|
15845
16619
|
}
|
|
15846
|
-
var
|
|
16620
|
+
var merge_default2 = createPrMergeCommand();
|
|
15847
16621
|
|
|
15848
16622
|
// src/pr/triage/evidence.ts
|
|
15849
16623
|
var FAILED_LOG_TAIL_LINES = 40;
|
|
@@ -16600,7 +17374,7 @@ function fileList(files) {
|
|
|
16600
17374
|
function conflictReason(files) {
|
|
16601
17375
|
return files.length === 0 ? "the head conflicts with the base and no conflicting file was read" : `the head conflicts with the base on ${fileList(files)}`;
|
|
16602
17376
|
}
|
|
16603
|
-
function
|
|
17377
|
+
function failureReason(step, failing) {
|
|
16604
17378
|
const count2 = checkCount(failing.length);
|
|
16605
17379
|
return step === undefined ? `${count2} failing, and no failing step was named` : `${count2} failing, the failing step being "${step.name}"`;
|
|
16606
17380
|
}
|
|
@@ -16632,7 +17406,7 @@ function decideClass(input, verdict, conflicting) {
|
|
|
16632
17406
|
triageClass: classifyFailedStep(step?.name),
|
|
16633
17407
|
files: [],
|
|
16634
17408
|
step,
|
|
16635
|
-
reason:
|
|
17409
|
+
reason: failureReason(step, failingRows(rows))
|
|
16636
17410
|
};
|
|
16637
17411
|
}
|
|
16638
17412
|
if (verdict === "none") {
|
|
@@ -16807,8 +17581,8 @@ function describeAge(updatedAt, now) {
|
|
|
16807
17581
|
return `${plural4(hours, "hour", "hours")} ago`;
|
|
16808
17582
|
return `${plural4(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
16809
17583
|
}
|
|
16810
|
-
function triageCommand(number,
|
|
16811
|
-
return
|
|
17584
|
+
function triageCommand(number, resolve15 = false) {
|
|
17585
|
+
return resolve15 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
16812
17586
|
}
|
|
16813
17587
|
function joinNumbers(pulls) {
|
|
16814
17588
|
const names = pulls.map((pull) => `#${pull.number}`);
|
|
@@ -16819,14 +17593,14 @@ function joinNumbers(pulls) {
|
|
|
16819
17593
|
function byNumber(pulls) {
|
|
16820
17594
|
return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
|
|
16821
17595
|
}
|
|
16822
|
-
function skipped(pull, now,
|
|
17596
|
+
function skipped(pull, now, resolve15) {
|
|
16823
17597
|
return Object.freeze({
|
|
16824
17598
|
number: pull.number,
|
|
16825
17599
|
title: pull.title,
|
|
16826
17600
|
updatedAt: pull.updatedAt,
|
|
16827
17601
|
age: describeAge(pull.updatedAt, now),
|
|
16828
17602
|
fresh: isFreshCandidate(pull.updatedAt, now),
|
|
16829
|
-
command: triageCommand(pull.number,
|
|
17603
|
+
command: triageCommand(pull.number, resolve15)
|
|
16830
17604
|
});
|
|
16831
17605
|
}
|
|
16832
17606
|
function skippedLine(one) {
|
|
@@ -16870,15 +17644,15 @@ function listReading(rest, count2) {
|
|
|
16870
17644
|
return listing("list", Object.freeze([]), rest, headline);
|
|
16871
17645
|
}
|
|
16872
17646
|
function selectTriagePullRequests(input) {
|
|
16873
|
-
const
|
|
17647
|
+
const resolve15 = input.resolve === true;
|
|
16874
17648
|
const { current, now } = input;
|
|
16875
17649
|
if (current !== null) {
|
|
16876
17650
|
return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
|
|
16877
17651
|
}
|
|
16878
17652
|
const candidates = byNumber(input.candidates);
|
|
16879
17653
|
const count2 = candidates.length;
|
|
16880
|
-
const lines2 = candidates.map((pull) => skipped(pull, now,
|
|
16881
|
-
if (
|
|
17654
|
+
const lines2 = candidates.map((pull) => skipped(pull, now, resolve15));
|
|
17655
|
+
if (resolve15 && count2 > 1)
|
|
16882
17656
|
return refusal12(lines2, count2);
|
|
16883
17657
|
if (count2 === 0) {
|
|
16884
17658
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
@@ -17213,10 +17987,10 @@ function withTaskBudget(plan2, usd) {
|
|
|
17213
17987
|
}
|
|
17214
17988
|
|
|
17215
17989
|
// src/pr/plans/load.ts
|
|
17216
|
-
import { existsSync as
|
|
17217
|
-
import { dirname as
|
|
17990
|
+
import { existsSync as existsSync17, readFileSync as readFileSync21 } from "fs";
|
|
17991
|
+
import { dirname as dirname8, join as join37 } from "path";
|
|
17218
17992
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17219
|
-
var MODULE_DIR4 =
|
|
17993
|
+
var MODULE_DIR4 = dirname8(fileURLToPath4(import.meta.url));
|
|
17220
17994
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
17221
17995
|
var PINNED_PLAN_CLASSES = Object.freeze([
|
|
17222
17996
|
...SIMPLE_TRIAGE_CLASSES,
|
|
@@ -17231,8 +18005,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
17231
18005
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
17232
18006
|
const name2 = pinnedPlanFileName(triageClass);
|
|
17233
18007
|
return [
|
|
17234
|
-
|
|
17235
|
-
|
|
18008
|
+
join37(moduleDir, PINNED_PLANS_DIRNAME, name2),
|
|
18009
|
+
join37(moduleDir, name2)
|
|
17236
18010
|
];
|
|
17237
18011
|
}
|
|
17238
18012
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -17240,11 +18014,11 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
17240
18014
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
17241
18015
|
}
|
|
17242
18016
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
17243
|
-
const found = candidates.find((candidate) =>
|
|
18017
|
+
const found = candidates.find((candidate) => existsSync17(candidate));
|
|
17244
18018
|
if (found === undefined) {
|
|
17245
18019
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
17246
18020
|
}
|
|
17247
|
-
return
|
|
18021
|
+
return readFileSync21(found, "utf8");
|
|
17248
18022
|
}
|
|
17249
18023
|
var SLOT_PATTERN2 = /\{[A-Z][A-Z0-9_]*\}/g;
|
|
17250
18024
|
var CONFLICT_SENTENCE_SLOT = "CONFLICT_SENTENCE";
|
|
@@ -17384,21 +18158,21 @@ function readAttemptRepeat(input) {
|
|
|
17384
18158
|
}
|
|
17385
18159
|
|
|
17386
18160
|
// src/commands/pr/resolve-loop.ts
|
|
17387
|
-
import { mkdirSync as mkdirSync7, writeFileSync as
|
|
17388
|
-
import { join as
|
|
17389
|
-
var RESOLVE_SUBDIR =
|
|
18161
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
18162
|
+
import { join as join38 } from "path";
|
|
18163
|
+
var RESOLVE_SUBDIR = join38(".rafa", "resolve");
|
|
17390
18164
|
var RUN_PREFIX = "pr-";
|
|
17391
18165
|
var ATTEMPT_PREFIX = "attempt-";
|
|
17392
18166
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
17393
18167
|
var CHILD_OUTPUT = "text";
|
|
17394
18168
|
function resolveRunDir(home, number, attempt3) {
|
|
17395
|
-
return
|
|
18169
|
+
return join38(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
17396
18170
|
}
|
|
17397
18171
|
function writeResolvePlan(write) {
|
|
17398
18172
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
17399
18173
|
mkdirSync7(dir, { recursive: true });
|
|
17400
|
-
const path =
|
|
17401
|
-
|
|
18174
|
+
const path = join38(dir, pinnedPlanFileName(write.triageClass));
|
|
18175
|
+
writeFileSync6(path, write.plan, "utf8");
|
|
17402
18176
|
return path;
|
|
17403
18177
|
}
|
|
17404
18178
|
function resolveLoopArgv(planPath) {
|
|
@@ -17661,7 +18435,7 @@ async function resolvePullRequest(run) {
|
|
|
17661
18435
|
throw new CommandExit(RESOLVE_REFUSE_EXIT, `\u274C ${refusal13}`);
|
|
17662
18436
|
return runAttempts(run, detail, first);
|
|
17663
18437
|
}
|
|
17664
|
-
function
|
|
18438
|
+
function ended(run, state, parts) {
|
|
17665
18439
|
return {
|
|
17666
18440
|
ran: parts.ran,
|
|
17667
18441
|
resolved: parts.resolved,
|
|
@@ -17689,7 +18463,7 @@ async function runAttempts(run, detail, first) {
|
|
|
17689
18463
|
if (start2.stopped) {
|
|
17690
18464
|
await writeResolveComment(run, detail, state.assessment, state.spent, false, workflowCountOf2(state.reading));
|
|
17691
18465
|
const removal2 = closeWorktree(run, resolveWorktreePath(run.home, run.number));
|
|
17692
|
-
return
|
|
18466
|
+
return ended(run, state, {
|
|
17693
18467
|
ran: state.lines.length > 0,
|
|
17694
18468
|
resolved: false,
|
|
17695
18469
|
stop: start2.stop,
|
|
@@ -17721,7 +18495,7 @@ async function runAttempts(run, detail, first) {
|
|
|
17721
18495
|
state.evidence = end.evidence;
|
|
17722
18496
|
if (end.green) {
|
|
17723
18497
|
await writeResolveComment(run, detail, state.assessment, state.spent, true, workflowCountOf2(state.reading));
|
|
17724
|
-
return
|
|
18498
|
+
return ended(run, state, {
|
|
17725
18499
|
ran: true,
|
|
17726
18500
|
resolved: true,
|
|
17727
18501
|
stop: null,
|
|
@@ -17745,7 +18519,7 @@ async function runAttempts(run, detail, first) {
|
|
|
17745
18519
|
continue;
|
|
17746
18520
|
await writeResolveComment(run, detail, state.assessment, state.spent, false, workflowCountOf2(state.reading));
|
|
17747
18521
|
const removal = closeWorktree(run, worktree);
|
|
17748
|
-
return
|
|
18522
|
+
return ended(run, state, {
|
|
17749
18523
|
ran: true,
|
|
17750
18524
|
resolved: false,
|
|
17751
18525
|
stop: repeat.stop,
|
|
@@ -17797,16 +18571,16 @@ async function redPullRequests(pr) {
|
|
|
17797
18571
|
}
|
|
17798
18572
|
return red;
|
|
17799
18573
|
}
|
|
17800
|
-
async function selectTargets(pr, warn3, now,
|
|
18574
|
+
async function selectTargets(pr, warn3, now, resolve15) {
|
|
17801
18575
|
const branch = readBranchOrNone(pr, warn3);
|
|
17802
18576
|
const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
17803
18577
|
if (current !== null)
|
|
17804
|
-
return selectTriagePullRequests({ current, candidates: [], now, resolve:
|
|
18578
|
+
return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve15 });
|
|
17805
18579
|
return selectTriagePullRequests({
|
|
17806
18580
|
current: null,
|
|
17807
18581
|
candidates: await redPullRequests(pr),
|
|
17808
18582
|
now,
|
|
17809
|
-
resolve:
|
|
18583
|
+
resolve: resolve15
|
|
17810
18584
|
});
|
|
17811
18585
|
}
|
|
17812
18586
|
async function detailOf4(pr, number) {
|
|
@@ -17935,7 +18709,7 @@ async function runTriage(context, seams) {
|
|
|
17935
18709
|
}
|
|
17936
18710
|
const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
|
|
17937
18711
|
const readings = [];
|
|
17938
|
-
let
|
|
18712
|
+
let resolve15 = null;
|
|
17939
18713
|
for (const number of numbers) {
|
|
17940
18714
|
const assess = () => assessOne({
|
|
17941
18715
|
pr,
|
|
@@ -17954,7 +18728,7 @@ async function runTriage(context, seams) {
|
|
|
17954
18728
|
readings.push(reading);
|
|
17955
18729
|
continue;
|
|
17956
18730
|
}
|
|
17957
|
-
|
|
18731
|
+
resolve15 = await resolvePullRequest({
|
|
17958
18732
|
trust: {
|
|
17959
18733
|
permissions,
|
|
17960
18734
|
trustedAuthors: pr.trustedAuthors,
|
|
@@ -17975,16 +18749,16 @@ async function runTriage(context, seams) {
|
|
|
17975
18749
|
...seams.clock === undefined ? {} : { clock: seams.clock },
|
|
17976
18750
|
...seams.sleep === undefined ? {} : { sleep: seams.sleep }
|
|
17977
18751
|
});
|
|
17978
|
-
readings.push(
|
|
18752
|
+
readings.push(resolve15.reading);
|
|
17979
18753
|
}
|
|
17980
18754
|
const text = [
|
|
17981
18755
|
...selection === null ? [] : [selection.message],
|
|
17982
18756
|
...readings.length === 0 ? [] : [renderTriages(readings)],
|
|
17983
|
-
...
|
|
18757
|
+
...resolve15 === null ? [] : resolve15.lines
|
|
17984
18758
|
].join(`
|
|
17985
18759
|
|
|
17986
18760
|
`);
|
|
17987
|
-
return { selection, readings, resolve:
|
|
18761
|
+
return { selection, readings, resolve: resolve15, text, exitCode: resolve15?.exitCode ?? 0 };
|
|
17988
18762
|
}
|
|
17989
18763
|
function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
17990
18764
|
const command = {
|
|
@@ -18273,8 +19047,8 @@ function createPrWaitCommand(seams = DEFAULT_WAIT_SEAMS) {
|
|
|
18273
19047
|
var wait_default = createPrWaitCommand();
|
|
18274
19048
|
|
|
18275
19049
|
// src/commands/release/tag.ts
|
|
18276
|
-
import { readFileSync as
|
|
18277
|
-
import { join as
|
|
19050
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
19051
|
+
import { join as join39 } from "path";
|
|
18278
19052
|
|
|
18279
19053
|
// src/commands/release/release-commit.ts
|
|
18280
19054
|
function unread2(reason, message) {
|
|
@@ -18332,13 +19106,13 @@ function readBranch2(git) {
|
|
|
18332
19106
|
}
|
|
18333
19107
|
function textOf2(path) {
|
|
18334
19108
|
try {
|
|
18335
|
-
return
|
|
19109
|
+
return readFileSync22(path, "utf8");
|
|
18336
19110
|
} catch {
|
|
18337
19111
|
return null;
|
|
18338
19112
|
}
|
|
18339
19113
|
}
|
|
18340
19114
|
function readVersion(root, configured) {
|
|
18341
|
-
const resolved =
|
|
19115
|
+
const resolved = join39(root, configured);
|
|
18342
19116
|
const text = textOf2(resolved);
|
|
18343
19117
|
if (text === null) {
|
|
18344
19118
|
return {
|
|
@@ -18352,7 +19126,7 @@ function readVersion(root, configured) {
|
|
|
18352
19126
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
18353
19127
|
}
|
|
18354
19128
|
function readNewestRelease(root, configured) {
|
|
18355
|
-
const resolved =
|
|
19129
|
+
const resolved = join39(root, configured);
|
|
18356
19130
|
const text = textOf2(resolved);
|
|
18357
19131
|
if (text === null) {
|
|
18358
19132
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -18693,16 +19467,16 @@ var self_update_default = createSelfUpdateCommand();
|
|
|
18693
19467
|
|
|
18694
19468
|
// src/commands/skill/backfill.ts
|
|
18695
19469
|
import { statSync as statSync13 } from "fs";
|
|
18696
|
-
import { join as
|
|
19470
|
+
import { join as join44, resolve as resolve17 } from "path";
|
|
18697
19471
|
|
|
18698
19472
|
// src/backfill/backup.ts
|
|
18699
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
18700
|
-
import { dirname as
|
|
19473
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
|
|
19474
|
+
import { dirname as dirname9, isAbsolute as isAbsolute8, join as join41, relative as relative8, resolve as resolve15, sep as sep5 } from "path";
|
|
18701
19475
|
|
|
18702
19476
|
// src/backfill/proposal-file.ts
|
|
18703
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
18704
|
-
import { join as
|
|
18705
|
-
var BACKFILL_PATH =
|
|
19477
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync23 } from "fs";
|
|
19478
|
+
import { join as join40 } from "path";
|
|
19479
|
+
var BACKFILL_PATH = join40(".rafa", "backfill");
|
|
18706
19480
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
18707
19481
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
18708
19482
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -18728,7 +19502,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
18728
19502
|
].join(`
|
|
18729
19503
|
`);
|
|
18730
19504
|
function proposalPath(dir, batch) {
|
|
18731
|
-
return
|
|
19505
|
+
return join40(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
18732
19506
|
}
|
|
18733
19507
|
function isProposalFileName(name2) {
|
|
18734
19508
|
return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -18864,9 +19638,9 @@ function readProposalFiles(dir) {
|
|
|
18864
19638
|
return [];
|
|
18865
19639
|
}
|
|
18866
19640
|
return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
|
|
18867
|
-
const path =
|
|
19641
|
+
const path = join40(dir, name2);
|
|
18868
19642
|
try {
|
|
18869
|
-
return { path, result: parseProposalFile(
|
|
19643
|
+
return { path, result: parseProposalFile(readFileSync23(path, "utf8")) };
|
|
18870
19644
|
} catch (error) {
|
|
18871
19645
|
return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
|
|
18872
19646
|
}
|
|
@@ -18874,19 +19648,19 @@ function readProposalFiles(dir) {
|
|
|
18874
19648
|
}
|
|
18875
19649
|
|
|
18876
19650
|
// src/backfill/backup.ts
|
|
18877
|
-
var BACKUP_PATH =
|
|
19651
|
+
var BACKUP_PATH = join41(BACKFILL_PATH, "backup");
|
|
18878
19652
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
18879
19653
|
function backupDirectory(base) {
|
|
18880
|
-
return
|
|
19654
|
+
return join41(base, BACKUP_PATH);
|
|
18881
19655
|
}
|
|
18882
19656
|
function isUnder(root, path) {
|
|
18883
|
-
const step = relative8(
|
|
19657
|
+
const step = relative8(resolve15(root), resolve15(path));
|
|
18884
19658
|
return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute8(step);
|
|
18885
19659
|
}
|
|
18886
19660
|
function backupPathFor(path, root, backupDir) {
|
|
18887
19661
|
if (!isUnder(root, path))
|
|
18888
19662
|
return null;
|
|
18889
|
-
return
|
|
19663
|
+
return join41(backupDir, relative8(resolve15(root), resolve15(path)));
|
|
18890
19664
|
}
|
|
18891
19665
|
function backupFile(path, options) {
|
|
18892
19666
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -18895,11 +19669,11 @@ function backupFile(path, options) {
|
|
|
18895
19669
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
18896
19670
|
if (backup === null)
|
|
18897
19671
|
return { path, kind: "outside", backup: null };
|
|
18898
|
-
if (!
|
|
19672
|
+
if (!existsSync18(path))
|
|
18899
19673
|
return { path, kind: "missing", backup: null };
|
|
18900
|
-
if (
|
|
19674
|
+
if (existsSync18(backup))
|
|
18901
19675
|
return { path, kind: "kept", backup };
|
|
18902
|
-
mkdirSync8(
|
|
19676
|
+
mkdirSync8(dirname9(backup), { recursive: true });
|
|
18903
19677
|
copyFileSync5(path, backup);
|
|
18904
19678
|
return { path, kind: "copied", backup };
|
|
18905
19679
|
}
|
|
@@ -18922,7 +19696,7 @@ function countBackups(results) {
|
|
|
18922
19696
|
}
|
|
18923
19697
|
|
|
18924
19698
|
// src/backfill/derive.ts
|
|
18925
|
-
import { readFileSync as
|
|
19699
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync7 } from "fs";
|
|
18926
19700
|
import { basename as basename7 } from "path";
|
|
18927
19701
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
18928
19702
|
function renderWhenToUse(trigger, prevents) {
|
|
@@ -19048,7 +19822,7 @@ function planFile(entry, options) {
|
|
|
19048
19822
|
}
|
|
19049
19823
|
let original;
|
|
19050
19824
|
try {
|
|
19051
|
-
original =
|
|
19825
|
+
original = readFileSync24(entry.path, "utf8");
|
|
19052
19826
|
} catch (error) {
|
|
19053
19827
|
return inert(entry, "skipped", `the file cannot be read (${error instanceof Error ? error.message : String(error)})`);
|
|
19054
19828
|
}
|
|
@@ -19092,7 +19866,7 @@ function planDerivation(root, options) {
|
|
|
19092
19866
|
function applyFile(action, options) {
|
|
19093
19867
|
if (action.text === null || action.original === null)
|
|
19094
19868
|
return action;
|
|
19095
|
-
const current =
|
|
19869
|
+
const current = readFileSync24(action.path, "utf8");
|
|
19096
19870
|
if (current !== action.original) {
|
|
19097
19871
|
return {
|
|
19098
19872
|
...action,
|
|
@@ -19101,7 +19875,7 @@ function applyFile(action, options) {
|
|
|
19101
19875
|
text: null
|
|
19102
19876
|
};
|
|
19103
19877
|
}
|
|
19104
|
-
|
|
19878
|
+
writeFileSync7(action.path, action.text, "utf8");
|
|
19105
19879
|
const after = failureLines(checkFile(action.path, "skill", {
|
|
19106
19880
|
projectRoot: options.projectRoot,
|
|
19107
19881
|
pathDirs: options.pathDirs
|
|
@@ -19109,7 +19883,7 @@ function applyFile(action, options) {
|
|
|
19109
19883
|
const added = addedFailures(action.before, after);
|
|
19110
19884
|
if (added.length === 0)
|
|
19111
19885
|
return action;
|
|
19112
|
-
|
|
19886
|
+
writeFileSync7(action.path, action.original, "utf8");
|
|
19113
19887
|
return {
|
|
19114
19888
|
...action,
|
|
19115
19889
|
kind: "refused",
|
|
@@ -19123,7 +19897,7 @@ function applyDerivation(plan2, options) {
|
|
|
19123
19897
|
}
|
|
19124
19898
|
|
|
19125
19899
|
// src/backfill/proposal-batch.ts
|
|
19126
|
-
import { readFileSync as
|
|
19900
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
19127
19901
|
import { relative as relative9 } from "path";
|
|
19128
19902
|
|
|
19129
19903
|
// src/demote/report.ts
|
|
@@ -19159,7 +19933,7 @@ function countVerdicts(rows) {
|
|
|
19159
19933
|
function escapeCell(value) {
|
|
19160
19934
|
return value.replace(/\s+/g, " ").trim().replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
19161
19935
|
}
|
|
19162
|
-
function
|
|
19936
|
+
function tableLine2(cells) {
|
|
19163
19937
|
return `| ${cells.join(" | ")} |`;
|
|
19164
19938
|
}
|
|
19165
19939
|
function splitTableRow(line) {
|
|
@@ -19214,7 +19988,7 @@ function summaryLine3(rows) {
|
|
|
19214
19988
|
return `${files}: ${parts.join(", ")}.`;
|
|
19215
19989
|
}
|
|
19216
19990
|
function writeDemotionReport(report2) {
|
|
19217
|
-
const rows = report2.rows.map((row2) =>
|
|
19991
|
+
const rows = report2.rows.map((row2) => tableLine2([
|
|
19218
19992
|
escapeCell(row2.path),
|
|
19219
19993
|
escapeCell(row2.hash),
|
|
19220
19994
|
escapeCell(row2.verdict),
|
|
@@ -19229,8 +20003,8 @@ function writeDemotionReport(report2) {
|
|
|
19229
20003
|
"",
|
|
19230
20004
|
summaryLine3(report2.rows),
|
|
19231
20005
|
"",
|
|
19232
|
-
|
|
19233
|
-
|
|
20006
|
+
tableLine2(DEMOTION_REPORT_COLUMNS),
|
|
20007
|
+
tableLine2(DEMOTION_REPORT_COLUMNS.map(() => "---")),
|
|
19234
20008
|
...rows,
|
|
19235
20009
|
""
|
|
19236
20010
|
].join(`
|
|
@@ -19422,7 +20196,7 @@ function readCandidate(root, entry) {
|
|
|
19422
20196
|
return null;
|
|
19423
20197
|
let text;
|
|
19424
20198
|
try {
|
|
19425
|
-
text =
|
|
20199
|
+
text = readFileSync25(entry.path, "utf8");
|
|
19426
20200
|
} catch {
|
|
19427
20201
|
return null;
|
|
19428
20202
|
}
|
|
@@ -19528,8 +20302,8 @@ function parseSessionAnswer(stdout) {
|
|
|
19528
20302
|
}
|
|
19529
20303
|
|
|
19530
20304
|
// src/backfill/propose.ts
|
|
19531
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
19532
|
-
import { join as
|
|
20305
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync8 } from "fs";
|
|
20306
|
+
import { join as join42, resolve as resolve16 } from "path";
|
|
19533
20307
|
var PROPOSAL_ACTION_KINDS = [
|
|
19534
20308
|
"applied",
|
|
19535
20309
|
"unchanged",
|
|
@@ -19613,7 +20387,7 @@ async function runProposalPass(options) {
|
|
|
19613
20387
|
const number = index + 1;
|
|
19614
20388
|
const file = await runProposalBatch(number, batch, options);
|
|
19615
20389
|
const path = proposalPath(options.backfillDir, number);
|
|
19616
|
-
|
|
20390
|
+
writeFileSync8(path, renderProposalFile(file), "utf8");
|
|
19617
20391
|
written.push({ path, file });
|
|
19618
20392
|
}
|
|
19619
20393
|
return { written, candidates: candidates.length };
|
|
@@ -19655,7 +20429,7 @@ function writeRow(path, row2, original, options) {
|
|
|
19655
20429
|
return inert2(path, row2, "unchanged", "every field the row proposes is already what the file carries");
|
|
19656
20430
|
}
|
|
19657
20431
|
const before = checkLines(path, options);
|
|
19658
|
-
|
|
20432
|
+
writeFileSync8(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
|
|
19659
20433
|
const added = addedFailures(before, checkLines(path, options));
|
|
19660
20434
|
if (added.length === 0) {
|
|
19661
20435
|
return {
|
|
@@ -19667,7 +20441,7 @@ function writeRow(path, row2, original, options) {
|
|
|
19667
20441
|
added: []
|
|
19668
20442
|
};
|
|
19669
20443
|
}
|
|
19670
|
-
|
|
20444
|
+
writeFileSync8(path, original, "utf8");
|
|
19671
20445
|
return {
|
|
19672
20446
|
path,
|
|
19673
20447
|
relative: row2.path,
|
|
@@ -19678,7 +20452,7 @@ function writeRow(path, row2, original, options) {
|
|
|
19678
20452
|
};
|
|
19679
20453
|
}
|
|
19680
20454
|
function applyRow(root, row2, options) {
|
|
19681
|
-
const path =
|
|
20455
|
+
const path = join42(root, ...row2.path.split("/"));
|
|
19682
20456
|
if (row2.status === UNANSWERED_ROW) {
|
|
19683
20457
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
19684
20458
|
}
|
|
@@ -19687,7 +20461,7 @@ function applyRow(root, row2, options) {
|
|
|
19687
20461
|
}
|
|
19688
20462
|
let original;
|
|
19689
20463
|
try {
|
|
19690
|
-
original =
|
|
20464
|
+
original = readFileSync26(path, "utf8");
|
|
19691
20465
|
} catch {
|
|
19692
20466
|
return inert2(path, row2, "refused", "the file the row names is gone");
|
|
19693
20467
|
}
|
|
@@ -19701,7 +20475,7 @@ function fileRefusal(file, root) {
|
|
|
19701
20475
|
if (file.status !== REVIEWED_FILE) {
|
|
19702
20476
|
return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
|
|
19703
20477
|
}
|
|
19704
|
-
return
|
|
20478
|
+
return resolve16(file.skills) === resolve16(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
|
|
19705
20479
|
}
|
|
19706
20480
|
function countProposalActions(actions) {
|
|
19707
20481
|
const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
|
|
@@ -19715,7 +20489,7 @@ function applyProposals(files, root, options) {
|
|
|
19715
20489
|
for (const file of files) {
|
|
19716
20490
|
const refusal13 = fileRefusal(file, root);
|
|
19717
20491
|
for (const row2 of file.rows) {
|
|
19718
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
20492
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join42(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
19719
20493
|
actions.push(action);
|
|
19720
20494
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
19721
20495
|
triggers[row2.name] = row2.trigger;
|
|
@@ -19726,13 +20500,13 @@ function applyProposals(files, root, options) {
|
|
|
19726
20500
|
}
|
|
19727
20501
|
|
|
19728
20502
|
// src/demote/select.ts
|
|
19729
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
19730
|
-
import { basename as basename8, dirname as
|
|
20503
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync27, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
|
|
20504
|
+
import { basename as basename8, dirname as dirname10, join as join43 } from "path";
|
|
19731
20505
|
var SKILLS_DIRECTORY = "skills";
|
|
19732
20506
|
var CLAUDE_DIRECTORY = ".claude";
|
|
19733
20507
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
19734
|
-
var DEMOTED_PATH =
|
|
19735
|
-
var INSTINCTS_PATH =
|
|
20508
|
+
var DEMOTED_PATH = join43(".rafa", "demoted");
|
|
20509
|
+
var INSTINCTS_PATH = join43(".rafa", "instincts");
|
|
19736
20510
|
var REPORT_FILE = "report.md";
|
|
19737
20511
|
function realOrSelf(path) {
|
|
19738
20512
|
try {
|
|
@@ -19751,18 +20525,18 @@ function samePath(left, right) {
|
|
|
19751
20525
|
function resolveDemotionScope(dir, home) {
|
|
19752
20526
|
if (basename8(dir) !== SKILLS_DIRECTORY)
|
|
19753
20527
|
return null;
|
|
19754
|
-
const claude =
|
|
20528
|
+
const claude = dirname10(dir);
|
|
19755
20529
|
if (basename8(claude) !== CLAUDE_DIRECTORY)
|
|
19756
20530
|
return null;
|
|
19757
|
-
const base =
|
|
19758
|
-
const demotedDir =
|
|
20531
|
+
const base = dirname10(claude);
|
|
20532
|
+
const demotedDir = join43(base, DEMOTED_PATH);
|
|
19759
20533
|
return {
|
|
19760
20534
|
scope: samePath(base, home) ? "user" : "project",
|
|
19761
20535
|
dir,
|
|
19762
20536
|
base,
|
|
19763
20537
|
demotedDir,
|
|
19764
|
-
instinctsDir:
|
|
19765
|
-
reportPath:
|
|
20538
|
+
instinctsDir: join43(base, INSTINCTS_PATH),
|
|
20539
|
+
reportPath: join43(demotedDir, REPORT_FILE)
|
|
19766
20540
|
};
|
|
19767
20541
|
}
|
|
19768
20542
|
function entryKind(path) {
|
|
@@ -19788,7 +20562,7 @@ function readSelected(absolute, path, entries) {
|
|
|
19788
20562
|
let text;
|
|
19789
20563
|
let mtime;
|
|
19790
20564
|
try {
|
|
19791
|
-
text =
|
|
20565
|
+
text = readFileSync27(absolute, "utf8");
|
|
19792
20566
|
mtime = statSync12(absolute).mtime.toISOString();
|
|
19793
20567
|
} catch {
|
|
19794
20568
|
return null;
|
|
@@ -19804,14 +20578,14 @@ function selectsLearned(text, scope) {
|
|
|
19804
20578
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
19805
20579
|
}
|
|
19806
20580
|
function selectLearned(scope) {
|
|
19807
|
-
const dir =
|
|
20581
|
+
const dir = join43(scope.dir, LEARNED_DIRECTORY);
|
|
19808
20582
|
if (entryKind(dir) !== "directory")
|
|
19809
20583
|
return [];
|
|
19810
20584
|
const selected = [];
|
|
19811
20585
|
for (const name2 of visibleNames(dir)) {
|
|
19812
20586
|
if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
|
|
19813
20587
|
continue;
|
|
19814
|
-
const absolute =
|
|
20588
|
+
const absolute = join43(dir, name2);
|
|
19815
20589
|
if (entryKind(absolute) !== "file")
|
|
19816
20590
|
continue;
|
|
19817
20591
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
|
|
@@ -19825,10 +20599,10 @@ function selectSkillFiles(scope) {
|
|
|
19825
20599
|
for (const name2 of visibleNames(scope.dir)) {
|
|
19826
20600
|
if (name2 === LEARNED_DIRECTORY)
|
|
19827
20601
|
continue;
|
|
19828
|
-
const skillDir =
|
|
20602
|
+
const skillDir = join43(scope.dir, name2);
|
|
19829
20603
|
if (entryKind(skillDir) !== "directory")
|
|
19830
20604
|
continue;
|
|
19831
|
-
const absolute =
|
|
20605
|
+
const absolute = join43(skillDir, SKILL_FILE);
|
|
19832
20606
|
if (entryKind(absolute) !== "file")
|
|
19833
20607
|
continue;
|
|
19834
20608
|
const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -19876,7 +20650,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
19876
20650
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
19877
20651
|
Usage: ${BACKFILL_USAGE}`);
|
|
19878
20652
|
}
|
|
19879
|
-
return
|
|
20653
|
+
return resolve17(cwd(), dir);
|
|
19880
20654
|
}
|
|
19881
20655
|
function readConsumerRoot(value, cwd) {
|
|
19882
20656
|
if (value === undefined || value === false)
|
|
@@ -19885,7 +20659,7 @@ function readConsumerRoot(value, cwd) {
|
|
|
19885
20659
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
19886
20660
|
Usage: ${BACKFILL_USAGE}`);
|
|
19887
20661
|
}
|
|
19888
|
-
return
|
|
20662
|
+
return resolve17(cwd(), value);
|
|
19889
20663
|
}
|
|
19890
20664
|
function isDirectory3(path) {
|
|
19891
20665
|
try {
|
|
@@ -20013,7 +20787,7 @@ function reviewedProposals(dir) {
|
|
|
20013
20787
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
20014
20788
|
}
|
|
20015
20789
|
function rowPaths(files, root) {
|
|
20016
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
20790
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join44(root, ...row2.path.split("/"))));
|
|
20017
20791
|
}
|
|
20018
20792
|
function runApply(context, scope, backfillDir, projectRoot3) {
|
|
20019
20793
|
const options = checkOptions(context, projectRoot3);
|
|
@@ -20059,7 +20833,7 @@ async function runBackfill(context, seams) {
|
|
|
20059
20833
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
20060
20834
|
const projectRoot3 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
20061
20835
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
20062
|
-
const backfillDir =
|
|
20836
|
+
const backfillDir = join44(scope.base, BACKFILL_PATH);
|
|
20063
20837
|
if (mode === "propose") {
|
|
20064
20838
|
await runPropose(context, scope, backfillDir, seams);
|
|
20065
20839
|
return;
|
|
@@ -20173,13 +20947,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
20173
20947
|
var check_default3 = createSkillCheckCommand();
|
|
20174
20948
|
|
|
20175
20949
|
// src/commands/skill/demote.ts
|
|
20176
|
-
import { existsSync as
|
|
20177
|
-
import { dirname as
|
|
20950
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync29, statSync as statSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
20951
|
+
import { dirname as dirname12, resolve as resolve18 } from "path";
|
|
20178
20952
|
|
|
20179
20953
|
// src/demote/apply.ts
|
|
20180
|
-
import { copyFileSync as copyFileSync6, existsSync as
|
|
20181
|
-
import { tmpdir as
|
|
20182
|
-
import { dirname as
|
|
20954
|
+
import { copyFileSync as copyFileSync6, existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readdirSync as readdirSync7, readFileSync as readFileSync28, renameSync as renameSync3, rmSync as rmSync6, statSync as statSync14, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
|
|
20955
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
20956
|
+
import { dirname as dirname11, join as join45 } from "path";
|
|
20183
20957
|
var DEMOTION_ACTION_KINDS = [
|
|
20184
20958
|
"demoted",
|
|
20185
20959
|
"kept",
|
|
@@ -20200,13 +20974,13 @@ function learnedSkillName(path) {
|
|
|
20200
20974
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
20201
20975
|
}
|
|
20202
20976
|
function sourcePath(scope, path) {
|
|
20203
|
-
return
|
|
20977
|
+
return join45(scope.dir, ...path.split("/"));
|
|
20204
20978
|
}
|
|
20205
20979
|
function demotedPath(scope, path) {
|
|
20206
|
-
return
|
|
20980
|
+
return join45(scope.demotedDir, ...path.split("/"));
|
|
20207
20981
|
}
|
|
20208
20982
|
function promotedPath(scope, path) {
|
|
20209
|
-
return
|
|
20983
|
+
return join45(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
20210
20984
|
}
|
|
20211
20985
|
function takenInstinctIds(dir) {
|
|
20212
20986
|
let names;
|
|
@@ -20219,7 +20993,7 @@ function takenInstinctIds(dir) {
|
|
|
20219
20993
|
}
|
|
20220
20994
|
function readText4(path) {
|
|
20221
20995
|
try {
|
|
20222
|
-
return statSync14(path).isFile() ?
|
|
20996
|
+
return statSync14(path).isFile() ? readFileSync28(path, "utf8") : null;
|
|
20223
20997
|
} catch {
|
|
20224
20998
|
return null;
|
|
20225
20999
|
}
|
|
@@ -20247,7 +21021,7 @@ function planKept(scope, row2, file) {
|
|
|
20247
21021
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
20248
21022
|
}
|
|
20249
21023
|
const to = promotedPath(scope, row2.path);
|
|
20250
|
-
if (
|
|
21024
|
+
if (existsSync19(to) || existsSync19(dirname11(to))) {
|
|
20251
21025
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
20252
21026
|
}
|
|
20253
21027
|
return {
|
|
@@ -20261,8 +21035,8 @@ function planKept(scope, row2, file) {
|
|
|
20261
21035
|
};
|
|
20262
21036
|
}
|
|
20263
21037
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
20264
|
-
const path =
|
|
20265
|
-
|
|
21038
|
+
const path = join45(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21039
|
+
writeFileSync9(path, text, "utf8");
|
|
20266
21040
|
const report3 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
20267
21041
|
return failureLines2(report3.issues);
|
|
20268
21042
|
}
|
|
@@ -20282,11 +21056,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
20282
21056
|
if (failures.length > 0) {
|
|
20283
21057
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
20284
21058
|
}
|
|
20285
|
-
const destination =
|
|
21059
|
+
const destination = join45(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
20286
21060
|
const kept = demotedPath(scope, row2.path);
|
|
20287
|
-
if (
|
|
21061
|
+
if (existsSync19(destination))
|
|
20288
21062
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
20289
|
-
if (
|
|
21063
|
+
if (existsSync19(kept))
|
|
20290
21064
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
20291
21065
|
context.taken.push(id2);
|
|
20292
21066
|
return {
|
|
@@ -20329,17 +21103,17 @@ function countActions(actions) {
|
|
|
20329
21103
|
return counts;
|
|
20330
21104
|
}
|
|
20331
21105
|
function planDemotion(report3, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
20332
|
-
const temp =
|
|
21106
|
+
const temp = mkdtempSync3(join45(tmpdir3(), "rafa-demote-"));
|
|
20333
21107
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
20334
21108
|
try {
|
|
20335
21109
|
const actions = report3.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
20336
21110
|
return { scope, actions, counts: countActions(actions) };
|
|
20337
21111
|
} finally {
|
|
20338
|
-
|
|
21112
|
+
rmSync6(temp, { recursive: true, force: true });
|
|
20339
21113
|
}
|
|
20340
21114
|
}
|
|
20341
21115
|
function moveFile(move3) {
|
|
20342
|
-
mkdirSync10(
|
|
21116
|
+
mkdirSync10(dirname11(move3.to), { recursive: true });
|
|
20343
21117
|
try {
|
|
20344
21118
|
renameSync3(move3.from, move3.to);
|
|
20345
21119
|
} catch {
|
|
@@ -20350,8 +21124,8 @@ function moveFile(move3) {
|
|
|
20350
21124
|
function applyDemotion(plan2) {
|
|
20351
21125
|
for (const action of plan2.actions) {
|
|
20352
21126
|
if (action.write !== null) {
|
|
20353
|
-
mkdirSync10(
|
|
20354
|
-
|
|
21127
|
+
mkdirSync10(dirname11(action.write.path), { recursive: true });
|
|
21128
|
+
writeFileSync9(action.write.path, action.write.text, "utf8");
|
|
20355
21129
|
}
|
|
20356
21130
|
if (action.move !== null)
|
|
20357
21131
|
moveFile(action.move);
|
|
@@ -20416,7 +21190,7 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
20416
21190
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
20417
21191
|
Usage: ${DEMOTE_USAGE}`);
|
|
20418
21192
|
}
|
|
20419
|
-
return
|
|
21193
|
+
return resolve18(cwd(), dir);
|
|
20420
21194
|
}
|
|
20421
21195
|
function isDirectory4(path) {
|
|
20422
21196
|
try {
|
|
@@ -20448,9 +21222,9 @@ function verdictLine2(report3) {
|
|
|
20448
21222
|
return `${String(report3.rows.length)} file(s): ${parts.join(", ")}`;
|
|
20449
21223
|
}
|
|
20450
21224
|
function previousReport(path, context) {
|
|
20451
|
-
if (!
|
|
21225
|
+
if (!existsSync20(path))
|
|
20452
21226
|
return null;
|
|
20453
|
-
const parsed = parseDemotionReport(
|
|
21227
|
+
const parsed = parseDemotionReport(readFileSync29(path, "utf8"));
|
|
20454
21228
|
if (parsed.report !== null)
|
|
20455
21229
|
return parsed.report;
|
|
20456
21230
|
context.output.warn(`the report already at ${path} does not parse, so no override in it is carried forward:` + ` ${parsed.issues.map((issue2) => `line ${String(issue2.line)} ${issue2.code}`).join(", ")}`);
|
|
@@ -20459,8 +21233,8 @@ function previousReport(path, context) {
|
|
|
20459
21233
|
function runWrite(context, scope) {
|
|
20460
21234
|
const files = selectSources(scope);
|
|
20461
21235
|
const report3 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
20462
|
-
mkdirSync11(
|
|
20463
|
-
|
|
21236
|
+
mkdirSync11(dirname12(scope.reportPath), { recursive: true });
|
|
21237
|
+
writeFileSync10(scope.reportPath, writeDemotionReport(report3), "utf8");
|
|
20464
21238
|
const result = {
|
|
20465
21239
|
applied: false,
|
|
20466
21240
|
scope: scope.scope,
|
|
@@ -20483,10 +21257,10 @@ function runWrite(context, scope) {
|
|
|
20483
21257
|
context.output.info(line);
|
|
20484
21258
|
}
|
|
20485
21259
|
function reviewedReport(scope) {
|
|
20486
|
-
if (!
|
|
21260
|
+
if (!existsSync20(scope.reportPath)) {
|
|
20487
21261
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
20488
21262
|
}
|
|
20489
|
-
const parsed = parseDemotionReport(
|
|
21263
|
+
const parsed = parseDemotionReport(readFileSync29(scope.reportPath, "utf8"));
|
|
20490
21264
|
if (parsed.report === null) {
|
|
20491
21265
|
const lines2 = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
|
|
20492
21266
|
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines2].join(`
|
|
@@ -20592,7 +21366,7 @@ var demote_default = createSkillDemoteCommand();
|
|
|
20592
21366
|
|
|
20593
21367
|
// src/commands/skill/show.ts
|
|
20594
21368
|
var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
|
|
20595
|
-
var
|
|
21369
|
+
var COMMAND_NAME11 = "rafa skill show";
|
|
20596
21370
|
var USAGE37 = "rafa skill show <name> [--full]";
|
|
20597
21371
|
function unknownSkillMessage(name2, inventory) {
|
|
20598
21372
|
const agent = findShown(inventory.records, "agent", name2);
|
|
@@ -20620,7 +21394,7 @@ async function runShow3(context, seams) {
|
|
|
20620
21394
|
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE37}`);
|
|
20621
21395
|
const name2 = expectOneArgument(context.args, USAGE37);
|
|
20622
21396
|
const project = projectOf13(context);
|
|
20623
|
-
const { inventory, settingSources } = await projectInventory(project, context, seams,
|
|
21397
|
+
const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME11);
|
|
20624
21398
|
const warnings = warningLines(inventory);
|
|
20625
21399
|
for (const warning of warnings)
|
|
20626
21400
|
context.output.warn(warning);
|
|
@@ -20704,6 +21478,10 @@ function awayLine(current, home) {
|
|
|
20704
21478
|
return null;
|
|
20705
21479
|
return `${AWAY_PREFIX}: working ${id2(placeNumber(current))} for ${id2(placeNumber(home))}`;
|
|
20706
21480
|
}
|
|
21481
|
+
var WAITING_ON = "owner review";
|
|
21482
|
+
function waitingLine(issue2) {
|
|
21483
|
+
return `waiting on ${id2(issue2)} (${WAITING_ON})`;
|
|
21484
|
+
}
|
|
20707
21485
|
|
|
20708
21486
|
// src/status/render.ts
|
|
20709
21487
|
var STATUS_SECTION_TITLES = Object.freeze({
|
|
@@ -20770,6 +21548,7 @@ function boardBody(reading) {
|
|
|
20770
21548
|
return [
|
|
20771
21549
|
info2(`${INDENT9}${placeLine2(place2.view)}`),
|
|
20772
21550
|
...away === null ? [] : [info2(`${INDENT9}${away}`)],
|
|
21551
|
+
...place2.waiting === undefined ? [] : [info2(`${INDENT9}${waitingLine(place2.waiting.issue)}`)],
|
|
20773
21552
|
...place2.notices.map((notice) => ({ level: "warn", text: `${INDENT9}${notice}` }))
|
|
20774
21553
|
];
|
|
20775
21554
|
}
|
|
@@ -20813,7 +21592,7 @@ function statusData(sections) {
|
|
|
20813
21592
|
}
|
|
20814
21593
|
|
|
20815
21594
|
// src/status/sections.ts
|
|
20816
|
-
import { existsSync as
|
|
21595
|
+
import { existsSync as existsSync21 } from "fs";
|
|
20817
21596
|
var STATUS_NETWORK_TIMEOUT_MS = 5000;
|
|
20818
21597
|
function isIdleWorktree(row2) {
|
|
20819
21598
|
return !row2.blockers.some((blocker) => blocker.kind === "recent");
|
|
@@ -20860,9 +21639,9 @@ function boundedRunner(open, root, deadline) {
|
|
|
20860
21639
|
}
|
|
20861
21640
|
async function networkSection(what, deadline, work) {
|
|
20862
21641
|
let timer;
|
|
20863
|
-
const expired = new Promise((
|
|
21642
|
+
const expired = new Promise((resolve19) => {
|
|
20864
21643
|
timer = setTimeout(() => {
|
|
20865
|
-
|
|
21644
|
+
resolve19(unread3(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
20866
21645
|
}, Math.max(0, deadline.left()));
|
|
20867
21646
|
});
|
|
20868
21647
|
try {
|
|
@@ -20952,13 +21731,52 @@ function nextOfPlace(current, epic, walk) {
|
|
|
20952
21731
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
20953
21732
|
}
|
|
20954
21733
|
function hasBoards(root, listing2) {
|
|
20955
|
-
if (
|
|
21734
|
+
if (existsSync21(positionFilePath(root)))
|
|
20956
21735
|
return true;
|
|
20957
21736
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
20958
21737
|
}
|
|
20959
|
-
|
|
21738
|
+
var NOT_WAITING = { waiting: null, notes: [] };
|
|
21739
|
+
function ref6(number) {
|
|
21740
|
+
return `#${String(number)}`;
|
|
21741
|
+
}
|
|
21742
|
+
async function readGate(sources, pullRequest) {
|
|
21743
|
+
try {
|
|
21744
|
+
return await sources.ownerGate()(pullRequest);
|
|
21745
|
+
} catch (error) {
|
|
21746
|
+
return { state: "unknown", reason: `could not read the owner gate of ${ref6(pullRequest)}: ${messageOf(error)}`, owners: [] };
|
|
21747
|
+
}
|
|
21748
|
+
}
|
|
21749
|
+
async function openPullGate(sources, pullRequest) {
|
|
21750
|
+
let detail;
|
|
21751
|
+
try {
|
|
21752
|
+
detail = await sources.pulls.get(pullRequest);
|
|
21753
|
+
} catch (error) {
|
|
21754
|
+
return { state: "unknown", reason: `could not read pull request ${ref6(pullRequest)}: ${messageOf(error)}`, owners: [] };
|
|
21755
|
+
}
|
|
21756
|
+
return detail?.state === "open" ? readGate(sources, pullRequest) : null;
|
|
21757
|
+
}
|
|
21758
|
+
async function readWaiting(sources) {
|
|
21759
|
+
const hop2 = readHopRecord(sources.root);
|
|
21760
|
+
if (!hop2.set) {
|
|
21761
|
+
return hop2.reason === "absent" ? NOT_WAITING : { waiting: null, notes: [`${hop2.detail}, so no waiting pull request is read`] };
|
|
21762
|
+
}
|
|
21763
|
+
const { record } = hop2;
|
|
21764
|
+
const { pullRequest } = record;
|
|
21765
|
+
if (record.state !== "waiting" || pullRequest === null)
|
|
21766
|
+
return NOT_WAITING;
|
|
21767
|
+
const placed = readPositionFile(sources.root);
|
|
21768
|
+
if (!placed.set || staleAgainst(record, placed.position))
|
|
21769
|
+
return NOT_WAITING;
|
|
21770
|
+
const gate = await openPullGate(sources, pullRequest);
|
|
21771
|
+
if (gate === null || gate.state === "approved" || gate.state === "not-gated")
|
|
21772
|
+
return NOT_WAITING;
|
|
21773
|
+
const waiting = { issue: record.target ?? pullRequest, pullRequest, gate: gate.state, reason: gate.reason };
|
|
21774
|
+
return { waiting, notes: [] };
|
|
21775
|
+
}
|
|
21776
|
+
async function readPlace(sources, walk, listing2) {
|
|
21777
|
+
const { root } = sources;
|
|
20960
21778
|
if (!hasBoards(root, listing2))
|
|
20961
|
-
return null;
|
|
21779
|
+
return { place: null, notes: [] };
|
|
20962
21780
|
const resolved = await resolvePlace({ root, listing: listing2, defaultBoard: () => Promise.resolve(walk.roadmap) });
|
|
20963
21781
|
const { current, home } = resolved;
|
|
20964
21782
|
const epic = current.epic === null ? undefined : readEpics({ issues: listing2, claims: new Set, today: new Date }).epics.find((one) => one.number === current.epic);
|
|
@@ -20968,21 +21786,23 @@ async function readPlace(root, walk, listing2) {
|
|
|
20968
21786
|
next: nextOfPlace(current, epic, walk)
|
|
20969
21787
|
};
|
|
20970
21788
|
const notices = resolved.notices.filter((notice) => notice.kind === "lost" || notice.reason !== "absent").map((notice) => notice.message);
|
|
20971
|
-
|
|
21789
|
+
const place2 = { current, home, view, notices };
|
|
21790
|
+
const { waiting, notes } = await readWaiting(sources);
|
|
21791
|
+
return waiting === null ? { place: place2, notes } : { place: { ...place2, waiting }, notes };
|
|
20972
21792
|
}
|
|
20973
|
-
async function placeOrNote(
|
|
21793
|
+
async function placeOrNote(sources, walk, listing2) {
|
|
20974
21794
|
try {
|
|
20975
|
-
return
|
|
21795
|
+
return await readPlace(sources, walk, await listing2());
|
|
20976
21796
|
} catch (error) {
|
|
20977
21797
|
return { place: null, notes: [`the current place could not be read: ${messageOf(error)}`] };
|
|
20978
21798
|
}
|
|
20979
21799
|
}
|
|
20980
|
-
async function readBoard(
|
|
21800
|
+
async function readBoard(sources, board, listing2, blocked) {
|
|
20981
21801
|
const [walk, report3] = await Promise.all([board.next(), blocked]);
|
|
20982
21802
|
const { line } = walk;
|
|
20983
21803
|
const next = line === null ? null : { line, ready: await board.isReady(line.issue), blocked: await board.blocking(line.issue) };
|
|
20984
21804
|
const listed2 = report3.problem === null ? [] : [`the issues labelled ${SPEC_BLOCKED_LABEL} could not be read: ${report3.problem}`];
|
|
20985
|
-
const { place: place2, notes } = await placeOrNote(
|
|
21805
|
+
const { place: place2, notes } = await placeOrNote(sources, walk, listing2);
|
|
20986
21806
|
const reading = {
|
|
20987
21807
|
roadmap: walk.roadmap,
|
|
20988
21808
|
next,
|
|
@@ -21013,6 +21833,12 @@ async function readStatusSections(input, seams = {}) {
|
|
|
21013
21833
|
const pulls = (seams.pullRequests ?? ((runner) => createGhPullRequests({ gh: runner })))(gh);
|
|
21014
21834
|
const listing2 = once2((seams.listing ?? ((runner) => createGhBoardListing({ gh: runner })))(gh));
|
|
21015
21835
|
const board = (seams.board ?? ghNextBoard)({ gh, git, configured: config.roadmapIssue, listing: listing2 });
|
|
21836
|
+
const openGate = seams.ownerGate ?? nextOwnerGate;
|
|
21837
|
+
const place2 = {
|
|
21838
|
+
root,
|
|
21839
|
+
pulls,
|
|
21840
|
+
ownerGate: () => openGate({ gh, root, pulls, configured: config.roadmapIssue, listing: listing2 })
|
|
21841
|
+
};
|
|
21016
21842
|
const sources = {
|
|
21017
21843
|
base: config.prBase ?? DEFAULT_BASE_BRANCH,
|
|
21018
21844
|
plans: plansDirAt(root, config.planDir),
|
|
@@ -21030,7 +21856,7 @@ async function readStatusSections(input, seams = {}) {
|
|
|
21030
21856
|
const blockedIssues = seams.blockedIssues ?? ((runner) => readBlockedIssues({ gh: runner }));
|
|
21031
21857
|
deadline.start();
|
|
21032
21858
|
const pull = pullSection(sources, branch, provider, deadline);
|
|
21033
|
-
const boardSection = isGh ? networkSection("board", deadline, () => readBoard(
|
|
21859
|
+
const boardSection = isGh ? networkSection("board", deadline, () => readBoard(place2, board, listing2, blockedIssues(gh))) : Promise.resolve(unread3(notGhProblem("board", provider)));
|
|
21034
21860
|
const housekeeping = readHousekeeping(input, cleanup, now);
|
|
21035
21861
|
return {
|
|
21036
21862
|
branch,
|
|
@@ -21042,7 +21868,7 @@ async function readStatusSections(input, seams = {}) {
|
|
|
21042
21868
|
}
|
|
21043
21869
|
|
|
21044
21870
|
// src/commands/status.ts
|
|
21045
|
-
var
|
|
21871
|
+
var COMMAND_NAME12 = "rafa status";
|
|
21046
21872
|
var STATUS_USAGE = "rafa status";
|
|
21047
21873
|
var STATUS_ARGUMENT_EXIT = 2;
|
|
21048
21874
|
var DEFAULT_STATUS_SEAMS = Object.freeze({});
|
|
@@ -21054,13 +21880,13 @@ function projectOf14(context) {
|
|
|
21054
21880
|
function expectNoWord(args) {
|
|
21055
21881
|
if (args.length === 0)
|
|
21056
21882
|
return;
|
|
21057
|
-
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${
|
|
21883
|
+
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME12}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
|
|
21058
21884
|
Usage: ${STATUS_USAGE}`);
|
|
21059
21885
|
}
|
|
21060
21886
|
async function runStatus2(context, seams) {
|
|
21061
21887
|
expectNoWord(context.args);
|
|
21062
21888
|
const project = projectOf14(context);
|
|
21063
|
-
const config = resolveProjectConfig(project,
|
|
21889
|
+
const config = resolveProjectConfig(project, COMMAND_NAME12, (message) => {
|
|
21064
21890
|
context.output.warn(message);
|
|
21065
21891
|
});
|
|
21066
21892
|
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
@@ -21126,7 +21952,7 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
21126
21952
|
{ name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
|
|
21127
21953
|
{ name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
|
|
21128
21954
|
{ name: "pr", summary: "the pull request of a branch: one line, in full or in the browser; list, wait on, merge and triage them" },
|
|
21129
|
-
{ name: "effort", summary: "collect session and commit rows; report per plan; read and repair the store: its schema, a copy for testing, migrations, and fixes" },
|
|
21955
|
+
{ name: "effort", summary: "collect session and commit rows; report per plan; read and repair the store: its schema, a copy for testing, migrations, and fixes; merge another device's store; move an NDJSON store to SQLite" },
|
|
21130
21956
|
{ name: "module", summary: "list the configured modules; run an action a module provides" },
|
|
21131
21957
|
{ name: "agent", summary: "copy an agent definition into the project; list what a session sees" },
|
|
21132
21958
|
{ name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
|
|
@@ -21152,7 +21978,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
21152
21978
|
show_default4,
|
|
21153
21979
|
create_default,
|
|
21154
21980
|
comment_default,
|
|
21155
|
-
|
|
21981
|
+
move_default3,
|
|
21156
21982
|
ready_default,
|
|
21157
21983
|
unblock_default,
|
|
21158
21984
|
check_default2,
|
|
@@ -21161,7 +21987,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
21161
21987
|
view_default,
|
|
21162
21988
|
list_default9,
|
|
21163
21989
|
wait_default,
|
|
21164
|
-
|
|
21990
|
+
merge_default2,
|
|
21165
21991
|
triage_default,
|
|
21166
21992
|
collect_default,
|
|
21167
21993
|
report_default,
|
|
@@ -21169,6 +21995,8 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
21169
21995
|
copy_default,
|
|
21170
21996
|
schema_default,
|
|
21171
21997
|
migrate_default,
|
|
21998
|
+
merge_default,
|
|
21999
|
+
move_default,
|
|
21172
22000
|
list_default8,
|
|
21173
22001
|
exec_default,
|
|
21174
22002
|
vendor_default,
|
|
@@ -21193,7 +22021,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
21193
22021
|
new_default,
|
|
21194
22022
|
defer_default,
|
|
21195
22023
|
promote_default,
|
|
21196
|
-
|
|
22024
|
+
move_default2,
|
|
21197
22025
|
close_default,
|
|
21198
22026
|
cancel_default,
|
|
21199
22027
|
status_default3,
|