@open-tomato/rafa 0.39.0 → 0.40.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/dist/cli.js +781 -164
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-m0wt4nr1.js → index-334y39tn.js} +66 -0
- package/dist/{index-1r2b1052.js → index-8veaj0t9.js} +2 -2
- package/dist/{index-psv1sxkv.js → index-j7kjj0dd.js} +138 -51
- package/dist/{index-g4116t9m.js → index-krpmxpkh.js} +1 -1
- package/dist/{index-nwgwxfkt.js → index-zcbj07aq.js} +553 -100
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/package.json +1 -1
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
openNdjsonStore,
|
|
15
15
|
openSqliteStore,
|
|
16
16
|
selectEffortStore
|
|
17
|
-
} from "../../index-
|
|
18
|
-
import"../../index-
|
|
17
|
+
} from "../../index-8veaj0t9.js";
|
|
18
|
+
import"../../index-334y39tn.js";
|
|
19
19
|
import"../../index-d6d0942q.js";
|
|
20
20
|
import"../../index-sr3kc4mf.js";
|
|
21
21
|
import {
|
|
@@ -175,6 +175,65 @@ var fragmentsDirectory = (raw, at) => {
|
|
|
175
175
|
return { value: undefined, problems: [problem], extras: [] };
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
+
// src/config-schema-board-project.ts
|
|
179
|
+
var BOARD_PROJECT_RETRIES_MIN = 1;
|
|
180
|
+
var BOARD_PROJECT_RETRIES_MAX = 10;
|
|
181
|
+
var BOARD_PROJECT_RETRY_WAIT_MIN_SECONDS = 1;
|
|
182
|
+
var BOARD_PROJECT_RETRY_WAIT_MAX_SECONDS = 60;
|
|
183
|
+
var BOARD_PROJECT_PROGRESS_MIN_SECONDS = 1;
|
|
184
|
+
var BOARD_PROJECT_PROGRESS_MAX_SECONDS = 300;
|
|
185
|
+
var BOARD_PROJECT_WRITE_BATCH_MIN = 1;
|
|
186
|
+
var BOARD_PROJECT_WRITE_BATCH_MAX = 100;
|
|
187
|
+
var BOARD_PROJECT_WRITE_PAUSE_MIN_MS = 0;
|
|
188
|
+
var BOARD_PROJECT_WRITE_PAUSE_MAX_MS = 60000;
|
|
189
|
+
function isWholeBetween(raw, min, max) {
|
|
190
|
+
return typeof raw === "number" && Number.isSafeInteger(raw) && raw >= min && raw <= max;
|
|
191
|
+
}
|
|
192
|
+
function rangeWords(min, max) {
|
|
193
|
+
return `a whole number from ${String(min)} to ${String(max)}`;
|
|
194
|
+
}
|
|
195
|
+
function wholeBetween(min, max) {
|
|
196
|
+
return (raw, at) => isWholeBetween(raw, min, max) ? { value: raw, problems: [], extras: [] } : refused(at, raw, rangeWords(min, max));
|
|
197
|
+
}
|
|
198
|
+
function offOrWholeBetween(min, max) {
|
|
199
|
+
return (raw, at) => raw === false || isWholeBetween(raw, min, max) ? { value: raw, problems: [], extras: [] } : refused(at, raw, `false or ${rangeWords(min, max)}`);
|
|
200
|
+
}
|
|
201
|
+
var boardProjectRetries = offOrWholeBetween(BOARD_PROJECT_RETRIES_MIN, BOARD_PROJECT_RETRIES_MAX);
|
|
202
|
+
var boardProjectRetryWaitSeconds = wholeBetween(BOARD_PROJECT_RETRY_WAIT_MIN_SECONDS, BOARD_PROJECT_RETRY_WAIT_MAX_SECONDS);
|
|
203
|
+
var boardProjectProgressSeconds = offOrWholeBetween(BOARD_PROJECT_PROGRESS_MIN_SECONDS, BOARD_PROJECT_PROGRESS_MAX_SECONDS);
|
|
204
|
+
var boardProjectWriteBatchSize = wholeBetween(BOARD_PROJECT_WRITE_BATCH_MIN, BOARD_PROJECT_WRITE_BATCH_MAX);
|
|
205
|
+
var boardProjectWritePauseMs = wholeBetween(BOARD_PROJECT_WRITE_PAUSE_MIN_MS, BOARD_PROJECT_WRITE_PAUSE_MAX_MS);
|
|
206
|
+
var BOARD_PROJECT_LIMIT_DEFAULTS = Object.freeze({
|
|
207
|
+
boardProjectRetries: 3,
|
|
208
|
+
boardProjectRetryWaitSeconds: 2,
|
|
209
|
+
boardProjectProgressSeconds: 10,
|
|
210
|
+
boardProjectWriteBatchSize: 20,
|
|
211
|
+
boardProjectWritePauseMs: 1000
|
|
212
|
+
});
|
|
213
|
+
var BOARD_PROJECT_LIMIT_SETTINGS = {
|
|
214
|
+
boardProjectRetries: { key: "board.project.retries", read: boardProjectRetries, cli: false },
|
|
215
|
+
boardProjectRetryWaitSeconds: {
|
|
216
|
+
key: "board.project.retryWaitSeconds",
|
|
217
|
+
read: boardProjectRetryWaitSeconds,
|
|
218
|
+
cli: false
|
|
219
|
+
},
|
|
220
|
+
boardProjectProgressSeconds: {
|
|
221
|
+
key: "board.project.progressSeconds",
|
|
222
|
+
read: boardProjectProgressSeconds,
|
|
223
|
+
cli: false
|
|
224
|
+
},
|
|
225
|
+
boardProjectWriteBatchSize: {
|
|
226
|
+
key: "board.project.writeBatchSize",
|
|
227
|
+
read: boardProjectWriteBatchSize,
|
|
228
|
+
cli: false
|
|
229
|
+
},
|
|
230
|
+
boardProjectWritePauseMs: {
|
|
231
|
+
key: "board.project.writePauseMs",
|
|
232
|
+
read: boardProjectWritePauseMs,
|
|
233
|
+
cli: false
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
178
237
|
// src/config-schema-hub.ts
|
|
179
238
|
var HUB_TIMEOUT_MIN_SECONDS = 1;
|
|
180
239
|
var HUB_TIMEOUT_MAX_SECONDS = 30;
|
|
@@ -569,6 +628,7 @@ var CONFIG_DEFAULTS = Object.freeze({
|
|
|
569
628
|
boardRelationships: "labels",
|
|
570
629
|
boardProjectTemplate: BOARD_PROJECT_TEMPLATE_DEFAULT,
|
|
571
630
|
boardProjectNumber: null,
|
|
631
|
+
...BOARD_PROJECT_LIMIT_DEFAULTS,
|
|
572
632
|
roadmapIssue: null,
|
|
573
633
|
claimsStaleAfter: "3d",
|
|
574
634
|
claimsAhead: "off",
|
|
@@ -659,6 +719,7 @@ var SETTINGS = {
|
|
|
659
719
|
},
|
|
660
720
|
boardProjectTemplate: { key: "board.project.template", read: projectUrl, cli: false },
|
|
661
721
|
boardProjectNumber: { key: "board.project.number", read: projectNumber, cli: false },
|
|
722
|
+
...BOARD_PROJECT_LIMIT_SETTINGS,
|
|
662
723
|
roadmapIssue: { key: "roadmap.issue", read: issueNumber, cli: false },
|
|
663
724
|
claimsStaleAfter: { key: "claims.staleAfter", read: claimsStaleAfter, cli: false },
|
|
664
725
|
claimsAhead: { key: "claims.ahead", read: claimsAhead, cli: false },
|
|
@@ -797,6 +858,11 @@ function readLayer(rawOf, atOf) {
|
|
|
797
858
|
boardRelationships: read("boardRelationships"),
|
|
798
859
|
boardProjectTemplate: read("boardProjectTemplate"),
|
|
799
860
|
boardProjectNumber: read("boardProjectNumber"),
|
|
861
|
+
boardProjectRetries: read("boardProjectRetries"),
|
|
862
|
+
boardProjectRetryWaitSeconds: read("boardProjectRetryWaitSeconds"),
|
|
863
|
+
boardProjectProgressSeconds: read("boardProjectProgressSeconds"),
|
|
864
|
+
boardProjectWriteBatchSize: read("boardProjectWriteBatchSize"),
|
|
865
|
+
boardProjectWritePauseMs: read("boardProjectWritePauseMs"),
|
|
800
866
|
roadmapIssue: read("roadmapIssue"),
|
|
801
867
|
claimsStaleAfter: read("claimsStaleAfter"),
|
|
802
868
|
claimsAhead: read("claimsAhead"),
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
parseConfigText,
|
|
16
16
|
readRafaBlocks,
|
|
17
17
|
resolveConfig
|
|
18
|
-
} from "./index-
|
|
18
|
+
} from "./index-334y39tn.js";
|
|
19
19
|
import {
|
|
20
20
|
bless,
|
|
21
21
|
merge
|
|
@@ -259,7 +259,7 @@ import { Database as Database2 } from "bun:sqlite";
|
|
|
259
259
|
|
|
260
260
|
// package.json
|
|
261
261
|
var name = "@open-tomato/rafa";
|
|
262
|
-
var version = "0.
|
|
262
|
+
var version = "0.40.0";
|
|
263
263
|
|
|
264
264
|
// src/cli/version.ts
|
|
265
265
|
var RAFA_VERSION = version;
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
ORIGIN_FIELD,
|
|
42
42
|
PREVIOUS_COPY_WARN_ABOVE,
|
|
43
43
|
PROJECT_SCOPE_FIX,
|
|
44
|
-
PROJECT_WRITE_PAUSE_MS,
|
|
45
44
|
PROVENANCE_FIELD,
|
|
46
45
|
REASON_FLAG,
|
|
47
46
|
RELEASE_BASE_BRANCH,
|
|
@@ -111,6 +110,8 @@ import {
|
|
|
111
110
|
closedIssuesIn,
|
|
112
111
|
collect,
|
|
113
112
|
collectSessionRow,
|
|
113
|
+
commandProgressFeed,
|
|
114
|
+
commandRetrySeams,
|
|
114
115
|
commandWords,
|
|
115
116
|
compareVersions,
|
|
116
117
|
configuredMoveRelations,
|
|
@@ -220,11 +221,14 @@ import {
|
|
|
220
221
|
noBoardListsLine,
|
|
221
222
|
normaliseReason,
|
|
222
223
|
notFoundWarning,
|
|
224
|
+
notRefreshedWarning,
|
|
223
225
|
notesFileName,
|
|
224
226
|
onTracker,
|
|
225
227
|
openBoards,
|
|
226
228
|
openNextRelations,
|
|
227
229
|
openNextSources,
|
|
230
|
+
openPhase,
|
|
231
|
+
openProjectRunner,
|
|
228
232
|
parseBaseStanding,
|
|
229
233
|
parseClaimMessage,
|
|
230
234
|
parseReportArgs,
|
|
@@ -361,6 +365,7 @@ import {
|
|
|
361
365
|
shellQuote,
|
|
362
366
|
show_default,
|
|
363
367
|
skillItem,
|
|
368
|
+
skippedFieldWarning,
|
|
364
369
|
sourceItem,
|
|
365
370
|
stackPaths,
|
|
366
371
|
stacksFromEccSkillName,
|
|
@@ -395,7 +400,7 @@ import {
|
|
|
395
400
|
worktreesHolding,
|
|
396
401
|
writeSession,
|
|
397
402
|
writeShippedFile
|
|
398
|
-
} from "./index-
|
|
403
|
+
} from "./index-zcbj07aq.js";
|
|
399
404
|
import {
|
|
400
405
|
PROBE_TIMEOUT_MS,
|
|
401
406
|
loadPlanPrerequisites,
|
|
@@ -407,7 +412,7 @@ import {
|
|
|
407
412
|
runPreflight,
|
|
408
413
|
splitBlockerComment,
|
|
409
414
|
trackerPathFor
|
|
410
|
-
} from "./index-
|
|
415
|
+
} from "./index-krpmxpkh.js";
|
|
411
416
|
import {
|
|
412
417
|
ACTION_HEADING,
|
|
413
418
|
CAUSE_HEADING,
|
|
@@ -518,7 +523,7 @@ import {
|
|
|
518
523
|
versionLine,
|
|
519
524
|
writeFrontmatter,
|
|
520
525
|
writePositionFile
|
|
521
|
-
} from "./index-
|
|
526
|
+
} from "./index-8veaj0t9.js";
|
|
522
527
|
import {
|
|
523
528
|
CONFIG_DEFAULTS,
|
|
524
529
|
CONFIG_FILE,
|
|
@@ -537,7 +542,7 @@ import {
|
|
|
537
542
|
readItemBytes,
|
|
538
543
|
readRafaBlocks,
|
|
539
544
|
resolveTiers
|
|
540
|
-
} from "./index-
|
|
545
|
+
} from "./index-334y39tn.js";
|
|
541
546
|
import {
|
|
542
547
|
promotable
|
|
543
548
|
} from "./index-d6d0942q.js";
|
|
@@ -3309,8 +3314,17 @@ async function addIssues(options, ref, issues) {
|
|
|
3309
3314
|
const project = await port.find(ref);
|
|
3310
3315
|
if (project === null)
|
|
3311
3316
|
return null;
|
|
3312
|
-
|
|
3313
|
-
|
|
3317
|
+
const phase = openPhase(options.progress, "adds", issues.length);
|
|
3318
|
+
let added = 0;
|
|
3319
|
+
try {
|
|
3320
|
+
for (const number of issues) {
|
|
3321
|
+
await port.addItem(project.id, { repository, number });
|
|
3322
|
+
added += 1;
|
|
3323
|
+
phase.advance(added);
|
|
3324
|
+
}
|
|
3325
|
+
} finally {
|
|
3326
|
+
phase.end({ done: added, refused: issues.length - added });
|
|
3327
|
+
}
|
|
3314
3328
|
return issues;
|
|
3315
3329
|
}
|
|
3316
3330
|
async function addMissing(options, first, synced) {
|
|
@@ -3327,6 +3341,7 @@ async function addMissing(options, first, synced) {
|
|
|
3327
3341
|
...synced,
|
|
3328
3342
|
changes: [...first.changes, ...second.changes],
|
|
3329
3343
|
added,
|
|
3344
|
+
refused: [...first.refused, ...second.refused],
|
|
3330
3345
|
writes: addedWrites(first.writes, second.writes),
|
|
3331
3346
|
warnings: [...first.warnings, ...second.warnings.filter((line) => !first.warnings.includes(line))]
|
|
3332
3347
|
};
|
|
@@ -3347,6 +3362,7 @@ async function syncProject(options) {
|
|
|
3347
3362
|
changes: first.changes,
|
|
3348
3363
|
missing: first.missing,
|
|
3349
3364
|
added: [],
|
|
3365
|
+
refused: first.refused,
|
|
3350
3366
|
writes: first.writes,
|
|
3351
3367
|
warnings: first.warnings
|
|
3352
3368
|
});
|
|
@@ -3364,17 +3380,21 @@ function counted(count, noun) {
|
|
|
3364
3380
|
function changeLine(change) {
|
|
3365
3381
|
return `#${String(change.issue)} ${change.name}: ${change.from ?? EMPTY_VALUE} \u2192 ${change.to ?? EMPTY_VALUE}`;
|
|
3366
3382
|
}
|
|
3383
|
+
function refusedPart(result) {
|
|
3384
|
+
return result.refused.length === 0 ? "" : `; ${counted(result.refused.length, "issue")} not refreshed`;
|
|
3385
|
+
}
|
|
3367
3386
|
function closingLine(result) {
|
|
3368
3387
|
const project = `project #${String(result.project.number)}`;
|
|
3369
3388
|
const adds = result.dryRun ? result.missing : result.added;
|
|
3370
3389
|
const changes = counted(result.changes.length, "change");
|
|
3371
3390
|
const issues = counted(adds.length, "issue");
|
|
3391
|
+
const inStep = result.changes.length === 0 && adds.length === 0 && result.refused.length === 0;
|
|
3372
3392
|
if (result.dryRun) {
|
|
3373
|
-
return
|
|
3393
|
+
return inStep ? `Dry run on ${project}: in step, nothing to change.` : `Dry run on ${project}: ${changes} and ${issues} to add${refusedPart(result)}; nothing written.`;
|
|
3374
3394
|
}
|
|
3375
3395
|
if (result.rateLimited)
|
|
3376
3396
|
return `Stopped on ${project}: ${String(result.written)} of ${changes} written; the rate limit refused the rest.`;
|
|
3377
|
-
return
|
|
3397
|
+
return inStep ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added${refusedPart(result)}.`;
|
|
3378
3398
|
}
|
|
3379
3399
|
function syncLines(result) {
|
|
3380
3400
|
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`);
|
|
@@ -3390,6 +3410,7 @@ function syncResultOf(synced) {
|
|
|
3390
3410
|
changes: synced.changes.map(plainChange),
|
|
3391
3411
|
missing: synced.missing,
|
|
3392
3412
|
added: synced.added,
|
|
3413
|
+
refused: synced.refused.map(({ number, reason }) => ({ issue: number, reason })),
|
|
3393
3414
|
written: synced.writes.written,
|
|
3394
3415
|
rateLimited: synced.writes.rateLimited,
|
|
3395
3416
|
notUpdated: synced.writes.notUpdated,
|
|
@@ -3407,9 +3428,10 @@ async function runBoardSync(context, seams) {
|
|
|
3407
3428
|
if (config.boardProjectNumber === null) {
|
|
3408
3429
|
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.`);
|
|
3409
3430
|
}
|
|
3410
|
-
const gh = seams.gh ?? createGhRunner({ cwd: project.root });
|
|
3431
|
+
const gh = openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep));
|
|
3411
3432
|
const dryRun = context.flags[DRY_RUN_FLAG] === true;
|
|
3412
|
-
const
|
|
3433
|
+
const progress = commandProgressFeed(context.output, context.outputMode, config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now });
|
|
3434
|
+
const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun, progress } : { config, gh, dryRun, progress, sleep: seams.sleep }).catch((error) => {
|
|
3413
3435
|
throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C Could not sync the project: ${messageOf(error)}`);
|
|
3414
3436
|
});
|
|
3415
3437
|
if (synced.kind !== "synced") {
|
|
@@ -3439,7 +3461,7 @@ function createBoardSyncCommand(seams = {}) {
|
|
|
3439
3461
|
subject: "board",
|
|
3440
3462
|
action: "sync",
|
|
3441
3463
|
summary: "bring the repository's GitHub project in step with its issues",
|
|
3442
|
-
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
|
|
3464
|
+
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. An issue whose facts could not be read is named in a warning, `#<n> not refreshed:" + " <reason>`, while the others are synced; the exit code stays 0 and the next run reads it again. Adding" + " the issues, reading their facts and writing their fields each print a start line with the total, a" + " progress line at most every board.project.progressSeconds, as `reading facts: 146/283, 6m 40s`, and an" + " end line; with `--output=json` each is a `progress` 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.",
|
|
3443
3465
|
args: [],
|
|
3444
3466
|
flags: [
|
|
3445
3467
|
{
|
|
@@ -8274,41 +8296,76 @@ async function missingIssues(options, repository, project) {
|
|
|
8274
8296
|
const held = heldIssues(await createGhProjectPort(gh).items(project.id), repository);
|
|
8275
8297
|
const ranked = board.closed.filter((issue) => rankOf(board.ranks, issue) !== null);
|
|
8276
8298
|
const unread = board.closed.filter((issue) => rankOf(board.ranks, issue) === null && !held.has(issue));
|
|
8277
|
-
const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
|
|
8299
|
+
const { facts } = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
|
|
8278
8300
|
const inReview = unread.filter((issue) => {
|
|
8279
8301
|
const read = facts.get(issue);
|
|
8280
8302
|
return read !== undefined && stageOf(read) === "In review";
|
|
8281
8303
|
});
|
|
8282
|
-
|
|
8304
|
+
const missing = [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
|
|
8305
|
+
return { held, missing };
|
|
8283
8306
|
}
|
|
8284
8307
|
async function itemsStep(options, repository, project) {
|
|
8285
8308
|
const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
|
|
8286
|
-
let
|
|
8309
|
+
let wanted;
|
|
8287
8310
|
try {
|
|
8288
|
-
|
|
8311
|
+
wanted = await missingIssues(options, repository, project);
|
|
8289
8312
|
} catch (error) {
|
|
8290
|
-
return partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`);
|
|
8313
|
+
return { part: partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`), holds: null };
|
|
8291
8314
|
}
|
|
8315
|
+
const { held, missing } = wanted;
|
|
8292
8316
|
if (missing.length === 0)
|
|
8293
|
-
return partOf("items", ITEMS_PART, "present", "the project holds every issue it shows");
|
|
8317
|
+
return { part: partOf("items", ITEMS_PART, "present", "the project holds every issue it shows"), holds: held.size };
|
|
8294
8318
|
const port = createGhProjectPort(options.gh);
|
|
8319
|
+
const phase = openPhase(options.progress, "adds", missing.length);
|
|
8295
8320
|
let added = 0;
|
|
8296
8321
|
try {
|
|
8297
8322
|
for (const number of missing) {
|
|
8298
8323
|
if (added > 0)
|
|
8299
|
-
await sleep(
|
|
8324
|
+
await sleep(options.config.boardProjectWritePauseMs);
|
|
8300
8325
|
await port.addItem(project.id, { repository, number });
|
|
8301
8326
|
added += 1;
|
|
8327
|
+
phase.advance(added);
|
|
8302
8328
|
}
|
|
8303
8329
|
} catch (error) {
|
|
8330
|
+
phase.end({ done: added, refused: missing.length - added });
|
|
8304
8331
|
const went = `${issueCount(added)} of ${String(missing.length)} added`;
|
|
8305
|
-
|
|
8332
|
+
const why = `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`;
|
|
8333
|
+
return { part: partOf("items", ITEMS_PART, "refused", why), holds: held.size + added };
|
|
8306
8334
|
}
|
|
8307
|
-
|
|
8335
|
+
phase.end({ done: added, refused: 0 });
|
|
8336
|
+
return { part: partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`), holds: held.size + added };
|
|
8337
|
+
}
|
|
8338
|
+
function withListed(part, listed2) {
|
|
8339
|
+
return listed2.length === 0 ? part : { ...part, listed: listed2 };
|
|
8308
8340
|
}
|
|
8309
|
-
|
|
8341
|
+
function refusedClause(refused) {
|
|
8342
|
+
return refused === 0 ? "" : `; ${issueCount(refused)} not refreshed`;
|
|
8343
|
+
}
|
|
8344
|
+
function refreshedPart(refreshed, holds) {
|
|
8345
|
+
const { writes, changes, skipped, refused } = refreshed;
|
|
8346
|
+
const listed2 = refused.map(notRefreshedWarning);
|
|
8347
|
+
if (writes.rateLimited)
|
|
8348
|
+
return withListed(partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), listed2);
|
|
8349
|
+
if (changes.length === 0 && refused.length > 0 && (holds === null || holds <= refused.length)) {
|
|
8350
|
+
return withListed(partOf("fields", FIELDS_PART, "refused", `no item was filled: ${issueCount(refused.length)} not refreshed`), listed2);
|
|
8351
|
+
}
|
|
8352
|
+
if (changes.length === 0) {
|
|
8353
|
+
const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
|
|
8354
|
+
return withListed(partOf("fields", FIELDS_PART, "present", `${held}${refusedClause(refused.length)}`), listed2);
|
|
8355
|
+
}
|
|
8356
|
+
const issues = new Set(changes.map(({ issue }) => issue)).size;
|
|
8357
|
+
const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
|
|
8358
|
+
return withListed(partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}${refusedClause(refused.length)}`), listed2);
|
|
8359
|
+
}
|
|
8360
|
+
async function fieldsStep(options, project, holds) {
|
|
8310
8361
|
const config = { ...options.config, boardProjectNumber: project.number };
|
|
8311
|
-
const
|
|
8362
|
+
const { sleep, progress } = options;
|
|
8363
|
+
const refreshOptions = {
|
|
8364
|
+
config,
|
|
8365
|
+
gh: options.gh,
|
|
8366
|
+
...sleep === undefined ? {} : { sleep },
|
|
8367
|
+
...progress === undefined ? {} : { progress }
|
|
8368
|
+
};
|
|
8312
8369
|
let refreshed;
|
|
8313
8370
|
try {
|
|
8314
8371
|
refreshed = await refreshProjectItems(refreshOptions, [], { everyItem: true });
|
|
@@ -8320,24 +8377,13 @@ async function fieldsStep(options, project) {
|
|
|
8320
8377
|
if (refreshed.kind !== "refreshed") {
|
|
8321
8378
|
return { part: partOf("fields", FIELDS_PART, "refused", refreshed.warnings.join(" ") || "the refresh sent no call"), problems: [] };
|
|
8322
8379
|
}
|
|
8323
|
-
|
|
8324
|
-
const problems = refreshed.warnings.slice(writes.rateLimited ? 1 : 0);
|
|
8325
|
-
if (writes.rateLimited) {
|
|
8326
|
-
return { part: partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), problems };
|
|
8327
|
-
}
|
|
8328
|
-
if (changes.length === 0) {
|
|
8329
|
-
const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
|
|
8330
|
-
return { part: partOf("fields", FIELDS_PART, "present", held), problems };
|
|
8331
|
-
}
|
|
8332
|
-
const issues = new Set(changes.map(({ issue }) => issue)).size;
|
|
8333
|
-
const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
|
|
8334
|
-
return { part: partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}`), problems };
|
|
8380
|
+
return { part: refreshedPart(refreshed, holds), problems: refreshed.skipped.map(skippedFieldWarning) };
|
|
8335
8381
|
}
|
|
8336
8382
|
async function onProject(options, repository, project) {
|
|
8337
8383
|
const items = await itemsStep(options, repository, project);
|
|
8338
|
-
const fields = await fieldsStep(options, project);
|
|
8384
|
+
const fields = await fieldsStep(options, project, items.holds);
|
|
8339
8385
|
const setting = writeProjectNumber(options.root, project.number);
|
|
8340
|
-
return { parts: [items, fields.part, setting], problems: fields.problems };
|
|
8386
|
+
return { parts: [items.part, fields.part, setting], problems: fields.problems };
|
|
8341
8387
|
}
|
|
8342
8388
|
async function setUpProject(options) {
|
|
8343
8389
|
const { gh, config } = options;
|
|
@@ -8369,12 +8415,16 @@ function projectSetupChanged(report2) {
|
|
|
8369
8415
|
}
|
|
8370
8416
|
var PROJECT_HEADING = "GitHub project:";
|
|
8371
8417
|
var OUTCOME_WIDTH3 = 7;
|
|
8418
|
+
var LISTED_INDENT = " ".repeat(2 + OUTCOME_WIDTH3 + 2);
|
|
8372
8419
|
function projectPartLine(part) {
|
|
8373
8420
|
const row = ` ${part.outcome.padEnd(OUTCOME_WIDTH3, " ")} ${part.name}`;
|
|
8374
8421
|
return part.outcome === "present" ? row : `${row}: ${part.detail}`;
|
|
8375
8422
|
}
|
|
8423
|
+
function projectPartLines(part) {
|
|
8424
|
+
return [projectPartLine(part), ...(part.listed ?? []).map((line) => `${LISTED_INDENT}${line}`)];
|
|
8425
|
+
}
|
|
8376
8426
|
function renderProjectSetup(report2) {
|
|
8377
|
-
return [PROJECT_HEADING, ...report2.parts.
|
|
8427
|
+
return [PROJECT_HEADING, ...report2.parts.flatMap((part) => projectPartLines(part)), ...report2.problems];
|
|
8378
8428
|
}
|
|
8379
8429
|
var PROJECT_QUESTION = "Also create a GitHub project with roadmap and kanban views? [y/N] ";
|
|
8380
8430
|
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}.`;
|
|
@@ -8394,6 +8444,13 @@ function withWrittenRoadmap(root, config) {
|
|
|
8394
8444
|
const written = readRoadmapSetting(root).issue;
|
|
8395
8445
|
return written === null ? config : { ...config, roadmapIssue: written };
|
|
8396
8446
|
}
|
|
8447
|
+
function retrySeamsOf(options) {
|
|
8448
|
+
const { sleep, onRetry } = options;
|
|
8449
|
+
return {
|
|
8450
|
+
...sleep === undefined ? {} : { sleep },
|
|
8451
|
+
...onRetry === undefined ? {} : { onRetry }
|
|
8452
|
+
};
|
|
8453
|
+
}
|
|
8397
8454
|
async function runProjectStep(options) {
|
|
8398
8455
|
const { wanted, board, root, isTerminal, openPrompter } = options;
|
|
8399
8456
|
if (board.status !== "ran") {
|
|
@@ -8403,7 +8460,16 @@ async function runProjectStep(options) {
|
|
|
8403
8460
|
return noProject("declined", false);
|
|
8404
8461
|
const run = async (asked) => {
|
|
8405
8462
|
const setUp = options.setUp ?? setUpProject;
|
|
8406
|
-
const
|
|
8463
|
+
const config = withWrittenRoadmap(root, options.config);
|
|
8464
|
+
const gh = openProjectRunner(options.openGh(), config, retrySeamsOf(options));
|
|
8465
|
+
const { sleep, progress } = options;
|
|
8466
|
+
const report2 = await setUp({
|
|
8467
|
+
root,
|
|
8468
|
+
config,
|
|
8469
|
+
gh,
|
|
8470
|
+
...sleep === undefined ? {} : { sleep },
|
|
8471
|
+
...progress === undefined ? {} : { progress }
|
|
8472
|
+
});
|
|
8407
8473
|
return { status: "ran", asked, report: report2, warnings: [] };
|
|
8408
8474
|
};
|
|
8409
8475
|
if (wanted === true)
|
|
@@ -8481,9 +8547,10 @@ function fieldsRow(project, template) {
|
|
|
8481
8547
|
return rowOf2("fields", FIELDS_ROW, "missing", `${sentences} ${fix}`);
|
|
8482
8548
|
}
|
|
8483
8549
|
async function readDoctorProject(input) {
|
|
8484
|
-
const {
|
|
8485
|
-
if (gh === null || number === null)
|
|
8550
|
+
const { number, template } = input;
|
|
8551
|
+
if (input.gh === null || number === null)
|
|
8486
8552
|
return null;
|
|
8553
|
+
const gh = openProjectRunner(input.gh, input.config, input.retry);
|
|
8487
8554
|
const scope = await readScopeRow(gh);
|
|
8488
8555
|
if (scope.outcome !== "present") {
|
|
8489
8556
|
const project = notRead("project", `${PROJECT_ROW} ${String(number)}`, scope);
|
|
@@ -10614,7 +10681,7 @@ async function runDoctor(context, seams) {
|
|
|
10614
10681
|
const modeSet = preflight.resolved.sources.boardRelationships !== "default";
|
|
10615
10682
|
const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue, preflight.config.boardRelationships, modeSet);
|
|
10616
10683
|
const { boardProjectNumber: number, boardProjectTemplate: template } = preflight.config;
|
|
10617
|
-
const projectRows = await readDoctorProject({ gh, number, template });
|
|
10684
|
+
const projectRows = await readDoctorProject({ gh, number, template, config: preflight.config, retry: commandRetrySeams(context.output, context.outputMode) });
|
|
10618
10685
|
const readings = { ...board, project: projectRows, cleanup, refs, release, tiers, effortSchema, effortSync };
|
|
10619
10686
|
writeText(context, renderDoctor(preflight));
|
|
10620
10687
|
await announceRisk(context, preflight, seams);
|
|
@@ -12002,7 +12069,8 @@ async function refreshProjectAfterEpic(context, seams, target) {
|
|
|
12002
12069
|
});
|
|
12003
12070
|
if (config.boardProjectNumber === null)
|
|
12004
12071
|
return null;
|
|
12005
|
-
const
|
|
12072
|
+
const retry = commandRetrySeams(context.output, context.outputMode, seams.sleep);
|
|
12073
|
+
const openGh = () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, retry);
|
|
12006
12074
|
warnings = await refreshLines(config, openGh, seams.projectRefresh ?? refreshIssueItems, target, named2);
|
|
12007
12075
|
} catch (error) {
|
|
12008
12076
|
warnings = [epicProjectProblemLine(named2, messageOf(error))];
|
|
@@ -14846,6 +14914,11 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
14846
14914
|
"# project:",
|
|
14847
14915
|
`# template: ${CONFIG_DEFAULTS.boardProjectTemplate} # the project URL rafa init --board --project copies`,
|
|
14848
14916
|
"# number: # the project rafa mirrors the board to; unset until rafa init --board --project",
|
|
14917
|
+
`# retries: ${String(CONFIG_DEFAULTS.boardProjectRetries)} # 1..10 | false, tries again after a failed project call`,
|
|
14918
|
+
`# retryWaitSeconds: ${String(CONFIG_DEFAULTS.boardProjectRetryWaitSeconds)} # 1..60, the first wait before a retry; each later one doubles`,
|
|
14919
|
+
`# progressSeconds: ${String(CONFIG_DEFAULTS.boardProjectProgressSeconds)} # 1..300 | false, the most seconds between two progress lines`,
|
|
14920
|
+
`# writeBatchSize: ${String(CONFIG_DEFAULTS.boardProjectWriteBatchSize)} # 1..100, field writes per request`,
|
|
14921
|
+
`# writePauseMs: ${String(CONFIG_DEFAULTS.boardProjectWritePauseMs)} # 0..60000, the pause between two write requests`,
|
|
14849
14922
|
"# roadmap:",
|
|
14850
14923
|
"# issue: # the issue plan create --next reads; unset is the one titled Roadmap",
|
|
14851
14924
|
"# claims:",
|
|
@@ -15248,7 +15321,9 @@ async function runInit(context, seams) {
|
|
|
15248
15321
|
config: scopes.config,
|
|
15249
15322
|
openGh: () => seams.gh(scopes.written.root),
|
|
15250
15323
|
isTerminal: seams.isTerminal,
|
|
15251
|
-
openPrompter: seams.openPrompter
|
|
15324
|
+
openPrompter: seams.openPrompter,
|
|
15325
|
+
...commandRetrySeams(context.output, context.outputMode, seams.sleep),
|
|
15326
|
+
progress: commandProgressFeed(context.output, context.outputMode, scopes.config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now })
|
|
15252
15327
|
});
|
|
15253
15328
|
const result2 = {
|
|
15254
15329
|
...scopes.written,
|
|
@@ -15285,7 +15360,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
15285
15360
|
subject: "init",
|
|
15286
15361
|
action: "init",
|
|
15287
15362
|
summary: "set up a rafa project: its root, .rafa/config.yaml, the tree and the .gitignore entry",
|
|
15288
|
-
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.",
|
|
15363
|
+
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." + " Adding the issues, reading their facts and writing their fields each print a start line with the" + " total, a progress line at most every board.project.progressSeconds and an end line, one `progress`" + " event each with `--output=json`." + " 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.",
|
|
15289
15364
|
args: [],
|
|
15290
15365
|
flags: [
|
|
15291
15366
|
{
|
|
@@ -15310,7 +15385,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
15310
15385
|
},
|
|
15311
15386
|
{
|
|
15312
15387
|
name: "project",
|
|
15313
|
-
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.",
|
|
15388
|
+
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. The adds, the facts reads and the" + " field writes print their progress, as `adding issues: 146/283, 6m 40s`.",
|
|
15314
15389
|
type: "boolean"
|
|
15315
15390
|
},
|
|
15316
15391
|
{
|
|
@@ -16400,7 +16475,7 @@ async function addCreatedToProject(context, seams, ref8) {
|
|
|
16400
16475
|
});
|
|
16401
16476
|
const lines = await addAndRefreshIssue({
|
|
16402
16477
|
config,
|
|
16403
|
-
openGh: () => seams.gh ?? createGhRunner({ cwd: project.root })
|
|
16478
|
+
openGh: () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep))
|
|
16404
16479
|
}, issue);
|
|
16405
16480
|
for (const line of lines)
|
|
16406
16481
|
context.output.warn(line);
|
|
@@ -19075,6 +19150,10 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
|
|
|
19075
19150
|
versionGuard: mergeGuardSettings(config),
|
|
19076
19151
|
projectRefresh: {
|
|
19077
19152
|
boardProjectNumber: config.boardProjectNumber,
|
|
19153
|
+
boardProjectRetries: config.boardProjectRetries,
|
|
19154
|
+
boardProjectRetryWaitSeconds: config.boardProjectRetryWaitSeconds,
|
|
19155
|
+
boardProjectWriteBatchSize: config.boardProjectWriteBatchSize,
|
|
19156
|
+
boardProjectWritePauseMs: config.boardProjectWritePauseMs,
|
|
19078
19157
|
boardRelationships: config.boardRelationships,
|
|
19079
19158
|
roadmapIssue: config.roadmapIssue,
|
|
19080
19159
|
releaseFragments: config.releaseFragments
|
|
@@ -19879,7 +19958,7 @@ async function refreshProjectAfterMerge(options) {
|
|
|
19879
19958
|
const closed = [...new Set(closedIssuesIn(options.body))];
|
|
19880
19959
|
if (config.boardProjectNumber === null || closed.length === 0)
|
|
19881
19960
|
return null;
|
|
19882
|
-
const gh = options.openGh();
|
|
19961
|
+
const gh = openProjectRunner(options.openGh(), config, options.retry);
|
|
19883
19962
|
const wanted = new Set(closed);
|
|
19884
19963
|
const warnings = [];
|
|
19885
19964
|
let listed2 = [];
|
|
@@ -20646,7 +20725,14 @@ async function runMerge2(context, seams) {
|
|
|
20646
20725
|
const roadmapTicks = await reportTick(context, pr, seams, detail);
|
|
20647
20726
|
const steps = cleanUpAfterMerge(git, detail, { info: info2, warn: warn3 });
|
|
20648
20727
|
const board = await reportAfterCleanUp(context, pr, seams, detail);
|
|
20649
|
-
await refreshProjectAfterMerge({
|
|
20728
|
+
await refreshProjectAfterMerge({
|
|
20729
|
+
body: detail.body,
|
|
20730
|
+
config: pr.projectRefresh,
|
|
20731
|
+
openGh: () => openGh(pr, seams),
|
|
20732
|
+
unblocked: board.unblocked,
|
|
20733
|
+
warn: warn3,
|
|
20734
|
+
retry: commandRetrySeams(context.output, context.outputMode)
|
|
20735
|
+
});
|
|
20650
20736
|
const followUps = reportFollowUps({
|
|
20651
20737
|
root: pr.project.root,
|
|
20652
20738
|
home: pr.project.home,
|
|
@@ -23822,7 +23908,7 @@ async function refreshProjectAfterSettle(options) {
|
|
|
23822
23908
|
const commits = releasedCommits(options.delivered);
|
|
23823
23909
|
if (config.boardProjectNumber === null || commits === null || commits.length === 0)
|
|
23824
23910
|
return null;
|
|
23825
|
-
const gh = options.openGh();
|
|
23911
|
+
const gh = openProjectRunner(options.openGh(), config, options.retry);
|
|
23826
23912
|
let pulls;
|
|
23827
23913
|
try {
|
|
23828
23914
|
pulls = await readPulls(gh, commits, branch);
|
|
@@ -23998,7 +24084,8 @@ async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
23998
24084
|
branch,
|
|
23999
24085
|
config,
|
|
24000
24086
|
openGh: () => (seams.gh ?? ((cwd) => createGhRunner({ cwd })))(project.root),
|
|
24001
|
-
refresh: seams.refresh
|
|
24087
|
+
refresh: seams.refresh,
|
|
24088
|
+
retry: commandRetrySeams(context.output, context.outputMode)
|
|
24002
24089
|
});
|
|
24003
24090
|
return { ...ran.value, project: refreshed };
|
|
24004
24091
|
}
|