@open-tomato/rafa 0.36.0 → 0.37.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 +5 -4
- package/dist/cli.js +1325 -545
- package/dist/effort/store/index.js +1 -1
- package/dist/{index-qvbeqrx3.js → index-1mxv55ec.js} +174 -40
- package/dist/{index-2n2nedrk.js → index-35ze56rz.js} +6 -3
- package/dist/{index-ddzzmcaz.js → index-b16va6he.js} +866 -237
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
BOARD_LISTING_LIMIT,
|
|
7
7
|
BRANCH_PREFIX,
|
|
8
8
|
CI_POLL_INTERVAL_MS,
|
|
9
|
+
CLAIMED_LABEL,
|
|
9
10
|
DEFAULT_BASE_BRANCH,
|
|
10
11
|
DEFAULT_CI_ATTEMPTS,
|
|
11
12
|
DEFAULT_CI_TIMEOUT_MIN,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
HORIZONS,
|
|
23
24
|
HORIZON_LABEL_PREFIX,
|
|
24
25
|
INDENT,
|
|
26
|
+
IN_DEVELOPMENT_LABEL,
|
|
25
27
|
ISSUE_LIST_FLAGS,
|
|
26
28
|
LABELS_ONLY_ACTIONS,
|
|
27
29
|
LABELS_READS,
|
|
@@ -56,6 +58,7 @@ import {
|
|
|
56
58
|
SKILL_USE_CLI_VERSION,
|
|
57
59
|
SPEC_BLOCKED_LABEL,
|
|
58
60
|
SPEC_EXTENSION,
|
|
61
|
+
SPEC_READY_LABEL,
|
|
59
62
|
SPEC_TEMPLATE_PATH,
|
|
60
63
|
STAGE_LABELS,
|
|
61
64
|
TEMPLATES_DIRNAME,
|
|
@@ -81,8 +84,10 @@ import {
|
|
|
81
84
|
boardChanged,
|
|
82
85
|
boardId,
|
|
83
86
|
boardOfEpic,
|
|
87
|
+
boardRepoLabel,
|
|
84
88
|
bodySections,
|
|
85
89
|
branchClaims,
|
|
90
|
+
branchName,
|
|
86
91
|
buildReport,
|
|
87
92
|
buildSettle,
|
|
88
93
|
bundledAgentsDirectory,
|
|
@@ -143,6 +148,7 @@ import {
|
|
|
143
148
|
expectTwoArguments,
|
|
144
149
|
failingRows,
|
|
145
150
|
fetchClaimBranches,
|
|
151
|
+
findReadinessGaps,
|
|
146
152
|
fingerprintText,
|
|
147
153
|
firstNowEpic,
|
|
148
154
|
firstSentence,
|
|
@@ -169,6 +175,7 @@ import {
|
|
|
169
175
|
hasDiverged,
|
|
170
176
|
hasPlanFor,
|
|
171
177
|
hasSpecBlockedLabel,
|
|
178
|
+
hasSpecLabel,
|
|
172
179
|
horizonOf,
|
|
173
180
|
idleText,
|
|
174
181
|
invalidateRows,
|
|
@@ -204,6 +211,7 @@ import {
|
|
|
204
211
|
nextOwnerGate,
|
|
205
212
|
nextQuestion,
|
|
206
213
|
noBoardListsLine,
|
|
214
|
+
normaliseReason,
|
|
207
215
|
notesFileName,
|
|
208
216
|
onTracker,
|
|
209
217
|
openBoards,
|
|
@@ -299,7 +307,9 @@ import {
|
|
|
299
307
|
replaceManifestVersion,
|
|
300
308
|
report,
|
|
301
309
|
reportFollowUps,
|
|
310
|
+
requireCompleteSpec,
|
|
302
311
|
requireGhProvider,
|
|
312
|
+
requireNoLeak,
|
|
303
313
|
resolveAgentRoster,
|
|
304
314
|
resolveCreateSpec,
|
|
305
315
|
resolveDefaultBoard,
|
|
@@ -364,7 +374,7 @@ import {
|
|
|
364
374
|
worktreesHolding,
|
|
365
375
|
writeSession,
|
|
366
376
|
writeShippedFile
|
|
367
|
-
} from "./index-
|
|
377
|
+
} from "./index-35ze56rz.js";
|
|
368
378
|
import {
|
|
369
379
|
PROBE_TIMEOUT_MS,
|
|
370
380
|
loadPlanPrerequisites,
|
|
@@ -404,6 +414,7 @@ import {
|
|
|
404
414
|
SQLITE_STORE_FILE_NAME,
|
|
405
415
|
SchemaRefusedError,
|
|
406
416
|
SessionRecordError,
|
|
417
|
+
TRUST_REFUSAL_EXIT,
|
|
407
418
|
UnionSchemaMismatch,
|
|
408
419
|
allRecords,
|
|
409
420
|
checkbox,
|
|
@@ -411,9 +422,11 @@ import {
|
|
|
411
422
|
copyEffortStore,
|
|
412
423
|
countTasks,
|
|
413
424
|
createFileSync,
|
|
425
|
+
createGhEditorLogin,
|
|
414
426
|
createGhPermissions,
|
|
415
427
|
createGhRunner,
|
|
416
428
|
createGitRunner,
|
|
429
|
+
editorRefusalMessage,
|
|
417
430
|
effortStoreDir,
|
|
418
431
|
effortStoreFileName,
|
|
419
432
|
errorCode,
|
|
@@ -424,6 +437,7 @@ import {
|
|
|
424
437
|
findRecords,
|
|
425
438
|
fix_schema_default,
|
|
426
439
|
formatCounts,
|
|
440
|
+
ghBoardTrust,
|
|
427
441
|
gitRemoteUrl,
|
|
428
442
|
gitSaid,
|
|
429
443
|
hop,
|
|
@@ -447,6 +461,8 @@ import {
|
|
|
447
461
|
plural,
|
|
448
462
|
positionFilePath,
|
|
449
463
|
readAuthorTrust,
|
|
464
|
+
readBoardTrust,
|
|
465
|
+
readEditorTrust,
|
|
450
466
|
readFrontmatter,
|
|
451
467
|
readFrontmatterDocument,
|
|
452
468
|
readHopRecord,
|
|
@@ -471,11 +487,12 @@ import {
|
|
|
471
487
|
staleAgainst,
|
|
472
488
|
toHeldRecords,
|
|
473
489
|
trustRefusalClause,
|
|
490
|
+
trustRefusalMessage,
|
|
474
491
|
version,
|
|
475
492
|
versionLine,
|
|
476
493
|
writeFrontmatter,
|
|
477
494
|
writePositionFile
|
|
478
|
-
} from "./index-
|
|
495
|
+
} from "./index-1mxv55ec.js";
|
|
479
496
|
import {
|
|
480
497
|
CONFIG_DEFAULTS,
|
|
481
498
|
CONFIG_FILE,
|
|
@@ -13277,6 +13294,615 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13277
13294
|
}
|
|
13278
13295
|
var create_default = createIssueCreateCommand();
|
|
13279
13296
|
|
|
13297
|
+
// src/commands/issue/edit-run.ts
|
|
13298
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
13299
|
+
import { join as join29 } from "path";
|
|
13300
|
+
|
|
13301
|
+
// src/board/issue-edit-body.ts
|
|
13302
|
+
var TRAILING_WHITESPACE = /\s+$/u;
|
|
13303
|
+
function trimmedText(text, what) {
|
|
13304
|
+
const trimmed = text.replace(TRAILING_WHITESPACE, "");
|
|
13305
|
+
if (trimmed.trim() === "")
|
|
13306
|
+
throw new RangeError(`${what} holds nothing but whitespace`);
|
|
13307
|
+
return trimmed;
|
|
13308
|
+
}
|
|
13309
|
+
function renderUpdateHeading(now, reason) {
|
|
13310
|
+
const normalised = normaliseReason(reason);
|
|
13311
|
+
if (normalised === "")
|
|
13312
|
+
throw new RangeError("the reason holds nothing but whitespace");
|
|
13313
|
+
return `**Updated ${localDay(now)}, ${normalised}:**`;
|
|
13314
|
+
}
|
|
13315
|
+
function renderUpdateBlock(now, reason, text) {
|
|
13316
|
+
return `${renderUpdateHeading(now, reason)}
|
|
13317
|
+
|
|
13318
|
+
${trimmedText(text, "the added text")}`;
|
|
13319
|
+
}
|
|
13320
|
+
function separatorAfter(body) {
|
|
13321
|
+
if (body === "" || body.endsWith(`
|
|
13322
|
+
|
|
13323
|
+
`))
|
|
13324
|
+
return "";
|
|
13325
|
+
return body.endsWith(`
|
|
13326
|
+
`) ? `
|
|
13327
|
+
` : `
|
|
13328
|
+
|
|
13329
|
+
`;
|
|
13330
|
+
}
|
|
13331
|
+
function appendedBody(body, block) {
|
|
13332
|
+
return `${body}${separatorAfter(body)}${block}`;
|
|
13333
|
+
}
|
|
13334
|
+
function replacedBody(text) {
|
|
13335
|
+
return trimmedText(text, "the replacing text");
|
|
13336
|
+
}
|
|
13337
|
+
function carriesUpdateBlock(body, block) {
|
|
13338
|
+
const trimmed = body.replace(TRAILING_WHITESPACE, "");
|
|
13339
|
+
if (!trimmed.endsWith(block))
|
|
13340
|
+
return false;
|
|
13341
|
+
const before = trimmed.slice(0, trimmed.length - block.length);
|
|
13342
|
+
return before === "" || before.endsWith(`
|
|
13343
|
+
`);
|
|
13344
|
+
}
|
|
13345
|
+
function originalPartHolds(before, after, block) {
|
|
13346
|
+
if (!after.startsWith(before))
|
|
13347
|
+
return false;
|
|
13348
|
+
const added = after.slice(before.length).replace(TRAILING_WHITESPACE, "");
|
|
13349
|
+
return added === `${separatorAfter(before)}${block}`;
|
|
13350
|
+
}
|
|
13351
|
+
|
|
13352
|
+
// src/board/issue-edit-state.ts
|
|
13353
|
+
import { readdirSync as readdirSync3, statSync as statSync8 } from "fs";
|
|
13354
|
+
import { join as join28, resolve as resolve10 } from "path";
|
|
13355
|
+
function isClaimed(labels) {
|
|
13356
|
+
return labels.includes(CLAIMED_LABEL) || labels.includes(IN_DEVELOPMENT_LABEL);
|
|
13357
|
+
}
|
|
13358
|
+
function refused(refusal10, branch = null) {
|
|
13359
|
+
return { pass: false, refusal: refusal10, branch };
|
|
13360
|
+
}
|
|
13361
|
+
function readEditState(input) {
|
|
13362
|
+
if (!input.open)
|
|
13363
|
+
return refused("closed");
|
|
13364
|
+
if (isClaimed(input.labels)) {
|
|
13365
|
+
const branch = branchName(input.issue, input.title);
|
|
13366
|
+
return input.kind === "append" && input.whileInDevelopment ? { pass: true, staleCopy: true, branch } : refused("in-development", branch);
|
|
13367
|
+
}
|
|
13368
|
+
const planned = input.savedCopies.length > 0;
|
|
13369
|
+
if (input.kind === "append")
|
|
13370
|
+
return { pass: true, staleCopy: planned, branch: null };
|
|
13371
|
+
if (input.labels.includes(SPEC_READY_LABEL))
|
|
13372
|
+
return refused("spec-ready");
|
|
13373
|
+
if (planned)
|
|
13374
|
+
return refused("planned");
|
|
13375
|
+
return { pass: true, staleCopy: false, branch: null };
|
|
13376
|
+
}
|
|
13377
|
+
function refreshCommand(issue) {
|
|
13378
|
+
return `rafa plan create --issue=${String(issue)} --refresh`;
|
|
13379
|
+
}
|
|
13380
|
+
function staleCopyMessage(issue, branch) {
|
|
13381
|
+
const n = String(issue);
|
|
13382
|
+
const refresh = refreshCommand(issue);
|
|
13383
|
+
if (branch === null) {
|
|
13384
|
+
return `Issue #${n} has a saved copy this edit makes stale: ${refresh} rebuilds it.`;
|
|
13385
|
+
}
|
|
13386
|
+
return `Issue #${n} is under way on ${branch}: the loop plans from the copy it already has, ` + `and ${refresh} rebuilds that copy.`;
|
|
13387
|
+
}
|
|
13388
|
+
function editName(kind) {
|
|
13389
|
+
if (kind === "append")
|
|
13390
|
+
return "An append to";
|
|
13391
|
+
return kind === "replace" ? "A replace of the body of" : "A title change on";
|
|
13392
|
+
}
|
|
13393
|
+
function appendLine2(issue, extra) {
|
|
13394
|
+
return `rafa issue edit ${String(issue)} --append-file=<file> --reason=<text>${extra}`;
|
|
13395
|
+
}
|
|
13396
|
+
function editStateRefusalMessage(input, reading) {
|
|
13397
|
+
if (reading.pass)
|
|
13398
|
+
throw new TypeError("issue edit state: a passing reading has no refusal to name");
|
|
13399
|
+
const what = `${editName(input.kind)} issue #${String(input.issue)}`;
|
|
13400
|
+
switch (reading.refusal) {
|
|
13401
|
+
case "closed":
|
|
13402
|
+
return `${what} is refused: the issue is closed, and rafa edits open issues only.`;
|
|
13403
|
+
case "in-development": {
|
|
13404
|
+
const where = `a loop holds it on ${reading.branch ?? branchName(input.issue, input.title)}`;
|
|
13405
|
+
return input.kind === "append" ? `${what} is refused: ${where}; add --while-in-development for an amendment the loop must not miss.` : `${what} is refused: ${where}, and a spec under way changes by append: ${appendLine2(input.issue, " --while-in-development")}`;
|
|
13406
|
+
}
|
|
13407
|
+
case "spec-ready":
|
|
13408
|
+
return `${what} is refused: it carries ${SPEC_READY_LABEL}, and a ready spec changes by append: ${appendLine2(input.issue, "")}`;
|
|
13409
|
+
case "planned":
|
|
13410
|
+
return `${what} is refused: it has a saved copy under specs.dir, and a planned spec changes by append: ${appendLine2(input.issue, "")}`;
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
13413
|
+
function isSavedCopyName(name, issue) {
|
|
13414
|
+
const prefix = `${boardId(issue)}-`;
|
|
13415
|
+
return name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name.length > prefix.length + SPEC_EXTENSION.length && name !== notesFileName(issue);
|
|
13416
|
+
}
|
|
13417
|
+
function findSavedCopies(root, specsDir, issue) {
|
|
13418
|
+
const dir = resolve10(root, specsDir);
|
|
13419
|
+
let names;
|
|
13420
|
+
try {
|
|
13421
|
+
names = readdirSync3(dir);
|
|
13422
|
+
} catch (error) {
|
|
13423
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
13424
|
+
return [];
|
|
13425
|
+
throw error;
|
|
13426
|
+
}
|
|
13427
|
+
return names.filter((name) => isSavedCopyName(name, issue)).filter((name) => statSync8(join28(dir, name)).isFile()).sort().map((name) => join28(specsDir, name));
|
|
13428
|
+
}
|
|
13429
|
+
|
|
13430
|
+
// src/board/issue-edit-text.ts
|
|
13431
|
+
function addedTextSource(issue) {
|
|
13432
|
+
return `the text added to issue #${String(issue)}`;
|
|
13433
|
+
}
|
|
13434
|
+
function editedBodySource(issue) {
|
|
13435
|
+
return `issue #${String(issue)} as edited`;
|
|
13436
|
+
}
|
|
13437
|
+
function completenessApplies(labels, before) {
|
|
13438
|
+
return hasSpecLabel(labels) && findReadinessGaps(before).length === 0;
|
|
13439
|
+
}
|
|
13440
|
+
function requireEditText(input) {
|
|
13441
|
+
requireNoLeak(addedTextSource(input.issue), input.added);
|
|
13442
|
+
if (!completenessApplies(input.labels, input.before))
|
|
13443
|
+
return;
|
|
13444
|
+
requireCompleteSpec(editedBodySource(input.issue), input.after);
|
|
13445
|
+
}
|
|
13446
|
+
|
|
13447
|
+
// src/commands/issue/edit-run.ts
|
|
13448
|
+
var EDIT_USAGE = "rafa issue edit <n> (--append-file=<file> | --append=<text>) --reason=<text>" + " [--while-in-development] | (--replace-file=<file> [--title=<text>] | --title=<text>) [--dry-run]";
|
|
13449
|
+
var EDIT_REFUSAL_EXIT = TRUST_REFUSAL_EXIT;
|
|
13450
|
+
var EDIT_CONFLICT_EXIT = 1;
|
|
13451
|
+
var EDIT_SCRATCH_DIR = join29(".rafa", "scratch");
|
|
13452
|
+
var STDIN_PATH2 = "-";
|
|
13453
|
+
var ISSUE_NUMBER8 = /^[1-9]\d*$/u;
|
|
13454
|
+
var TRAILING_WHITESPACE2 = /\s+$/u;
|
|
13455
|
+
var ACCOUNTLESS_KIND = "local";
|
|
13456
|
+
var LOCAL_OWNERSHIP = "local: the local tracker holds no accounts, so nobody is asked about";
|
|
13457
|
+
var FLAG = Object.freeze({
|
|
13458
|
+
appendFile: "append-file",
|
|
13459
|
+
append: "append",
|
|
13460
|
+
reason: "reason",
|
|
13461
|
+
replaceFile: "replace-file",
|
|
13462
|
+
title: "title",
|
|
13463
|
+
dryRun: "dry-run",
|
|
13464
|
+
whileInDevelopment: "while-in-development"
|
|
13465
|
+
});
|
|
13466
|
+
|
|
13467
|
+
class EditRefusal extends CommandExit {
|
|
13468
|
+
refusal;
|
|
13469
|
+
readings;
|
|
13470
|
+
constructor(refusal10, sentence, readings, dryRun) {
|
|
13471
|
+
const passed = dryRun ? readings.map(renderReading) : [];
|
|
13472
|
+
super(EDIT_REFUSAL_EXIT, [...passed, `\u274C refused ${refusal10}: ${sentence}`].join(`
|
|
13473
|
+
`));
|
|
13474
|
+
this.refusal = refusal10;
|
|
13475
|
+
this.readings = readings;
|
|
13476
|
+
}
|
|
13477
|
+
}
|
|
13478
|
+
function editExitCode(outcome) {
|
|
13479
|
+
return outcome === "conflict" ? EDIT_CONFLICT_EXIT : 0;
|
|
13480
|
+
}
|
|
13481
|
+
function renderReading(reading) {
|
|
13482
|
+
return `${reading.gate}: ${reading.line}`;
|
|
13483
|
+
}
|
|
13484
|
+
function readSwitch3(flags, name) {
|
|
13485
|
+
const value = flags[name];
|
|
13486
|
+
if (value === undefined)
|
|
13487
|
+
return false;
|
|
13488
|
+
if (typeof value === "boolean")
|
|
13489
|
+
return value;
|
|
13490
|
+
if (value === "true" || value === "false")
|
|
13491
|
+
return value === "true";
|
|
13492
|
+
throw lineRefusal(`--${name} takes no value, and read "${value}" as one`, EDIT_USAGE);
|
|
13493
|
+
}
|
|
13494
|
+
function readEditIssue(args) {
|
|
13495
|
+
if (args.length > 1) {
|
|
13496
|
+
throw lineRefusal(`Expected one issue number, got ${String(args.length)}: ${args.join(" ")}`, EDIT_USAGE);
|
|
13497
|
+
}
|
|
13498
|
+
const word = args[0];
|
|
13499
|
+
if (word === undefined)
|
|
13500
|
+
throw lineRefusal("Name the issue number to edit", EDIT_USAGE);
|
|
13501
|
+
if (!ISSUE_NUMBER8.test(word)) {
|
|
13502
|
+
throw lineRefusal(`"${word}" is no issue number, which is a whole number from 1`, EDIT_USAGE);
|
|
13503
|
+
}
|
|
13504
|
+
return Number(word);
|
|
13505
|
+
}
|
|
13506
|
+
function notTogether(first, second, why) {
|
|
13507
|
+
return lineRefusal(`--${first} and --${second} cannot be used together: ${why}`, EDIT_USAGE);
|
|
13508
|
+
}
|
|
13509
|
+
function readEditFlags(flags) {
|
|
13510
|
+
return {
|
|
13511
|
+
appendFile: readNonBlankFlag(flags, FLAG.appendFile, EDIT_USAGE),
|
|
13512
|
+
append: readNonBlankFlag(flags, FLAG.append, EDIT_USAGE),
|
|
13513
|
+
reason: readNonBlankFlag(flags, FLAG.reason, EDIT_USAGE),
|
|
13514
|
+
replaceFile: readNonBlankFlag(flags, FLAG.replaceFile, EDIT_USAGE),
|
|
13515
|
+
title: readNonBlankFlag(flags, FLAG.title, EDIT_USAGE),
|
|
13516
|
+
whileInDevelopment: readSwitch3(flags, FLAG.whileInDevelopment),
|
|
13517
|
+
dryRun: readSwitch3(flags, FLAG.dryRun)
|
|
13518
|
+
};
|
|
13519
|
+
}
|
|
13520
|
+
function appendLine3(issue, read) {
|
|
13521
|
+
if (read.appendFile !== undefined && read.append !== undefined) {
|
|
13522
|
+
throw notTogether(FLAG.appendFile, FLAG.append, "name the added text one way");
|
|
13523
|
+
}
|
|
13524
|
+
if (read.title !== undefined)
|
|
13525
|
+
throw notTogether(FLAG.title, FLAG.append, "a title changes under the gates of a replace");
|
|
13526
|
+
if (read.reason === undefined) {
|
|
13527
|
+
throw lineRefusal(`--${FLAG.reason} is required with an append: --${FLAG.reason}=<text>`, EDIT_USAGE);
|
|
13528
|
+
}
|
|
13529
|
+
const source = read.appendFile === undefined ? { from: "line", text: read.append ?? "" } : { from: "file", flag: FLAG.appendFile, path: read.appendFile };
|
|
13530
|
+
return { ...lineOf(issue, read, "append", source), reason: read.reason, whileInDevelopment: read.whileInDevelopment };
|
|
13531
|
+
}
|
|
13532
|
+
function lineOf(issue, read, kind, source) {
|
|
13533
|
+
return { issue, kind, source, reason: null, title: read.title ?? null, whileInDevelopment: false, dryRun: read.dryRun };
|
|
13534
|
+
}
|
|
13535
|
+
function readEditLine(args, flags) {
|
|
13536
|
+
const issue = readEditIssue(args);
|
|
13537
|
+
const read = readEditFlags(flags);
|
|
13538
|
+
const appending = read.appendFile !== undefined || read.append !== undefined;
|
|
13539
|
+
if (appending && read.replaceFile !== undefined) {
|
|
13540
|
+
const appendFlag = read.appendFile === undefined ? FLAG.append : FLAG.appendFile;
|
|
13541
|
+
throw notTogether(appendFlag, FLAG.replaceFile, "an edit either appends or replaces");
|
|
13542
|
+
}
|
|
13543
|
+
if (appending)
|
|
13544
|
+
return appendLine3(issue, read);
|
|
13545
|
+
if (read.reason !== undefined) {
|
|
13546
|
+
throw lineRefusal(`--${FLAG.reason} goes with an append only: a replace and a title change take none`, EDIT_USAGE);
|
|
13547
|
+
}
|
|
13548
|
+
if (read.whileInDevelopment) {
|
|
13549
|
+
throw lineRefusal(`--${FLAG.whileInDevelopment} goes with an append only: a spec under way is never replaced or retitled`, EDIT_USAGE);
|
|
13550
|
+
}
|
|
13551
|
+
if (read.replaceFile !== undefined) {
|
|
13552
|
+
return lineOf(issue, read, "replace", { from: "file", flag: FLAG.replaceFile, path: read.replaceFile });
|
|
13553
|
+
}
|
|
13554
|
+
if (read.title !== undefined)
|
|
13555
|
+
return lineOf(issue, read, "title", null);
|
|
13556
|
+
throw lineRefusal(`Name the edit: --${FLAG.appendFile}, --${FLAG.append}, --${FLAG.replaceFile} or --${FLAG.title}`, EDIT_USAGE);
|
|
13557
|
+
}
|
|
13558
|
+
async function readEditText(source, stdin) {
|
|
13559
|
+
if (source.from === "line")
|
|
13560
|
+
return source.text;
|
|
13561
|
+
const where = source.path === STDIN_PATH2 ? "standard input" : `"${source.path}"`;
|
|
13562
|
+
let text;
|
|
13563
|
+
try {
|
|
13564
|
+
text = source.path === STDIN_PATH2 ? await stdin() : await Bun.file(source.path).text();
|
|
13565
|
+
} catch (error) {
|
|
13566
|
+
throw lineRefusal(`--${source.flag} cannot read ${where}: ${messageOf(error)}`, EDIT_USAGE);
|
|
13567
|
+
}
|
|
13568
|
+
if (text.trim() === "")
|
|
13569
|
+
throw lineRefusal(`--${source.flag} read nothing but whitespace from ${where}`, EDIT_USAGE);
|
|
13570
|
+
return text;
|
|
13571
|
+
}
|
|
13572
|
+
async function readEditRequest(line, stdin) {
|
|
13573
|
+
const { source, ...rest } = line;
|
|
13574
|
+
if (source === null)
|
|
13575
|
+
return { ...rest, text: null };
|
|
13576
|
+
return { ...rest, text: await readEditText(source, stdin) };
|
|
13577
|
+
}
|
|
13578
|
+
function requireEditingTracker(tracker) {
|
|
13579
|
+
if (holdsEditing(tracker))
|
|
13580
|
+
return tracker;
|
|
13581
|
+
throw new CommandExit(1, `\u274C The ${tracker.kind} tracker cannot edit an issue: it reads and writes no issue body.`);
|
|
13582
|
+
}
|
|
13583
|
+
function holdsEditing(tracker) {
|
|
13584
|
+
return tracker.editable !== undefined && tracker.edit !== undefined;
|
|
13585
|
+
}
|
|
13586
|
+
function planEdit(request, found, now) {
|
|
13587
|
+
const titled = request.title === null ? {} : { title: request.title };
|
|
13588
|
+
if (request.kind === "append") {
|
|
13589
|
+
const block = renderUpdateBlock(now, request.reason ?? "", request.text ?? "");
|
|
13590
|
+
const already = carriesUpdateBlock(found.body, block);
|
|
13591
|
+
const after = already ? found.body : appendedBody(found.body, block);
|
|
13592
|
+
return { added: request.text ?? "", shown: block, block, after, already, change: { body: after } };
|
|
13593
|
+
}
|
|
13594
|
+
if (request.kind === "replace") {
|
|
13595
|
+
const body = replacedBody(request.text ?? "");
|
|
13596
|
+
const added = [request.title, body].filter((part) => part !== null).join(`
|
|
13597
|
+
|
|
13598
|
+
`);
|
|
13599
|
+
return { added, shown: added, block: null, after: body, already: false, change: { body, ...titled } };
|
|
13600
|
+
}
|
|
13601
|
+
const title = request.title ?? "";
|
|
13602
|
+
return { added: title, shown: title, block: null, after: found.body, already: false, change: { title } };
|
|
13603
|
+
}
|
|
13604
|
+
function trustedLine(reading, repo) {
|
|
13605
|
+
return reading.source === "allow-list" ? `${reading.login}, listed in board.trustedAuthors` : `${reading.login}, who has write access to ${repo}`;
|
|
13606
|
+
}
|
|
13607
|
+
function gatesFor(request) {
|
|
13608
|
+
const readings = [];
|
|
13609
|
+
return {
|
|
13610
|
+
readings,
|
|
13611
|
+
pass: (gate, line) => {
|
|
13612
|
+
readings.push({ gate, line });
|
|
13613
|
+
},
|
|
13614
|
+
refuse: (refusal10, sentence) => {
|
|
13615
|
+
throw new EditRefusal(refusal10, sentence, [...readings], request.dryRun);
|
|
13616
|
+
}
|
|
13617
|
+
};
|
|
13618
|
+
}
|
|
13619
|
+
async function weighOwnership(options, found, gates) {
|
|
13620
|
+
const { request, tracker } = options;
|
|
13621
|
+
if (tracker.kind === ACCOUNTLESS_KIND) {
|
|
13622
|
+
gates.pass("editor", LOCAL_OWNERSHIP);
|
|
13623
|
+
gates.pass("author", LOCAL_OWNERSHIP);
|
|
13624
|
+
return;
|
|
13625
|
+
}
|
|
13626
|
+
const trust = options.trust();
|
|
13627
|
+
const item = { kind: "issue", number: request.issue, repo: trust.repo };
|
|
13628
|
+
const editor = await readEditorTrust(trust, options.editor);
|
|
13629
|
+
if (!editor.trusted || editor.reading === null) {
|
|
13630
|
+
gates.refuse(editor.refusal ?? "ownership-unknown", editorRefusalMessage(item, editor));
|
|
13631
|
+
}
|
|
13632
|
+
gates.pass("editor", trustedLine(editor.reading, trust.repo));
|
|
13633
|
+
const author = await readBoardTrust(trust, found.author);
|
|
13634
|
+
if (!author.trusted) {
|
|
13635
|
+
const refusal10 = author.refusal === "lookup-failed" ? "ownership-unknown" : "ownership";
|
|
13636
|
+
gates.refuse(refusal10, trustRefusalMessage(item, author));
|
|
13637
|
+
}
|
|
13638
|
+
gates.pass("author", `#${String(request.issue)} was opened by ${trustedLine(author, trust.repo)}`);
|
|
13639
|
+
}
|
|
13640
|
+
function weighText(request, found, planned, gates) {
|
|
13641
|
+
try {
|
|
13642
|
+
requireEditText({
|
|
13643
|
+
issue: request.issue,
|
|
13644
|
+
labels: found.labels,
|
|
13645
|
+
before: found.body,
|
|
13646
|
+
added: planned.added,
|
|
13647
|
+
after: planned.after
|
|
13648
|
+
});
|
|
13649
|
+
} catch (error) {
|
|
13650
|
+
if (error instanceof CommandExit)
|
|
13651
|
+
gates.refuse("text", error.message);
|
|
13652
|
+
throw error;
|
|
13653
|
+
}
|
|
13654
|
+
const complete = completenessApplies(found.labels, found.body) ? ", and the edited body still fills the spec template" : "";
|
|
13655
|
+
gates.pass("text", `the added text names no machine path or credential${complete}`);
|
|
13656
|
+
}
|
|
13657
|
+
function stateLine(input, reading) {
|
|
13658
|
+
if (reading.branch !== null) {
|
|
13659
|
+
return `a loop holds it on ${reading.branch}; --while-in-development lets the append through`;
|
|
13660
|
+
}
|
|
13661
|
+
if (input.savedCopies.length > 0)
|
|
13662
|
+
return `planned, its saved copy at ${input.savedCopies.join(", ")}`;
|
|
13663
|
+
return input.labels.length === 0 ? "open, with no plan and no loop building from it" : `open, labelled ${input.labels.join(", ")}, with no saved copy and no loop building from it`;
|
|
13664
|
+
}
|
|
13665
|
+
function weighState(options, found, gates) {
|
|
13666
|
+
const { request } = options;
|
|
13667
|
+
const input = {
|
|
13668
|
+
issue: request.issue,
|
|
13669
|
+
title: found.title,
|
|
13670
|
+
open: found.open,
|
|
13671
|
+
labels: found.labels,
|
|
13672
|
+
savedCopies: findSavedCopies(options.root, options.specsDir, request.issue),
|
|
13673
|
+
kind: request.kind,
|
|
13674
|
+
whileInDevelopment: request.whileInDevelopment
|
|
13675
|
+
};
|
|
13676
|
+
const reading = readEditState(input);
|
|
13677
|
+
if (!reading.pass)
|
|
13678
|
+
gates.refuse(reading.refusal, editStateRefusalMessage(input, reading));
|
|
13679
|
+
gates.pass("state", stateLine(input, reading));
|
|
13680
|
+
return reading;
|
|
13681
|
+
}
|
|
13682
|
+
function normalised(text) {
|
|
13683
|
+
return text.replace(/\r\n/gu, `
|
|
13684
|
+
`).replace(TRAILING_WHITESPACE2, "");
|
|
13685
|
+
}
|
|
13686
|
+
function writeHeld(found, planned, reread) {
|
|
13687
|
+
const { change } = planned;
|
|
13688
|
+
if (change.title !== undefined && reread.title !== change.title)
|
|
13689
|
+
return false;
|
|
13690
|
+
const before = found.body.replace(/\r\n/gu, `
|
|
13691
|
+
`);
|
|
13692
|
+
const after = reread.body.replace(/\r\n/gu, `
|
|
13693
|
+
`);
|
|
13694
|
+
if (planned.block !== null)
|
|
13695
|
+
return originalPartHolds(before, after, normalised(planned.block));
|
|
13696
|
+
return normalised(after) === normalised(change.body ?? found.body);
|
|
13697
|
+
}
|
|
13698
|
+
function conflictPaths(issue, stamp, attempt3) {
|
|
13699
|
+
const suffix = attempt3 === 1 ? "" : `-${String(attempt3)}`;
|
|
13700
|
+
const name = (side) => join29(EDIT_SCRATCH_DIR, `${boardId(issue)}-edit-${side}-${stamp}${suffix}.md`);
|
|
13701
|
+
return { before: name("before"), after: name("after") };
|
|
13702
|
+
}
|
|
13703
|
+
function saveConflictBodies(root, issue, now, before, after) {
|
|
13704
|
+
mkdirSync5(join29(root, EDIT_SCRATCH_DIR), { recursive: true });
|
|
13705
|
+
const stamp = fileStamp(now);
|
|
13706
|
+
let attempt3 = 1;
|
|
13707
|
+
let paths = conflictPaths(issue, stamp, attempt3);
|
|
13708
|
+
while (existsSync9(join29(root, paths.before)) || existsSync9(join29(root, paths.after))) {
|
|
13709
|
+
attempt3 += 1;
|
|
13710
|
+
paths = conflictPaths(issue, stamp, attempt3);
|
|
13711
|
+
}
|
|
13712
|
+
writeFileSync6(join29(root, paths.before), before, { flag: "wx" });
|
|
13713
|
+
writeFileSync6(join29(root, paths.after), after, { flag: "wx" });
|
|
13714
|
+
return paths;
|
|
13715
|
+
}
|
|
13716
|
+
function heldOutcome(request, state) {
|
|
13717
|
+
if (request.kind !== "append")
|
|
13718
|
+
return "replaced";
|
|
13719
|
+
return state.pass && state.staleCopy ? "stale-copy" : "appended";
|
|
13720
|
+
}
|
|
13721
|
+
function outcomeMessage(request, outcome, branch, name) {
|
|
13722
|
+
switch (outcome) {
|
|
13723
|
+
case "appended":
|
|
13724
|
+
return `Appended an update to ${name}; the original part is unchanged.`;
|
|
13725
|
+
case "stale-copy":
|
|
13726
|
+
return `Appended an update to ${name}. ${staleCopyMessage(request.issue, branch)}`;
|
|
13727
|
+
case "already":
|
|
13728
|
+
return `${name} already ends with this update; nothing was written.`;
|
|
13729
|
+
case "replaced": {
|
|
13730
|
+
if (request.text === null)
|
|
13731
|
+
return `Changed the title of ${name}.`;
|
|
13732
|
+
return request.title === null ? `Replaced the body of ${name}.` : `Replaced the body and the title of ${name}.`;
|
|
13733
|
+
}
|
|
13734
|
+
case "conflict":
|
|
13735
|
+
throw new TypeError("issue edit: a conflict is worded with the paths it saved");
|
|
13736
|
+
}
|
|
13737
|
+
}
|
|
13738
|
+
function dryRunMessage(issue, due, branch) {
|
|
13739
|
+
const stale = due === "stale-copy" ? ` ${staleCopyMessage(issue, branch)}` : "";
|
|
13740
|
+
return `--dry-run: nothing was written; the edit would answer ${due}.${stale}`;
|
|
13741
|
+
}
|
|
13742
|
+
function conflictMessage(name, scratch) {
|
|
13743
|
+
return `The edit was written, but ${name} read back otherwise: somebody changed it around the write. ` + `The body read before is at ${scratch.before} and the body read after at ${scratch.after}; ` + "compare the two and make the edit again over the body as it stands.";
|
|
13744
|
+
}
|
|
13745
|
+
function report2(request, planned, readings, fields) {
|
|
13746
|
+
return Object.freeze({
|
|
13747
|
+
...fields,
|
|
13748
|
+
issue: request.issue,
|
|
13749
|
+
exitCode: editExitCode(fields.outcome),
|
|
13750
|
+
dryRun: request.dryRun,
|
|
13751
|
+
added: planned.shown,
|
|
13752
|
+
readings,
|
|
13753
|
+
refresh: fields.outcome === "stale-copy" ? refreshCommand(request.issue) : null,
|
|
13754
|
+
scratch: null
|
|
13755
|
+
});
|
|
13756
|
+
}
|
|
13757
|
+
async function runIssueEdit(options) {
|
|
13758
|
+
const { request, tracker } = options;
|
|
13759
|
+
const ref8 = issueRef(tracker, String(request.issue));
|
|
13760
|
+
const name = issueName(ref8);
|
|
13761
|
+
const now = options.now();
|
|
13762
|
+
const found = await onTracker(tracker, `read ${name}`, () => tracker.editable(ref8));
|
|
13763
|
+
const gates = gatesFor(request);
|
|
13764
|
+
await weighOwnership(options, found, gates);
|
|
13765
|
+
const planned = planEdit(request, found, now);
|
|
13766
|
+
weighText(request, found, planned, gates);
|
|
13767
|
+
const state = weighState(options, found, gates);
|
|
13768
|
+
const { branch } = state;
|
|
13769
|
+
function done(outcome, written, message) {
|
|
13770
|
+
return report2(request, planned, gates.readings, { outcome, written, branch, message });
|
|
13771
|
+
}
|
|
13772
|
+
const due = planned.already ? "already" : heldOutcome(request, state);
|
|
13773
|
+
if (request.dryRun)
|
|
13774
|
+
return done(due, false, dryRunMessage(request.issue, due, branch));
|
|
13775
|
+
if (planned.already)
|
|
13776
|
+
return done("already", false, outcomeMessage(request, "already", branch, name));
|
|
13777
|
+
await onTracker(tracker, `edit ${name}`, () => tracker.edit(ref8, planned.change));
|
|
13778
|
+
const reread = await onTracker(tracker, `read ${name} again after the edit was written`, () => tracker.editable(ref8));
|
|
13779
|
+
if (writeHeld(found, planned, reread))
|
|
13780
|
+
return done(due, true, outcomeMessage(request, due, branch, name));
|
|
13781
|
+
const scratch = saveConflictBodies(options.root, request.issue, now, found.body, reread.body);
|
|
13782
|
+
return Object.freeze({ ...done("conflict", true, conflictMessage(name, scratch)), scratch });
|
|
13783
|
+
}
|
|
13784
|
+
function renderEditReport(made) {
|
|
13785
|
+
if (!made.dryRun)
|
|
13786
|
+
return [made.message];
|
|
13787
|
+
return [...made.readings.map(renderReading), "added:", made.added, made.message];
|
|
13788
|
+
}
|
|
13789
|
+
async function editIssue2(context, seams) {
|
|
13790
|
+
const line = readEditLine(context.args, context.flags);
|
|
13791
|
+
const request = await readEditRequest(line, seams.stdin ?? (() => Bun.stdin.text()));
|
|
13792
|
+
const project = issueProject(context);
|
|
13793
|
+
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
13794
|
+
const editing = requireEditingTracker(tracker);
|
|
13795
|
+
const config = issueSubjectConfig(project, () => {
|
|
13796
|
+
return;
|
|
13797
|
+
});
|
|
13798
|
+
const gh = seams.gh ?? createGhRunner({ cwd: project.root });
|
|
13799
|
+
const openGit = seams.openGit ?? createGitRunner;
|
|
13800
|
+
const made = await runIssueEdit({
|
|
13801
|
+
request,
|
|
13802
|
+
tracker: editing,
|
|
13803
|
+
root: project.root,
|
|
13804
|
+
specsDir: config.specsDir,
|
|
13805
|
+
trust: () => ghBoardTrust({
|
|
13806
|
+
gh,
|
|
13807
|
+
trustedAuthors: config.boardTrustedAuthors,
|
|
13808
|
+
repo: boardRepoLabel(openGit(project.root))
|
|
13809
|
+
}),
|
|
13810
|
+
editor: createGhEditorLogin({ gh }),
|
|
13811
|
+
now: seams.now ?? (() => new Date)
|
|
13812
|
+
});
|
|
13813
|
+
return Object.freeze({ ...made, tracker: data });
|
|
13814
|
+
}
|
|
13815
|
+
|
|
13816
|
+
// src/commands/issue/edit.ts
|
|
13817
|
+
var DEFAULT_EDIT_SEAMS = Object.freeze({});
|
|
13818
|
+
function createIssueEditCommand(seams = DEFAULT_EDIT_SEAMS) {
|
|
13819
|
+
const command = {
|
|
13820
|
+
name: "issue edit",
|
|
13821
|
+
subject: "issue",
|
|
13822
|
+
action: "edit",
|
|
13823
|
+
summary: "append a dated update to an issue body, or replace its body or title, after four gates",
|
|
13824
|
+
description: "Edits one issue on the tracker the chain lands on, after four gates weighed in order, the first" + " refusal ending the run with nothing written and exit code 2: the account editing must have write" + " access or be in board.trustedAuthors; so must the issue's author; the text added must leak nothing," + " and a complete spec must stay complete; and the issue must be open, its labels and saved spec copies" + " allowing the edit. An append (`--append-file` or `--append`, with `--reason`) adds a block" + " `**Updated <YYYY-MM-DD>, <reason>:**` below the body, leaving the original part byte for byte; the" + " same block already closing the body answers `already`. `--replace-file` replaces the body and" + " `--title` the title. After the write the issue is read again: a body that no longer holds what was" + " written answers `conflict` with exit code 1, the bodies read before and after saved under" + " `.rafa/scratch/`. An append on an issue already planned answers `stale-copy` and names" + " `rafa plan create --issue=<n> --refresh`, which it never runs. `--dry-run` prints each gate's" + " reading and the added part and writes nothing. With `--output=json` the report and the tracker are" + " the data of the terminal result event.",
|
|
13825
|
+
args: [
|
|
13826
|
+
{
|
|
13827
|
+
name: "n",
|
|
13828
|
+
description: "The issue number on the tracker.",
|
|
13829
|
+
type: "string",
|
|
13830
|
+
required: true
|
|
13831
|
+
}
|
|
13832
|
+
],
|
|
13833
|
+
flags: [
|
|
13834
|
+
{
|
|
13835
|
+
name: "append-file",
|
|
13836
|
+
description: "A file holding the text to append, read whole; `-` reads standard input." + " Needs --reason. Not with --append, --replace-file or --title.",
|
|
13837
|
+
type: "string"
|
|
13838
|
+
},
|
|
13839
|
+
{
|
|
13840
|
+
name: "append",
|
|
13841
|
+
description: "The text to append. Needs --reason. Not with --append-file, --replace-file or --title.",
|
|
13842
|
+
type: "string"
|
|
13843
|
+
},
|
|
13844
|
+
{
|
|
13845
|
+
name: "reason",
|
|
13846
|
+
description: "Why the update was made, written into its heading. Required with an append, refused otherwise.",
|
|
13847
|
+
type: "string"
|
|
13848
|
+
},
|
|
13849
|
+
{
|
|
13850
|
+
name: "replace-file",
|
|
13851
|
+
description: "A file holding the new body, read whole; `-` reads standard input. Not with an append.",
|
|
13852
|
+
type: "string"
|
|
13853
|
+
},
|
|
13854
|
+
{
|
|
13855
|
+
name: "title",
|
|
13856
|
+
description: "The new title, alone or beside --replace-file. Not with an append.",
|
|
13857
|
+
type: "string"
|
|
13858
|
+
},
|
|
13859
|
+
{
|
|
13860
|
+
name: "while-in-development",
|
|
13861
|
+
description: "Allows an append on an issue a loop has claimed, answering stale-copy. Only with an append.",
|
|
13862
|
+
type: "boolean"
|
|
13863
|
+
},
|
|
13864
|
+
{
|
|
13865
|
+
name: "dry-run",
|
|
13866
|
+
description: "Prints the added part and each gate's reading and writes nothing; a refusal still exits 2.",
|
|
13867
|
+
type: "boolean"
|
|
13868
|
+
}
|
|
13869
|
+
],
|
|
13870
|
+
examples: [
|
|
13871
|
+
{
|
|
13872
|
+
cmd: 'rafa issue edit 57 --append-file=update.md --reason="scope narrowed"',
|
|
13873
|
+
note: "Appends what update.md holds to #57 below a dated heading naming the reason."
|
|
13874
|
+
},
|
|
13875
|
+
{
|
|
13876
|
+
cmd: 'rafa issue edit 57 --append="Also covers --dry-run." --reason=clarified --dry-run',
|
|
13877
|
+
note: "Prints each gate's reading and the block it would append, and writes nothing."
|
|
13878
|
+
},
|
|
13879
|
+
{
|
|
13880
|
+
cmd: 'rafa issue edit 57 --title="Faster plan show"',
|
|
13881
|
+
note: "Changes the title of #57 and leaves its body alone."
|
|
13882
|
+
},
|
|
13883
|
+
{
|
|
13884
|
+
cmd: "rafa issue edit 57 --replace-file=spec.md --output=json",
|
|
13885
|
+
note: "Replaces the body of #57; the report and the tracker are the data of the result event."
|
|
13886
|
+
}
|
|
13887
|
+
],
|
|
13888
|
+
outputs: ["text", "json"],
|
|
13889
|
+
run: async (context) => {
|
|
13890
|
+
const made = await editIssue2(context, seams);
|
|
13891
|
+
if (made.exitCode !== 0)
|
|
13892
|
+
throw new CommandExit(made.exitCode, renderEditReport(made).join(`
|
|
13893
|
+
`));
|
|
13894
|
+
if (context.outputMode === "json") {
|
|
13895
|
+
context.output.result(made);
|
|
13896
|
+
return;
|
|
13897
|
+
}
|
|
13898
|
+
for (const line of renderEditReport(made))
|
|
13899
|
+
context.output.info(line);
|
|
13900
|
+
}
|
|
13901
|
+
};
|
|
13902
|
+
return Object.freeze(command);
|
|
13903
|
+
}
|
|
13904
|
+
var edit_default = createIssueEditCommand();
|
|
13905
|
+
|
|
13280
13906
|
// src/commands/issue/move.ts
|
|
13281
13907
|
var USAGE23 = "rafa issue move <id> <state>";
|
|
13282
13908
|
async function moveIssue(context, seams) {
|
|
@@ -14201,7 +14827,7 @@ function poll(following, before, context, seams) {
|
|
|
14201
14827
|
}
|
|
14202
14828
|
return { cursor, found: null };
|
|
14203
14829
|
}
|
|
14204
|
-
function
|
|
14830
|
+
function lineOf2(found, following, session, reading) {
|
|
14205
14831
|
if (found.event !== null)
|
|
14206
14832
|
return eventLine(found.event);
|
|
14207
14833
|
if (found.reason === "exit")
|
|
@@ -14222,7 +14848,7 @@ function resultOf5(found, following, seams) {
|
|
|
14222
14848
|
return {
|
|
14223
14849
|
reason: found.reason,
|
|
14224
14850
|
exitCode: found.reason === "timeout" ? WAIT_TIMEOUT_EXIT : waitExitCode(found.reason),
|
|
14225
|
-
line:
|
|
14851
|
+
line: lineOf2(found, following, session, reading),
|
|
14226
14852
|
event: found.event,
|
|
14227
14853
|
session,
|
|
14228
14854
|
awakeMinutes: reading.awakeMinutes,
|
|
@@ -14520,7 +15146,7 @@ var STATE_MARK = "\uD83D\uDCCD";
|
|
|
14520
15146
|
var PROPOSAL_MARK = "\uD83D\uDC49";
|
|
14521
15147
|
var STOP_MARK = "\u23F9";
|
|
14522
15148
|
var MAX_ACTIONS = 12;
|
|
14523
|
-
function
|
|
15149
|
+
function stateLine2(state) {
|
|
14524
15150
|
return `${STATE_MARK} ${state.reading}.`;
|
|
14525
15151
|
}
|
|
14526
15152
|
function proposalLine(state, invocation) {
|
|
@@ -14677,7 +15303,7 @@ async function playTurn(options, state, previous) {
|
|
|
14677
15303
|
for (const problem2 of state.problems)
|
|
14678
15304
|
warn3(problem2);
|
|
14679
15305
|
const invocation = actionInvocation(state, passedOptions(roadmap));
|
|
14680
|
-
info2(
|
|
15306
|
+
info2(stateLine2(state));
|
|
14681
15307
|
info2(proposalLine(state, invocation));
|
|
14682
15308
|
const halt = (stop, over = {}) => ({ stop, step: stepOf2(state, invocation, over) });
|
|
14683
15309
|
if (dryRun !== null)
|
|
@@ -14703,7 +15329,7 @@ async function runNextChain(options) {
|
|
|
14703
15329
|
let steps = [];
|
|
14704
15330
|
let hops = [];
|
|
14705
15331
|
let previous = null;
|
|
14706
|
-
const
|
|
15332
|
+
const report3 = (stop) => Object.freeze({
|
|
14707
15333
|
steps: Object.freeze([...steps]),
|
|
14708
15334
|
stop,
|
|
14709
15335
|
...roadmap === undefined ? {} : { hops: Object.freeze([...hops]) }
|
|
@@ -14712,7 +15338,7 @@ async function runNextChain(options) {
|
|
|
14712
15338
|
const line = stopLine(stop, state, ceiling, dryRun, roadmap !== undefined);
|
|
14713
15339
|
if (line !== null)
|
|
14714
15340
|
info2(line);
|
|
14715
|
-
return
|
|
15341
|
+
return report3(stop);
|
|
14716
15342
|
};
|
|
14717
15343
|
const turn = async (state, before) => {
|
|
14718
15344
|
const end = await playTurn(options, state, before);
|
|
@@ -14752,8 +15378,8 @@ async function runStateAction(context, sources, state, passed) {
|
|
|
14752
15378
|
if (outcome.said !== "")
|
|
14753
15379
|
context.output.info(outcome.said);
|
|
14754
15380
|
}
|
|
14755
|
-
function endOfEpic(context, sources,
|
|
14756
|
-
if (epic === null ||
|
|
15381
|
+
function endOfEpic(context, sources, report3, epic) {
|
|
15382
|
+
if (epic === null || report3.steps.at(-1)?.state !== "nothing-left")
|
|
14757
15383
|
return;
|
|
14758
15384
|
const release = sources.release();
|
|
14759
15385
|
if (release.problem !== null)
|
|
@@ -14787,7 +15413,7 @@ async function runNext(context, seams) {
|
|
|
14787
15413
|
const prompter = lazyPrompter(openPrompter);
|
|
14788
15414
|
const dry = watchDryEpic();
|
|
14789
15415
|
try {
|
|
14790
|
-
const
|
|
15416
|
+
const report3 = await runNextChain({
|
|
14791
15417
|
read: () => readNextState(dry.watch(sources.answer())),
|
|
14792
15418
|
run: (state) => runStateAction(context, sources, state, {
|
|
14793
15419
|
roadmap: sources.roadmap !== undefined,
|
|
@@ -14808,8 +15434,8 @@ async function runNext(context, seams) {
|
|
|
14808
15434
|
}),
|
|
14809
15435
|
...roadmap ? { roadmap: { afterLoop: () => homeAfterLoop(context, sources), claimAhead } } : {}
|
|
14810
15436
|
});
|
|
14811
|
-
endOfEpic(context, sources,
|
|
14812
|
-
return
|
|
15437
|
+
endOfEpic(context, sources, report3, dry.last());
|
|
15438
|
+
return report3;
|
|
14813
15439
|
} finally {
|
|
14814
15440
|
prompter.close();
|
|
14815
15441
|
}
|
|
@@ -14871,9 +15497,9 @@ function createNextCommand(seams = DEFAULT_NEXT_SEAMS) {
|
|
|
14871
15497
|
outputs: ["text", "json"],
|
|
14872
15498
|
spends: { when: "through", what: "when the step it runs is one of the above" },
|
|
14873
15499
|
run: async (context) => {
|
|
14874
|
-
const
|
|
15500
|
+
const report3 = await runNext(context, seams);
|
|
14875
15501
|
if (context.outputMode === "json")
|
|
14876
|
-
context.output.result(
|
|
15502
|
+
context.output.result(report3);
|
|
14877
15503
|
}
|
|
14878
15504
|
};
|
|
14879
15505
|
return Object.freeze(command);
|
|
@@ -14991,7 +15617,7 @@ var create_default2 = endingWith(wrapped2);
|
|
|
14991
15617
|
|
|
14992
15618
|
// src/commands/plan/risk.ts
|
|
14993
15619
|
import { readFileSync as readFileSync16 } from "fs";
|
|
14994
|
-
import { resolve as
|
|
15620
|
+
import { resolve as resolve11 } from "path";
|
|
14995
15621
|
var COMMAND6 = "rafa plan risk";
|
|
14996
15622
|
var USAGE33 = "rafa plan risk [<plan>] [--strict]";
|
|
14997
15623
|
var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
|
|
@@ -15004,7 +15630,7 @@ function riskPlanPath(root, planDir, typed) {
|
|
|
15004
15630
|
return path;
|
|
15005
15631
|
if (typed !== null)
|
|
15006
15632
|
throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
|
|
15007
|
-
const lookedFor = [...new Set([
|
|
15633
|
+
const lookedFor = [...new Set([resolve11(root, planDir, DEFAULT_PLAN_FILE), resolve11(root, DEFAULT_PLAN_FILE)])];
|
|
15008
15634
|
throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
|
|
15009
15635
|
Usage: ${USAGE33}`);
|
|
15010
15636
|
}
|
|
@@ -15013,8 +15639,8 @@ function riskLine(finding) {
|
|
|
15013
15639
|
const place2 = finding.file === undefined ? "" : `${finding.file}${line}: `;
|
|
15014
15640
|
return `${place2}${finding.kind}: ${finding.text}`;
|
|
15015
15641
|
}
|
|
15016
|
-
function strictRefusal(
|
|
15017
|
-
return `\u274C ${
|
|
15642
|
+
function strictRefusal(report3) {
|
|
15643
|
+
return `\u274C ${report3.plan}: ${plural(report3.total.high, "high finding")}; --strict refuses a plan with any`;
|
|
15018
15644
|
}
|
|
15019
15645
|
async function readRisk(path, project, config, context, seams) {
|
|
15020
15646
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
@@ -15076,24 +15702,24 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
15076
15702
|
context.output.warn(message);
|
|
15077
15703
|
});
|
|
15078
15704
|
const path = riskPlanPath(project.root, config.planDir, typed);
|
|
15079
|
-
const
|
|
15080
|
-
const
|
|
15705
|
+
const report3 = await readRisk(path, project, config, context, seams);
|
|
15706
|
+
const refused2 = strict && report3.total.high > 0;
|
|
15081
15707
|
if (context.outputMode === "json") {
|
|
15082
|
-
if (!
|
|
15083
|
-
context.output.result(
|
|
15708
|
+
if (!refused2) {
|
|
15709
|
+
context.output.result(report3);
|
|
15084
15710
|
return;
|
|
15085
15711
|
}
|
|
15086
|
-
for (const finding of
|
|
15712
|
+
for (const finding of report3.findings) {
|
|
15087
15713
|
if (finding.level === "high")
|
|
15088
15714
|
context.output.error(riskLine(finding));
|
|
15089
15715
|
}
|
|
15090
15716
|
} else {
|
|
15091
|
-
for (const line of renderRiskText(
|
|
15717
|
+
for (const line of renderRiskText(report3).split(`
|
|
15092
15718
|
`))
|
|
15093
15719
|
context.output.info(line);
|
|
15094
15720
|
}
|
|
15095
|
-
if (
|
|
15096
|
-
throw new CommandExit(1, strictRefusal(
|
|
15721
|
+
if (refused2)
|
|
15722
|
+
throw new CommandExit(1, strictRefusal(report3));
|
|
15097
15723
|
}
|
|
15098
15724
|
};
|
|
15099
15725
|
return Object.freeze(command);
|
|
@@ -15102,7 +15728,7 @@ var risk_default = createPlanRiskCommand();
|
|
|
15102
15728
|
|
|
15103
15729
|
// src/commands/plan/show.ts
|
|
15104
15730
|
import { readFileSync as readFileSync17 } from "fs";
|
|
15105
|
-
import { join as
|
|
15731
|
+
import { join as join30 } from "path";
|
|
15106
15732
|
var USAGE34 = "rafa plan show <stub> [--tracker]";
|
|
15107
15733
|
var COMMAND7 = "rafa plan show";
|
|
15108
15734
|
function readTrackerFlag(value) {
|
|
@@ -15120,9 +15746,9 @@ function showPlan(plans, stub, tracker) {
|
|
|
15120
15746
|
throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
|
|
15121
15747
|
}
|
|
15122
15748
|
const name = planFileName(stub, tracker);
|
|
15123
|
-
const file =
|
|
15749
|
+
const file = join30(plans.path, name);
|
|
15124
15750
|
if (!isFile(file))
|
|
15125
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
15751
|
+
throw new CommandExit(1, missingText(stub, tracker, join30(plans.label, name)));
|
|
15126
15752
|
const model = parsePlan(readFileSync17(file, "utf8"));
|
|
15127
15753
|
const { header } = model;
|
|
15128
15754
|
return {
|
|
@@ -15146,7 +15772,7 @@ function taskLine(task) {
|
|
|
15146
15772
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
15147
15773
|
}
|
|
15148
15774
|
function renderShownPlan(shown, dirLabel) {
|
|
15149
|
-
const relative8 =
|
|
15775
|
+
const relative8 = join30(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
15150
15776
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
15151
15777
|
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative8}`];
|
|
15152
15778
|
if (shown.header.issue !== null)
|
|
@@ -15700,7 +16326,7 @@ function freedIssueLine(issue) {
|
|
|
15700
16326
|
const title = issue.title.trim();
|
|
15701
16327
|
return title === "" ? `${INDENT}#${String(issue.number)}` : `${INDENT}#${String(issue.number)} ${title}`;
|
|
15702
16328
|
}
|
|
15703
|
-
function
|
|
16329
|
+
function report3(closed, freed, problem2) {
|
|
15704
16330
|
return Object.freeze({ relationships: "native", closed, freed: Object.freeze(freed), problem: problem2 });
|
|
15705
16331
|
}
|
|
15706
16332
|
async function readFreed(gh, closed) {
|
|
@@ -15721,14 +16347,14 @@ async function freedAfterMerge(options) {
|
|
|
15721
16347
|
} catch (error) {
|
|
15722
16348
|
const problem2 = messageOf(error);
|
|
15723
16349
|
warn3(freedProblemLine(problem2));
|
|
15724
|
-
return
|
|
16350
|
+
return report3(closed, [], problem2);
|
|
15725
16351
|
}
|
|
15726
16352
|
if (freed.length > 0) {
|
|
15727
16353
|
info2(freedHeaderLine(closed, freed.length));
|
|
15728
16354
|
for (const issue of freed)
|
|
15729
16355
|
info2(freedIssueLine(issue));
|
|
15730
16356
|
}
|
|
15731
|
-
return
|
|
16357
|
+
return report3(closed, freed, null);
|
|
15732
16358
|
}
|
|
15733
16359
|
|
|
15734
16360
|
// src/commands/pr/merge-guard.ts
|
|
@@ -15785,18 +16411,18 @@ async function guardBeforeMerge(step) {
|
|
|
15785
16411
|
const answer2 = answerOf(reading);
|
|
15786
16412
|
const reaction = guardReaction(reading, settings);
|
|
15787
16413
|
const lines = guardLines(reading, settings.releaseVersionFile);
|
|
15788
|
-
const
|
|
16414
|
+
const report4 = { answer: answer2, reaction, lines };
|
|
15789
16415
|
if (reaction === "refuse")
|
|
15790
16416
|
throw guardRefusal(refusalHead(step.detail.number, answer2), lines);
|
|
15791
16417
|
if (reaction === "ask")
|
|
15792
|
-
return { report:
|
|
16418
|
+
return { report: report4, go: await askGuard(step, answer2, lines) };
|
|
15793
16419
|
printLines(step, reaction, lines);
|
|
15794
|
-
return { report:
|
|
16420
|
+
return { report: report4, go: true };
|
|
15795
16421
|
}
|
|
15796
16422
|
|
|
15797
16423
|
// src/commands/pr/merge-loop-worktree.ts
|
|
15798
|
-
import { copyFileSync as copyFileSync3, mkdirSync as
|
|
15799
|
-
import { dirname as dirname6, isAbsolute as isAbsolute6, join as
|
|
16424
|
+
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync6, realpathSync as realpathSync3, statSync as statSync9 } from "fs";
|
|
16425
|
+
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join31, relative as relative8, resolve as resolve12, sep as sep4 } from "path";
|
|
15800
16426
|
var REFUSAL_PREFIX = "rafa pr merge refuses";
|
|
15801
16427
|
var INDENT4 = " ";
|
|
15802
16428
|
var LIVE_STATES = new Set(["running", "paused"]);
|
|
@@ -15804,9 +16430,9 @@ function defaultLoopWorktreeSeams(mainCheckout) {
|
|
|
15804
16430
|
return {
|
|
15805
16431
|
git: createGitRunner(mainCheckout),
|
|
15806
16432
|
gitAt: createGitRunner,
|
|
15807
|
-
modifiedAt: (path) =>
|
|
16433
|
+
modifiedAt: (path) => statSync9(path, { throwIfNoEntry: false })?.mtime ?? null,
|
|
15808
16434
|
copyFile: (from, to) => {
|
|
15809
|
-
|
|
16435
|
+
mkdirSync6(dirname6(to), { recursive: true });
|
|
15810
16436
|
copyFileSync3(from, to);
|
|
15811
16437
|
},
|
|
15812
16438
|
realPath: (path) => {
|
|
@@ -15837,11 +16463,11 @@ function readLoopHolder(seams, reading) {
|
|
|
15837
16463
|
};
|
|
15838
16464
|
}
|
|
15839
16465
|
function freeLoopWorktree(seams, reading, holder) {
|
|
15840
|
-
const into =
|
|
16466
|
+
const into = resolve12(reading.mainCheckout, reading.planDir);
|
|
15841
16467
|
const files = [];
|
|
15842
16468
|
for (const name of planFileNames(holder.stub)) {
|
|
15843
|
-
const from =
|
|
15844
|
-
const to =
|
|
16469
|
+
const from = resolve12(holder.path, reading.planDir, name);
|
|
16470
|
+
const to = join31(into, name);
|
|
15845
16471
|
try {
|
|
15846
16472
|
files.push({ name, from, to, outcome: copyPlanFile(seams, from, to) });
|
|
15847
16473
|
} catch (error) {
|
|
@@ -17633,8 +18259,8 @@ function describeAge(updatedAt, now) {
|
|
|
17633
18259
|
return `${plural3(hours, "hour", "hours")} ago`;
|
|
17634
18260
|
return `${plural3(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
17635
18261
|
}
|
|
17636
|
-
function triageCommand(number,
|
|
17637
|
-
return
|
|
18262
|
+
function triageCommand(number, resolve13 = false) {
|
|
18263
|
+
return resolve13 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
17638
18264
|
}
|
|
17639
18265
|
function joinNumbers(pulls) {
|
|
17640
18266
|
const names = pulls.map((pull2) => `#${pull2.number}`);
|
|
@@ -17645,14 +18271,14 @@ function joinNumbers(pulls) {
|
|
|
17645
18271
|
function byNumber2(pulls) {
|
|
17646
18272
|
return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
|
|
17647
18273
|
}
|
|
17648
|
-
function skipped(pull2, now,
|
|
18274
|
+
function skipped(pull2, now, resolve13) {
|
|
17649
18275
|
return Object.freeze({
|
|
17650
18276
|
number: pull2.number,
|
|
17651
18277
|
title: pull2.title,
|
|
17652
18278
|
updatedAt: pull2.updatedAt,
|
|
17653
18279
|
age: describeAge(pull2.updatedAt, now),
|
|
17654
18280
|
fresh: isFreshCandidate(pull2.updatedAt, now),
|
|
17655
|
-
command: triageCommand(pull2.number,
|
|
18281
|
+
command: triageCommand(pull2.number, resolve13)
|
|
17656
18282
|
});
|
|
17657
18283
|
}
|
|
17658
18284
|
function skippedLine(one) {
|
|
@@ -17696,15 +18322,15 @@ function listReading(rest, count) {
|
|
|
17696
18322
|
return listing("list", Object.freeze([]), rest, headline);
|
|
17697
18323
|
}
|
|
17698
18324
|
function selectTriagePullRequests(input) {
|
|
17699
|
-
const
|
|
18325
|
+
const resolve13 = input.resolve === true;
|
|
17700
18326
|
const { current, now } = input;
|
|
17701
18327
|
if (current !== null) {
|
|
17702
18328
|
return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
|
|
17703
18329
|
}
|
|
17704
18330
|
const candidates = byNumber2(input.candidates);
|
|
17705
18331
|
const count = candidates.length;
|
|
17706
|
-
const lines = candidates.map((pull2) => skipped(pull2, now,
|
|
17707
|
-
if (
|
|
18332
|
+
const lines = candidates.map((pull2) => skipped(pull2, now, resolve13));
|
|
18333
|
+
if (resolve13 && count > 1)
|
|
17708
18334
|
return refusal12(lines, count);
|
|
17709
18335
|
if (count === 0) {
|
|
17710
18336
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
@@ -18064,8 +18690,8 @@ function withTaskBudget(plan2, usd) {
|
|
|
18064
18690
|
}
|
|
18065
18691
|
|
|
18066
18692
|
// src/pr/plans/load.ts
|
|
18067
|
-
import { existsSync as
|
|
18068
|
-
import { dirname as dirname7, join as
|
|
18693
|
+
import { existsSync as existsSync10, readFileSync as readFileSync18 } from "fs";
|
|
18694
|
+
import { dirname as dirname7, join as join32 } from "path";
|
|
18069
18695
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18070
18696
|
var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
|
|
18071
18697
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
@@ -18082,8 +18708,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
18082
18708
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
18083
18709
|
const name = pinnedPlanFileName(triageClass);
|
|
18084
18710
|
return [
|
|
18085
|
-
|
|
18086
|
-
|
|
18711
|
+
join32(moduleDir, PINNED_PLANS_DIRNAME, name),
|
|
18712
|
+
join32(moduleDir, name)
|
|
18087
18713
|
];
|
|
18088
18714
|
}
|
|
18089
18715
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -18091,7 +18717,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
18091
18717
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
18092
18718
|
}
|
|
18093
18719
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
18094
|
-
const found = candidates.find((candidate) =>
|
|
18720
|
+
const found = candidates.find((candidate) => existsSync10(candidate));
|
|
18095
18721
|
if (found === undefined) {
|
|
18096
18722
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
18097
18723
|
}
|
|
@@ -18235,21 +18861,21 @@ function readAttemptRepeat(input) {
|
|
|
18235
18861
|
}
|
|
18236
18862
|
|
|
18237
18863
|
// src/commands/pr/resolve-loop.ts
|
|
18238
|
-
import { mkdirSync as
|
|
18239
|
-
import { join as
|
|
18240
|
-
var RESOLVE_SUBDIR =
|
|
18864
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
18865
|
+
import { join as join33 } from "path";
|
|
18866
|
+
var RESOLVE_SUBDIR = join33(".rafa", "resolve");
|
|
18241
18867
|
var RUN_PREFIX = "pr-";
|
|
18242
18868
|
var ATTEMPT_PREFIX = "attempt-";
|
|
18243
18869
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
18244
18870
|
var CHILD_OUTPUT = "text";
|
|
18245
18871
|
function resolveRunDir(home, number, attempt3) {
|
|
18246
|
-
return
|
|
18872
|
+
return join33(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
18247
18873
|
}
|
|
18248
18874
|
function writeResolvePlan(write) {
|
|
18249
18875
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
18250
|
-
|
|
18251
|
-
const path =
|
|
18252
|
-
|
|
18876
|
+
mkdirSync7(dir, { recursive: true });
|
|
18877
|
+
const path = join33(dir, pinnedPlanFileName(write.triageClass));
|
|
18878
|
+
writeFileSync7(path, write.plan, "utf8");
|
|
18253
18879
|
return path;
|
|
18254
18880
|
}
|
|
18255
18881
|
function resolveLoopArgv(planPath) {
|
|
@@ -18289,8 +18915,8 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
18289
18915
|
}
|
|
18290
18916
|
|
|
18291
18917
|
// src/pr/triage/version-convert.ts
|
|
18292
|
-
import { mkdirSync as
|
|
18293
|
-
import { dirname as dirname8, join as
|
|
18918
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "fs";
|
|
18919
|
+
import { dirname as dirname8, join as join34, posix } from "path";
|
|
18294
18920
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
18295
18921
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
18296
18922
|
var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
|
|
@@ -18333,7 +18959,7 @@ function fragmentNamesAt(git, commit, directory) {
|
|
|
18333
18959
|
return [];
|
|
18334
18960
|
return listed2.stdout.split("\x00").filter((path) => path !== "").map((path) => posix.basename(path));
|
|
18335
18961
|
}
|
|
18336
|
-
function
|
|
18962
|
+
function refused2(problem2, problems) {
|
|
18337
18963
|
return { outcome: "refused", problem: problem2, problems };
|
|
18338
18964
|
}
|
|
18339
18965
|
function fragmentTitle(title, plan2) {
|
|
@@ -18370,9 +18996,9 @@ function planConversion(input, problems) {
|
|
|
18370
18996
|
const baseRef = `${input.remote ?? RELEASE_REMOTE}/${input.base}`;
|
|
18371
18997
|
const headCommit = commitOf(git, "HEAD");
|
|
18372
18998
|
if (headCommit === null)
|
|
18373
|
-
return
|
|
18999
|
+
return refused2(`the worktree at ${input.worktree} has no HEAD commit`, problems);
|
|
18374
19000
|
if (input.head !== null && headCommit !== input.head) {
|
|
18375
|
-
return
|
|
19001
|
+
return refused2(`the worktree's ${input.branch} is at ${headCommit.slice(0, SHORT_HASH_LENGTH)}, not at the pull request's` + ` head ${input.head.slice(0, SHORT_HASH_LENGTH)}; bring the branch up to its pull request's head and run it again`, problems);
|
|
18376
19002
|
}
|
|
18377
19003
|
const guard = readGuard({
|
|
18378
19004
|
git,
|
|
@@ -18382,7 +19008,7 @@ function planConversion(input, problems) {
|
|
|
18382
19008
|
now: input.now
|
|
18383
19009
|
});
|
|
18384
19010
|
if (!guard.ok)
|
|
18385
|
-
return
|
|
19011
|
+
return refused2(`the release guard could not read ${input.branch}: ${guard.problem}`, problems);
|
|
18386
19012
|
const { verdict } = guard;
|
|
18387
19013
|
if (verdict.answer === "clean" || verdict.answer === "missing") {
|
|
18388
19014
|
return { outcome: "unstamped", answer: verdict.answer, problems };
|
|
@@ -18390,25 +19016,25 @@ function planConversion(input, problems) {
|
|
|
18390
19016
|
const { stamp } = verdict;
|
|
18391
19017
|
const start2 = mergeBaseSide(git, guard.base.commit, headCommit, settings);
|
|
18392
19018
|
if (start2 === null)
|
|
18393
|
-
return
|
|
19019
|
+
return refused2(`${input.branch} and ${baseRef} share no merge base`, problems);
|
|
18394
19020
|
const level = conversionLevel(start2.version, stamp.version);
|
|
18395
19021
|
if (level === null) {
|
|
18396
|
-
return
|
|
19022
|
+
return refused2(`no release level reads off the stamp: ${input.branch} stamped ${stamp.version} over` + ` ${start2.version ?? "no version"} at its merge base with ${baseRef}`, problems);
|
|
18397
19023
|
}
|
|
18398
19024
|
if (start2.version === null)
|
|
18399
|
-
return
|
|
19025
|
+
return refused2(`the merge base declares no version in ${settings.releaseVersionFile} to restore`, problems);
|
|
18400
19026
|
if (start2.changelog === null) {
|
|
18401
|
-
return
|
|
19027
|
+
return refused2(`the merge base holds no ${settings.releaseChangelog}, so ${input.branch} created it and there is no text to restore`, problems);
|
|
18402
19028
|
}
|
|
18403
19029
|
let branchVersionText;
|
|
18404
19030
|
try {
|
|
18405
|
-
branchVersionText = readFileSync19(
|
|
19031
|
+
branchVersionText = readFileSync19(join34(input.worktree, settings.releaseVersionFile), "utf8");
|
|
18406
19032
|
} catch (error) {
|
|
18407
|
-
return
|
|
19033
|
+
return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
18408
19034
|
}
|
|
18409
19035
|
const rewritten = replaceManifestVersion(branchVersionText, start2.version);
|
|
18410
19036
|
if (rewritten === null) {
|
|
18411
|
-
return
|
|
19037
|
+
return refused2(`${input.branch}'s ${settings.releaseVersionFile} holds no version pair ${start2.version} could be written over`, problems);
|
|
18412
19038
|
}
|
|
18413
19039
|
return {
|
|
18414
19040
|
stamp,
|
|
@@ -18428,10 +19054,10 @@ function writeConversion(input, plan2) {
|
|
|
18428
19054
|
[input.settings.releaseChangelog, plan2.changelogText]
|
|
18429
19055
|
];
|
|
18430
19056
|
for (const [path, text] of writes) {
|
|
18431
|
-
const target =
|
|
19057
|
+
const target = join34(input.worktree, path);
|
|
18432
19058
|
try {
|
|
18433
|
-
|
|
18434
|
-
|
|
19059
|
+
mkdirSync8(dirname8(target), { recursive: true });
|
|
19060
|
+
writeFileSync8(target, text);
|
|
18435
19061
|
} catch (error) {
|
|
18436
19062
|
return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
|
|
18437
19063
|
}
|
|
@@ -18450,18 +19076,18 @@ function convertStampedVersion(input) {
|
|
|
18450
19076
|
return plan2;
|
|
18451
19077
|
const written = writeConversion(input, plan2);
|
|
18452
19078
|
if (written !== null)
|
|
18453
|
-
return
|
|
19079
|
+
return refused2(written, problems);
|
|
18454
19080
|
const paths = [plan2.fragmentPath, settings.releaseVersionFile, settings.releaseChangelog].map((path) => gitPathOf(path));
|
|
18455
19081
|
const added = git(["add", "--", ...paths]);
|
|
18456
19082
|
if (!added.ok)
|
|
18457
|
-
return
|
|
19083
|
+
return refused2(`the conversion's files could not be staged, and are left in the worktree: ${gitSaid(added)}`, problems);
|
|
18458
19084
|
const subject = conversionCommitSubject(plan2.plan, plan2.stamp.version);
|
|
18459
19085
|
const made = git(["commit", "--cleanup=whitespace", "-m", subject, "--", ...paths]);
|
|
18460
19086
|
if (!made.ok)
|
|
18461
|
-
return
|
|
19087
|
+
return refused2(`the conversion could not be committed, and is left staged in the worktree: ${gitSaid(made)}`, problems);
|
|
18462
19088
|
const commit = commitOf(git, "HEAD");
|
|
18463
19089
|
if (commit === null)
|
|
18464
|
-
return
|
|
19090
|
+
return refused2("the conversion was committed, but HEAD names no commit to report", problems);
|
|
18465
19091
|
return {
|
|
18466
19092
|
outcome: "converted",
|
|
18467
19093
|
commit,
|
|
@@ -18898,16 +19524,16 @@ async function redPullRequests(pr) {
|
|
|
18898
19524
|
}
|
|
18899
19525
|
return red;
|
|
18900
19526
|
}
|
|
18901
|
-
async function selectTargets(pr, warn3, now,
|
|
19527
|
+
async function selectTargets(pr, warn3, now, resolve13) {
|
|
18902
19528
|
const branch = readBranchOrNone(pr, warn3);
|
|
18903
19529
|
const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
18904
19530
|
if (current !== null)
|
|
18905
|
-
return selectTriagePullRequests({ current, candidates: [], now, resolve:
|
|
19531
|
+
return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve13 });
|
|
18906
19532
|
return selectTriagePullRequests({
|
|
18907
19533
|
current: null,
|
|
18908
19534
|
candidates: await redPullRequests(pr),
|
|
18909
19535
|
now,
|
|
18910
|
-
resolve:
|
|
19536
|
+
resolve: resolve13
|
|
18911
19537
|
});
|
|
18912
19538
|
}
|
|
18913
19539
|
async function detailOf2(pr, number) {
|
|
@@ -19044,7 +19670,7 @@ async function runTriage(context, seams) {
|
|
|
19044
19670
|
}
|
|
19045
19671
|
const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
|
|
19046
19672
|
const readings = [];
|
|
19047
|
-
let
|
|
19673
|
+
let resolve13 = null;
|
|
19048
19674
|
for (const number of numbers) {
|
|
19049
19675
|
const assess = () => assessOne({
|
|
19050
19676
|
pr,
|
|
@@ -19064,7 +19690,7 @@ async function runTriage(context, seams) {
|
|
|
19064
19690
|
readings.push(reading);
|
|
19065
19691
|
continue;
|
|
19066
19692
|
}
|
|
19067
|
-
|
|
19693
|
+
resolve13 = await resolvePullRequest({
|
|
19068
19694
|
trust: {
|
|
19069
19695
|
permissions,
|
|
19070
19696
|
trustedAuthors: pr.trustedAuthors,
|
|
@@ -19086,16 +19712,16 @@ async function runTriage(context, seams) {
|
|
|
19086
19712
|
...seams.clock === undefined ? {} : { clock: seams.clock },
|
|
19087
19713
|
...seams.sleep === undefined ? {} : { sleep: seams.sleep }
|
|
19088
19714
|
});
|
|
19089
|
-
readings.push(
|
|
19715
|
+
readings.push(resolve13.reading);
|
|
19090
19716
|
}
|
|
19091
19717
|
const text = [
|
|
19092
19718
|
...selection === null ? [] : [selection.message],
|
|
19093
19719
|
...readings.length === 0 ? [] : [renderTriages(readings)],
|
|
19094
|
-
...
|
|
19720
|
+
...resolve13 === null ? [] : resolve13.lines
|
|
19095
19721
|
].join(`
|
|
19096
19722
|
|
|
19097
19723
|
`);
|
|
19098
|
-
return { selection, readings, resolve:
|
|
19724
|
+
return { selection, readings, resolve: resolve13, text, exitCode: resolve13?.exitCode ?? 0 };
|
|
19099
19725
|
}
|
|
19100
19726
|
function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
19101
19727
|
const command = {
|
|
@@ -19495,9 +20121,9 @@ async function settleByPr(worktree, settings, pulls) {
|
|
|
19495
20121
|
if ("problem" in branch)
|
|
19496
20122
|
return failed2("unpushed", build, false, branch.problem);
|
|
19497
20123
|
if (!branch.same) {
|
|
19498
|
-
const
|
|
19499
|
-
if (
|
|
19500
|
-
return
|
|
20124
|
+
const refused3 = pushReleaseBranch(worktree, build, branch.current);
|
|
20125
|
+
if (refused3 !== null)
|
|
20126
|
+
return refused3;
|
|
19501
20127
|
}
|
|
19502
20128
|
const head = branch.same && branch.current !== null ? branch.current : build.release;
|
|
19503
20129
|
const draft = {
|
|
@@ -19660,9 +20286,9 @@ function tagSettle(worktree, mode, delivered) {
|
|
|
19660
20286
|
}
|
|
19661
20287
|
|
|
19662
20288
|
// src/release/settle-worktree.ts
|
|
19663
|
-
import { existsSync as
|
|
20289
|
+
import { existsSync as existsSync11, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync2 } from "fs";
|
|
19664
20290
|
import { tmpdir as tmpdir2 } from "os";
|
|
19665
|
-
import { join as
|
|
20291
|
+
import { join as join35 } from "path";
|
|
19666
20292
|
var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
|
|
19667
20293
|
var WORKTREE_DIRECTORY = "base";
|
|
19668
20294
|
function removeScratch(git, scratch, path) {
|
|
@@ -19679,14 +20305,14 @@ function removeScratch(git, scratch, path) {
|
|
|
19679
20305
|
problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
|
|
19680
20306
|
}
|
|
19681
20307
|
}
|
|
19682
|
-
if (problems.length === 0 &&
|
|
20308
|
+
if (problems.length === 0 && existsSync11(scratch)) {
|
|
19683
20309
|
problems.push(`the scratch directory ${scratch} is still there`);
|
|
19684
20310
|
}
|
|
19685
20311
|
return problems.length === 0 ? null : problems.join("; ");
|
|
19686
20312
|
}
|
|
19687
20313
|
function makeScratch(root) {
|
|
19688
20314
|
try {
|
|
19689
|
-
return realpathSync4(mkdtempSync2(
|
|
20315
|
+
return realpathSync4(mkdtempSync2(join35(root, SETTLE_SCRATCH_PREFIX)));
|
|
19690
20316
|
} catch (error) {
|
|
19691
20317
|
return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
|
|
19692
20318
|
}
|
|
@@ -19708,7 +20334,7 @@ function addWorktree(options) {
|
|
|
19708
20334
|
const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
|
|
19709
20335
|
if (typeof scratch !== "string")
|
|
19710
20336
|
return scratch;
|
|
19711
|
-
const path =
|
|
20337
|
+
const path = join35(scratch, WORKTREE_DIRECTORY);
|
|
19712
20338
|
const added = git(["worktree", "add", "--detach", path, commit]);
|
|
19713
20339
|
if (!added.ok) {
|
|
19714
20340
|
return {
|
|
@@ -19931,7 +20557,7 @@ var settle_default = createReleaseSettleCommand();
|
|
|
19931
20557
|
|
|
19932
20558
|
// src/commands/release/tag.ts
|
|
19933
20559
|
import { readFileSync as readFileSync20 } from "fs";
|
|
19934
|
-
import { join as
|
|
20560
|
+
import { join as join36 } from "path";
|
|
19935
20561
|
|
|
19936
20562
|
// src/commands/release/release-commit.ts
|
|
19937
20563
|
function unread(reason, message) {
|
|
@@ -19995,7 +20621,7 @@ function textOf(path) {
|
|
|
19995
20621
|
}
|
|
19996
20622
|
}
|
|
19997
20623
|
function readVersion(root, configured) {
|
|
19998
|
-
const resolved =
|
|
20624
|
+
const resolved = join36(root, configured);
|
|
19999
20625
|
const text = textOf(resolved);
|
|
20000
20626
|
if (text === null) {
|
|
20001
20627
|
return {
|
|
@@ -20009,7 +20635,7 @@ function readVersion(root, configured) {
|
|
|
20009
20635
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
20010
20636
|
}
|
|
20011
20637
|
function readNewestRelease(root, configured) {
|
|
20012
|
-
const resolved =
|
|
20638
|
+
const resolved = join36(root, configured);
|
|
20013
20639
|
const text = textOf(resolved);
|
|
20014
20640
|
if (text === null) {
|
|
20015
20641
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -20279,17 +20905,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
20279
20905
|
var roadmap_default = createRoadmapCommand();
|
|
20280
20906
|
|
|
20281
20907
|
// src/commands/skill/backfill.ts
|
|
20282
|
-
import { statSync as
|
|
20283
|
-
import { join as
|
|
20908
|
+
import { statSync as statSync11 } from "fs";
|
|
20909
|
+
import { join as join41, resolve as resolve15 } from "path";
|
|
20284
20910
|
|
|
20285
20911
|
// src/backfill/backup.ts
|
|
20286
|
-
import { copyFileSync as copyFileSync4, existsSync as
|
|
20287
|
-
import { dirname as dirname9, isAbsolute as isAbsolute7, join as
|
|
20912
|
+
import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
|
|
20913
|
+
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join38, relative as relative9, resolve as resolve13, sep as sep5 } from "path";
|
|
20288
20914
|
|
|
20289
20915
|
// src/backfill/proposal-file.ts
|
|
20290
|
-
import { readdirSync as
|
|
20291
|
-
import { join as
|
|
20292
|
-
var BACKFILL_PATH =
|
|
20916
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync21 } from "fs";
|
|
20917
|
+
import { join as join37 } from "path";
|
|
20918
|
+
var BACKFILL_PATH = join37(".rafa", "backfill");
|
|
20293
20919
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
20294
20920
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
20295
20921
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -20315,7 +20941,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
20315
20941
|
].join(`
|
|
20316
20942
|
`);
|
|
20317
20943
|
function proposalPath(dir, batch) {
|
|
20318
|
-
return
|
|
20944
|
+
return join37(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
20319
20945
|
}
|
|
20320
20946
|
function isProposalFileName(name) {
|
|
20321
20947
|
return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -20446,12 +21072,12 @@ function parseProposalFile(text) {
|
|
|
20446
21072
|
function readProposalFiles(dir) {
|
|
20447
21073
|
let names;
|
|
20448
21074
|
try {
|
|
20449
|
-
names =
|
|
21075
|
+
names = readdirSync4(dir);
|
|
20450
21076
|
} catch {
|
|
20451
21077
|
return [];
|
|
20452
21078
|
}
|
|
20453
21079
|
return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
20454
|
-
const path =
|
|
21080
|
+
const path = join37(dir, name);
|
|
20455
21081
|
try {
|
|
20456
21082
|
return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
|
|
20457
21083
|
} catch (error) {
|
|
@@ -20461,19 +21087,19 @@ function readProposalFiles(dir) {
|
|
|
20461
21087
|
}
|
|
20462
21088
|
|
|
20463
21089
|
// src/backfill/backup.ts
|
|
20464
|
-
var BACKUP_PATH =
|
|
21090
|
+
var BACKUP_PATH = join38(BACKFILL_PATH, "backup");
|
|
20465
21091
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
20466
21092
|
function backupDirectory(base) {
|
|
20467
|
-
return
|
|
21093
|
+
return join38(base, BACKUP_PATH);
|
|
20468
21094
|
}
|
|
20469
21095
|
function isUnder(root, path) {
|
|
20470
|
-
const step = relative9(
|
|
21096
|
+
const step = relative9(resolve13(root), resolve13(path));
|
|
20471
21097
|
return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute7(step);
|
|
20472
21098
|
}
|
|
20473
21099
|
function backupPathFor(path, root, backupDir) {
|
|
20474
21100
|
if (!isUnder(root, path))
|
|
20475
21101
|
return null;
|
|
20476
|
-
return
|
|
21102
|
+
return join38(backupDir, relative9(resolve13(root), resolve13(path)));
|
|
20477
21103
|
}
|
|
20478
21104
|
function backupFile(path, options) {
|
|
20479
21105
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -20482,11 +21108,11 @@ function backupFile(path, options) {
|
|
|
20482
21108
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
20483
21109
|
if (backup === null)
|
|
20484
21110
|
return { path, kind: "outside", backup: null };
|
|
20485
|
-
if (!
|
|
21111
|
+
if (!existsSync12(path))
|
|
20486
21112
|
return { path, kind: "missing", backup: null };
|
|
20487
|
-
if (
|
|
21113
|
+
if (existsSync12(backup))
|
|
20488
21114
|
return { path, kind: "kept", backup };
|
|
20489
|
-
|
|
21115
|
+
mkdirSync9(dirname9(backup), { recursive: true });
|
|
20490
21116
|
copyFileSync4(path, backup);
|
|
20491
21117
|
return { path, kind: "copied", backup };
|
|
20492
21118
|
}
|
|
@@ -20509,7 +21135,7 @@ function countBackups(results) {
|
|
|
20509
21135
|
}
|
|
20510
21136
|
|
|
20511
21137
|
// src/backfill/derive.ts
|
|
20512
|
-
import { readFileSync as readFileSync22, writeFileSync as
|
|
21138
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
20513
21139
|
import { basename as basename6 } from "path";
|
|
20514
21140
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
20515
21141
|
function renderWhenToUse(trigger, prevents) {
|
|
@@ -20688,7 +21314,7 @@ function applyFile(action, options) {
|
|
|
20688
21314
|
text: null
|
|
20689
21315
|
};
|
|
20690
21316
|
}
|
|
20691
|
-
|
|
21317
|
+
writeFileSync9(action.path, action.text, "utf8");
|
|
20692
21318
|
const after = failureLines(checkFile(action.path, "skill", {
|
|
20693
21319
|
projectRoot: options.projectRoot,
|
|
20694
21320
|
pathDirs: options.pathDirs
|
|
@@ -20696,7 +21322,7 @@ function applyFile(action, options) {
|
|
|
20696
21322
|
const added = addedFailures(action.before, after);
|
|
20697
21323
|
if (added.length === 0)
|
|
20698
21324
|
return action;
|
|
20699
|
-
|
|
21325
|
+
writeFileSync9(action.path, action.original, "utf8");
|
|
20700
21326
|
return {
|
|
20701
21327
|
...action,
|
|
20702
21328
|
kind: "refused",
|
|
@@ -20800,8 +21426,8 @@ function summaryLine3(rows) {
|
|
|
20800
21426
|
const parts = DEMOTION_VERDICTS.map((verdict) => `${counts[verdict]} ${verdict}`);
|
|
20801
21427
|
return `${files}: ${parts.join(", ")}.`;
|
|
20802
21428
|
}
|
|
20803
|
-
function writeDemotionReport(
|
|
20804
|
-
const rows =
|
|
21429
|
+
function writeDemotionReport(report4) {
|
|
21430
|
+
const rows = report4.rows.map((row2) => tableLine2([
|
|
20805
21431
|
escapeCell(row2.path),
|
|
20806
21432
|
escapeCell(row2.hash),
|
|
20807
21433
|
escapeCell(row2.verdict),
|
|
@@ -20810,11 +21436,11 @@ function writeDemotionReport(report3) {
|
|
|
20810
21436
|
escapeCell(row2.overrideReason ?? EMPTY_CELL)
|
|
20811
21437
|
]));
|
|
20812
21438
|
return [
|
|
20813
|
-
renderHeader(
|
|
21439
|
+
renderHeader(report4.status),
|
|
20814
21440
|
"",
|
|
20815
21441
|
REPORT_TITLE,
|
|
20816
21442
|
"",
|
|
20817
|
-
summaryLine3(
|
|
21443
|
+
summaryLine3(report4.rows),
|
|
20818
21444
|
"",
|
|
20819
21445
|
tableLine2(DEMOTION_REPORT_COLUMNS),
|
|
20820
21446
|
tableLine2(DEMOTION_REPORT_COLUMNS.map(() => "---")),
|
|
@@ -21115,8 +21741,8 @@ function parseSessionAnswer(stdout) {
|
|
|
21115
21741
|
}
|
|
21116
21742
|
|
|
21117
21743
|
// src/backfill/propose.ts
|
|
21118
|
-
import { mkdirSync as
|
|
21119
|
-
import { join as
|
|
21744
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "fs";
|
|
21745
|
+
import { join as join39, resolve as resolve14 } from "path";
|
|
21120
21746
|
var PROPOSAL_ACTION_KINDS = [
|
|
21121
21747
|
"applied",
|
|
21122
21748
|
"unchanged",
|
|
@@ -21195,12 +21821,12 @@ async function runProposalPass(options) {
|
|
|
21195
21821
|
const batches = batchCandidates(candidates);
|
|
21196
21822
|
const written = [];
|
|
21197
21823
|
if (batches.length > 0)
|
|
21198
|
-
|
|
21824
|
+
mkdirSync10(options.backfillDir, { recursive: true });
|
|
21199
21825
|
for (const [index, batch] of batches.entries()) {
|
|
21200
21826
|
const number = index + 1;
|
|
21201
21827
|
const file = await runProposalBatch(number, batch, options);
|
|
21202
21828
|
const path = proposalPath(options.backfillDir, number);
|
|
21203
|
-
|
|
21829
|
+
writeFileSync10(path, renderProposalFile(file), "utf8");
|
|
21204
21830
|
written.push({ path, file });
|
|
21205
21831
|
}
|
|
21206
21832
|
return { written, candidates: candidates.length };
|
|
@@ -21242,7 +21868,7 @@ function writeRow(path, row2, original, options) {
|
|
|
21242
21868
|
return inert2(path, row2, "unchanged", "every field the row proposes is already what the file carries");
|
|
21243
21869
|
}
|
|
21244
21870
|
const before = checkLines(path, options);
|
|
21245
|
-
|
|
21871
|
+
writeFileSync10(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
|
|
21246
21872
|
const added = addedFailures(before, checkLines(path, options));
|
|
21247
21873
|
if (added.length === 0) {
|
|
21248
21874
|
return {
|
|
@@ -21254,7 +21880,7 @@ function writeRow(path, row2, original, options) {
|
|
|
21254
21880
|
added: []
|
|
21255
21881
|
};
|
|
21256
21882
|
}
|
|
21257
|
-
|
|
21883
|
+
writeFileSync10(path, original, "utf8");
|
|
21258
21884
|
return {
|
|
21259
21885
|
path,
|
|
21260
21886
|
relative: row2.path,
|
|
@@ -21265,7 +21891,7 @@ function writeRow(path, row2, original, options) {
|
|
|
21265
21891
|
};
|
|
21266
21892
|
}
|
|
21267
21893
|
function applyRow(root, row2, options) {
|
|
21268
|
-
const path =
|
|
21894
|
+
const path = join39(root, ...row2.path.split("/"));
|
|
21269
21895
|
if (row2.status === UNANSWERED_ROW) {
|
|
21270
21896
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
21271
21897
|
}
|
|
@@ -21288,7 +21914,7 @@ function fileRefusal(file, root) {
|
|
|
21288
21914
|
if (file.status !== REVIEWED_FILE) {
|
|
21289
21915
|
return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
|
|
21290
21916
|
}
|
|
21291
|
-
return
|
|
21917
|
+
return resolve14(file.skills) === resolve14(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
|
|
21292
21918
|
}
|
|
21293
21919
|
function countProposalActions(actions) {
|
|
21294
21920
|
const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
|
|
@@ -21302,7 +21928,7 @@ function applyProposals(files, root, options) {
|
|
|
21302
21928
|
for (const file of files) {
|
|
21303
21929
|
const refusal13 = fileRefusal(file, root);
|
|
21304
21930
|
for (const row2 of file.rows) {
|
|
21305
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
21931
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join39(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
21306
21932
|
actions.push(action);
|
|
21307
21933
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
21308
21934
|
triggers[row2.name] = row2.trigger;
|
|
@@ -21313,13 +21939,13 @@ function applyProposals(files, root, options) {
|
|
|
21313
21939
|
}
|
|
21314
21940
|
|
|
21315
21941
|
// src/demote/select.ts
|
|
21316
|
-
import { readdirSync as
|
|
21317
|
-
import { basename as basename7, dirname as dirname10, join as
|
|
21942
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
|
|
21943
|
+
import { basename as basename7, dirname as dirname10, join as join40 } from "path";
|
|
21318
21944
|
var SKILLS_DIRECTORY = "skills";
|
|
21319
21945
|
var CLAUDE_DIRECTORY = ".claude";
|
|
21320
21946
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
21321
|
-
var DEMOTED_PATH =
|
|
21322
|
-
var INSTINCTS_PATH =
|
|
21947
|
+
var DEMOTED_PATH = join40(".rafa", "demoted");
|
|
21948
|
+
var INSTINCTS_PATH = join40(".rafa", "instincts");
|
|
21323
21949
|
var REPORT_FILE = "report.md";
|
|
21324
21950
|
function realOrSelf(path) {
|
|
21325
21951
|
try {
|
|
@@ -21342,19 +21968,19 @@ function resolveDemotionScope(dir, home) {
|
|
|
21342
21968
|
if (basename7(claude) !== CLAUDE_DIRECTORY)
|
|
21343
21969
|
return null;
|
|
21344
21970
|
const base = dirname10(claude);
|
|
21345
|
-
const demotedDir =
|
|
21971
|
+
const demotedDir = join40(base, DEMOTED_PATH);
|
|
21346
21972
|
return {
|
|
21347
21973
|
scope: samePath(base, home) ? "user" : "project",
|
|
21348
21974
|
dir,
|
|
21349
21975
|
base,
|
|
21350
21976
|
demotedDir,
|
|
21351
|
-
instinctsDir:
|
|
21352
|
-
reportPath:
|
|
21977
|
+
instinctsDir: join40(base, INSTINCTS_PATH),
|
|
21978
|
+
reportPath: join40(demotedDir, REPORT_FILE)
|
|
21353
21979
|
};
|
|
21354
21980
|
}
|
|
21355
21981
|
function entryKind(path) {
|
|
21356
21982
|
try {
|
|
21357
|
-
const stats =
|
|
21983
|
+
const stats = statSync10(path);
|
|
21358
21984
|
if (stats.isFile())
|
|
21359
21985
|
return "file";
|
|
21360
21986
|
if (stats.isDirectory())
|
|
@@ -21366,7 +21992,7 @@ function entryKind(path) {
|
|
|
21366
21992
|
}
|
|
21367
21993
|
function visibleNames(dir) {
|
|
21368
21994
|
try {
|
|
21369
|
-
return
|
|
21995
|
+
return readdirSync5(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
21370
21996
|
} catch {
|
|
21371
21997
|
return [];
|
|
21372
21998
|
}
|
|
@@ -21376,7 +22002,7 @@ function readSelected(absolute, path, entries) {
|
|
|
21376
22002
|
let mtime;
|
|
21377
22003
|
try {
|
|
21378
22004
|
text = readFileSync25(absolute, "utf8");
|
|
21379
|
-
mtime =
|
|
22005
|
+
mtime = statSync10(absolute).mtime.toISOString();
|
|
21380
22006
|
} catch {
|
|
21381
22007
|
return null;
|
|
21382
22008
|
}
|
|
@@ -21391,14 +22017,14 @@ function selectsLearned(text, scope) {
|
|
|
21391
22017
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
21392
22018
|
}
|
|
21393
22019
|
function selectLearned(scope) {
|
|
21394
|
-
const dir =
|
|
22020
|
+
const dir = join40(scope.dir, LEARNED_DIRECTORY);
|
|
21395
22021
|
if (entryKind(dir) !== "directory")
|
|
21396
22022
|
return [];
|
|
21397
22023
|
const selected = [];
|
|
21398
22024
|
for (const name of visibleNames(dir)) {
|
|
21399
22025
|
if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
|
|
21400
22026
|
continue;
|
|
21401
|
-
const absolute =
|
|
22027
|
+
const absolute = join40(dir, name);
|
|
21402
22028
|
if (entryKind(absolute) !== "file")
|
|
21403
22029
|
continue;
|
|
21404
22030
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
|
|
@@ -21412,10 +22038,10 @@ function selectSkillFiles(scope) {
|
|
|
21412
22038
|
for (const name of visibleNames(scope.dir)) {
|
|
21413
22039
|
if (name === LEARNED_DIRECTORY)
|
|
21414
22040
|
continue;
|
|
21415
|
-
const skillDir =
|
|
22041
|
+
const skillDir = join40(scope.dir, name);
|
|
21416
22042
|
if (entryKind(skillDir) !== "directory")
|
|
21417
22043
|
continue;
|
|
21418
|
-
const absolute =
|
|
22044
|
+
const absolute = join40(skillDir, SKILL_FILE);
|
|
21419
22045
|
if (entryKind(absolute) !== "file")
|
|
21420
22046
|
continue;
|
|
21421
22047
|
const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -21434,7 +22060,7 @@ var DEFAULT_BACKFILL_SEAMS = Object.freeze({
|
|
|
21434
22060
|
});
|
|
21435
22061
|
var BACKFILL_USAGE = "rafa skill backfill <dir> [--propose|--apply] [--project=<root>]";
|
|
21436
22062
|
var REFUSED_CAP = 255;
|
|
21437
|
-
function
|
|
22063
|
+
function readSwitch4(name, value) {
|
|
21438
22064
|
if (value === undefined || value === false || value === "false")
|
|
21439
22065
|
return false;
|
|
21440
22066
|
if (value === true || value === "true")
|
|
@@ -21443,8 +22069,8 @@ function readSwitch3(name, value) {
|
|
|
21443
22069
|
Usage: ${BACKFILL_USAGE}`);
|
|
21444
22070
|
}
|
|
21445
22071
|
function readMode(flags) {
|
|
21446
|
-
const propose =
|
|
21447
|
-
const apply =
|
|
22072
|
+
const propose = readSwitch4("propose", flags["propose"]);
|
|
22073
|
+
const apply = readSwitch4("apply", flags["apply"]);
|
|
21448
22074
|
if (propose && apply) {
|
|
21449
22075
|
throw new CommandExit(1, "\u274C --propose asks the sessions and --apply writes their reviewed answers, so a run is one or the" + ` other
|
|
21450
22076
|
Usage: ${BACKFILL_USAGE}`);
|
|
@@ -21463,7 +22089,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
21463
22089
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
21464
22090
|
Usage: ${BACKFILL_USAGE}`);
|
|
21465
22091
|
}
|
|
21466
|
-
return
|
|
22092
|
+
return resolve15(cwd(), dir);
|
|
21467
22093
|
}
|
|
21468
22094
|
function readConsumerRoot(value, cwd) {
|
|
21469
22095
|
if (value === undefined || value === false)
|
|
@@ -21472,11 +22098,11 @@ function readConsumerRoot(value, cwd) {
|
|
|
21472
22098
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
21473
22099
|
Usage: ${BACKFILL_USAGE}`);
|
|
21474
22100
|
}
|
|
21475
|
-
return
|
|
22101
|
+
return resolve15(cwd(), value);
|
|
21476
22102
|
}
|
|
21477
22103
|
function isDirectory3(path) {
|
|
21478
22104
|
try {
|
|
21479
|
-
return
|
|
22105
|
+
return statSync11(path).isDirectory();
|
|
21480
22106
|
} catch {
|
|
21481
22107
|
return false;
|
|
21482
22108
|
}
|
|
@@ -21525,7 +22151,7 @@ function actionLines(actions) {
|
|
|
21525
22151
|
function checkOptions(context, projectRoot2) {
|
|
21526
22152
|
return { projectRoot: projectRoot2, pathDirs: pathDirectories(context.env["PATH"]) };
|
|
21527
22153
|
}
|
|
21528
|
-
function
|
|
22154
|
+
function report4(context, result2, lines) {
|
|
21529
22155
|
if (context.outputMode === "json") {
|
|
21530
22156
|
context.output.result(result2);
|
|
21531
22157
|
return;
|
|
@@ -21546,7 +22172,7 @@ function runPlan(context, scope, backfillDir, projectRoot2) {
|
|
|
21546
22172
|
counts: plan2.counts,
|
|
21547
22173
|
candidates
|
|
21548
22174
|
};
|
|
21549
|
-
|
|
22175
|
+
report4(context, result2, [
|
|
21550
22176
|
`${scope.scope} scope: ${scope.dir}`,
|
|
21551
22177
|
...actionLines(actions),
|
|
21552
22178
|
derivationLine(plan2),
|
|
@@ -21580,7 +22206,7 @@ async function runPropose(context, scope, backfillDir, seams) {
|
|
|
21580
22206
|
candidates: pass.candidates,
|
|
21581
22207
|
files
|
|
21582
22208
|
};
|
|
21583
|
-
|
|
22209
|
+
report4(context, result2, [
|
|
21584
22210
|
`${scope.scope} scope: ${scope.dir}`,
|
|
21585
22211
|
`${String(pass.candidates)} file(s) to ask about, in ${String(files.length)} batch(es)`,
|
|
21586
22212
|
...files.map((file) => `\u2705 ${file.path}: batch ${String(file.batch)}, session exit ${String(file.exitCode)},` + ` ${String(file.rows)} row(s), ${String(file.unanswered)} unanswered`),
|
|
@@ -21600,7 +22226,7 @@ function reviewedProposals(dir) {
|
|
|
21600
22226
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
21601
22227
|
}
|
|
21602
22228
|
function rowPaths(files, root) {
|
|
21603
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
22229
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join41(root, ...row2.path.split("/"))));
|
|
21604
22230
|
}
|
|
21605
22231
|
function runApply(context, scope, backfillDir, projectRoot2) {
|
|
21606
22232
|
const options = checkOptions(context, projectRoot2);
|
|
@@ -21635,18 +22261,18 @@ function runApply(context, scope, backfillDir, projectRoot2) {
|
|
|
21635
22261
|
derivationLine(derived),
|
|
21636
22262
|
`${String(copied)} file(s) copied under ${backupDir} before they were rewritten`
|
|
21637
22263
|
];
|
|
21638
|
-
const
|
|
21639
|
-
if (
|
|
21640
|
-
throw new CommandExit(Math.min(
|
|
22264
|
+
const refused3 = proposals.counts.refused + derived.counts.refused;
|
|
22265
|
+
if (refused3 > 0)
|
|
22266
|
+
throw new CommandExit(Math.min(refused3, REFUSED_CAP), lines.join(`
|
|
21641
22267
|
`));
|
|
21642
|
-
|
|
22268
|
+
report4(context, result2, lines);
|
|
21643
22269
|
}
|
|
21644
22270
|
async function runBackfill(context, seams) {
|
|
21645
22271
|
const mode = readMode(context.flags);
|
|
21646
22272
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
21647
22273
|
const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
21648
22274
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
21649
|
-
const backfillDir =
|
|
22275
|
+
const backfillDir = join41(scope.base, BACKFILL_PATH);
|
|
21650
22276
|
if (mode === "propose") {
|
|
21651
22277
|
await runPropose(context, scope, backfillDir, seams);
|
|
21652
22278
|
return;
|
|
@@ -21760,13 +22386,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
21760
22386
|
var check_default3 = createSkillCheckCommand();
|
|
21761
22387
|
|
|
21762
22388
|
// src/commands/skill/demote.ts
|
|
21763
|
-
import { existsSync as
|
|
21764
|
-
import { dirname as dirname12, resolve as
|
|
22389
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync27, statSync as statSync13, writeFileSync as writeFileSync12 } from "fs";
|
|
22390
|
+
import { dirname as dirname12, resolve as resolve16 } from "path";
|
|
21765
22391
|
|
|
21766
22392
|
// src/demote/apply.ts
|
|
21767
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
22393
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync12, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
|
|
21768
22394
|
import { tmpdir as tmpdir3 } from "os";
|
|
21769
|
-
import { dirname as dirname11, join as
|
|
22395
|
+
import { dirname as dirname11, join as join42 } from "path";
|
|
21770
22396
|
var DEMOTION_ACTION_KINDS = [
|
|
21771
22397
|
"demoted",
|
|
21772
22398
|
"kept",
|
|
@@ -21787,18 +22413,18 @@ function learnedSkillName(path) {
|
|
|
21787
22413
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
21788
22414
|
}
|
|
21789
22415
|
function sourcePath(scope, path) {
|
|
21790
|
-
return
|
|
22416
|
+
return join42(scope.dir, ...path.split("/"));
|
|
21791
22417
|
}
|
|
21792
22418
|
function demotedPath(scope, path) {
|
|
21793
|
-
return
|
|
22419
|
+
return join42(scope.demotedDir, ...path.split("/"));
|
|
21794
22420
|
}
|
|
21795
22421
|
function promotedPath(scope, path) {
|
|
21796
|
-
return
|
|
22422
|
+
return join42(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
21797
22423
|
}
|
|
21798
22424
|
function takenInstinctIds(dir) {
|
|
21799
22425
|
let names;
|
|
21800
22426
|
try {
|
|
21801
|
-
names =
|
|
22427
|
+
names = readdirSync6(dir);
|
|
21802
22428
|
} catch {
|
|
21803
22429
|
return [];
|
|
21804
22430
|
}
|
|
@@ -21806,7 +22432,7 @@ function takenInstinctIds(dir) {
|
|
|
21806
22432
|
}
|
|
21807
22433
|
function readText4(path) {
|
|
21808
22434
|
try {
|
|
21809
|
-
return
|
|
22435
|
+
return statSync12(path).isFile() ? readFileSync26(path, "utf8") : null;
|
|
21810
22436
|
} catch {
|
|
21811
22437
|
return null;
|
|
21812
22438
|
}
|
|
@@ -21834,7 +22460,7 @@ function planKept(scope, row2, file) {
|
|
|
21834
22460
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
21835
22461
|
}
|
|
21836
22462
|
const to = promotedPath(scope, row2.path);
|
|
21837
|
-
if (
|
|
22463
|
+
if (existsSync13(to) || existsSync13(dirname11(to))) {
|
|
21838
22464
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
21839
22465
|
}
|
|
21840
22466
|
return {
|
|
@@ -21848,10 +22474,10 @@ function planKept(scope, row2, file) {
|
|
|
21848
22474
|
};
|
|
21849
22475
|
}
|
|
21850
22476
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
21851
|
-
const path =
|
|
21852
|
-
|
|
21853
|
-
const
|
|
21854
|
-
return failureLines2(
|
|
22477
|
+
const path = join42(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
22478
|
+
writeFileSync11(path, text, "utf8");
|
|
22479
|
+
const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
22480
|
+
return failureLines2(report5.issues);
|
|
21855
22481
|
}
|
|
21856
22482
|
function planDemoted(scope, row2, file, context) {
|
|
21857
22483
|
const conversion = convertToInstinct(file, {
|
|
@@ -21869,11 +22495,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
21869
22495
|
if (failures.length > 0) {
|
|
21870
22496
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
21871
22497
|
}
|
|
21872
|
-
const destination =
|
|
22498
|
+
const destination = join42(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21873
22499
|
const kept = demotedPath(scope, row2.path);
|
|
21874
|
-
if (
|
|
22500
|
+
if (existsSync13(destination))
|
|
21875
22501
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
21876
|
-
if (
|
|
22502
|
+
if (existsSync13(kept))
|
|
21877
22503
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
21878
22504
|
context.taken.push(id2);
|
|
21879
22505
|
return {
|
|
@@ -21905,7 +22531,7 @@ function planRow(scope, row2, context) {
|
|
|
21905
22531
|
entries: undefined,
|
|
21906
22532
|
absolute,
|
|
21907
22533
|
hash,
|
|
21908
|
-
mtime:
|
|
22534
|
+
mtime: statSync12(absolute).mtime.toISOString()
|
|
21909
22535
|
};
|
|
21910
22536
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
21911
22537
|
}
|
|
@@ -21915,18 +22541,18 @@ function countActions(actions) {
|
|
|
21915
22541
|
counts[action.kind] += 1;
|
|
21916
22542
|
return counts;
|
|
21917
22543
|
}
|
|
21918
|
-
function planDemotion(
|
|
21919
|
-
const temp = mkdtempSync3(
|
|
22544
|
+
function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
22545
|
+
const temp = mkdtempSync3(join42(tmpdir3(), "rafa-demote-"));
|
|
21920
22546
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
21921
22547
|
try {
|
|
21922
|
-
const actions =
|
|
22548
|
+
const actions = report5.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
21923
22549
|
return { scope, actions, counts: countActions(actions) };
|
|
21924
22550
|
} finally {
|
|
21925
22551
|
rmSync3(temp, { recursive: true, force: true });
|
|
21926
22552
|
}
|
|
21927
22553
|
}
|
|
21928
22554
|
function moveFile(move3) {
|
|
21929
|
-
|
|
22555
|
+
mkdirSync11(dirname11(move3.to), { recursive: true });
|
|
21930
22556
|
try {
|
|
21931
22557
|
renameSync(move3.from, move3.to);
|
|
21932
22558
|
} catch {
|
|
@@ -21937,8 +22563,8 @@ function moveFile(move3) {
|
|
|
21937
22563
|
function applyDemotion(plan2) {
|
|
21938
22564
|
for (const action of plan2.actions) {
|
|
21939
22565
|
if (action.write !== null) {
|
|
21940
|
-
|
|
21941
|
-
|
|
22566
|
+
mkdirSync11(dirname11(action.write.path), { recursive: true });
|
|
22567
|
+
writeFileSync11(action.write.path, action.write.text, "utf8");
|
|
21942
22568
|
}
|
|
21943
22569
|
if (action.move !== null)
|
|
21944
22570
|
moveFile(action.move);
|
|
@@ -22003,11 +22629,11 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
22003
22629
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
22004
22630
|
Usage: ${DEMOTE_USAGE}`);
|
|
22005
22631
|
}
|
|
22006
|
-
return
|
|
22632
|
+
return resolve16(cwd(), dir);
|
|
22007
22633
|
}
|
|
22008
22634
|
function isDirectory4(path) {
|
|
22009
22635
|
try {
|
|
22010
|
-
return
|
|
22636
|
+
return statSync13(path).isDirectory();
|
|
22011
22637
|
} catch {
|
|
22012
22638
|
return false;
|
|
22013
22639
|
}
|
|
@@ -22029,13 +22655,13 @@ function projectOf12(context) {
|
|
|
22029
22655
|
throw new Error("rafa skill demote runs inside a project, and was handed none");
|
|
22030
22656
|
return context.project;
|
|
22031
22657
|
}
|
|
22032
|
-
function verdictLine2(
|
|
22033
|
-
const counts = countVerdicts(
|
|
22658
|
+
function verdictLine2(report5) {
|
|
22659
|
+
const counts = countVerdicts(report5.rows);
|
|
22034
22660
|
const parts = DEMOTION_VERDICTS.map((verdict) => `${String(counts[verdict])} ${verdict}`);
|
|
22035
|
-
return `${String(
|
|
22661
|
+
return `${String(report5.rows.length)} file(s): ${parts.join(", ")}`;
|
|
22036
22662
|
}
|
|
22037
22663
|
function previousReport(path, context) {
|
|
22038
|
-
if (!
|
|
22664
|
+
if (!existsSync14(path))
|
|
22039
22665
|
return null;
|
|
22040
22666
|
const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
|
|
22041
22667
|
if (parsed.report !== null)
|
|
@@ -22045,17 +22671,17 @@ function previousReport(path, context) {
|
|
|
22045
22671
|
}
|
|
22046
22672
|
function runWrite(context, scope) {
|
|
22047
22673
|
const files = selectSources(scope);
|
|
22048
|
-
const
|
|
22049
|
-
|
|
22050
|
-
|
|
22674
|
+
const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
22675
|
+
mkdirSync12(dirname12(scope.reportPath), { recursive: true });
|
|
22676
|
+
writeFileSync12(scope.reportPath, writeDemotionReport(report5), "utf8");
|
|
22051
22677
|
const result2 = {
|
|
22052
22678
|
applied: false,
|
|
22053
22679
|
scope: scope.scope,
|
|
22054
22680
|
dir: scope.dir,
|
|
22055
22681
|
reportPath: scope.reportPath,
|
|
22056
|
-
status:
|
|
22057
|
-
files:
|
|
22058
|
-
counts: countVerdicts(
|
|
22682
|
+
status: report5.status,
|
|
22683
|
+
files: report5.rows.length,
|
|
22684
|
+
counts: countVerdicts(report5.rows)
|
|
22059
22685
|
};
|
|
22060
22686
|
if (context.outputMode === "json") {
|
|
22061
22687
|
context.output.result(result2);
|
|
@@ -22063,14 +22689,14 @@ function runWrite(context, scope) {
|
|
|
22063
22689
|
}
|
|
22064
22690
|
for (const line of [
|
|
22065
22691
|
`${scope.scope} scope: ${scope.dir}`,
|
|
22066
|
-
verdictLine2(
|
|
22067
|
-
`Report written to ${scope.reportPath} (status: ${
|
|
22692
|
+
verdictLine2(report5),
|
|
22693
|
+
`Report written to ${scope.reportPath} (status: ${report5.status}); nothing was moved`,
|
|
22068
22694
|
`Review each row, then mark the report ${REVIEWED_STATUS} and re-run with --apply`
|
|
22069
22695
|
])
|
|
22070
22696
|
context.output.info(line);
|
|
22071
22697
|
}
|
|
22072
22698
|
function reviewedReport(scope) {
|
|
22073
|
-
if (!
|
|
22699
|
+
if (!existsSync14(scope.reportPath)) {
|
|
22074
22700
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
22075
22701
|
}
|
|
22076
22702
|
const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
|
|
@@ -22100,8 +22726,8 @@ function renderApply(result2) {
|
|
|
22100
22726
|
return [...rows, `${String(result2.actions.length)} row(s) of ${result2.reportPath}: ${parts.join(", ")}`];
|
|
22101
22727
|
}
|
|
22102
22728
|
function runApply2(context, scope) {
|
|
22103
|
-
const
|
|
22104
|
-
const plan2 = planDemotion(
|
|
22729
|
+
const report5 = reviewedReport(scope);
|
|
22730
|
+
const plan2 = planDemotion(report5, scope, {
|
|
22105
22731
|
now: () => new Date().toISOString(),
|
|
22106
22732
|
pathDirs: pathDirectories(context.env["PATH"])
|
|
22107
22733
|
});
|
|
@@ -22446,12 +23072,12 @@ function statusData(sections) {
|
|
|
22446
23072
|
}
|
|
22447
23073
|
|
|
22448
23074
|
// src/status/sections.ts
|
|
22449
|
-
import { existsSync as
|
|
23075
|
+
import { existsSync as existsSync15 } from "fs";
|
|
22450
23076
|
|
|
22451
23077
|
// src/status/blocked-count.ts
|
|
22452
23078
|
async function labelsCount(options) {
|
|
22453
|
-
const
|
|
22454
|
-
return
|
|
23079
|
+
const report5 = await options.blockedIssues(options.gh);
|
|
23080
|
+
return report5.problem === null ? { count: report5.readings.length, notes: [] } : { count: null, notes: [`the issues labelled ${SPEC_BLOCKED_LABEL} could not be read: ${report5.problem}`] };
|
|
22455
23081
|
}
|
|
22456
23082
|
async function nativeCount(relations, listing2) {
|
|
22457
23083
|
try {
|
|
@@ -22614,9 +23240,9 @@ function boundedRunner(open, root, deadline) {
|
|
|
22614
23240
|
}
|
|
22615
23241
|
async function networkSection(what, deadline, work) {
|
|
22616
23242
|
let timer;
|
|
22617
|
-
const expired = new Promise((
|
|
23243
|
+
const expired = new Promise((resolve17) => {
|
|
22618
23244
|
timer = setTimeout(() => {
|
|
22619
|
-
|
|
23245
|
+
resolve17(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
22620
23246
|
}, Math.max(0, deadline.left()));
|
|
22621
23247
|
});
|
|
22622
23248
|
try {
|
|
@@ -22706,7 +23332,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
22706
23332
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
22707
23333
|
}
|
|
22708
23334
|
function hasBoards(root, listing2) {
|
|
22709
|
-
if (
|
|
23335
|
+
if (existsSync15(positionFilePath(root)))
|
|
22710
23336
|
return true;
|
|
22711
23337
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
22712
23338
|
}
|
|
@@ -22907,8 +23533,8 @@ function createUpdateStub(spec) {
|
|
|
22907
23533
|
var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
|
|
22908
23534
|
|
|
22909
23535
|
// src/project/lock.ts
|
|
22910
|
-
import { existsSync as
|
|
22911
|
-
import { join as
|
|
23536
|
+
import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "fs";
|
|
23537
|
+
import { join as join43 } from "path";
|
|
22912
23538
|
var LOCK_FILE = "rafa.lock";
|
|
22913
23539
|
var LOCKFILE_VERSION = 1;
|
|
22914
23540
|
|
|
@@ -22916,7 +23542,7 @@ class ProjectLockError extends Error {
|
|
|
22916
23542
|
name = "ProjectLockError";
|
|
22917
23543
|
}
|
|
22918
23544
|
function lockPath(root) {
|
|
22919
|
-
return
|
|
23545
|
+
return join43(root, LOCK_FILE);
|
|
22920
23546
|
}
|
|
22921
23547
|
function projectLockText(version2) {
|
|
22922
23548
|
const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
|
|
@@ -22949,7 +23575,7 @@ function lockOf(value) {
|
|
|
22949
23575
|
}
|
|
22950
23576
|
function readProjectLock(root) {
|
|
22951
23577
|
const path = lockPath(root);
|
|
22952
|
-
if (!
|
|
23578
|
+
if (!existsSync16(path)) {
|
|
22953
23579
|
if (isLink(path))
|
|
22954
23580
|
refuse8("is a link to nothing");
|
|
22955
23581
|
return null;
|
|
@@ -22969,15 +23595,15 @@ function readProjectLock(root) {
|
|
|
22969
23595
|
return lockOf(value);
|
|
22970
23596
|
}
|
|
22971
23597
|
function writeProjectLock(root, version2) {
|
|
22972
|
-
|
|
23598
|
+
writeFileSync13(lockPath(root), projectLockText(version2), "utf8");
|
|
22973
23599
|
}
|
|
22974
23600
|
|
|
22975
23601
|
// src/project/update-current.ts
|
|
22976
|
-
import { existsSync as
|
|
22977
|
-
import { join as
|
|
23602
|
+
import { existsSync as existsSync17 } from "fs";
|
|
23603
|
+
import { join as join44 } from "path";
|
|
22978
23604
|
var DEPRECATIONS = Object.freeze([]);
|
|
22979
23605
|
function missingFolders(root) {
|
|
22980
|
-
return PROJECT_TREE.map((name) =>
|
|
23606
|
+
return PROJECT_TREE.map((name) => join44(SCOPE_DIR, name)).filter((relative11) => !existsSync17(join44(root, relative11)));
|
|
22981
23607
|
}
|
|
22982
23608
|
async function readBoard3(access) {
|
|
22983
23609
|
if (access.gh === null)
|
|
@@ -22992,10 +23618,10 @@ async function readBoard3(access) {
|
|
|
22992
23618
|
function lockChangeOf(range) {
|
|
22993
23619
|
if (range.kind === "adopt")
|
|
22994
23620
|
return "created";
|
|
22995
|
-
return range.kind === "
|
|
23621
|
+
return range.kind === "same" ? "unchanged" : "updated";
|
|
22996
23622
|
}
|
|
22997
23623
|
function fromOf(range) {
|
|
22998
|
-
if (range.kind === "patch")
|
|
23624
|
+
if (range.kind === "patch" || range.kind === "minor")
|
|
22999
23625
|
return range.from;
|
|
23000
23626
|
if (range.kind === "same")
|
|
23001
23627
|
return range.to;
|
|
@@ -23030,7 +23656,7 @@ class UpdateStepError extends Error {
|
|
|
23030
23656
|
}
|
|
23031
23657
|
}
|
|
23032
23658
|
function foldersNowThere(plan2) {
|
|
23033
|
-
return plan2.folders.filter((relative11) =>
|
|
23659
|
+
return plan2.folders.filter((relative11) => existsSync17(join44(plan2.root, relative11)));
|
|
23034
23660
|
}
|
|
23035
23661
|
function writeFolders(plan2) {
|
|
23036
23662
|
if (plan2.folders.length === 0)
|
|
@@ -23058,27 +23684,29 @@ async function applyUpdatePlan(plan2, gh) {
|
|
|
23058
23684
|
}
|
|
23059
23685
|
|
|
23060
23686
|
// src/project/update-range.ts
|
|
23061
|
-
function
|
|
23687
|
+
function refused3(reason, message) {
|
|
23062
23688
|
return { kind: "refused", reason, message };
|
|
23063
23689
|
}
|
|
23064
23690
|
function readCurrentRange(recorded, installed) {
|
|
23065
23691
|
const to = parseSemanticVersion(installed);
|
|
23066
23692
|
if (to === null)
|
|
23067
|
-
return
|
|
23693
|
+
return refused3("unreadable", `the installed rafa answers "${installed}", which is no version`);
|
|
23068
23694
|
if (recorded === null)
|
|
23069
23695
|
return { kind: "adopt", to: installed };
|
|
23070
23696
|
const from = parseSemanticVersion(recorded);
|
|
23071
23697
|
if (from === null)
|
|
23072
|
-
return
|
|
23698
|
+
return refused3("unreadable", `the project records rafa "${recorded}", which is no version`);
|
|
23073
23699
|
const order = compareVersions(to, from);
|
|
23074
23700
|
if (order < 0) {
|
|
23075
|
-
return
|
|
23701
|
+
return refused3("older", `the installed rafa ${installed} is older than the project's ${recorded}, and no downgrade runs before 1.0.0`);
|
|
23076
23702
|
}
|
|
23077
23703
|
if (to.major !== from.major) {
|
|
23078
|
-
return
|
|
23704
|
+
return refused3("newer-major", `the installed rafa ${installed} is a newer major than the project's ${recorded}; that move is rafa update latest`);
|
|
23079
23705
|
}
|
|
23706
|
+
if (to.minor !== from.minor && to.major === 0)
|
|
23707
|
+
return { kind: "minor", from: recorded, to: installed };
|
|
23080
23708
|
if (to.minor !== from.minor) {
|
|
23081
|
-
return
|
|
23709
|
+
return refused3("newer-minor", `the installed rafa ${installed} is a newer minor than the project's ${recorded}; that move is rafa update next`);
|
|
23082
23710
|
}
|
|
23083
23711
|
return order === 0 ? { kind: "same", to: installed } : { kind: "patch", from: recorded, to: installed };
|
|
23084
23712
|
}
|
|
@@ -23102,7 +23730,7 @@ var DEFAULT_UPDATE_CURRENT_SEAMS = Object.freeze({
|
|
|
23102
23730
|
openPrompter: () => createLinePrompter(process.stdin, process.stderr),
|
|
23103
23731
|
lockRootOf: (root) => checkoutLockRoot(root, process.cwd())
|
|
23104
23732
|
});
|
|
23105
|
-
function
|
|
23733
|
+
function readSwitch5(context, name) {
|
|
23106
23734
|
const value = context.flags[name];
|
|
23107
23735
|
if (value === undefined || value === false || value === "false")
|
|
23108
23736
|
return false;
|
|
@@ -23232,10 +23860,10 @@ async function applyAndReport(plan2, gh, context) {
|
|
|
23232
23860
|
return applied;
|
|
23233
23861
|
}
|
|
23234
23862
|
function refusedLabels(applied) {
|
|
23235
|
-
const
|
|
23236
|
-
if (
|
|
23863
|
+
const refused4 = applied.labels.filter((part) => part.outcome === "refused");
|
|
23864
|
+
if (refused4.length === 0)
|
|
23237
23865
|
return null;
|
|
23238
|
-
return new CommandExit(1, `${String(
|
|
23866
|
+
return new CommandExit(1, `${String(refused4.length)} board label(s) were not created; everything else was applied.` + " Run rafa update current again once gh can create them.");
|
|
23239
23867
|
}
|
|
23240
23868
|
function stopLine2(plan2, dryRun) {
|
|
23241
23869
|
if (!planChanges(plan2))
|
|
@@ -23246,8 +23874,8 @@ function stopLine2(plan2, dryRun) {
|
|
|
23246
23874
|
}
|
|
23247
23875
|
async function runUpdateCurrent(context, seams) {
|
|
23248
23876
|
expectNoArgument(context.args, USAGE43);
|
|
23249
|
-
const dryRun =
|
|
23250
|
-
const yes =
|
|
23877
|
+
const dryRun = readSwitch5(context, "dry-run");
|
|
23878
|
+
const yes = readSwitch5(context, "yes");
|
|
23251
23879
|
const project = projectOf15(context);
|
|
23252
23880
|
const lockRoot = seams.lockRootOf(project.root);
|
|
23253
23881
|
const range = readRange(lockRoot, seams.installed);
|
|
@@ -23283,8 +23911,8 @@ function createUpdateCurrentCommand(seams = DEFAULT_UPDATE_CURRENT_SEAMS) {
|
|
|
23283
23911
|
name: "update current",
|
|
23284
23912
|
subject: "update",
|
|
23285
23913
|
action: "current",
|
|
23286
|
-
summary: "bring this project to the installed rafa
|
|
23287
|
-
description: "Brings the project to the installed rafa when the installed version shares the major and" + ` minor of the version \`${LOCK_FILE}\` records at the project root, and is the same patch or newer
|
|
23914
|
+
summary: "bring this project to the installed rafa: a newer patch, or any newer minor below 1.0.0",
|
|
23915
|
+
description: "Brings the project to the installed rafa when the installed version shares the major and" + ` minor of the version \`${LOCK_FILE}\` records at the project root, and is the same patch or newer;` + " below 1.0.0 it also crosses newer minors, as `0.34.1` to `0.36.0`." + " A project with no lock is adopted at the installed version. It creates the missing `.rafa/` folders" + " and the missing board labels on a GitHub board, runs the deprecations step (none registered yet)," + ` and writes \`${LOCK_FILE}\`. It prints every change first; \`--dry-run\` stops there. Otherwise it` + " warns that the changes cannot be rolled back and asks once, `--yes` answering; with no terminal and" + " no `--yes` it refuses with exit code 1. A newer minor from 1.0.0 on, a newer major, or an installed rafa older than the" + " lock, refuses with exit code 1, and so does a board label `gh` would not create, once everything" + " else is applied. A lock or config that cannot be read, or a `.rafa/` path holding something else," + " exits 2 before anything is asked; a folder or the lock that cannot be written exits 2 naming the" + " step and what was already written. In a linked worktree the lock is the worktree's, on its branch." + " With `--output=json` the plan and what was applied are the data of the terminal result event.",
|
|
23288
23916
|
args: [],
|
|
23289
23917
|
flags: [
|
|
23290
23918
|
{ name: "dry-run", description: "Print every change and write nothing.", type: "boolean" },
|
|
@@ -23323,7 +23951,7 @@ var self_default = createUpdateStub({ action: "self", summary: "update rafa itse
|
|
|
23323
23951
|
var CORE_SUBJECTS = Object.freeze([
|
|
23324
23952
|
{ name: "plan", summary: "create plans from specs; list, show and validate them; read their risk and needs" },
|
|
23325
23953
|
{ name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
|
|
23326
|
-
{ name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
|
|
23954
|
+
{ name: "issue", summary: "the tracker: list, show, create, edit, comment on and move issues; mark one ready, unblock it and check its references" },
|
|
23327
23955
|
{ name: "pr", summary: "the pull request of a branch: one line, in full or in the browser; list, wait on, merge and triage them" },
|
|
23328
23956
|
{ 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 or import another device's store; move an NDJSON store to SQLite" },
|
|
23329
23957
|
{ name: "module", summary: "list the configured modules; run an action a module provides" },
|
|
@@ -23358,6 +23986,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
23358
23986
|
ready_default,
|
|
23359
23987
|
unblock_default,
|
|
23360
23988
|
check_default2,
|
|
23989
|
+
edit_default,
|
|
23361
23990
|
current_default,
|
|
23362
23991
|
show_default6,
|
|
23363
23992
|
view_default,
|