@open-tomato/rafa 0.37.0 → 0.38.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 +13 -0
- package/dist/cli.js +10723 -8410
- package/dist/{describe-5hqfwbjc.js → describe-pwhh0vws.js} +3 -3
- package/dist/effort/store/index.js +4 -4
- package/dist/{index-1mxv55ec.js → index-74btmn1e.js} +4 -4
- package/dist/{index-35ze56rz.js → index-e3vhhfka.js} +9499 -8344
- package/dist/{index-k6sd4ym5.js → index-eyjq63mx.js} +2 -2
- package/dist/{index-fzhd3d4y.js → index-m2ahxjw8.js} +5 -1
- package/dist/{index-b16va6he.js → index-mzcz139z.js} +1438 -384
- package/dist/{index-3mzw3kzh.js → index-sr3kc4mf.js} +1 -1
- package/dist/{index-jfas157k.js → index-w9naz372.js} +10 -1
- package/dist/{index-7bb0fdb7.js → index-zdz0mp63.js} +2 -2
- package/dist/index.js +10 -8
- package/dist/plan/index.js +3 -3
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BLOCKED_BY_FIELD,
|
|
5
5
|
BOARD_LABELS,
|
|
6
6
|
BOARD_LISTING_LIMIT,
|
|
7
|
+
BOARD_SYNC_FIX,
|
|
7
8
|
BRANCH_PREFIX,
|
|
8
9
|
CI_POLL_INTERVAL_MS,
|
|
9
10
|
CLAIMED_LABEL,
|
|
@@ -38,6 +39,8 @@ import {
|
|
|
38
39
|
NoSessionRefusal,
|
|
39
40
|
ORIGIN_FIELD,
|
|
40
41
|
PREVIOUS_COPY_WARN_ABOVE,
|
|
42
|
+
PROJECT_SCOPE_FIX,
|
|
43
|
+
PROJECT_WRITE_PAUSE_MS,
|
|
41
44
|
PROVENANCE_FIELD,
|
|
42
45
|
REASON_FLAG,
|
|
43
46
|
RELEASE_BASE_BRANCH,
|
|
@@ -117,6 +120,7 @@ import {
|
|
|
117
120
|
createGhBoardListing,
|
|
118
121
|
createGhIssueBoard,
|
|
119
122
|
createGhOpenPullRequests,
|
|
123
|
+
createGhProjectPort,
|
|
120
124
|
createGhPullRequests,
|
|
121
125
|
createGhRoadmapBody,
|
|
122
126
|
createGhRoadmapSearch,
|
|
@@ -181,6 +185,7 @@ import {
|
|
|
181
185
|
invalidateRows,
|
|
182
186
|
isFirstDispatch,
|
|
183
187
|
isLive,
|
|
188
|
+
isMissingProjectScope,
|
|
184
189
|
isNotPlanned,
|
|
185
190
|
isStackList,
|
|
186
191
|
isWaiting,
|
|
@@ -199,6 +204,7 @@ import {
|
|
|
199
204
|
localDay,
|
|
200
205
|
localPlanNotes,
|
|
201
206
|
makeOwnershipCommit,
|
|
207
|
+
matchProjectFields,
|
|
202
208
|
memoiseVerifier,
|
|
203
209
|
mergeGuardSettings,
|
|
204
210
|
missingBoardLabels,
|
|
@@ -212,6 +218,7 @@ import {
|
|
|
212
218
|
nextQuestion,
|
|
213
219
|
noBoardListsLine,
|
|
214
220
|
normaliseReason,
|
|
221
|
+
notFoundWarning,
|
|
215
222
|
notesFileName,
|
|
216
223
|
onTracker,
|
|
217
224
|
openBoards,
|
|
@@ -237,6 +244,8 @@ import {
|
|
|
237
244
|
pushOwnershipCommit,
|
|
238
245
|
pushResolved,
|
|
239
246
|
rankCandidates,
|
|
247
|
+
rankOf,
|
|
248
|
+
rateLimitWarning,
|
|
240
249
|
readAgentDirectory,
|
|
241
250
|
readBinPath,
|
|
242
251
|
readBlockedBy,
|
|
@@ -260,6 +269,7 @@ import {
|
|
|
260
269
|
readEpics,
|
|
261
270
|
readGuard,
|
|
262
271
|
readHomeAfterLoop,
|
|
272
|
+
readIssueFacts,
|
|
263
273
|
readListedEpics,
|
|
264
274
|
readManifestVersion,
|
|
265
275
|
readMergeGuard,
|
|
@@ -272,6 +282,7 @@ import {
|
|
|
272
282
|
readReason,
|
|
273
283
|
readReceiptVerdict,
|
|
274
284
|
readRecords,
|
|
285
|
+
readRefreshBoard,
|
|
275
286
|
readRefsText,
|
|
276
287
|
readRequiredFlag,
|
|
277
288
|
readRoadmapSetting,
|
|
@@ -289,6 +300,9 @@ import {
|
|
|
289
300
|
ready_default,
|
|
290
301
|
receiptProblem,
|
|
291
302
|
recordPlanCi,
|
|
303
|
+
refreshFailedWarning,
|
|
304
|
+
refreshIssueItems,
|
|
305
|
+
refreshProjectItems,
|
|
292
306
|
refusal,
|
|
293
307
|
refuseMisplacedRuntime,
|
|
294
308
|
releaseCommitSubject,
|
|
@@ -329,6 +343,7 @@ import {
|
|
|
329
343
|
runIssueList,
|
|
330
344
|
samePlace,
|
|
331
345
|
scanClaimBranches,
|
|
346
|
+
scopeWarning,
|
|
332
347
|
selectBoardRelations,
|
|
333
348
|
self_update_default,
|
|
334
349
|
serializeFragment,
|
|
@@ -345,6 +360,7 @@ import {
|
|
|
345
360
|
sourceItem,
|
|
346
361
|
stackPaths,
|
|
347
362
|
stacksFromEccSkillName,
|
|
363
|
+
stageOf,
|
|
348
364
|
start,
|
|
349
365
|
status_default,
|
|
350
366
|
tickLine,
|
|
@@ -374,7 +390,7 @@ import {
|
|
|
374
390
|
worktreesHolding,
|
|
375
391
|
writeSession,
|
|
376
392
|
writeShippedFile
|
|
377
|
-
} from "./index-
|
|
393
|
+
} from "./index-e3vhhfka.js";
|
|
378
394
|
import {
|
|
379
395
|
PROBE_TIMEOUT_MS,
|
|
380
396
|
loadPlanPrerequisites,
|
|
@@ -386,7 +402,7 @@ import {
|
|
|
386
402
|
runPreflight,
|
|
387
403
|
splitBlockerComment,
|
|
388
404
|
trackerPathFor
|
|
389
|
-
} from "./index-
|
|
405
|
+
} from "./index-zdz0mp63.js";
|
|
390
406
|
import {
|
|
391
407
|
ACTION_HEADING,
|
|
392
408
|
CAUSE_HEADING,
|
|
@@ -492,7 +508,7 @@ import {
|
|
|
492
508
|
versionLine,
|
|
493
509
|
writeFrontmatter,
|
|
494
510
|
writePositionFile
|
|
495
|
-
} from "./index-
|
|
511
|
+
} from "./index-74btmn1e.js";
|
|
496
512
|
import {
|
|
497
513
|
CONFIG_DEFAULTS,
|
|
498
514
|
CONFIG_FILE,
|
|
@@ -507,7 +523,7 @@ import {
|
|
|
507
523
|
readItemBytes,
|
|
508
524
|
readRafaBlocks,
|
|
509
525
|
resolveTiers
|
|
510
|
-
} from "./index-
|
|
526
|
+
} from "./index-w9naz372.js";
|
|
511
527
|
import {
|
|
512
528
|
promotable
|
|
513
529
|
} from "./index-d6d0942q.js";
|
|
@@ -515,10 +531,10 @@ import {
|
|
|
515
531
|
createCommandRegistry,
|
|
516
532
|
describeRegistry,
|
|
517
533
|
mountKey
|
|
518
|
-
} from "./index-
|
|
534
|
+
} from "./index-eyjq63mx.js";
|
|
519
535
|
import {
|
|
520
536
|
CommandExit
|
|
521
|
-
} from "./index-
|
|
537
|
+
} from "./index-sr3kc4mf.js";
|
|
522
538
|
import {
|
|
523
539
|
CLAIMS_STALE_DISABLED,
|
|
524
540
|
MERGE_METHODS,
|
|
@@ -531,7 +547,7 @@ import {
|
|
|
531
547
|
isSkillTier,
|
|
532
548
|
messageOf,
|
|
533
549
|
parseBudgetUsd
|
|
534
|
-
} from "./index-
|
|
550
|
+
} from "./index-m2ahxjw8.js";
|
|
535
551
|
import {
|
|
536
552
|
triggerKey
|
|
537
553
|
} from "./index-wp8c28fd.js";
|
|
@@ -3261,8 +3277,187 @@ function createBoardListCommand(seams = {}) {
|
|
|
3261
3277
|
}
|
|
3262
3278
|
var list_default5 = createBoardListCommand();
|
|
3263
3279
|
|
|
3280
|
+
// src/board/project/sync.ts
|
|
3281
|
+
var WHOLE_PROJECT = Object.freeze({ everyItem: true, openIssues: true });
|
|
3282
|
+
function addedWrites(first, second) {
|
|
3283
|
+
return {
|
|
3284
|
+
written: first.written + second.written,
|
|
3285
|
+
notUpdated: first.notUpdated + second.notUpdated,
|
|
3286
|
+
rateLimited: first.rateLimited || second.rateLimited,
|
|
3287
|
+
detail: [first.detail, second.detail].filter((detail) => detail !== "").join(`
|
|
3288
|
+
`)
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
async function addIssues(options, ref, issues) {
|
|
3292
|
+
const repository = await readBoardRepository(options.gh);
|
|
3293
|
+
const port = createGhProjectPort(options.gh);
|
|
3294
|
+
const project = await port.find(ref);
|
|
3295
|
+
if (project === null)
|
|
3296
|
+
return null;
|
|
3297
|
+
for (const number of issues)
|
|
3298
|
+
await port.addItem(project.id, { repository, number });
|
|
3299
|
+
return issues;
|
|
3300
|
+
}
|
|
3301
|
+
async function addMissing(options, first, synced) {
|
|
3302
|
+
if (synced.dryRun || first.missing.length === 0 || first.writes.rateLimited)
|
|
3303
|
+
return synced;
|
|
3304
|
+
try {
|
|
3305
|
+
const added = await addIssues(options, first.project, first.missing);
|
|
3306
|
+
if (added === null)
|
|
3307
|
+
return { kind: "not-found", project: first.project, warnings: [notFoundWarning(first.project)] };
|
|
3308
|
+
const second = await refreshProjectItems(options, added);
|
|
3309
|
+
if (second.kind !== "refreshed")
|
|
3310
|
+
return second;
|
|
3311
|
+
return {
|
|
3312
|
+
...synced,
|
|
3313
|
+
changes: [...first.changes, ...second.changes],
|
|
3314
|
+
added,
|
|
3315
|
+
writes: addedWrites(first.writes, second.writes),
|
|
3316
|
+
warnings: [...first.warnings, ...second.warnings.filter((line) => !first.warnings.includes(line))]
|
|
3317
|
+
};
|
|
3318
|
+
} catch (error) {
|
|
3319
|
+
if (isMissingProjectScope(error))
|
|
3320
|
+
return { kind: "refused", reason: "scope", project: first.project, warnings: [scopeWarning()] };
|
|
3321
|
+
throw error;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
async function syncProject(options) {
|
|
3325
|
+
const first = await refreshProjectItems(options, [], WHOLE_PROJECT);
|
|
3326
|
+
if (first.kind !== "refreshed")
|
|
3327
|
+
return first;
|
|
3328
|
+
return addMissing(options, first, {
|
|
3329
|
+
kind: "synced",
|
|
3330
|
+
project: first.project,
|
|
3331
|
+
dryRun: options.dryRun === true,
|
|
3332
|
+
changes: first.changes,
|
|
3333
|
+
missing: first.missing,
|
|
3334
|
+
added: [],
|
|
3335
|
+
writes: first.writes,
|
|
3336
|
+
warnings: first.warnings
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// src/commands/board/sync.ts
|
|
3341
|
+
var USAGE7 = "rafa board sync [--dry-run]";
|
|
3342
|
+
var DRY_RUN_FLAG = "dry-run";
|
|
3343
|
+
var BOARD_SYNC_REFUSAL_EXIT = 2;
|
|
3344
|
+
var PROJECT_INIT_FIX = "rafa init --board --project";
|
|
3345
|
+
var EMPTY_VALUE = "(empty)";
|
|
3346
|
+
function counted(count, noun) {
|
|
3347
|
+
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
3348
|
+
}
|
|
3349
|
+
function changeLine(change) {
|
|
3350
|
+
return `#${String(change.issue)} ${change.name}: ${change.from ?? EMPTY_VALUE} \u2192 ${change.to ?? EMPTY_VALUE}`;
|
|
3351
|
+
}
|
|
3352
|
+
function closingLine(result) {
|
|
3353
|
+
const project = `project #${String(result.project.number)}`;
|
|
3354
|
+
const adds = result.dryRun ? result.missing : result.added;
|
|
3355
|
+
const changes = counted(result.changes.length, "change");
|
|
3356
|
+
const issues = counted(adds.length, "issue");
|
|
3357
|
+
if (result.dryRun) {
|
|
3358
|
+
return result.changes.length === 0 && adds.length === 0 ? `Dry run on ${project}: in step, nothing to change.` : `Dry run on ${project}: ${changes} and ${issues} to add; nothing written.`;
|
|
3359
|
+
}
|
|
3360
|
+
if (result.rateLimited)
|
|
3361
|
+
return `Stopped on ${project}: ${String(result.written)} of ${changes} written; the rate limit refused the rest.`;
|
|
3362
|
+
return result.changes.length === 0 && adds.length === 0 ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added.`;
|
|
3363
|
+
}
|
|
3364
|
+
function syncLines(result) {
|
|
3365
|
+
const adds = result.dryRun ? result.missing.map((issue) => `#${String(issue)} is not on the project: would be added`) : result.added.map((issue) => `#${String(issue)} added to the project`);
|
|
3366
|
+
return [...result.changes.map(changeLine), ...adds, closingLine(result)];
|
|
3367
|
+
}
|
|
3368
|
+
function plainChange(change) {
|
|
3369
|
+
return { issue: change.issue, field: change.field, name: change.name, from: change.from, to: change.to };
|
|
3370
|
+
}
|
|
3371
|
+
function syncResultOf(synced) {
|
|
3372
|
+
return {
|
|
3373
|
+
project: synced.project,
|
|
3374
|
+
dryRun: synced.dryRun,
|
|
3375
|
+
changes: synced.changes.map(plainChange),
|
|
3376
|
+
missing: synced.missing,
|
|
3377
|
+
added: synced.added,
|
|
3378
|
+
written: synced.writes.written,
|
|
3379
|
+
rateLimited: synced.writes.rateLimited,
|
|
3380
|
+
notUpdated: synced.writes.notUpdated,
|
|
3381
|
+
warnings: synced.warnings
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
async function runBoardSync(context, seams) {
|
|
3385
|
+
if (context.args.length > 0) {
|
|
3386
|
+
throw lineRefusal(`Expected no arguments, got ${String(context.args.length)}: ${context.args.join(" ")}`, USAGE7);
|
|
3387
|
+
}
|
|
3388
|
+
const project = issueProject(context);
|
|
3389
|
+
const config = issueSubjectConfig(project, (message) => {
|
|
3390
|
+
context.output.warn(message);
|
|
3391
|
+
});
|
|
3392
|
+
if (config.boardProjectNumber === null) {
|
|
3393
|
+
throw new CommandExit(1, `\u274C board.project.number is not set, so there is no project to sync. Run \`${PROJECT_INIT_FIX}\` to create one.`);
|
|
3394
|
+
}
|
|
3395
|
+
const gh = seams.gh ?? createGhRunner({ cwd: project.root });
|
|
3396
|
+
const dryRun = context.flags[DRY_RUN_FLAG] === true;
|
|
3397
|
+
const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun } : { config, gh, dryRun, sleep: seams.sleep }).catch((error) => {
|
|
3398
|
+
throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C Could not sync the project: ${messageOf(error)}`);
|
|
3399
|
+
});
|
|
3400
|
+
if (synced.kind !== "synced") {
|
|
3401
|
+
throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C ${synced.warnings.join(`
|
|
3402
|
+
`)}`);
|
|
3403
|
+
}
|
|
3404
|
+
return syncResultOf(synced);
|
|
3405
|
+
}
|
|
3406
|
+
function writeResult(context, result) {
|
|
3407
|
+
const refusal3 = rateLimitWarning(result.notUpdated);
|
|
3408
|
+
if (context.outputMode !== "json") {
|
|
3409
|
+
for (const line of syncLines(result))
|
|
3410
|
+
context.output.info(line);
|
|
3411
|
+
}
|
|
3412
|
+
for (const line of result.warnings) {
|
|
3413
|
+
if (!result.rateLimited || line !== refusal3)
|
|
3414
|
+
context.output.warn(line);
|
|
3415
|
+
}
|
|
3416
|
+
if (result.rateLimited)
|
|
3417
|
+
throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C ${refusal3}`);
|
|
3418
|
+
if (context.outputMode === "json")
|
|
3419
|
+
context.output.result(result);
|
|
3420
|
+
}
|
|
3421
|
+
function createBoardSyncCommand(seams = {}) {
|
|
3422
|
+
const command = {
|
|
3423
|
+
name: "sync",
|
|
3424
|
+
subject: "board",
|
|
3425
|
+
action: "sync",
|
|
3426
|
+
summary: "bring the repository's GitHub project in step with its issues",
|
|
3427
|
+
description: "Refreshes every item of this repository on the GitHub project board.project.number names, writing" + " only the Stage, Horizon, Rank, Blocked by and Progress values that differ from what rafa reads of the" + " issues, then adds every open issue missing from the project and fills its fields. It repairs what changed" + " outside rafa: a label edited in the web UI, an issue closed by hand, the Roadmap issue's order edited in" + " its body. Prints one line per change and per issue added, then a closing count. With `--dry-run` it" + " prints the same lines and writes nothing. With `--output=json` the changes are the data of the terminal" + " result event. Refused with exit code 1 when board.project.number is unset, and with exit code 2 when the" + " token lacks the project scope, the number names no project, or the rate limit stopped the writes.",
|
|
3428
|
+
args: [],
|
|
3429
|
+
flags: [
|
|
3430
|
+
{
|
|
3431
|
+
name: DRY_RUN_FLAG,
|
|
3432
|
+
description: "Print each change the sync would make and each issue it would add, and write nothing.",
|
|
3433
|
+
type: "boolean"
|
|
3434
|
+
}
|
|
3435
|
+
],
|
|
3436
|
+
examples: [
|
|
3437
|
+
{
|
|
3438
|
+
cmd: "rafa board sync --dry-run",
|
|
3439
|
+
note: "Shows what drifted on the project since rafa last wrote it, writing nothing."
|
|
3440
|
+
},
|
|
3441
|
+
{
|
|
3442
|
+
cmd: "rafa board sync",
|
|
3443
|
+
note: "Writes those changes and adds the open issues the project lacks."
|
|
3444
|
+
},
|
|
3445
|
+
{
|
|
3446
|
+
cmd: "rafa board sync --output=json",
|
|
3447
|
+
note: "Gives the changes, the issues added and the counts as the terminal result."
|
|
3448
|
+
}
|
|
3449
|
+
],
|
|
3450
|
+
outputs: ["text", "json"],
|
|
3451
|
+
run: async (context) => {
|
|
3452
|
+
writeResult(context, await runBoardSync(context, seams));
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
return Object.freeze(command);
|
|
3456
|
+
}
|
|
3457
|
+
var sync_default = createBoardSyncCommand();
|
|
3458
|
+
|
|
3264
3459
|
// src/commands/claim/release.ts
|
|
3265
|
-
var
|
|
3460
|
+
var USAGE8 = "rafa claim release <n>";
|
|
3266
3461
|
var COMMAND = "rafa claim release";
|
|
3267
3462
|
var ISSUE_NUMBER2 = /^[1-9]\d*$/;
|
|
3268
3463
|
var RELEASING = { during: "was being released", nothing: "nothing was released" };
|
|
@@ -3344,7 +3539,7 @@ function pushOnClaimTip(git, reading, record, refuseWith, doing) {
|
|
|
3344
3539
|
return made.sha;
|
|
3345
3540
|
}
|
|
3346
3541
|
async function releaseClaim(context, makeSeams) {
|
|
3347
|
-
const issue = readIssueArgument(context.args,
|
|
3542
|
+
const issue = readIssueArgument(context.args, USAGE8);
|
|
3348
3543
|
const project = requireProject(context, COMMAND);
|
|
3349
3544
|
const config = resolveProjectConfig(project, COMMAND, (message) => context.output.warn(message));
|
|
3350
3545
|
const seams = makeSeams(project.root, config);
|
|
@@ -3402,7 +3597,7 @@ function createClaimReleaseCommand(makeSeams = DEFAULT_SEAMS) {
|
|
|
3402
3597
|
var release_default = createClaimReleaseCommand();
|
|
3403
3598
|
|
|
3404
3599
|
// src/commands/claim/accept.ts
|
|
3405
|
-
var
|
|
3600
|
+
var USAGE9 = "rafa claim accept <n>";
|
|
3406
3601
|
var COMMAND2 = "rafa claim accept";
|
|
3407
3602
|
var ACCEPTING = { during: "was being accepted", nothing: "nothing was accepted" };
|
|
3408
3603
|
function refuse2(message) {
|
|
@@ -3475,7 +3670,7 @@ ${reasons.map((reason) => ` ${reason}`).join(`
|
|
|
3475
3670
|
`)}`);
|
|
3476
3671
|
}
|
|
3477
3672
|
function acceptClaim(context, makeSeams) {
|
|
3478
|
-
const issue = readIssueArgument(context.args,
|
|
3673
|
+
const issue = readIssueArgument(context.args, USAGE9);
|
|
3479
3674
|
const project = requireProject(context, COMMAND2);
|
|
3480
3675
|
const config = resolveProjectConfig(project, COMMAND2, (message) => context.output.warn(message));
|
|
3481
3676
|
const seams = makeSeams(project.root, config);
|
|
@@ -3530,7 +3725,7 @@ function createClaimAcceptCommand(makeSeams = DEFAULT_SEAMS2) {
|
|
|
3530
3725
|
var accept_default = createClaimAcceptCommand();
|
|
3531
3726
|
|
|
3532
3727
|
// src/commands/claim/hand.ts
|
|
3533
|
-
var
|
|
3728
|
+
var USAGE10 = "rafa claim hand <n> --to=<store id> | rafa claim hand <n> --withdraw";
|
|
3534
3729
|
var COMMAND3 = "rafa claim hand";
|
|
3535
3730
|
var TO_FLAG = "to";
|
|
3536
3731
|
var WITHDRAW_FLAG = "withdraw";
|
|
@@ -3541,7 +3736,7 @@ function refuse3(message) {
|
|
|
3541
3736
|
}
|
|
3542
3737
|
function lineRefusal3(message) {
|
|
3543
3738
|
return refuse3(`${message}
|
|
3544
|
-
Usage: ${
|
|
3739
|
+
Usage: ${USAGE10}`);
|
|
3545
3740
|
}
|
|
3546
3741
|
function readWithdraw(flags) {
|
|
3547
3742
|
const value = flags[WITHDRAW_FLAG];
|
|
@@ -3594,7 +3789,7 @@ function recordFor(reading, request, issue, storeId) {
|
|
|
3594
3789
|
}
|
|
3595
3790
|
function handClaim(context, makeSeams) {
|
|
3596
3791
|
const request = readHandRequest(context.flags);
|
|
3597
|
-
const issue = readIssueArgument(context.args,
|
|
3792
|
+
const issue = readIssueArgument(context.args, USAGE10);
|
|
3598
3793
|
const project = requireProject(context, COMMAND3);
|
|
3599
3794
|
const config = resolveProjectConfig(project, COMMAND3, (message) => context.output.warn(message));
|
|
3600
3795
|
const seams = makeSeams(project.root, config);
|
|
@@ -3669,7 +3864,7 @@ function createClaimHandCommand(makeSeams = DEFAULT_SEAMS3) {
|
|
|
3669
3864
|
var hand_default = createClaimHandCommand();
|
|
3670
3865
|
|
|
3671
3866
|
// src/commands/claim/take.ts
|
|
3672
|
-
var
|
|
3867
|
+
var USAGE11 = "rafa claim take <n> [--stale]";
|
|
3673
3868
|
var COMMAND4 = "rafa claim take";
|
|
3674
3869
|
var STALE_FLAG = "stale";
|
|
3675
3870
|
var TAKING = { during: "was being taken over", nothing: "nothing was taken" };
|
|
@@ -3683,7 +3878,7 @@ function readStaleFlag(flags) {
|
|
|
3683
3878
|
if (typeof value === "boolean")
|
|
3684
3879
|
return value;
|
|
3685
3880
|
throw refuse4(`--${STALE_FLAG} takes no value, and read "${value}" as one; type the issue number before the flags
|
|
3686
|
-
Usage: ${
|
|
3881
|
+
Usage: ${USAGE11}`);
|
|
3687
3882
|
}
|
|
3688
3883
|
function labelNote(weights) {
|
|
3689
3884
|
return weights.labelWarning === null ? "" : ` (${weights.labelWarning}, so it reads as in development)`;
|
|
@@ -3766,7 +3961,7 @@ ${reasons.map((reason) => ` ${reason}`).join(`
|
|
|
3766
3961
|
}
|
|
3767
3962
|
async function takeClaim(context, makeSeams) {
|
|
3768
3963
|
const stale = readStaleFlag(context.flags);
|
|
3769
|
-
const issue = readIssueArgument(context.args,
|
|
3964
|
+
const issue = readIssueArgument(context.args, USAGE11);
|
|
3770
3965
|
const project = requireProject(context, COMMAND4);
|
|
3771
3966
|
const config = resolveProjectConfig(project, COMMAND4, (message) => context.output.warn(message));
|
|
3772
3967
|
const seams = makeSeams(project.root, config);
|
|
@@ -4199,7 +4394,7 @@ function worktreeData(row) {
|
|
|
4199
4394
|
// src/commands/cleanup.ts
|
|
4200
4395
|
var COMMAND_NAME4 = "rafa cleanup";
|
|
4201
4396
|
var CLEANUP_USAGE = "rafa cleanup [--dry-run]";
|
|
4202
|
-
var
|
|
4397
|
+
var DRY_RUN_FLAG2 = "dry-run";
|
|
4203
4398
|
var CLEANUP_MESSAGE = "Tick what to remove (space ticks, a ticks a group, enter goes on)";
|
|
4204
4399
|
var NOTHING_LISTED_TEXT = "Nothing to clean up: no branch or worktree is listed.";
|
|
4205
4400
|
var NOTHING_REMOVED_TEXT = "Nothing removed.";
|
|
@@ -4207,14 +4402,14 @@ var DEFAULT_CLEANUP_SEAMS = Object.freeze({});
|
|
|
4207
4402
|
function isBranchRow(row) {
|
|
4208
4403
|
return "group" in row;
|
|
4209
4404
|
}
|
|
4210
|
-
function
|
|
4405
|
+
function counted2(count, noun, pluralNoun) {
|
|
4211
4406
|
return count === 1 ? `1 ${noun}` : `${String(count)} ${pluralNoun}`;
|
|
4212
4407
|
}
|
|
4213
4408
|
function cleanupQuestion(branches, worktrees) {
|
|
4214
|
-
return `Delete ${
|
|
4409
|
+
return `Delete ${counted2(branches, "branch", "branches")} and remove ${counted2(worktrees, "worktree", "worktrees")}? [y/N] `;
|
|
4215
4410
|
}
|
|
4216
4411
|
function notPushedQuestion(row) {
|
|
4217
|
-
const commits =
|
|
4412
|
+
const commits = counted2(row.commits, "commit", "commits");
|
|
4218
4413
|
return `${row.branch.name} holds ${commits} no remote has, which deleting it loses. Delete ${row.branch.name}? [y/N] `;
|
|
4219
4414
|
}
|
|
4220
4415
|
function worktreeLabel(row, width) {
|
|
@@ -4286,7 +4481,7 @@ function writeOutcomes(context, outcomes) {
|
|
|
4286
4481
|
const failed = outcomes.filter((outcome) => !outcome.ok).length;
|
|
4287
4482
|
if (failed === 0)
|
|
4288
4483
|
return;
|
|
4289
|
-
throw new CommandExit(1, `\u274C ${COMMAND_NAME4}: ${
|
|
4484
|
+
throw new CommandExit(1, `\u274C ${COMMAND_NAME4}: ${counted2(failed, "step", "steps")} of ${String(outcomes.length)} did not run clean; each is named above.`);
|
|
4290
4485
|
}
|
|
4291
4486
|
async function askAndRemove(run) {
|
|
4292
4487
|
const { context, read, seams } = run;
|
|
@@ -4338,7 +4533,7 @@ async function readOrRefuse(seams, cleanup, settings) {
|
|
|
4338
4533
|
}
|
|
4339
4534
|
async function runCleanup(context, seams) {
|
|
4340
4535
|
expectNoArgument(context.args, CLEANUP_USAGE);
|
|
4341
|
-
const dryRun = readSwitch(
|
|
4536
|
+
const dryRun = readSwitch(DRY_RUN_FLAG2, context.flags[DRY_RUN_FLAG2], `Usage: ${CLEANUP_USAGE}`);
|
|
4342
4537
|
const project = projectOf6(context);
|
|
4343
4538
|
const config = resolveProjectConfig(project, COMMAND_NAME4, (message) => {
|
|
4344
4539
|
context.output.warn(message);
|
|
@@ -4379,7 +4574,7 @@ function createCleanupCommand(seams = DEFAULT_CLEANUP_SEAMS) {
|
|
|
4379
4574
|
args: [],
|
|
4380
4575
|
flags: [
|
|
4381
4576
|
{
|
|
4382
|
-
name:
|
|
4577
|
+
name: DRY_RUN_FLAG2,
|
|
4383
4578
|
description: "Show the checklist and ask as usual, then print the `git` commands the answer would run" + " instead of running them. Removes nothing.",
|
|
4384
4579
|
type: "boolean"
|
|
4385
4580
|
}
|
|
@@ -6136,7 +6331,7 @@ function isUnmet(need) {
|
|
|
6136
6331
|
// src/commands/plan/needs.ts
|
|
6137
6332
|
import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve2 } from "path";
|
|
6138
6333
|
var COMMAND5 = "rafa plan needs";
|
|
6139
|
-
var
|
|
6334
|
+
var USAGE12 = "rafa plan needs [<plan> | --spec=<file> | --issue=<n>] [--missing] [--source=<source>]";
|
|
6140
6335
|
var MISSING_HINT = `Type the plan first: ${COMMAND5} <plan> --missing`;
|
|
6141
6336
|
var MET_MARK = "\u2713";
|
|
6142
6337
|
var UNMET_MARK = "\u2717";
|
|
@@ -6151,7 +6346,7 @@ function readNeedsSource(value) {
|
|
|
6151
6346
|
return value;
|
|
6152
6347
|
const shown = typeof value === "string" ? `"${value}"` : "given no value";
|
|
6153
6348
|
throw new CommandExit(1, `\u274C --source is ${shown}, expected one of: project, rafa, user, plugin:<name>, addon:<name>` + `
|
|
6154
|
-
Usage: ${
|
|
6349
|
+
Usage: ${USAGE12}`);
|
|
6155
6350
|
}
|
|
6156
6351
|
function specRouteWords(flags) {
|
|
6157
6352
|
const words = ["spec", "issue"].flatMap((name) => {
|
|
@@ -6174,7 +6369,7 @@ function needsPlanPath(root, planDir, typed) {
|
|
|
6174
6369
|
throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
|
|
6175
6370
|
const lookedFor = [...new Set([resolve2(root, planDir, DEFAULT_PLAN_FILE), resolve2(root, DEFAULT_PLAN_FILE)])];
|
|
6176
6371
|
throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
|
|
6177
|
-
Usage: ${
|
|
6372
|
+
Usage: ${USAGE12}`);
|
|
6178
6373
|
}
|
|
6179
6374
|
function fromSource(need, source) {
|
|
6180
6375
|
if (source === null)
|
|
@@ -6319,11 +6514,11 @@ async function routedSpec(words, project, config, seams) {
|
|
|
6319
6514
|
async function runNeeds(context, seams) {
|
|
6320
6515
|
const missing = readSwitch("missing", context.flags["missing"], MISSING_HINT);
|
|
6321
6516
|
const source = readNeedsSource(context.flags["source"]);
|
|
6322
|
-
const typed = expectAtMostOneArgument(context.args,
|
|
6517
|
+
const typed = expectAtMostOneArgument(context.args, USAGE12);
|
|
6323
6518
|
const words = specRouteWords(context.flags);
|
|
6324
6519
|
if (typed !== null && words !== null) {
|
|
6325
6520
|
throw new CommandExit(1, `\u274C ${typed} and ${words.join(" and ")} each name what to read; give one
|
|
6326
|
-
Usage: ${
|
|
6521
|
+
Usage: ${USAGE12}`);
|
|
6327
6522
|
}
|
|
6328
6523
|
const project = requireProject(context, COMMAND5);
|
|
6329
6524
|
const { config, needs } = await readMachine(project, context, seams);
|
|
@@ -6934,7 +7129,7 @@ function renderDeep(reading) {
|
|
|
6934
7129
|
}
|
|
6935
7130
|
|
|
6936
7131
|
// src/commands/doctor-description.ts
|
|
6937
|
-
var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
|
|
7132
|
+
var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. Where `board.project.number` is set, it then" + " prints, under `GitHub project:`, the `project` scope, the project at that number and its five fields as" + " present, missing or unknown, each one not present naming its fix and a project in place naming" + " `rafa board sync --dry-run`; it reads no item and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
|
|
6938
7133
|
|
|
6939
7134
|
// src/commands/doctor-effort-schema.ts
|
|
6940
7135
|
import { join as join12 } from "path";
|
|
@@ -7135,15 +7330,485 @@ function writeInstall(context, install) {
|
|
|
7135
7330
|
}
|
|
7136
7331
|
}
|
|
7137
7332
|
|
|
7333
|
+
// src/commands/init-board-project-setting.ts
|
|
7334
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
7335
|
+
var PROJECT_NUMBER_SETTING = "board.project.number";
|
|
7336
|
+
var BOARD_KEY = /^board:\s*$/u;
|
|
7337
|
+
var PROJECT_KEY = /^ {2}project:\s*$/u;
|
|
7338
|
+
var ANY_PROJECT_KEY = /^ {2}project:/u;
|
|
7339
|
+
var NUMBER_KEY = /^ {4}number:/u;
|
|
7340
|
+
var COMMENTED_BOARD_KEY = /^#\s*board:\s*$/u;
|
|
7341
|
+
var COMMENTED_CHILD = /^# {2,}\S/u;
|
|
7342
|
+
var TOP_LEVEL = /^[^\s#]/u;
|
|
7343
|
+
var ABOVE_SECOND_LEVEL = /^ {0,3}[^\s#]/u;
|
|
7344
|
+
function settingLines(number, withProject) {
|
|
7345
|
+
return withProject ? [" project:", numberLine(number)] : [numberLine(number)];
|
|
7346
|
+
}
|
|
7347
|
+
function insertedAfter(lines, at, inserted) {
|
|
7348
|
+
return [...lines.slice(0, at + 1), ...inserted, ...lines.slice(at + 1)];
|
|
7349
|
+
}
|
|
7350
|
+
function replacedAt(lines, at, line) {
|
|
7351
|
+
return lines.map((held, index) => index === at ? line : held);
|
|
7352
|
+
}
|
|
7353
|
+
function matching(pattern) {
|
|
7354
|
+
return (line) => pattern.test(line);
|
|
7355
|
+
}
|
|
7356
|
+
function matchingUncommented(pattern) {
|
|
7357
|
+
return (line) => pattern.test(line.replace(/^# /u, ""));
|
|
7358
|
+
}
|
|
7359
|
+
function indexAfter(lines, from, end, test) {
|
|
7360
|
+
const found = lines.slice(from + 1, end).findIndex(test);
|
|
7361
|
+
return found < 0 ? -1 : from + 1 + found;
|
|
7362
|
+
}
|
|
7363
|
+
function blockEnd(lines, open, end, closes) {
|
|
7364
|
+
const found = indexAfter(lines, open, end, closes);
|
|
7365
|
+
return found < 0 ? end : found;
|
|
7366
|
+
}
|
|
7367
|
+
function numberLine(number) {
|
|
7368
|
+
return ` number: ${String(number)}`;
|
|
7369
|
+
}
|
|
7370
|
+
function underOpenBoard(lines, open, number) {
|
|
7371
|
+
const end = blockEnd(lines, open, lines.length, matching(TOP_LEVEL));
|
|
7372
|
+
const project = indexAfter(lines, open, end, matching(PROJECT_KEY));
|
|
7373
|
+
if (project < 0) {
|
|
7374
|
+
return indexAfter(lines, open, end, matching(ANY_PROJECT_KEY)) < 0 ? insertedAfter(lines, open, settingLines(number, true)) : null;
|
|
7375
|
+
}
|
|
7376
|
+
const projectEnd = blockEnd(lines, project, end, matching(ABOVE_SECOND_LEVEL));
|
|
7377
|
+
const held = indexAfter(lines, project, projectEnd, matching(NUMBER_KEY));
|
|
7378
|
+
return held < 0 ? insertedAfter(lines, project, settingLines(number, false)) : replacedAt(lines, held, numberLine(number));
|
|
7379
|
+
}
|
|
7380
|
+
function underCommentedBoard(lines, open, number) {
|
|
7381
|
+
const opened = replacedAt(lines, open, "board:");
|
|
7382
|
+
const end = blockEnd(lines, open, lines.length, (line) => !COMMENTED_CHILD.test(line));
|
|
7383
|
+
const project = indexAfter(lines, open, end, matchingUncommented(PROJECT_KEY));
|
|
7384
|
+
if (project < 0)
|
|
7385
|
+
return insertedAfter(opened, open, settingLines(number, true));
|
|
7386
|
+
const projectEnd = blockEnd(lines, project, end, matchingUncommented(ABOVE_SECOND_LEVEL));
|
|
7387
|
+
const held = indexAfter(lines, project, projectEnd, matchingUncommented(NUMBER_KEY));
|
|
7388
|
+
const withProject = replacedAt(opened, project, " project:");
|
|
7389
|
+
return held < 0 ? insertedAfter(withProject, project, settingLines(number, false)) : replacedAt(withProject, held, numberLine(number));
|
|
7390
|
+
}
|
|
7391
|
+
function namesBoard(text) {
|
|
7392
|
+
let document;
|
|
7393
|
+
try {
|
|
7394
|
+
document = Bun.YAML.parse(text);
|
|
7395
|
+
} catch {
|
|
7396
|
+
return false;
|
|
7397
|
+
}
|
|
7398
|
+
return isMapping(document) && Object.hasOwn(document, "board");
|
|
7399
|
+
}
|
|
7400
|
+
function appended(lines, block) {
|
|
7401
|
+
const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
|
|
7402
|
+
return [...body, ...block, ""];
|
|
7403
|
+
}
|
|
7404
|
+
function withProjectNumber(text, number) {
|
|
7405
|
+
const lines = text.split(`
|
|
7406
|
+
`);
|
|
7407
|
+
const open = lines.findIndex((line) => BOARD_KEY.test(line));
|
|
7408
|
+
if (open >= 0)
|
|
7409
|
+
return underOpenBoard(lines, open, number)?.join(`
|
|
7410
|
+
`) ?? null;
|
|
7411
|
+
if (namesBoard(text))
|
|
7412
|
+
return null;
|
|
7413
|
+
const commented = lines.findIndex((line) => COMMENTED_BOARD_KEY.test(line));
|
|
7414
|
+
if (commented >= 0)
|
|
7415
|
+
return underCommentedBoard(lines, commented, number).join(`
|
|
7416
|
+
`);
|
|
7417
|
+
return appended(lines, ["board:", ...settingLines(number, true)]).join(`
|
|
7418
|
+
`);
|
|
7419
|
+
}
|
|
7420
|
+
function projectNumberIn(text, path) {
|
|
7421
|
+
return parseConfigText(text, path).values.boardProjectNumber ?? null;
|
|
7422
|
+
}
|
|
7423
|
+
function readBackProblem(text, written, path, number) {
|
|
7424
|
+
let before;
|
|
7425
|
+
let after;
|
|
7426
|
+
try {
|
|
7427
|
+
before = parseConfigText(text, path);
|
|
7428
|
+
after = parseConfigText(written, path);
|
|
7429
|
+
} catch (error) {
|
|
7430
|
+
return `does not parse: ${messageOf(error)}`;
|
|
7431
|
+
}
|
|
7432
|
+
const read = after.values.boardProjectNumber ?? null;
|
|
7433
|
+
if (read !== number)
|
|
7434
|
+
return `reads ${PROJECT_NUMBER_SETTING} as ${describeValue(read)}`;
|
|
7435
|
+
const others = (file) => ({ values: { ...file.values, boardProjectNumber: null }, extras: file.extras });
|
|
7436
|
+
return Bun.deepEquals(others(before), others(after), true) ? null : `reads a setting other than ${PROJECT_NUMBER_SETTING} differently, or a key no setting names`;
|
|
7437
|
+
}
|
|
7438
|
+
function settingPart(outcome, detail) {
|
|
7439
|
+
return { kind: "setting", name: PROJECT_NUMBER_SETTING, outcome, detail };
|
|
7440
|
+
}
|
|
7441
|
+
function writeProjectNumber(root, number) {
|
|
7442
|
+
const path = configFilePath(root);
|
|
7443
|
+
let text;
|
|
7444
|
+
let held;
|
|
7445
|
+
try {
|
|
7446
|
+
text = readFileSync6(path, "utf8");
|
|
7447
|
+
held = projectNumberIn(text, path);
|
|
7448
|
+
} catch (error) {
|
|
7449
|
+
return settingPart("refused", `${path} could not be read: ${messageOf(error)}`);
|
|
7450
|
+
}
|
|
7451
|
+
if (held === number)
|
|
7452
|
+
return settingPart("present", `${path} names project ${String(number)}`);
|
|
7453
|
+
const written = withProjectNumber(text, number);
|
|
7454
|
+
if (written === null) {
|
|
7455
|
+
const why = `it spells board or board.project in a shape this command does not edit, so set ${PROJECT_NUMBER_SETTING}: ${String(number)} by hand`;
|
|
7456
|
+
return settingPart("refused", `${path} was left as it was: ${why}`);
|
|
7457
|
+
}
|
|
7458
|
+
const problem = readBackProblem(text, written, path, number);
|
|
7459
|
+
if (problem !== null)
|
|
7460
|
+
return settingPart("refused", `${path} was left as it was: the file this run would have written ${problem}`);
|
|
7461
|
+
try {
|
|
7462
|
+
writeFileSync2(path, written, "utf8");
|
|
7463
|
+
} catch (error) {
|
|
7464
|
+
return settingPart("refused", `${path} could not be written: ${messageOf(error)}`);
|
|
7465
|
+
}
|
|
7466
|
+
return settingPart("created", `${path} now names project ${String(number)}`);
|
|
7467
|
+
}
|
|
7468
|
+
|
|
7469
|
+
// src/commands/init-board-project.ts
|
|
7470
|
+
var PROJECT_STEP_FIX = "rafa init --board --project";
|
|
7471
|
+
var SCOPE_PART = "project scope";
|
|
7472
|
+
var COPY_PART = "project";
|
|
7473
|
+
var ITEMS_PART = "project issues";
|
|
7474
|
+
var FIELDS_PART = "project fields";
|
|
7475
|
+
var PROJECT_SCOPE = "project";
|
|
7476
|
+
var SCOPE_ARGS = Object.freeze(["auth", "status", "--active", "--json", "hosts"]);
|
|
7477
|
+
var LOGGED_IN = "success";
|
|
7478
|
+
function partOf(kind, name, outcome, detail) {
|
|
7479
|
+
return { kind, name, outcome, detail };
|
|
7480
|
+
}
|
|
7481
|
+
function notTried(kinds, why) {
|
|
7482
|
+
const names = {
|
|
7483
|
+
scope: SCOPE_PART,
|
|
7484
|
+
project: COPY_PART,
|
|
7485
|
+
items: ITEMS_PART,
|
|
7486
|
+
fields: FIELDS_PART,
|
|
7487
|
+
setting: PROJECT_NUMBER_SETTING
|
|
7488
|
+
};
|
|
7489
|
+
return kinds.map((kind) => partOf(kind, names[kind], "refused", `not tried: ${why}`));
|
|
7490
|
+
}
|
|
7491
|
+
function scopeDetail() {
|
|
7492
|
+
return `the gh token has no \`${PROJECT_SCOPE}\` scope; run \`${PROJECT_SCOPE_FIX}\`, then \`${PROJECT_STEP_FIX}\``;
|
|
7493
|
+
}
|
|
7494
|
+
function refusalOf(error) {
|
|
7495
|
+
return isMissingProjectScope(error) ? scopeDetail() : messageOf(error);
|
|
7496
|
+
}
|
|
7497
|
+
function holdsProjectScope(hosts) {
|
|
7498
|
+
if (!isMapping(hosts))
|
|
7499
|
+
return false;
|
|
7500
|
+
return Object.values(hosts).some((accounts) => Array.isArray(accounts) && accounts.some((account) => isMapping(account) && account["active"] === true && account["state"] === LOGGED_IN && typeof account["scopes"] === "string" && account["scopes"].split(",").map((scope) => scope.trim()).includes(PROJECT_SCOPE)));
|
|
7501
|
+
}
|
|
7502
|
+
async function readScopePart(gh) {
|
|
7503
|
+
const command = `gh ${SCOPE_ARGS.join(" ")}`;
|
|
7504
|
+
const result2 = await gh(SCOPE_ARGS);
|
|
7505
|
+
if (!result2.ok) {
|
|
7506
|
+
const written = result2.stderr.trim() || result2.stdout.trim() || "it wrote nothing";
|
|
7507
|
+
return partOf("scope", SCOPE_PART, "refused", `${command} failed: ${written}; ${scopeDetail()}`);
|
|
7508
|
+
}
|
|
7509
|
+
let payload;
|
|
7510
|
+
try {
|
|
7511
|
+
payload = JSON.parse(result2.stdout);
|
|
7512
|
+
} catch (error) {
|
|
7513
|
+
return partOf("scope", SCOPE_PART, "refused", `${command} wrote output that is not JSON: ${messageOf(error)}`);
|
|
7514
|
+
}
|
|
7515
|
+
const hosts = isMapping(payload) ? payload["hosts"] : null;
|
|
7516
|
+
return holdsProjectScope(hosts) ? partOf("scope", SCOPE_PART, "present", `the gh token holds the \`${PROJECT_SCOPE}\` scope`) : partOf("scope", SCOPE_PART, "refused", scopeDetail());
|
|
7517
|
+
}
|
|
7518
|
+
function projectRefOf(url) {
|
|
7519
|
+
const match = /^https:\/\/github\.com\/(?:orgs|users)\/([A-Za-z0-9][A-Za-z0-9-]*)\/projects\/([1-9]\d*)$/u.exec(url);
|
|
7520
|
+
if (match === null)
|
|
7521
|
+
return null;
|
|
7522
|
+
return { owner: match[1] ?? "", number: Number.parseInt(match[2] ?? "", 10) };
|
|
7523
|
+
}
|
|
7524
|
+
async function copyTemplate(port, config, repository, owner) {
|
|
7525
|
+
const template = config.boardProjectTemplate;
|
|
7526
|
+
const ref2 = projectRefOf(template);
|
|
7527
|
+
if (ref2 === null) {
|
|
7528
|
+
return { part: partOf("project", COPY_PART, "refused", `board.project.template is ${template}, not a project URL`), project: null };
|
|
7529
|
+
}
|
|
7530
|
+
const found = await port.find(ref2);
|
|
7531
|
+
if (found === null) {
|
|
7532
|
+
const why = `the template ${template} was not found; set board.project.template to a project the gh account can read`;
|
|
7533
|
+
return { part: partOf("project", COPY_PART, "refused", why), project: null };
|
|
7534
|
+
}
|
|
7535
|
+
const title = repository.split("/")[1] ?? repository;
|
|
7536
|
+
const project = await port.copy({ templateId: found.id, owner, title });
|
|
7537
|
+
const copied = `copied ${template} to ${project.url}, titled ${title}`;
|
|
7538
|
+
try {
|
|
7539
|
+
await port.link(project.id, repository);
|
|
7540
|
+
} catch (error) {
|
|
7541
|
+
return { part: partOf("project", COPY_PART, "refused", `${copied}, but ${repository} was not linked: ${refusalOf(error)}`), project };
|
|
7542
|
+
}
|
|
7543
|
+
return { part: partOf("project", COPY_PART, "created", `${copied}, and linked ${repository}`), project };
|
|
7544
|
+
}
|
|
7545
|
+
async function copyStep(port, config, repository) {
|
|
7546
|
+
const owner = repository.split("/")[0] ?? "";
|
|
7547
|
+
try {
|
|
7548
|
+
const number = config.boardProjectNumber;
|
|
7549
|
+
const held = number === null ? null : await port.find({ owner, number });
|
|
7550
|
+
if (held !== null)
|
|
7551
|
+
return { part: partOf("project", COPY_PART, "present", held.url), project: held };
|
|
7552
|
+
return await copyTemplate(port, config, repository, owner);
|
|
7553
|
+
} catch (error) {
|
|
7554
|
+
return { part: partOf("project", COPY_PART, "refused", refusalOf(error)), project: null };
|
|
7555
|
+
}
|
|
7556
|
+
}
|
|
7557
|
+
function heldIssues(items, repository) {
|
|
7558
|
+
const wanted = repository.toLowerCase();
|
|
7559
|
+
return new Set(items.flatMap(({ content }) => content.kind === "issue" && content.repository.toLowerCase() === wanted ? [content.number] : []));
|
|
7560
|
+
}
|
|
7561
|
+
function issueCount(count) {
|
|
7562
|
+
return count === 1 ? "1 issue" : `${String(count)} issues`;
|
|
7563
|
+
}
|
|
7564
|
+
async function missingIssues(options, repository, project) {
|
|
7565
|
+
const { config, gh } = options;
|
|
7566
|
+
const board = await readRefreshBoard(config, gh, repository);
|
|
7567
|
+
const held = heldIssues(await createGhProjectPort(gh).items(project.id), repository);
|
|
7568
|
+
const ranked = board.closed.filter((issue) => rankOf(board.ranks, issue) !== null);
|
|
7569
|
+
const unread = board.closed.filter((issue) => rankOf(board.ranks, issue) === null && !held.has(issue));
|
|
7570
|
+
const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
|
|
7571
|
+
const inReview = unread.filter((issue) => {
|
|
7572
|
+
const read = facts.get(issue);
|
|
7573
|
+
return read !== undefined && stageOf(read) === "In review";
|
|
7574
|
+
});
|
|
7575
|
+
return [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
|
|
7576
|
+
}
|
|
7577
|
+
async function itemsStep(options, repository, project) {
|
|
7578
|
+
const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
|
|
7579
|
+
let missing;
|
|
7580
|
+
try {
|
|
7581
|
+
missing = await missingIssues(options, repository, project);
|
|
7582
|
+
} catch (error) {
|
|
7583
|
+
return partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`);
|
|
7584
|
+
}
|
|
7585
|
+
if (missing.length === 0)
|
|
7586
|
+
return partOf("items", ITEMS_PART, "present", "the project holds every issue it shows");
|
|
7587
|
+
const port = createGhProjectPort(options.gh);
|
|
7588
|
+
let added = 0;
|
|
7589
|
+
try {
|
|
7590
|
+
for (const number of missing) {
|
|
7591
|
+
if (added > 0)
|
|
7592
|
+
await sleep(PROJECT_WRITE_PAUSE_MS);
|
|
7593
|
+
await port.addItem(project.id, { repository, number });
|
|
7594
|
+
added += 1;
|
|
7595
|
+
}
|
|
7596
|
+
} catch (error) {
|
|
7597
|
+
const went = `${issueCount(added)} of ${String(missing.length)} added`;
|
|
7598
|
+
return partOf("items", ITEMS_PART, "refused", `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`);
|
|
7599
|
+
}
|
|
7600
|
+
return partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`);
|
|
7601
|
+
}
|
|
7602
|
+
async function fieldsStep(options, project) {
|
|
7603
|
+
const config = { ...options.config, boardProjectNumber: project.number };
|
|
7604
|
+
const refreshOptions = options.sleep === undefined ? { config, gh: options.gh } : { config, gh: options.gh, sleep: options.sleep };
|
|
7605
|
+
let refreshed;
|
|
7606
|
+
try {
|
|
7607
|
+
refreshed = await refreshProjectItems(refreshOptions, [], { everyItem: true });
|
|
7608
|
+
} catch (error) {
|
|
7609
|
+
return { part: partOf("fields", FIELDS_PART, "refused", refusalOf(error)), problems: [] };
|
|
7610
|
+
}
|
|
7611
|
+
if (refreshed.kind === "refused")
|
|
7612
|
+
return { part: partOf("fields", FIELDS_PART, "refused", scopeDetail()), problems: [] };
|
|
7613
|
+
if (refreshed.kind !== "refreshed") {
|
|
7614
|
+
return { part: partOf("fields", FIELDS_PART, "refused", refreshed.warnings.join(" ") || "the refresh sent no call"), problems: [] };
|
|
7615
|
+
}
|
|
7616
|
+
const { writes, changes, skipped } = refreshed;
|
|
7617
|
+
const problems = refreshed.warnings.slice(writes.rateLimited ? 1 : 0);
|
|
7618
|
+
if (writes.rateLimited) {
|
|
7619
|
+
return { part: partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), problems };
|
|
7620
|
+
}
|
|
7621
|
+
if (changes.length === 0) {
|
|
7622
|
+
const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
|
|
7623
|
+
return { part: partOf("fields", FIELDS_PART, "present", held), problems };
|
|
7624
|
+
}
|
|
7625
|
+
const issues = new Set(changes.map(({ issue }) => issue)).size;
|
|
7626
|
+
const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
|
|
7627
|
+
return { part: partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}`), problems };
|
|
7628
|
+
}
|
|
7629
|
+
async function onProject(options, repository, project) {
|
|
7630
|
+
const items = await itemsStep(options, repository, project);
|
|
7631
|
+
const fields = await fieldsStep(options, project);
|
|
7632
|
+
const setting = writeProjectNumber(options.root, project.number);
|
|
7633
|
+
return { parts: [items, fields.part, setting], problems: fields.problems };
|
|
7634
|
+
}
|
|
7635
|
+
async function setUpProject(options) {
|
|
7636
|
+
const { gh, config } = options;
|
|
7637
|
+
const scope = await readScopePart(gh);
|
|
7638
|
+
if (scope.outcome === "refused") {
|
|
7639
|
+
return { parts: [scope, ...notTried(["project", "items", "fields", "setting"], "the gh token has no project scope")], project: null, problems: [] };
|
|
7640
|
+
}
|
|
7641
|
+
let repository;
|
|
7642
|
+
try {
|
|
7643
|
+
repository = await readBoardRepository(gh);
|
|
7644
|
+
} catch (error) {
|
|
7645
|
+
const why = messageOf(error);
|
|
7646
|
+
return { parts: [scope, partOf("project", COPY_PART, "refused", why), ...notTried(["items", "fields", "setting"], "no project")], project: null, problems: [] };
|
|
7647
|
+
}
|
|
7648
|
+
const copied = await copyStep(createGhProjectPort(gh), config, repository);
|
|
7649
|
+
if (copied.project === null) {
|
|
7650
|
+
return { parts: [scope, copied.part, ...notTried(["items", "fields", "setting"], "no project")], project: null, problems: [] };
|
|
7651
|
+
}
|
|
7652
|
+
const { project } = copied;
|
|
7653
|
+
const rest = await onProject(options, repository, project);
|
|
7654
|
+
return {
|
|
7655
|
+
parts: [scope, copied.part, ...rest.parts],
|
|
7656
|
+
project: { owner: project.owner, number: project.number, url: project.url },
|
|
7657
|
+
problems: rest.problems
|
|
7658
|
+
};
|
|
7659
|
+
}
|
|
7660
|
+
function projectSetupChanged(report2) {
|
|
7661
|
+
return report2.parts.some((part) => part.outcome === "created");
|
|
7662
|
+
}
|
|
7663
|
+
var PROJECT_HEADING = "GitHub project:";
|
|
7664
|
+
var OUTCOME_WIDTH3 = 7;
|
|
7665
|
+
function projectPartLine(part) {
|
|
7666
|
+
const row = ` ${part.outcome.padEnd(OUTCOME_WIDTH3, " ")} ${part.name}`;
|
|
7667
|
+
return part.outcome === "present" ? row : `${row}: ${part.detail}`;
|
|
7668
|
+
}
|
|
7669
|
+
function renderProjectSetup(report2) {
|
|
7670
|
+
return [PROJECT_HEADING, ...report2.parts.map((part) => projectPartLine(part)), ...report2.problems];
|
|
7671
|
+
}
|
|
7672
|
+
var PROJECT_QUESTION = "Also create a GitHub project with roadmap and kanban views? [y/N] ";
|
|
7673
|
+
var PROJECT_NO_BOARD_WARNING = "--project creates the GitHub project with the GitHub board," + ` and the board step did not run, so no project was created; run ${PROJECT_STEP_FIX}.`;
|
|
7674
|
+
function noProject(status, asked, warnings = []) {
|
|
7675
|
+
return { status, asked, report: null, warnings: Object.freeze([...warnings]) };
|
|
7676
|
+
}
|
|
7677
|
+
async function askProject(openPrompter) {
|
|
7678
|
+
const prompter = openPrompter();
|
|
7679
|
+
try {
|
|
7680
|
+
const answer = await prompter.ask(PROJECT_QUESTION);
|
|
7681
|
+
return answer !== null && YES_ANSWERS.includes(answer.trim().toLowerCase());
|
|
7682
|
+
} finally {
|
|
7683
|
+
prompter.close();
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
function withWrittenRoadmap(root, config) {
|
|
7687
|
+
const written = readRoadmapSetting(root).issue;
|
|
7688
|
+
return written === null ? config : { ...config, roadmapIssue: written };
|
|
7689
|
+
}
|
|
7690
|
+
async function runProjectStep(options) {
|
|
7691
|
+
const { wanted, board, root, isTerminal, openPrompter } = options;
|
|
7692
|
+
if (board.status !== "ran") {
|
|
7693
|
+
return noProject("not-run", false, wanted === true ? [PROJECT_NO_BOARD_WARNING] : []);
|
|
7694
|
+
}
|
|
7695
|
+
if (wanted === false)
|
|
7696
|
+
return noProject("declined", false);
|
|
7697
|
+
const run = async (asked) => {
|
|
7698
|
+
const setUp = options.setUp ?? setUpProject;
|
|
7699
|
+
const report2 = await setUp({ root, config: withWrittenRoadmap(root, options.config), gh: options.openGh() });
|
|
7700
|
+
return { status: "ran", asked, report: report2, warnings: [] };
|
|
7701
|
+
};
|
|
7702
|
+
if (wanted === true)
|
|
7703
|
+
return run(false);
|
|
7704
|
+
if (!isTerminal())
|
|
7705
|
+
return noProject("unasked", false);
|
|
7706
|
+
return await askProject(openPrompter) ? run(true) : noProject("declined", true);
|
|
7707
|
+
}
|
|
7708
|
+
function projectStepChanged(result2) {
|
|
7709
|
+
return result2.report !== null && projectSetupChanged(result2.report);
|
|
7710
|
+
}
|
|
7711
|
+
function renderProjectStep(result2) {
|
|
7712
|
+
if (result2.report !== null)
|
|
7713
|
+
return renderProjectSetup(result2.report);
|
|
7714
|
+
if (result2.status === "declined")
|
|
7715
|
+
return [`The GitHub project was left out; run ${PROJECT_STEP_FIX} to create it.`];
|
|
7716
|
+
if (result2.status === "unasked")
|
|
7717
|
+
return [`The GitHub project question needs a terminal; run ${PROJECT_STEP_FIX} to create it.`];
|
|
7718
|
+
return [];
|
|
7719
|
+
}
|
|
7720
|
+
|
|
7721
|
+
// src/commands/doctor-project.ts
|
|
7722
|
+
var SCOPE_ROW = "project scope";
|
|
7723
|
+
var PROJECT_ROW = "project";
|
|
7724
|
+
var FIELDS_ROW = "project fields";
|
|
7725
|
+
var DRIFT_CHECK = "rafa board sync --dry-run";
|
|
7726
|
+
var OUTCOME_WIDTH4 = 7;
|
|
7727
|
+
function rowOf2(kind, name, outcome, detail) {
|
|
7728
|
+
return { kind, name, outcome, detail };
|
|
7729
|
+
}
|
|
7730
|
+
function noScope() {
|
|
7731
|
+
return `the gh token has no \`${PROJECT_SCOPE}\` scope; run \`${PROJECT_SCOPE_FIX}\``;
|
|
7732
|
+
}
|
|
7733
|
+
async function readScopeRow(gh) {
|
|
7734
|
+
const command = `gh ${SCOPE_ARGS.join(" ")}`;
|
|
7735
|
+
const result2 = await gh(SCOPE_ARGS);
|
|
7736
|
+
if (!result2.ok) {
|
|
7737
|
+
const written = result2.stderr.trim() || result2.stdout.trim() || "it wrote nothing";
|
|
7738
|
+
return rowOf2("scope", SCOPE_ROW, "unknown", `${command} failed: ${written}`);
|
|
7739
|
+
}
|
|
7740
|
+
let payload;
|
|
7741
|
+
try {
|
|
7742
|
+
payload = JSON.parse(result2.stdout);
|
|
7743
|
+
} catch (error) {
|
|
7744
|
+
return rowOf2("scope", SCOPE_ROW, "unknown", `${command} wrote output that is not JSON: ${messageOf(error)}`);
|
|
7745
|
+
}
|
|
7746
|
+
const hosts = isMapping(payload) ? payload["hosts"] : null;
|
|
7747
|
+
return holdsProjectScope(hosts) ? rowOf2("scope", SCOPE_ROW, "present", `the gh token holds the \`${PROJECT_SCOPE}\` scope`) : rowOf2("scope", SCOPE_ROW, "missing", noScope());
|
|
7748
|
+
}
|
|
7749
|
+
function notRead(kind, name, before) {
|
|
7750
|
+
return rowOf2(kind, name, "unknown", `not read: the ${before.name} row is ${before.outcome}`);
|
|
7751
|
+
}
|
|
7752
|
+
async function readProjectStep(gh, number) {
|
|
7753
|
+
const name = `${PROJECT_ROW} ${String(number)}`;
|
|
7754
|
+
try {
|
|
7755
|
+
const owner = (await readBoardRepository(gh)).split("/")[0] ?? "";
|
|
7756
|
+
const project = await createGhProjectPort(gh).find({ owner, number });
|
|
7757
|
+
if (project === null) {
|
|
7758
|
+
const why = `board.project.number ${String(number)} names no project of ${owner}; run \`${PROJECT_STEP_FIX}\` to make one`;
|
|
7759
|
+
return { row: rowOf2("project", name, "missing", why), project: null };
|
|
7760
|
+
}
|
|
7761
|
+
return { row: rowOf2("project", name, "present", project.url), project };
|
|
7762
|
+
} catch (error) {
|
|
7763
|
+
const row = isMissingProjectScope(error) ? rowOf2("project", name, "missing", noScope()) : rowOf2("project", name, "unknown", messageOf(error));
|
|
7764
|
+
return { row, project: null };
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
function fieldsRow(project, template) {
|
|
7768
|
+
const { mismatched } = matchProjectFields(project);
|
|
7769
|
+
if (mismatched.length === 0)
|
|
7770
|
+
return rowOf2("fields", FIELDS_ROW, "present", "the five fields carry the template's names and options");
|
|
7771
|
+
const sentences = mismatched.map((mismatch) => mismatch.sentence).join(" ");
|
|
7772
|
+
const skipped = mismatched.length === 1 ? "that field" : "those fields";
|
|
7773
|
+
const fix = `Give the project the fields and options of board.project.template (${template}); until then the refresh skips ${skipped}.`;
|
|
7774
|
+
return rowOf2("fields", FIELDS_ROW, "missing", `${sentences} ${fix}`);
|
|
7775
|
+
}
|
|
7776
|
+
async function readDoctorProject(input) {
|
|
7777
|
+
const { gh, number, template } = input;
|
|
7778
|
+
if (gh === null || number === null)
|
|
7779
|
+
return null;
|
|
7780
|
+
const scope = await readScopeRow(gh);
|
|
7781
|
+
if (scope.outcome !== "present") {
|
|
7782
|
+
const project = notRead("project", `${PROJECT_ROW} ${String(number)}`, scope);
|
|
7783
|
+
return { number, rows: [scope, project, notRead("fields", FIELDS_ROW, project)] };
|
|
7784
|
+
}
|
|
7785
|
+
const step = await readProjectStep(gh, number);
|
|
7786
|
+
const fields = step.project === null ? notRead("fields", FIELDS_ROW, step.row) : fieldsRow(step.project, template);
|
|
7787
|
+
return { number, rows: [scope, step.row, fields] };
|
|
7788
|
+
}
|
|
7789
|
+
function projectRowLine(row) {
|
|
7790
|
+
const line = ` ${row.outcome.padEnd(OUTCOME_WIDTH4, " ")} ${row.name}`;
|
|
7791
|
+
if (row.outcome !== "present")
|
|
7792
|
+
return `${line}: ${row.detail}`;
|
|
7793
|
+
return row.kind === "project" ? `${line}: ${row.detail}` : line;
|
|
7794
|
+
}
|
|
7795
|
+
function renderDoctorProject(reading) {
|
|
7796
|
+
if (reading === null)
|
|
7797
|
+
return [];
|
|
7798
|
+
const inPlace = reading.rows.every((row) => row.outcome === "present");
|
|
7799
|
+
const drift = inPlace ? [`Run ${DRIFT_CHECK} to check the project for drift.`] : [];
|
|
7800
|
+
return [PROJECT_HEADING, ...reading.rows.map(projectRowLine), ...drift];
|
|
7801
|
+
}
|
|
7802
|
+
|
|
7138
7803
|
// src/commands/doctor-release.ts
|
|
7139
|
-
import { readFileSync as
|
|
7804
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
7140
7805
|
import { join as join14 } from "path";
|
|
7141
7806
|
var SETTLE_COMMAND = "rafa release settle";
|
|
7142
7807
|
var UNREAD = "?";
|
|
7143
7808
|
function readTopHeading(root, configured) {
|
|
7144
7809
|
const resolved = join14(root, configured);
|
|
7145
7810
|
try {
|
|
7146
|
-
const text =
|
|
7811
|
+
const text = readFileSync7(resolved, "utf8");
|
|
7147
7812
|
return { path: configured, version: changelogVersions(text)[0] ?? null, problem: null };
|
|
7148
7813
|
} catch {
|
|
7149
7814
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -7186,8 +7851,8 @@ function waitingPart(waiting) {
|
|
|
7186
7851
|
return "no fragment waits";
|
|
7187
7852
|
const ids = waiting.fragments.map((each) => each.id);
|
|
7188
7853
|
const named = ids.length === 0 ? "" : ` (${ids.join(", ")})`;
|
|
7189
|
-
const
|
|
7190
|
-
return `${
|
|
7854
|
+
const counted3 = count === 1 ? "1 fragment waits" : `${String(count)} fragments wait`;
|
|
7855
|
+
return `${counted3}${named}: ${forecastPhrase(waiting)}; run ${SETTLE_COMMAND} to fold them into one version`;
|
|
7191
7856
|
}
|
|
7192
7857
|
function releaseRow(reading) {
|
|
7193
7858
|
const { waiting } = reading;
|
|
@@ -7225,14 +7890,14 @@ function writeDoctorRelease(context, reading) {
|
|
|
7225
7890
|
}
|
|
7226
7891
|
|
|
7227
7892
|
// src/commands/doctor-tiers.ts
|
|
7228
|
-
import { readFileSync as
|
|
7893
|
+
import { readFileSync as readFileSync8, realpathSync as realpathSync2 } from "fs";
|
|
7229
7894
|
import { basename as basename4, dirname as dirname2 } from "path";
|
|
7230
7895
|
var TIERS_SECTION_TITLE = "Skill tiers";
|
|
7231
7896
|
var CLAUDE_VERSION_TIMEOUT_MS = 1e4;
|
|
7232
7897
|
var VERSION_PATTERN = /^\s*(\d+\.\d+\.\d+)/;
|
|
7233
7898
|
function readTextFile(path) {
|
|
7234
7899
|
try {
|
|
7235
|
-
return
|
|
7900
|
+
return readFileSync8(path, "utf8");
|
|
7236
7901
|
} catch {
|
|
7237
7902
|
return null;
|
|
7238
7903
|
}
|
|
@@ -7531,6 +8196,7 @@ function resultOf(preflight, install, readings, deep) {
|
|
|
7531
8196
|
epics: readings.epics,
|
|
7532
8197
|
boards: readings.boards,
|
|
7533
8198
|
...relationsResultOf(readings),
|
|
8199
|
+
project: readings.project,
|
|
7534
8200
|
cleanup: readings.cleanup,
|
|
7535
8201
|
refs: readings.refs,
|
|
7536
8202
|
release: readings.release,
|
|
@@ -7575,10 +8241,12 @@ async function runDoctor(context, seams) {
|
|
|
7575
8241
|
const effortSync = await readDoctorEffortSync({ root: project.root, home: project.home, resolved: preflight.resolved }, seams);
|
|
7576
8242
|
const modeSet = preflight.resolved.sources.boardRelationships !== "default";
|
|
7577
8243
|
const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue, preflight.config.boardRelationships, modeSet);
|
|
7578
|
-
const
|
|
8244
|
+
const { boardProjectNumber: number, boardProjectTemplate: template } = preflight.config;
|
|
8245
|
+
const projectRows = await readDoctorProject({ gh, number, template });
|
|
8246
|
+
const readings = { ...board, project: projectRows, cleanup, refs, release, tiers, effortSchema, effortSync };
|
|
7579
8247
|
writeText(context, renderDoctor(preflight));
|
|
7580
8248
|
await announceRisk(context, preflight, seams);
|
|
7581
|
-
const repository = [...renderDoctorBoard(readings), ...renderDoctorCleanup(readings.cleanup)];
|
|
8249
|
+
const repository = [...renderDoctorBoard(readings), ...renderDoctorProject(projectRows), ...renderDoctorCleanup(readings.cleanup)];
|
|
7582
8250
|
writeText(context, [...repository, ...renderDoctorRefs(readings.refs)]);
|
|
7583
8251
|
writeDoctorRelease(context, release);
|
|
7584
8252
|
writeText(context, renderDoctorTiers(readings.tiers));
|
|
@@ -8054,10 +8722,10 @@ function readDashboard(options) {
|
|
|
8054
8722
|
|
|
8055
8723
|
// src/commands/effort/dashboard.ts
|
|
8056
8724
|
var COMMAND_NAME6 = "rafa effort dashboard";
|
|
8057
|
-
var
|
|
8725
|
+
var USAGE13 = "rafa effort dashboard [--days=<n>] [--recent=<n>] [--loops=<n>] [--by=effort|model|agent]";
|
|
8058
8726
|
var TREND_FLAGS = ["days", "recent", "loops", "by"];
|
|
8059
8727
|
function refuse5(problems) {
|
|
8060
|
-
throw new CommandExit(1, [...problems.map((problem) => `\u274C ${COMMAND_NAME6}: ${problem}`), `Usage: ${
|
|
8728
|
+
throw new CommandExit(1, [...problems.map((problem) => `\u274C ${COMMAND_NAME6}: ${problem}`), `Usage: ${USAGE13}`].join(`
|
|
8061
8729
|
`));
|
|
8062
8730
|
}
|
|
8063
8731
|
function trendOptionsOf(context) {
|
|
@@ -8073,7 +8741,7 @@ function trendOptionsOf(context) {
|
|
|
8073
8741
|
return parsed.trend;
|
|
8074
8742
|
}
|
|
8075
8743
|
function runDashboard(context, seams) {
|
|
8076
|
-
expectNoArgument(context.args,
|
|
8744
|
+
expectNoArgument(context.args, USAGE13);
|
|
8077
8745
|
const root = requireProject(context, COMMAND_NAME6).root;
|
|
8078
8746
|
const trend = trendOptionsOf(context);
|
|
8079
8747
|
let dashboard;
|
|
@@ -8150,7 +8818,7 @@ import { resolve as resolve6 } from "path";
|
|
|
8150
8818
|
import { join as join16, resolve as resolve5 } from "path";
|
|
8151
8819
|
var COMMAND_NAME7 = "rafa effort merge";
|
|
8152
8820
|
var MERGE_USAGE = "rafa effort merge <file> [--dry-run]";
|
|
8153
|
-
var
|
|
8821
|
+
var DRY_RUN_FLAG3 = "dry-run";
|
|
8154
8822
|
var REFUSED_EXIT2 = 1;
|
|
8155
8823
|
var UNMERGEABLE_EXIT = 2;
|
|
8156
8824
|
var UNMERGEABLE_REASONS = new Set(["other-project", "ndjson"]);
|
|
@@ -8167,7 +8835,7 @@ function tableLine(entry) {
|
|
|
8167
8835
|
];
|
|
8168
8836
|
return ` ${entry.table}: ${counts.join(", ")}`;
|
|
8169
8837
|
}
|
|
8170
|
-
function
|
|
8838
|
+
function closingLine2(result2, command) {
|
|
8171
8839
|
if (result2.status === "would-merge") {
|
|
8172
8840
|
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.`;
|
|
8173
8841
|
}
|
|
@@ -8181,7 +8849,7 @@ function renderMerge(result2, command = COMMAND_NAME7) {
|
|
|
8181
8849
|
...forward,
|
|
8182
8850
|
...result2.tables.map(tableLine),
|
|
8183
8851
|
`Total: ${String(result2.rowsAdded)} added, ${String(result2.rowsSkipped)} skipped,` + ` ${String(result2.rowsInConflict)} in conflict; ${String(result2.gapsRewritten)} commit gaps recomputed.`,
|
|
8184
|
-
|
|
8852
|
+
closingLine2(result2, command)
|
|
8185
8853
|
];
|
|
8186
8854
|
}
|
|
8187
8855
|
function mergeTargetPath(context, root, command = COMMAND_NAME7) {
|
|
@@ -8210,7 +8878,7 @@ function probeSeams(seams) {
|
|
|
8210
8878
|
}
|
|
8211
8879
|
function runMerge(context, seams) {
|
|
8212
8880
|
const file = expectOneArgument(context.args, MERGE_USAGE);
|
|
8213
|
-
const dryRun = readSwitch(
|
|
8881
|
+
const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MERGE_USAGE}`);
|
|
8214
8882
|
const project = requireProject(context, COMMAND_NAME7);
|
|
8215
8883
|
const path = mergeTargetPath(context, project.root);
|
|
8216
8884
|
const backend = resolveProjectConfig(project, COMMAND_NAME7, (message) => context.output.warn(message)).store;
|
|
@@ -8246,7 +8914,7 @@ function createMergeCommand(seams = {}) {
|
|
|
8246
8914
|
],
|
|
8247
8915
|
flags: [
|
|
8248
8916
|
{
|
|
8249
|
-
name:
|
|
8917
|
+
name: DRY_RUN_FLAG3,
|
|
8250
8918
|
description: "Build and check the merged store, print what it would add, then delete it. This store is" + " only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8251
8919
|
type: "boolean"
|
|
8252
8920
|
}
|
|
@@ -8274,7 +8942,7 @@ var merge_default = createMergeCommand();
|
|
|
8274
8942
|
// src/commands/effort/import.ts
|
|
8275
8943
|
var COMMAND_NAME8 = "rafa effort import";
|
|
8276
8944
|
var IMPORT_USAGE = "rafa effort import <file> [--dry-run]";
|
|
8277
|
-
var
|
|
8945
|
+
var DRY_RUN_FLAG4 = "dry-run";
|
|
8278
8946
|
function syncOptions(context, repoRoot, backend, seams) {
|
|
8279
8947
|
const optional = {
|
|
8280
8948
|
now: seams.now,
|
|
@@ -8301,7 +8969,7 @@ async function pull(sync, otherPath, dryRun) {
|
|
|
8301
8969
|
}
|
|
8302
8970
|
async function runImport(context, seams) {
|
|
8303
8971
|
const file = expectOneArgument(context.args, IMPORT_USAGE);
|
|
8304
|
-
const dryRun = readSwitch(
|
|
8972
|
+
const dryRun = readSwitch(DRY_RUN_FLAG4, context.flags[DRY_RUN_FLAG4], `Usage: ${IMPORT_USAGE}`);
|
|
8305
8973
|
const project = requireProject(context, COMMAND_NAME8);
|
|
8306
8974
|
mergeTargetPath(context, project.root, COMMAND_NAME8);
|
|
8307
8975
|
const backend = resolveProjectConfig(project, COMMAND_NAME8, (message) => context.output.warn(message)).store;
|
|
@@ -8332,7 +9000,7 @@ function createImportCommand(seams = {}) {
|
|
|
8332
9000
|
],
|
|
8333
9001
|
flags: [
|
|
8334
9002
|
{
|
|
8335
|
-
name:
|
|
9003
|
+
name: DRY_RUN_FLAG4,
|
|
8336
9004
|
description: "Build and check the merged store, print what it would add, then delete it. This store is" + " only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8337
9005
|
type: "boolean"
|
|
8338
9006
|
}
|
|
@@ -8360,7 +9028,7 @@ var import_default = createImportCommand();
|
|
|
8360
9028
|
import { join as join17 } from "path";
|
|
8361
9029
|
var COMMAND_NAME9 = "rafa effort migrate";
|
|
8362
9030
|
var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
|
|
8363
|
-
var
|
|
9031
|
+
var DRY_RUN_FLAG5 = "dry-run";
|
|
8364
9032
|
var REFUSED_EXIT3 = 1;
|
|
8365
9033
|
function builtLines(result2) {
|
|
8366
9034
|
const rows = result2.counted.reduce((sum, table) => sum + table.rows, 0);
|
|
@@ -8397,7 +9065,7 @@ function storePath(context, root) {
|
|
|
8397
9065
|
}
|
|
8398
9066
|
function runMigrate(context, seams) {
|
|
8399
9067
|
expectNoArgument(context.args, MIGRATE_USAGE);
|
|
8400
|
-
const dryRun = readSwitch(
|
|
9068
|
+
const dryRun = readSwitch(DRY_RUN_FLAG5, context.flags[DRY_RUN_FLAG5], `Usage: ${MIGRATE_USAGE}`);
|
|
8401
9069
|
const path = storePath(context, requireProject(context, COMMAND_NAME9).root);
|
|
8402
9070
|
const now = seams.now ?? (() => new Date);
|
|
8403
9071
|
let result2;
|
|
@@ -8435,7 +9103,7 @@ function createMigrateCommand(seams = {}) {
|
|
|
8435
9103
|
args: [],
|
|
8436
9104
|
flags: [
|
|
8437
9105
|
{
|
|
8438
|
-
name:
|
|
9106
|
+
name: DRY_RUN_FLAG5,
|
|
8439
9107
|
description: "Build and check the migrated store, print what it applies, then delete it. The live store" + " is only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8440
9108
|
type: "boolean"
|
|
8441
9109
|
}
|
|
@@ -8461,7 +9129,7 @@ function createMigrateCommand(seams = {}) {
|
|
|
8461
9129
|
var migrate_default = createMigrateCommand();
|
|
8462
9130
|
|
|
8463
9131
|
// src/effort/store/move.ts
|
|
8464
|
-
import { readFileSync as
|
|
9132
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
8465
9133
|
var MOVED_KINDS = Object.freeze(["sessions", "commits"]);
|
|
8466
9134
|
var MOVE_TARGET = "sqlite";
|
|
8467
9135
|
var STORE_LINE = `store: ${MOVE_TARGET}`;
|
|
@@ -8503,7 +9171,7 @@ function withSqliteStoreSetting(text) {
|
|
|
8503
9171
|
function plannedConfig(path) {
|
|
8504
9172
|
let text;
|
|
8505
9173
|
try {
|
|
8506
|
-
text =
|
|
9174
|
+
text = readFileSync9(path, "utf8");
|
|
8507
9175
|
} catch (error) {
|
|
8508
9176
|
throw new MoveRefusal("config", `${path} could not be read: ${messageOf(error)}. Nothing was moved.`);
|
|
8509
9177
|
}
|
|
@@ -8543,22 +9211,22 @@ function moveKind(kind, source, target) {
|
|
|
8543
9211
|
const { keyed, unkeyed } = keyedRows(kind, reading.rows);
|
|
8544
9212
|
const project = EFFORT_KEY_PROJECTIONS[kind];
|
|
8545
9213
|
const before = target.keys(kind).size;
|
|
8546
|
-
const
|
|
9214
|
+
const appended2 = target.append(kind, keyed);
|
|
8547
9215
|
const keys = keyed.map((row2) => project(row2) ?? "");
|
|
8548
|
-
checkCounts(kind, before,
|
|
9216
|
+
checkCounts(kind, before, appended2.appended, keys, target.keys(kind));
|
|
8549
9217
|
return {
|
|
8550
9218
|
kind,
|
|
8551
9219
|
from: source.path(kind),
|
|
8552
9220
|
read: keyed.length,
|
|
8553
|
-
added:
|
|
8554
|
-
skipped:
|
|
9221
|
+
added: appended2.appended,
|
|
9222
|
+
skipped: appended2.skipped,
|
|
8555
9223
|
unkeyed,
|
|
8556
9224
|
unparsed: reading.unparsedLineCount
|
|
8557
9225
|
};
|
|
8558
9226
|
}
|
|
8559
9227
|
function writeConfig(path, text) {
|
|
8560
9228
|
try {
|
|
8561
|
-
|
|
9229
|
+
writeFileSync3(path, text, "utf8");
|
|
8562
9230
|
} catch (error) {
|
|
8563
9231
|
throw new MoveRefusal("config", `${path} could not be written (${messageOf(error)}). The rows were moved and checked; set ${STORE_LINE}` + " there by hand, or run the move again, which adds nothing.");
|
|
8564
9232
|
}
|
|
@@ -8884,6 +9552,93 @@ function keptLinksLine(issue, waitsOn) {
|
|
|
8884
9552
|
return `${ref2(issue)} keeps its blocked-by ${links} to ${waitsOn.map(ref2).join(" ")}: in native mode the cancel clears` + " nothing, and GitHub stops holding an issue back once its blocker closes.";
|
|
8885
9553
|
}
|
|
8886
9554
|
|
|
9555
|
+
// src/board/project/add-issue.ts
|
|
9556
|
+
async function addItem(gh, number, issue) {
|
|
9557
|
+
const repository = await readBoardRepository(gh);
|
|
9558
|
+
const ref3 = { owner: repository.split("/")[0] ?? "", number };
|
|
9559
|
+
const port = createGhProjectPort(gh);
|
|
9560
|
+
const project = await port.find(ref3);
|
|
9561
|
+
if (project === null)
|
|
9562
|
+
return notFoundWarning(ref3);
|
|
9563
|
+
await port.addItem(project.id, { repository, number: issue });
|
|
9564
|
+
return null;
|
|
9565
|
+
}
|
|
9566
|
+
async function addAndRefreshIssue(options, issue) {
|
|
9567
|
+
const { config, openGh, refresh = refreshProjectItems } = options;
|
|
9568
|
+
if (config.boardProjectNumber === null)
|
|
9569
|
+
return [];
|
|
9570
|
+
const gh = openGh();
|
|
9571
|
+
try {
|
|
9572
|
+
const refused = await addItem(gh, config.boardProjectNumber, issue);
|
|
9573
|
+
if (refused !== null)
|
|
9574
|
+
return [refused];
|
|
9575
|
+
const refreshOptions = options.sleep === undefined ? { config, gh } : { config, gh, sleep: options.sleep };
|
|
9576
|
+
return (await refresh(refreshOptions, [issue])).warnings;
|
|
9577
|
+
} catch (error) {
|
|
9578
|
+
return isMissingProjectScope(error) ? [scopeWarning()] : [refreshFailedWarning(issue, error)];
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
|
|
9582
|
+
// src/commands/epic/epic-project.ts
|
|
9583
|
+
function oneLine2(message) {
|
|
9584
|
+
return message.replace(/^\u274C\s*/u, "").split(`
|
|
9585
|
+
`).map((line) => line.trim()).filter((line) => line !== "").join(" ");
|
|
9586
|
+
}
|
|
9587
|
+
function epicProjectProblemLine(issues, problem) {
|
|
9588
|
+
const named = issues.map((issue) => `#${String(issue)}`).join(", ");
|
|
9589
|
+
return `The project was not updated for ${named}: ${oneLine2(problem)}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
|
|
9590
|
+
}
|
|
9591
|
+
function newEpicTarget(result2) {
|
|
9592
|
+
return { epics: [result2.epic.number], issues: [], added: result2.epic.number };
|
|
9593
|
+
}
|
|
9594
|
+
function horizonTarget(result2) {
|
|
9595
|
+
return result2.status === "moved" ? { epics: [result2.epic], issues: [], added: null } : null;
|
|
9596
|
+
}
|
|
9597
|
+
function moveTarget(result2) {
|
|
9598
|
+
return result2.outcome === null ? null : { epics: [result2.from, result2.to], issues: [result2.issue], added: null };
|
|
9599
|
+
}
|
|
9600
|
+
function cancelTarget(result2) {
|
|
9601
|
+
if (result2.status !== "cancelled")
|
|
9602
|
+
return null;
|
|
9603
|
+
const moves = result2.applied.flatMap(({ answer }) => answer.kind === "moved" ? [answer.from, answer.to] : []);
|
|
9604
|
+
return { epics: [result2.epic, ...moves], issues: result2.applied.map(({ issue }) => issue), added: null };
|
|
9605
|
+
}
|
|
9606
|
+
function closeTarget(result2) {
|
|
9607
|
+
return { epics: [result2.epic], issues: [], added: null };
|
|
9608
|
+
}
|
|
9609
|
+
async function refreshLines(config, openGh, refresh, target, named) {
|
|
9610
|
+
const widening = { membersOf: target.epics, shiftedRanks: true };
|
|
9611
|
+
if (target.added !== null) {
|
|
9612
|
+
return addAndRefreshIssue({ config, openGh, refresh: (options, issues) => refresh(options, [...new Set([...issues, ...named])], widening) }, target.added);
|
|
9613
|
+
}
|
|
9614
|
+
try {
|
|
9615
|
+
return (await refresh({ config, gh: openGh() }, named, widening)).warnings;
|
|
9616
|
+
} catch (error) {
|
|
9617
|
+
return [epicProjectProblemLine(named, messageOf(error))];
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9620
|
+
async function refreshProjectAfterEpic(context, seams, target) {
|
|
9621
|
+
if (target === null)
|
|
9622
|
+
return null;
|
|
9623
|
+
const project = issueProject(context);
|
|
9624
|
+
const named = [...new Set([...target.epics, ...target.issues])];
|
|
9625
|
+
let warnings;
|
|
9626
|
+
try {
|
|
9627
|
+
const config = issueSubjectConfig(project, () => {
|
|
9628
|
+
return;
|
|
9629
|
+
});
|
|
9630
|
+
if (config.boardProjectNumber === null)
|
|
9631
|
+
return null;
|
|
9632
|
+
const openGh = () => seams.gh ?? createGhRunner({ cwd: project.root });
|
|
9633
|
+
warnings = await refreshLines(config, openGh, seams.projectRefresh ?? refreshIssueItems, target, named);
|
|
9634
|
+
} catch (error) {
|
|
9635
|
+
warnings = [epicProjectProblemLine(named, messageOf(error))];
|
|
9636
|
+
}
|
|
9637
|
+
for (const line of warnings)
|
|
9638
|
+
context.output.warn(line);
|
|
9639
|
+
return { issues: named, membersOf: target.epics, added: target.added, warnings };
|
|
9640
|
+
}
|
|
9641
|
+
|
|
8887
9642
|
// src/board/epic-horizon.ts
|
|
8888
9643
|
var EPIC_HORIZON_REFUSAL_EXIT = 2;
|
|
8889
9644
|
var BRANCH_REF = /^refs\/(?:heads\/|remotes\/[^/]+\/)/u;
|
|
@@ -9019,11 +9774,11 @@ function readHorizonLine(context, action) {
|
|
|
9019
9774
|
const reason = readTextFlag(flags, REASON_FLAG, action.usage) ?? null;
|
|
9020
9775
|
return Object.freeze({ epic: Number(word), to, reason });
|
|
9021
9776
|
}
|
|
9022
|
-
function
|
|
9777
|
+
function rankOf2(horizon) {
|
|
9023
9778
|
return HORIZONS.indexOf(horizon);
|
|
9024
9779
|
}
|
|
9025
9780
|
function wrongDirection(action, change) {
|
|
9026
|
-
const later =
|
|
9781
|
+
const later = rankOf2(change.to) > rankOf2(change.from);
|
|
9027
9782
|
if (later === (action.name === "defer"))
|
|
9028
9783
|
return null;
|
|
9029
9784
|
const other = action.name === "defer" ? "promote" : "defer";
|
|
@@ -9176,25 +9931,24 @@ function closeFailure(result2) {
|
|
|
9176
9931
|
async function runEpicHorizon(context, action, seams) {
|
|
9177
9932
|
const result2 = await changeEpicHorizon(context, action, seams);
|
|
9178
9933
|
const failure = closeFailure(result2);
|
|
9179
|
-
if (context.outputMode
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
if (line.warn)
|
|
9187
|
-
context.output.warn(line.text);
|
|
9188
|
-
else
|
|
9189
|
-
context.output.info(line.text);
|
|
9934
|
+
if (context.outputMode !== "json") {
|
|
9935
|
+
for (const line of renderEpicHorizon(result2)) {
|
|
9936
|
+
if (line.warn)
|
|
9937
|
+
context.output.warn(line.text);
|
|
9938
|
+
else
|
|
9939
|
+
context.output.info(line.text);
|
|
9940
|
+
}
|
|
9190
9941
|
}
|
|
9942
|
+
await refreshProjectAfterEpic(context, seams, horizonTarget(result2));
|
|
9191
9943
|
if (failure !== null)
|
|
9192
9944
|
throw failure;
|
|
9945
|
+
if (context.outputMode === "json")
|
|
9946
|
+
context.output.result(result2);
|
|
9193
9947
|
}
|
|
9194
9948
|
|
|
9195
9949
|
// src/commands/epic/move.ts
|
|
9196
9950
|
var EPIC_MOVE_REFUSAL_EXIT = 2;
|
|
9197
|
-
var
|
|
9951
|
+
var USAGE14 = 'rafa epic move <issue> --to=<epic> [--reason="<why>"]';
|
|
9198
9952
|
var ISSUE_NUMBER4 = /^[1-9]\d*$/u;
|
|
9199
9953
|
function isNativeReading(reading) {
|
|
9200
9954
|
return "relationships" in reading;
|
|
@@ -9207,7 +9961,7 @@ function refusal5(message) {
|
|
|
9207
9961
|
}
|
|
9208
9962
|
function issueNumber(word, what) {
|
|
9209
9963
|
if (!ISSUE_NUMBER4.test(word))
|
|
9210
|
-
throw lineRefusal(`"${word}" is no ${what}, which is a whole number from 1`,
|
|
9964
|
+
throw lineRefusal(`"${word}" is no ${what}, which is a whole number from 1`, USAGE14);
|
|
9211
9965
|
return Number(word);
|
|
9212
9966
|
}
|
|
9213
9967
|
function readMoveLine(context) {
|
|
@@ -9215,14 +9969,14 @@ function readMoveLine(context) {
|
|
|
9215
9969
|
const [word] = args;
|
|
9216
9970
|
if (args.length !== 1 || word === undefined) {
|
|
9217
9971
|
const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
|
|
9218
|
-
throw lineRefusal(`Expected one issue number, got ${got}`,
|
|
9972
|
+
throw lineRefusal(`Expected one issue number, got ${got}`, USAGE14);
|
|
9219
9973
|
}
|
|
9220
9974
|
const issue = issueNumber(word, "issue number");
|
|
9221
|
-
const target = readTextFlag(flags, TO_FLAG4,
|
|
9975
|
+
const target = readTextFlag(flags, TO_FLAG4, USAGE14);
|
|
9222
9976
|
if (target === undefined)
|
|
9223
|
-
throw lineRefusal(`--${TO_FLAG4} is required: --${TO_FLAG4}=<epic>`,
|
|
9977
|
+
throw lineRefusal(`--${TO_FLAG4} is required: --${TO_FLAG4}=<epic>`, USAGE14);
|
|
9224
9978
|
const to = issueNumber(target, "epic number");
|
|
9225
|
-
const reason = readTextFlag(flags, REASON_FLAG,
|
|
9979
|
+
const reason = readTextFlag(flags, REASON_FLAG, USAGE14) ?? null;
|
|
9226
9980
|
return Object.freeze({ issue, to, reason });
|
|
9227
9981
|
}
|
|
9228
9982
|
function memberRow(issues, number) {
|
|
@@ -9504,20 +10258,19 @@ function moveFailure(result2) {
|
|
|
9504
10258
|
async function runEpicMove(context, seams) {
|
|
9505
10259
|
const result2 = await moveEpicIssue(context, seams);
|
|
9506
10260
|
const failure = moveFailure(result2);
|
|
9507
|
-
if (context.outputMode
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
if (out.warn)
|
|
9515
|
-
context.output.warn(out.text);
|
|
9516
|
-
else
|
|
9517
|
-
context.output.info(out.text);
|
|
10261
|
+
if (context.outputMode !== "json") {
|
|
10262
|
+
for (const out of renderEpicMove(result2)) {
|
|
10263
|
+
if (out.warn)
|
|
10264
|
+
context.output.warn(out.text);
|
|
10265
|
+
else
|
|
10266
|
+
context.output.info(out.text);
|
|
10267
|
+
}
|
|
9518
10268
|
}
|
|
10269
|
+
await refreshProjectAfterEpic(context, seams, moveTarget(result2));
|
|
9519
10270
|
if (failure !== null)
|
|
9520
10271
|
throw failure;
|
|
10272
|
+
if (context.outputMode === "json")
|
|
10273
|
+
context.output.result(result2);
|
|
9521
10274
|
}
|
|
9522
10275
|
function createEpicMoveCommand(seams = {}) {
|
|
9523
10276
|
const command = {
|
|
@@ -9562,7 +10315,7 @@ var move_default2 = createEpicMoveCommand();
|
|
|
9562
10315
|
|
|
9563
10316
|
// src/commands/epic/cancel.ts
|
|
9564
10317
|
var EPIC_CANCEL_REFUSAL_EXIT = 2;
|
|
9565
|
-
var
|
|
10318
|
+
var USAGE15 = 'rafa epic cancel <n> [--reason="<why>"]';
|
|
9566
10319
|
var ISSUE_NUMBER5 = /^[1-9]\d*$/u;
|
|
9567
10320
|
var EPIC_ANSWER = /^#?([1-9]\d*)$/u;
|
|
9568
10321
|
var LINE_BREAK = /\r\n|\n|\r/u;
|
|
@@ -9581,11 +10334,11 @@ function readCancelLine(context) {
|
|
|
9581
10334
|
const [word] = args;
|
|
9582
10335
|
if (args.length !== 1 || word === undefined) {
|
|
9583
10336
|
const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
|
|
9584
|
-
throw lineRefusal(`Expected one epic number, got ${got}`,
|
|
10337
|
+
throw lineRefusal(`Expected one epic number, got ${got}`, USAGE15);
|
|
9585
10338
|
}
|
|
9586
10339
|
if (!ISSUE_NUMBER5.test(word))
|
|
9587
|
-
throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`,
|
|
9588
|
-
const reason = readTextFlag(flags, REASON_FLAG,
|
|
10340
|
+
throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE15);
|
|
10341
|
+
const reason = readTextFlag(flags, REASON_FLAG, USAGE15) ?? null;
|
|
9589
10342
|
return Object.freeze({ epic: Number(word), reason });
|
|
9590
10343
|
}
|
|
9591
10344
|
function readEpicToCancel(issues, number, relations = LABELS_READS) {
|
|
@@ -9711,7 +10464,7 @@ async function commentOn(writes, issue, body) {
|
|
|
9711
10464
|
}
|
|
9712
10465
|
async function applyMove(input, dependent, reading) {
|
|
9713
10466
|
const { change } = reading;
|
|
9714
|
-
const base = { issue: change.issue, waitsOn: dependent.waitsOn, answer: { kind: "moved", to: change.to } };
|
|
10467
|
+
const base = { issue: change.issue, waitsOn: dependent.waitsOn, answer: { kind: "moved", from: change.from, to: change.to } };
|
|
9715
10468
|
try {
|
|
9716
10469
|
const writes = { board: input.writes.board, relations: input.writes.moves, issues: input.issues };
|
|
9717
10470
|
const outcome = await applyEpicMove(writes, reading, cancelMoveReason(input.epic));
|
|
@@ -9921,6 +10674,7 @@ function cancelFailure(result2) {
|
|
|
9921
10674
|
}
|
|
9922
10675
|
async function runEpicCancel(context, seams) {
|
|
9923
10676
|
const result2 = await cancelEpic(context, seams);
|
|
10677
|
+
await refreshProjectAfterEpic(context, seams, cancelTarget(result2));
|
|
9924
10678
|
const failure = cancelFailure(result2);
|
|
9925
10679
|
if (failure !== null)
|
|
9926
10680
|
throw failure;
|
|
@@ -9967,7 +10721,7 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
9967
10721
|
import { join as join22 } from "path";
|
|
9968
10722
|
|
|
9969
10723
|
// src/board/epic-template.ts
|
|
9970
|
-
import { existsSync as existsSync4, readFileSync as
|
|
10724
|
+
import { existsSync as existsSync4, readFileSync as readFileSync10 } from "fs";
|
|
9971
10725
|
import { dirname as dirname3, join as join19 } from "path";
|
|
9972
10726
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9973
10727
|
var MODULE_DIR2 = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -9984,7 +10738,7 @@ function readEpicTemplate(moduleDir = MODULE_DIR2) {
|
|
|
9984
10738
|
if (!existsSync4(source)) {
|
|
9985
10739
|
throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
|
|
9986
10740
|
}
|
|
9987
|
-
return
|
|
10741
|
+
return readFileSync10(source, "utf8");
|
|
9988
10742
|
}
|
|
9989
10743
|
function placeholderCriteria(criteria) {
|
|
9990
10744
|
if (criteria === null)
|
|
@@ -10075,7 +10829,7 @@ function renderEpicCost(cost, estimate) {
|
|
|
10075
10829
|
}
|
|
10076
10830
|
|
|
10077
10831
|
// src/epic/verify-plan.ts
|
|
10078
|
-
import { existsSync as existsSync5, readFileSync as
|
|
10832
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
|
|
10079
10833
|
import { dirname as dirname4, join as join20 } from "path";
|
|
10080
10834
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10081
10835
|
var MODULE_DIR3 = dirname4(fileURLToPath3(import.meta.url));
|
|
@@ -10141,7 +10895,7 @@ function readVerifyPrompt(moduleDir = MODULE_DIR3) {
|
|
|
10141
10895
|
if (found === undefined) {
|
|
10142
10896
|
throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
|
|
10143
10897
|
}
|
|
10144
|
-
return
|
|
10898
|
+
return readFileSync11(found, "utf8");
|
|
10145
10899
|
}
|
|
10146
10900
|
function fenceFor2(text) {
|
|
10147
10901
|
const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
|
|
@@ -10467,16 +11221,16 @@ async function runVerification(options) {
|
|
|
10467
11221
|
var EPIC_CLOSE_REFUSAL_EXIT = 2;
|
|
10468
11222
|
var ACCEPT_UNCHECKED_FLAG = "accept-unchecked";
|
|
10469
11223
|
var PLAN_TOOLS = ["Read", "Grep", "Glob"];
|
|
10470
|
-
var
|
|
11224
|
+
var USAGE16 = `rafa epic close <n> [--${ACCEPT_UNCHECKED_FLAG}]`;
|
|
10471
11225
|
var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
|
|
10472
11226
|
function ref7(issue) {
|
|
10473
11227
|
return `#${String(issue)}`;
|
|
10474
11228
|
}
|
|
10475
|
-
function
|
|
11229
|
+
function oneLine3(text) {
|
|
10476
11230
|
return text.replace(/\s+/gu, " ").trim();
|
|
10477
11231
|
}
|
|
10478
11232
|
function criterionName(criterion) {
|
|
10479
|
-
return `criterion ${String(criterion.number)}: ${
|
|
11233
|
+
return `criterion ${String(criterion.number)}: ${oneLine3(criterion.text)}`;
|
|
10480
11234
|
}
|
|
10481
11235
|
function criteria(count) {
|
|
10482
11236
|
return count === 1 ? "1 criterion" : `${String(count)} criteria`;
|
|
@@ -10489,10 +11243,10 @@ function readCloseLine(context) {
|
|
|
10489
11243
|
const [word] = args;
|
|
10490
11244
|
if (args.length !== 1 || word === undefined) {
|
|
10491
11245
|
const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
|
|
10492
|
-
throw lineRefusal(`Expected one epic number, got ${got}`,
|
|
11246
|
+
throw lineRefusal(`Expected one epic number, got ${got}`, USAGE16);
|
|
10493
11247
|
}
|
|
10494
11248
|
if (!ISSUE_NUMBER6.test(word))
|
|
10495
|
-
throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`,
|
|
11249
|
+
throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, USAGE16);
|
|
10496
11250
|
const acceptUnchecked = readSwitch(ACCEPT_UNCHECKED_FLAG, flags[ACCEPT_UNCHECKED_FLAG], `Write it after the epic number: rafa epic close <n> --${ACCEPT_UNCHECKED_FLAG}.`);
|
|
10497
11251
|
return Object.freeze({ epic: Number(word), acceptUnchecked });
|
|
10498
11252
|
}
|
|
@@ -10500,7 +11254,7 @@ function refuseOpenMembers(number, members) {
|
|
|
10500
11254
|
const open = members.filter((member) => member.state === "OPEN");
|
|
10501
11255
|
if (open.length === 0)
|
|
10502
11256
|
return;
|
|
10503
|
-
const named = open.map((member) => `${ref7(member.number)} ${
|
|
11257
|
+
const named = open.map((member) => `${ref7(member.number)} ${oneLine3(member.title)}`).join(", ");
|
|
10504
11258
|
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");
|
|
10505
11259
|
}
|
|
10506
11260
|
function labelsEpicToClose(issues, epic) {
|
|
@@ -10572,7 +11326,7 @@ function closeTriageFile(root, epic) {
|
|
|
10572
11326
|
return join22(root, ".rafa", `epic-${String(epic)}-close`);
|
|
10573
11327
|
}
|
|
10574
11328
|
function failedCheckArtifact(epic, criterion) {
|
|
10575
|
-
return `epic ${ref7(epic)} acceptance criterion: ${
|
|
11329
|
+
return `epic ${ref7(epic)} acceptance criterion: ${oneLine3(criterion.text)}`;
|
|
10576
11330
|
}
|
|
10577
11331
|
function failedCheckReport(epic, failure, commit) {
|
|
10578
11332
|
const { criterion } = failure.check;
|
|
@@ -10596,7 +11350,7 @@ function failedCheckReport(epic, failure, commit) {
|
|
|
10596
11350
|
skillsUsed: [],
|
|
10597
11351
|
blockers: [],
|
|
10598
11352
|
outOfScopeBugs: [Object.freeze({
|
|
10599
|
-
what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${
|
|
11353
|
+
what: `Epic ${ref7(epic)} acceptance criterion ${String(criterion.number)} fails against main: ${oneLine3(criterion.text)}`,
|
|
10600
11354
|
artifact: failedCheckArtifact(epic, criterion),
|
|
10601
11355
|
security: false,
|
|
10602
11356
|
extras: []
|
|
@@ -10689,9 +11443,9 @@ async function weighResults(run, epic, ran) {
|
|
|
10689
11443
|
if (result2.kind === "unanswered")
|
|
10690
11444
|
warn2(run, `No answer for ${name} \u2014 ${result2.reason}`);
|
|
10691
11445
|
else if (result2.result === "pass")
|
|
10692
|
-
say(run, `\u2713 Passed ${name} \u2014 ${
|
|
11446
|
+
say(run, `\u2713 Passed ${name} \u2014 ${oneLine3(result2.evidence)}`);
|
|
10693
11447
|
else
|
|
10694
|
-
warn2(run, `\u2717 Failed ${name} \u2014 ${
|
|
11448
|
+
warn2(run, `\u2717 Failed ${name} \u2014 ${oneLine3(result2.evidence)}`);
|
|
10695
11449
|
}
|
|
10696
11450
|
if (!ran.removal.ok) {
|
|
10697
11451
|
const said = ran.removal.said === "" ? "git said nothing" : ran.removal.said;
|
|
@@ -10788,6 +11542,7 @@ async function closeEpic(context, seams) {
|
|
|
10788
11542
|
}
|
|
10789
11543
|
async function runEpicClose(context, seams) {
|
|
10790
11544
|
const result2 = await closeEpic(context, seams);
|
|
11545
|
+
await refreshProjectAfterEpic(context, seams, closeTarget(result2));
|
|
10791
11546
|
if (context.outputMode === "json")
|
|
10792
11547
|
context.output.result(result2);
|
|
10793
11548
|
}
|
|
@@ -10874,7 +11629,7 @@ function createEpicDeferCommand(seams = {}) {
|
|
|
10874
11629
|
var defer_default = createEpicDeferCommand();
|
|
10875
11630
|
|
|
10876
11631
|
// src/commands/epic/new.ts
|
|
10877
|
-
var
|
|
11632
|
+
var USAGE17 = 'rafa epic new "<title>" --slug=<slug> [--horizon=now|next|later]';
|
|
10878
11633
|
var EPIC_NEW_REFUSAL_EXIT = 2;
|
|
10879
11634
|
var DEFAULT_HORIZON = "later";
|
|
10880
11635
|
var KEBAB_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
@@ -10912,26 +11667,26 @@ function readTitle(args) {
|
|
|
10912
11667
|
const [title] = args;
|
|
10913
11668
|
if (args.length !== 1 || title === undefined) {
|
|
10914
11669
|
const got = args.length === 0 ? "none" : `${String(args.length)}: ${args.join(" ")}`;
|
|
10915
|
-
throw lineRefusal(`Expected one title, quoted, got ${got}`,
|
|
11670
|
+
throw lineRefusal(`Expected one title, quoted, got ${got}`, USAGE17);
|
|
10916
11671
|
}
|
|
10917
11672
|
if (title.trim() === "" || LINE_BREAK2.test(title)) {
|
|
10918
|
-
throw lineRefusal("The title has to be one line holding some text",
|
|
11673
|
+
throw lineRefusal("The title has to be one line holding some text", USAGE17);
|
|
10919
11674
|
}
|
|
10920
11675
|
return title.trim();
|
|
10921
11676
|
}
|
|
10922
11677
|
function readEpicDraft(args, flags) {
|
|
10923
11678
|
const title = readTitle(args);
|
|
10924
|
-
const slug = readRequiredFlag(flags, "slug",
|
|
10925
|
-
const horizon = readChoiceFlag(flags, "horizon", HORIZONS,
|
|
11679
|
+
const slug = readRequiredFlag(flags, "slug", USAGE17);
|
|
11680
|
+
const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE17) ?? DEFAULT_HORIZON;
|
|
10926
11681
|
if (!KEBAB_SLUG.test(slug)) {
|
|
10927
11682
|
throw refusal8(`--slug is "${slug}", which is not a kebab word: lowercase letters and digits` + ` joined by single hyphens, as sign-in
|
|
10928
|
-
Usage: ${
|
|
11683
|
+
Usage: ${USAGE17}`);
|
|
10929
11684
|
}
|
|
10930
11685
|
return Object.freeze({ title, slug, horizon });
|
|
10931
11686
|
}
|
|
10932
11687
|
function readNativeEpicDraft(args, flags) {
|
|
10933
11688
|
const title = readTitle(args);
|
|
10934
|
-
const horizon = readChoiceFlag(flags, "horizon", HORIZONS,
|
|
11689
|
+
const horizon = readChoiceFlag(flags, "horizon", HORIZONS, USAGE17) ?? DEFAULT_HORIZON;
|
|
10935
11690
|
return Object.freeze({ title, horizon });
|
|
10936
11691
|
}
|
|
10937
11692
|
function sameLabel(label, name) {
|
|
@@ -11074,12 +11829,13 @@ function renderEpicNew(result2) {
|
|
|
11074
11829
|
}
|
|
11075
11830
|
async function runEpicNew(context, seams) {
|
|
11076
11831
|
const result2 = await createEpic(context, seams);
|
|
11077
|
-
if (context.outputMode
|
|
11078
|
-
|
|
11079
|
-
|
|
11832
|
+
if (context.outputMode !== "json") {
|
|
11833
|
+
for (const text of renderEpicNew(result2))
|
|
11834
|
+
context.output.info(text);
|
|
11080
11835
|
}
|
|
11081
|
-
|
|
11082
|
-
|
|
11836
|
+
await refreshProjectAfterEpic(context, seams, newEpicTarget(result2));
|
|
11837
|
+
if (context.outputMode === "json")
|
|
11838
|
+
context.output.result(result2);
|
|
11083
11839
|
}
|
|
11084
11840
|
function createEpicNewCommand(seams = {}) {
|
|
11085
11841
|
const command = {
|
|
@@ -11170,12 +11926,12 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
11170
11926
|
var promote_default = createEpicPromoteCommand();
|
|
11171
11927
|
|
|
11172
11928
|
// src/commands/init.ts
|
|
11173
|
-
import { existsSync as existsSync8, readFileSync as
|
|
11929
|
+
import { existsSync as existsSync8, readFileSync as readFileSync15 } from "fs";
|
|
11174
11930
|
import { homedir as homedir2 } from "os";
|
|
11175
11931
|
import { join as join26, relative as relative5, sep as sep3 } from "path";
|
|
11176
11932
|
|
|
11177
11933
|
// src/agents/vendorable.ts
|
|
11178
|
-
import { readdirSync, readFileSync as
|
|
11934
|
+
import { readdirSync, readFileSync as readFileSync12 } from "fs";
|
|
11179
11935
|
import { isAbsolute as isAbsolute3, join as join23 } from "path";
|
|
11180
11936
|
function planFiles(dir) {
|
|
11181
11937
|
let entries;
|
|
@@ -11188,7 +11944,7 @@ function planFiles(dir) {
|
|
|
11188
11944
|
}
|
|
11189
11945
|
function readText2(path) {
|
|
11190
11946
|
try {
|
|
11191
|
-
return
|
|
11947
|
+
return readFileSync12(path, "utf8");
|
|
11192
11948
|
} catch {
|
|
11193
11949
|
return null;
|
|
11194
11950
|
}
|
|
@@ -11233,7 +11989,7 @@ function vendorableAgentWarnings(agents, root) {
|
|
|
11233
11989
|
|
|
11234
11990
|
// src/project/gitignore.ts
|
|
11235
11991
|
import { createHash } from "crypto";
|
|
11236
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as
|
|
11992
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
|
|
11237
11993
|
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join24 } from "path";
|
|
11238
11994
|
var GITIGNORE_FILE = ".gitignore";
|
|
11239
11995
|
var DIGEST_NAME = "tracking.digest";
|
|
@@ -11330,7 +12086,7 @@ function attempt(file, failure, act) {
|
|
|
11330
12086
|
function readIfPresent2(file) {
|
|
11331
12087
|
if (!existsSync6(file))
|
|
11332
12088
|
return null;
|
|
11333
|
-
return attempt(file, "cannot be read", () =>
|
|
12089
|
+
return attempt(file, "cannot be read", () => readFileSync13(file, "utf8"));
|
|
11334
12090
|
}
|
|
11335
12091
|
function writeTrackingGitignore(root, flags) {
|
|
11336
12092
|
requireAbsolute(root);
|
|
@@ -11340,7 +12096,7 @@ function writeTrackingGitignore(root, flags) {
|
|
|
11340
12096
|
if (next === text)
|
|
11341
12097
|
return { file, change: "unchanged" };
|
|
11342
12098
|
attempt(file, "cannot be written", () => {
|
|
11343
|
-
|
|
12099
|
+
writeFileSync4(file, next);
|
|
11344
12100
|
});
|
|
11345
12101
|
const change = text === null ? "created" : "updated";
|
|
11346
12102
|
return { file, change };
|
|
@@ -11379,7 +12135,7 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
|
11379
12135
|
print2(exposureNotice(root));
|
|
11380
12136
|
attempt(digestFile, "cannot be written", () => {
|
|
11381
12137
|
mkdirSync3(dirname5(digestFile), { recursive: true });
|
|
11382
|
-
|
|
12138
|
+
writeFileSync4(digestFile, recorded);
|
|
11383
12139
|
});
|
|
11384
12140
|
return { digestFile, changed: true, printed: flags.trackingAll };
|
|
11385
12141
|
}
|
|
@@ -11468,14 +12224,14 @@ async function promptForRoot(found, prompter, seams) {
|
|
|
11468
12224
|
}
|
|
11469
12225
|
|
|
11470
12226
|
// src/project/scaffold.ts
|
|
11471
|
-
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as
|
|
12227
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
11472
12228
|
import { join as join25 } from "path";
|
|
11473
12229
|
|
|
11474
12230
|
// src/commands/init-release.ts
|
|
11475
|
-
import { writeFileSync as
|
|
12231
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
11476
12232
|
|
|
11477
12233
|
// src/release/setting.ts
|
|
11478
|
-
import { readFileSync as
|
|
12234
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
11479
12235
|
var RELEASE_ENABLED_SETTING = "release.enabled";
|
|
11480
12236
|
function releaseEnabledIn(text, path) {
|
|
11481
12237
|
return parseConfigText(text, path).values.releaseEnabled ?? null;
|
|
@@ -11484,7 +12240,7 @@ function readReleaseSetting(root) {
|
|
|
11484
12240
|
const path = configFilePath(root);
|
|
11485
12241
|
let text;
|
|
11486
12242
|
try {
|
|
11487
|
-
text =
|
|
12243
|
+
text = readFileSync14(path, "utf8");
|
|
11488
12244
|
} catch (error) {
|
|
11489
12245
|
return { enabled: null, text: "", problem: `${path} could not be read: ${messageOf(error)}` };
|
|
11490
12246
|
}
|
|
@@ -11511,7 +12267,7 @@ function enabledLineIn(lines, open) {
|
|
|
11511
12267
|
}
|
|
11512
12268
|
return null;
|
|
11513
12269
|
}
|
|
11514
|
-
function
|
|
12270
|
+
function appended2(lines, block) {
|
|
11515
12271
|
const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
|
|
11516
12272
|
return [...body, ...block, ""];
|
|
11517
12273
|
}
|
|
@@ -11547,7 +12303,7 @@ function withReleaseEnabled(text, enabled) {
|
|
|
11547
12303
|
}
|
|
11548
12304
|
if (namesRelease(text))
|
|
11549
12305
|
return null;
|
|
11550
|
-
return
|
|
12306
|
+
return appended2(lines, ["release:", setting]).join(`
|
|
11551
12307
|
`);
|
|
11552
12308
|
}
|
|
11553
12309
|
|
|
@@ -11613,7 +12369,7 @@ function writeSetting(root, text, enabled) {
|
|
|
11613
12369
|
return `the file this run would have written reads ${RELEASE_ENABLED_SETTING} as ${describeValue(read)}`;
|
|
11614
12370
|
}
|
|
11615
12371
|
try {
|
|
11616
|
-
|
|
12372
|
+
writeFileSync5(path, written, "utf8");
|
|
11617
12373
|
} catch (error) {
|
|
11618
12374
|
return `it could not be written: ${messageOf(error)}`;
|
|
11619
12375
|
}
|
|
@@ -11714,6 +12470,9 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
11714
12470
|
"# board:",
|
|
11715
12471
|
"# trustedAuthors: [] # logins trusted with board text besides the repo write-holders",
|
|
11716
12472
|
`# relationships: ${CONFIG_DEFAULTS.boardRelationships} # labels | native, where epics and blockers are recorded`,
|
|
12473
|
+
"# project:",
|
|
12474
|
+
`# template: ${CONFIG_DEFAULTS.boardProjectTemplate} # the project URL rafa init --board --project copies`,
|
|
12475
|
+
"# number: # the project rafa mirrors the board to; unset until rafa init --board --project",
|
|
11717
12476
|
"# roadmap:",
|
|
11718
12477
|
"# issue: # the issue plan create --next reads; unset is the one titled Roadmap",
|
|
11719
12478
|
"# claims:",
|
|
@@ -11834,7 +12593,7 @@ function ensureFile(path, text) {
|
|
|
11834
12593
|
if (existsSync7(path))
|
|
11835
12594
|
return { path, kind: "file", change: "unchanged" };
|
|
11836
12595
|
attempt2(path, "cannot be written", () => {
|
|
11837
|
-
|
|
12596
|
+
writeFileSync6(path, text, { encoding: "utf8", flag: "wx" });
|
|
11838
12597
|
});
|
|
11839
12598
|
return { path, kind: "file", change: "created" };
|
|
11840
12599
|
}
|
|
@@ -11905,6 +12664,15 @@ function readEpicGuardFlag(value) {
|
|
|
11905
12664
|
return false;
|
|
11906
12665
|
throw refusal9([`rafa init: --epic-guard takes no value, and read "${value}" as one;` + " install the workflow with --epic-guard, or leave it out with --no-epic-guard"]);
|
|
11907
12666
|
}
|
|
12667
|
+
function readProjectFlag(value) {
|
|
12668
|
+
if (value === undefined)
|
|
12669
|
+
return null;
|
|
12670
|
+
if (value === true || value === "true")
|
|
12671
|
+
return true;
|
|
12672
|
+
if (value === false || value === "false")
|
|
12673
|
+
return false;
|
|
12674
|
+
throw refusal9([`rafa init: --project takes no value, and read "${value}" as one;` + " create the GitHub project with --project, or leave it out with --no-project"]);
|
|
12675
|
+
}
|
|
11908
12676
|
function readReleaseFlag(value) {
|
|
11909
12677
|
if (value === undefined)
|
|
11910
12678
|
return null;
|
|
@@ -11969,7 +12737,7 @@ function resolvedConfig2(root, home, warn3) {
|
|
|
11969
12737
|
function checkGitignore(root, flags) {
|
|
11970
12738
|
const file = join26(root, GITIGNORE_FILE);
|
|
11971
12739
|
try {
|
|
11972
|
-
const text = existsSync8(file) ?
|
|
12740
|
+
const text = existsSync8(file) ? readFileSync15(file, "utf8") : null;
|
|
11973
12741
|
withTrackingBlock(text, flags, file);
|
|
11974
12742
|
} catch (error) {
|
|
11975
12743
|
const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
|
|
@@ -12066,7 +12834,8 @@ function renderInit(result2) {
|
|
|
12066
12834
|
...renderReleaseStep(result2.release),
|
|
12067
12835
|
...renderBoardStep(result2.board),
|
|
12068
12836
|
...renderEpicGuardStep(result2.epicGuard),
|
|
12069
|
-
...renderRelationsMoveStep(result2.relationsMove ?? null)
|
|
12837
|
+
...renderRelationsMoveStep(result2.relationsMove ?? null),
|
|
12838
|
+
...renderProjectStep(result2.project)
|
|
12070
12839
|
];
|
|
12071
12840
|
return result2.changed ? [head, ...changed, ...steps] : [head, ...steps, "Nothing changed."];
|
|
12072
12841
|
}
|
|
@@ -12075,6 +12844,7 @@ async function runInit(context, seams) {
|
|
|
12075
12844
|
const wantsBoard = readBoardFlag(context.flags["board"]);
|
|
12076
12845
|
const wantsGuard = readEpicGuardFlag(context.flags["epic-guard"]);
|
|
12077
12846
|
const wantsRelease = readReleaseFlag(context.flags["release"]);
|
|
12847
|
+
const wantsProject = readProjectFlag(context.flags["project"]);
|
|
12078
12848
|
const yes = readYesFlag(context.flags["yes"]);
|
|
12079
12849
|
const start2 = seams.cwd();
|
|
12080
12850
|
const home = seams.home();
|
|
@@ -12098,13 +12868,23 @@ async function runInit(context, seams) {
|
|
|
12098
12868
|
isTerminal: seams.isTerminal,
|
|
12099
12869
|
openPrompter: seams.openPrompter
|
|
12100
12870
|
});
|
|
12871
|
+
const project = await runProjectStep({
|
|
12872
|
+
wanted: wantsProject,
|
|
12873
|
+
board,
|
|
12874
|
+
root: scopes.written.root,
|
|
12875
|
+
config: scopes.config,
|
|
12876
|
+
openGh: () => seams.gh(scopes.written.root),
|
|
12877
|
+
isTerminal: seams.isTerminal,
|
|
12878
|
+
openPrompter: seams.openPrompter
|
|
12879
|
+
});
|
|
12101
12880
|
const result2 = {
|
|
12102
12881
|
...scopes.written,
|
|
12103
|
-
changed: scopes.written.changed || release.changed || boardStepChanged(board) || epicGuardChanged(epicGuard) || relationsMoveChanged(relationsMove),
|
|
12882
|
+
changed: scopes.written.changed || release.changed || boardStepChanged(board) || epicGuardChanged(epicGuard) || relationsMoveChanged(relationsMove) || projectStepChanged(project),
|
|
12104
12883
|
release,
|
|
12105
12884
|
board,
|
|
12106
12885
|
epicGuard,
|
|
12107
|
-
...relationsMove === null ? {} : { relationsMove }
|
|
12886
|
+
...relationsMove === null ? {} : { relationsMove },
|
|
12887
|
+
project
|
|
12108
12888
|
};
|
|
12109
12889
|
if (context.outputMode === "json")
|
|
12110
12890
|
context.output.result(result2);
|
|
@@ -12121,6 +12901,8 @@ async function runInit(context, seams) {
|
|
|
12121
12901
|
context.output.warn(line);
|
|
12122
12902
|
for (const line of relationsMove?.warnings ?? [])
|
|
12123
12903
|
context.output.warn(line);
|
|
12904
|
+
for (const line of project.warnings)
|
|
12905
|
+
context.output.warn(line);
|
|
12124
12906
|
for (const line of vendorableAgentWarnings(result2.vendorableAgents, result2.root))
|
|
12125
12907
|
context.output.warn(line);
|
|
12126
12908
|
}
|
|
@@ -12130,7 +12912,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
12130
12912
|
subject: "init",
|
|
12131
12913
|
action: "init",
|
|
12132
12914
|
summary: "set up a rafa project: its root, .rafa/config.yaml, the tree and the .gitignore entry",
|
|
12133
|
-
description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
|
|
12915
|
+
description: "Sets up a rafa project. On a terminal it lists the root candidates, the git toplevel or" + " the working directory outside a repository and then the outermost monorepo root above it, each" + " refused one with its reason, and reads a candidate number or a path typed freely. `--root=<path>`" + " names the root instead, absolute or relative to the working directory, and `--yes` takes the" + " first candidate; without a terminal one of the two is needed. Under the root it writes" + " `.rafa/config.yaml` with every setting commented out at its default, the tree `specs/`, `plans/`," + " `runs/`, `effort/` and `instincts/` under `.rafa/`, and the rafa block in `.gitignore` from the" + " `tracking` settings; under the home it writes `~/.rafa/config.yaml` and `~/.rafa/instincts/` when" + " they are missing. Only what is missing or stale is written, so a rerun changes no byte and says" + " so. It warns when `~/.rafa/bin` is not on PATH ahead of `~/.bun/bin`, and when a plan under" + " `plan.dir` routes to an agent that resolves only in `~/.claude/agents` and rafa does not ship," + " naming `rafa agent vendor <name>`; it writes nothing under `.claude/` itself. A repository whose pull request provider is `gh` ends with one" + " question about setting up the GitHub board, which `--board` and `--no-board` answer for a" + " script, and then, once the board has run, one question about installing the epic guard" + " workflow `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue" + " and comments why; `--epic-guard` and `--no-epic-guard` answer that one. When `board.relationships`" + " is set, the board is then read for relationships held in the other mode: every write that moves" + " them into the configured one is printed and a terminal is asked once, nothing is written on a no," + " and once every write went through a second question asks whether to remove the old marks. Last, once the" + " board has run, one question asks whether to copy the GitHub project template to the repository's" + " owner, link it, add the issues and fill their fields; `--project` and `--no-project` answer that one." + " On a terminal it also asks once whether every pull request bumps the version and gains" + " a changelog entry, and writes the answer as `release.enabled`; `--release` and `--no-release`" + " answer that one, and `--yes` leaves it unset. With `--output=json` the" + " root and every path checked are the data of the terminal result event.",
|
|
12134
12916
|
args: [],
|
|
12135
12917
|
flags: [
|
|
12136
12918
|
{
|
|
@@ -12153,6 +12935,11 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
12153
12935
|
description: "Install the epic guard workflow without asking, once the board has run:" + " `.github/workflows/epic-guard.yml`, which removes a second `epic:` label from an issue and" + " comments why. `--no-epic-guard` leaves it out. Without either, a terminal is asked once and a" + " run with no terminal leaves it out. A file already at that path is left as it is.",
|
|
12154
12936
|
type: "boolean"
|
|
12155
12937
|
},
|
|
12938
|
+
{
|
|
12939
|
+
name: "project",
|
|
12940
|
+
description: "Create the GitHub project without asking, once the board has run: copy" + " `board.project.template` to the repository's owner, link the repository, add its issues, fill" + " their fields and save `board.project.number`. `--no-project` leaves it out. Without either, a" + " terminal is asked once and a run with no terminal leaves it out.",
|
|
12941
|
+
type: "boolean"
|
|
12942
|
+
},
|
|
12156
12943
|
{
|
|
12157
12944
|
name: "release",
|
|
12158
12945
|
description: "Write `release.enabled: true` without asking, so every pull request bumps" + " `release.versionFile` and gains a `release.changelog` entry. `--no-release` writes `false`." + " Without either, a terminal is asked once, and `--yes` or a run with no terminal leaves the" + " setting unset at its default `auto`.",
|
|
@@ -12183,6 +12970,10 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
12183
12970
|
{
|
|
12184
12971
|
cmd: "rafa init --yes --board --epic-guard",
|
|
12185
12972
|
note: "Sets up the GitHub board and installs the epic guard workflow, asking nothing."
|
|
12973
|
+
},
|
|
12974
|
+
{
|
|
12975
|
+
cmd: "rafa init --yes --board --project",
|
|
12976
|
+
note: "Sets up the GitHub board and its GitHub project, asking nothing. A second run writes nothing."
|
|
12186
12977
|
}
|
|
12187
12978
|
],
|
|
12188
12979
|
outputs: ["text", "json"],
|
|
@@ -12341,7 +13132,7 @@ var check_default = createInstinctCheckCommand();
|
|
|
12341
13132
|
|
|
12342
13133
|
// src/commands/instinct/flag.ts
|
|
12343
13134
|
var NAME = "rafa instinct flag";
|
|
12344
|
-
var
|
|
13135
|
+
var USAGE18 = "rafa instinct flag <id> <reason>";
|
|
12345
13136
|
function refusedFlagMessage(id2, kind, reason) {
|
|
12346
13137
|
return [
|
|
12347
13138
|
`\u274C ${NAME}: the \`${kind}\` learning adapter did not flag "${id2}": ${reason}`,
|
|
@@ -12351,7 +13142,7 @@ function refusedFlagMessage(id2, kind, reason) {
|
|
|
12351
13142
|
}
|
|
12352
13143
|
function blankReasonMessage2(id2) {
|
|
12353
13144
|
return `\u274C ${NAME}: the reason to flag "${id2}" is blank; say why the lesson is wrong.
|
|
12354
|
-
Usage: ${
|
|
13145
|
+
Usage: ${USAGE18}`;
|
|
12355
13146
|
}
|
|
12356
13147
|
function makeAdapter(registry, kind, context) {
|
|
12357
13148
|
try {
|
|
@@ -12365,7 +13156,7 @@ function makeAdapter(registry, kind, context) {
|
|
|
12365
13156
|
}
|
|
12366
13157
|
}
|
|
12367
13158
|
async function flagInstinct(context, seams) {
|
|
12368
|
-
const [id2, reason] = expectTwoArguments(context.args,
|
|
13159
|
+
const [id2, reason] = expectTwoArguments(context.args, USAGE18);
|
|
12369
13160
|
if (reason.trim() === "")
|
|
12370
13161
|
throw new CommandExit(1, blankReasonMessage2(id2));
|
|
12371
13162
|
const project = requireProject(context, NAME);
|
|
@@ -12434,7 +13225,7 @@ var flag_default = createInstinctFlagCommand();
|
|
|
12434
13225
|
|
|
12435
13226
|
// src/commands/instinct/promote.ts
|
|
12436
13227
|
var NAME2 = "rafa instinct promote";
|
|
12437
|
-
var
|
|
13228
|
+
var USAGE19 = "rafa instinct promote";
|
|
12438
13229
|
var CONFIDENCE_DIGITS = 2;
|
|
12439
13230
|
function refusedPullMessage(kind, reason, name = NAME2) {
|
|
12440
13231
|
return `\u274C ${name}: the \`${kind}\` learning adapter answered no blessed set: ${reason}`;
|
|
@@ -12451,7 +13242,7 @@ function makeLearningAdapter(registry, kind, context, name = NAME2) {
|
|
|
12451
13242
|
}
|
|
12452
13243
|
}
|
|
12453
13244
|
async function promotableLessons(context, seams) {
|
|
12454
|
-
expectNoArgument(context.args,
|
|
13245
|
+
expectNoArgument(context.args, USAGE19);
|
|
12455
13246
|
const project = requireProject(context, NAME2);
|
|
12456
13247
|
const config = resolveProjectConfig(project, NAME2, (message) => {
|
|
12457
13248
|
context.output.warn(message);
|
|
@@ -12521,7 +13312,7 @@ var promote_default2 = createInstinctPromoteCommand();
|
|
|
12521
13312
|
|
|
12522
13313
|
// src/commands/instinct/list.ts
|
|
12523
13314
|
var NAME3 = "rafa instinct list";
|
|
12524
|
-
var
|
|
13315
|
+
var USAGE20 = "rafa instinct list [--blessed | --conflicts]";
|
|
12525
13316
|
var CONFIDENCE_DIGITS2 = 2;
|
|
12526
13317
|
var HUNDREDTHS = 100;
|
|
12527
13318
|
function instinctRowLine(entry) {
|
|
@@ -12554,20 +13345,20 @@ function readViewFlag(name, value) {
|
|
|
12554
13345
|
if (value === true || value === "true")
|
|
12555
13346
|
return true;
|
|
12556
13347
|
throw new CommandExit(1, `\u274C --${name} takes no value, and read "${value}" as one
|
|
12557
|
-
Usage: ${
|
|
13348
|
+
Usage: ${USAGE20}`);
|
|
12558
13349
|
}
|
|
12559
13350
|
function readListView(flags) {
|
|
12560
13351
|
const blessed = readViewFlag("blessed", flags["blessed"]);
|
|
12561
13352
|
const conflicts = readViewFlag("conflicts", flags["conflicts"]);
|
|
12562
13353
|
if (blessed && conflicts) {
|
|
12563
13354
|
throw new CommandExit(1, `\u274C --blessed and --conflicts are two views; give one
|
|
12564
|
-
Usage: ${
|
|
13355
|
+
Usage: ${USAGE20}`);
|
|
12565
13356
|
}
|
|
12566
13357
|
if (blessed)
|
|
12567
13358
|
return "blessed";
|
|
12568
13359
|
return conflicts ? "conflicts" : "all";
|
|
12569
13360
|
}
|
|
12570
|
-
function
|
|
13361
|
+
function oneLine4(text) {
|
|
12571
13362
|
return text.trim().replace(/\s+/gu, " ");
|
|
12572
13363
|
}
|
|
12573
13364
|
function byConfidence(left, right) {
|
|
@@ -12600,7 +13391,7 @@ function conflictLines(conflict) {
|
|
|
12600
13391
|
const width = Math.max(...conflict.records.map((record) => record.id.length));
|
|
12601
13392
|
return [
|
|
12602
13393
|
` ${conflict.scope} ${conflict.trigger}`,
|
|
12603
|
-
...conflict.records.map((record) => lessonLine(" ", record, width,
|
|
13394
|
+
...conflict.records.map((record) => lessonLine(" ", record, width, oneLine4(record.action)))
|
|
12604
13395
|
];
|
|
12605
13396
|
}
|
|
12606
13397
|
function renderConflicts(result2) {
|
|
@@ -12665,7 +13456,7 @@ function answer(context, result2, lines) {
|
|
|
12665
13456
|
context.output.info(line);
|
|
12666
13457
|
}
|
|
12667
13458
|
async function runList4(context, seams) {
|
|
12668
|
-
expectNoArgument(context.args,
|
|
13459
|
+
expectNoArgument(context.args, USAGE20);
|
|
12669
13460
|
const view = readListView(context.flags);
|
|
12670
13461
|
if (view === "blessed") {
|
|
12671
13462
|
const result3 = await blessedLessons(context, seams);
|
|
@@ -12728,7 +13519,7 @@ function createInstinctListCommand(seams = {}) {
|
|
|
12728
13519
|
var list_default6 = createInstinctListCommand();
|
|
12729
13520
|
|
|
12730
13521
|
// src/commands/instinct/show.ts
|
|
12731
|
-
var
|
|
13522
|
+
var USAGE21 = "rafa instinct show <id>";
|
|
12732
13523
|
var FIELD_ORDER = [
|
|
12733
13524
|
"trigger",
|
|
12734
13525
|
"kind",
|
|
@@ -12800,11 +13591,11 @@ function unreadableMessage(entry) {
|
|
|
12800
13591
|
}
|
|
12801
13592
|
function unknownIdMessage(id2, scopes) {
|
|
12802
13593
|
const looked = scopes.map((listing) => ` ${listing.scope} ${listing.dir}${listing.exists ? "" : " (no such directory)"}`);
|
|
12803
|
-
return [`\u274C No instinct is filed under "${id2}". Looked in:`, ...looked, `Usage: ${
|
|
13594
|
+
return [`\u274C No instinct is filed under "${id2}". Looked in:`, ...looked, `Usage: ${USAGE21}`].join(`
|
|
12804
13595
|
`);
|
|
12805
13596
|
}
|
|
12806
13597
|
function runShow2(context) {
|
|
12807
|
-
const id2 = expectOneArgument(context.args,
|
|
13598
|
+
const id2 = expectOneArgument(context.args, USAGE21);
|
|
12808
13599
|
const project = instinctProject(context, "rafa instinct show");
|
|
12809
13600
|
const scopes = readScopes({ home: project.home, projectRoot: project.root });
|
|
12810
13601
|
const found = findRecords(scopes, id2);
|
|
@@ -12863,7 +13654,7 @@ function createInstinctShowCommand() {
|
|
|
12863
13654
|
var show_default3 = createInstinctShowCommand();
|
|
12864
13655
|
|
|
12865
13656
|
// src/commands/issue/check.ts
|
|
12866
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
13657
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
|
|
12867
13658
|
import { join as join27, resolve as resolve9 } from "path";
|
|
12868
13659
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
12869
13660
|
var STAMP_FLAG = "stamp";
|
|
@@ -12963,7 +13754,7 @@ async function readReferences(absolute, issue, verify, stamp) {
|
|
|
12963
13754
|
return rows.map((row2) => checkedRef(row2, row2.stamp));
|
|
12964
13755
|
}
|
|
12965
13756
|
const once2 = memoiseVerifier(verify);
|
|
12966
|
-
const before = await readRefsText({ copy:
|
|
13757
|
+
const before = await readRefsText({ copy: readFileSync16(absolute, "utf8"), issue, verify: once2 });
|
|
12967
13758
|
const after = await restampCopyRefs({ path: absolute, issue, verify: once2 });
|
|
12968
13759
|
return before.rows.map((row2, index) => checkedRef(row2, after.rows[index]?.stamp ?? null));
|
|
12969
13760
|
}
|
|
@@ -13038,10 +13829,10 @@ function createIssueCheckCommand(seams = {}) {
|
|
|
13038
13829
|
var check_default2 = createIssueCheckCommand();
|
|
13039
13830
|
|
|
13040
13831
|
// src/commands/issue/comment.ts
|
|
13041
|
-
var
|
|
13832
|
+
var USAGE22 = "rafa issue comment <id> --body=<text>";
|
|
13042
13833
|
async function commentOnIssue(context, seams) {
|
|
13043
|
-
const id2 = expectOneArgument(context.args,
|
|
13044
|
-
const body = readRequiredFlag(context.flags, "body",
|
|
13834
|
+
const id2 = expectOneArgument(context.args, USAGE22);
|
|
13835
|
+
const body = readRequiredFlag(context.flags, "body", USAGE22);
|
|
13045
13836
|
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
13046
13837
|
const ref8 = issueRef(tracker, id2);
|
|
13047
13838
|
await onTracker(tracker, `comment on issue ${id2}`, () => tracker.comment(ref8, body));
|
|
@@ -13166,24 +13957,24 @@ async function settleSpecLine(draft, tracker) {
|
|
|
13166
13957
|
}
|
|
13167
13958
|
|
|
13168
13959
|
// src/commands/issue/create.ts
|
|
13169
|
-
var
|
|
13960
|
+
var USAGE23 = "rafa issue create --title=<text> [--body=<text> | --body-file=<path>] [--type=<type>] [--module=<name>] [--priority=<priority>]";
|
|
13170
13961
|
var STDIN_PATH = "-";
|
|
13171
13962
|
var DEFAULT_ISSUE_TYPE = "code";
|
|
13172
13963
|
function readBodyFileFlag(flags) {
|
|
13173
|
-
const path = readNonBlankFlag(flags, "body-file",
|
|
13964
|
+
const path = readNonBlankFlag(flags, "body-file", USAGE23);
|
|
13174
13965
|
if (path === undefined || flags.body === undefined)
|
|
13175
13966
|
return path;
|
|
13176
|
-
throw lineRefusal("--body and --body-file cannot be used together: name the body one way",
|
|
13967
|
+
throw lineRefusal("--body and --body-file cannot be used together: name the body one way", USAGE23);
|
|
13177
13968
|
}
|
|
13178
13969
|
function readIssueDraft(flags) {
|
|
13179
13970
|
readBodyFileFlag(flags);
|
|
13180
13971
|
return {
|
|
13181
13972
|
opt: 0,
|
|
13182
|
-
title: readRequiredFlag(flags, "title",
|
|
13183
|
-
body: readTextFlag(flags, "body",
|
|
13184
|
-
type: readChoiceFlag(flags, "type", ISSUE_TYPES,
|
|
13185
|
-
module: readNonBlankFlag(flags, "module",
|
|
13186
|
-
priority: readChoiceFlag(flags, "priority", ISSUE_PRIORITIES,
|
|
13973
|
+
title: readRequiredFlag(flags, "title", USAGE23),
|
|
13974
|
+
body: readTextFlag(flags, "body", USAGE23) ?? "",
|
|
13975
|
+
type: readChoiceFlag(flags, "type", ISSUE_TYPES, USAGE23) ?? DEFAULT_ISSUE_TYPE,
|
|
13976
|
+
module: readNonBlankFlag(flags, "module", USAGE23) ?? TRIAGE_MODULE,
|
|
13977
|
+
priority: readChoiceFlag(flags, "priority", ISSUE_PRIORITIES, USAGE23) ?? null,
|
|
13187
13978
|
project: null,
|
|
13188
13979
|
blockedBy: []
|
|
13189
13980
|
};
|
|
@@ -13194,7 +13985,7 @@ async function readBodyFile(path, seams) {
|
|
|
13194
13985
|
return await read();
|
|
13195
13986
|
} catch (error) {
|
|
13196
13987
|
const where = path === STDIN_PATH ? "standard input" : `"${path}"`;
|
|
13197
|
-
throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`,
|
|
13988
|
+
throw lineRefusal(`--body-file cannot read ${where}: ${messageOf(error)}`, USAGE23);
|
|
13198
13989
|
}
|
|
13199
13990
|
}
|
|
13200
13991
|
async function readIssueLine(flags, seams) {
|
|
@@ -13206,7 +13997,7 @@ function renderCreated(ref8) {
|
|
|
13206
13997
|
return [`Created ${issueName(ref8)}.`, ...urlLines(ref8)];
|
|
13207
13998
|
}
|
|
13208
13999
|
async function createIssue(context, seams) {
|
|
13209
|
-
expectNoArgument(context.args,
|
|
14000
|
+
expectNoArgument(context.args, USAGE23);
|
|
13210
14001
|
const read = await readIssueLine(context.flags, seams);
|
|
13211
14002
|
const blockedLine = readSpecLine(read);
|
|
13212
14003
|
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
@@ -13220,13 +14011,34 @@ async function settledDraft(read, tracker, context) {
|
|
|
13220
14011
|
context.output.warn(`issue create: ${settled.unchecked}`);
|
|
13221
14012
|
return settled.draft;
|
|
13222
14013
|
}
|
|
14014
|
+
function projectIssueNumber(ref8) {
|
|
14015
|
+
if (ref8.kind !== "github" || !/^[1-9]\d*$/u.test(ref8.externalId))
|
|
14016
|
+
return null;
|
|
14017
|
+
const number = Number(ref8.externalId);
|
|
14018
|
+
return Number.isSafeInteger(number) ? number : null;
|
|
14019
|
+
}
|
|
14020
|
+
async function addCreatedToProject(context, seams, ref8) {
|
|
14021
|
+
const issue = projectIssueNumber(ref8);
|
|
14022
|
+
if (issue === null)
|
|
14023
|
+
return;
|
|
14024
|
+
const project = issueProject(context);
|
|
14025
|
+
const config = issueSubjectConfig(project, () => {
|
|
14026
|
+
return;
|
|
14027
|
+
});
|
|
14028
|
+
const lines = await addAndRefreshIssue({
|
|
14029
|
+
config,
|
|
14030
|
+
openGh: () => seams.gh ?? createGhRunner({ cwd: project.root })
|
|
14031
|
+
}, issue);
|
|
14032
|
+
for (const line of lines)
|
|
14033
|
+
context.output.warn(line);
|
|
14034
|
+
}
|
|
13223
14035
|
function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
13224
14036
|
const command = {
|
|
13225
14037
|
name: "issue create",
|
|
13226
14038
|
subject: "issue",
|
|
13227
14039
|
action: "create",
|
|
13228
14040
|
summary: "create an issue on the tracker from a title and a body",
|
|
13229
|
-
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.",
|
|
14041
|
+
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. With `board.project.number` set, a GitHub issue is" + " added to the project and its fields filled, any failure there a warning that keeps the exit code. 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.",
|
|
13230
14042
|
args: [],
|
|
13231
14043
|
flags: [
|
|
13232
14044
|
{
|
|
@@ -13282,12 +14094,12 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13282
14094
|
const created = await createIssue(context, seams);
|
|
13283
14095
|
if (created.ref.warning !== undefined)
|
|
13284
14096
|
context.output.warn(`${issueName(created.ref)}: ${created.ref.warning}`);
|
|
13285
|
-
if (context.outputMode === "json")
|
|
14097
|
+
if (context.outputMode === "json")
|
|
13286
14098
|
context.output.result(created);
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
14099
|
+
else
|
|
14100
|
+
for (const line of renderCreated(created.ref))
|
|
14101
|
+
context.output.info(line);
|
|
14102
|
+
await addCreatedToProject(context, seams, created.ref);
|
|
13291
14103
|
}
|
|
13292
14104
|
};
|
|
13293
14105
|
return Object.freeze(command);
|
|
@@ -13295,7 +14107,7 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13295
14107
|
var create_default = createIssueCreateCommand();
|
|
13296
14108
|
|
|
13297
14109
|
// src/commands/issue/edit-run.ts
|
|
13298
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as
|
|
14110
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
13299
14111
|
import { join as join29 } from "path";
|
|
13300
14112
|
|
|
13301
14113
|
// src/board/issue-edit-body.ts
|
|
@@ -13709,8 +14521,8 @@ function saveConflictBodies(root, issue, now, before, after) {
|
|
|
13709
14521
|
attempt3 += 1;
|
|
13710
14522
|
paths = conflictPaths(issue, stamp, attempt3);
|
|
13711
14523
|
}
|
|
13712
|
-
|
|
13713
|
-
|
|
14524
|
+
writeFileSync7(join29(root, paths.before), before, { flag: "wx" });
|
|
14525
|
+
writeFileSync7(join29(root, paths.after), after, { flag: "wx" });
|
|
13714
14526
|
return paths;
|
|
13715
14527
|
}
|
|
13716
14528
|
function heldOutcome(request, state) {
|
|
@@ -13903,16 +14715,43 @@ function createIssueEditCommand(seams = DEFAULT_EDIT_SEAMS) {
|
|
|
13903
14715
|
}
|
|
13904
14716
|
var edit_default = createIssueEditCommand();
|
|
13905
14717
|
|
|
14718
|
+
// src/commands/issue/move-project.ts
|
|
14719
|
+
function issueNumberOf(externalId) {
|
|
14720
|
+
const number = Number(externalId);
|
|
14721
|
+
return Number.isSafeInteger(number) && number >= 1 ? number : null;
|
|
14722
|
+
}
|
|
14723
|
+
async function refreshMovedIssue(tracker, externalId, options) {
|
|
14724
|
+
const issue = issueNumberOf(externalId);
|
|
14725
|
+
if (tracker !== "github" || issue === null || options.config.boardProjectNumber === null)
|
|
14726
|
+
return [];
|
|
14727
|
+
const refresh = options.refresh ?? refreshIssueItems;
|
|
14728
|
+
try {
|
|
14729
|
+
return (await refresh({ config: options.config, gh: options.openGh() }, [issue])).warnings;
|
|
14730
|
+
} catch (error) {
|
|
14731
|
+
return [refreshFailedWarning(issue, error)];
|
|
14732
|
+
}
|
|
14733
|
+
}
|
|
14734
|
+
|
|
13906
14735
|
// src/commands/issue/move.ts
|
|
13907
|
-
var
|
|
14736
|
+
var USAGE24 = "rafa issue move <id> <state>";
|
|
13908
14737
|
async function moveIssue(context, seams) {
|
|
13909
|
-
const [id2, word] = expectTwoArguments(context.args,
|
|
13910
|
-
const state = readChoice(word, "The state", ISSUE_STATES,
|
|
14738
|
+
const [id2, word] = expectTwoArguments(context.args, USAGE24);
|
|
14739
|
+
const state = readChoice(word, "The state", ISSUE_STATES, USAGE24);
|
|
13911
14740
|
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
13912
14741
|
const ref8 = issueRef(tracker, id2);
|
|
13913
14742
|
const moved = await onTracker(tracker, `move issue ${id2} to ${state}`, () => tracker.transition(ref8, state));
|
|
13914
14743
|
return { tracker: data, ref: ref8, state, warning: moved.warning ?? null };
|
|
13915
14744
|
}
|
|
14745
|
+
function moveProjectOptions(context, seams) {
|
|
14746
|
+
const project = issueProject(context);
|
|
14747
|
+
const { gh } = seams;
|
|
14748
|
+
return {
|
|
14749
|
+
config: issueSubjectConfig(project, () => {
|
|
14750
|
+
return;
|
|
14751
|
+
}),
|
|
14752
|
+
openGh: () => gh ?? createGhRunner({ cwd: project.root })
|
|
14753
|
+
};
|
|
14754
|
+
}
|
|
13916
14755
|
function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
13917
14756
|
const command = {
|
|
13918
14757
|
name: "issue move",
|
|
@@ -13948,9 +14787,12 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13948
14787
|
outputs: ["text", "json"],
|
|
13949
14788
|
run: async (context) => {
|
|
13950
14789
|
const moved = await moveIssue(context, seams);
|
|
14790
|
+
const refreshed = await refreshMovedIssue(moved.tracker.kind, moved.ref.externalId, moveProjectOptions(context, seams));
|
|
13951
14791
|
const warn3 = () => {
|
|
13952
14792
|
if (moved.warning !== null)
|
|
13953
14793
|
context.output.warn(`${issueName(moved.ref)}: ${moved.warning}`);
|
|
14794
|
+
for (const line of refreshed)
|
|
14795
|
+
context.output.warn(line);
|
|
13954
14796
|
};
|
|
13955
14797
|
if (context.outputMode === "json") {
|
|
13956
14798
|
warn3();
|
|
@@ -13966,7 +14808,7 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
13966
14808
|
var move_default3 = createIssueMoveCommand();
|
|
13967
14809
|
|
|
13968
14810
|
// src/commands/issue/show.ts
|
|
13969
|
-
var
|
|
14811
|
+
var USAGE25 = "rafa issue show <id>";
|
|
13970
14812
|
var NO_PRIORITY = "needs-triage";
|
|
13971
14813
|
function renderIssue(kind, issue) {
|
|
13972
14814
|
const project = issue.project === null ? [] : [`Project: ${issue.project}`];
|
|
@@ -13988,7 +14830,7 @@ function renderIssue(kind, issue) {
|
|
|
13988
14830
|
];
|
|
13989
14831
|
}
|
|
13990
14832
|
async function showIssue(context, seams) {
|
|
13991
|
-
const id2 = expectOneArgument(context.args,
|
|
14833
|
+
const id2 = expectOneArgument(context.args, USAGE25);
|
|
13992
14834
|
const { tracker, data } = await resolveIssueTracker(context, seams);
|
|
13993
14835
|
const issue = await onTracker(tracker, `read issue ${id2}`, () => tracker.get(issueRef(tracker, id2)));
|
|
13994
14836
|
return { tracker: data, issue };
|
|
@@ -14036,7 +14878,7 @@ var show_default4 = createIssueShowCommand();
|
|
|
14036
14878
|
|
|
14037
14879
|
// src/commands/loop/list.ts
|
|
14038
14880
|
import { isAbsolute as isAbsolute5, relative as relative6 } from "path";
|
|
14039
|
-
var
|
|
14881
|
+
var USAGE26 = "rafa loop list";
|
|
14040
14882
|
function worktreeLabel2(root, worktree) {
|
|
14041
14883
|
const under = relative6(root, worktree);
|
|
14042
14884
|
return under === "" || under.startsWith("..") || isAbsolute5(under) ? worktree : under;
|
|
@@ -14056,7 +14898,7 @@ function renderSessionList(root, list) {
|
|
|
14056
14898
|
];
|
|
14057
14899
|
}
|
|
14058
14900
|
async function runList5(context, seams) {
|
|
14059
|
-
expectNoArgument(context.args,
|
|
14901
|
+
expectNoArgument(context.args, USAGE26);
|
|
14060
14902
|
const root = projectRoot(context);
|
|
14061
14903
|
const sessions = readRecords(root, seams).filter(isLive).map((session) => {
|
|
14062
14904
|
const checklist = readSessionChecklist(root, session);
|
|
@@ -14102,7 +14944,7 @@ function createLoopListCommand(seams = {}) {
|
|
|
14102
14944
|
var list_default7 = createLoopListCommand();
|
|
14103
14945
|
|
|
14104
14946
|
// src/commands/loop/pause.ts
|
|
14105
|
-
var
|
|
14947
|
+
var USAGE27 = "rafa loop pause [-s|--session-id=<id>]";
|
|
14106
14948
|
function renderPause(result2) {
|
|
14107
14949
|
const { session } = result2;
|
|
14108
14950
|
if (!result2.changed)
|
|
@@ -14111,8 +14953,8 @@ function renderPause(result2) {
|
|
|
14111
14953
|
return [when, " Nothing is marked. `rafa loop resume` goes on, and `rafa loop stop` ends the run."];
|
|
14112
14954
|
}
|
|
14113
14955
|
async function runPause(context, seams) {
|
|
14114
|
-
expectNoArgument(context.args,
|
|
14115
|
-
const { root, record } = pickSession(context,
|
|
14956
|
+
expectNoArgument(context.args, USAGE27);
|
|
14957
|
+
const { root, record } = pickSession(context, USAGE27, "live", seams);
|
|
14116
14958
|
if (!isLive(record)) {
|
|
14117
14959
|
throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to pause.`);
|
|
14118
14960
|
}
|
|
@@ -14152,7 +14994,7 @@ function createLoopPauseCommand(seams = {}) {
|
|
|
14152
14994
|
var pause_default = createLoopPauseCommand();
|
|
14153
14995
|
|
|
14154
14996
|
// src/commands/loop/resume.ts
|
|
14155
|
-
var
|
|
14997
|
+
var USAGE28 = "rafa loop resume [-s|--session-id=<id>]";
|
|
14156
14998
|
function renderResume(result2) {
|
|
14157
14999
|
const { session } = result2;
|
|
14158
15000
|
if (!result2.changed)
|
|
@@ -14160,8 +15002,8 @@ function renderResume(result2) {
|
|
|
14160
15002
|
return [session.task === null ? `Resumed session ${session.sessionId}: it goes on with its next task.` : `Resumed session ${session.sessionId}: it goes on past its running task, line ${session.task.line}, ${session.task.text}`];
|
|
14161
15003
|
}
|
|
14162
15004
|
async function runResume(context, seams) {
|
|
14163
|
-
expectNoArgument(context.args,
|
|
14164
|
-
const { root, record } = pickSession(context,
|
|
15005
|
+
expectNoArgument(context.args, USAGE28);
|
|
15006
|
+
const { root, record } = pickSession(context, USAGE28, "live", seams);
|
|
14165
15007
|
if (!isLive(record)) {
|
|
14166
15008
|
throw refusal(`Session ${record.sessionId} reads ${record.state}: its run has ended, so there is nothing to resume.`, `\`rafa loop start --plan=${record.plan}\` runs its plan again as a new session, its blocked tasks first.`);
|
|
14167
15009
|
}
|
|
@@ -14311,7 +15153,7 @@ var loopStart = Object.freeze({
|
|
|
14311
15153
|
var start_default = endingWith(loopStart);
|
|
14312
15154
|
|
|
14313
15155
|
// src/commands/loop/status.ts
|
|
14314
|
-
var
|
|
15156
|
+
var USAGE29 = "rafa loop status [-s|--session-id=<id>]";
|
|
14315
15157
|
var BLOCKED_TASK_LINE = /^- \[BLOCKED\] (.+)/;
|
|
14316
15158
|
var NO_BLOCKER = "the line trails no blocker comment";
|
|
14317
15159
|
function blockerAt(checklist, lineNum) {
|
|
@@ -14347,8 +15189,8 @@ function renderStatus(status) {
|
|
|
14347
15189
|
];
|
|
14348
15190
|
}
|
|
14349
15191
|
async function runStatus(context, seams) {
|
|
14350
|
-
expectNoArgument(context.args,
|
|
14351
|
-
const { root, record } = pickSession(context,
|
|
15192
|
+
expectNoArgument(context.args, USAGE29);
|
|
15193
|
+
const { root, record } = pickSession(context, USAGE29, "live-or-newest", seams);
|
|
14352
15194
|
const checklist = readSessionChecklist(root, record);
|
|
14353
15195
|
const tasks = checklist === null ? null : countTasks(checklist.tasks);
|
|
14354
15196
|
let eta = null;
|
|
@@ -14402,7 +15244,7 @@ function createLoopStatusCommand(seams = {}) {
|
|
|
14402
15244
|
var status_default2 = createLoopStatusCommand();
|
|
14403
15245
|
|
|
14404
15246
|
// src/commands/loop/stop.ts
|
|
14405
|
-
var
|
|
15247
|
+
var USAGE30 = "rafa loop stop [-s|--session-id=<id>]";
|
|
14406
15248
|
function signalRun(record, interrupt) {
|
|
14407
15249
|
try {
|
|
14408
15250
|
interrupt(record.pid);
|
|
@@ -14455,8 +15297,8 @@ function renderStop(result2) {
|
|
|
14455
15297
|
];
|
|
14456
15298
|
}
|
|
14457
15299
|
async function runStop(context, seams) {
|
|
14458
|
-
expectNoArgument(context.args,
|
|
14459
|
-
const { root, record } = pickSession(context,
|
|
15300
|
+
expectNoArgument(context.args, USAGE30);
|
|
15301
|
+
const { root, record } = pickSession(context, USAGE30, "live", seams);
|
|
14460
15302
|
if (!isLive(record)) {
|
|
14461
15303
|
throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to stop.`);
|
|
14462
15304
|
}
|
|
@@ -14647,14 +15489,14 @@ class WaitUntilError extends Error {
|
|
|
14647
15489
|
this.entry = entry;
|
|
14648
15490
|
}
|
|
14649
15491
|
}
|
|
14650
|
-
function
|
|
15492
|
+
function rowOf3(reason) {
|
|
14651
15493
|
const row2 = WAIT_REASONS.find((candidate) => candidate.reason === reason);
|
|
14652
15494
|
if (row2 === undefined)
|
|
14653
15495
|
throw new Error(`wait reasons: no row for ${reason}`);
|
|
14654
15496
|
return row2;
|
|
14655
15497
|
}
|
|
14656
15498
|
function waitExitCode(reason) {
|
|
14657
|
-
return
|
|
15499
|
+
return rowOf3(reason).exit;
|
|
14658
15500
|
}
|
|
14659
15501
|
function isPlainReason(entry) {
|
|
14660
15502
|
return WAIT_REASONS.some((row2) => row2.reason === entry && row2.reason !== "quiet");
|
|
@@ -14711,7 +15553,7 @@ function matchQuiet(awakeMinutes, until) {
|
|
|
14711
15553
|
}
|
|
14712
15554
|
|
|
14713
15555
|
// src/commands/loop/wait.ts
|
|
14714
|
-
var
|
|
15556
|
+
var USAGE31 = "rafa loop wait [-s|--session-id=<id>] [--until=<reasons>] [--timeout=<minutes>]";
|
|
14715
15557
|
var WAIT_POLL_MS = 5000;
|
|
14716
15558
|
var UNTIL_FLAG = "until";
|
|
14717
15559
|
var TIMEOUT_FLAG = "timeout";
|
|
@@ -14732,12 +15574,12 @@ function readUntil(context) {
|
|
|
14732
15574
|
if (value === undefined)
|
|
14733
15575
|
return DEFAULT_WAIT_UNTIL;
|
|
14734
15576
|
if (typeof value !== "string")
|
|
14735
|
-
throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`,
|
|
15577
|
+
throw lineRefusal2(`--${UNTIL_FLAG} needs a value: --${UNTIL_FLAG}=<reasons>`, USAGE31);
|
|
14736
15578
|
try {
|
|
14737
15579
|
return parseWaitUntil(value);
|
|
14738
15580
|
} catch (error) {
|
|
14739
15581
|
if (error instanceof WaitUntilError)
|
|
14740
|
-
throw lineRefusal2(error.message,
|
|
15582
|
+
throw lineRefusal2(error.message, USAGE31);
|
|
14741
15583
|
throw error;
|
|
14742
15584
|
}
|
|
14743
15585
|
}
|
|
@@ -14748,7 +15590,7 @@ function readTimeout(context) {
|
|
|
14748
15590
|
const minutes = typeof value === "string" ? Number(value) : Number.NaN;
|
|
14749
15591
|
if (typeof value !== "string" || !/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(minutes)) {
|
|
14750
15592
|
const spelled = typeof value === "string" ? JSON.stringify(value) : `--${TIMEOUT_FLAG} with no value`;
|
|
14751
|
-
throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`,
|
|
15593
|
+
throw lineRefusal2(`${spelled} is no timeout, which is a whole number of minutes from 1`, USAGE31);
|
|
14752
15594
|
}
|
|
14753
15595
|
return minutes;
|
|
14754
15596
|
}
|
|
@@ -14857,7 +15699,7 @@ function resultOf5(found, following, seams) {
|
|
|
14857
15699
|
}
|
|
14858
15700
|
function pickOrNoSession(context, seams) {
|
|
14859
15701
|
try {
|
|
14860
|
-
return pickSession(context,
|
|
15702
|
+
return pickSession(context, USAGE31, "live-or-newest", {
|
|
14861
15703
|
...resolveLoopSeams(),
|
|
14862
15704
|
readBranch: seams.readBranch,
|
|
14863
15705
|
isAlive: seams.isAlive
|
|
@@ -14869,7 +15711,7 @@ function pickOrNoSession(context, seams) {
|
|
|
14869
15711
|
}
|
|
14870
15712
|
}
|
|
14871
15713
|
async function runLoopWait(context, seams = {}) {
|
|
14872
|
-
expectNoArgument(context.args,
|
|
15714
|
+
expectNoArgument(context.args, USAGE31);
|
|
14873
15715
|
const until = readUntil(context);
|
|
14874
15716
|
const timeout = readTimeout(context);
|
|
14875
15717
|
const resolved = resolveWaitSeams(seams);
|
|
@@ -14956,7 +15798,7 @@ function createLoopWaitCommand(seams = {}) {
|
|
|
14956
15798
|
var wait_default = createLoopWaitCommand();
|
|
14957
15799
|
|
|
14958
15800
|
// src/commands/module/exec.ts
|
|
14959
|
-
var
|
|
15801
|
+
var USAGE32 = "rafa module exec <module> <action> [args] [flags]";
|
|
14960
15802
|
var execCommand = {
|
|
14961
15803
|
name: "module exec",
|
|
14962
15804
|
subject: "module",
|
|
@@ -14985,13 +15827,13 @@ var execCommand = {
|
|
|
14985
15827
|
const mounts = context.registry.mounts();
|
|
14986
15828
|
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(", ");
|
|
14987
15829
|
throw new CommandExit(1, `\u274C Expected a module and one of its actions; mounted: ${mounted}
|
|
14988
|
-
Usage: ${
|
|
15830
|
+
Usage: ${USAGE32}`);
|
|
14989
15831
|
}
|
|
14990
15832
|
};
|
|
14991
15833
|
var exec_default = Object.freeze(execCommand);
|
|
14992
15834
|
|
|
14993
15835
|
// src/commands/module/list.ts
|
|
14994
|
-
var
|
|
15836
|
+
var USAGE33 = "rafa module list";
|
|
14995
15837
|
function sourceText(source) {
|
|
14996
15838
|
const ref8 = source.ref === null ? "" : `@${source.ref}`;
|
|
14997
15839
|
return `${source.kind} ${source.location}${ref8}`;
|
|
@@ -15012,10 +15854,10 @@ function renderModuleList(list) {
|
|
|
15012
15854
|
return ["Modules:", ...list.modules.flatMap(moduleLines)];
|
|
15013
15855
|
}
|
|
15014
15856
|
async function runList6(context, seams) {
|
|
15015
|
-
expectNoArgument(context.args,
|
|
15857
|
+
expectNoArgument(context.args, USAGE33);
|
|
15016
15858
|
const { project } = context;
|
|
15017
15859
|
if (project === null)
|
|
15018
|
-
throw new CommandExit(1, `\u274C ${
|
|
15860
|
+
throw new CommandExit(1, `\u274C ${USAGE33} runs inside a project`);
|
|
15019
15861
|
let loaded;
|
|
15020
15862
|
try {
|
|
15021
15863
|
const resolved = loadConfig({ root: project.root, home: project.home });
|
|
@@ -15139,7 +15981,7 @@ function allowedUnasked(action, ceiling) {
|
|
|
15139
15981
|
|
|
15140
15982
|
// src/next/lines.ts
|
|
15141
15983
|
var NEXT_USAGE = "rafa next [--dry-run] [--roadmap [--claim-ahead]] [--yes[=<action ids>]]";
|
|
15142
|
-
var
|
|
15984
|
+
var DRY_RUN_FLAG6 = "dry-run";
|
|
15143
15985
|
var ROADMAP_FLAG = "roadmap";
|
|
15144
15986
|
var CLAIM_AHEAD_FLAG = "claim-ahead";
|
|
15145
15987
|
var STATE_MARK = "\uD83D\uDCCD";
|
|
@@ -15164,7 +16006,7 @@ function dryRunLine(dryRun, roadmap) {
|
|
|
15164
16006
|
if (dryRun === "no-terminal") {
|
|
15165
16007
|
return `${STOP_MARK} There is no terminal to answer on, so nothing ran; run rafa next where you can answer,` + ` or type --${YES_FLAG}=${shownIds(BARE_YES_ACTIONS, roadmap).join(",")} to allow those steps unasked.`;
|
|
15166
16008
|
}
|
|
15167
|
-
return `${STOP_MARK} --${
|
|
16009
|
+
return `${STOP_MARK} --${DRY_RUN_FLAG6}: nothing ran.`;
|
|
15168
16010
|
}
|
|
15169
16011
|
function stopLine(stop, state, ceiling, dryRun, roadmap = false) {
|
|
15170
16012
|
if (stop === "dry-run")
|
|
@@ -15194,7 +16036,7 @@ function lineRefusal4(problem2) {
|
|
|
15194
16036
|
Usage: ${NEXT_USAGE}`);
|
|
15195
16037
|
}
|
|
15196
16038
|
function readDryRun(flags) {
|
|
15197
|
-
return readBareFlag(flags,
|
|
16039
|
+
return readBareFlag(flags, DRY_RUN_FLAG6);
|
|
15198
16040
|
}
|
|
15199
16041
|
function readRoadmap(flags) {
|
|
15200
16042
|
return readBareFlag(flags, ROADMAP_FLAG);
|
|
@@ -15243,19 +16085,19 @@ function trackingOf(plan2) {
|
|
|
15243
16085
|
function fastForwardBase(git, plan2) {
|
|
15244
16086
|
const { base } = plan2;
|
|
15245
16087
|
const tracking = trackingOf(plan2);
|
|
15246
|
-
const
|
|
15247
|
-
if (!
|
|
16088
|
+
const counted3 = git(["rev-list", "--left-right", "--count", `${base}...${tracking}`]);
|
|
16089
|
+
if (!counted3.ok) {
|
|
15248
16090
|
return refuse6([
|
|
15249
16091
|
`\u274C Could not read how ${base} stands against ${tracking}.`,
|
|
15250
|
-
...quotedLines(gitSaid(
|
|
16092
|
+
...quotedLines(gitSaid(counted3)),
|
|
15251
16093
|
stillAt(base)
|
|
15252
16094
|
]);
|
|
15253
16095
|
}
|
|
15254
|
-
const standing = parseBaseStanding(
|
|
16096
|
+
const standing = parseBaseStanding(counted3.stdout);
|
|
15255
16097
|
if (standing === null) {
|
|
15256
16098
|
return refuse6([
|
|
15257
16099
|
`\u274C Could not read how ${base} stands against ${tracking}.`,
|
|
15258
|
-
`${INDENT2}git answered ${JSON.stringify(
|
|
16100
|
+
`${INDENT2}git answered ${JSON.stringify(counted3.stdout)}, which is no pair of counts.`,
|
|
15259
16101
|
stillAt(base)
|
|
15260
16102
|
]);
|
|
15261
16103
|
}
|
|
@@ -15443,7 +16285,7 @@ async function runNext(context, seams) {
|
|
|
15443
16285
|
var YES_ID_LIST = YES_ACTIONS.join(", ");
|
|
15444
16286
|
var NEXT_FLAGS = Object.freeze([
|
|
15445
16287
|
{
|
|
15446
|
-
name:
|
|
16288
|
+
name: DRY_RUN_FLAG6,
|
|
15447
16289
|
description: "Print where the project stands and what to do about it, and stop without asking or running it." + " A run with no terminal to answer on and no --yes does this whether or not the flag is typed.",
|
|
15448
16290
|
type: "boolean"
|
|
15449
16291
|
},
|
|
@@ -15616,10 +16458,10 @@ var wrapped2 = wrapPhaseZeroCommand({
|
|
|
15616
16458
|
var create_default2 = endingWith(wrapped2);
|
|
15617
16459
|
|
|
15618
16460
|
// src/commands/plan/risk.ts
|
|
15619
|
-
import { readFileSync as
|
|
16461
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
15620
16462
|
import { resolve as resolve11 } from "path";
|
|
15621
16463
|
var COMMAND6 = "rafa plan risk";
|
|
15622
|
-
var
|
|
16464
|
+
var USAGE34 = "rafa plan risk [<plan>] [--strict]";
|
|
15623
16465
|
var STRICT_HINT = `Type the plan first: ${COMMAND6} <plan> --strict`;
|
|
15624
16466
|
function spawningRunners(root) {
|
|
15625
16467
|
return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
|
|
@@ -15632,7 +16474,7 @@ function riskPlanPath(root, planDir, typed) {
|
|
|
15632
16474
|
throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
|
|
15633
16475
|
const lookedFor = [...new Set([resolve11(root, planDir, DEFAULT_PLAN_FILE), resolve11(root, DEFAULT_PLAN_FILE)])];
|
|
15634
16476
|
throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
|
|
15635
|
-
Usage: ${
|
|
16477
|
+
Usage: ${USAGE34}`);
|
|
15636
16478
|
}
|
|
15637
16479
|
function riskLine(finding) {
|
|
15638
16480
|
const line = finding.line === undefined ? "" : `:${String(finding.line)}`;
|
|
@@ -15644,7 +16486,7 @@ function strictRefusal(report3) {
|
|
|
15644
16486
|
}
|
|
15645
16487
|
async function readRisk(path, project, config, context, seams) {
|
|
15646
16488
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
15647
|
-
return assessPlanRisk({ path, text:
|
|
16489
|
+
return assessPlanRisk({ path, text: readFileSync17(path, "utf8") }, {
|
|
15648
16490
|
repoRoot: project.root,
|
|
15649
16491
|
home: project.home,
|
|
15650
16492
|
settingSources: config.settingSources,
|
|
@@ -15696,7 +16538,7 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
15696
16538
|
outputs: ["text", "json"],
|
|
15697
16539
|
run: async (context) => {
|
|
15698
16540
|
const strict = readSwitch("strict", context.flags["strict"], STRICT_HINT);
|
|
15699
|
-
const typed = expectAtMostOneArgument(context.args,
|
|
16541
|
+
const typed = expectAtMostOneArgument(context.args, USAGE34);
|
|
15700
16542
|
const project = requireProject(context, COMMAND6);
|
|
15701
16543
|
const config = resolveProjectConfig(project, COMMAND6, (message) => {
|
|
15702
16544
|
context.output.warn(message);
|
|
@@ -15727,9 +16569,9 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
15727
16569
|
var risk_default = createPlanRiskCommand();
|
|
15728
16570
|
|
|
15729
16571
|
// src/commands/plan/show.ts
|
|
15730
|
-
import { readFileSync as
|
|
16572
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
15731
16573
|
import { join as join30 } from "path";
|
|
15732
|
-
var
|
|
16574
|
+
var USAGE35 = "rafa plan show <stub> [--tracker]";
|
|
15733
16575
|
var COMMAND7 = "rafa plan show";
|
|
15734
16576
|
function readTrackerFlag(value) {
|
|
15735
16577
|
if (value === undefined || value === false || value === "false")
|
|
@@ -15749,7 +16591,7 @@ function showPlan(plans, stub, tracker) {
|
|
|
15749
16591
|
const file = join30(plans.path, name);
|
|
15750
16592
|
if (!isFile(file))
|
|
15751
16593
|
throw new CommandExit(1, missingText(stub, tracker, join30(plans.label, name)));
|
|
15752
|
-
const model = parsePlan(
|
|
16594
|
+
const model = parsePlan(readFileSync18(file, "utf8"));
|
|
15753
16595
|
const { header } = model;
|
|
15754
16596
|
return {
|
|
15755
16597
|
stub,
|
|
@@ -15825,7 +16667,7 @@ var planShowCommand = {
|
|
|
15825
16667
|
outputs: ["text", "json"],
|
|
15826
16668
|
run: async (context) => {
|
|
15827
16669
|
const tracker = readTrackerFlag(context.flags.tracker);
|
|
15828
|
-
const stub = expectOneArgument(context.args,
|
|
16670
|
+
const stub = expectOneArgument(context.args, USAGE35);
|
|
15829
16671
|
const plans = resolvePlansDir(requireProject(context, COMMAND7), COMMAND7, (message) => {
|
|
15830
16672
|
context.output.warn(message);
|
|
15831
16673
|
});
|
|
@@ -15932,7 +16774,13 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
|
|
|
15932
16774
|
planDir: config.planDir,
|
|
15933
16775
|
worktreeDir: config.loopWorktreeDir,
|
|
15934
16776
|
readBranch: () => readBranch2(project.root),
|
|
15935
|
-
versionGuard: mergeGuardSettings(config)
|
|
16777
|
+
versionGuard: mergeGuardSettings(config),
|
|
16778
|
+
projectRefresh: {
|
|
16779
|
+
boardProjectNumber: config.boardProjectNumber,
|
|
16780
|
+
boardRelationships: config.boardRelationships,
|
|
16781
|
+
roadmapIssue: config.roadmapIssue,
|
|
16782
|
+
releaseFragments: config.releaseFragments
|
|
16783
|
+
}
|
|
15936
16784
|
};
|
|
15937
16785
|
}
|
|
15938
16786
|
async function onProvider(doing, call) {
|
|
@@ -15968,7 +16816,7 @@ Run rafa pr list to see the open pull requests.`, usage);
|
|
|
15968
16816
|
}
|
|
15969
16817
|
|
|
15970
16818
|
// src/commands/pr/current.ts
|
|
15971
|
-
var
|
|
16819
|
+
var USAGE36 = PR_USAGE.current;
|
|
15972
16820
|
var SEPARATOR2 = " \u2014 ";
|
|
15973
16821
|
function trimmedOrNull(value) {
|
|
15974
16822
|
const trimmed = value.trim();
|
|
@@ -15999,9 +16847,9 @@ async function probeChecks(pulls, number) {
|
|
|
15999
16847
|
}
|
|
16000
16848
|
}
|
|
16001
16849
|
async function readCurrent(context, seams) {
|
|
16002
|
-
expectNoArguments(context.args,
|
|
16850
|
+
expectNoArguments(context.args, USAGE36);
|
|
16003
16851
|
const pr = openPrContext(context, seams);
|
|
16004
|
-
const pick = await pickPullRequest(pr, null,
|
|
16852
|
+
const pick = await pickPullRequest(pr, null, USAGE36);
|
|
16005
16853
|
const summary = pick.summary;
|
|
16006
16854
|
const probe = await probeChecks(pr.pulls, pick.number);
|
|
16007
16855
|
const reading = {
|
|
@@ -16166,7 +17014,7 @@ function forecastLine(number, forecast) {
|
|
|
16166
17014
|
}
|
|
16167
17015
|
|
|
16168
17016
|
// src/commands/pr/list.ts
|
|
16169
|
-
var
|
|
17017
|
+
var USAGE37 = PR_USAGE.list;
|
|
16170
17018
|
var UNREADABLE = "?";
|
|
16171
17019
|
var PROBE_LIMIT = 6;
|
|
16172
17020
|
var TITLE_WIDTH = 60;
|
|
@@ -16254,7 +17102,7 @@ async function probe(call) {
|
|
|
16254
17102
|
return { value: null, problem: messageOf(error) };
|
|
16255
17103
|
}
|
|
16256
17104
|
}
|
|
16257
|
-
async function
|
|
17105
|
+
async function rowOf4(pulls, pull2, readBaseOf) {
|
|
16258
17106
|
const [checks, detail] = await Promise.all([
|
|
16259
17107
|
probe(() => pulls.checks(pull2.number)),
|
|
16260
17108
|
probe(() => pulls.get(pull2.number))
|
|
@@ -16270,12 +17118,12 @@ async function rowOf3(pulls, pull2, readBaseOf) {
|
|
|
16270
17118
|
};
|
|
16271
17119
|
}
|
|
16272
17120
|
async function readList(context, seams, now) {
|
|
16273
|
-
expectNoArguments(context.args,
|
|
17121
|
+
expectNoArguments(context.args, USAGE37);
|
|
16274
17122
|
const pr = openPrContext(context, seams);
|
|
16275
17123
|
const open = await onProvider("list the open pull requests", () => pr.pulls.list());
|
|
16276
17124
|
const root = pr.project.root;
|
|
16277
17125
|
const readBaseOf = resolveReleaseEnabled(pr.versionGuard, root).enabled ? createBaseReader((seams.git ?? createGitRunner)(root), pr.versionGuard) : null;
|
|
16278
|
-
const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) =>
|
|
17126
|
+
const rows = await mapWithLimit(open, PROBE_LIMIT, (pull2) => rowOf4(pr.pulls, pull2, readBaseOf));
|
|
16279
17127
|
return { rows, text: renderList(rows, now) };
|
|
16280
17128
|
}
|
|
16281
17129
|
function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
|
|
@@ -16597,6 +17445,55 @@ function plural2(count, noun) {
|
|
|
16597
17445
|
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
16598
17446
|
}
|
|
16599
17447
|
|
|
17448
|
+
// src/commands/pr/merge-project.ts
|
|
17449
|
+
function named2(issues) {
|
|
17450
|
+
return issues.map((issue) => `#${String(issue)}`).join(", ");
|
|
17451
|
+
}
|
|
17452
|
+
function blockingProblemLine(closed, problem2) {
|
|
17453
|
+
return `The project was not updated for the issues ${named2(closed)} were blocking: ${problem2}.` + ` Run \`${BOARD_SYNC_FIX}\` to catch up.`;
|
|
17454
|
+
}
|
|
17455
|
+
function refreshProblemLine(issues, problem2) {
|
|
17456
|
+
return `The project was not updated for ${named2(issues)}: ${problem2}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
|
|
17457
|
+
}
|
|
17458
|
+
function waitsOnOneOf(blockers, closed) {
|
|
17459
|
+
return blockers.kind === "blocked" && blockers.blockers.some((blocker) => blocker.repository === null && closed.has(blocker.number));
|
|
17460
|
+
}
|
|
17461
|
+
async function readBlocking(config, gh, closed) {
|
|
17462
|
+
const repository = config.boardRelationships === "native" ? await readBoardRepository(gh) : "";
|
|
17463
|
+
const relations = selectBoardRelations(config, { gh, repository });
|
|
17464
|
+
const listing = await createGhBoardListing({ gh, mode: relations.mode })();
|
|
17465
|
+
const reading = relations.read(listing);
|
|
17466
|
+
return listing.filter((row2) => waitsOnOneOf(reading.blockersOf(row2), closed)).map((row2) => row2.number);
|
|
17467
|
+
}
|
|
17468
|
+
function consideredBlocking(unblocked, closed) {
|
|
17469
|
+
return (unblocked?.issues ?? []).filter((outcome) => outcome.blockers.some((blocker) => closed.has(blocker))).map((outcome) => outcome.issue);
|
|
17470
|
+
}
|
|
17471
|
+
async function refreshProjectAfterMerge(options) {
|
|
17472
|
+
const { config, warn: warn3, refresh = refreshIssueItems } = options;
|
|
17473
|
+
const closed = [...new Set(closedIssuesIn(options.body))];
|
|
17474
|
+
if (config.boardProjectNumber === null || closed.length === 0)
|
|
17475
|
+
return null;
|
|
17476
|
+
const gh = options.openGh();
|
|
17477
|
+
const wanted = new Set(closed);
|
|
17478
|
+
const warnings = [];
|
|
17479
|
+
let listed2 = [];
|
|
17480
|
+
try {
|
|
17481
|
+
listed2 = await readBlocking(config, gh, wanted);
|
|
17482
|
+
} catch (error) {
|
|
17483
|
+
warnings.push(blockingProblemLine(closed, messageOf(error)));
|
|
17484
|
+
}
|
|
17485
|
+
const blocking = [...new Set([...listed2, ...consideredBlocking(options.unblocked, wanted)])].filter((issue) => !wanted.has(issue)).sort((a, b) => a - b);
|
|
17486
|
+
const issues = [...closed, ...blocking];
|
|
17487
|
+
try {
|
|
17488
|
+
warnings.push(...(await refresh({ config, gh }, issues)).warnings);
|
|
17489
|
+
} catch (error) {
|
|
17490
|
+
warnings.push(refreshProblemLine(issues, messageOf(error)));
|
|
17491
|
+
}
|
|
17492
|
+
for (const line of warnings)
|
|
17493
|
+
warn3(line);
|
|
17494
|
+
return { closed, blocking, warnings };
|
|
17495
|
+
}
|
|
17496
|
+
|
|
16600
17497
|
// src/pr/unchecked.ts
|
|
16601
17498
|
var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
|
|
16602
17499
|
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";
|
|
@@ -16704,16 +17601,16 @@ function readBranches(filter, base) {
|
|
|
16704
17601
|
const patterns = patternListOf(filter);
|
|
16705
17602
|
if (patterns === null)
|
|
16706
17603
|
return UNKNOWN;
|
|
16707
|
-
let
|
|
17604
|
+
let named3 = false;
|
|
16708
17605
|
for (const pattern of patterns) {
|
|
16709
17606
|
const negated = pattern.startsWith("!");
|
|
16710
17607
|
const matches = branchPatternMatches(negated ? pattern.slice(1) : pattern, base);
|
|
16711
17608
|
if (matches === null)
|
|
16712
17609
|
return UNKNOWN;
|
|
16713
17610
|
if (matches)
|
|
16714
|
-
|
|
17611
|
+
named3 = !negated;
|
|
16715
17612
|
}
|
|
16716
|
-
return
|
|
17613
|
+
return named3;
|
|
16717
17614
|
}
|
|
16718
17615
|
function readBranchesIgnore(filter, base) {
|
|
16719
17616
|
const patterns = patternListOf(filter);
|
|
@@ -16888,9 +17785,21 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
|
|
|
16888
17785
|
return null;
|
|
16889
17786
|
}
|
|
16890
17787
|
}
|
|
17788
|
+
function uncheckedReport(unchecked, commentUrl) {
|
|
17789
|
+
return unchecked === null ? null : { workflowCount: unchecked.workflowCount, case: unchecked.reading.case, commentUrl };
|
|
17790
|
+
}
|
|
17791
|
+
async function commentIfUnchecked(pulls, number, unchecked, report4) {
|
|
17792
|
+
if (unchecked === null)
|
|
17793
|
+
return null;
|
|
17794
|
+
const posted = await postUncheckedComment(pulls, number, unchecked, report4.warn);
|
|
17795
|
+
if (posted === null)
|
|
17796
|
+
return null;
|
|
17797
|
+
report4.info(`Commented on #${number} that it was merged with no checks: ${posted.url}`);
|
|
17798
|
+
return posted.url;
|
|
17799
|
+
}
|
|
16891
17800
|
|
|
16892
17801
|
// src/commands/pr/merge.ts
|
|
16893
|
-
var
|
|
17802
|
+
var USAGE38 = PR_USAGE.merge;
|
|
16894
17803
|
var YES_ANSWERS4 = ["y", "yes"];
|
|
16895
17804
|
var DEFAULT_MERGE_SEAMS = Object.freeze({});
|
|
16896
17805
|
function refusal11(lines) {
|
|
@@ -17059,32 +17968,18 @@ async function askToMerge(context, pr, seams, ask) {
|
|
|
17059
17968
|
}
|
|
17060
17969
|
return { go: yes || await confirmed(seams), unchecked };
|
|
17061
17970
|
}
|
|
17062
|
-
function uncheckedReport(unchecked, commentUrl) {
|
|
17063
|
-
return unchecked === null ? null : { workflowCount: unchecked.workflowCount, case: unchecked.reading.case, commentUrl };
|
|
17064
|
-
}
|
|
17065
|
-
async function commentIfUnchecked(context, pr, number, unchecked) {
|
|
17066
|
-
if (unchecked === null)
|
|
17067
|
-
return null;
|
|
17068
|
-
const posted = await postUncheckedComment(pr.pulls, number, unchecked, (message) => {
|
|
17069
|
-
context.output.warn(message);
|
|
17070
|
-
});
|
|
17071
|
-
if (posted === null)
|
|
17072
|
-
return null;
|
|
17073
|
-
context.output.info(`Commented on #${number} that it was merged with no checks: ${posted.url}`);
|
|
17074
|
-
return posted.url;
|
|
17075
|
-
}
|
|
17076
17971
|
async function runMerge2(context, seams) {
|
|
17077
|
-
const yes = readBooleanFlag(context.flags, "yes",
|
|
17078
|
-
const skipChecks = readBooleanFlag(context.flags, "skip-checks",
|
|
17079
|
-
const wanted = readMethodFlag(context.flags,
|
|
17080
|
-
const asked = readPullArgument(context.args,
|
|
17972
|
+
const yes = readBooleanFlag(context.flags, "yes", USAGE38);
|
|
17973
|
+
const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE38);
|
|
17974
|
+
const wanted = readMethodFlag(context.flags, USAGE38);
|
|
17975
|
+
const asked = readPullArgument(context.args, USAGE38);
|
|
17081
17976
|
const pr = openPrContext(context, seams);
|
|
17082
17977
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
17083
17978
|
const method = wanted ?? pr.mergeMethod;
|
|
17084
|
-
const pick = await pickPullRequest(pr, asked,
|
|
17979
|
+
const pick = await pickPullRequest(pr, asked, USAGE38);
|
|
17085
17980
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
17086
17981
|
if (detail === null) {
|
|
17087
|
-
throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`,
|
|
17982
|
+
throw lineRefusal5(`No pull request #${pick.number} at ${pr.project.root}`, USAGE38);
|
|
17088
17983
|
}
|
|
17089
17984
|
const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
|
|
17090
17985
|
recordPlanCi({
|
|
@@ -17140,16 +18035,17 @@ async function runMerge2(context, seams) {
|
|
|
17140
18035
|
throw refusal11([`\u274C ${pr.pulls.kind} would not merge #${detail.number}: ${outcome.detail}`]);
|
|
17141
18036
|
}
|
|
17142
18037
|
context.output.info(`Merged #${detail.number} into ${detail.baseRefName} (${method}).`);
|
|
17143
|
-
const commentUrl = await commentIfUnchecked(context, pr, detail.number, answer2.unchecked);
|
|
17144
|
-
const roadmapTicks = await reportTick(context, pr, seams, detail);
|
|
17145
18038
|
const warn3 = (message) => {
|
|
17146
18039
|
context.output.warn(message);
|
|
17147
18040
|
};
|
|
17148
18041
|
const info2 = (message) => {
|
|
17149
18042
|
context.output.info(message);
|
|
17150
18043
|
};
|
|
18044
|
+
const commentUrl = await commentIfUnchecked(pr.pulls, detail.number, answer2.unchecked, { info: info2, warn: warn3 });
|
|
18045
|
+
const roadmapTicks = await reportTick(context, pr, seams, detail);
|
|
17151
18046
|
const steps = cleanUpAfterMerge(git, detail, { info: info2, warn: warn3 });
|
|
17152
18047
|
const board = await reportAfterCleanUp(context, pr, seams, detail);
|
|
18048
|
+
await refreshProjectAfterMerge({ body: detail.body, config: pr.projectRefresh, openGh: () => openGh(pr, seams), unblocked: board.unblocked, warn: warn3 });
|
|
17153
18049
|
const followUps = reportFollowUps({
|
|
17154
18050
|
root: pr.project.root,
|
|
17155
18051
|
home: pr.project.home,
|
|
@@ -17243,11 +18139,11 @@ var BOM = "\uFEFF";
|
|
|
17243
18139
|
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z /;
|
|
17244
18140
|
var COLOUR = new RegExp(`(?:${String.fromCharCode(27)}|\\^\\[)\\[[0-9;]*m`, "g");
|
|
17245
18141
|
var PREFIX2 = "pr triage evidence";
|
|
17246
|
-
function
|
|
18142
|
+
function named3(value) {
|
|
17247
18143
|
return value === "" ? undefined : value;
|
|
17248
18144
|
}
|
|
17249
18145
|
function stepColumn(value) {
|
|
17250
|
-
return value === UNKNOWN_STEP ? undefined :
|
|
18146
|
+
return value === UNKNOWN_STEP ? undefined : named3(value);
|
|
17251
18147
|
}
|
|
17252
18148
|
function stripText(text) {
|
|
17253
18149
|
const withoutBom = text.startsWith(BOM) ? text.slice(BOM.length) : text;
|
|
@@ -17256,7 +18152,7 @@ function stripText(text) {
|
|
|
17256
18152
|
function groupStep(text) {
|
|
17257
18153
|
if (!text.startsWith(GROUP_STEP_PREFIX))
|
|
17258
18154
|
return;
|
|
17259
|
-
return
|
|
18155
|
+
return named3(text.slice(GROUP_STEP_PREFIX.length).trim());
|
|
17260
18156
|
}
|
|
17261
18157
|
function parseFailedLogLine(line) {
|
|
17262
18158
|
const firstTab = line.indexOf(COLUMN);
|
|
@@ -17265,7 +18161,7 @@ function parseFailedLogLine(line) {
|
|
|
17265
18161
|
return { job: undefined, step: undefined, text: stripText(line) };
|
|
17266
18162
|
}
|
|
17267
18163
|
return {
|
|
17268
|
-
job:
|
|
18164
|
+
job: named3(line.slice(0, firstTab)),
|
|
17269
18165
|
step: stepColumn(line.slice(firstTab + 1, secondTab)),
|
|
17270
18166
|
text: stripText(line.slice(secondTab + 1))
|
|
17271
18167
|
};
|
|
@@ -17373,12 +18269,12 @@ function fencedBlock(body, language) {
|
|
|
17373
18269
|
return `${fence}${language}
|
|
17374
18270
|
${text}${fence}`;
|
|
17375
18271
|
}
|
|
17376
|
-
function
|
|
18272
|
+
function counted3(count, noun) {
|
|
17377
18273
|
return count === 1 ? `1 ${noun}` : `${count} ${noun}s`;
|
|
17378
18274
|
}
|
|
17379
18275
|
function excerptCaption(reading) {
|
|
17380
|
-
const shown =
|
|
17381
|
-
return reading.omitted === 0 ? `The failing job log, all ${shown} of it.` : `The last ${shown} of the failing job log, ${
|
|
18276
|
+
const shown = counted3(reading.lines.length, "line");
|
|
18277
|
+
return reading.omitted === 0 ? `The failing job log, all ${shown} of it.` : `The last ${shown} of the failing job log, ${counted3(reading.omitted, "earlier line")}` + ` omitted of ${reading.total} in all.`;
|
|
17382
18278
|
}
|
|
17383
18279
|
function pullRequestSection(pr) {
|
|
17384
18280
|
const fork = pr.isCrossRepository ? `
|
|
@@ -17391,7 +18287,7 @@ function pullRequestSection(pr) {
|
|
|
17391
18287
|
function classSection(assessment) {
|
|
17392
18288
|
const eligibility = assessment.simple ? "simple, so a pinned resolve plan covers it" : "not simple, so the fix needs judgement about what this pull request is for";
|
|
17393
18289
|
const bump = assessment.dependencyBump ? "yes" : "no";
|
|
17394
|
-
const failing =
|
|
18290
|
+
const failing = counted3(assessment.failing.length, "failing row");
|
|
17395
18291
|
return `- Class: \`${assessment.triageClass}\` (${eligibility})
|
|
17396
18292
|
` + `- Why: ${assessment.reason}
|
|
17397
18293
|
` + `- Checks verdict: \`${assessment.verdict}\`, ${failing} in the rollup
|
|
@@ -17419,7 +18315,7 @@ function excerptSection(input) {
|
|
|
17419
18315
|
const reading = excerptLines(evidence, input.maxExcerptLines);
|
|
17420
18316
|
if (reading.lines.length === 0)
|
|
17421
18317
|
return NO_EXCERPT;
|
|
17422
|
-
const jobs = evidence.jobs.length === 0 ? "" : `${
|
|
18318
|
+
const jobs = evidence.jobs.length === 0 ? "" : `${counted3(evidence.jobs.length, "failing job")}: ` + `${evidence.jobs.map((job) => `\`${job}\``).join(", ")}.
|
|
17423
18319
|
`;
|
|
17424
18320
|
return `${jobs}${excerptCaption(reading)}
|
|
17425
18321
|
|
|
@@ -17670,7 +18566,7 @@ function readLastTriage(comments) {
|
|
|
17670
18566
|
}
|
|
17671
18567
|
|
|
17672
18568
|
// src/commands/pr/show.ts
|
|
17673
|
-
var
|
|
18569
|
+
var USAGE39 = PR_USAGE.show;
|
|
17674
18570
|
var INDENT6 = " ";
|
|
17675
18571
|
var SHORT_SHA = 7;
|
|
17676
18572
|
var NOT_IN_BODY = "not named by the body";
|
|
@@ -17708,19 +18604,19 @@ function factsLine(detail) {
|
|
|
17708
18604
|
function repositoryOf(detail) {
|
|
17709
18605
|
return PULL_URL_REPOSITORY.exec(detail.url.trim())?.[1] ?? null;
|
|
17710
18606
|
}
|
|
17711
|
-
function linkedWord(issue, own,
|
|
18607
|
+
function linkedWord(issue, own, named4) {
|
|
17712
18608
|
if (own !== null && issue.repository !== own) {
|
|
17713
18609
|
return `${issue.repository}#${issue.number} (${OTHER_REPOSITORY})`;
|
|
17714
18610
|
}
|
|
17715
|
-
return
|
|
18611
|
+
return named4.has(issue.number) ? `#${issue.number}` : `#${issue.number} (${NOT_IN_BODY})`;
|
|
17716
18612
|
}
|
|
17717
18613
|
function closesLine(reading) {
|
|
17718
18614
|
const own = repositoryOf(reading.detail);
|
|
17719
|
-
const
|
|
18615
|
+
const named4 = new Set(reading.bodyCloses);
|
|
17720
18616
|
const linked = new Set(reading.detail.closes.filter((issue) => own === null || issue.repository === own).map((issue) => issue.number));
|
|
17721
18617
|
const words = [
|
|
17722
|
-
...reading.detail.closes.map((issue) => linkedWord(issue, own,
|
|
17723
|
-
...[...
|
|
18618
|
+
...reading.detail.closes.map((issue) => linkedWord(issue, own, named4)),
|
|
18619
|
+
...[...named4].filter((number) => !linked.has(number)).map((number) => `#${number} (${KEYWORD_ONLY})`)
|
|
17724
18620
|
];
|
|
17725
18621
|
return words.length === 0 ? "closes none" : `closes ${words.join(", ")}`;
|
|
17726
18622
|
}
|
|
@@ -17801,12 +18697,12 @@ async function probe2(call) {
|
|
|
17801
18697
|
}
|
|
17802
18698
|
}
|
|
17803
18699
|
async function readShow(context, seams) {
|
|
17804
|
-
const asked = readPullArgument(context.args,
|
|
18700
|
+
const asked = readPullArgument(context.args, USAGE39);
|
|
17805
18701
|
const pr = openPrContext(context, seams);
|
|
17806
|
-
const pick = await pickPullRequest(pr, asked,
|
|
18702
|
+
const pick = await pickPullRequest(pr, asked, USAGE39);
|
|
17807
18703
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
17808
18704
|
if (detail === null) {
|
|
17809
|
-
throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`,
|
|
18705
|
+
throw lineRefusal5(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE39);
|
|
17810
18706
|
}
|
|
17811
18707
|
const checks = await probe2(() => pr.pulls.checks(pick.number));
|
|
17812
18708
|
const comments = await probe2(() => pr.pulls.comments(pick.number));
|
|
@@ -18690,7 +19586,7 @@ function withTaskBudget(plan2, usd) {
|
|
|
18690
19586
|
}
|
|
18691
19587
|
|
|
18692
19588
|
// src/pr/plans/load.ts
|
|
18693
|
-
import { existsSync as existsSync10, readFileSync as
|
|
19589
|
+
import { existsSync as existsSync10, readFileSync as readFileSync19 } from "fs";
|
|
18694
19590
|
import { dirname as dirname7, join as join32 } from "path";
|
|
18695
19591
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18696
19592
|
var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
|
|
@@ -18721,7 +19617,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
18721
19617
|
if (found === undefined) {
|
|
18722
19618
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
18723
19619
|
}
|
|
18724
|
-
return
|
|
19620
|
+
return readFileSync19(found, "utf8");
|
|
18725
19621
|
}
|
|
18726
19622
|
var SLOT_PATTERN2 = /\{[A-Z][A-Z0-9_]*\}/g;
|
|
18727
19623
|
var CONFLICT_SENTENCE_SLOT = "CONFLICT_SENTENCE";
|
|
@@ -18861,7 +19757,7 @@ function readAttemptRepeat(input) {
|
|
|
18861
19757
|
}
|
|
18862
19758
|
|
|
18863
19759
|
// src/commands/pr/resolve-loop.ts
|
|
18864
|
-
import { mkdirSync as mkdirSync7, writeFileSync as
|
|
19760
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
18865
19761
|
import { join as join33 } from "path";
|
|
18866
19762
|
var RESOLVE_SUBDIR = join33(".rafa", "resolve");
|
|
18867
19763
|
var RUN_PREFIX = "pr-";
|
|
@@ -18875,7 +19771,7 @@ function writeResolvePlan(write) {
|
|
|
18875
19771
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
18876
19772
|
mkdirSync7(dir, { recursive: true });
|
|
18877
19773
|
const path = join33(dir, pinnedPlanFileName(write.triageClass));
|
|
18878
|
-
|
|
19774
|
+
writeFileSync8(path, write.plan, "utf8");
|
|
18879
19775
|
return path;
|
|
18880
19776
|
}
|
|
18881
19777
|
function resolveLoopArgv(planPath) {
|
|
@@ -18915,7 +19811,7 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
18915
19811
|
}
|
|
18916
19812
|
|
|
18917
19813
|
// src/pr/triage/version-convert.ts
|
|
18918
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
19814
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
|
|
18919
19815
|
import { dirname as dirname8, join as join34, posix } from "path";
|
|
18920
19816
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
18921
19817
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
@@ -19028,7 +19924,7 @@ function planConversion(input, problems) {
|
|
|
19028
19924
|
}
|
|
19029
19925
|
let branchVersionText;
|
|
19030
19926
|
try {
|
|
19031
|
-
branchVersionText =
|
|
19927
|
+
branchVersionText = readFileSync20(join34(input.worktree, settings.releaseVersionFile), "utf8");
|
|
19032
19928
|
} catch (error) {
|
|
19033
19929
|
return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
19034
19930
|
}
|
|
@@ -19057,7 +19953,7 @@ function writeConversion(input, plan2) {
|
|
|
19057
19953
|
const target = join34(input.worktree, path);
|
|
19058
19954
|
try {
|
|
19059
19955
|
mkdirSync8(dirname8(target), { recursive: true });
|
|
19060
|
-
|
|
19956
|
+
writeFileSync9(target, text);
|
|
19061
19957
|
} catch (error) {
|
|
19062
19958
|
return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
|
|
19063
19959
|
}
|
|
@@ -19154,9 +20050,9 @@ function ghPermissionsIn(root) {
|
|
|
19154
20050
|
return createGhPermissions({ gh: createGhRunner({ cwd: root }) });
|
|
19155
20051
|
}
|
|
19156
20052
|
function repoLabel(url, remote) {
|
|
19157
|
-
const
|
|
19158
|
-
if (
|
|
19159
|
-
return
|
|
20053
|
+
const named4 = PULL_URL.exec(url.trim())?.[1];
|
|
20054
|
+
if (named4 !== undefined)
|
|
20055
|
+
return named4;
|
|
19160
20056
|
const normalized = remote === null ? "" : normalizeRemote(remote.trim());
|
|
19161
20057
|
return normalized === "" ? UNNAMED_REPO : normalized;
|
|
19162
20058
|
}
|
|
@@ -19483,7 +20379,7 @@ async function runAttempts(run, detail, first) {
|
|
|
19483
20379
|
}
|
|
19484
20380
|
|
|
19485
20381
|
// src/commands/pr/triage.ts
|
|
19486
|
-
var
|
|
20382
|
+
var USAGE40 = PR_USAGE.triage;
|
|
19487
20383
|
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
19488
20384
|
var DETACHED_HEAD2 = "HEAD";
|
|
19489
20385
|
var RESOLVED = false;
|
|
@@ -19539,7 +20435,7 @@ async function selectTargets(pr, warn3, now, resolve13) {
|
|
|
19539
20435
|
async function detailOf2(pr, number) {
|
|
19540
20436
|
const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
|
|
19541
20437
|
if (detail === null) {
|
|
19542
|
-
throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`,
|
|
20438
|
+
throw lineRefusal5(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE40);
|
|
19543
20439
|
}
|
|
19544
20440
|
return detail;
|
|
19545
20441
|
}
|
|
@@ -19650,14 +20546,14 @@ async function assessOne(options) {
|
|
|
19650
20546
|
function refuseResolveWithoutComment(wantsResolve, wantsComment) {
|
|
19651
20547
|
if (!wantsResolve || wantsComment)
|
|
19652
20548
|
return;
|
|
19653
|
-
throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other",
|
|
20549
|
+
throw lineRefusal5("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE40);
|
|
19654
20550
|
}
|
|
19655
20551
|
async function runTriage(context, seams) {
|
|
19656
|
-
const wantsComment = readBooleanFlag(context.flags, "comment",
|
|
19657
|
-
const wantsResolve = readBooleanFlag(context.flags, "resolve",
|
|
19658
|
-
const maxAttempts = readMaxAttempts(context.flags,
|
|
20552
|
+
const wantsComment = readBooleanFlag(context.flags, "comment", USAGE40, true);
|
|
20553
|
+
const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE40, false);
|
|
20554
|
+
const maxAttempts = readMaxAttempts(context.flags, USAGE40);
|
|
19659
20555
|
refuseResolveWithoutComment(wantsResolve, wantsComment);
|
|
19660
|
-
const asked = readPullArgument(context.args,
|
|
20556
|
+
const asked = readPullArgument(context.args, USAGE40);
|
|
19661
20557
|
const pr = openPrContext(context, seams);
|
|
19662
20558
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
19663
20559
|
const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
|
|
@@ -19795,7 +20691,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
|
19795
20691
|
var triage_default = createPrTriageCommand();
|
|
19796
20692
|
|
|
19797
20693
|
// src/commands/pr/view.ts
|
|
19798
|
-
var
|
|
20694
|
+
var USAGE41 = PR_USAGE.view;
|
|
19799
20695
|
function trimmedOrNull2(value) {
|
|
19800
20696
|
const trimmed = value.trim();
|
|
19801
20697
|
return trimmed === "" ? null : trimmed;
|
|
@@ -19805,9 +20701,9 @@ function renderView(number, url) {
|
|
|
19805
20701
|
return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
|
|
19806
20702
|
}
|
|
19807
20703
|
async function runView2(context, seams) {
|
|
19808
|
-
const asked = readPullArgument(context.args,
|
|
20704
|
+
const asked = readPullArgument(context.args, USAGE41);
|
|
19809
20705
|
const pr = openPrContext(context, seams);
|
|
19810
|
-
const pick = await pickPullRequest(pr, asked,
|
|
20706
|
+
const pick = await pickPullRequest(pr, asked, USAGE41);
|
|
19811
20707
|
await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
|
|
19812
20708
|
const url = trimmedOrNull2(pick.summary?.url ?? "");
|
|
19813
20709
|
return {
|
|
@@ -19862,7 +20758,7 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
19862
20758
|
var view_default = createPrViewCommand();
|
|
19863
20759
|
|
|
19864
20760
|
// src/commands/pr/wait.ts
|
|
19865
|
-
var
|
|
20761
|
+
var USAGE42 = PR_USAGE.wait;
|
|
19866
20762
|
var TIMEOUT_FLAG2 = "timeout";
|
|
19867
20763
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
19868
20764
|
var MS_PER_MIN = 60000;
|
|
@@ -19928,10 +20824,10 @@ function renderWait(reading) {
|
|
|
19928
20824
|
`);
|
|
19929
20825
|
}
|
|
19930
20826
|
async function runWait(context, seams) {
|
|
19931
|
-
const minutes = readTimeoutMinutes(context.flags,
|
|
19932
|
-
const asked = readPullArgument(context.args,
|
|
20827
|
+
const minutes = readTimeoutMinutes(context.flags, USAGE42);
|
|
20828
|
+
const asked = readPullArgument(context.args, USAGE42);
|
|
19933
20829
|
const pr = openPrContext(context, seams);
|
|
19934
|
-
const pick = await pickPullRequest(pr, asked,
|
|
20830
|
+
const pick = await pickPullRequest(pr, asked, USAGE42);
|
|
19935
20831
|
const timeoutMs = minutes * MS_PER_MIN;
|
|
19936
20832
|
context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
|
|
19937
20833
|
const waited = await waitForChecks({
|
|
@@ -20037,11 +20933,11 @@ function releasePullBody(build, base) {
|
|
|
20037
20933
|
`);
|
|
20038
20934
|
}
|
|
20039
20935
|
function foreignSentence(pull2, base) {
|
|
20040
|
-
const
|
|
20936
|
+
const named4 = `pull request #${pull2.number} from ${RELEASE_PR_BRANCH}`;
|
|
20041
20937
|
if (pull2.isCrossRepository)
|
|
20042
|
-
return `${
|
|
20938
|
+
return `${named4} comes from a fork, so settle will not replace the branch under it; close it and settle again`;
|
|
20043
20939
|
if (pull2.baseRefName !== base) {
|
|
20044
|
-
return `${
|
|
20940
|
+
return `${named4} goes into ${pull2.baseRefName}, not ${base}, so settle will not replace the branch under it; close it, or settle with pr.base ${pull2.baseRefName}`;
|
|
20045
20941
|
}
|
|
20046
20942
|
return null;
|
|
20047
20943
|
}
|
|
@@ -20363,10 +21259,152 @@ async function withSettleWorktree(options, body) {
|
|
|
20363
21259
|
return { ok: true, value, ref: worktree.ref, commit: worktree.commit, leftover };
|
|
20364
21260
|
}
|
|
20365
21261
|
|
|
21262
|
+
// src/commands/release/settle-project.ts
|
|
21263
|
+
var COMMIT_BATCH = 20;
|
|
21264
|
+
var COMMIT_PULL_LIMIT = 10;
|
|
21265
|
+
var CLOSING_LIMIT = 100;
|
|
21266
|
+
var PREFIX5 = "release settle project";
|
|
21267
|
+
function named4(issues) {
|
|
21268
|
+
return issues.map((issue) => `#${String(issue)}`).join(", ");
|
|
21269
|
+
}
|
|
21270
|
+
function settleProblemLine(issues, problem2) {
|
|
21271
|
+
const which = issues.length === 0 ? "the issues this release closes" : named4(issues);
|
|
21272
|
+
return `The project was not updated for ${which}: ${problem2}. Run \`${BOARD_SYNC_FIX}\` to catch up.`;
|
|
21273
|
+
}
|
|
21274
|
+
function commitAlias(index) {
|
|
21275
|
+
return `c${String(index)}`;
|
|
21276
|
+
}
|
|
21277
|
+
function commitPullsArgs(commits) {
|
|
21278
|
+
const declared = commits.map((_, index) => `, $${commitAlias(index)}: GitObjectID!`).join("");
|
|
21279
|
+
const objects = commits.map((_, index) => ` ${commitAlias(index)}: object(oid: $${commitAlias(index)})` + ` { ... on Commit { associatedPullRequests(first: ${String(COMMIT_PULL_LIMIT)})` + " { pageInfo { hasNextPage } nodes { number state baseRefName body mergeCommit { oid }" + ` closingIssuesReferences(first: ${String(CLOSING_LIMIT)}) { pageInfo { hasNextPage }` + " nodes { number repository { nameWithOwner } } } } } } }").join("");
|
|
21280
|
+
const query = `query($owner: String!, $repo: String!${declared}) { repository(owner: $owner, name: $repo) { nameWithOwner${objects} } }`;
|
|
21281
|
+
return Object.freeze([
|
|
21282
|
+
"api",
|
|
21283
|
+
"graphql",
|
|
21284
|
+
"-F",
|
|
21285
|
+
"owner={owner}",
|
|
21286
|
+
"-F",
|
|
21287
|
+
"repo={repo}",
|
|
21288
|
+
...commits.flatMap((commit, index) => ["-f", `${commitAlias(index)}=${commit}`]),
|
|
21289
|
+
"-f",
|
|
21290
|
+
`query=${query}`
|
|
21291
|
+
]);
|
|
21292
|
+
}
|
|
21293
|
+
function refuse7(problem2) {
|
|
21294
|
+
throw new Error(`${PREFIX5}: ${problem2}`);
|
|
21295
|
+
}
|
|
21296
|
+
function readMapping2(value, where) {
|
|
21297
|
+
if (!isMapping(value))
|
|
21298
|
+
refuse7(`${where} is ${describeValue(value)}, expected a mapping`);
|
|
21299
|
+
return value;
|
|
21300
|
+
}
|
|
21301
|
+
function readList2(value, where) {
|
|
21302
|
+
if (!Array.isArray(value))
|
|
21303
|
+
refuse7(`${where} is ${describeValue(value)}, expected a list`);
|
|
21304
|
+
return value;
|
|
21305
|
+
}
|
|
21306
|
+
function readNumber(value, where) {
|
|
21307
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
|
|
21308
|
+
refuse7(`${where} is ${describeValue(value)}, expected a positive whole number`);
|
|
21309
|
+
}
|
|
21310
|
+
return value;
|
|
21311
|
+
}
|
|
21312
|
+
function readString(value, where) {
|
|
21313
|
+
if (typeof value !== "string")
|
|
21314
|
+
refuse7(`${where} is ${describeValue(value)}, expected a string`);
|
|
21315
|
+
return value;
|
|
21316
|
+
}
|
|
21317
|
+
function readOnePage(value, where) {
|
|
21318
|
+
const connection = readMapping2(value, where);
|
|
21319
|
+
if (readMapping2(connection["pageInfo"], `${where}.pageInfo`)["hasNextPage"] !== false) {
|
|
21320
|
+
refuse7(`${where} holds more entries than one page`);
|
|
21321
|
+
}
|
|
21322
|
+
return readList2(connection["nodes"], `${where}.nodes`);
|
|
21323
|
+
}
|
|
21324
|
+
function readPull(value, where, repository) {
|
|
21325
|
+
const pull2 = readMapping2(value, where);
|
|
21326
|
+
if (pull2["state"] !== "MERGED")
|
|
21327
|
+
return null;
|
|
21328
|
+
const merge = pull2["mergeCommit"];
|
|
21329
|
+
const linked = readOnePage(pull2["closingIssuesReferences"], `${where}.closingIssuesReferences`).flatMap((node, index) => {
|
|
21330
|
+
const at = `${where}.closingIssuesReferences[${String(index)}]`;
|
|
21331
|
+
const issue = readMapping2(node, at);
|
|
21332
|
+
const owner = readString(readMapping2(issue["repository"], `${at}.repository`)["nameWithOwner"], `${at}.repository.nameWithOwner`);
|
|
21333
|
+
return owner.toLowerCase() === repository.toLowerCase() ? [readNumber(issue["number"], `${at}.number`)] : [];
|
|
21334
|
+
});
|
|
21335
|
+
return {
|
|
21336
|
+
number: readNumber(pull2["number"], `${where}.number`),
|
|
21337
|
+
baseRefName: readString(pull2["baseRefName"], `${where}.baseRefName`),
|
|
21338
|
+
mergeCommit: isMapping(merge) ? readString(merge["oid"], `${where}.mergeCommit.oid`) : null,
|
|
21339
|
+
issues: [...linked, ...closedIssuesIn(readString(pull2["body"], `${where}.body`))]
|
|
21340
|
+
};
|
|
21341
|
+
}
|
|
21342
|
+
function broughtBy(pulls, commit, branch) {
|
|
21343
|
+
const merged2 = pulls.filter((pull2) => pull2.mergeCommit === commit);
|
|
21344
|
+
return merged2.length > 0 ? merged2 : pulls.filter((pull2) => pull2.baseRefName === branch);
|
|
21345
|
+
}
|
|
21346
|
+
async function readBatch(gh, commits, branch) {
|
|
21347
|
+
const result2 = await gh(commitPullsArgs(commits));
|
|
21348
|
+
if (!result2.ok)
|
|
21349
|
+
refuse7(`gh api graphql failed: ${result2.stderr.trim() || result2.stdout.trim()}`);
|
|
21350
|
+
let parsed;
|
|
21351
|
+
try {
|
|
21352
|
+
parsed = JSON.parse(result2.stdout);
|
|
21353
|
+
} catch {
|
|
21354
|
+
refuse7("gh api graphql answered text that is not JSON");
|
|
21355
|
+
}
|
|
21356
|
+
const data = readMapping2(readMapping2(parsed, "the answer")["data"], "data");
|
|
21357
|
+
const answer2 = readMapping2(data["repository"], "data.repository");
|
|
21358
|
+
const repository = readString(answer2["nameWithOwner"], "data.repository.nameWithOwner");
|
|
21359
|
+
return commits.flatMap((commit, index) => {
|
|
21360
|
+
const where = `commit ${commit}`;
|
|
21361
|
+
const object = answer2[commitAlias(index)];
|
|
21362
|
+
if (object === null || object === undefined)
|
|
21363
|
+
return [];
|
|
21364
|
+
const pulls = readOnePage(readMapping2(object, where)["associatedPullRequests"], `${where}.associatedPullRequests`).map((pull2, at) => readPull(pull2, `${where}.associatedPullRequests[${String(at)}]`, repository)).filter((pull2) => pull2 !== null);
|
|
21365
|
+
return broughtBy(pulls, commit, branch);
|
|
21366
|
+
});
|
|
21367
|
+
}
|
|
21368
|
+
async function readPulls(gh, commits, branch) {
|
|
21369
|
+
const pulls = [];
|
|
21370
|
+
for (let start2 = 0;start2 < commits.length; start2 += COMMIT_BATCH) {
|
|
21371
|
+
pulls.push(...await readBatch(gh, commits.slice(start2, start2 + COMMIT_BATCH), branch));
|
|
21372
|
+
}
|
|
21373
|
+
return pulls;
|
|
21374
|
+
}
|
|
21375
|
+
function releasedCommits(delivered) {
|
|
21376
|
+
const { outcome } = delivered;
|
|
21377
|
+
if (outcome.outcome !== "pushed" && outcome.outcome !== "superseded")
|
|
21378
|
+
return null;
|
|
21379
|
+
return [...new Set(outcome.build.fragments.map((fragment) => fragment.commit))];
|
|
21380
|
+
}
|
|
21381
|
+
async function refreshProjectAfterSettle(options) {
|
|
21382
|
+
const { config, branch, refresh = refreshIssueItems } = options;
|
|
21383
|
+
const commits = releasedCommits(options.delivered);
|
|
21384
|
+
if (config.boardProjectNumber === null || commits === null || commits.length === 0)
|
|
21385
|
+
return null;
|
|
21386
|
+
const gh = options.openGh();
|
|
21387
|
+
let pulls;
|
|
21388
|
+
try {
|
|
21389
|
+
pulls = await readPulls(gh, commits, branch);
|
|
21390
|
+
} catch (error) {
|
|
21391
|
+
return { commits, pullRequests: [], issues: [], warnings: [settleProblemLine([], messageOf(error))] };
|
|
21392
|
+
}
|
|
21393
|
+
const pullRequests = [...new Set(pulls.map((pull2) => pull2.number))];
|
|
21394
|
+
const issues = [...new Set(pulls.flatMap((pull2) => pull2.issues))];
|
|
21395
|
+
if (issues.length === 0)
|
|
21396
|
+
return { commits, pullRequests, issues, warnings: [] };
|
|
21397
|
+
try {
|
|
21398
|
+
return { commits, pullRequests, issues, warnings: (await refresh({ config, gh }, issues)).warnings };
|
|
21399
|
+
} catch (error) {
|
|
21400
|
+
return { commits, pullRequests, issues, warnings: [settleProblemLine(issues, messageOf(error))] };
|
|
21401
|
+
}
|
|
21402
|
+
}
|
|
21403
|
+
|
|
20366
21404
|
// src/commands/release/settle.ts
|
|
20367
21405
|
var RELEASE_SETTLE_USAGE = "rafa release settle [--dry-run]";
|
|
20368
21406
|
var COMMAND_NAME12 = "rafa release settle";
|
|
20369
|
-
var
|
|
21407
|
+
var DRY_RUN_FLAG7 = "dry-run";
|
|
20370
21408
|
var INDENT9 = " ";
|
|
20371
21409
|
var SHORT_HASH4 = 12;
|
|
20372
21410
|
var DEFAULT_RELEASE_SETTLE_SEAMS = Object.freeze({});
|
|
@@ -20423,7 +21461,7 @@ function unwrittenEnding(reading) {
|
|
|
20423
21461
|
}
|
|
20424
21462
|
function resultOf6(base, lines, problem2) {
|
|
20425
21463
|
const exitCode = problem2 === null ? 0 : 1;
|
|
20426
|
-
return { ...base, lines, problem: problem2, exitCode };
|
|
21464
|
+
return { ...base, lines, problem: problem2, exitCode, project: null };
|
|
20427
21465
|
}
|
|
20428
21466
|
function dryRunResult(reading, ref8, branch) {
|
|
20429
21467
|
const base = { dryRun: true, delivery: null, reading, delivered: null, tag: null };
|
|
@@ -20481,7 +21519,7 @@ function providerFor(project, config, seams) {
|
|
|
20481
21519
|
}
|
|
20482
21520
|
async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
20483
21521
|
expectNoArgument(context.args, RELEASE_SETTLE_USAGE);
|
|
20484
|
-
const dryRun = readSwitch(
|
|
21522
|
+
const dryRun = readSwitch(DRY_RUN_FLAG7, context.flags[DRY_RUN_FLAG7], `Usage: ${RELEASE_SETTLE_USAGE}`);
|
|
20485
21523
|
const project = projectOf9(context);
|
|
20486
21524
|
const config = resolveProjectConfig(project, COMMAND_NAME12, (message) => {
|
|
20487
21525
|
context.output.warn(message);
|
|
@@ -20505,7 +21543,16 @@ async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
20505
21543
|
context.output.warn(`The settle worktree was not fully removed: ${ran.leftover}`);
|
|
20506
21544
|
if (!ran.ok)
|
|
20507
21545
|
throw new CommandExit(1, `\u274C ${ran.problem}`);
|
|
20508
|
-
|
|
21546
|
+
if (ran.value.delivered === null)
|
|
21547
|
+
return ran.value;
|
|
21548
|
+
const refreshed = await refreshProjectAfterSettle({
|
|
21549
|
+
delivered: ran.value.delivered,
|
|
21550
|
+
branch,
|
|
21551
|
+
config,
|
|
21552
|
+
openGh: () => (seams.gh ?? ((cwd) => createGhRunner({ cwd })))(project.root),
|
|
21553
|
+
refresh: seams.refresh
|
|
21554
|
+
});
|
|
21555
|
+
return { ...ran.value, project: refreshed };
|
|
20509
21556
|
}
|
|
20510
21557
|
function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
20511
21558
|
const command = {
|
|
@@ -20517,7 +21564,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
20517
21564
|
args: [],
|
|
20518
21565
|
flags: [
|
|
20519
21566
|
{
|
|
20520
|
-
name:
|
|
21567
|
+
name: DRY_RUN_FLAG7,
|
|
20521
21568
|
description: "Print the fragments, their order, the strategy and the version, and write nothing:" + " no commit, no push, no tag.",
|
|
20522
21569
|
type: "boolean"
|
|
20523
21570
|
}
|
|
@@ -20539,7 +21586,12 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
20539
21586
|
outputs: ["text", "json"],
|
|
20540
21587
|
run: async (context) => {
|
|
20541
21588
|
const result2 = await runSettle(context, seams);
|
|
21589
|
+
const warnProject = () => {
|
|
21590
|
+
for (const line of result2.project?.warnings ?? [])
|
|
21591
|
+
context.output.warn(line);
|
|
21592
|
+
};
|
|
20542
21593
|
if (context.outputMode === "json") {
|
|
21594
|
+
warnProject();
|
|
20543
21595
|
if (result2.problem !== null)
|
|
20544
21596
|
throw new CommandExit(1, result2.problem);
|
|
20545
21597
|
context.output.result(result2);
|
|
@@ -20547,6 +21599,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
20547
21599
|
}
|
|
20548
21600
|
for (const line of result2.lines)
|
|
20549
21601
|
context.output.info(line);
|
|
21602
|
+
warnProject();
|
|
20550
21603
|
if (result2.problem !== null)
|
|
20551
21604
|
throw new CommandExit(1, result2.problem);
|
|
20552
21605
|
}
|
|
@@ -20556,7 +21609,7 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
20556
21609
|
var settle_default = createReleaseSettleCommand();
|
|
20557
21610
|
|
|
20558
21611
|
// src/commands/release/tag.ts
|
|
20559
|
-
import { readFileSync as
|
|
21612
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
20560
21613
|
import { join as join36 } from "path";
|
|
20561
21614
|
|
|
20562
21615
|
// src/commands/release/release-commit.ts
|
|
@@ -20615,7 +21668,7 @@ function readBranch2(git) {
|
|
|
20615
21668
|
}
|
|
20616
21669
|
function textOf(path) {
|
|
20617
21670
|
try {
|
|
20618
|
-
return
|
|
21671
|
+
return readFileSync21(path, "utf8");
|
|
20619
21672
|
} catch {
|
|
20620
21673
|
return null;
|
|
20621
21674
|
}
|
|
@@ -20694,24 +21747,24 @@ function followUpsFor(tag, version2, target, publish, place2 = ON_HEAD) {
|
|
|
20694
21747
|
}
|
|
20695
21748
|
];
|
|
20696
21749
|
}
|
|
20697
|
-
function
|
|
21750
|
+
function refuse8(reason, message) {
|
|
20698
21751
|
return { kind: "refused", reason, message };
|
|
20699
21752
|
}
|
|
20700
21753
|
function branchRefusal(inputs) {
|
|
20701
21754
|
const { branch, problem: problem2 } = inputs.branch;
|
|
20702
21755
|
if (branch === null) {
|
|
20703
|
-
return
|
|
21756
|
+
return refuse8("branch", `${problem2 ?? "no branch is checked out"}, and a release is tagged` + ` on ${inputs.releaseBranch}`);
|
|
20704
21757
|
}
|
|
20705
|
-
return branch === inputs.releaseBranch ? null :
|
|
21758
|
+
return branch === inputs.releaseBranch ? null : refuse8("branch", `a release is tagged on ${inputs.releaseBranch}, and ${branch} is checked out`);
|
|
20706
21759
|
}
|
|
20707
21760
|
function changelogRefusal(inputs, version2) {
|
|
20708
21761
|
const { path, version: newest, problem: problem2 } = inputs.changelog;
|
|
20709
21762
|
if (problem2 !== null)
|
|
20710
|
-
return
|
|
21763
|
+
return refuse8("changelog", problem2);
|
|
20711
21764
|
if (newest === null) {
|
|
20712
|
-
return
|
|
21765
|
+
return refuse8("changelog", `${path} names no release, so nothing says ${version2} is the version to tag`);
|
|
20713
21766
|
}
|
|
20714
|
-
return newest === version2 ? null :
|
|
21767
|
+
return newest === version2 ? null : refuse8("changelog", `${inputs.version.path} says ${version2} and the newest section of ${path}` + ` says ${newest}`);
|
|
20715
21768
|
}
|
|
20716
21769
|
function decideTag(inputs) {
|
|
20717
21770
|
const onBranch = branchRefusal(inputs);
|
|
@@ -20719,13 +21772,13 @@ function decideTag(inputs) {
|
|
|
20719
21772
|
return onBranch;
|
|
20720
21773
|
const version2 = inputs.version.version;
|
|
20721
21774
|
if (version2 === null) {
|
|
20722
|
-
return
|
|
21775
|
+
return refuse8("version", inputs.version.problem ?? `${inputs.version.path} declares no version`);
|
|
20723
21776
|
}
|
|
20724
21777
|
if (inputs.tags.problem !== null)
|
|
20725
|
-
return
|
|
21778
|
+
return refuse8("git", inputs.tags.problem);
|
|
20726
21779
|
const held = inputs.tags.tags.find((candidate) => candidate.version === version2);
|
|
20727
21780
|
if (held !== undefined) {
|
|
20728
|
-
return
|
|
21781
|
+
return refuse8("tagged", `${held.tag} already names ${version2}, so there is nothing to tag`);
|
|
20729
21782
|
}
|
|
20730
21783
|
const disagrees = changelogRefusal(inputs, version2);
|
|
20731
21784
|
if (disagrees !== null)
|
|
@@ -20733,14 +21786,14 @@ function decideTag(inputs) {
|
|
|
20733
21786
|
const path = inputs.changelog.path;
|
|
20734
21787
|
const unreceipted = inputs.receipt === null ? `${path} was not read for the receipt of ${version2}` : receiptProblem(inputs.receipt, version2, path);
|
|
20735
21788
|
if (unreceipted !== null)
|
|
20736
|
-
return
|
|
21789
|
+
return refuse8("receipt", unreceipted);
|
|
20737
21790
|
const release = inputs.release;
|
|
20738
21791
|
if (release === null)
|
|
20739
|
-
return
|
|
21792
|
+
return refuse8("version", `${inputs.version.path} was not looked up in the history`);
|
|
20740
21793
|
if (release.problem !== null)
|
|
20741
|
-
return
|
|
21794
|
+
return refuse8(release.problem.reason, release.problem.message);
|
|
20742
21795
|
if (release.commit === null)
|
|
20743
|
-
return
|
|
21796
|
+
return refuse8("git", `no commit could be named for ${version2}`);
|
|
20744
21797
|
return { kind: "ready", version: version2, tag: versionTag(version2), commit: release.commit, ahead: release.ahead };
|
|
20745
21798
|
}
|
|
20746
21799
|
function projectOf10(context) {
|
|
@@ -20913,7 +21966,7 @@ import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as
|
|
|
20913
21966
|
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join38, relative as relative9, resolve as resolve13, sep as sep5 } from "path";
|
|
20914
21967
|
|
|
20915
21968
|
// src/backfill/proposal-file.ts
|
|
20916
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
21969
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync22 } from "fs";
|
|
20917
21970
|
import { join as join37 } from "path";
|
|
20918
21971
|
var BACKFILL_PATH = join37(".rafa", "backfill");
|
|
20919
21972
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
@@ -21079,7 +22132,7 @@ function readProposalFiles(dir) {
|
|
|
21079
22132
|
return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
21080
22133
|
const path = join37(dir, name);
|
|
21081
22134
|
try {
|
|
21082
|
-
return { path, result: parseProposalFile(
|
|
22135
|
+
return { path, result: parseProposalFile(readFileSync22(path, "utf8")) };
|
|
21083
22136
|
} catch (error) {
|
|
21084
22137
|
return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
|
|
21085
22138
|
}
|
|
@@ -21135,7 +22188,7 @@ function countBackups(results) {
|
|
|
21135
22188
|
}
|
|
21136
22189
|
|
|
21137
22190
|
// src/backfill/derive.ts
|
|
21138
|
-
import { readFileSync as
|
|
22191
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
|
|
21139
22192
|
import { basename as basename6 } from "path";
|
|
21140
22193
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
21141
22194
|
function renderWhenToUse(trigger, prevents) {
|
|
@@ -21261,7 +22314,7 @@ function planFile(entry, options) {
|
|
|
21261
22314
|
}
|
|
21262
22315
|
let original;
|
|
21263
22316
|
try {
|
|
21264
|
-
original =
|
|
22317
|
+
original = readFileSync23(entry.path, "utf8");
|
|
21265
22318
|
} catch (error) {
|
|
21266
22319
|
return inert(entry, "skipped", `the file cannot be read (${error instanceof Error ? error.message : String(error)})`);
|
|
21267
22320
|
}
|
|
@@ -21305,7 +22358,7 @@ function planDerivation(root, options) {
|
|
|
21305
22358
|
function applyFile(action, options) {
|
|
21306
22359
|
if (action.text === null || action.original === null)
|
|
21307
22360
|
return action;
|
|
21308
|
-
const current =
|
|
22361
|
+
const current = readFileSync23(action.path, "utf8");
|
|
21309
22362
|
if (current !== action.original) {
|
|
21310
22363
|
return {
|
|
21311
22364
|
...action,
|
|
@@ -21314,7 +22367,7 @@ function applyFile(action, options) {
|
|
|
21314
22367
|
text: null
|
|
21315
22368
|
};
|
|
21316
22369
|
}
|
|
21317
|
-
|
|
22370
|
+
writeFileSync10(action.path, action.text, "utf8");
|
|
21318
22371
|
const after = failureLines(checkFile(action.path, "skill", {
|
|
21319
22372
|
projectRoot: options.projectRoot,
|
|
21320
22373
|
pathDirs: options.pathDirs
|
|
@@ -21322,7 +22375,7 @@ function applyFile(action, options) {
|
|
|
21322
22375
|
const added = addedFailures(action.before, after);
|
|
21323
22376
|
if (added.length === 0)
|
|
21324
22377
|
return action;
|
|
21325
|
-
|
|
22378
|
+
writeFileSync10(action.path, action.original, "utf8");
|
|
21326
22379
|
return {
|
|
21327
22380
|
...action,
|
|
21328
22381
|
kind: "refused",
|
|
@@ -21336,7 +22389,7 @@ function applyDerivation(plan2, options) {
|
|
|
21336
22389
|
}
|
|
21337
22390
|
|
|
21338
22391
|
// src/backfill/proposal-batch.ts
|
|
21339
|
-
import { readFileSync as
|
|
22392
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
21340
22393
|
import { relative as relative10 } from "path";
|
|
21341
22394
|
|
|
21342
22395
|
// src/demote/report.ts
|
|
@@ -21635,7 +22688,7 @@ function readCandidate(root, entry) {
|
|
|
21635
22688
|
return null;
|
|
21636
22689
|
let text;
|
|
21637
22690
|
try {
|
|
21638
|
-
text =
|
|
22691
|
+
text = readFileSync24(entry.path, "utf8");
|
|
21639
22692
|
} catch {
|
|
21640
22693
|
return null;
|
|
21641
22694
|
}
|
|
@@ -21741,7 +22794,7 @@ function parseSessionAnswer(stdout) {
|
|
|
21741
22794
|
}
|
|
21742
22795
|
|
|
21743
22796
|
// src/backfill/propose.ts
|
|
21744
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
22797
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync25, writeFileSync as writeFileSync11 } from "fs";
|
|
21745
22798
|
import { join as join39, resolve as resolve14 } from "path";
|
|
21746
22799
|
var PROPOSAL_ACTION_KINDS = [
|
|
21747
22800
|
"applied",
|
|
@@ -21826,7 +22879,7 @@ async function runProposalPass(options) {
|
|
|
21826
22879
|
const number = index + 1;
|
|
21827
22880
|
const file = await runProposalBatch(number, batch, options);
|
|
21828
22881
|
const path = proposalPath(options.backfillDir, number);
|
|
21829
|
-
|
|
22882
|
+
writeFileSync11(path, renderProposalFile(file), "utf8");
|
|
21830
22883
|
written.push({ path, file });
|
|
21831
22884
|
}
|
|
21832
22885
|
return { written, candidates: candidates.length };
|
|
@@ -21868,7 +22921,7 @@ function writeRow(path, row2, original, options) {
|
|
|
21868
22921
|
return inert2(path, row2, "unchanged", "every field the row proposes is already what the file carries");
|
|
21869
22922
|
}
|
|
21870
22923
|
const before = checkLines(path, options);
|
|
21871
|
-
|
|
22924
|
+
writeFileSync11(path, writeFrontmatter(document, mergeFrontmatter(document.data, changes)), "utf8");
|
|
21872
22925
|
const added = addedFailures(before, checkLines(path, options));
|
|
21873
22926
|
if (added.length === 0) {
|
|
21874
22927
|
return {
|
|
@@ -21880,7 +22933,7 @@ function writeRow(path, row2, original, options) {
|
|
|
21880
22933
|
added: []
|
|
21881
22934
|
};
|
|
21882
22935
|
}
|
|
21883
|
-
|
|
22936
|
+
writeFileSync11(path, original, "utf8");
|
|
21884
22937
|
return {
|
|
21885
22938
|
path,
|
|
21886
22939
|
relative: row2.path,
|
|
@@ -21900,7 +22953,7 @@ function applyRow(root, row2, options) {
|
|
|
21900
22953
|
}
|
|
21901
22954
|
let original;
|
|
21902
22955
|
try {
|
|
21903
|
-
original =
|
|
22956
|
+
original = readFileSync25(path, "utf8");
|
|
21904
22957
|
} catch {
|
|
21905
22958
|
return inert2(path, row2, "refused", "the file the row names is gone");
|
|
21906
22959
|
}
|
|
@@ -21939,7 +22992,7 @@ function applyProposals(files, root, options) {
|
|
|
21939
22992
|
}
|
|
21940
22993
|
|
|
21941
22994
|
// src/demote/select.ts
|
|
21942
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
22995
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync26, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
|
|
21943
22996
|
import { basename as basename7, dirname as dirname10, join as join40 } from "path";
|
|
21944
22997
|
var SKILLS_DIRECTORY = "skills";
|
|
21945
22998
|
var CLAUDE_DIRECTORY = ".claude";
|
|
@@ -22001,7 +23054,7 @@ function readSelected(absolute, path, entries) {
|
|
|
22001
23054
|
let text;
|
|
22002
23055
|
let mtime;
|
|
22003
23056
|
try {
|
|
22004
|
-
text =
|
|
23057
|
+
text = readFileSync26(absolute, "utf8");
|
|
22005
23058
|
mtime = statSync10(absolute).mtime.toISOString();
|
|
22006
23059
|
} catch {
|
|
22007
23060
|
return null;
|
|
@@ -22386,11 +23439,11 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
22386
23439
|
var check_default3 = createSkillCheckCommand();
|
|
22387
23440
|
|
|
22388
23441
|
// src/commands/skill/demote.ts
|
|
22389
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as
|
|
23442
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync28, statSync as statSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
22390
23443
|
import { dirname as dirname12, resolve as resolve16 } from "path";
|
|
22391
23444
|
|
|
22392
23445
|
// src/demote/apply.ts
|
|
22393
|
-
import { copyFileSync as copyFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as
|
|
23446
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync27, renameSync, rmSync as rmSync3, statSync as statSync12, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
|
|
22394
23447
|
import { tmpdir as tmpdir3 } from "os";
|
|
22395
23448
|
import { dirname as dirname11, join as join42 } from "path";
|
|
22396
23449
|
var DEMOTION_ACTION_KINDS = [
|
|
@@ -22432,7 +23485,7 @@ function takenInstinctIds(dir) {
|
|
|
22432
23485
|
}
|
|
22433
23486
|
function readText4(path) {
|
|
22434
23487
|
try {
|
|
22435
|
-
return statSync12(path).isFile() ?
|
|
23488
|
+
return statSync12(path).isFile() ? readFileSync27(path, "utf8") : null;
|
|
22436
23489
|
} catch {
|
|
22437
23490
|
return null;
|
|
22438
23491
|
}
|
|
@@ -22475,7 +23528,7 @@ function planKept(scope, row2, file) {
|
|
|
22475
23528
|
}
|
|
22476
23529
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
22477
23530
|
const path = join42(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
22478
|
-
|
|
23531
|
+
writeFileSync12(path, text, "utf8");
|
|
22479
23532
|
const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
22480
23533
|
return failureLines2(report5.issues);
|
|
22481
23534
|
}
|
|
@@ -22564,7 +23617,7 @@ function applyDemotion(plan2) {
|
|
|
22564
23617
|
for (const action of plan2.actions) {
|
|
22565
23618
|
if (action.write !== null) {
|
|
22566
23619
|
mkdirSync11(dirname11(action.write.path), { recursive: true });
|
|
22567
|
-
|
|
23620
|
+
writeFileSync12(action.write.path, action.write.text, "utf8");
|
|
22568
23621
|
}
|
|
22569
23622
|
if (action.move !== null)
|
|
22570
23623
|
moveFile(action.move);
|
|
@@ -22663,7 +23716,7 @@ function verdictLine2(report5) {
|
|
|
22663
23716
|
function previousReport(path, context) {
|
|
22664
23717
|
if (!existsSync14(path))
|
|
22665
23718
|
return null;
|
|
22666
|
-
const parsed = parseDemotionReport(
|
|
23719
|
+
const parsed = parseDemotionReport(readFileSync28(path, "utf8"));
|
|
22667
23720
|
if (parsed.report !== null)
|
|
22668
23721
|
return parsed.report;
|
|
22669
23722
|
context.output.warn(`the report already at ${path} does not parse, so no override in it is carried forward:` + ` ${parsed.issues.map((issue2) => `line ${String(issue2.line)} ${issue2.code}`).join(", ")}`);
|
|
@@ -22673,7 +23726,7 @@ function runWrite(context, scope) {
|
|
|
22673
23726
|
const files = selectSources(scope);
|
|
22674
23727
|
const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
22675
23728
|
mkdirSync12(dirname12(scope.reportPath), { recursive: true });
|
|
22676
|
-
|
|
23729
|
+
writeFileSync13(scope.reportPath, writeDemotionReport(report5), "utf8");
|
|
22677
23730
|
const result2 = {
|
|
22678
23731
|
applied: false,
|
|
22679
23732
|
scope: scope.scope,
|
|
@@ -22699,7 +23752,7 @@ function reviewedReport(scope) {
|
|
|
22699
23752
|
if (!existsSync14(scope.reportPath)) {
|
|
22700
23753
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
22701
23754
|
}
|
|
22702
|
-
const parsed = parseDemotionReport(
|
|
23755
|
+
const parsed = parseDemotionReport(readFileSync28(scope.reportPath, "utf8"));
|
|
22703
23756
|
if (parsed.report === null) {
|
|
22704
23757
|
const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
|
|
22705
23758
|
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
|
|
@@ -22806,7 +23859,7 @@ var demote_default = createSkillDemoteCommand();
|
|
|
22806
23859
|
// src/commands/skill/show.ts
|
|
22807
23860
|
var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
|
|
22808
23861
|
var COMMAND_NAME13 = "rafa skill show";
|
|
22809
|
-
var
|
|
23862
|
+
var USAGE43 = "rafa skill show <name> [--full]";
|
|
22810
23863
|
function unknownSkillMessage(name, inventory) {
|
|
22811
23864
|
const agent = findShown(inventory.records, "agent", name);
|
|
22812
23865
|
const hint = agent === null ? [] : [`An agent is named "${name}" (${agent.source}): rafa agent show ${name}`];
|
|
@@ -22814,7 +23867,7 @@ function unknownSkillMessage(name, inventory) {
|
|
|
22814
23867
|
`\u274C No skill is named "${name}".`,
|
|
22815
23868
|
...hint,
|
|
22816
23869
|
"Run `rafa skill list` to see every skill and where it comes from.",
|
|
22817
|
-
`Usage: ${
|
|
23870
|
+
`Usage: ${USAGE43}`
|
|
22818
23871
|
].join(`
|
|
22819
23872
|
`);
|
|
22820
23873
|
}
|
|
@@ -22830,8 +23883,8 @@ function projectOf13(context) {
|
|
|
22830
23883
|
return context.project;
|
|
22831
23884
|
}
|
|
22832
23885
|
async function runShow3(context, seams) {
|
|
22833
|
-
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${
|
|
22834
|
-
const name = expectOneArgument(context.args,
|
|
23886
|
+
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE43}`);
|
|
23887
|
+
const name = expectOneArgument(context.args, USAGE43);
|
|
22835
23888
|
const project = projectOf13(context);
|
|
22836
23889
|
const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME13);
|
|
22837
23890
|
const warnings = warningLines(inventory);
|
|
@@ -22943,7 +23996,7 @@ var VERDICT_WORDS = Object.freeze({
|
|
|
22943
23996
|
pending: "checks pending",
|
|
22944
23997
|
none: "no checks"
|
|
22945
23998
|
});
|
|
22946
|
-
function
|
|
23999
|
+
function counted4(count, noun, plural4 = `${noun}s`) {
|
|
22947
24000
|
return `${String(count)} ${count === 1 ? noun : plural4}`;
|
|
22948
24001
|
}
|
|
22949
24002
|
function info2(text) {
|
|
@@ -22958,7 +24011,7 @@ function blockedTaskCount(blocked) {
|
|
|
22958
24011
|
}
|
|
22959
24012
|
function loopsText(reading) {
|
|
22960
24013
|
const tasks = blockedTaskCount(reading.blocked);
|
|
22961
|
-
return `${String(reading.live.length)} running, ${
|
|
24014
|
+
return `${String(reading.live.length)} running, ${counted4(tasks, "task")} blocked`;
|
|
22962
24015
|
}
|
|
22963
24016
|
function loopsBody(reading) {
|
|
22964
24017
|
const running = reading.live.map((record) => info2(`${INDENT11}${sessionLine(record)}`));
|
|
@@ -22977,9 +24030,9 @@ function pullText(reading) {
|
|
|
22977
24030
|
function blockedText(reading) {
|
|
22978
24031
|
const { blockedIssues } = reading;
|
|
22979
24032
|
if (reading.mode === "native") {
|
|
22980
|
-
return blockedIssues === null ? "the issues with an open blocker were not read" : `${
|
|
24033
|
+
return blockedIssues === null ? "the issues with an open blocker were not read" : `${counted4(blockedIssues, "issue")} with an open blocker`;
|
|
22981
24034
|
}
|
|
22982
|
-
return blockedIssues === null ? `the ${SPEC_BLOCKED_LABEL} issues were not read` : `${
|
|
24035
|
+
return blockedIssues === null ? `the ${SPEC_BLOCKED_LABEL} issues were not read` : `${counted4(blockedIssues, "issue")} labelled ${SPEC_BLOCKED_LABEL}`;
|
|
22983
24036
|
}
|
|
22984
24037
|
function boardText(reading) {
|
|
22985
24038
|
const roadmap = `roadmap #${String(reading.roadmap)}`;
|
|
@@ -23008,7 +24061,7 @@ function claimsText(reading) {
|
|
|
23008
24061
|
if (claims.length === 0)
|
|
23009
24062
|
return "none on origin as last fetched";
|
|
23010
24063
|
const count = (states) => claims.filter((row2) => states.includes(row2.state)).length;
|
|
23011
|
-
return `${
|
|
24064
|
+
return `${counted4(claims.length, "claim branch", "claim branches")} on origin as last fetched:` + ` ${String(count(["held"]))} held, ${String(count(["stale-claimed", "stale-in-development"]))} stale,` + ` ${String(count(["released"]))} released`;
|
|
23012
24065
|
}
|
|
23013
24066
|
function claimHolder(row2) {
|
|
23014
24067
|
if (row2.owner === null)
|
|
@@ -23037,7 +24090,7 @@ function claimsBody(reading) {
|
|
|
23037
24090
|
}
|
|
23038
24091
|
function housekeepingText(reading) {
|
|
23039
24092
|
const { merged: merged2, stale, notPushed, worktrees } = reading.counts;
|
|
23040
|
-
return `${String(merged2)} merged, ${String(stale)} stale, ${String(notPushed)} not pushed,` + ` ${
|
|
24093
|
+
return `${String(merged2)} merged, ${String(stale)} stale, ${String(notPushed)} not pushed,` + ` ${counted4(worktrees, "worktree")} (${String(reading.idleWorktrees)} idle)`;
|
|
23041
24094
|
}
|
|
23042
24095
|
function sectionLines(key, section, text, body = () => []) {
|
|
23043
24096
|
const title = STATUS_SECTION_TITLES[key];
|
|
@@ -23115,7 +24168,7 @@ function readClaimBranches(git) {
|
|
|
23115
24168
|
function isClaim(reading) {
|
|
23116
24169
|
return reading.state === "found" && reading.ownership.state !== "none";
|
|
23117
24170
|
}
|
|
23118
|
-
function
|
|
24171
|
+
function stageOf2(labels) {
|
|
23119
24172
|
return STAGE_LABELS.filter((label) => labels.includes(label));
|
|
23120
24173
|
}
|
|
23121
24174
|
function labelsOf2(labels, issue2) {
|
|
@@ -23123,7 +24176,7 @@ function labelsOf2(labels, issue2) {
|
|
|
23123
24176
|
return null;
|
|
23124
24177
|
return labels.issues.find((one) => one.number === issue2)?.labels ?? null;
|
|
23125
24178
|
}
|
|
23126
|
-
function
|
|
24179
|
+
function rowOf5(reading, input) {
|
|
23127
24180
|
const { ownership, tipCommittedAt, branch } = reading;
|
|
23128
24181
|
const issue2 = claimBranchIssue(branch) ?? 0;
|
|
23129
24182
|
const labels = labelsOf2(input.labels, issue2);
|
|
@@ -23135,7 +24188,7 @@ function rowOf4(reading, input) {
|
|
|
23135
24188
|
owner: ownership.state === "held" ? ownership.owner : null,
|
|
23136
24189
|
handingTo: ownership.state === "held" ? ownership.pending?.to ?? null : null,
|
|
23137
24190
|
releasedBy: ownership.state === "released" ? ownership.releasedBy : null,
|
|
23138
|
-
stage: labels === null ? null :
|
|
24191
|
+
stage: labels === null ? null : stageOf2(labels),
|
|
23139
24192
|
state: state.state,
|
|
23140
24193
|
tipCommittedAt: tipCommittedAt.toISOString(),
|
|
23141
24194
|
idleMs: Number.isFinite(elapsed) ? Math.max(0, elapsed) : 0
|
|
@@ -23149,7 +24202,7 @@ function labelNotes(rows, labels) {
|
|
|
23149
24202
|
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`);
|
|
23150
24203
|
}
|
|
23151
24204
|
function readClaims2(input) {
|
|
23152
|
-
const claims = input.branches.found.map((reading) =>
|
|
24205
|
+
const claims = input.branches.found.map((reading) => rowOf5(reading, input));
|
|
23153
24206
|
return { claims, notes: [...input.branches.notes, ...labelNotes(claims, input.labels)] };
|
|
23154
24207
|
}
|
|
23155
24208
|
|
|
@@ -23295,9 +24348,9 @@ function pullSection(sources, branch, provider, deadline) {
|
|
|
23295
24348
|
if (!branch.read) {
|
|
23296
24349
|
return Promise.resolve(unread2(`no pull request was looked for, as the branch was not read: ${branch.problem}`));
|
|
23297
24350
|
}
|
|
23298
|
-
return networkSection("pull request", deadline, () =>
|
|
24351
|
+
return networkSection("pull request", deadline, () => readPull2(sources, branch.branch));
|
|
23299
24352
|
}
|
|
23300
|
-
async function
|
|
24353
|
+
async function readPull2(sources, branch) {
|
|
23301
24354
|
let notes = [];
|
|
23302
24355
|
const pull2 = await readOpenPull(sources, branch, (problem2) => {
|
|
23303
24356
|
notes = [...notes, problem2];
|
|
@@ -23361,7 +24414,7 @@ async function placeOrNote(sources, walk, listing2) {
|
|
|
23361
24414
|
}
|
|
23362
24415
|
}
|
|
23363
24416
|
async function readBoard2(sources, board, listing2, blocked, native) {
|
|
23364
|
-
const [walk,
|
|
24417
|
+
const [walk, counted5] = await Promise.all([board.next(), blocked]);
|
|
23365
24418
|
const { line } = walk;
|
|
23366
24419
|
const next = line === null ? null : { line, ready: await board.isReady(line.issue), blocked: await board.blocking(line.issue) };
|
|
23367
24420
|
const { place: place2, notes } = await placeOrNote(sources, walk, listing2);
|
|
@@ -23369,8 +24422,8 @@ async function readBoard2(sources, board, listing2, blocked, native) {
|
|
|
23369
24422
|
roadmap: walk.roadmap,
|
|
23370
24423
|
next,
|
|
23371
24424
|
passed: walk.passed,
|
|
23372
|
-
blockedIssues:
|
|
23373
|
-
notes: [...walk.problems, ...
|
|
24425
|
+
blockedIssues: counted5.count,
|
|
24426
|
+
notes: [...walk.problems, ...counted5.notes, ...notes]
|
|
23374
24427
|
};
|
|
23375
24428
|
const reading = native ? { ...read3, mode: "native" } : read3;
|
|
23376
24429
|
return place2 === null ? reading : { ...reading, place: place2 };
|
|
@@ -23533,7 +24586,7 @@ function createUpdateStub(spec) {
|
|
|
23533
24586
|
var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
|
|
23534
24587
|
|
|
23535
24588
|
// src/project/lock.ts
|
|
23536
|
-
import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as
|
|
24589
|
+
import { existsSync as existsSync16, lstatSync as lstatSync2, readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "fs";
|
|
23537
24590
|
import { join as join43 } from "path";
|
|
23538
24591
|
var LOCK_FILE = "rafa.lock";
|
|
23539
24592
|
var LOCKFILE_VERSION = 1;
|
|
@@ -23556,20 +24609,20 @@ function isLink(path) {
|
|
|
23556
24609
|
return false;
|
|
23557
24610
|
}
|
|
23558
24611
|
}
|
|
23559
|
-
function
|
|
24612
|
+
function refuse9(problem2) {
|
|
23560
24613
|
throw new ProjectLockError(`${LOCK_FILE} ${problem2}`);
|
|
23561
24614
|
}
|
|
23562
24615
|
function lockOf(value) {
|
|
23563
24616
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
23564
|
-
|
|
24617
|
+
refuse9("holds no JSON object");
|
|
23565
24618
|
const record = value;
|
|
23566
24619
|
const format = record["lockfileVersion"];
|
|
23567
24620
|
if (format !== LOCKFILE_VERSION) {
|
|
23568
|
-
|
|
24621
|
+
refuse9(`is in lockfileVersion ${String(format)}, and this rafa reads ${String(LOCKFILE_VERSION)}: a newer rafa wrote it`);
|
|
23569
24622
|
}
|
|
23570
24623
|
const rafa = record["rafa"];
|
|
23571
24624
|
if (typeof rafa !== "string" || parseSemanticVersion(rafa) === null) {
|
|
23572
|
-
|
|
24625
|
+
refuse9(`names rafa ${JSON.stringify(rafa)}, which is no version`);
|
|
23573
24626
|
}
|
|
23574
24627
|
return { lockfileVersion: LOCKFILE_VERSION, rafa };
|
|
23575
24628
|
}
|
|
@@ -23577,25 +24630,25 @@ function readProjectLock(root) {
|
|
|
23577
24630
|
const path = lockPath(root);
|
|
23578
24631
|
if (!existsSync16(path)) {
|
|
23579
24632
|
if (isLink(path))
|
|
23580
|
-
|
|
24633
|
+
refuse9("is a link to nothing");
|
|
23581
24634
|
return null;
|
|
23582
24635
|
}
|
|
23583
24636
|
let text;
|
|
23584
24637
|
try {
|
|
23585
|
-
text =
|
|
24638
|
+
text = readFileSync29(path, "utf8");
|
|
23586
24639
|
} catch (error) {
|
|
23587
|
-
|
|
24640
|
+
refuse9(`could not be read: ${messageOf(error)}`);
|
|
23588
24641
|
}
|
|
23589
24642
|
let value;
|
|
23590
24643
|
try {
|
|
23591
24644
|
value = JSON.parse(text);
|
|
23592
24645
|
} catch {
|
|
23593
|
-
|
|
24646
|
+
refuse9("is not JSON");
|
|
23594
24647
|
}
|
|
23595
24648
|
return lockOf(value);
|
|
23596
24649
|
}
|
|
23597
24650
|
function writeProjectLock(root, version2) {
|
|
23598
|
-
|
|
24651
|
+
writeFileSync14(lockPath(root), projectLockText(version2), "utf8");
|
|
23599
24652
|
}
|
|
23600
24653
|
|
|
23601
24654
|
// src/project/update-current.ts
|
|
@@ -23712,7 +24765,7 @@ function readCurrentRange(recorded, installed) {
|
|
|
23712
24765
|
}
|
|
23713
24766
|
|
|
23714
24767
|
// src/commands/update/current.ts
|
|
23715
|
-
var
|
|
24768
|
+
var USAGE44 = "rafa update current [--dry-run] [--yes]";
|
|
23716
24769
|
var APPLY_QUESTION = "Apply these changes? [y/N] ";
|
|
23717
24770
|
var YES_ANSWERS5 = ["y", "yes"];
|
|
23718
24771
|
function checkoutLockRoot(root, dir) {
|
|
@@ -23737,7 +24790,7 @@ function readSwitch5(context, name) {
|
|
|
23737
24790
|
if (value === true || value === "true")
|
|
23738
24791
|
return true;
|
|
23739
24792
|
throw new CommandExit(1, `\u274C --${name} takes no value, and read "${String(value)}" as one
|
|
23740
|
-
Usage: ${
|
|
24793
|
+
Usage: ${USAGE44}`);
|
|
23741
24794
|
}
|
|
23742
24795
|
function couldNotRun(problem2) {
|
|
23743
24796
|
return new CommandExit(2, `FAIL \u2014 ${problem2}
|
|
@@ -23873,7 +24926,7 @@ function stopLine2(plan2, dryRun) {
|
|
|
23873
24926
|
return null;
|
|
23874
24927
|
}
|
|
23875
24928
|
async function runUpdateCurrent(context, seams) {
|
|
23876
|
-
expectNoArgument(context.args,
|
|
24929
|
+
expectNoArgument(context.args, USAGE44);
|
|
23877
24930
|
const dryRun = readSwitch5(context, "dry-run");
|
|
23878
24931
|
const yes = readSwitch5(context, "yes");
|
|
23879
24932
|
const project = projectOf15(context);
|
|
@@ -23959,7 +25012,7 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
23959
25012
|
{ name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
|
|
23960
25013
|
{ name: "instinct", summary: "check an instincts directory; list, show, flag and promote its records" },
|
|
23961
25014
|
{ name: "release", summary: "read the release state of the project; settle the waiting fragments into a version; tag the commit that set it" },
|
|
23962
|
-
{ name: "board", summary: "list the boards with their owner, epic count, and which is current and home" },
|
|
25015
|
+
{ name: "board", summary: "list the boards with their owner, epic count, and which is current and home; sync the GitHub project" },
|
|
23963
25016
|
{ name: "epic", summary: "show one epic's issues as the Roadmap table; create an epic; defer or promote it; move an issue to it; close it through the gate or cancel it" },
|
|
23964
25017
|
{ name: "claim", summary: "give up this device's claim on an issue; hand it to another store or withdraw the offer; accept a handover; take over a stale claim" },
|
|
23965
25018
|
{ name: "update", summary: "bring this project to the installed rafa; the other updates are in development" }
|
|
@@ -24025,6 +25078,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
24025
25078
|
settle_default,
|
|
24026
25079
|
tag_default,
|
|
24027
25080
|
list_default5,
|
|
25081
|
+
sync_default,
|
|
24028
25082
|
show_default,
|
|
24029
25083
|
new_default,
|
|
24030
25084
|
defer_default,
|