@open-tomato/rafa 0.34.1 → 0.36.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 +18 -18
- package/dist/bundled/agents/doc-updater.md +1 -1
- package/dist/bundled/operators/.claude-plugin/plugin.json +5 -0
- package/dist/bundled/operators/agents/rafa-stretch-engineer.md +40 -19
- package/dist/bundled/operators/agents/rafa-stretch-watchtower.md +9 -9
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +2 -2
- package/dist/cli.js +3748 -1682
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-s8ff86xt.js → index-2n2nedrk.js} +1857 -860
- package/dist/{index-v8d7c6sw.js → index-7bb0fdb7.js} +40 -2
- package/dist/{index-nr4hdb35.js → index-ddzzmcaz.js} +1127 -346
- package/dist/{index-vp0xghk2.js → index-jfas157k.js} +6 -3
- package/dist/{index-bebv92ha.js → index-qvbeqrx3.js} +401 -201
- package/dist/index.js +4 -6
- package/dist/plan/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
AGENT_DEFINITION_DIR,
|
|
4
|
+
BLOCKED_BY_FIELD,
|
|
4
5
|
BOARD_LABELS,
|
|
5
6
|
BOARD_LISTING_LIMIT,
|
|
6
7
|
BRANCH_PREFIX,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
NATIVE_RETRY_HINT,
|
|
33
34
|
NEXT_COMMAND_ACTIONS,
|
|
34
35
|
NO_NEXT_STEP,
|
|
36
|
+
NoSessionRefusal,
|
|
35
37
|
ORIGIN_FIELD,
|
|
36
38
|
PREVIOUS_COPY_WARN_ABOVE,
|
|
37
39
|
PROVENANCE_FIELD,
|
|
@@ -61,6 +63,7 @@ import {
|
|
|
61
63
|
VENDOR_COMMAND,
|
|
62
64
|
WHEN_TO_USE_HEADING,
|
|
63
65
|
WORKTREES_SUBDIR,
|
|
66
|
+
WORKTREE_STATUS,
|
|
64
67
|
actionInvocation,
|
|
65
68
|
actionOutput,
|
|
66
69
|
addBlockerToBody,
|
|
@@ -103,6 +106,7 @@ import {
|
|
|
103
106
|
compareVersions,
|
|
104
107
|
configuredMoveRelations,
|
|
105
108
|
convertToInstinct,
|
|
109
|
+
countChanges,
|
|
106
110
|
countCharacters,
|
|
107
111
|
countPreviousCopies,
|
|
108
112
|
createGhBoardListing,
|
|
@@ -135,6 +139,7 @@ import {
|
|
|
135
139
|
epicTickSentence,
|
|
136
140
|
estimateEta,
|
|
137
141
|
etaLine,
|
|
142
|
+
eventsFilePath,
|
|
138
143
|
expectTwoArguments,
|
|
139
144
|
failingRows,
|
|
140
145
|
fetchClaimBranches,
|
|
@@ -179,6 +184,7 @@ import {
|
|
|
179
184
|
labelsLineBlockersOf,
|
|
180
185
|
lazyPrompter,
|
|
181
186
|
lineRefusal,
|
|
187
|
+
lineRefusal1 as lineRefusal2,
|
|
182
188
|
listBoardLabels,
|
|
183
189
|
list_default,
|
|
184
190
|
list_default1 as list_default2,
|
|
@@ -346,7 +352,6 @@ import {
|
|
|
346
352
|
unlabelledRoadmapMessage,
|
|
347
353
|
unresolvedTracker,
|
|
348
354
|
urlLines,
|
|
349
|
-
usage,
|
|
350
355
|
validateManifest,
|
|
351
356
|
validate_default,
|
|
352
357
|
verdictOf,
|
|
@@ -355,9 +360,11 @@ import {
|
|
|
355
360
|
waitingSettingsOf,
|
|
356
361
|
watchDryEpic,
|
|
357
362
|
worktreeAt,
|
|
363
|
+
worktreeDirAt,
|
|
364
|
+
worktreesHolding,
|
|
358
365
|
writeSession,
|
|
359
366
|
writeShippedFile
|
|
360
|
-
} from "./index-
|
|
367
|
+
} from "./index-2n2nedrk.js";
|
|
361
368
|
import {
|
|
362
369
|
PROBE_TIMEOUT_MS,
|
|
363
370
|
loadPlanPrerequisites,
|
|
@@ -369,7 +376,7 @@ import {
|
|
|
369
376
|
runPreflight,
|
|
370
377
|
splitBlockerComment,
|
|
371
378
|
trackerPathFor
|
|
372
|
-
} from "./index-
|
|
379
|
+
} from "./index-7bb0fdb7.js";
|
|
373
380
|
import {
|
|
374
381
|
ACTION_HEADING,
|
|
375
382
|
CAUSE_HEADING,
|
|
@@ -379,6 +386,7 @@ import {
|
|
|
379
386
|
EFFORT_DIR_VARIABLE,
|
|
380
387
|
EFFORT_KEY_PROJECTIONS,
|
|
381
388
|
EFFORT_STORE_DIR,
|
|
389
|
+
EVENT_PREFIX,
|
|
382
390
|
EffortCopyFailure,
|
|
383
391
|
EffortCopyRefusal,
|
|
384
392
|
HOP_FILE,
|
|
@@ -422,6 +430,7 @@ import {
|
|
|
422
430
|
instinctProject,
|
|
423
431
|
isFile,
|
|
424
432
|
issueLine,
|
|
433
|
+
keepsIdLine,
|
|
425
434
|
loadConfig,
|
|
426
435
|
mainCheckoutOf,
|
|
427
436
|
mergeFrontmatter,
|
|
@@ -429,8 +438,10 @@ import {
|
|
|
429
438
|
migrateStore,
|
|
430
439
|
minutesBetween,
|
|
431
440
|
normalizeRemote,
|
|
441
|
+
oneLine,
|
|
432
442
|
openNdjsonStore,
|
|
433
443
|
openSqliteStore,
|
|
444
|
+
padKind,
|
|
434
445
|
planFileName,
|
|
435
446
|
plansDirAt,
|
|
436
447
|
plural,
|
|
@@ -464,7 +475,7 @@ import {
|
|
|
464
475
|
versionLine,
|
|
465
476
|
writeFrontmatter,
|
|
466
477
|
writePositionFile
|
|
467
|
-
} from "./index-
|
|
478
|
+
} from "./index-qvbeqrx3.js";
|
|
468
479
|
import {
|
|
469
480
|
CONFIG_DEFAULTS,
|
|
470
481
|
CONFIG_FILE,
|
|
@@ -479,7 +490,7 @@ import {
|
|
|
479
490
|
readItemBytes,
|
|
480
491
|
readRafaBlocks,
|
|
481
492
|
resolveTiers
|
|
482
|
-
} from "./index-
|
|
493
|
+
} from "./index-jfas157k.js";
|
|
483
494
|
import {
|
|
484
495
|
promotable
|
|
485
496
|
} from "./index-d6d0942q.js";
|
|
@@ -1751,12 +1762,12 @@ function interactiveFlag() {
|
|
|
1751
1762
|
function interactiveInstead(commandName) {
|
|
1752
1763
|
return `Run \`${commandName} --output=json\` to read the rows without one.`;
|
|
1753
1764
|
}
|
|
1754
|
-
function interactiveTerminal(context, seams, commandName,
|
|
1765
|
+
function interactiveTerminal(context, seams, commandName, usage) {
|
|
1755
1766
|
if (context.flags["interactive"] !== true)
|
|
1756
1767
|
return null;
|
|
1757
1768
|
if (context.outputMode === "json") {
|
|
1758
1769
|
throw new CommandExit(1, "\u274C -i browses the rows in a terminal and --output=json writes them as data;" + ` give one or the other
|
|
1759
|
-
Usage: ${
|
|
1770
|
+
Usage: ${usage}`);
|
|
1760
1771
|
}
|
|
1761
1772
|
const terminal = seams.terminal?.() ?? processTerminal();
|
|
1762
1773
|
refuseWithoutTerminal(terminal, { instead: interactiveInstead(commandName) });
|
|
@@ -2456,14 +2467,14 @@ function usageOf(kind) {
|
|
|
2456
2467
|
return `rafa ${kind} search "<question>" [--all] [--no-model]`;
|
|
2457
2468
|
}
|
|
2458
2469
|
function readSearchLine(context, kind) {
|
|
2459
|
-
const
|
|
2460
|
-
const hint = `Type the question ahead of it: ${
|
|
2470
|
+
const usage = usageOf(kind);
|
|
2471
|
+
const hint = `Type the question ahead of it: ${usage}`;
|
|
2461
2472
|
const all = readSwitch("all", context.flags["all"], hint);
|
|
2462
2473
|
const model = readSwitch("model", context.flags["model"] ?? true, hint);
|
|
2463
|
-
const question = expectOneArgument(context.args,
|
|
2474
|
+
const question = expectOneArgument(context.args, usage);
|
|
2464
2475
|
if (question.trim() === "")
|
|
2465
2476
|
throw new CommandExit(1, `\u274C The question is blank.
|
|
2466
|
-
Usage: ${
|
|
2477
|
+
Usage: ${usage}`);
|
|
2467
2478
|
const kinds = all ? [kind, otherKind(kind)] : [kind];
|
|
2468
2479
|
return { question, kinds, model };
|
|
2469
2480
|
}
|
|
@@ -3241,11 +3252,11 @@ var RELEASING = { during: "was being released", nothing: "nothing was released"
|
|
|
3241
3252
|
function refuse(message) {
|
|
3242
3253
|
return new CommandExit(1, `\u274C ${COMMAND}: ${message}`);
|
|
3243
3254
|
}
|
|
3244
|
-
function readIssueArgument(args,
|
|
3245
|
-
const word = expectOneArgument(args,
|
|
3255
|
+
function readIssueArgument(args, usage) {
|
|
3256
|
+
const word = expectOneArgument(args, usage);
|
|
3246
3257
|
if (!ISSUE_NUMBER2.test(word) || !Number.isSafeInteger(Number(word))) {
|
|
3247
3258
|
throw new CommandExit(1, `\u274C "${word}" is no issue number, expected a positive whole number
|
|
3248
|
-
Usage: ${
|
|
3259
|
+
Usage: ${usage}`);
|
|
3249
3260
|
}
|
|
3250
3261
|
return Number(word);
|
|
3251
3262
|
}
|
|
@@ -3511,7 +3522,7 @@ var HANDING = { during: "was being handed over", nothing: "nothing was pushed" }
|
|
|
3511
3522
|
function refuse3(message) {
|
|
3512
3523
|
return new CommandExit(1, `\u274C ${COMMAND3}: ${message}`);
|
|
3513
3524
|
}
|
|
3514
|
-
function
|
|
3525
|
+
function lineRefusal3(message) {
|
|
3515
3526
|
return refuse3(`${message}
|
|
3516
3527
|
Usage: ${USAGE9}`);
|
|
3517
3528
|
}
|
|
@@ -3521,29 +3532,29 @@ function readWithdraw(flags) {
|
|
|
3521
3532
|
return false;
|
|
3522
3533
|
if (typeof value === "boolean")
|
|
3523
3534
|
return value;
|
|
3524
|
-
throw
|
|
3535
|
+
throw lineRefusal3(`--${WITHDRAW_FLAG} takes no value, and read "${value}" as one; type the issue number before the flags`);
|
|
3525
3536
|
}
|
|
3526
3537
|
function readTo(flags) {
|
|
3527
3538
|
const value = flags[TO_FLAG];
|
|
3528
3539
|
if (value === undefined)
|
|
3529
3540
|
return null;
|
|
3530
3541
|
if (typeof value === "boolean")
|
|
3531
|
-
throw
|
|
3542
|
+
throw lineRefusal3(`--${TO_FLAG} needs a store id, as --${TO_FLAG}=<store id>`);
|
|
3532
3543
|
const to = value.trim();
|
|
3533
3544
|
if (!STORE_ID.test(to))
|
|
3534
|
-
throw
|
|
3545
|
+
throw lineRefusal3(`--${TO_FLAG}=${JSON.stringify(value)} is no store id, which is one word with no blank`);
|
|
3535
3546
|
return to;
|
|
3536
3547
|
}
|
|
3537
3548
|
function readHandRequest(flags) {
|
|
3538
3549
|
const withdraw = readWithdraw(flags);
|
|
3539
3550
|
const to = readTo(flags);
|
|
3540
3551
|
if (withdraw && to !== null) {
|
|
3541
|
-
throw
|
|
3552
|
+
throw lineRefusal3(`--${TO_FLAG} and --${WITHDRAW_FLAG} are two actions; give one`);
|
|
3542
3553
|
}
|
|
3543
3554
|
if (withdraw)
|
|
3544
3555
|
return { action: "withdraw" };
|
|
3545
3556
|
if (to === null)
|
|
3546
|
-
throw
|
|
3557
|
+
throw lineRefusal3(`name the receiving store with --${TO_FLAG}=<store id>, or take a pending handover back with --${WITHDRAW_FLAG}`);
|
|
3547
3558
|
return { action: "hand", to };
|
|
3548
3559
|
}
|
|
3549
3560
|
function recordFor(reading, request, issue, storeId) {
|
|
@@ -4326,7 +4337,8 @@ async function runCleanup(context, seams) {
|
|
|
4326
4337
|
now: (seams.now ?? (() => new Date))(),
|
|
4327
4338
|
home: project.home,
|
|
4328
4339
|
cwd,
|
|
4329
|
-
projectRoot: project.root
|
|
4340
|
+
projectRoot: project.root,
|
|
4341
|
+
worktreeDir: config.loopWorktreeDir
|
|
4330
4342
|
});
|
|
4331
4343
|
if (context.outputMode === "json") {
|
|
4332
4344
|
context.output.result(cleanupData(read));
|
|
@@ -4346,7 +4358,7 @@ function createCleanupCommand(seams = DEFAULT_CLEANUP_SEAMS) {
|
|
|
4346
4358
|
subject: "cleanup",
|
|
4347
4359
|
action: "cleanup",
|
|
4348
4360
|
summary: "list merged, stale and unpushed branches and leftover worktrees; remove the ones ticked",
|
|
4349
|
-
description: "Runs `git fetch --prune`, then lists the local branches and worktrees that have piled up in" + " four groups, each row its name, its last commit date and why it is listed: Merged (reachable from" + " the base, its pull request merged per the provider, or its upstream gone), Stale (an upstream, not" + " merged, no commit in `cleanup.staleDays`), Not pushed (no upstream, or commits ahead of it) and" + " Worktrees (under `.claude/worktrees
|
|
4361
|
+
description: "Runs `git fetch --prune`, then lists the local branches and worktrees that have piled up in" + " four groups, each row its name, its last commit date and why it is listed: Merged (reachable from" + " the base, its pull request merged per the provider, or its upstream gone), Stale (an upstream, not" + " merged, no commit in `cleanup.staleDays`), Not pushed (no upstream, or commits ahead of it) and" + " Worktrees (under `.claude/worktrees/`, `~/.rafa/worktrees/` and `loop.worktreeDir`). The current" + " branch, the base and every name a `cleanup.keep` glob matches are never listed. With a terminal" + " the groups are one checklist: space ticks a row, `a` ticks a whole group, and Merged rows and" + " clean worktrees on a" + " Merged branch start ticked; a worktree that is dirty, locked, the current one, running a loop" + " session or modified within `cleanup.worktreeIdleDays` cannot be ticked, and says why. A ticked" + " Not-pushed branch asks again, naming the commits deleting it loses. Enter then asks `Delete <n>" + " branches and remove <m> worktrees? [y/N]`, and a yes runs `git worktree remove` for each worktree" + " and `git branch -d` for each Merged branch, `-D` for a squash-merged one and for a Stale or" + " Not-pushed branch ticked and confirmed. Nothing runs with `--force` and nothing remote is" + " deleted: a Stale row names the `git push origin --delete <b>` to run by hand. It exits 1 when a" + " step did not run clean. Without a terminal, or with `--output=json`, it prints the four groups," + " asks nothing and removes nothing, exiting 0; with `--output=json` they are the data of the terminal" + " result event. Starts no session.",
|
|
4350
4362
|
args: [],
|
|
4351
4363
|
flags: [
|
|
4352
4364
|
{
|
|
@@ -5530,7 +5542,8 @@ function doctorCleanupSettings(input, now) {
|
|
|
5530
5542
|
now,
|
|
5531
5543
|
home: input.home,
|
|
5532
5544
|
cwd: input.root,
|
|
5533
|
-
projectRoot: input.root
|
|
5545
|
+
projectRoot: input.root,
|
|
5546
|
+
worktreeDir: config.loopWorktreeDir ?? CONFIG_DEFAULTS.loopWorktreeDir
|
|
5534
5547
|
};
|
|
5535
5548
|
}
|
|
5536
5549
|
async function readDoctorCleanup(input, seams = {}) {
|
|
@@ -8141,7 +8154,7 @@ function closingLine(result2, command) {
|
|
|
8141
8154
|
if (result2.status === "would-merge") {
|
|
8142
8155
|
return "\uD83D\uDD0D Dry run: the merged store was built beside it, checked (row counts, integrity_check, this rafa's" + ` schema plan) and deleted. Run \`${command} ${result2.otherPath}\` to swap it in; the original would be` + ` kept whole at ${result2.path}.before-merge-<stamp>.bak.`;
|
|
8143
8156
|
}
|
|
8144
|
-
return `\u2705 Merged. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo
|
|
8157
|
+
return `\u2705 Merged. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.` + ` ${keepsIdLine("merged store")}`;
|
|
8145
8158
|
}
|
|
8146
8159
|
function renderMerge(result2, command = COMMAND_NAME7) {
|
|
8147
8160
|
const other = result2.otherStore === null ? "a store that names no origin" : `store ${result2.otherStore}`;
|
|
@@ -8205,7 +8218,7 @@ function createMergeCommand(seams = {}) {
|
|
|
8205
8218
|
subject: "effort",
|
|
8206
8219
|
action: "merge",
|
|
8207
8220
|
summary: "merge another device's effort store file into this one, keeping every row and the original",
|
|
8208
|
-
description: "Adds to `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, every row another" + " device's store file holds that this one lacks. A row matches on its origin pair, or on its table's" + " identity columns when either side has no origin; an equal pair is skipped, a set-once field NULL here" + " is filled, and any other difference is kept on both sides and recorded in `merge_conflicts`. The gap" + " of each commit brought in, and of the commit after it, is recomputed. The other file is only read." + " The merged store is built beside this one, checked (each table's count plus the rows added," + " integrity_check, the schema plan), and swapped in with the original kept whole as" + " `effort.sqlite.before-merge-<stamp>.bak`. It prints the rows added, skipped and in conflict per table" + " and the backup's name. Exit code 2 for another project's store, and for a `store: ndjson` project," + " naming `rafa effort move --to=sqlite`; exit code 1, changing nothing, for a missing or damaged file," + " a write in flight, a loop session running or paused, and a development build over a store it does not" + " own. With `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8221
|
+
description: "Adds to `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, every row another" + " device's store file holds that this one lacks. A row matches on its origin pair, or on its table's" + " identity columns when either side has no origin; an equal pair is skipped, a set-once field NULL here" + " is filled, and any other difference is kept on both sides and recorded in `merge_conflicts`. The gap" + " of each commit brought in, and of the commit after it, is recomputed. The other file is only read." + " The merged store is built beside this one, checked (each table's count plus the rows added," + " integrity_check, the schema plan), and swapped in with the original kept whole as" + " `effort.sqlite.before-merge-<stamp>.bak`. The merged store keeps this store's id; the backup is a copy," + " so renamed back it takes a new id on its next write. It prints the rows added, skipped and in conflict per table" + " and the backup's name. Exit code 2 for another project's store, and for a `store: ndjson` project," + " naming `rafa effort move --to=sqlite`; exit code 1, changing nothing, for a missing or damaged file," + " a write in flight, a loop session running or paused, and a development build over a store it does not" + " own. With `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8209
8222
|
args: [
|
|
8210
8223
|
{
|
|
8211
8224
|
name: "file",
|
|
@@ -8354,7 +8367,7 @@ function renderMigrate(result2) {
|
|
|
8354
8367
|
case "migrated":
|
|
8355
8368
|
return [
|
|
8356
8369
|
...builtLines(result2),
|
|
8357
|
-
`\u2705 Migrated. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.`
|
|
8370
|
+
`\u2705 Migrated. The original is kept whole at ${String(result2.backupPath)}; rename it back to undo.` + ` ${keepsIdLine("migrated store")}`
|
|
8358
8371
|
];
|
|
8359
8372
|
}
|
|
8360
8373
|
}
|
|
@@ -8401,7 +8414,7 @@ function createMigrateCommand(seams = {}) {
|
|
|
8401
8414
|
subject: "effort",
|
|
8402
8415
|
action: "migrate",
|
|
8403
8416
|
summary: "apply the effort store's pending migrations, one that breaks older runtimes included, keeping the original",
|
|
8404
|
-
description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then
|
|
8417
|
+
description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then writes the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. The" + " migrated store keeps the store's id; the backup is a copy, so renamed back it takes a new id on its" + " next write. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8405
8418
|
args: [],
|
|
8406
8419
|
flags: [
|
|
8407
8420
|
{
|
|
@@ -10000,8 +10013,8 @@ function spanMinutes(row2) {
|
|
|
10000
10013
|
return minutesBetween(first, last);
|
|
10001
10014
|
}
|
|
10002
10015
|
function tokensOf(row2) {
|
|
10003
|
-
const
|
|
10004
|
-
return
|
|
10016
|
+
const usage = row2.usage;
|
|
10017
|
+
return usage.inputTokens + usage.outputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
10005
10018
|
}
|
|
10006
10019
|
function summariseEpicCost(rows, members) {
|
|
10007
10020
|
const ids = members.map(boardId);
|
|
@@ -10442,11 +10455,11 @@ var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
|
|
|
10442
10455
|
function ref7(issue) {
|
|
10443
10456
|
return `#${String(issue)}`;
|
|
10444
10457
|
}
|
|
10445
|
-
function
|
|
10458
|
+
function oneLine2(text) {
|
|
10446
10459
|
return text.replace(/\s+/gu, " ").trim();
|
|
10447
10460
|
}
|
|
10448
10461
|
function criterionName(criterion) {
|
|
10449
|
-
return `criterion ${String(criterion.number)}: ${
|
|
10462
|
+
return `criterion ${String(criterion.number)}: ${oneLine2(criterion.text)}`;
|
|
10450
10463
|
}
|
|
10451
10464
|
function criteria(count) {
|
|
10452
10465
|
return count === 1 ? "1 criterion" : `${String(count)} criteria`;
|
|
@@ -10470,7 +10483,7 @@ function refuseOpenMembers(number, members) {
|
|
|
10470
10483
|
const open = members.filter((member) => member.state === "OPEN");
|
|
10471
10484
|
if (open.length === 0)
|
|
10472
10485
|
return;
|
|
10473
|
-
const named = open.map((member) => `${ref7(member.number)} ${
|
|
10486
|
+
const named = open.map((member) => `${ref7(member.number)} ${oneLine2(member.title)}`).join(", ");
|
|
10474
10487
|
throw refusal7(`Epic ${ref7(number)} has ${String(open.length)} open ${open.length === 1 ? "member" : "members"}: ${named}. The gate checks an epic only once every member is closed: close them, or move them` + " out with rafa epic move <issue> --to=<epic>. No session was started");
|
|
10475
10488
|
}
|
|
10476
10489
|
function labelsEpicToClose(issues, epic) {
|
|
@@ -10542,7 +10555,7 @@ function closeTriageFile(root, epic) {
|
|
|
10542
10555
|
return join22(root, ".rafa", `epic-${String(epic)}-close`);
|
|
10543
10556
|
}
|
|
10544
10557
|
function failedCheckArtifact(epic, criterion) {
|
|
10545
|
-
return `epic ${ref7(epic)} acceptance criterion: ${
|
|
10558
|
+
return `epic ${ref7(epic)} acceptance criterion: ${oneLine2(criterion.text)}`;
|
|
10546
10559
|
}
|
|
10547
10560
|
function failedCheckReport(epic, failure, commit) {
|
|
10548
10561
|
const { criterion } = failure.check;
|
|
@@ -10566,7 +10579,7 @@ function failedCheckReport(epic, failure, commit) {
|
|
|
10566
10579
|
skillsUsed: [],
|
|
10567
10580
|
blockers: [],
|
|
10568
10581
|
outOfScopeBugs: [Object.freeze({
|
|
10569
|
-
what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${
|
|
10582
|
+
what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine2(criterion.text)}`,
|
|
10570
10583
|
artifact: failedCheckArtifact(epic, criterion),
|
|
10571
10584
|
security: false,
|
|
10572
10585
|
extras: []
|
|
@@ -10659,9 +10672,9 @@ async function weighResults(run, epic, ran) {
|
|
|
10659
10672
|
if (result2.kind === "unanswered")
|
|
10660
10673
|
warn2(run, `No answer for ${name} \u2014 ${result2.reason}`);
|
|
10661
10674
|
else if (result2.result === "pass")
|
|
10662
|
-
say(run, `\u2713 Passed ${name} \u2014 ${
|
|
10675
|
+
say(run, `\u2713 Passed ${name} \u2014 ${oneLine2(result2.evidence)}`);
|
|
10663
10676
|
else
|
|
10664
|
-
warn2(run, `\u2717 Failed ${name} \u2014 ${
|
|
10677
|
+
warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine2(result2.evidence)}`);
|
|
10665
10678
|
}
|
|
10666
10679
|
if (!ran.removal.ok) {
|
|
10667
10680
|
const said = ran.removal.said === "" ? "git said nothing" : ran.removal.said;
|
|
@@ -11721,7 +11734,8 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
11721
11734
|
`# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`,
|
|
11722
11735
|
"# tests:",
|
|
11723
11736
|
`# fullSuiteTriggers: ${globsLine(CONFIG_DEFAULTS.testsFullSuiteTriggers)} # a task whose diff matches one runs the full suite`,
|
|
11724
|
-
`# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs
|
|
11737
|
+
`# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`,
|
|
11738
|
+
`# alwaysRun: ${globsLine(CONFIG_DEFAULTS.testsAlwaysRun)} # test files a task step runs beside its changed-file tests`
|
|
11725
11739
|
]);
|
|
11726
11740
|
var VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
|
|
11727
11741
|
var PROJECT_HEADER = Object.freeze([
|
|
@@ -12169,32 +12183,32 @@ var DEFAULT_CHECK_SEAMS = Object.freeze({
|
|
|
12169
12183
|
});
|
|
12170
12184
|
var SKILL_CHECK_USAGE = "rafa skill check <dir> [--fix] [--project=<root>]";
|
|
12171
12185
|
var INSTINCT_CHECK_USAGE = "rafa instinct check <dir>";
|
|
12172
|
-
function readCheckDirectory(args,
|
|
12186
|
+
function readCheckDirectory(args, usage, cwd) {
|
|
12173
12187
|
const [dir, extra] = args;
|
|
12174
12188
|
if (dir === undefined) {
|
|
12175
12189
|
throw new CommandExit(1, `\u274C Expected a directory to check, got none
|
|
12176
|
-
Usage: ${
|
|
12190
|
+
Usage: ${usage}`);
|
|
12177
12191
|
}
|
|
12178
12192
|
if (extra !== undefined) {
|
|
12179
12193
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
12180
|
-
Usage: ${
|
|
12194
|
+
Usage: ${usage}`);
|
|
12181
12195
|
}
|
|
12182
12196
|
return resolve8(cwd(), dir);
|
|
12183
12197
|
}
|
|
12184
|
-
function readFix(value,
|
|
12198
|
+
function readFix(value, usage) {
|
|
12185
12199
|
if (value === undefined || value === false || value === "false")
|
|
12186
12200
|
return false;
|
|
12187
12201
|
if (value === true || value === "true")
|
|
12188
12202
|
return true;
|
|
12189
12203
|
throw new CommandExit(1, `\u274C --fix takes no value, and read "${value}" as one; type it after the directory
|
|
12190
|
-
Usage: ${
|
|
12204
|
+
Usage: ${usage}`);
|
|
12191
12205
|
}
|
|
12192
|
-
function readProjectRoot(value,
|
|
12206
|
+
function readProjectRoot(value, usage, cwd) {
|
|
12193
12207
|
if (value === undefined || value === false)
|
|
12194
12208
|
return null;
|
|
12195
12209
|
if (value === true || value === "") {
|
|
12196
12210
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
12197
|
-
Usage: ${
|
|
12211
|
+
Usage: ${usage}`);
|
|
12198
12212
|
}
|
|
12199
12213
|
return resolve8(cwd(), value);
|
|
12200
12214
|
}
|
|
@@ -12238,12 +12252,12 @@ function checkCommandResult(report2, projectRoot2) {
|
|
|
12238
12252
|
warningFiles: warningFileCount(report2.reports)
|
|
12239
12253
|
};
|
|
12240
12254
|
}
|
|
12241
|
-
function scan(root, kind, options,
|
|
12255
|
+
function scan(root, kind, options, usage) {
|
|
12242
12256
|
try {
|
|
12243
12257
|
return checkDirectory(root, kind, options);
|
|
12244
12258
|
} catch (error) {
|
|
12245
12259
|
const reason = error instanceof Error ? error.message : String(error);
|
|
12246
|
-
throw new CommandExit(1, `\u274C ${
|
|
12260
|
+
throw new CommandExit(1, `\u274C ${usage}: ${reason}`);
|
|
12247
12261
|
}
|
|
12248
12262
|
}
|
|
12249
12263
|
async function runCheckCommand(context, spec, seams) {
|
|
@@ -12536,7 +12550,7 @@ Usage: ${USAGE19}`);
|
|
|
12536
12550
|
return "blessed";
|
|
12537
12551
|
return conflicts ? "conflicts" : "all";
|
|
12538
12552
|
}
|
|
12539
|
-
function
|
|
12553
|
+
function oneLine3(text) {
|
|
12540
12554
|
return text.trim().replace(/\s+/gu, " ");
|
|
12541
12555
|
}
|
|
12542
12556
|
function byConfidence(left, right) {
|
|
@@ -12569,7 +12583,7 @@ function conflictLines(conflict) {
|
|
|
12569
12583
|
const width = Math.max(...conflict.records.map((record) => record.id.length));
|
|
12570
12584
|
return [
|
|
12571
12585
|
` ${conflict.scope} ${conflict.trigger}`,
|
|
12572
|
-
...conflict.records.map((record) => lessonLine(" ", record, width,
|
|
12586
|
+
...conflict.records.map((record) => lessonLine(" ", record, width, oneLine3(record.action)))
|
|
12573
12587
|
];
|
|
12574
12588
|
}
|
|
12575
12589
|
function renderConflicts(result2) {
|
|
@@ -12837,7 +12851,11 @@ import { join as join27, resolve as resolve9 } from "path";
|
|
|
12837
12851
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
12838
12852
|
var STAMP_FLAG = "stamp";
|
|
12839
12853
|
var ISSUE_NUMBER7 = /^[1-9]\d*$/u;
|
|
12840
|
-
var STATE_ORDER = ["ok", "dangling", "suspect", "resolved", "unknown"];
|
|
12854
|
+
var STATE_ORDER = ["ok", "new", "dangling", "suspect", "resolved", "unknown"];
|
|
12855
|
+
var NOTES = new Map([
|
|
12856
|
+
["new", " \u2014 not there yet, read as a target the spec is to add"],
|
|
12857
|
+
["unknown", " \u2014 its repository could not be read, so it is not checked"]
|
|
12858
|
+
]);
|
|
12841
12859
|
function readCheckIssue(args) {
|
|
12842
12860
|
if (args.length > 1) {
|
|
12843
12861
|
throw lineRefusal(`Expected one issue number, got ${String(args.length)}: ${args.join(" ")}`, CHECK_USAGE);
|
|
@@ -12901,8 +12919,8 @@ function rowSuffix(ref8) {
|
|
|
12901
12919
|
const noun = ref8.changedHeadings.length === 1 ? "heading" : "headings";
|
|
12902
12920
|
const headings = ref8.changedHeadings.length === 0 ? "" : `: ${noun} ${ref8.changedHeadings.map((heading) => `"${heading}"`).join(", ")} changed`;
|
|
12903
12921
|
const after = ref8.unblock === null ? "" : ` \u2014 ${ref8.unblock}`;
|
|
12904
|
-
const
|
|
12905
|
-
return `${headings} (line ${String(ref8.line)})${after}${
|
|
12922
|
+
const note = NOTES.get(ref8.state) ?? "";
|
|
12923
|
+
return `${headings} (line ${String(ref8.line)})${after}${note}`;
|
|
12906
12924
|
}
|
|
12907
12925
|
function checkedRefLine(ref8) {
|
|
12908
12926
|
return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix(ref8)}`;
|
|
@@ -12957,7 +12975,7 @@ function createIssueCheckCommand(seams = {}) {
|
|
|
12957
12975
|
subject: "issue",
|
|
12958
12976
|
action: "check",
|
|
12959
12977
|
summary: "read the references a spec's saved copy names, each with its state",
|
|
12960
|
-
description: "Reads the saved copy of issue <n> under `specs.dir` and prints every reference it names \u2014 an" + " issue, a file,
|
|
12978
|
+
description: "Reads the saved copy of issue <n> under `specs.dir` and prints every reference it names \u2014 an" + " issue, a file, a declared symbol, a command, a flag, a config key \u2014 with its state against the" + " stamp the copy keeps: ok, new, dangling, suspect, resolved or unknown. A reference the copy keeps no" + " stamp for is stamped on this read. Exits 0 whatever the states are, and plans nothing. A missing" + " saved copy is refused, naming the `rafa plan create --issue=<n>` that writes one. With" + " `--output=json` every reference, with its kind, line, state and fingerprint, is the data of the" + " terminal result event.",
|
|
12961
12979
|
args: [
|
|
12962
12980
|
{
|
|
12963
12981
|
name: "n",
|
|
@@ -13059,10 +13077,89 @@ function createIssueCommentCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13059
13077
|
}
|
|
13060
13078
|
var comment_default = createIssueCommentCommand();
|
|
13061
13079
|
|
|
13080
|
+
// src/commands/issue/create-blocked.ts
|
|
13081
|
+
var KNOWN_LIST_LIMIT = 1000;
|
|
13082
|
+
var UNFILED = 0;
|
|
13083
|
+
var REMEDY = `name the issues it waits on as "${BLOCKED_BY_FIELD}: #24 #26", or take the line out`;
|
|
13084
|
+
function nameIds(ids) {
|
|
13085
|
+
return ids.map((id2) => `#${String(id2)}`).join(" ");
|
|
13086
|
+
}
|
|
13087
|
+
function faultClause(read) {
|
|
13088
|
+
if (read.kind === "no-ids" && read.foreign.length > 0) {
|
|
13089
|
+
return `names only issues on other repositories: ${read.foreign.join(" ")}`;
|
|
13090
|
+
}
|
|
13091
|
+
if (read.kind === "no-ids")
|
|
13092
|
+
return `names no issue: "${read.text ?? ""}"`;
|
|
13093
|
+
if (read.kind === "self-reference") {
|
|
13094
|
+
return `names #${String(read.issue)}, the number this issue would be filed as`;
|
|
13095
|
+
}
|
|
13096
|
+
return `names ${nameIds(read.unknown)}, which the board has no issue for`;
|
|
13097
|
+
}
|
|
13098
|
+
function specLineFault(read) {
|
|
13099
|
+
if (read.kind === "blocked" || read.kind === "no-line") {
|
|
13100
|
+
throw new TypeError(`issue create: a ${read.kind} reading of the "${BLOCKED_BY_FIELD}:" line has no fault to name`);
|
|
13101
|
+
}
|
|
13102
|
+
return `The spec's "${BLOCKED_BY_FIELD}:" line, line ${String(read.line ?? 0)} of the body, ` + `${faultClause(read)}: ${REMEDY}`;
|
|
13103
|
+
}
|
|
13104
|
+
function faultRefusal(read) {
|
|
13105
|
+
return new CommandExit(1, `\u274C ${specLineFault(read)}`);
|
|
13106
|
+
}
|
|
13107
|
+
function readSpecLine(draft) {
|
|
13108
|
+
if (draft.type !== "spec")
|
|
13109
|
+
return null;
|
|
13110
|
+
const read = readBlockedBy(UNFILED, draft.body);
|
|
13111
|
+
if (read.kind === "no-line")
|
|
13112
|
+
return null;
|
|
13113
|
+
if (read.kind === "no-ids")
|
|
13114
|
+
throw faultRefusal(read);
|
|
13115
|
+
return read;
|
|
13116
|
+
}
|
|
13117
|
+
function issueNumbers(ids) {
|
|
13118
|
+
const numbers = [];
|
|
13119
|
+
for (const id2 of ids) {
|
|
13120
|
+
if (!/^[1-9]\d*$/u.test(id2))
|
|
13121
|
+
return null;
|
|
13122
|
+
const number = Number(id2);
|
|
13123
|
+
if (!Number.isSafeInteger(number))
|
|
13124
|
+
return null;
|
|
13125
|
+
numbers.push(number);
|
|
13126
|
+
}
|
|
13127
|
+
return numbers;
|
|
13128
|
+
}
|
|
13129
|
+
function uncheckedNumbers(why) {
|
|
13130
|
+
return { known: undefined, next: UNFILED, unchecked: `${why}, so no "${BLOCKED_BY_FIELD}:" id was checked against it` };
|
|
13131
|
+
}
|
|
13132
|
+
async function readBoardNumbers(tracker) {
|
|
13133
|
+
const refs2 = await onTracker(tracker, "list the board's issues", () => tracker.find({ limit: KNOWN_LIST_LIMIT }));
|
|
13134
|
+
if (refs2.length >= KNOWN_LIST_LIMIT) {
|
|
13135
|
+
return uncheckedNumbers(`the ${tracker.kind} tracker answered the ${String(KNOWN_LIST_LIMIT)} issues the listing asked for and may hold more`);
|
|
13136
|
+
}
|
|
13137
|
+
const numbers = issueNumbers(refs2.map((ref8) => ref8.externalId));
|
|
13138
|
+
if (numbers === null) {
|
|
13139
|
+
return uncheckedNumbers(`the ${tracker.kind} tracker answered an issue id that is not an issue number`);
|
|
13140
|
+
}
|
|
13141
|
+
return { known: new Set(numbers), next: Math.max(0, ...numbers) + 1, unchecked: null };
|
|
13142
|
+
}
|
|
13143
|
+
async function settleSpecLine(draft, tracker) {
|
|
13144
|
+
const board = await readBoardNumbers(tracker);
|
|
13145
|
+
const read = readBlockedBy(board.next, draft.body, board.known);
|
|
13146
|
+
if (read.kind !== "blocked")
|
|
13147
|
+
throw faultRefusal(read);
|
|
13148
|
+
return { draft: { ...draft, specBlocked: true }, unchecked: board.unchecked };
|
|
13149
|
+
}
|
|
13150
|
+
|
|
13062
13151
|
// src/commands/issue/create.ts
|
|
13063
|
-
var USAGE22 = "rafa issue create --title=<text> [--body=<text>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
|
|
13152
|
+
var USAGE22 = "rafa issue create --title=<text> [--body=<text> | --body-file=<path>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
|
|
13153
|
+
var STDIN_PATH = "-";
|
|
13064
13154
|
var DEFAULT_ISSUE_TYPE = "code";
|
|
13155
|
+
function readBodyFileFlag(flags) {
|
|
13156
|
+
const path = readNonBlankFlag(flags, "body-file", USAGE22);
|
|
13157
|
+
if (path === undefined || flags.body === undefined)
|
|
13158
|
+
return path;
|
|
13159
|
+
throw lineRefusal("--body and --body-file cannot be used together: name the body one way", USAGE22);
|
|
13160
|
+
}
|
|
13065
13161
|
function readIssueDraft(flags) {
|
|
13162
|
+
readBodyFileFlag(flags);
|
|
13066
13163
|
return {
|
|
13067
13164
|
opt: 0,
|
|
13068
13165
|
title: readRequiredFlag(flags, "title", USAGE22),
|
|
@@ -13074,23 +13171,45 @@ function readIssueDraft(flags) {
|
|
|
13074
13171
|
blockedBy: []
|
|
13075
13172
|
};
|
|
13076
13173
|
}
|
|
13174
|
+
async function readBodyFile(path, seams) {
|
|
13175
|
+
const read = path === STDIN_PATH ? seams.stdin ?? (() => Bun.stdin.text()) : () => Bun.file(path).text();
|
|
13176
|
+
try {
|
|
13177
|
+
return await read();
|
|
13178
|
+
} catch (error) {
|
|
13179
|
+
const where = path === STDIN_PATH ? "standard input" : `"${path}"`;
|
|
13180
|
+
throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`, USAGE22);
|
|
13181
|
+
}
|
|
13182
|
+
}
|
|
13183
|
+
async function readIssueLine(flags, seams) {
|
|
13184
|
+
const draft = readIssueDraft(flags);
|
|
13185
|
+
const path = readBodyFileFlag(flags);
|
|
13186
|
+
return path === undefined ? draft : { ...draft, body: await readBodyFile(path, seams) };
|
|
13187
|
+
}
|
|
13077
13188
|
function renderCreated(ref8) {
|
|
13078
13189
|
return [`Created ${issueName(ref8)}.`, ...urlLines(ref8)];
|
|
13079
13190
|
}
|
|
13080
13191
|
async function createIssue(context, seams) {
|
|
13081
13192
|
expectNoArgument(context.args, USAGE22);
|
|
13082
|
-
const
|
|
13193
|
+
const read = await readIssueLine(context.flags, seams);
|
|
13194
|
+
const blockedLine = readSpecLine(read);
|
|
13083
13195
|
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
13196
|
+
const draft = blockedLine === null ? read : await settledDraft(read, tracker, context);
|
|
13084
13197
|
const ref8 = await onTracker(tracker, "create the issue", () => tracker.create(draft));
|
|
13085
13198
|
return { tracker: data, ref: ref8 };
|
|
13086
13199
|
}
|
|
13200
|
+
async function settledDraft(read, tracker, context) {
|
|
13201
|
+
const settled = await settleSpecLine(read, tracker);
|
|
13202
|
+
if (settled.unchecked !== null)
|
|
13203
|
+
context.output.warn(`issue create: ${settled.unchecked}`);
|
|
13204
|
+
return settled.draft;
|
|
13205
|
+
}
|
|
13087
13206
|
function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
13088
13207
|
const command = {
|
|
13089
13208
|
name: "issue create",
|
|
13090
13209
|
subject: "issue",
|
|
13091
13210
|
action: "create",
|
|
13092
13211
|
summary: "create an issue on the tracker from a title and a body",
|
|
13093
|
-
description: "Files one issue on the tracker `tracker.default` names in `.rafa/config.yaml`, or on the first" + " `tracker.fallback` kind whose preflight passes when it fails, each kind passed over warned about." + " `--title` is required. The issue is of type `code` and module `unassigned` unless `--type` and" + " `--module` say otherwise, and with no `--priority` the tracker marks it needs-triage. Prints the" + " tracker and id of the issue filed, and its URL when there is one. With `--output=json` the tracker" + " and the ref are the data of the terminal result event.",
|
|
13212
|
+
description: "Files one issue on the tracker `tracker.default` names in `.rafa/config.yaml`, or on the first" + " `tracker.fallback` kind whose preflight passes when it fails, each kind passed over warned about." + " `--title` is required. The issue is of type `code` and module `unassigned` unless `--type` and" + " `--module` say otherwise, and with no `--priority` the tracker marks it needs-triage. The body is" + " `--body`, or what the file `--body-file` names holds, standard input for `-`. A spec whose body" + " carries a `Blocked by: #24` line is filed blocked, and refused when the line names no issue, the" + " issue itself or an issue the board does not hold. Prints the" + " tracker and id of the issue filed, and its URL when there is one. With `--output=json` the tracker" + " and the ref are the data of the terminal result event.",
|
|
13094
13213
|
args: [],
|
|
13095
13214
|
flags: [
|
|
13096
13215
|
{
|
|
@@ -13101,7 +13220,12 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13101
13220
|
},
|
|
13102
13221
|
{
|
|
13103
13222
|
name: "body",
|
|
13104
|
-
description: "The body of the issue, as markdown. Empty when left out.",
|
|
13223
|
+
description: "The body of the issue, as markdown. Empty when left out. Not with --body-file.",
|
|
13224
|
+
type: "string"
|
|
13225
|
+
},
|
|
13226
|
+
{
|
|
13227
|
+
name: "body-file",
|
|
13228
|
+
description: "A file holding the body of the issue, read whole; `-` reads standard input. Not with --body.",
|
|
13105
13229
|
type: "string"
|
|
13106
13230
|
},
|
|
13107
13231
|
{
|
|
@@ -13130,6 +13254,10 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13130
13254
|
{
|
|
13131
13255
|
cmd: 'rafa issue create --title="Document issue move" --priority=low --output=json',
|
|
13132
13256
|
note: "Writes a start event, then a result event whose data holds the tracker and the ref of the issue."
|
|
13257
|
+
},
|
|
13258
|
+
{
|
|
13259
|
+
cmd: 'rafa issue create --title="Faster plan show" --body-file=spec.md',
|
|
13260
|
+
note: "Files an issue whose body is what spec.md holds; --body-file=- reads it from standard input."
|
|
13133
13261
|
}
|
|
13134
13262
|
],
|
|
13135
13263
|
outputs: ["text", "json"],
|
|
@@ -13752,8 +13880,457 @@ function createLoopStopCommand(seams = {}) {
|
|
|
13752
13880
|
}
|
|
13753
13881
|
var stop_default = createLoopStopCommand();
|
|
13754
13882
|
|
|
13883
|
+
// src/commands/loop/wait.ts
|
|
13884
|
+
import { relative as relative7 } from "path";
|
|
13885
|
+
|
|
13886
|
+
// src/loop/awake-clock.ts
|
|
13887
|
+
var NS_PER_MINUTE = 60000000000n;
|
|
13888
|
+
var MS_PER_MINUTE = 60000;
|
|
13889
|
+
var SUSPEND_REPORT_MINUTES = 1;
|
|
13890
|
+
var SYSTEM_CLOCK_SOURCE = Object.freeze({
|
|
13891
|
+
monotonicNs: () => process.hrtime.bigint(),
|
|
13892
|
+
wallMs: () => Date.now()
|
|
13893
|
+
});
|
|
13894
|
+
function suspendedMinutesOf(wallMinutes, awakeMinutes) {
|
|
13895
|
+
const gap = wallMinutes - awakeMinutes;
|
|
13896
|
+
return gap > SUSPEND_REPORT_MINUTES ? gap : 0;
|
|
13897
|
+
}
|
|
13898
|
+
function createAwakeClock(source = SYSTEM_CLOCK_SOURCE) {
|
|
13899
|
+
const mark = () => Object.freeze({
|
|
13900
|
+
monotonicNs: source.monotonicNs(),
|
|
13901
|
+
wallMs: source.wallMs()
|
|
13902
|
+
});
|
|
13903
|
+
const since = (from) => {
|
|
13904
|
+
const monotonicNs = source.monotonicNs() - from.monotonicNs;
|
|
13905
|
+
const awakeMinutes = monotonicNs > 0n ? Number(monotonicNs) / Number(NS_PER_MINUTE) : 0;
|
|
13906
|
+
const wallMinutes = (source.wallMs() - from.wallMs) / MS_PER_MINUTE;
|
|
13907
|
+
return Object.freeze({
|
|
13908
|
+
awakeMinutes,
|
|
13909
|
+
suspendedMinutes: suspendedMinutesOf(wallMinutes, awakeMinutes)
|
|
13910
|
+
});
|
|
13911
|
+
};
|
|
13912
|
+
return Object.freeze({ mark, since });
|
|
13913
|
+
}
|
|
13914
|
+
|
|
13915
|
+
// src/loop/events-file.ts
|
|
13916
|
+
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
13917
|
+
var NEWLINE = 10;
|
|
13918
|
+
function eventsFileOf(root, record) {
|
|
13919
|
+
return eventsFilePath(root, record.sessionId);
|
|
13920
|
+
}
|
|
13921
|
+
function isPlainObject(value) {
|
|
13922
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13923
|
+
}
|
|
13924
|
+
function parseEventLine(line) {
|
|
13925
|
+
let value;
|
|
13926
|
+
try {
|
|
13927
|
+
value = JSON.parse(line);
|
|
13928
|
+
} catch {
|
|
13929
|
+
return null;
|
|
13930
|
+
}
|
|
13931
|
+
if (!isPlainObject(value))
|
|
13932
|
+
return null;
|
|
13933
|
+
const { name, summary, data, ts } = value;
|
|
13934
|
+
if (typeof name !== "string" || typeof summary !== "string" || typeof ts !== "string")
|
|
13935
|
+
return null;
|
|
13936
|
+
if (!isPlainObject(data))
|
|
13937
|
+
return null;
|
|
13938
|
+
return Object.freeze({ name, summary, data: Object.freeze({ ...data }), ts });
|
|
13939
|
+
}
|
|
13940
|
+
function bytesFrom(fd, offset) {
|
|
13941
|
+
const length = fstatSync(fd).size - offset;
|
|
13942
|
+
if (length <= 0)
|
|
13943
|
+
return null;
|
|
13944
|
+
const buffer = Buffer.alloc(length);
|
|
13945
|
+
let filled = 0;
|
|
13946
|
+
while (filled < length) {
|
|
13947
|
+
const read = readSync(fd, buffer, filled, length - filled, offset + filled);
|
|
13948
|
+
if (read === 0)
|
|
13949
|
+
break;
|
|
13950
|
+
filled += read;
|
|
13951
|
+
}
|
|
13952
|
+
return buffer.subarray(0, filled);
|
|
13953
|
+
}
|
|
13954
|
+
function readTail(file, offset) {
|
|
13955
|
+
let fd;
|
|
13956
|
+
try {
|
|
13957
|
+
fd = openSync(file, "r");
|
|
13958
|
+
} catch (error) {
|
|
13959
|
+
if (errorCode(error) === "ENOENT")
|
|
13960
|
+
return "absent";
|
|
13961
|
+
throw error;
|
|
13962
|
+
}
|
|
13963
|
+
try {
|
|
13964
|
+
return bytesFrom(fd, offset);
|
|
13965
|
+
} finally {
|
|
13966
|
+
closeSync(fd);
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
function readEventsFrom(file, offset) {
|
|
13970
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
13971
|
+
throw new RangeError(`events file: unusable offset ${String(offset)}`);
|
|
13972
|
+
}
|
|
13973
|
+
const tail = readTail(file, offset);
|
|
13974
|
+
if (tail === "absent")
|
|
13975
|
+
return Object.freeze({ kind: "absent" });
|
|
13976
|
+
const end = tail?.lastIndexOf(NEWLINE) ?? -1;
|
|
13977
|
+
if (tail === null || end < 0) {
|
|
13978
|
+
return Object.freeze({ kind: "read", events: Object.freeze([]), malformed: 0, offset });
|
|
13979
|
+
}
|
|
13980
|
+
const lines = tail.subarray(0, end).toString("utf8").split(`
|
|
13981
|
+
`);
|
|
13982
|
+
const parsed = lines.map(parseEventLine);
|
|
13983
|
+
const events = parsed.filter((event) => event !== null);
|
|
13984
|
+
return Object.freeze({
|
|
13985
|
+
kind: "read",
|
|
13986
|
+
events: Object.freeze(events),
|
|
13987
|
+
malformed: parsed.length - events.length,
|
|
13988
|
+
offset: offset + end + 1
|
|
13989
|
+
});
|
|
13990
|
+
}
|
|
13991
|
+
|
|
13992
|
+
// src/loop/wait-reasons.ts
|
|
13993
|
+
var WAIT_REASONS = Object.freeze([
|
|
13994
|
+
Object.freeze({ reason: "pr", event: "pr", matches: "event pr", exit: 0 }),
|
|
13995
|
+
Object.freeze({ reason: "no-pr", event: "no-pr", matches: "event no-pr", exit: 10 }),
|
|
13996
|
+
Object.freeze({ reason: "halt", event: "halt", matches: "event halt", exit: 11 }),
|
|
13997
|
+
Object.freeze({ reason: "error", event: "error", matches: "event error", exit: 12 }),
|
|
13998
|
+
Object.freeze({ reason: "blocked", event: "task-blocked", matches: "event task-blocked", exit: 13 }),
|
|
13999
|
+
Object.freeze({
|
|
14000
|
+
reason: "exit",
|
|
14001
|
+
event: null,
|
|
14002
|
+
matches: "the run's pid not alive, or its record reading stopped or done",
|
|
14003
|
+
exit: 14
|
|
14004
|
+
}),
|
|
14005
|
+
Object.freeze({ reason: "quiet", event: null, matches: "no event for <minutes> awake minutes", exit: 15 })
|
|
14006
|
+
]);
|
|
14007
|
+
var WAIT_TIMEOUT_EXIT = 16;
|
|
14008
|
+
var WAIT_NO_SESSION_EXIT = 2;
|
|
14009
|
+
var QUIET_PREFIX = "quiet:";
|
|
14010
|
+
var POSITIVE_WHOLE = /^[1-9]\d*$/;
|
|
14011
|
+
var DEFAULT_WAIT_UNTIL = Object.freeze({
|
|
14012
|
+
reasons: Object.freeze(["pr", "no-pr", "halt", "error", "exit"]),
|
|
14013
|
+
quietMinutes: null
|
|
14014
|
+
});
|
|
14015
|
+
|
|
14016
|
+
class WaitUntilError extends Error {
|
|
14017
|
+
entry;
|
|
14018
|
+
constructor(entry, why) {
|
|
14019
|
+
super(`--until: ${why}: '${entry}'`);
|
|
14020
|
+
this.name = "WaitUntilError";
|
|
14021
|
+
this.entry = entry;
|
|
14022
|
+
}
|
|
14023
|
+
}
|
|
14024
|
+
function rowOf2(reason) {
|
|
14025
|
+
const row2 = WAIT_REASONS.find((candidate) => candidate.reason === reason);
|
|
14026
|
+
if (row2 === undefined)
|
|
14027
|
+
throw new Error(`wait reasons: no row for ${reason}`);
|
|
14028
|
+
return row2;
|
|
14029
|
+
}
|
|
14030
|
+
function waitExitCode(reason) {
|
|
14031
|
+
return rowOf2(reason).exit;
|
|
14032
|
+
}
|
|
14033
|
+
function isPlainReason(entry) {
|
|
14034
|
+
return WAIT_REASONS.some((row2) => row2.reason === entry && row2.reason !== "quiet");
|
|
14035
|
+
}
|
|
14036
|
+
function quietMinutesOf(entry, written) {
|
|
14037
|
+
const minutes = entry.slice(QUIET_PREFIX.length);
|
|
14038
|
+
const value = Number(minutes);
|
|
14039
|
+
if (!POSITIVE_WHOLE.test(minutes) || !Number.isSafeInteger(value)) {
|
|
14040
|
+
throw new WaitUntilError(written, "quiet takes a positive whole number of minutes");
|
|
14041
|
+
}
|
|
14042
|
+
return value;
|
|
14043
|
+
}
|
|
14044
|
+
function parseWaitUntil(raw) {
|
|
14045
|
+
if (raw === undefined)
|
|
14046
|
+
return DEFAULT_WAIT_UNTIL;
|
|
14047
|
+
const asked = new Set;
|
|
14048
|
+
let quietMinutes = null;
|
|
14049
|
+
for (const written of raw.split(",")) {
|
|
14050
|
+
const entry = written.trim();
|
|
14051
|
+
if (entry === "")
|
|
14052
|
+
throw new WaitUntilError(written, "empty entry");
|
|
14053
|
+
if (isPlainReason(entry)) {
|
|
14054
|
+
asked.add(entry);
|
|
14055
|
+
continue;
|
|
14056
|
+
}
|
|
14057
|
+
if (entry === "quiet")
|
|
14058
|
+
throw new WaitUntilError(written, "quiet needs minutes, as quiet:<minutes>");
|
|
14059
|
+
if (!entry.startsWith(QUIET_PREFIX))
|
|
14060
|
+
throw new WaitUntilError(written, "unknown reason");
|
|
14061
|
+
if (quietMinutes !== null)
|
|
14062
|
+
throw new WaitUntilError(written, "quiet asked for twice");
|
|
14063
|
+
quietMinutes = quietMinutesOf(entry, written);
|
|
14064
|
+
asked.add("quiet");
|
|
14065
|
+
}
|
|
14066
|
+
const reasons = WAIT_REASONS.map((row2) => row2.reason).filter((reason) => asked.has(reason));
|
|
14067
|
+
return Object.freeze({ reasons: Object.freeze(reasons), quietMinutes });
|
|
14068
|
+
}
|
|
14069
|
+
function matchEvent(event, until) {
|
|
14070
|
+
const row2 = WAIT_REASONS.find((candidate) => candidate.event === event.name);
|
|
14071
|
+
if (row2 === undefined || !until.reasons.includes(row2.reason))
|
|
14072
|
+
return null;
|
|
14073
|
+
return row2.reason;
|
|
14074
|
+
}
|
|
14075
|
+
function matchRecord(reading, until) {
|
|
14076
|
+
if (!until.reasons.includes("exit"))
|
|
14077
|
+
return null;
|
|
14078
|
+
const ended = !reading.pidAlive || reading.state === "stopped" || reading.state === "done";
|
|
14079
|
+
return ended ? "exit" : null;
|
|
14080
|
+
}
|
|
14081
|
+
function matchQuiet(awakeMinutes, until) {
|
|
14082
|
+
if (until.quietMinutes === null)
|
|
14083
|
+
return null;
|
|
14084
|
+
return awakeMinutes >= until.quietMinutes ? "quiet" : null;
|
|
14085
|
+
}
|
|
14086
|
+
|
|
14087
|
+
// src/commands/loop/wait.ts
|
|
14088
|
+
var USAGE30 = "rafa loop wait [-s|--session-id=<id>] [--until=<reasons>] [--timeout=<minutes>]";
|
|
14089
|
+
var WAIT_POLL_MS = 5000;
|
|
14090
|
+
var UNTIL_FLAG = "until";
|
|
14091
|
+
var TIMEOUT_FLAG = "timeout";
|
|
14092
|
+
var WAIT_EVENT = "wait";
|
|
14093
|
+
function resolveWaitSeams(seams) {
|
|
14094
|
+
const base = resolveLoopSeams();
|
|
14095
|
+
return {
|
|
14096
|
+
readBranch: seams.readBranch ?? base.readBranch,
|
|
14097
|
+
isAlive: seams.isAlive ?? base.isAlive,
|
|
14098
|
+
sleep: seams.sleep ?? base.sleep,
|
|
14099
|
+
readRecord: seams.readRecord ?? ((root, sessionId, isAlive) => readSession(root, sessionId, { isAlive })),
|
|
14100
|
+
clock: seams.clock ?? createAwakeClock(),
|
|
14101
|
+
pollMs: seams.pollMs ?? WAIT_POLL_MS
|
|
14102
|
+
};
|
|
14103
|
+
}
|
|
14104
|
+
function readUntil(context) {
|
|
14105
|
+
const value = context.flags[UNTIL_FLAG];
|
|
14106
|
+
if (value === undefined)
|
|
14107
|
+
return DEFAULT_WAIT_UNTIL;
|
|
14108
|
+
if (typeof value !== "string")
|
|
14109
|
+
throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`, USAGE30);
|
|
14110
|
+
try {
|
|
14111
|
+
return parseWaitUntil(value);
|
|
14112
|
+
} catch (error) {
|
|
14113
|
+
if (error instanceof WaitUntilError)
|
|
14114
|
+
throw lineRefusal2(error.message, USAGE30);
|
|
14115
|
+
throw error;
|
|
14116
|
+
}
|
|
14117
|
+
}
|
|
14118
|
+
function readTimeout(context) {
|
|
14119
|
+
const value = context.flags[TIMEOUT_FLAG];
|
|
14120
|
+
if (value === undefined)
|
|
14121
|
+
return null;
|
|
14122
|
+
const minutes = typeof value === "string" ? Number(value) : Number.NaN;
|
|
14123
|
+
if (typeof value !== "string" || !/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(minutes)) {
|
|
14124
|
+
const spelled = typeof value === "string" ? JSON.stringify(value) : `--${TIMEOUT_FLAG} with no value`;
|
|
14125
|
+
throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`, USAGE30);
|
|
14126
|
+
}
|
|
14127
|
+
return minutes;
|
|
14128
|
+
}
|
|
14129
|
+
function suspendedNote(reading) {
|
|
14130
|
+
const minutes = Math.round(reading.suspendedMinutes);
|
|
14131
|
+
return minutes > 0 ? `, ${plural(minutes, "minute")} more suspended` : "";
|
|
14132
|
+
}
|
|
14133
|
+
function ownLine(kind, text) {
|
|
14134
|
+
return `${EVENT_PREFIX}${padKind(kind)}${text}`;
|
|
14135
|
+
}
|
|
14136
|
+
function eventLine(event) {
|
|
14137
|
+
return `${EVENT_PREFIX}${oneLine(event.summary)}`;
|
|
14138
|
+
}
|
|
14139
|
+
function exitLine(session) {
|
|
14140
|
+
const pid = session.pidAlive ? "alive" : "not alive";
|
|
14141
|
+
return ownLine("exit", `session ${session.sessionId} ${session.state}, pid ${String(session.pid)} ${pid}`);
|
|
14142
|
+
}
|
|
14143
|
+
function quietLine(minutes, reading) {
|
|
14144
|
+
return ownLine("quiet", `no event for ${plural(minutes, "awake minute")}${suspendedNote(reading)}`);
|
|
14145
|
+
}
|
|
14146
|
+
function timeoutLine(minutes, reading) {
|
|
14147
|
+
return ownLine("timeout", `no reason asked for in ${plural(minutes, "awake minute")}${suspendedNote(reading)}`);
|
|
14148
|
+
}
|
|
14149
|
+
function noEventsNote(following) {
|
|
14150
|
+
const path = relative7(following.root, following.file);
|
|
14151
|
+
return `Session ${following.record.sessionId} has no events file at ${path}: a run from an older rafa writes none,` + " and no event can answer until one appears.";
|
|
14152
|
+
}
|
|
14153
|
+
function firstAsked(events, until) {
|
|
14154
|
+
for (const event of events) {
|
|
14155
|
+
const reason = matchEvent(event, until);
|
|
14156
|
+
if (reason !== null)
|
|
14157
|
+
return { reason, event };
|
|
14158
|
+
}
|
|
14159
|
+
return null;
|
|
14160
|
+
}
|
|
14161
|
+
function readNewEvents(following, cursor, context, seams) {
|
|
14162
|
+
const read = readEventsFrom(following.file, cursor.offset);
|
|
14163
|
+
if (read.kind === "absent") {
|
|
14164
|
+
if (!cursor.noted)
|
|
14165
|
+
context.output.warn(noEventsNote(following));
|
|
14166
|
+
return { cursor: { ...cursor, noted: true }, found: null };
|
|
14167
|
+
}
|
|
14168
|
+
const moved = {
|
|
14169
|
+
...cursor,
|
|
14170
|
+
offset: read.offset,
|
|
14171
|
+
quietMark: read.events.length > 0 ? seams.clock.mark() : cursor.quietMark
|
|
14172
|
+
};
|
|
14173
|
+
return { cursor: moved, found: firstAsked(read.events, following.until) };
|
|
14174
|
+
}
|
|
14175
|
+
function readRecordOrRefuse(following, seams) {
|
|
14176
|
+
const { sessionId } = following.record;
|
|
14177
|
+
try {
|
|
14178
|
+
return seams.readRecord(following.root, sessionId, seams.isAlive);
|
|
14179
|
+
} catch (error) {
|
|
14180
|
+
if (!(error instanceof SessionRecordError))
|
|
14181
|
+
throw error;
|
|
14182
|
+
throw refusal(`Session ${sessionId}: its record cannot be read any more.`, messageOf(error));
|
|
14183
|
+
}
|
|
14184
|
+
}
|
|
14185
|
+
function poll(following, before, context, seams) {
|
|
14186
|
+
const fromEvents = readNewEvents(following, before, context, seams);
|
|
14187
|
+
if (fromEvents.found !== null)
|
|
14188
|
+
return fromEvents;
|
|
14189
|
+
const { cursor } = fromEvents;
|
|
14190
|
+
const record = readRecordOrRefuse(following, seams);
|
|
14191
|
+
if (matchRecord({ state: record.state, pidAlive: seams.isAlive(record.pid) }, following.until) !== null) {
|
|
14192
|
+
const last = readNewEvents(following, cursor, context, seams);
|
|
14193
|
+
return { cursor: last.cursor, found: last.found ?? { reason: "exit", event: null } };
|
|
14194
|
+
}
|
|
14195
|
+
if (matchQuiet(seams.clock.since(cursor.quietMark).awakeMinutes, following.until) !== null) {
|
|
14196
|
+
return { cursor, found: { reason: "quiet", event: null } };
|
|
14197
|
+
}
|
|
14198
|
+
const { timeout } = following;
|
|
14199
|
+
if (timeout !== null && seams.clock.since(following.start).awakeMinutes >= timeout) {
|
|
14200
|
+
return { cursor, found: { reason: "timeout", event: null } };
|
|
14201
|
+
}
|
|
14202
|
+
return { cursor, found: null };
|
|
14203
|
+
}
|
|
14204
|
+
function lineOf(found, following, session, reading) {
|
|
14205
|
+
if (found.event !== null)
|
|
14206
|
+
return eventLine(found.event);
|
|
14207
|
+
if (found.reason === "exit")
|
|
14208
|
+
return exitLine(session);
|
|
14209
|
+
if (found.reason === "quiet")
|
|
14210
|
+
return quietLine(following.until.quietMinutes ?? 0, reading);
|
|
14211
|
+
return timeoutLine(following.timeout ?? 0, reading);
|
|
14212
|
+
}
|
|
14213
|
+
function resultOf5(found, following, seams) {
|
|
14214
|
+
const record = readRecordOrRefuse(following, seams);
|
|
14215
|
+
const session = {
|
|
14216
|
+
sessionId: record.sessionId,
|
|
14217
|
+
state: record.state,
|
|
14218
|
+
pid: record.pid,
|
|
14219
|
+
pidAlive: seams.isAlive(record.pid)
|
|
14220
|
+
};
|
|
14221
|
+
const reading = seams.clock.since(following.start);
|
|
14222
|
+
return {
|
|
14223
|
+
reason: found.reason,
|
|
14224
|
+
exitCode: found.reason === "timeout" ? WAIT_TIMEOUT_EXIT : waitExitCode(found.reason),
|
|
14225
|
+
line: lineOf(found, following, session, reading),
|
|
14226
|
+
event: found.event,
|
|
14227
|
+
session,
|
|
14228
|
+
awakeMinutes: reading.awakeMinutes,
|
|
14229
|
+
suspendedMinutes: reading.suspendedMinutes
|
|
14230
|
+
};
|
|
14231
|
+
}
|
|
14232
|
+
function pickOrNoSession(context, seams) {
|
|
14233
|
+
try {
|
|
14234
|
+
return pickSession(context, USAGE30, "live-or-newest", {
|
|
14235
|
+
...resolveLoopSeams(),
|
|
14236
|
+
readBranch: seams.readBranch,
|
|
14237
|
+
isAlive: seams.isAlive
|
|
14238
|
+
});
|
|
14239
|
+
} catch (error) {
|
|
14240
|
+
if (error instanceof NoSessionRefusal)
|
|
14241
|
+
throw new CommandExit(WAIT_NO_SESSION_EXIT, error.message);
|
|
14242
|
+
throw error;
|
|
14243
|
+
}
|
|
14244
|
+
}
|
|
14245
|
+
async function runLoopWait(context, seams = {}) {
|
|
14246
|
+
expectNoArgument(context.args, USAGE30);
|
|
14247
|
+
const until = readUntil(context);
|
|
14248
|
+
const timeout = readTimeout(context);
|
|
14249
|
+
const resolved = resolveWaitSeams(seams);
|
|
14250
|
+
const { root, record } = pickOrNoSession(context, resolved);
|
|
14251
|
+
const start2 = resolved.clock.mark();
|
|
14252
|
+
const following = { root, record, file: eventsFileOf(root, record), until, timeout, start: start2 };
|
|
14253
|
+
let cursor = { offset: 0, quietMark: start2, noted: false };
|
|
14254
|
+
for (;; ) {
|
|
14255
|
+
const polled = poll(following, cursor, context, resolved);
|
|
14256
|
+
if (polled.found !== null)
|
|
14257
|
+
return resultOf5(polled.found, following, resolved);
|
|
14258
|
+
cursor = polled.cursor;
|
|
14259
|
+
await resolved.sleep(resolved.pollMs);
|
|
14260
|
+
}
|
|
14261
|
+
}
|
|
14262
|
+
function spelled(row2) {
|
|
14263
|
+
return row2.reason === "quiet" ? "quiet:<minutes>" : row2.reason;
|
|
14264
|
+
}
|
|
14265
|
+
function reasonsText() {
|
|
14266
|
+
return WAIT_REASONS.map((row2) => `\`${spelled(row2)}\`, ${row2.matches}, exits ${String(row2.exit)}`).join("; ");
|
|
14267
|
+
}
|
|
14268
|
+
function description() {
|
|
14269
|
+
return "Follows a loop run until a reason `--until` asks for happens, then prints one `rafa\xB7` line naming it" + " and exits with that reason's code. It reads the run's events file, `.rafa/runs/<session-id>.events.ndjson`," + " from its first line, so a run that has already ended still answers, and reads the run's record and" + ` probes its pid every ${String(WAIT_POLL_MS / 1000)} seconds. The reasons: ${reasonsText()}.` + ` \`--timeout\` reached exits ${String(WAIT_TIMEOUT_EXIT)}, and a session it cannot find exits` + ` ${String(WAIT_NO_SESSION_EXIT)}. Without \`--until\` it waits for \`${DEFAULT_WAIT_UNTIL.reasons.join(",")}\`;` + " `blocked` and `quiet` are never in that set. Quiet minutes and the timeout count time the machine is" + " awake, so a suspended laptop never reads as a stuck loop; on macOS the clock keeps counting through" + " sleep, so there a long sleep can raise a false `quiet` or an early timeout. A run with no events file," + " from an older rafa, is noted once, and only `exit`, `quiet` and the timeout can answer for it. Without" + " `--session-id` it waits on the session running on the branch checked out at the project root, or the" + " newest one that ran there. With `--output=json` the reason, the event, the session's state and the" + ` minutes are the data of a \`${WAIT_EVENT}\` event, and of the terminal result when it exits 0.`;
|
|
14270
|
+
}
|
|
14271
|
+
function createLoopWaitCommand(seams = {}) {
|
|
14272
|
+
const command = {
|
|
14273
|
+
name: "loop wait",
|
|
14274
|
+
subject: "loop",
|
|
14275
|
+
action: "wait",
|
|
14276
|
+
summary: "wait on a session until a reason --until names happens",
|
|
14277
|
+
description: description(),
|
|
14278
|
+
args: [],
|
|
14279
|
+
flags: [
|
|
14280
|
+
sessionIdFlag("to wait on"),
|
|
14281
|
+
{
|
|
14282
|
+
name: UNTIL_FLAG,
|
|
14283
|
+
description: `The reasons to wait for, a comma list of ${WAIT_REASONS.map((row2) => spelled(row2)).join(", ")}.` + ` \`${DEFAULT_WAIT_UNTIL.reasons.join(",")}\` when it is left out.`,
|
|
14284
|
+
type: "string"
|
|
14285
|
+
},
|
|
14286
|
+
{
|
|
14287
|
+
name: TIMEOUT_FLAG,
|
|
14288
|
+
description: `How many awake minutes to wait before giving up with exit code ${String(WAIT_TIMEOUT_EXIT)}.` + " No limit when it is left out.",
|
|
14289
|
+
type: "string"
|
|
14290
|
+
}
|
|
14291
|
+
],
|
|
14292
|
+
examples: [
|
|
14293
|
+
{
|
|
14294
|
+
cmd: "rafa loop wait",
|
|
14295
|
+
note: "Waits on the session of the branch checked out here until it opens a PR, ends without one, halts," + " fails or exits."
|
|
14296
|
+
},
|
|
14297
|
+
{
|
|
14298
|
+
cmd: "rafa loop wait --session-id=9185b41c-65f7-4dd6-a0c1-6494c4028f0f --until=blocked,exit,quiet:30",
|
|
14299
|
+
note: "Wakes on a blocked task, the run's end, or 30 awake minutes with no event."
|
|
14300
|
+
},
|
|
14301
|
+
{
|
|
14302
|
+
cmd: "rafa loop wait --timeout=60 --output=json",
|
|
14303
|
+
note: "Waits up to an hour awake, then writes the reason, the event and the session's state as a wait event."
|
|
14304
|
+
}
|
|
14305
|
+
],
|
|
14306
|
+
outputs: ["text", "json"],
|
|
14307
|
+
run: async (context) => {
|
|
14308
|
+
const result2 = await runLoopWait(context, seams);
|
|
14309
|
+
if (context.outputMode === "json") {
|
|
14310
|
+
context.output.emit({
|
|
14311
|
+
type: "event",
|
|
14312
|
+
name: WAIT_EVENT,
|
|
14313
|
+
summary: result2.line.slice(EVENT_PREFIX.length),
|
|
14314
|
+
data: result2,
|
|
14315
|
+
ts: new Date().toISOString()
|
|
14316
|
+
});
|
|
14317
|
+
if (result2.exitCode === 0)
|
|
14318
|
+
context.output.result(result2);
|
|
14319
|
+
else
|
|
14320
|
+
throw new CommandExit(result2.exitCode, result2.line);
|
|
14321
|
+
return;
|
|
14322
|
+
}
|
|
14323
|
+
context.output.info(result2.line);
|
|
14324
|
+
if (result2.exitCode !== 0)
|
|
14325
|
+
throw new CommandExit(result2.exitCode);
|
|
14326
|
+
}
|
|
14327
|
+
};
|
|
14328
|
+
return Object.freeze(command);
|
|
14329
|
+
}
|
|
14330
|
+
var wait_default = createLoopWaitCommand();
|
|
14331
|
+
|
|
13755
14332
|
// src/commands/module/exec.ts
|
|
13756
|
-
var
|
|
14333
|
+
var USAGE31 = "rafa module exec <module> <action> [args] [flags]";
|
|
13757
14334
|
var execCommand = {
|
|
13758
14335
|
name: "module exec",
|
|
13759
14336
|
subject: "module",
|
|
@@ -13782,13 +14359,13 @@ var execCommand = {
|
|
|
13782
14359
|
const mounts = context.registry.mounts();
|
|
13783
14360
|
const mounted = mounts.length === 0 ? "none is mounted; a module is mounted when allowList: names it and modules: gives its path source" : mounts.map((mount) => `${mount.name} (${context.registry.actionsOf(mountKey(mount.name)).map((held) => held.action).join(", ")})`).join(", ");
|
|
13784
14361
|
throw new CommandExit(1, `\u274C Expected a module and one of its actions; mounted: ${mounted}
|
|
13785
|
-
Usage: ${
|
|
14362
|
+
Usage: ${USAGE31}`);
|
|
13786
14363
|
}
|
|
13787
14364
|
};
|
|
13788
14365
|
var exec_default = Object.freeze(execCommand);
|
|
13789
14366
|
|
|
13790
14367
|
// src/commands/module/list.ts
|
|
13791
|
-
var
|
|
14368
|
+
var USAGE32 = "rafa module list";
|
|
13792
14369
|
function sourceText(source) {
|
|
13793
14370
|
const ref8 = source.ref === null ? "" : `@${source.ref}`;
|
|
13794
14371
|
return `${source.kind} ${source.location}${ref8}`;
|
|
@@ -13809,10 +14386,10 @@ function renderModuleList(list) {
|
|
|
13809
14386
|
return ["Modules:", ...list.modules.flatMap(moduleLines)];
|
|
13810
14387
|
}
|
|
13811
14388
|
async function runList6(context, seams) {
|
|
13812
|
-
expectNoArgument(context.args,
|
|
14389
|
+
expectNoArgument(context.args, USAGE32);
|
|
13813
14390
|
const { project } = context;
|
|
13814
14391
|
if (project === null)
|
|
13815
|
-
throw new CommandExit(1, `\u274C ${
|
|
14392
|
+
throw new CommandExit(1, `\u274C ${USAGE32} runs inside a project`);
|
|
13816
14393
|
let loaded;
|
|
13817
14394
|
try {
|
|
13818
14395
|
const resolved = loadConfig({ root: project.root, home: project.home });
|
|
@@ -13903,30 +14480,30 @@ function yesActionsFor(mode) {
|
|
|
13903
14480
|
function bareYesActionsFor(mode) {
|
|
13904
14481
|
return BARE_YES_ACTIONS_BY_MODE[mode];
|
|
13905
14482
|
}
|
|
13906
|
-
function ceilingRefusal(problem2,
|
|
14483
|
+
function ceilingRefusal(problem2, usage) {
|
|
13907
14484
|
return new CommandExit(CEILING_REFUSAL_EXIT, `\u274C ${problem2}
|
|
13908
|
-
Usage: ${
|
|
14485
|
+
Usage: ${usage}`);
|
|
13909
14486
|
}
|
|
13910
|
-
function readCeilingId(word,
|
|
14487
|
+
function readCeilingId(word, usage, mode) {
|
|
13911
14488
|
if (isAlwaysAsked(word)) {
|
|
13912
|
-
throw ceilingRefusal(`--${YES_FLAG} names ${word}, which no list runs unasked: ${ALWAYS_ASKED_WHY[word]}`,
|
|
14489
|
+
throw ceilingRefusal(`--${YES_FLAG} names ${word}, which no list runs unasked: ${ALWAYS_ASKED_WHY[word]}`, usage);
|
|
13913
14490
|
}
|
|
13914
14491
|
const ids = yesActionsFor(mode);
|
|
13915
14492
|
if (mode === "native" && isLabelsOnly(word)) {
|
|
13916
|
-
throw ceilingRefusal(`--${YES_FLAG} names ${word}, which is offered in labels mode only and board.relationships is native:` + ` the tracker clears a blocker when it closes; the ids are ${ids.join(", ")}`,
|
|
14493
|
+
throw ceilingRefusal(`--${YES_FLAG} names ${word}, which is offered in labels mode only and board.relationships is native:` + ` the tracker clears a blocker when it closes; the ids are ${ids.join(", ")}`, usage);
|
|
13917
14494
|
}
|
|
13918
14495
|
const named = ids.find((action) => action === word);
|
|
13919
14496
|
if (named !== undefined)
|
|
13920
14497
|
return named;
|
|
13921
|
-
throw ceilingRefusal(`--${YES_FLAG} names "${word}", which is no step of rafa next;` + ` the ids are ${ids.join(", ")}`,
|
|
14498
|
+
throw ceilingRefusal(`--${YES_FLAG} names "${word}", which is no step of rafa next;` + ` the ids are ${ids.join(", ")}`, usage);
|
|
13922
14499
|
}
|
|
13923
|
-
function readYesCeiling(flags,
|
|
14500
|
+
function readYesCeiling(flags, usage, mode = "labels") {
|
|
13924
14501
|
const value = flags[YES_FLAG];
|
|
13925
14502
|
if (value === undefined || value === false)
|
|
13926
14503
|
return null;
|
|
13927
14504
|
if (value === true)
|
|
13928
14505
|
return bareYesActionsFor(mode);
|
|
13929
|
-
return Object.freeze(value.split(",").map((word) => word.trim()).filter((word) => word !== "").map((word) => readCeilingId(word,
|
|
14506
|
+
return Object.freeze(value.split(",").map((word) => word.trim()).filter((word) => word !== "").map((word) => readCeilingId(word, usage, mode)));
|
|
13930
14507
|
}
|
|
13931
14508
|
function allowedUnasked(action, ceiling) {
|
|
13932
14509
|
if (ceiling === null || ALWAYS_ASKED.has(action))
|
|
@@ -13986,7 +14563,7 @@ function stopLine(stop, state, ceiling, dryRun, roadmap = false) {
|
|
|
13986
14563
|
}
|
|
13987
14564
|
return null;
|
|
13988
14565
|
}
|
|
13989
|
-
function
|
|
14566
|
+
function lineRefusal4(problem2) {
|
|
13990
14567
|
return new CommandExit(1, `\u274C ${problem2}
|
|
13991
14568
|
Usage: ${NEXT_USAGE}`);
|
|
13992
14569
|
}
|
|
@@ -13999,7 +14576,7 @@ function readRoadmap(flags) {
|
|
|
13999
14576
|
function readClaimAhead(flags, roadmap) {
|
|
14000
14577
|
const claimAhead = readBareFlag(flags, CLAIM_AHEAD_FLAG);
|
|
14001
14578
|
if (claimAhead && !roadmap) {
|
|
14002
|
-
throw
|
|
14579
|
+
throw lineRefusal4(`--${CLAIM_AHEAD_FLAG} is passed on to plan create with --${ROADMAP_FLAG}, and this line gives no` + ` --${ROADMAP_FLAG}; write --${ROADMAP_FLAG} --${CLAIM_AHEAD_FLAG}, or drop --${CLAIM_AHEAD_FLAG}`);
|
|
14003
14580
|
}
|
|
14004
14581
|
return claimAhead;
|
|
14005
14582
|
}
|
|
@@ -14011,7 +14588,7 @@ function readBareFlag(flags, name) {
|
|
|
14011
14588
|
return value;
|
|
14012
14589
|
if (value === "true" || value === "false")
|
|
14013
14590
|
return value === "true";
|
|
14014
|
-
throw
|
|
14591
|
+
throw lineRefusal4(`--${name} takes no value, and read "${value}" as one`);
|
|
14015
14592
|
}
|
|
14016
14593
|
function dryRunOf(flag, ceiling, hasTerminal) {
|
|
14017
14594
|
if (flag)
|
|
@@ -14416,7 +14993,7 @@ var create_default2 = endingWith(wrapped2);
|
|
|
14416
14993
|
import { readFileSync as readFileSync16 } from "fs";
|
|
14417
14994
|
import { resolve as resolve10 } from "path";
|
|
14418
14995
|
var COMMAND6 = "rafa plan risk";
|
|
14419
|
-
var
|
|
14996
|
+
var USAGE33 = "rafa plan risk [<plan>] [--strict]";
|
|
14420
14997
|
var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
|
|
14421
14998
|
function spawningRunners(root) {
|
|
14422
14999
|
return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
|
|
@@ -14429,7 +15006,7 @@ function riskPlanPath(root, planDir, typed) {
|
|
|
14429
15006
|
throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
|
|
14430
15007
|
const lookedFor = [...new Set([resolve10(root, planDir, DEFAULT_PLAN_FILE), resolve10(root, DEFAULT_PLAN_FILE)])];
|
|
14431
15008
|
throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
|
|
14432
|
-
Usage: ${
|
|
15009
|
+
Usage: ${USAGE33}`);
|
|
14433
15010
|
}
|
|
14434
15011
|
function riskLine(finding) {
|
|
14435
15012
|
const line = finding.line === undefined ? "" : `:${String(finding.line)}`;
|
|
@@ -14493,7 +15070,7 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
14493
15070
|
outputs: ["text", "json"],
|
|
14494
15071
|
run: async (context) => {
|
|
14495
15072
|
const strict = readSwitch("strict", context.flags["strict"], STRICT_HINT);
|
|
14496
|
-
const typed = expectAtMostOneArgument(context.args,
|
|
15073
|
+
const typed = expectAtMostOneArgument(context.args, USAGE33);
|
|
14497
15074
|
const project = requireProject(context, COMMAND6);
|
|
14498
15075
|
const config = resolveProjectConfig(project, COMMAND6, (message) => {
|
|
14499
15076
|
context.output.warn(message);
|
|
@@ -14526,7 +15103,7 @@ var risk_default = createPlanRiskCommand();
|
|
|
14526
15103
|
// src/commands/plan/show.ts
|
|
14527
15104
|
import { readFileSync as readFileSync17 } from "fs";
|
|
14528
15105
|
import { join as join28 } from "path";
|
|
14529
|
-
var
|
|
15106
|
+
var USAGE34 = "rafa plan show <stub> [--tracker]";
|
|
14530
15107
|
var COMMAND7 = "rafa plan show";
|
|
14531
15108
|
function readTrackerFlag(value) {
|
|
14532
15109
|
if (value === undefined || value === false || value === "false")
|
|
@@ -14535,8 +15112,8 @@ function readTrackerFlag(value) {
|
|
|
14535
15112
|
return true;
|
|
14536
15113
|
throw new CommandExit(1, `\u274C --tracker takes no value, and read "${value}" as one. Type the stub first: rafa plan show <stub> --tracker`);
|
|
14537
15114
|
}
|
|
14538
|
-
function missingText(stub, tracker,
|
|
14539
|
-
return tracker ? `\u274C No tracker for plan "${stub}": ${
|
|
15115
|
+
function missingText(stub, tracker, relative8) {
|
|
15116
|
+
return tracker ? `\u274C No tracker for plan "${stub}": ${relative8} does not exist until rafa loop start first runs the plan.` : `\u274C No plan "${stub}": ${relative8} does not exist. rafa plan list names the plans there are.`;
|
|
14540
15117
|
}
|
|
14541
15118
|
function showPlan(plans, stub, tracker) {
|
|
14542
15119
|
if (!isStampableStub(stub)) {
|
|
@@ -14569,9 +15146,9 @@ function taskLine(task) {
|
|
|
14569
15146
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
14570
15147
|
}
|
|
14571
15148
|
function renderShownPlan(shown, dirLabel) {
|
|
14572
|
-
const
|
|
15149
|
+
const relative8 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
14573
15150
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
14574
|
-
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${
|
|
15151
|
+
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative8}`];
|
|
14575
15152
|
if (shown.header.issue !== null)
|
|
14576
15153
|
lines.push(`Issue: ${shown.header.issue}`);
|
|
14577
15154
|
if (shown.header.spec !== null)
|
|
@@ -14584,7 +15161,7 @@ function renderShownPlan(shown, dirLabel) {
|
|
|
14584
15161
|
lines.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
|
|
14585
15162
|
}
|
|
14586
15163
|
if (shown.issues.length > 0) {
|
|
14587
|
-
lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(
|
|
15164
|
+
lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative8, issue)}`));
|
|
14588
15165
|
}
|
|
14589
15166
|
return lines;
|
|
14590
15167
|
}
|
|
@@ -14622,7 +15199,7 @@ var planShowCommand = {
|
|
|
14622
15199
|
outputs: ["text", "json"],
|
|
14623
15200
|
run: async (context) => {
|
|
14624
15201
|
const tracker = readTrackerFlag(context.flags.tracker);
|
|
14625
|
-
const stub = expectOneArgument(context.args,
|
|
15202
|
+
const stub = expectOneArgument(context.args, USAGE34);
|
|
14626
15203
|
const plans = resolvePlansDir(requireProject(context, COMMAND7), COMMAND7, (message) => {
|
|
14627
15204
|
context.output.warn(message);
|
|
14628
15205
|
});
|
|
@@ -14650,28 +15227,28 @@ var PR_USAGE = Object.freeze({
|
|
|
14650
15227
|
});
|
|
14651
15228
|
var PULL_NUMBER = /^[1-9]\d*$/;
|
|
14652
15229
|
var DETACHED_HEAD = "HEAD";
|
|
14653
|
-
function
|
|
15230
|
+
function lineRefusal5(problem2, usage) {
|
|
14654
15231
|
return new CommandExit(1, `\u274C ${problem2}
|
|
14655
|
-
Usage: ${
|
|
15232
|
+
Usage: ${usage}`);
|
|
14656
15233
|
}
|
|
14657
|
-
function expectNoArguments(args,
|
|
15234
|
+
function expectNoArguments(args, usage) {
|
|
14658
15235
|
if (args.length === 0)
|
|
14659
15236
|
return;
|
|
14660
|
-
throw
|
|
15237
|
+
throw lineRefusal5(`Expected no arguments, got ${args.length}: ${args.join(" ")}`, usage);
|
|
14661
15238
|
}
|
|
14662
|
-
function readPullArgument(args,
|
|
15239
|
+
function readPullArgument(args, usage) {
|
|
14663
15240
|
if (args.length === 0)
|
|
14664
15241
|
return null;
|
|
14665
15242
|
if (args.length > 1) {
|
|
14666
|
-
throw
|
|
15243
|
+
throw lineRefusal5(`Expected at most one pull request number, got ${args.length}: ${args.join(" ")}`, usage);
|
|
14667
15244
|
}
|
|
14668
15245
|
const word = args[0] ?? "";
|
|
14669
15246
|
if (!PULL_NUMBER.test(word)) {
|
|
14670
|
-
throw
|
|
15247
|
+
throw lineRefusal5(`"${word}" is no pull request number, which is a whole number from 1`, usage);
|
|
14671
15248
|
}
|
|
14672
15249
|
return Number(word);
|
|
14673
15250
|
}
|
|
14674
|
-
function readBooleanFlag(flags, name,
|
|
15251
|
+
function readBooleanFlag(flags, name, usage, whenAbsent = false) {
|
|
14675
15252
|
const value = flags[name];
|
|
14676
15253
|
if (value === undefined)
|
|
14677
15254
|
return whenAbsent;
|
|
@@ -14679,7 +15256,7 @@ function readBooleanFlag(flags, name, usage2, whenAbsent = false) {
|
|
|
14679
15256
|
return value;
|
|
14680
15257
|
if (value === "true" || value === "false")
|
|
14681
15258
|
return value === "true";
|
|
14682
|
-
throw
|
|
15259
|
+
throw lineRefusal5(`--${name} takes no value, and read "${value}" as one; type the pull request number before the flags`, usage);
|
|
14683
15260
|
}
|
|
14684
15261
|
var DEFAULT_PR_SEAMS = Object.freeze({});
|
|
14685
15262
|
function projectOf8(context) {
|
|
@@ -14727,6 +15304,7 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
|
|
|
14727
15304
|
roadmapIssue: config.roadmapIssue,
|
|
14728
15305
|
relationships: config.boardRelationships,
|
|
14729
15306
|
planDir: config.planDir,
|
|
15307
|
+
worktreeDir: config.loopWorktreeDir,
|
|
14730
15308
|
readBranch: () => readBranch2(project.root),
|
|
14731
15309
|
versionGuard: mergeGuardSettings(config)
|
|
14732
15310
|
};
|
|
@@ -14738,33 +15316,33 @@ async function onProvider(doing, call) {
|
|
|
14738
15316
|
throw new CommandExit(1, `\u274C Could not ${doing}: ${messageOf(error)}`);
|
|
14739
15317
|
}
|
|
14740
15318
|
}
|
|
14741
|
-
function branchOf(pr,
|
|
15319
|
+
function branchOf(pr, usage) {
|
|
14742
15320
|
let branch;
|
|
14743
15321
|
try {
|
|
14744
15322
|
branch = pr.readBranch();
|
|
14745
15323
|
} catch (error) {
|
|
14746
|
-
throw
|
|
15324
|
+
throw lineRefusal5(`The branch checked out at ${pr.project.root} cannot be read: ${messageOf(error)}`, usage);
|
|
14747
15325
|
}
|
|
14748
15326
|
if (branch === DETACHED_HEAD || branch === "") {
|
|
14749
|
-
throw
|
|
15327
|
+
throw lineRefusal5(`${pr.project.root} is on no branch, and a detached HEAD names no pull request`, usage);
|
|
14750
15328
|
}
|
|
14751
15329
|
return branch;
|
|
14752
15330
|
}
|
|
14753
|
-
async function pickPullRequest(pr, asked,
|
|
15331
|
+
async function pickPullRequest(pr, asked, usage) {
|
|
14754
15332
|
if (asked !== null) {
|
|
14755
15333
|
return { number: asked, source: "argument", branch: null, summary: null };
|
|
14756
15334
|
}
|
|
14757
|
-
const branch = branchOf(pr,
|
|
15335
|
+
const branch = branchOf(pr, usage);
|
|
14758
15336
|
const summary = await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
14759
15337
|
if (summary === null) {
|
|
14760
|
-
throw
|
|
14761
|
-
Run rafa pr list to see the open pull requests.`,
|
|
15338
|
+
throw lineRefusal5(`No open pull request for the branch "${branch}" at ${pr.project.root}` + `
|
|
15339
|
+
Run rafa pr list to see the open pull requests.`, usage);
|
|
14762
15340
|
}
|
|
14763
15341
|
return { number: summary.number, source: "branch", branch, summary };
|
|
14764
15342
|
}
|
|
14765
15343
|
|
|
14766
15344
|
// src/commands/pr/current.ts
|
|
14767
|
-
var
|
|
15345
|
+
var USAGE35 = PR_USAGE.current;
|
|
14768
15346
|
var SEPARATOR2 = " \u2014 ";
|
|
14769
15347
|
function trimmedOrNull(value) {
|
|
14770
15348
|
const trimmed = value.trim();
|
|
@@ -14795,9 +15373,9 @@ async function probeChecks(pulls, number) {
|
|
|
14795
15373
|
}
|
|
14796
15374
|
}
|
|
14797
15375
|
async function readCurrent(context, seams) {
|
|
14798
|
-
expectNoArguments(context.args,
|
|
15376
|
+
expectNoArguments(context.args, USAGE35);
|
|
14799
15377
|
const pr = openPrContext(context, seams);
|
|
14800
|
-
const pick = await pickPullRequest(pr, null,
|
|
15378
|
+
const pick = await pickPullRequest(pr, null, USAGE35);
|
|
14801
15379
|
const summary = pick.summary;
|
|
14802
15380
|
const probe = await probeChecks(pr.pulls, pick.number);
|
|
14803
15381
|
const reading = {
|
|
@@ -14962,7 +15540,7 @@ function forecastLine(number, forecast) {
|
|
|
14962
15540
|
}
|
|
14963
15541
|
|
|
14964
15542
|
// src/commands/pr/list.ts
|
|
14965
|
-
var
|
|
15543
|
+
var USAGE36 = PR_USAGE.list;
|
|
14966
15544
|
var UNREADABLE = "?";
|
|
14967
15545
|
var PROBE_LIMIT = 6;
|
|
14968
15546
|
var TITLE_WIDTH = 60;
|
|
@@ -15050,7 +15628,7 @@ async function probe(call) {
|
|
|
15050
15628
|
return { value: null, problem: messageOf(error) };
|
|
15051
15629
|
}
|
|
15052
15630
|
}
|
|
15053
|
-
async function
|
|
15631
|
+
async function rowOf3(pulls, pull2, readBaseOf) {
|
|
15054
15632
|
const [checks, detail] = await Promise.all([
|
|
15055
15633
|
probe(() => pulls.checks(pull2.number)),
|
|
15056
15634
|
probe(() => pulls.get(pull2.number))
|
|
@@ -15066,12 +15644,12 @@ async function rowOf2(pulls, pull2, readBaseOf) {
|
|
|
15066
15644
|
};
|
|
15067
15645
|
}
|
|
15068
15646
|
async function readList(context, seams, now) {
|
|
15069
|
-
expectNoArguments(context.args,
|
|
15647
|
+
expectNoArguments(context.args, USAGE36);
|
|
15070
15648
|
const pr = openPrContext(context, seams);
|
|
15071
15649
|
const open = await onProvider("list the open pull requests", () => pr.pulls.list());
|
|
15072
15650
|
const root = pr.project.root;
|
|
15073
15651
|
const readBaseOf = resolveReleaseEnabled(pr.versionGuard, root).enabled ? createBaseReader((seams.git ?? createGitRunner)(root), pr.versionGuard) : null;
|
|
15074
|
-
const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) =>
|
|
15652
|
+
const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf3(pr.pulls, pull2, readBaseOf));
|
|
15075
15653
|
return { rows, text: renderList(rows, now) };
|
|
15076
15654
|
}
|
|
15077
15655
|
function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
|
|
@@ -15216,6 +15794,183 @@ async function guardBeforeMerge(step) {
|
|
|
15216
15794
|
return { report: report3, go: true };
|
|
15217
15795
|
}
|
|
15218
15796
|
|
|
15797
|
+
// src/commands/pr/merge-loop-worktree.ts
|
|
15798
|
+
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync5, realpathSync as realpathSync3, statSync as statSync8 } from "fs";
|
|
15799
|
+
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join29, relative as relative8, resolve as resolve11, sep as sep4 } from "path";
|
|
15800
|
+
var REFUSAL_PREFIX = "rafa pr merge refuses";
|
|
15801
|
+
var INDENT4 = " ";
|
|
15802
|
+
var LIVE_STATES = new Set(["running", "paused"]);
|
|
15803
|
+
function defaultLoopWorktreeSeams(mainCheckout) {
|
|
15804
|
+
return {
|
|
15805
|
+
git: createGitRunner(mainCheckout),
|
|
15806
|
+
gitAt: createGitRunner,
|
|
15807
|
+
modifiedAt: (path) => statSync8(path, { throwIfNoEntry: false })?.mtime ?? null,
|
|
15808
|
+
copyFile: (from, to) => {
|
|
15809
|
+
mkdirSync5(dirname6(to), { recursive: true });
|
|
15810
|
+
copyFileSync3(from, to);
|
|
15811
|
+
},
|
|
15812
|
+
realPath: (path) => {
|
|
15813
|
+
try {
|
|
15814
|
+
return realpathSync3(path);
|
|
15815
|
+
} catch {
|
|
15816
|
+
return path;
|
|
15817
|
+
}
|
|
15818
|
+
}
|
|
15819
|
+
};
|
|
15820
|
+
}
|
|
15821
|
+
function readLoopHolder(seams, reading) {
|
|
15822
|
+
const holder = findLoopHolder(seams, reading);
|
|
15823
|
+
if (holder === null)
|
|
15824
|
+
return { kind: "none" };
|
|
15825
|
+
const reasons = [...liveReasons(reading, holder.path), ...dirtyReasons(seams.gitAt(holder.path))];
|
|
15826
|
+
if (reasons.length === 0)
|
|
15827
|
+
return { kind: "freeable", ...holder };
|
|
15828
|
+
return {
|
|
15829
|
+
kind: "refused",
|
|
15830
|
+
path: holder.path,
|
|
15831
|
+
message: [
|
|
15832
|
+
`${REFUSAL_PREFIX}: branch ${reading.branch} is checked out in the loop worktree ${holder.path}, which cannot be freed:`,
|
|
15833
|
+
...reasons.map((reason) => `${INDENT4}${reason}`),
|
|
15834
|
+
"Once none of that holds, run rafa pr merge again."
|
|
15835
|
+
].join(`
|
|
15836
|
+
`)
|
|
15837
|
+
};
|
|
15838
|
+
}
|
|
15839
|
+
function freeLoopWorktree(seams, reading, holder) {
|
|
15840
|
+
const into = resolve11(reading.mainCheckout, reading.planDir);
|
|
15841
|
+
const files = [];
|
|
15842
|
+
for (const name of planFileNames(holder.stub)) {
|
|
15843
|
+
const from = resolve11(holder.path, reading.planDir, name);
|
|
15844
|
+
const to = join29(into, name);
|
|
15845
|
+
try {
|
|
15846
|
+
files.push({ name, from, to, outcome: copyPlanFile(seams, from, to) });
|
|
15847
|
+
} catch (error) {
|
|
15848
|
+
return {
|
|
15849
|
+
ok: false,
|
|
15850
|
+
message: [
|
|
15851
|
+
`${REFUSAL_PREFIX}: ${name} could not be copied into ${into}: ${messageOf(error)}`,
|
|
15852
|
+
`${INDENT4}${filesClause(files) ?? "nothing was copied"}; the worktree was left in place, and nothing was merged.`
|
|
15853
|
+
].join(`
|
|
15854
|
+
`)
|
|
15855
|
+
};
|
|
15856
|
+
}
|
|
15857
|
+
}
|
|
15858
|
+
const removed = seams.git(["worktree", "remove", holder.path]);
|
|
15859
|
+
const copies = filesClause(files);
|
|
15860
|
+
if (!removed.ok) {
|
|
15861
|
+
return {
|
|
15862
|
+
ok: false,
|
|
15863
|
+
message: [
|
|
15864
|
+
`${REFUSAL_PREFIX}: the loop worktree ${holder.path} holding ${reading.branch} could not be removed, so nothing was merged.`,
|
|
15865
|
+
...quotedLines2(gitSaid(removed)),
|
|
15866
|
+
`${INDENT4}${copies ?? "no plan file was there to copy"} into ${into}.`,
|
|
15867
|
+
`Run git worktree remove ${shellQuote(holder.path)} yourself, then rafa pr merge again.`
|
|
15868
|
+
].join(`
|
|
15869
|
+
`)
|
|
15870
|
+
};
|
|
15871
|
+
}
|
|
15872
|
+
return {
|
|
15873
|
+
ok: true,
|
|
15874
|
+
files,
|
|
15875
|
+
line: `Freed the ended loop worktree ${holder.path} holding ${reading.branch}: ` + `${copies ?? "no plan file to copy"} into ${into}, then the worktree removed.`
|
|
15876
|
+
};
|
|
15877
|
+
}
|
|
15878
|
+
function freeLoopHolder(seams, reading) {
|
|
15879
|
+
const answer2 = readLoopHolder(seams, reading);
|
|
15880
|
+
if (answer2.kind === "none")
|
|
15881
|
+
return answer2;
|
|
15882
|
+
if (answer2.kind === "refused")
|
|
15883
|
+
return { kind: "refused", message: answer2.message };
|
|
15884
|
+
const freed = freeLoopWorktree(seams, reading, answer2);
|
|
15885
|
+
return freed.ok ? { kind: "freed", line: freed.line } : { kind: "refused", message: freed.message };
|
|
15886
|
+
}
|
|
15887
|
+
function findLoopHolder(seams, reading) {
|
|
15888
|
+
const holders = worktreesHolding(reading.worktrees, reading.branch, reading.mainCheckout);
|
|
15889
|
+
const holder = holders[0];
|
|
15890
|
+
if (holders.length !== 1 || holder === undefined)
|
|
15891
|
+
return null;
|
|
15892
|
+
const stub = loopStubOf(seams, reading, holder.path);
|
|
15893
|
+
return stub === null ? null : { path: holder.path, stub };
|
|
15894
|
+
}
|
|
15895
|
+
function freeLoopHolderBeforeMerge(seams, place2, sessions) {
|
|
15896
|
+
const holder = findLoopHolder(seams, place2);
|
|
15897
|
+
if (holder === null)
|
|
15898
|
+
return { kind: "none" };
|
|
15899
|
+
let liveLoops;
|
|
15900
|
+
try {
|
|
15901
|
+
liveLoops = readSessions(sessions.root, { isAlive: sessions.isAlive });
|
|
15902
|
+
} catch (error) {
|
|
15903
|
+
return {
|
|
15904
|
+
kind: "refused",
|
|
15905
|
+
message: `${REFUSAL_PREFIX}: branch ${place2.branch} is checked out in the loop worktree ${holder.path}, ` + `and whether a loop still runs in it could not be read: ${messageOf(error)}`
|
|
15906
|
+
};
|
|
15907
|
+
}
|
|
15908
|
+
return freeLoopHolder(seams, { ...place2, liveLoops });
|
|
15909
|
+
}
|
|
15910
|
+
function planFileNames(stub) {
|
|
15911
|
+
return [`CLOSEOUT-${stub}.md`, `PLAN_TRACKER-${stub}.md`];
|
|
15912
|
+
}
|
|
15913
|
+
function loopStubOf(seams, reading, path) {
|
|
15914
|
+
const dir = worktreeDirAt(reading.mainCheckout, reading.worktreeDir);
|
|
15915
|
+
const forms = [...new Set([dir, seams.realPath(dir)])];
|
|
15916
|
+
for (const form of forms) {
|
|
15917
|
+
const below = relative8(form, path);
|
|
15918
|
+
if (below !== "" && !below.startsWith("..") && !isAbsolute6(below) && !below.includes(sep4))
|
|
15919
|
+
return below;
|
|
15920
|
+
}
|
|
15921
|
+
return null;
|
|
15922
|
+
}
|
|
15923
|
+
function liveReasons(reading, path) {
|
|
15924
|
+
return reading.liveLoops.filter((loop) => LIVE_STATES.has(loop.state)).filter((loop) => loop.branch === reading.branch || loop.worktree === path).map((loop) => `loop session ${loop.sessionId} (pid ${String(loop.pid)}) is ${loop.state} in it`);
|
|
15925
|
+
}
|
|
15926
|
+
function dirtyReasons(git) {
|
|
15927
|
+
const status = git(WORKTREE_STATUS);
|
|
15928
|
+
if (!status.ok)
|
|
15929
|
+
return [`its changes could not be read: ${failure("git status", status)}`];
|
|
15930
|
+
const { changed, untracked } = countChanges(status.stdout);
|
|
15931
|
+
const parts = [
|
|
15932
|
+
...changed > 0 ? [plural2(changed, "uncommitted change")] : [],
|
|
15933
|
+
...untracked > 0 ? [plural2(untracked, "untracked file")] : []
|
|
15934
|
+
];
|
|
15935
|
+
return parts.length === 0 ? [] : [parts.join(", ")];
|
|
15936
|
+
}
|
|
15937
|
+
function copyPlanFile(seams, from, to) {
|
|
15938
|
+
if (from === to)
|
|
15939
|
+
return "in-place";
|
|
15940
|
+
const source = seams.modifiedAt(from);
|
|
15941
|
+
if (source === null)
|
|
15942
|
+
return "absent";
|
|
15943
|
+
const destination = seams.modifiedAt(to);
|
|
15944
|
+
if (destination !== null && destination.getTime() > source.getTime())
|
|
15945
|
+
return "kept";
|
|
15946
|
+
seams.copyFile(from, to);
|
|
15947
|
+
return "copied";
|
|
15948
|
+
}
|
|
15949
|
+
function filesClause(files) {
|
|
15950
|
+
if (files.length === 0)
|
|
15951
|
+
return null;
|
|
15952
|
+
return files.map((file) => `${file.name} ${outcomeWords(file.outcome)}`).join(" and ");
|
|
15953
|
+
}
|
|
15954
|
+
function outcomeWords(outcome) {
|
|
15955
|
+
if (outcome === "kept")
|
|
15956
|
+
return "kept (the main checkout's is newer)";
|
|
15957
|
+
if (outcome === "in-place")
|
|
15958
|
+
return "already in place";
|
|
15959
|
+
return outcome;
|
|
15960
|
+
}
|
|
15961
|
+
function quotedLines2(said) {
|
|
15962
|
+
return said === "" ? [] : said.split(`
|
|
15963
|
+
`).map((line) => `${INDENT4}${line}`);
|
|
15964
|
+
}
|
|
15965
|
+
function failure(what, result2) {
|
|
15966
|
+
const said = gitSaid(result2).split(`
|
|
15967
|
+
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
15968
|
+
return said === "" ? `${what} failed` : `${what} failed: ${said}`;
|
|
15969
|
+
}
|
|
15970
|
+
function plural2(count, noun) {
|
|
15971
|
+
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
15972
|
+
}
|
|
15973
|
+
|
|
15219
15974
|
// src/pr/unchecked.ts
|
|
15220
15975
|
var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
|
|
15221
15976
|
var WORKFLOWS_EXIST_WARNING = "CI may not have started (a path filter, a draft, Actions disabled, or it has not registered yet); this is probably not what you want";
|
|
@@ -15288,7 +16043,7 @@ function workflowNamesBase(workflowText, base) {
|
|
|
15288
16043
|
return readWorkflow(document, base) !== false;
|
|
15289
16044
|
}
|
|
15290
16045
|
function readWorkflow(document, base) {
|
|
15291
|
-
if (!
|
|
16046
|
+
if (!isPlainObject2(document) || !("on" in document))
|
|
15292
16047
|
return UNKNOWN;
|
|
15293
16048
|
const on = document.on;
|
|
15294
16049
|
if (typeof on === "string")
|
|
@@ -15298,7 +16053,7 @@ function readWorkflow(document, base) {
|
|
|
15298
16053
|
return UNKNOWN;
|
|
15299
16054
|
return on.includes(PULL_REQUEST_EVENT);
|
|
15300
16055
|
}
|
|
15301
|
-
if (!
|
|
16056
|
+
if (!isPlainObject2(on))
|
|
15302
16057
|
return UNKNOWN;
|
|
15303
16058
|
if (!(PULL_REQUEST_EVENT in on))
|
|
15304
16059
|
return false;
|
|
@@ -15307,7 +16062,7 @@ function readWorkflow(document, base) {
|
|
|
15307
16062
|
function readPullRequestTrigger(trigger, base) {
|
|
15308
16063
|
if (trigger === null || trigger === undefined)
|
|
15309
16064
|
return true;
|
|
15310
|
-
if (!
|
|
16065
|
+
if (!isPlainObject2(trigger))
|
|
15311
16066
|
return UNKNOWN;
|
|
15312
16067
|
const branches = trigger.branches;
|
|
15313
16068
|
const ignored = trigger["branches-ignore"];
|
|
@@ -15408,12 +16163,12 @@ function regexSourceOf(pattern) {
|
|
|
15408
16163
|
function escapeRegex(char) {
|
|
15409
16164
|
return char.replace(REGEX_SPECIAL, "\\$&");
|
|
15410
16165
|
}
|
|
15411
|
-
function
|
|
16166
|
+
function isPlainObject2(value) {
|
|
15412
16167
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15413
16168
|
}
|
|
15414
16169
|
|
|
15415
16170
|
// src/commands/pr/merge-unchecked.ts
|
|
15416
|
-
var
|
|
16171
|
+
var INDENT5 = " ";
|
|
15417
16172
|
var YES_ANSWERS3 = ["y", "yes"];
|
|
15418
16173
|
var WORKFLOWS_DIR = ".github/workflows/";
|
|
15419
16174
|
var WORKFLOW_ENDINGS = [".yml", ".yaml"];
|
|
@@ -15421,8 +16176,8 @@ var LS_TREE_ENTRY = /^\d+ (\w+) ([0-9a-f]+)\t(.+)$/s;
|
|
|
15421
16176
|
function refusal10(head, reading, summary, tail) {
|
|
15422
16177
|
return new CommandExit(1, [
|
|
15423
16178
|
`\u274C ${head}`,
|
|
15424
|
-
...reading.warning.map((line) => `${
|
|
15425
|
-
`${
|
|
16179
|
+
...reading.warning.map((line) => `${INDENT5}${line}`),
|
|
16180
|
+
`${INDENT5}${summary}`,
|
|
15426
16181
|
tail
|
|
15427
16182
|
].join(`
|
|
15428
16183
|
`));
|
|
@@ -15493,7 +16248,7 @@ async function confirmUncheckedMerge(unchecked, options) {
|
|
|
15493
16248
|
}
|
|
15494
16249
|
function commentProblemLine(number, problem2, body) {
|
|
15495
16250
|
const quoted = body.split(`
|
|
15496
|
-
`).map((line) => `${
|
|
16251
|
+
`).map((line) => `${INDENT5}${line}`).join(`
|
|
15497
16252
|
`);
|
|
15498
16253
|
return `the unchecked-merge comment was not posted on #${number}: ${problem2}. Post it yourself:
|
|
15499
16254
|
${quoted}`;
|
|
@@ -15509,21 +16264,21 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
|
|
|
15509
16264
|
}
|
|
15510
16265
|
|
|
15511
16266
|
// src/commands/pr/merge.ts
|
|
15512
|
-
var
|
|
16267
|
+
var USAGE37 = PR_USAGE.merge;
|
|
15513
16268
|
var YES_ANSWERS4 = ["y", "yes"];
|
|
15514
16269
|
var DEFAULT_MERGE_SEAMS = Object.freeze({});
|
|
15515
16270
|
function refusal11(lines) {
|
|
15516
16271
|
return new CommandExit(1, lines.join(`
|
|
15517
16272
|
`));
|
|
15518
16273
|
}
|
|
15519
|
-
function readMethodFlag(flags,
|
|
16274
|
+
function readMethodFlag(flags, usage) {
|
|
15520
16275
|
const value = flags["method"];
|
|
15521
16276
|
if (value === undefined)
|
|
15522
16277
|
return null;
|
|
15523
16278
|
if (isMergeMethod(value))
|
|
15524
16279
|
return value;
|
|
15525
|
-
const
|
|
15526
|
-
throw
|
|
16280
|
+
const spelled2 = typeof value === "boolean" ? "--method with no value" : `"${value}"`;
|
|
16281
|
+
throw lineRefusal5(`${spelled2} is no merge method; one of: ${MERGE_METHODS.join(", ")}`, usage);
|
|
15527
16282
|
}
|
|
15528
16283
|
function gitOrRefuse(git, args, doing) {
|
|
15529
16284
|
const result2 = git(args);
|
|
@@ -15536,10 +16291,20 @@ function summaryLine2(detail, method) {
|
|
|
15536
16291
|
const head = title === "" ? `#${detail.number}` : `#${detail.number} ${title}`;
|
|
15537
16292
|
return `${head} \u2014 ${detail.headRefName} \u2192 ${detail.baseRefName} \u2014 ${method}`;
|
|
15538
16293
|
}
|
|
15539
|
-
function refuseFromGit(
|
|
16294
|
+
function refuseFromGit(merge, detail, checks, skipChecks) {
|
|
16295
|
+
const { git, pr, seams } = merge;
|
|
15540
16296
|
const tree = parseWorkingTree(gitOrRefuse(git, ["status", "--porcelain"], "read the working tree"));
|
|
15541
|
-
const
|
|
16297
|
+
const listed2 = () => parseWorktrees(gitOrRefuse(git, ["worktree", "list", "--porcelain"], "list the worktrees"));
|
|
16298
|
+
const held = listed2();
|
|
15542
16299
|
const at = gitOrRefuse(git, ["rev-parse", "--show-toplevel"], "read the repository root").trim();
|
|
16300
|
+
const place2 = { worktrees: held, branch: detail.headRefName, mainCheckout: at, worktreeDir: pr.worktreeDir, planDir: pr.planDir };
|
|
16301
|
+
const loopSeams = { ...defaultLoopWorktreeSeams(at), git, gitAt: seams.git ?? createGitRunner };
|
|
16302
|
+
const freed = freeLoopHolderBeforeMerge(loopSeams, place2, { root: pr.project.root, isAlive: seams.isAlive });
|
|
16303
|
+
if (freed.kind === "refused")
|
|
16304
|
+
throw refusal11([`\u274C ${freed.message}`]);
|
|
16305
|
+
if (freed.kind === "freed")
|
|
16306
|
+
merge.info(freed.line);
|
|
16307
|
+
const worktrees = freed.kind === "freed" ? listed2() : held;
|
|
15543
16308
|
const found = readMergeRefusal({
|
|
15544
16309
|
number: detail.number,
|
|
15545
16310
|
branch: detail.headRefName,
|
|
@@ -15683,17 +16448,17 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
|
|
|
15683
16448
|
return posted.url;
|
|
15684
16449
|
}
|
|
15685
16450
|
async function runMerge2(context, seams) {
|
|
15686
|
-
const yes = readBooleanFlag(context.flags, "yes",
|
|
15687
|
-
const skipChecks = readBooleanFlag(context.flags, "skip-checks",
|
|
15688
|
-
const wanted = readMethodFlag(context.flags,
|
|
15689
|
-
const asked = readPullArgument(context.args,
|
|
16451
|
+
const yes = readBooleanFlag(context.flags, "yes", USAGE37);
|
|
16452
|
+
const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE37);
|
|
16453
|
+
const wanted = readMethodFlag(context.flags, USAGE37);
|
|
16454
|
+
const asked = readPullArgument(context.args, USAGE37);
|
|
15690
16455
|
const pr = openPrContext(context, seams);
|
|
15691
16456
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
15692
16457
|
const method = wanted ?? pr.mergeMethod;
|
|
15693
|
-
const pick = await pickPullRequest(pr, asked,
|
|
16458
|
+
const pick = await pickPullRequest(pr, asked, USAGE37);
|
|
15694
16459
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
15695
16460
|
if (detail === null) {
|
|
15696
|
-
throw
|
|
16461
|
+
throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`, USAGE37);
|
|
15697
16462
|
}
|
|
15698
16463
|
const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
|
|
15699
16464
|
recordPlanCi({
|
|
@@ -15705,7 +16470,7 @@ async function runMerge2(context, seams) {
|
|
|
15705
16470
|
context.output.warn(message);
|
|
15706
16471
|
}
|
|
15707
16472
|
});
|
|
15708
|
-
refuseFromGit(git, detail, checks, skipChecks);
|
|
16473
|
+
refuseFromGit({ git, pr, seams, info: (message) => context.output.info(message) }, detail, checks, skipChecks);
|
|
15709
16474
|
const guard = await guardBeforeMerge({
|
|
15710
16475
|
pr,
|
|
15711
16476
|
git,
|
|
@@ -15785,7 +16550,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15785
16550
|
subject: "pr",
|
|
15786
16551
|
action: "merge",
|
|
15787
16552
|
summary: "merge a pull request and clean up both branches",
|
|
15788
|
-
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
16553
|
+
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree, except a" + " clean, ended loop worktree under `loop.worktreeDir`, which it removes after copying its two plan files out. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15789
16554
|
args: [
|
|
15790
16555
|
{
|
|
15791
16556
|
name: "n",
|
|
@@ -16279,9 +17044,22 @@ function readLastTriage(comments) {
|
|
|
16279
17044
|
}
|
|
16280
17045
|
|
|
16281
17046
|
// src/commands/pr/show.ts
|
|
16282
|
-
var
|
|
16283
|
-
var
|
|
17047
|
+
var USAGE38 = PR_USAGE.show;
|
|
17048
|
+
var INDENT6 = " ";
|
|
16284
17049
|
var SHORT_SHA = 7;
|
|
17050
|
+
var NOT_IN_BODY = "not named by the body";
|
|
17051
|
+
var KEYWORD_ONLY = "keyword only, not linked";
|
|
17052
|
+
var OTHER_REPOSITORY = "another repository";
|
|
17053
|
+
var PULL_URL_REPOSITORY = /^https?:\/\/[^/]+\/([^/]+\/[^/]+)\/pull\/\d+\/?$/u;
|
|
17054
|
+
var PR_SHOW_JSON_FIELDS = Object.freeze([
|
|
17055
|
+
"detail",
|
|
17056
|
+
"bodyCloses",
|
|
17057
|
+
"checks",
|
|
17058
|
+
"checksProblem",
|
|
17059
|
+
"triage",
|
|
17060
|
+
"triageProblem",
|
|
17061
|
+
"text"
|
|
17062
|
+
]);
|
|
16285
17063
|
function headLine3(detail) {
|
|
16286
17064
|
const title = detail.title.trim();
|
|
16287
17065
|
return title === "" ? `#${detail.number}` : `#${detail.number} ${title}`;
|
|
@@ -16301,6 +17079,25 @@ function factsLine(detail) {
|
|
|
16301
17079
|
mergeWord(detail)
|
|
16302
17080
|
].join(SEPARATOR2);
|
|
16303
17081
|
}
|
|
17082
|
+
function repositoryOf(detail) {
|
|
17083
|
+
return PULL_URL_REPOSITORY.exec(detail.url.trim())?.[1] ?? null;
|
|
17084
|
+
}
|
|
17085
|
+
function linkedWord(issue, own, named3) {
|
|
17086
|
+
if (own !== null && issue.repository !== own) {
|
|
17087
|
+
return `${issue.repository}#${issue.number} (${OTHER_REPOSITORY})`;
|
|
17088
|
+
}
|
|
17089
|
+
return named3.has(issue.number) ? `#${issue.number}` : `#${issue.number} (${NOT_IN_BODY})`;
|
|
17090
|
+
}
|
|
17091
|
+
function closesLine(reading) {
|
|
17092
|
+
const own = repositoryOf(reading.detail);
|
|
17093
|
+
const named3 = new Set(reading.bodyCloses);
|
|
17094
|
+
const linked = new Set(reading.detail.closes.filter((issue) => own === null || issue.repository === own).map((issue) => issue.number));
|
|
17095
|
+
const words = [
|
|
17096
|
+
...reading.detail.closes.map((issue) => linkedWord(issue, own, named3)),
|
|
17097
|
+
...[...named3].filter((number) => !linked.has(number)).map((number) => `#${number} (${KEYWORD_ONLY})`)
|
|
17098
|
+
];
|
|
17099
|
+
return words.length === 0 ? "closes none" : `closes ${words.join(", ")}`;
|
|
17100
|
+
}
|
|
16304
17101
|
function checksLines(reading) {
|
|
16305
17102
|
if (reading.checks === null) {
|
|
16306
17103
|
return [`checks could not be read${SEPARATOR2}${reading.checksProblem ?? "the provider gave no reason"}`];
|
|
@@ -16338,8 +17135,8 @@ function foundTriageLines(triage, headRefOid) {
|
|
|
16338
17135
|
].filter((word) => word !== null);
|
|
16339
17136
|
return [
|
|
16340
17137
|
words.join(SEPARATOR2),
|
|
16341
|
-
`${
|
|
16342
|
-
...triage.problems.map((problem2) => `${
|
|
17138
|
+
`${INDENT6}${triage.url}${SEPARATOR2}by ${triage.author}`,
|
|
17139
|
+
...triage.problems.map((problem2) => `${INDENT6}${problem2}`)
|
|
16343
17140
|
];
|
|
16344
17141
|
}
|
|
16345
17142
|
function triageLines(reading) {
|
|
@@ -16356,7 +17153,8 @@ function renderShow(reading) {
|
|
|
16356
17153
|
const head = [
|
|
16357
17154
|
headLine3(reading.detail),
|
|
16358
17155
|
factsLine(reading.detail),
|
|
16359
|
-
...url === "" ? [] : [url]
|
|
17156
|
+
...url === "" ? [] : [url],
|
|
17157
|
+
closesLine(reading)
|
|
16360
17158
|
];
|
|
16361
17159
|
return [
|
|
16362
17160
|
head.join(`
|
|
@@ -16377,17 +17175,18 @@ async function probe2(call) {
|
|
|
16377
17175
|
}
|
|
16378
17176
|
}
|
|
16379
17177
|
async function readShow(context, seams) {
|
|
16380
|
-
const asked = readPullArgument(context.args,
|
|
17178
|
+
const asked = readPullArgument(context.args, USAGE38);
|
|
16381
17179
|
const pr = openPrContext(context, seams);
|
|
16382
|
-
const pick = await pickPullRequest(pr, asked,
|
|
17180
|
+
const pick = await pickPullRequest(pr, asked, USAGE38);
|
|
16383
17181
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
16384
17182
|
if (detail === null) {
|
|
16385
|
-
throw
|
|
17183
|
+
throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE38);
|
|
16386
17184
|
}
|
|
16387
17185
|
const checks = await probe2(() => pr.pulls.checks(pick.number));
|
|
16388
17186
|
const comments = await probe2(() => pr.pulls.comments(pick.number));
|
|
16389
17187
|
const reading = {
|
|
16390
17188
|
detail,
|
|
17189
|
+
bodyCloses: closedIssuesIn(detail.body),
|
|
16391
17190
|
checks: checks.value,
|
|
16392
17191
|
checksProblem: checks.problem,
|
|
16393
17192
|
triage: comments.value === null ? null : readLastTriage(comments.value),
|
|
@@ -16401,7 +17200,7 @@ function createPrShowCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
16401
17200
|
subject: "pr",
|
|
16402
17201
|
action: "show",
|
|
16403
17202
|
summary: "show one pull request in detail, with its checks and its last triage",
|
|
16404
|
-
description: "Reads one pull request in full and prints its number and title, its state, author, head branch," + " base branch and mergeability, its URL, every check with its state and its link, and the last rafa triage" + "
|
|
17203
|
+
description: "Reads one pull request in full and prints its number and title, its state, author, head branch," + " base branch and mergeability, its URL, the issues it closes, every check with its state and its link, and" + " the last rafa triage comment made on it. The closes line marks an issue the provider links that the body" + " names by no closing keyword, and an issue the body names by keyword that the provider does not link." + " Without a number it reads the open pull request whose head is the branch checked out" + " at the project root. The checks and the triage comment are read separately, and one that cannot be read" + " says so in its own section rather than refusing the command, where a pull request with no checks reads" + " `checks none`. With `--output=json` the data of the terminal result event holds the fields " + PR_SHOW_JSON_FIELDS.map((field) => `\`${field}\``).join(", ") + ": the pull request, the issues its body closes by keyword, the checks, the triage and the rendered text," + " each read that failed carrying its problem. Refuses with exit code 2 where `pr.provider` is not `gh`.",
|
|
16405
17204
|
args: [
|
|
16406
17205
|
{
|
|
16407
17206
|
name: "n",
|
|
@@ -16805,8 +17604,8 @@ var TRIAGE_FRESH_WINDOW_MS = TRIAGE_FRESH_HOURS * HOUR_MS2;
|
|
|
16805
17604
|
var MAX_TRIAGE_CANDIDATES = 3;
|
|
16806
17605
|
var REFUSE_EXIT_CODE = 2;
|
|
16807
17606
|
var DAYS_FROM_HOURS = 48;
|
|
16808
|
-
var
|
|
16809
|
-
var
|
|
17607
|
+
var REFUSAL_PREFIX2 = "rafa pr triage --resolve refuses";
|
|
17608
|
+
var INDENT7 = " ";
|
|
16810
17609
|
function ageMs(updatedAt, now) {
|
|
16811
17610
|
const then = Date.parse(updatedAt);
|
|
16812
17611
|
const at = Date.parse(now);
|
|
@@ -16818,7 +17617,7 @@ function isFreshCandidate(updatedAt, now) {
|
|
|
16818
17617
|
const age = ageMs(updatedAt, now);
|
|
16819
17618
|
return age !== null && age <= TRIAGE_FRESH_WINDOW_MS;
|
|
16820
17619
|
}
|
|
16821
|
-
function
|
|
17620
|
+
function plural3(count, singular, many) {
|
|
16822
17621
|
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
16823
17622
|
}
|
|
16824
17623
|
function describeAge(updatedAt, now) {
|
|
@@ -16831,11 +17630,11 @@ function describeAge(updatedAt, now) {
|
|
|
16831
17630
|
if (hours < 1)
|
|
16832
17631
|
return "less than an hour ago";
|
|
16833
17632
|
if (hours < DAYS_FROM_HOURS)
|
|
16834
|
-
return `${
|
|
16835
|
-
return `${
|
|
17633
|
+
return `${plural3(hours, "hour", "hours")} ago`;
|
|
17634
|
+
return `${plural3(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
16836
17635
|
}
|
|
16837
|
-
function triageCommand(number,
|
|
16838
|
-
return
|
|
17636
|
+
function triageCommand(number, resolve12 = false) {
|
|
17637
|
+
return resolve12 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
16839
17638
|
}
|
|
16840
17639
|
function joinNumbers(pulls) {
|
|
16841
17640
|
const names = pulls.map((pull2) => `#${pull2.number}`);
|
|
@@ -16846,18 +17645,18 @@ function joinNumbers(pulls) {
|
|
|
16846
17645
|
function byNumber2(pulls) {
|
|
16847
17646
|
return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
|
|
16848
17647
|
}
|
|
16849
|
-
function skipped(pull2, now,
|
|
17648
|
+
function skipped(pull2, now, resolve12) {
|
|
16850
17649
|
return Object.freeze({
|
|
16851
17650
|
number: pull2.number,
|
|
16852
17651
|
title: pull2.title,
|
|
16853
17652
|
updatedAt: pull2.updatedAt,
|
|
16854
17653
|
age: describeAge(pull2.updatedAt, now),
|
|
16855
17654
|
fresh: isFreshCandidate(pull2.updatedAt, now),
|
|
16856
|
-
command: triageCommand(pull2.number,
|
|
17655
|
+
command: triageCommand(pull2.number, resolve12)
|
|
16857
17656
|
});
|
|
16858
17657
|
}
|
|
16859
17658
|
function skippedLine(one) {
|
|
16860
|
-
return `${
|
|
17659
|
+
return `${INDENT7}#${one.number} last moved ${one.age}, run ${one.command}`;
|
|
16861
17660
|
}
|
|
16862
17661
|
function plain(decision, assess, headline) {
|
|
16863
17662
|
return Object.freeze({
|
|
@@ -16884,11 +17683,11 @@ function listing(decision, assess, rest, headline, exitCode = 0) {
|
|
|
16884
17683
|
});
|
|
16885
17684
|
}
|
|
16886
17685
|
function refusal12(rest, count) {
|
|
16887
|
-
const headline = `${
|
|
17686
|
+
const headline = `${REFUSAL_PREFIX2}: ${plural3(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
|
|
16888
17687
|
return listing("refuse", Object.freeze([]), rest, headline, REFUSE_EXIT_CODE);
|
|
16889
17688
|
}
|
|
16890
17689
|
function someReading(fresh, stale, count) {
|
|
16891
|
-
const older = stale.length === 0 ? "" : `; ${
|
|
17690
|
+
const older = stale.length === 0 ? "" : `; ${plural3(stale.length, "older one is", "older ones are")} skipped`;
|
|
16892
17691
|
const headline = `${count} red: assessing ${joinNumbers(fresh)}${older}`;
|
|
16893
17692
|
return listing("some", fresh, stale, headline);
|
|
16894
17693
|
}
|
|
@@ -16897,15 +17696,15 @@ function listReading(rest, count) {
|
|
|
16897
17696
|
return listing("list", Object.freeze([]), rest, headline);
|
|
16898
17697
|
}
|
|
16899
17698
|
function selectTriagePullRequests(input) {
|
|
16900
|
-
const
|
|
17699
|
+
const resolve12 = input.resolve === true;
|
|
16901
17700
|
const { current, now } = input;
|
|
16902
17701
|
if (current !== null) {
|
|
16903
17702
|
return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
|
|
16904
17703
|
}
|
|
16905
17704
|
const candidates = byNumber2(input.candidates);
|
|
16906
17705
|
const count = candidates.length;
|
|
16907
|
-
const lines = candidates.map((pull2) => skipped(pull2, now,
|
|
16908
|
-
if (
|
|
17706
|
+
const lines = candidates.map((pull2) => skipped(pull2, now, resolve12));
|
|
17707
|
+
if (resolve12 && count > 1)
|
|
16909
17708
|
return refusal12(lines, count);
|
|
16910
17709
|
if (count === 0) {
|
|
16911
17710
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
@@ -17088,10 +17887,10 @@ function readTriageGuard(input) {
|
|
|
17088
17887
|
}
|
|
17089
17888
|
|
|
17090
17889
|
// src/commands/pr/triage-report.ts
|
|
17091
|
-
var
|
|
17890
|
+
var INDENT8 = " ";
|
|
17092
17891
|
var SHORT_SHA2 = 7;
|
|
17093
17892
|
function ignoredLines(ignored) {
|
|
17094
|
-
return ignored.map((comment) => `${
|
|
17893
|
+
return ignored.map((comment) => `${INDENT8}${comment.reason}`);
|
|
17095
17894
|
}
|
|
17096
17895
|
function evidenceOf(reading) {
|
|
17097
17896
|
return reading.logs?.chosen?.evidence;
|
|
@@ -17137,8 +17936,8 @@ function excerptBlock(reading) {
|
|
|
17137
17936
|
if (excerpt.lines.length === 0)
|
|
17138
17937
|
return [];
|
|
17139
17938
|
return [
|
|
17140
|
-
`${
|
|
17141
|
-
...excerpt.lines.map((line) => `${
|
|
17939
|
+
`${INDENT8}${excerptCaption(excerpt)}`,
|
|
17940
|
+
...excerpt.lines.map((line) => `${INDENT8}${INDENT8}${line}`)
|
|
17142
17941
|
];
|
|
17143
17942
|
}
|
|
17144
17943
|
function noChecksLines2(reading, assessment) {
|
|
@@ -17149,19 +17948,19 @@ function noChecksLines2(reading, assessment) {
|
|
|
17149
17948
|
const [count = "", warning = ""] = unchecked.warning;
|
|
17150
17949
|
const yes = unchecked.yesMayAnswer ? "--yes may answer it" : "--yes is refused, so a person must answer it";
|
|
17151
17950
|
return [
|
|
17152
|
-
`${
|
|
17153
|
-
`${
|
|
17154
|
-
`${
|
|
17951
|
+
`${INDENT8}Workflows: ${count}`,
|
|
17952
|
+
`${INDENT8}Warning: ${warning}`,
|
|
17953
|
+
`${INDENT8}To merge it anyway: ${skipChecksCommand(number)} (it asks first; ${yes})`
|
|
17155
17954
|
];
|
|
17156
17955
|
}
|
|
17157
17956
|
function evidenceLines2(reading, assessment) {
|
|
17158
17957
|
const step = assessment.step === undefined ? "none named" : assessment.step.name;
|
|
17159
17958
|
return [
|
|
17160
|
-
`${
|
|
17161
|
-
`${
|
|
17162
|
-
`${
|
|
17163
|
-
`${
|
|
17164
|
-
`${
|
|
17959
|
+
`${INDENT8}Why: ${assessment.reason}`,
|
|
17960
|
+
`${INDENT8}Conflicting files: ${pathList2(assessment.files, noFiles(reading))}`,
|
|
17961
|
+
`${INDENT8}Failing step: ${step}`,
|
|
17962
|
+
`${INDENT8}Failing checks: ${pathList2(assessment.failing.map((row2) => row2.name), "none failing")}`,
|
|
17963
|
+
`${INDENT8}Checks verdict: ${assessment.verdict}`,
|
|
17165
17964
|
...noChecksLines2(reading, assessment),
|
|
17166
17965
|
...excerptBlock(reading)
|
|
17167
17966
|
];
|
|
@@ -17173,16 +17972,16 @@ function storedBlockLine(block) {
|
|
|
17173
17972
|
block.attempts === null ? null : `attempts ${block.attempts}`,
|
|
17174
17973
|
block.files === null || block.files.length === 0 ? null : `files ${block.files.join(", ")}`
|
|
17175
17974
|
].filter((word) => word !== null);
|
|
17176
|
-
return `${
|
|
17975
|
+
return `${INDENT8}${words.join(SEPARATOR2)}`;
|
|
17177
17976
|
}
|
|
17178
17977
|
function storedLines(reading) {
|
|
17179
17978
|
const comment = reading.rerun.comment;
|
|
17180
17979
|
if (comment === null)
|
|
17181
17980
|
return [];
|
|
17182
17981
|
return [
|
|
17183
|
-
`${
|
|
17982
|
+
`${INDENT8}${comment.url}${SEPARATOR2}by ${comment.author.login}`,
|
|
17184
17983
|
...reading.rerun.block === null ? [] : [storedBlockLine(reading.rerun.block)],
|
|
17185
|
-
...reading.rerun.problems.map((problem2) => `${
|
|
17984
|
+
...reading.rerun.problems.map((problem2) => `${INDENT8}${problem2}`)
|
|
17186
17985
|
];
|
|
17187
17986
|
}
|
|
17188
17987
|
function writeLines(reading) {
|
|
@@ -17199,8 +17998,8 @@ function logProblemLines(reading) {
|
|
|
17199
17998
|
const logs = reading.logs;
|
|
17200
17999
|
if (logs === null)
|
|
17201
18000
|
return [];
|
|
17202
|
-
const unlinked = logs.unlinked.length === 0 ? [] : [`${
|
|
17203
|
-
return [...logs.problems.map((problem2) => `${
|
|
18001
|
+
const unlinked = logs.unlinked.length === 0 ? [] : [`${INDENT8}no Actions run is linked from ${logs.unlinked.join(", ")}, so no log was read for it`];
|
|
18002
|
+
return [...logs.problems.map((problem2) => `${INDENT8}${problem2}`), ...unlinked];
|
|
17204
18003
|
}
|
|
17205
18004
|
function assessedLines(reading, assessment) {
|
|
17206
18005
|
return [
|
|
@@ -17234,11 +18033,11 @@ var TASK_LINE = /^- \[[^\]]*\] /;
|
|
|
17234
18033
|
var TRAILING_BLOCK = /\{([^{}]*)\}$/;
|
|
17235
18034
|
var PREFIX3 = "pr triage --resolve budget";
|
|
17236
18035
|
function budgetEntry(usd) {
|
|
17237
|
-
const
|
|
17238
|
-
if (parseBudgetUsd(
|
|
18036
|
+
const spelled2 = String(usd);
|
|
18037
|
+
if (parseBudgetUsd(spelled2) === null) {
|
|
17239
18038
|
throw new TypeError(`${PREFIX3}: refused an amount of ${JSON.stringify(usd)}, expected US dollars above zero` + " with at most six whole digits and six decimal places");
|
|
17240
18039
|
}
|
|
17241
|
-
return `${BUDGET_KEY}=${
|
|
18040
|
+
return `${BUDGET_KEY}=${spelled2}`;
|
|
17242
18041
|
}
|
|
17243
18042
|
function declaresBudget(body) {
|
|
17244
18043
|
return body.split(/\s+/).some((token) => token.startsWith(`${BUDGET_KEY}=`));
|
|
@@ -17266,9 +18065,9 @@ function withTaskBudget(plan2, usd) {
|
|
|
17266
18065
|
|
|
17267
18066
|
// src/pr/plans/load.ts
|
|
17268
18067
|
import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
|
|
17269
|
-
import { dirname as
|
|
18068
|
+
import { dirname as dirname7, join as join30 } from "path";
|
|
17270
18069
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17271
|
-
var MODULE_DIR4 =
|
|
18070
|
+
var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
|
|
17272
18071
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
17273
18072
|
var PINNED_PLAN_CLASSES = Object.freeze([
|
|
17274
18073
|
...SIMPLE_TRIAGE_CLASSES,
|
|
@@ -17283,8 +18082,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
17283
18082
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
17284
18083
|
const name = pinnedPlanFileName(triageClass);
|
|
17285
18084
|
return [
|
|
17286
|
-
|
|
17287
|
-
|
|
18085
|
+
join30(moduleDir, PINNED_PLANS_DIRNAME, name),
|
|
18086
|
+
join30(moduleDir, name)
|
|
17288
18087
|
];
|
|
17289
18088
|
}
|
|
17290
18089
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -17436,20 +18235,20 @@ function readAttemptRepeat(input) {
|
|
|
17436
18235
|
}
|
|
17437
18236
|
|
|
17438
18237
|
// src/commands/pr/resolve-loop.ts
|
|
17439
|
-
import { mkdirSync as
|
|
17440
|
-
import { join as
|
|
17441
|
-
var RESOLVE_SUBDIR =
|
|
18238
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
18239
|
+
import { join as join31 } from "path";
|
|
18240
|
+
var RESOLVE_SUBDIR = join31(".rafa", "resolve");
|
|
17442
18241
|
var RUN_PREFIX = "pr-";
|
|
17443
18242
|
var ATTEMPT_PREFIX = "attempt-";
|
|
17444
18243
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
17445
18244
|
var CHILD_OUTPUT = "text";
|
|
17446
18245
|
function resolveRunDir(home, number, attempt3) {
|
|
17447
|
-
return
|
|
18246
|
+
return join31(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
17448
18247
|
}
|
|
17449
18248
|
function writeResolvePlan(write) {
|
|
17450
18249
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
17451
|
-
|
|
17452
|
-
const path =
|
|
18250
|
+
mkdirSync6(dir, { recursive: true });
|
|
18251
|
+
const path = join31(dir, pinnedPlanFileName(write.triageClass));
|
|
17453
18252
|
writeFileSync6(path, write.plan, "utf8");
|
|
17454
18253
|
return path;
|
|
17455
18254
|
}
|
|
@@ -17490,8 +18289,8 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
17490
18289
|
}
|
|
17491
18290
|
|
|
17492
18291
|
// src/pr/triage/version-convert.ts
|
|
17493
|
-
import { mkdirSync as
|
|
17494
|
-
import { dirname as
|
|
18292
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
|
|
18293
|
+
import { dirname as dirname8, join as join32, posix } from "path";
|
|
17495
18294
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
17496
18295
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
17497
18296
|
var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
|
|
@@ -17603,7 +18402,7 @@ function planConversion(input, problems) {
|
|
|
17603
18402
|
}
|
|
17604
18403
|
let branchVersionText;
|
|
17605
18404
|
try {
|
|
17606
|
-
branchVersionText = readFileSync19(
|
|
18405
|
+
branchVersionText = readFileSync19(join32(input.worktree, settings.releaseVersionFile), "utf8");
|
|
17607
18406
|
} catch (error) {
|
|
17608
18407
|
return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
17609
18408
|
}
|
|
@@ -17629,9 +18428,9 @@ function writeConversion(input, plan2) {
|
|
|
17629
18428
|
[input.settings.releaseChangelog, plan2.changelogText]
|
|
17630
18429
|
];
|
|
17631
18430
|
for (const [path, text] of writes) {
|
|
17632
|
-
const target =
|
|
18431
|
+
const target = join32(input.worktree, path);
|
|
17633
18432
|
try {
|
|
17634
|
-
|
|
18433
|
+
mkdirSync7(dirname8(target), { recursive: true });
|
|
17635
18434
|
writeFileSync7(target, text);
|
|
17636
18435
|
} catch (error) {
|
|
17637
18436
|
return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
|
|
@@ -18058,19 +18857,19 @@ async function runAttempts(run, detail, first) {
|
|
|
18058
18857
|
}
|
|
18059
18858
|
|
|
18060
18859
|
// src/commands/pr/triage.ts
|
|
18061
|
-
var
|
|
18860
|
+
var USAGE39 = PR_USAGE.triage;
|
|
18062
18861
|
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
18063
18862
|
var DETACHED_HEAD2 = "HEAD";
|
|
18064
18863
|
var RESOLVED = false;
|
|
18065
18864
|
var DEFAULT_TRIAGE_SEAMS = Object.freeze({});
|
|
18066
|
-
function readMaxAttempts(flags,
|
|
18865
|
+
function readMaxAttempts(flags, usage) {
|
|
18067
18866
|
const value = flags["max-attempts"];
|
|
18068
18867
|
if (value === undefined)
|
|
18069
18868
|
return DEFAULT_MAX_ATTEMPTS;
|
|
18070
|
-
const
|
|
18869
|
+
const spelled2 = typeof value === "boolean" ? "--max-attempts with no value" : `"${value}"`;
|
|
18071
18870
|
const count = typeof value === "string" ? Number(value) : Number.NaN;
|
|
18072
18871
|
if (!Number.isSafeInteger(count) || count < 1) {
|
|
18073
|
-
throw
|
|
18872
|
+
throw lineRefusal5(`${spelled2} is no attempt count, which is a whole number from 1;` + " type the pull request number before the flags", usage);
|
|
18074
18873
|
}
|
|
18075
18874
|
return count;
|
|
18076
18875
|
}
|
|
@@ -18099,22 +18898,22 @@ async function redPullRequests(pr) {
|
|
|
18099
18898
|
}
|
|
18100
18899
|
return red;
|
|
18101
18900
|
}
|
|
18102
|
-
async function selectTargets(pr, warn3, now,
|
|
18901
|
+
async function selectTargets(pr, warn3, now, resolve12) {
|
|
18103
18902
|
const branch = readBranchOrNone(pr, warn3);
|
|
18104
18903
|
const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
18105
18904
|
if (current !== null)
|
|
18106
|
-
return selectTriagePullRequests({ current, candidates: [], now, resolve:
|
|
18905
|
+
return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve12 });
|
|
18107
18906
|
return selectTriagePullRequests({
|
|
18108
18907
|
current: null,
|
|
18109
18908
|
candidates: await redPullRequests(pr),
|
|
18110
18909
|
now,
|
|
18111
|
-
resolve:
|
|
18910
|
+
resolve: resolve12
|
|
18112
18911
|
});
|
|
18113
18912
|
}
|
|
18114
18913
|
async function detailOf2(pr, number) {
|
|
18115
18914
|
const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
|
|
18116
18915
|
if (detail === null) {
|
|
18117
|
-
throw
|
|
18916
|
+
throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE39);
|
|
18118
18917
|
}
|
|
18119
18918
|
return detail;
|
|
18120
18919
|
}
|
|
@@ -18225,14 +19024,14 @@ async function assessOne(options) {
|
|
|
18225
19024
|
function refuseResolveWithoutComment(wantsResolve, wantsComment) {
|
|
18226
19025
|
if (!wantsResolve || wantsComment)
|
|
18227
19026
|
return;
|
|
18228
|
-
throw
|
|
19027
|
+
throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE39);
|
|
18229
19028
|
}
|
|
18230
19029
|
async function runTriage(context, seams) {
|
|
18231
|
-
const wantsComment = readBooleanFlag(context.flags, "comment",
|
|
18232
|
-
const wantsResolve = readBooleanFlag(context.flags, "resolve",
|
|
18233
|
-
const maxAttempts = readMaxAttempts(context.flags,
|
|
19030
|
+
const wantsComment = readBooleanFlag(context.flags, "comment", USAGE39, true);
|
|
19031
|
+
const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE39, false);
|
|
19032
|
+
const maxAttempts = readMaxAttempts(context.flags, USAGE39);
|
|
18234
19033
|
refuseResolveWithoutComment(wantsResolve, wantsComment);
|
|
18235
|
-
const asked = readPullArgument(context.args,
|
|
19034
|
+
const asked = readPullArgument(context.args, USAGE39);
|
|
18236
19035
|
const pr = openPrContext(context, seams);
|
|
18237
19036
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
18238
19037
|
const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
|
|
@@ -18245,7 +19044,7 @@ async function runTriage(context, seams) {
|
|
|
18245
19044
|
}
|
|
18246
19045
|
const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
|
|
18247
19046
|
const readings = [];
|
|
18248
|
-
let
|
|
19047
|
+
let resolve12 = null;
|
|
18249
19048
|
for (const number of numbers) {
|
|
18250
19049
|
const assess = () => assessOne({
|
|
18251
19050
|
pr,
|
|
@@ -18265,7 +19064,7 @@ async function runTriage(context, seams) {
|
|
|
18265
19064
|
readings.push(reading);
|
|
18266
19065
|
continue;
|
|
18267
19066
|
}
|
|
18268
|
-
|
|
19067
|
+
resolve12 = await resolvePullRequest({
|
|
18269
19068
|
trust: {
|
|
18270
19069
|
permissions,
|
|
18271
19070
|
trustedAuthors: pr.trustedAuthors,
|
|
@@ -18287,16 +19086,16 @@ async function runTriage(context, seams) {
|
|
|
18287
19086
|
...seams.clock === undefined ? {} : { clock: seams.clock },
|
|
18288
19087
|
...seams.sleep === undefined ? {} : { sleep: seams.sleep }
|
|
18289
19088
|
});
|
|
18290
|
-
readings.push(
|
|
19089
|
+
readings.push(resolve12.reading);
|
|
18291
19090
|
}
|
|
18292
19091
|
const text = [
|
|
18293
19092
|
...selection === null ? [] : [selection.message],
|
|
18294
19093
|
...readings.length === 0 ? [] : [renderTriages(readings)],
|
|
18295
|
-
...
|
|
19094
|
+
...resolve12 === null ? [] : resolve12.lines
|
|
18296
19095
|
].join(`
|
|
18297
19096
|
|
|
18298
19097
|
`);
|
|
18299
|
-
return { selection, readings, resolve:
|
|
19098
|
+
return { selection, readings, resolve: resolve12, text, exitCode: resolve12?.exitCode ?? 0 };
|
|
18300
19099
|
}
|
|
18301
19100
|
function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
18302
19101
|
const command = {
|
|
@@ -18370,7 +19169,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
|
18370
19169
|
var triage_default = createPrTriageCommand();
|
|
18371
19170
|
|
|
18372
19171
|
// src/commands/pr/view.ts
|
|
18373
|
-
var
|
|
19172
|
+
var USAGE40 = PR_USAGE.view;
|
|
18374
19173
|
function trimmedOrNull2(value) {
|
|
18375
19174
|
const trimmed = value.trim();
|
|
18376
19175
|
return trimmed === "" ? null : trimmed;
|
|
@@ -18380,9 +19179,9 @@ function renderView(number, url) {
|
|
|
18380
19179
|
return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
|
|
18381
19180
|
}
|
|
18382
19181
|
async function runView2(context, seams) {
|
|
18383
|
-
const asked = readPullArgument(context.args,
|
|
19182
|
+
const asked = readPullArgument(context.args, USAGE40);
|
|
18384
19183
|
const pr = openPrContext(context, seams);
|
|
18385
|
-
const pick = await pickPullRequest(pr, asked,
|
|
19184
|
+
const pick = await pickPullRequest(pr, asked, USAGE40);
|
|
18386
19185
|
await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
|
|
18387
19186
|
const url = trimmedOrNull2(pick.summary?.url ?? "");
|
|
18388
19187
|
return {
|
|
@@ -18437,8 +19236,8 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
18437
19236
|
var view_default = createPrViewCommand();
|
|
18438
19237
|
|
|
18439
19238
|
// src/commands/pr/wait.ts
|
|
18440
|
-
var
|
|
18441
|
-
var
|
|
19239
|
+
var USAGE41 = PR_USAGE.wait;
|
|
19240
|
+
var TIMEOUT_FLAG2 = "timeout";
|
|
18442
19241
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
18443
19242
|
var MS_PER_MIN = 60000;
|
|
18444
19243
|
var MS_PER_SECOND2 = 1000;
|
|
@@ -18450,14 +19249,14 @@ var WAIT_EXIT_CODES = Object.freeze({
|
|
|
18450
19249
|
timeout: 3
|
|
18451
19250
|
});
|
|
18452
19251
|
var DEFAULT_WAIT_SEAMS = Object.freeze({});
|
|
18453
|
-
function readTimeoutMinutes(flags,
|
|
18454
|
-
const value = flags[
|
|
19252
|
+
function readTimeoutMinutes(flags, usage) {
|
|
19253
|
+
const value = flags[TIMEOUT_FLAG2];
|
|
18455
19254
|
if (value === undefined)
|
|
18456
19255
|
return DEFAULT_WAIT_TIMEOUT_MIN;
|
|
18457
|
-
const
|
|
19256
|
+
const spelled2 = typeof value === "boolean" ? `--${TIMEOUT_FLAG2} with no value` : `"${value}"`;
|
|
18458
19257
|
const minutes = typeof value === "string" ? Number(value) : Number.NaN;
|
|
18459
19258
|
if (!Number.isSafeInteger(minutes) || minutes < 1) {
|
|
18460
|
-
throw
|
|
19259
|
+
throw lineRefusal5(`${spelled2} is no timeout, which is a whole number of minutes from 1;` + " type the pull request number before the flags", usage);
|
|
18461
19260
|
}
|
|
18462
19261
|
return minutes;
|
|
18463
19262
|
}
|
|
@@ -18503,10 +19302,10 @@ function renderWait(reading) {
|
|
|
18503
19302
|
`);
|
|
18504
19303
|
}
|
|
18505
19304
|
async function runWait(context, seams) {
|
|
18506
|
-
const minutes = readTimeoutMinutes(context.flags,
|
|
18507
|
-
const asked = readPullArgument(context.args,
|
|
19305
|
+
const minutes = readTimeoutMinutes(context.flags, USAGE41);
|
|
19306
|
+
const asked = readPullArgument(context.args, USAGE41);
|
|
18508
19307
|
const pr = openPrContext(context, seams);
|
|
18509
|
-
const pick = await pickPullRequest(pr, asked,
|
|
19308
|
+
const pick = await pickPullRequest(pr, asked, USAGE41);
|
|
18510
19309
|
const timeoutMs = minutes * MS_PER_MIN;
|
|
18511
19310
|
context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
|
|
18512
19311
|
const waited = await waitForChecks({
|
|
@@ -18548,7 +19347,7 @@ function createPrWaitCommand(seams = DEFAULT_WAIT_SEAMS) {
|
|
|
18548
19347
|
],
|
|
18549
19348
|
flags: [
|
|
18550
19349
|
{
|
|
18551
|
-
name:
|
|
19350
|
+
name: TIMEOUT_FLAG2,
|
|
18552
19351
|
description: `How many minutes to wait before giving up. ${DEFAULT_WAIT_TIMEOUT_MIN} when it is left out.`,
|
|
18553
19352
|
type: "string"
|
|
18554
19353
|
},
|
|
@@ -18582,7 +19381,7 @@ function createPrWaitCommand(seams = DEFAULT_WAIT_SEAMS) {
|
|
|
18582
19381
|
};
|
|
18583
19382
|
return Object.freeze(command);
|
|
18584
19383
|
}
|
|
18585
|
-
var
|
|
19384
|
+
var wait_default2 = createPrWaitCommand();
|
|
18586
19385
|
|
|
18587
19386
|
// src/release/settle-pr.ts
|
|
18588
19387
|
var RELEASE_PR_BRANCH = "rafa/release";
|
|
@@ -18861,9 +19660,9 @@ function tagSettle(worktree, mode, delivered) {
|
|
|
18861
19660
|
}
|
|
18862
19661
|
|
|
18863
19662
|
// src/release/settle-worktree.ts
|
|
18864
|
-
import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as
|
|
19663
|
+
import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync2 } from "fs";
|
|
18865
19664
|
import { tmpdir as tmpdir2 } from "os";
|
|
18866
|
-
import { join as
|
|
19665
|
+
import { join as join33 } from "path";
|
|
18867
19666
|
var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
|
|
18868
19667
|
var WORKTREE_DIRECTORY = "base";
|
|
18869
19668
|
function removeScratch(git, scratch, path) {
|
|
@@ -18887,7 +19686,7 @@ function removeScratch(git, scratch, path) {
|
|
|
18887
19686
|
}
|
|
18888
19687
|
function makeScratch(root) {
|
|
18889
19688
|
try {
|
|
18890
|
-
return
|
|
19689
|
+
return realpathSync4(mkdtempSync2(join33(root, SETTLE_SCRATCH_PREFIX)));
|
|
18891
19690
|
} catch (error) {
|
|
18892
19691
|
return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
|
|
18893
19692
|
}
|
|
@@ -18909,7 +19708,7 @@ function addWorktree(options) {
|
|
|
18909
19708
|
const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
|
|
18910
19709
|
if (typeof scratch !== "string")
|
|
18911
19710
|
return scratch;
|
|
18912
|
-
const path =
|
|
19711
|
+
const path = join33(scratch, WORKTREE_DIRECTORY);
|
|
18913
19712
|
const added = git(["worktree", "add", "--detach", path, commit]);
|
|
18914
19713
|
if (!added.ok) {
|
|
18915
19714
|
return {
|
|
@@ -18942,7 +19741,7 @@ async function withSettleWorktree(options, body) {
|
|
|
18942
19741
|
var RELEASE_SETTLE_USAGE = "rafa release settle [--dry-run]";
|
|
18943
19742
|
var COMMAND_NAME12 = "rafa release settle";
|
|
18944
19743
|
var DRY_RUN_FLAG6 = "dry-run";
|
|
18945
|
-
var
|
|
19744
|
+
var INDENT9 = " ";
|
|
18946
19745
|
var SHORT_HASH4 = 12;
|
|
18947
19746
|
var DEFAULT_RELEASE_SETTLE_SEAMS = Object.freeze({});
|
|
18948
19747
|
function projectOf9(context) {
|
|
@@ -18976,7 +19775,7 @@ function readingLines(reading, ref8) {
|
|
|
18976
19775
|
lines.push("Fragments, in the order the base received them:");
|
|
18977
19776
|
reading.fragments.forEach((each, index) => {
|
|
18978
19777
|
const { level, title } = each.fragment;
|
|
18979
|
-
lines.push(`${
|
|
19778
|
+
lines.push(`${INDENT9}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
|
|
18980
19779
|
});
|
|
18981
19780
|
}
|
|
18982
19781
|
if (reading.outcome === "folded" || reading.outcome === "built" || reading.outcome === "unbuilt") {
|
|
@@ -18996,7 +19795,7 @@ function unwrittenEnding(reading) {
|
|
|
18996
19795
|
return { line: "Nothing to settle: no fragment waits that ships a release.", refuses: false };
|
|
18997
19796
|
}
|
|
18998
19797
|
}
|
|
18999
|
-
function
|
|
19798
|
+
function resultOf6(base, lines, problem2) {
|
|
19000
19799
|
const exitCode = problem2 === null ? 0 : 1;
|
|
19001
19800
|
return { ...base, lines, problem: problem2, exitCode };
|
|
19002
19801
|
}
|
|
@@ -19005,10 +19804,10 @@ function dryRunResult(reading, ref8, branch) {
|
|
|
19005
19804
|
const lines = [...readingLines(reading, ref8)];
|
|
19006
19805
|
if (reading.outcome === "folded") {
|
|
19007
19806
|
lines.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
|
|
19008
|
-
return
|
|
19807
|
+
return resultOf6(base, lines, null);
|
|
19009
19808
|
}
|
|
19010
19809
|
const ending = unwrittenEnding(reading);
|
|
19011
|
-
return ending.refuses ?
|
|
19810
|
+
return ending.refuses ? resultOf6(base, lines, ending.line) : resultOf6(base, [...lines, ending.line], null);
|
|
19012
19811
|
}
|
|
19013
19812
|
function deliveryEnding(delivered, branch) {
|
|
19014
19813
|
const { outcome } = delivered;
|
|
@@ -19040,15 +19839,15 @@ function settledResult(delivered, tag, ref8, branch) {
|
|
|
19040
19839
|
const lines = [...readingLines(reading, ref8)];
|
|
19041
19840
|
const ending = deliveryEnding(delivered, branch);
|
|
19042
19841
|
if (ending.refuses)
|
|
19043
|
-
return
|
|
19842
|
+
return resultOf6(base, lines, ending.line);
|
|
19044
19843
|
lines.push(ending.line);
|
|
19045
19844
|
if (tag.outcome === "failed")
|
|
19046
|
-
return
|
|
19845
|
+
return resultOf6(base, lines, `\u274C ${tag.sentence}`);
|
|
19047
19846
|
if (tag.outcome === "tagged")
|
|
19048
19847
|
lines.push(`\u2705 ${tag.sentence}`);
|
|
19049
19848
|
if (tag.outcome === "skipped" && tag.sentence !== null)
|
|
19050
19849
|
lines.push(`Tag: ${tag.sentence}`);
|
|
19051
|
-
return
|
|
19850
|
+
return resultOf6(base, lines, null);
|
|
19052
19851
|
}
|
|
19053
19852
|
function providerFor(project, config, seams) {
|
|
19054
19853
|
requireGhProvider(resolvePrProvider({ configured: config.prProvider, dir: project.root, readRemote: seams.readRemote }));
|
|
@@ -19132,7 +19931,7 @@ var settle_default = createReleaseSettleCommand();
|
|
|
19132
19931
|
|
|
19133
19932
|
// src/commands/release/tag.ts
|
|
19134
19933
|
import { readFileSync as readFileSync20 } from "fs";
|
|
19135
|
-
import { join as
|
|
19934
|
+
import { join as join34 } from "path";
|
|
19136
19935
|
|
|
19137
19936
|
// src/commands/release/release-commit.ts
|
|
19138
19937
|
function unread(reason, message) {
|
|
@@ -19175,7 +19974,7 @@ var RELEASE_TAG_USAGE = "rafa release tag";
|
|
|
19175
19974
|
var DEFAULT_RELEASE_BRANCH = "main";
|
|
19176
19975
|
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
19177
19976
|
var RELEASE_REMOTE2 = "origin";
|
|
19178
|
-
var
|
|
19977
|
+
var INDENT10 = " ";
|
|
19179
19978
|
var SHORT_COMMIT = 7;
|
|
19180
19979
|
function readBranch2(git) {
|
|
19181
19980
|
const result2 = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -19196,7 +19995,7 @@ function textOf(path) {
|
|
|
19196
19995
|
}
|
|
19197
19996
|
}
|
|
19198
19997
|
function readVersion(root, configured) {
|
|
19199
|
-
const resolved =
|
|
19998
|
+
const resolved = join34(root, configured);
|
|
19200
19999
|
const text = textOf(resolved);
|
|
19201
20000
|
if (text === null) {
|
|
19202
20001
|
return {
|
|
@@ -19210,7 +20009,7 @@ function readVersion(root, configured) {
|
|
|
19210
20009
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
19211
20010
|
}
|
|
19212
20011
|
function readNewestRelease(root, configured) {
|
|
19213
|
-
const resolved =
|
|
20012
|
+
const resolved = join34(root, configured);
|
|
19214
20013
|
const text = textOf(resolved);
|
|
19215
20014
|
if (text === null) {
|
|
19216
20015
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -19370,7 +20169,7 @@ function renderTagged(written, branch, followUps) {
|
|
|
19370
20169
|
return [
|
|
19371
20170
|
`\u2705 Tagged ${written.tag} at ${where}.`,
|
|
19372
20171
|
"Next:",
|
|
19373
|
-
...followUps.map((followUp2) => `${
|
|
20172
|
+
...followUps.map((followUp2) => `${INDENT10}${followUp2.command} \u2014 ${followUp2.why}`)
|
|
19374
20173
|
];
|
|
19375
20174
|
}
|
|
19376
20175
|
function runTag(context, seams = DEFAULT_RELEASE_SEAMS) {
|
|
@@ -19480,17 +20279,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
19480
20279
|
var roadmap_default = createRoadmapCommand();
|
|
19481
20280
|
|
|
19482
20281
|
// src/commands/skill/backfill.ts
|
|
19483
|
-
import { statSync as
|
|
19484
|
-
import { join as
|
|
20282
|
+
import { statSync as statSync10 } from "fs";
|
|
20283
|
+
import { join as join39, resolve as resolve14 } from "path";
|
|
19485
20284
|
|
|
19486
20285
|
// src/backfill/backup.ts
|
|
19487
|
-
import { copyFileSync as
|
|
19488
|
-
import { dirname as
|
|
20286
|
+
import { copyFileSync as copyFileSync4, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
|
|
20287
|
+
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join36, relative as relative9, resolve as resolve12, sep as sep5 } from "path";
|
|
19489
20288
|
|
|
19490
20289
|
// src/backfill/proposal-file.ts
|
|
19491
20290
|
import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
|
|
19492
|
-
import { join as
|
|
19493
|
-
var BACKFILL_PATH =
|
|
20291
|
+
import { join as join35 } from "path";
|
|
20292
|
+
var BACKFILL_PATH = join35(".rafa", "backfill");
|
|
19494
20293
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
19495
20294
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
19496
20295
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -19516,7 +20315,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
19516
20315
|
].join(`
|
|
19517
20316
|
`);
|
|
19518
20317
|
function proposalPath(dir, batch) {
|
|
19519
|
-
return
|
|
20318
|
+
return join35(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
19520
20319
|
}
|
|
19521
20320
|
function isProposalFileName(name) {
|
|
19522
20321
|
return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -19652,7 +20451,7 @@ function readProposalFiles(dir) {
|
|
|
19652
20451
|
return [];
|
|
19653
20452
|
}
|
|
19654
20453
|
return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
19655
|
-
const path =
|
|
20454
|
+
const path = join35(dir, name);
|
|
19656
20455
|
try {
|
|
19657
20456
|
return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
|
|
19658
20457
|
} catch (error) {
|
|
@@ -19662,19 +20461,19 @@ function readProposalFiles(dir) {
|
|
|
19662
20461
|
}
|
|
19663
20462
|
|
|
19664
20463
|
// src/backfill/backup.ts
|
|
19665
|
-
var BACKUP_PATH =
|
|
20464
|
+
var BACKUP_PATH = join36(BACKFILL_PATH, "backup");
|
|
19666
20465
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
19667
20466
|
function backupDirectory(base) {
|
|
19668
|
-
return
|
|
20467
|
+
return join36(base, BACKUP_PATH);
|
|
19669
20468
|
}
|
|
19670
20469
|
function isUnder(root, path) {
|
|
19671
|
-
const step =
|
|
19672
|
-
return step === "" || !step.startsWith(`..${
|
|
20470
|
+
const step = relative9(resolve12(root), resolve12(path));
|
|
20471
|
+
return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute7(step);
|
|
19673
20472
|
}
|
|
19674
20473
|
function backupPathFor(path, root, backupDir) {
|
|
19675
20474
|
if (!isUnder(root, path))
|
|
19676
20475
|
return null;
|
|
19677
|
-
return
|
|
20476
|
+
return join36(backupDir, relative9(resolve12(root), resolve12(path)));
|
|
19678
20477
|
}
|
|
19679
20478
|
function backupFile(path, options) {
|
|
19680
20479
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -19687,8 +20486,8 @@ function backupFile(path, options) {
|
|
|
19687
20486
|
return { path, kind: "missing", backup: null };
|
|
19688
20487
|
if (existsSync11(backup))
|
|
19689
20488
|
return { path, kind: "kept", backup };
|
|
19690
|
-
|
|
19691
|
-
|
|
20489
|
+
mkdirSync8(dirname9(backup), { recursive: true });
|
|
20490
|
+
copyFileSync4(path, backup);
|
|
19692
20491
|
return { path, kind: "copied", backup };
|
|
19693
20492
|
}
|
|
19694
20493
|
function backupFiles(paths, options) {
|
|
@@ -19741,8 +20540,8 @@ function triggerSentence(body) {
|
|
|
19741
20540
|
const section = bodySections(body).find((entry) => WHEN_TO_USE_HEADING.test(entry.title));
|
|
19742
20541
|
return section === undefined ? "" : firstSentence(section.text);
|
|
19743
20542
|
}
|
|
19744
|
-
function trimWhenToUse(
|
|
19745
|
-
const budget = LISTING_LIMIT - 1 - countCharacters(
|
|
20543
|
+
function trimWhenToUse(description2, whenToUse) {
|
|
20544
|
+
const budget = LISTING_LIMIT - 1 - countCharacters(description2);
|
|
19746
20545
|
if (budget <= 0)
|
|
19747
20546
|
return "";
|
|
19748
20547
|
if (countCharacters(whenToUse) <= budget)
|
|
@@ -19912,7 +20711,7 @@ function applyDerivation(plan2, options) {
|
|
|
19912
20711
|
|
|
19913
20712
|
// src/backfill/proposal-batch.ts
|
|
19914
20713
|
import { readFileSync as readFileSync23 } from "fs";
|
|
19915
|
-
import { relative as
|
|
20714
|
+
import { relative as relative10 } from "path";
|
|
19916
20715
|
|
|
19917
20716
|
// src/demote/report.ts
|
|
19918
20717
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -20201,7 +21000,7 @@ function candidateNeeds(data, body) {
|
|
|
20201
21000
|
return needs;
|
|
20202
21001
|
}
|
|
20203
21002
|
function posixRelative(root, path) {
|
|
20204
|
-
return
|
|
21003
|
+
return relative10(root, path).split("\\").join("/");
|
|
20205
21004
|
}
|
|
20206
21005
|
function readCandidate(root, entry) {
|
|
20207
21006
|
if (!entry.isFile || entry.name === null)
|
|
@@ -20316,8 +21115,8 @@ function parseSessionAnswer(stdout) {
|
|
|
20316
21115
|
}
|
|
20317
21116
|
|
|
20318
21117
|
// src/backfill/propose.ts
|
|
20319
|
-
import { mkdirSync as
|
|
20320
|
-
import { join as
|
|
21118
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
|
|
21119
|
+
import { join as join37, resolve as resolve13 } from "path";
|
|
20321
21120
|
var PROPOSAL_ACTION_KINDS = [
|
|
20322
21121
|
"applied",
|
|
20323
21122
|
"unchanged",
|
|
@@ -20396,7 +21195,7 @@ async function runProposalPass(options) {
|
|
|
20396
21195
|
const batches = batchCandidates(candidates);
|
|
20397
21196
|
const written = [];
|
|
20398
21197
|
if (batches.length > 0)
|
|
20399
|
-
|
|
21198
|
+
mkdirSync9(options.backfillDir, { recursive: true });
|
|
20400
21199
|
for (const [index, batch] of batches.entries()) {
|
|
20401
21200
|
const number = index + 1;
|
|
20402
21201
|
const file = await runProposalBatch(number, batch, options);
|
|
@@ -20466,7 +21265,7 @@ function writeRow(path, row2, original, options) {
|
|
|
20466
21265
|
};
|
|
20467
21266
|
}
|
|
20468
21267
|
function applyRow(root, row2, options) {
|
|
20469
|
-
const path =
|
|
21268
|
+
const path = join37(root, ...row2.path.split("/"));
|
|
20470
21269
|
if (row2.status === UNANSWERED_ROW) {
|
|
20471
21270
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
20472
21271
|
}
|
|
@@ -20489,7 +21288,7 @@ function fileRefusal(file, root) {
|
|
|
20489
21288
|
if (file.status !== REVIEWED_FILE) {
|
|
20490
21289
|
return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
|
|
20491
21290
|
}
|
|
20492
|
-
return
|
|
21291
|
+
return resolve13(file.skills) === resolve13(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
|
|
20493
21292
|
}
|
|
20494
21293
|
function countProposalActions(actions) {
|
|
20495
21294
|
const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
|
|
@@ -20503,7 +21302,7 @@ function applyProposals(files, root, options) {
|
|
|
20503
21302
|
for (const file of files) {
|
|
20504
21303
|
const refusal13 = fileRefusal(file, root);
|
|
20505
21304
|
for (const row2 of file.rows) {
|
|
20506
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
21305
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join37(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
20507
21306
|
actions.push(action);
|
|
20508
21307
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
20509
21308
|
triggers[row2.name] = row2.trigger;
|
|
@@ -20514,17 +21313,17 @@ function applyProposals(files, root, options) {
|
|
|
20514
21313
|
}
|
|
20515
21314
|
|
|
20516
21315
|
// src/demote/select.ts
|
|
20517
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as
|
|
20518
|
-
import { basename as basename7, dirname as
|
|
21316
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
|
|
21317
|
+
import { basename as basename7, dirname as dirname10, join as join38 } from "path";
|
|
20519
21318
|
var SKILLS_DIRECTORY = "skills";
|
|
20520
21319
|
var CLAUDE_DIRECTORY = ".claude";
|
|
20521
21320
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
20522
|
-
var DEMOTED_PATH =
|
|
20523
|
-
var INSTINCTS_PATH =
|
|
21321
|
+
var DEMOTED_PATH = join38(".rafa", "demoted");
|
|
21322
|
+
var INSTINCTS_PATH = join38(".rafa", "instincts");
|
|
20524
21323
|
var REPORT_FILE = "report.md";
|
|
20525
21324
|
function realOrSelf(path) {
|
|
20526
21325
|
try {
|
|
20527
|
-
return
|
|
21326
|
+
return realpathSync5(path);
|
|
20528
21327
|
} catch {
|
|
20529
21328
|
return path;
|
|
20530
21329
|
}
|
|
@@ -20539,23 +21338,23 @@ function samePath(left, right) {
|
|
|
20539
21338
|
function resolveDemotionScope(dir, home) {
|
|
20540
21339
|
if (basename7(dir) !== SKILLS_DIRECTORY)
|
|
20541
21340
|
return null;
|
|
20542
|
-
const claude =
|
|
21341
|
+
const claude = dirname10(dir);
|
|
20543
21342
|
if (basename7(claude) !== CLAUDE_DIRECTORY)
|
|
20544
21343
|
return null;
|
|
20545
|
-
const base =
|
|
20546
|
-
const demotedDir =
|
|
21344
|
+
const base = dirname10(claude);
|
|
21345
|
+
const demotedDir = join38(base, DEMOTED_PATH);
|
|
20547
21346
|
return {
|
|
20548
21347
|
scope: samePath(base, home) ? "user" : "project",
|
|
20549
21348
|
dir,
|
|
20550
21349
|
base,
|
|
20551
21350
|
demotedDir,
|
|
20552
|
-
instinctsDir:
|
|
20553
|
-
reportPath:
|
|
21351
|
+
instinctsDir: join38(base, INSTINCTS_PATH),
|
|
21352
|
+
reportPath: join38(demotedDir, REPORT_FILE)
|
|
20554
21353
|
};
|
|
20555
21354
|
}
|
|
20556
21355
|
function entryKind(path) {
|
|
20557
21356
|
try {
|
|
20558
|
-
const stats =
|
|
21357
|
+
const stats = statSync9(path);
|
|
20559
21358
|
if (stats.isFile())
|
|
20560
21359
|
return "file";
|
|
20561
21360
|
if (stats.isDirectory())
|
|
@@ -20577,7 +21376,7 @@ function readSelected(absolute, path, entries) {
|
|
|
20577
21376
|
let mtime;
|
|
20578
21377
|
try {
|
|
20579
21378
|
text = readFileSync25(absolute, "utf8");
|
|
20580
|
-
mtime =
|
|
21379
|
+
mtime = statSync9(absolute).mtime.toISOString();
|
|
20581
21380
|
} catch {
|
|
20582
21381
|
return null;
|
|
20583
21382
|
}
|
|
@@ -20592,14 +21391,14 @@ function selectsLearned(text, scope) {
|
|
|
20592
21391
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
20593
21392
|
}
|
|
20594
21393
|
function selectLearned(scope) {
|
|
20595
|
-
const dir =
|
|
21394
|
+
const dir = join38(scope.dir, LEARNED_DIRECTORY);
|
|
20596
21395
|
if (entryKind(dir) !== "directory")
|
|
20597
21396
|
return [];
|
|
20598
21397
|
const selected = [];
|
|
20599
21398
|
for (const name of visibleNames(dir)) {
|
|
20600
21399
|
if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
|
|
20601
21400
|
continue;
|
|
20602
|
-
const absolute =
|
|
21401
|
+
const absolute = join38(dir, name);
|
|
20603
21402
|
if (entryKind(absolute) !== "file")
|
|
20604
21403
|
continue;
|
|
20605
21404
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
|
|
@@ -20613,10 +21412,10 @@ function selectSkillFiles(scope) {
|
|
|
20613
21412
|
for (const name of visibleNames(scope.dir)) {
|
|
20614
21413
|
if (name === LEARNED_DIRECTORY)
|
|
20615
21414
|
continue;
|
|
20616
|
-
const skillDir =
|
|
21415
|
+
const skillDir = join38(scope.dir, name);
|
|
20617
21416
|
if (entryKind(skillDir) !== "directory")
|
|
20618
21417
|
continue;
|
|
20619
|
-
const absolute =
|
|
21418
|
+
const absolute = join38(skillDir, SKILL_FILE);
|
|
20620
21419
|
if (entryKind(absolute) !== "file")
|
|
20621
21420
|
continue;
|
|
20622
21421
|
const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -20664,7 +21463,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
20664
21463
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
20665
21464
|
Usage: ${BACKFILL_USAGE}`);
|
|
20666
21465
|
}
|
|
20667
|
-
return
|
|
21466
|
+
return resolve14(cwd(), dir);
|
|
20668
21467
|
}
|
|
20669
21468
|
function readConsumerRoot(value, cwd) {
|
|
20670
21469
|
if (value === undefined || value === false)
|
|
@@ -20673,11 +21472,11 @@ function readConsumerRoot(value, cwd) {
|
|
|
20673
21472
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
20674
21473
|
Usage: ${BACKFILL_USAGE}`);
|
|
20675
21474
|
}
|
|
20676
|
-
return
|
|
21475
|
+
return resolve14(cwd(), value);
|
|
20677
21476
|
}
|
|
20678
21477
|
function isDirectory3(path) {
|
|
20679
21478
|
try {
|
|
20680
|
-
return
|
|
21479
|
+
return statSync10(path).isDirectory();
|
|
20681
21480
|
} catch {
|
|
20682
21481
|
return false;
|
|
20683
21482
|
}
|
|
@@ -20801,7 +21600,7 @@ function reviewedProposals(dir) {
|
|
|
20801
21600
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
20802
21601
|
}
|
|
20803
21602
|
function rowPaths(files, root) {
|
|
20804
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
21603
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join39(root, ...row2.path.split("/"))));
|
|
20805
21604
|
}
|
|
20806
21605
|
function runApply(context, scope, backfillDir, projectRoot2) {
|
|
20807
21606
|
const options = checkOptions(context, projectRoot2);
|
|
@@ -20847,7 +21646,7 @@ async function runBackfill(context, seams) {
|
|
|
20847
21646
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
20848
21647
|
const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
20849
21648
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
20850
|
-
const backfillDir =
|
|
21649
|
+
const backfillDir = join39(scope.base, BACKFILL_PATH);
|
|
20851
21650
|
if (mode === "propose") {
|
|
20852
21651
|
await runPropose(context, scope, backfillDir, seams);
|
|
20853
21652
|
return;
|
|
@@ -20961,13 +21760,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
20961
21760
|
var check_default3 = createSkillCheckCommand();
|
|
20962
21761
|
|
|
20963
21762
|
// src/commands/skill/demote.ts
|
|
20964
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
20965
|
-
import { dirname as
|
|
21763
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync27, statSync as statSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
21764
|
+
import { dirname as dirname12, resolve as resolve15 } from "path";
|
|
20966
21765
|
|
|
20967
21766
|
// src/demote/apply.ts
|
|
20968
|
-
import { copyFileSync as
|
|
21767
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync12, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
|
|
20969
21768
|
import { tmpdir as tmpdir3 } from "os";
|
|
20970
|
-
import { dirname as
|
|
21769
|
+
import { dirname as dirname11, join as join40 } from "path";
|
|
20971
21770
|
var DEMOTION_ACTION_KINDS = [
|
|
20972
21771
|
"demoted",
|
|
20973
21772
|
"kept",
|
|
@@ -20988,13 +21787,13 @@ function learnedSkillName(path) {
|
|
|
20988
21787
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
20989
21788
|
}
|
|
20990
21789
|
function sourcePath(scope, path) {
|
|
20991
|
-
return
|
|
21790
|
+
return join40(scope.dir, ...path.split("/"));
|
|
20992
21791
|
}
|
|
20993
21792
|
function demotedPath(scope, path) {
|
|
20994
|
-
return
|
|
21793
|
+
return join40(scope.demotedDir, ...path.split("/"));
|
|
20995
21794
|
}
|
|
20996
21795
|
function promotedPath(scope, path) {
|
|
20997
|
-
return
|
|
21796
|
+
return join40(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
20998
21797
|
}
|
|
20999
21798
|
function takenInstinctIds(dir) {
|
|
21000
21799
|
let names;
|
|
@@ -21007,7 +21806,7 @@ function takenInstinctIds(dir) {
|
|
|
21007
21806
|
}
|
|
21008
21807
|
function readText4(path) {
|
|
21009
21808
|
try {
|
|
21010
|
-
return
|
|
21809
|
+
return statSync11(path).isFile() ? readFileSync26(path, "utf8") : null;
|
|
21011
21810
|
} catch {
|
|
21012
21811
|
return null;
|
|
21013
21812
|
}
|
|
@@ -21035,7 +21834,7 @@ function planKept(scope, row2, file) {
|
|
|
21035
21834
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
21036
21835
|
}
|
|
21037
21836
|
const to = promotedPath(scope, row2.path);
|
|
21038
|
-
if (existsSync12(to) || existsSync12(
|
|
21837
|
+
if (existsSync12(to) || existsSync12(dirname11(to))) {
|
|
21039
21838
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
21040
21839
|
}
|
|
21041
21840
|
return {
|
|
@@ -21049,7 +21848,7 @@ function planKept(scope, row2, file) {
|
|
|
21049
21848
|
};
|
|
21050
21849
|
}
|
|
21051
21850
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
21052
|
-
const path =
|
|
21851
|
+
const path = join40(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21053
21852
|
writeFileSync10(path, text, "utf8");
|
|
21054
21853
|
const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
21055
21854
|
return failureLines2(report4.issues);
|
|
@@ -21070,7 +21869,7 @@ function planDemoted(scope, row2, file, context) {
|
|
|
21070
21869
|
if (failures.length > 0) {
|
|
21071
21870
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
21072
21871
|
}
|
|
21073
|
-
const destination =
|
|
21872
|
+
const destination = join40(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21074
21873
|
const kept = demotedPath(scope, row2.path);
|
|
21075
21874
|
if (existsSync12(destination))
|
|
21076
21875
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
@@ -21106,7 +21905,7 @@ function planRow(scope, row2, context) {
|
|
|
21106
21905
|
entries: undefined,
|
|
21107
21906
|
absolute,
|
|
21108
21907
|
hash,
|
|
21109
|
-
mtime:
|
|
21908
|
+
mtime: statSync11(absolute).mtime.toISOString()
|
|
21110
21909
|
};
|
|
21111
21910
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
21112
21911
|
}
|
|
@@ -21117,7 +21916,7 @@ function countActions(actions) {
|
|
|
21117
21916
|
return counts;
|
|
21118
21917
|
}
|
|
21119
21918
|
function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
21120
|
-
const temp = mkdtempSync3(
|
|
21919
|
+
const temp = mkdtempSync3(join40(tmpdir3(), "rafa-demote-"));
|
|
21121
21920
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
21122
21921
|
try {
|
|
21123
21922
|
const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
@@ -21127,18 +21926,18 @@ function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
|
21127
21926
|
}
|
|
21128
21927
|
}
|
|
21129
21928
|
function moveFile(move3) {
|
|
21130
|
-
|
|
21929
|
+
mkdirSync10(dirname11(move3.to), { recursive: true });
|
|
21131
21930
|
try {
|
|
21132
21931
|
renameSync(move3.from, move3.to);
|
|
21133
21932
|
} catch {
|
|
21134
|
-
|
|
21933
|
+
copyFileSync5(move3.from, move3.to);
|
|
21135
21934
|
unlinkSync(move3.from);
|
|
21136
21935
|
}
|
|
21137
21936
|
}
|
|
21138
21937
|
function applyDemotion(plan2) {
|
|
21139
21938
|
for (const action of plan2.actions) {
|
|
21140
21939
|
if (action.write !== null) {
|
|
21141
|
-
|
|
21940
|
+
mkdirSync10(dirname11(action.write.path), { recursive: true });
|
|
21142
21941
|
writeFileSync10(action.write.path, action.write.text, "utf8");
|
|
21143
21942
|
}
|
|
21144
21943
|
if (action.move !== null)
|
|
@@ -21204,11 +22003,11 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
21204
22003
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
21205
22004
|
Usage: ${DEMOTE_USAGE}`);
|
|
21206
22005
|
}
|
|
21207
|
-
return
|
|
22006
|
+
return resolve15(cwd(), dir);
|
|
21208
22007
|
}
|
|
21209
22008
|
function isDirectory4(path) {
|
|
21210
22009
|
try {
|
|
21211
|
-
return
|
|
22010
|
+
return statSync12(path).isDirectory();
|
|
21212
22011
|
} catch {
|
|
21213
22012
|
return false;
|
|
21214
22013
|
}
|
|
@@ -21247,7 +22046,7 @@ function previousReport(path, context) {
|
|
|
21247
22046
|
function runWrite(context, scope) {
|
|
21248
22047
|
const files = selectSources(scope);
|
|
21249
22048
|
const report4 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
21250
|
-
|
|
22049
|
+
mkdirSync11(dirname12(scope.reportPath), { recursive: true });
|
|
21251
22050
|
writeFileSync11(scope.reportPath, writeDemotionReport(report4), "utf8");
|
|
21252
22051
|
const result2 = {
|
|
21253
22052
|
applied: false,
|
|
@@ -21381,7 +22180,7 @@ var demote_default = createSkillDemoteCommand();
|
|
|
21381
22180
|
// src/commands/skill/show.ts
|
|
21382
22181
|
var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
|
|
21383
22182
|
var COMMAND_NAME13 = "rafa skill show";
|
|
21384
|
-
var
|
|
22183
|
+
var USAGE42 = "rafa skill show <name> [--full]";
|
|
21385
22184
|
function unknownSkillMessage(name, inventory) {
|
|
21386
22185
|
const agent = findShown(inventory.records, "agent", name);
|
|
21387
22186
|
const hint = agent === null ? [] : [`An agent is named "${name}" (${agent.source}): rafa agent show ${name}`];
|
|
@@ -21389,7 +22188,7 @@ function unknownSkillMessage(name, inventory) {
|
|
|
21389
22188
|
`\u274C No skill is named "${name}".`,
|
|
21390
22189
|
...hint,
|
|
21391
22190
|
"Run `rafa skill list` to see every skill and where it comes from.",
|
|
21392
|
-
`Usage: ${
|
|
22191
|
+
`Usage: ${USAGE42}`
|
|
21393
22192
|
].join(`
|
|
21394
22193
|
`);
|
|
21395
22194
|
}
|
|
@@ -21405,8 +22204,8 @@ function projectOf13(context) {
|
|
|
21405
22204
|
return context.project;
|
|
21406
22205
|
}
|
|
21407
22206
|
async function runShow3(context, seams) {
|
|
21408
|
-
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${
|
|
21409
|
-
const name = expectOneArgument(context.args,
|
|
22207
|
+
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE42}`);
|
|
22208
|
+
const name = expectOneArgument(context.args, USAGE42);
|
|
21410
22209
|
const project = projectOf13(context);
|
|
21411
22210
|
const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME13);
|
|
21412
22211
|
const warnings = warningLines(inventory);
|
|
@@ -21506,7 +22305,7 @@ var STATUS_SECTION_TITLES = Object.freeze({
|
|
|
21506
22305
|
claims: "Claims",
|
|
21507
22306
|
housekeeping: "Housekeeping"
|
|
21508
22307
|
});
|
|
21509
|
-
var
|
|
22308
|
+
var INDENT11 = " ";
|
|
21510
22309
|
var MERGEABILITY_WORDS = Object.freeze({
|
|
21511
22310
|
mergeable: "mergeable",
|
|
21512
22311
|
conflicting: "conflicting",
|
|
@@ -21518,8 +22317,8 @@ var VERDICT_WORDS = Object.freeze({
|
|
|
21518
22317
|
pending: "checks pending",
|
|
21519
22318
|
none: "no checks"
|
|
21520
22319
|
});
|
|
21521
|
-
function counted3(count, noun,
|
|
21522
|
-
return `${String(count)} ${count === 1 ? noun :
|
|
22320
|
+
function counted3(count, noun, plural4 = `${noun}s`) {
|
|
22321
|
+
return `${String(count)} ${count === 1 ? noun : plural4}`;
|
|
21523
22322
|
}
|
|
21524
22323
|
function info2(text) {
|
|
21525
22324
|
return { level: "info", text };
|
|
@@ -21536,10 +22335,10 @@ function loopsText(reading) {
|
|
|
21536
22335
|
return `${String(reading.live.length)} running, ${counted3(tasks, "task")} blocked`;
|
|
21537
22336
|
}
|
|
21538
22337
|
function loopsBody(reading) {
|
|
21539
|
-
const running = reading.live.map((record) => info2(`${
|
|
22338
|
+
const running = reading.live.map((record) => info2(`${INDENT11}${sessionLine(record)}`));
|
|
21540
22339
|
const blocked = reading.blocked.flatMap((one) => one.tasks.map((task) => {
|
|
21541
22340
|
const blocker = task.blocker === null ? "" : ` (${task.blocker})`;
|
|
21542
|
-
return info2(`${
|
|
22341
|
+
return info2(`${INDENT11}blocked: plan \`${planLabel(one.session)}\` line ${String(task.line)}: ${task.text}${blocker}`);
|
|
21543
22342
|
}));
|
|
21544
22343
|
return [...running, ...blocked];
|
|
21545
22344
|
}
|
|
@@ -21567,10 +22366,10 @@ function boardBody(reading) {
|
|
|
21567
22366
|
return [];
|
|
21568
22367
|
const away = awayLine(place2.current, place2.home);
|
|
21569
22368
|
return [
|
|
21570
|
-
info2(`${
|
|
21571
|
-
...away === null ? [] : [info2(`${
|
|
21572
|
-
...place2.waiting === undefined ? [] : [info2(`${
|
|
21573
|
-
...place2.notices.map((notice) => ({ level: "warn", text: `${
|
|
22369
|
+
info2(`${INDENT11}${placeLine2(place2.view)}`),
|
|
22370
|
+
...away === null ? [] : [info2(`${INDENT11}${away}`)],
|
|
22371
|
+
...place2.waiting === undefined ? [] : [info2(`${INDENT11}${waitingLine(place2.waiting.issue)}`)],
|
|
22372
|
+
...place2.notices.map((notice) => ({ level: "warn", text: `${INDENT11}${notice}` }))
|
|
21574
22373
|
];
|
|
21575
22374
|
}
|
|
21576
22375
|
function nextStanding(next) {
|
|
@@ -21608,7 +22407,7 @@ function claimLine(row2) {
|
|
|
21608
22407
|
return `#${String(row2.issue)} \`${row2.branch}\`: ${claimHolder(row2)}, ${claimStage(row2.stage)}, ${claimStanding(row2)}`;
|
|
21609
22408
|
}
|
|
21610
22409
|
function claimsBody(reading) {
|
|
21611
|
-
return reading.claims.map((row2) => info2(`${
|
|
22410
|
+
return reading.claims.map((row2) => info2(`${INDENT11}${claimLine(row2)}`));
|
|
21612
22411
|
}
|
|
21613
22412
|
function housekeepingText(reading) {
|
|
21614
22413
|
const { merged: merged2, stale, notPushed, worktrees } = reading.counts;
|
|
@@ -21698,7 +22497,7 @@ function labelsOf2(labels, issue2) {
|
|
|
21698
22497
|
return null;
|
|
21699
22498
|
return labels.issues.find((one) => one.number === issue2)?.labels ?? null;
|
|
21700
22499
|
}
|
|
21701
|
-
function
|
|
22500
|
+
function rowOf4(reading, input) {
|
|
21702
22501
|
const { ownership, tipCommittedAt, branch } = reading;
|
|
21703
22502
|
const issue2 = claimBranchIssue(branch) ?? 0;
|
|
21704
22503
|
const labels = labelsOf2(input.labels, issue2);
|
|
@@ -21724,7 +22523,7 @@ function labelNotes(rows, labels) {
|
|
|
21724
22523
|
return rows.filter((row2) => row2.stage === null).map((row2) => `#${String(row2.issue)} is not in the board listing, so its stage labels were not read`);
|
|
21725
22524
|
}
|
|
21726
22525
|
function readClaims2(input) {
|
|
21727
|
-
const claims = input.branches.found.map((reading) =>
|
|
22526
|
+
const claims = input.branches.found.map((reading) => rowOf4(reading, input));
|
|
21728
22527
|
return { claims, notes: [...input.branches.notes, ...labelNotes(claims, input.labels)] };
|
|
21729
22528
|
}
|
|
21730
22529
|
|
|
@@ -21815,9 +22614,9 @@ function boundedRunner(open, root, deadline) {
|
|
|
21815
22614
|
}
|
|
21816
22615
|
async function networkSection(what, deadline, work) {
|
|
21817
22616
|
let timer;
|
|
21818
|
-
const expired = new Promise((
|
|
22617
|
+
const expired = new Promise((resolve16) => {
|
|
21819
22618
|
timer = setTimeout(() => {
|
|
21820
|
-
|
|
22619
|
+
resolve16(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
21821
22620
|
}, Math.max(0, deadline.left()));
|
|
21822
22621
|
});
|
|
21823
22622
|
try {
|
|
@@ -22109,7 +22908,7 @@ var board_default = createUpdateStub({ action: "board", summary: "apply the boar
|
|
|
22109
22908
|
|
|
22110
22909
|
// src/project/lock.ts
|
|
22111
22910
|
import { existsSync as existsSync15, lstatSync as lstatSync2, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
|
|
22112
|
-
import { join as
|
|
22911
|
+
import { join as join41 } from "path";
|
|
22113
22912
|
var LOCK_FILE = "rafa.lock";
|
|
22114
22913
|
var LOCKFILE_VERSION = 1;
|
|
22115
22914
|
|
|
@@ -22117,7 +22916,7 @@ class ProjectLockError extends Error {
|
|
|
22117
22916
|
name = "ProjectLockError";
|
|
22118
22917
|
}
|
|
22119
22918
|
function lockPath(root) {
|
|
22120
|
-
return
|
|
22919
|
+
return join41(root, LOCK_FILE);
|
|
22121
22920
|
}
|
|
22122
22921
|
function projectLockText(version2) {
|
|
22123
22922
|
const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
|
|
@@ -22175,10 +22974,10 @@ function writeProjectLock(root, version2) {
|
|
|
22175
22974
|
|
|
22176
22975
|
// src/project/update-current.ts
|
|
22177
22976
|
import { existsSync as existsSync16 } from "fs";
|
|
22178
|
-
import { join as
|
|
22977
|
+
import { join as join42 } from "path";
|
|
22179
22978
|
var DEPRECATIONS = Object.freeze([]);
|
|
22180
22979
|
function missingFolders(root) {
|
|
22181
|
-
return PROJECT_TREE.map((name) =>
|
|
22980
|
+
return PROJECT_TREE.map((name) => join42(SCOPE_DIR, name)).filter((relative11) => !existsSync16(join42(root, relative11)));
|
|
22182
22981
|
}
|
|
22183
22982
|
async function readBoard3(access) {
|
|
22184
22983
|
if (access.gh === null)
|
|
@@ -22231,7 +23030,7 @@ class UpdateStepError extends Error {
|
|
|
22231
23030
|
}
|
|
22232
23031
|
}
|
|
22233
23032
|
function foldersNowThere(plan2) {
|
|
22234
|
-
return plan2.folders.filter((
|
|
23033
|
+
return plan2.folders.filter((relative11) => existsSync16(join42(plan2.root, relative11)));
|
|
22235
23034
|
}
|
|
22236
23035
|
function writeFolders(plan2) {
|
|
22237
23036
|
if (plan2.folders.length === 0)
|
|
@@ -22285,7 +23084,7 @@ function readCurrentRange(recorded, installed) {
|
|
|
22285
23084
|
}
|
|
22286
23085
|
|
|
22287
23086
|
// src/commands/update/current.ts
|
|
22288
|
-
var
|
|
23087
|
+
var USAGE43 = "rafa update current [--dry-run] [--yes]";
|
|
22289
23088
|
var APPLY_QUESTION = "Apply these changes? [y/N] ";
|
|
22290
23089
|
var YES_ANSWERS5 = ["y", "yes"];
|
|
22291
23090
|
function checkoutLockRoot(root, dir) {
|
|
@@ -22310,7 +23109,7 @@ function readSwitch4(context, name) {
|
|
|
22310
23109
|
if (value === true || value === "true")
|
|
22311
23110
|
return true;
|
|
22312
23111
|
throw new CommandExit(1, `\u274C --${name} takes no value, and read "${String(value)}" as one
|
|
22313
|
-
Usage: ${
|
|
23112
|
+
Usage: ${USAGE43}`);
|
|
22314
23113
|
}
|
|
22315
23114
|
function couldNotRun(problem2) {
|
|
22316
23115
|
return new CommandExit(2, `FAIL \u2014 ${problem2}
|
|
@@ -22446,7 +23245,7 @@ function stopLine2(plan2, dryRun) {
|
|
|
22446
23245
|
return null;
|
|
22447
23246
|
}
|
|
22448
23247
|
async function runUpdateCurrent(context, seams) {
|
|
22449
|
-
expectNoArgument(context.args,
|
|
23248
|
+
expectNoArgument(context.args, USAGE43);
|
|
22450
23249
|
const dryRun = readSwitch4(context, "dry-run");
|
|
22451
23250
|
const yes = readSwitch4(context, "yes");
|
|
22452
23251
|
const project = projectOf15(context);
|
|
@@ -22520,24 +23319,6 @@ var rafa_default = createUpdateStub({ action: "rafa", summary: "move this projec
|
|
|
22520
23319
|
// src/commands/update/self.ts
|
|
22521
23320
|
var self_default = createUpdateStub({ action: "self", summary: "update rafa itself, from npm, a pinned version or another source", issue: 715 });
|
|
22522
23321
|
|
|
22523
|
-
// src/commands/usage.ts
|
|
22524
|
-
var usage_default = wrapPhaseZeroCommand({
|
|
22525
|
-
name: "usage",
|
|
22526
|
-
subject: "usage",
|
|
22527
|
-
action: "usage",
|
|
22528
|
-
summary: "show how much of the Claude usage limit is spent",
|
|
22529
|
-
description: "Prints the Claude usage percentage beside a bar and a level: OK below 70%, ELEVATED" + " from 70%, HIGH from 80% and CRITICAL from 90%. No live source is wired in yet, so the reading" + " is `CLAUDE_USAGE_PERCENT` when it is set, and unavailable otherwise.",
|
|
22530
|
-
args: [],
|
|
22531
|
-
flags: [],
|
|
22532
|
-
examples: [
|
|
22533
|
-
{
|
|
22534
|
-
cmd: "rafa usage",
|
|
22535
|
-
note: "Prints the usage bar, or says the reading is unavailable."
|
|
22536
|
-
}
|
|
22537
|
-
],
|
|
22538
|
-
outputs: ["text", "json"]
|
|
22539
|
-
}, usage);
|
|
22540
|
-
|
|
22541
23322
|
// src/commands/index.ts
|
|
22542
23323
|
var CORE_SUBJECTS = Object.freeze([
|
|
22543
23324
|
{ name: "plan", summary: "create plans from specs; list, show and validate them; read their risk and needs" },
|
|
@@ -22568,6 +23349,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
22568
23349
|
resume_default,
|
|
22569
23350
|
status_default2,
|
|
22570
23351
|
list_default7,
|
|
23352
|
+
wait_default,
|
|
22571
23353
|
list_default,
|
|
22572
23354
|
show_default4,
|
|
22573
23355
|
create_default,
|
|
@@ -22580,7 +23362,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
22580
23362
|
show_default6,
|
|
22581
23363
|
view_default,
|
|
22582
23364
|
list_default9,
|
|
22583
|
-
|
|
23365
|
+
wait_default2,
|
|
22584
23366
|
merge_default2,
|
|
22585
23367
|
triage_default,
|
|
22586
23368
|
collect_default,
|
|
@@ -22641,7 +23423,6 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
22641
23423
|
doctor_default,
|
|
22642
23424
|
cleanup_default,
|
|
22643
23425
|
self_update_default,
|
|
22644
|
-
usage_default,
|
|
22645
23426
|
describe_default
|
|
22646
23427
|
]);
|
|
22647
23428
|
var CORE_REGISTRY = createCommandRegistry({ subjects: CORE_SUBJECTS, commands: CORE_COMMANDS });
|