@open-tomato/rafa 0.39.0 → 0.41.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 +1 -1
- package/dist/cli.js +3533 -1662
- package/dist/continue-criteria.md +31 -0
- package/dist/continue-decision-prompt.md +94 -0
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-m0wt4nr1.js → index-3jpz9nr3.js} +129 -8
- package/dist/{index-1r2b1052.js → index-cd0wzc12.js} +76 -6
- package/dist/{index-psv1sxkv.js → index-snf04fpt.js} +390 -577
- package/dist/{index-nwgwxfkt.js → index-wjsarzf5.js} +2638 -820
- package/dist/{index-g4116t9m.js → index-zg0qsb5b.js} +29 -14
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/package.json +2 -2
|
@@ -39,9 +39,9 @@ import {
|
|
|
39
39
|
NO_NEXT_STEP,
|
|
40
40
|
NoSessionRefusal,
|
|
41
41
|
ORIGIN_FIELD,
|
|
42
|
+
PLACEHOLDER_REASON,
|
|
42
43
|
PREVIOUS_COPY_WARN_ABOVE,
|
|
43
44
|
PROJECT_SCOPE_FIX,
|
|
44
|
-
PROJECT_WRITE_PAUSE_MS,
|
|
45
45
|
PROVENANCE_FIELD,
|
|
46
46
|
REASON_FLAG,
|
|
47
47
|
RELEASE_BASE_BRANCH,
|
|
@@ -94,6 +94,7 @@ import {
|
|
|
94
94
|
branchName,
|
|
95
95
|
buildReport,
|
|
96
96
|
buildSettle,
|
|
97
|
+
buildVerifyPrompt,
|
|
97
98
|
bundledAgentsDirectory,
|
|
98
99
|
byName,
|
|
99
100
|
cancelMoveReason,
|
|
@@ -111,6 +112,8 @@ import {
|
|
|
111
112
|
closedIssuesIn,
|
|
112
113
|
collect,
|
|
113
114
|
collectSessionRow,
|
|
115
|
+
commandProgressFeed,
|
|
116
|
+
commandRetrySeams,
|
|
114
117
|
commandWords,
|
|
115
118
|
compareVersions,
|
|
116
119
|
configuredMoveRelations,
|
|
@@ -133,6 +136,7 @@ import {
|
|
|
133
136
|
createPlanDirNames,
|
|
134
137
|
createPlanRefsVerifier,
|
|
135
138
|
createStartPreflightClaim,
|
|
139
|
+
criteriaToAsk,
|
|
136
140
|
crossRepositoryRefusal,
|
|
137
141
|
defaultCleanupSeams,
|
|
138
142
|
developmentApplied,
|
|
@@ -149,9 +153,10 @@ import {
|
|
|
149
153
|
epicTickSentence,
|
|
150
154
|
estimateEta,
|
|
151
155
|
etaLine,
|
|
152
|
-
|
|
156
|
+
eventsFileOf,
|
|
153
157
|
expectTwoArguments,
|
|
154
158
|
failingRows,
|
|
159
|
+
fenceFor,
|
|
155
160
|
fetchClaimBranches,
|
|
156
161
|
findReadinessGaps,
|
|
157
162
|
fingerprintText,
|
|
@@ -220,17 +225,21 @@ import {
|
|
|
220
225
|
noBoardListsLine,
|
|
221
226
|
normaliseReason,
|
|
222
227
|
notFoundWarning,
|
|
228
|
+
notRefreshedWarning,
|
|
223
229
|
notesFileName,
|
|
224
230
|
onTracker,
|
|
225
231
|
openBoards,
|
|
226
232
|
openNextRelations,
|
|
227
233
|
openNextSources,
|
|
234
|
+
openPhase,
|
|
235
|
+
openProjectRunner,
|
|
228
236
|
parseBaseStanding,
|
|
229
237
|
parseClaimMessage,
|
|
230
238
|
parseReportArgs,
|
|
231
239
|
parseRoadmapBody,
|
|
232
240
|
parseSemanticVersion,
|
|
233
241
|
parseTreePaths,
|
|
242
|
+
parseVerifyPlan,
|
|
234
243
|
parseWorkingTree,
|
|
235
244
|
parseWorktrees,
|
|
236
245
|
pathDirectories,
|
|
@@ -269,6 +278,7 @@ import {
|
|
|
269
278
|
readEpicDependents,
|
|
270
279
|
readEpicProblems,
|
|
271
280
|
readEpics,
|
|
281
|
+
readEventsFrom,
|
|
272
282
|
readGuard,
|
|
273
283
|
readHomeAfterLoop,
|
|
274
284
|
readIssueFacts,
|
|
@@ -299,6 +309,7 @@ import {
|
|
|
299
309
|
readTextFlag,
|
|
300
310
|
readTrees,
|
|
301
311
|
readTrendInputs,
|
|
312
|
+
readVerifyPrompt,
|
|
302
313
|
readWaiting,
|
|
303
314
|
ready_default,
|
|
304
315
|
receiptProblem,
|
|
@@ -316,6 +327,7 @@ import {
|
|
|
316
327
|
renderDependentComment,
|
|
317
328
|
renderDoctorEffortSync,
|
|
318
329
|
renderDoctorRefs,
|
|
330
|
+
renderEpicBody,
|
|
319
331
|
renderHorizonComment,
|
|
320
332
|
renderMoveComment,
|
|
321
333
|
renderParkedPullRequestComment,
|
|
@@ -361,7 +373,9 @@ import {
|
|
|
361
373
|
shellQuote,
|
|
362
374
|
show_default,
|
|
363
375
|
skillItem,
|
|
376
|
+
skippedFieldWarning,
|
|
364
377
|
sourceItem,
|
|
378
|
+
splitCriteria,
|
|
365
379
|
stackPaths,
|
|
366
380
|
stacksFromEccSkillName,
|
|
367
381
|
stageOf,
|
|
@@ -395,7 +409,7 @@ import {
|
|
|
395
409
|
worktreesHolding,
|
|
396
410
|
writeSession,
|
|
397
411
|
writeShippedFile
|
|
398
|
-
} from "./index-
|
|
412
|
+
} from "./index-wjsarzf5.js";
|
|
399
413
|
import {
|
|
400
414
|
PROBE_TIMEOUT_MS,
|
|
401
415
|
loadPlanPrerequisites,
|
|
@@ -407,7 +421,7 @@ import {
|
|
|
407
421
|
runPreflight,
|
|
408
422
|
splitBlockerComment,
|
|
409
423
|
trackerPathFor
|
|
410
|
-
} from "./index-
|
|
424
|
+
} from "./index-zg0qsb5b.js";
|
|
411
425
|
import {
|
|
412
426
|
ACTION_HEADING,
|
|
413
427
|
CAUSE_HEADING,
|
|
@@ -518,7 +532,7 @@ import {
|
|
|
518
532
|
versionLine,
|
|
519
533
|
writeFrontmatter,
|
|
520
534
|
writePositionFile
|
|
521
|
-
} from "./index-
|
|
535
|
+
} from "./index-cd0wzc12.js";
|
|
522
536
|
import {
|
|
523
537
|
CONFIG_DEFAULTS,
|
|
524
538
|
CONFIG_FILE,
|
|
@@ -537,7 +551,7 @@ import {
|
|
|
537
551
|
readItemBytes,
|
|
538
552
|
readRafaBlocks,
|
|
539
553
|
resolveTiers
|
|
540
|
-
} from "./index-
|
|
554
|
+
} from "./index-3jpz9nr3.js";
|
|
541
555
|
import {
|
|
542
556
|
promotable
|
|
543
557
|
} from "./index-d6d0942q.js";
|
|
@@ -2163,7 +2177,7 @@ import { join as join4, relative as relative2 } from "path";
|
|
|
2163
2177
|
var SEARCH_PROMPT_PREFIX = "# Inventory search instructions";
|
|
2164
2178
|
var SEARCH_BLOCK_FENCE = "rafa:search";
|
|
2165
2179
|
var MIN_FENCE_LENGTH = 3;
|
|
2166
|
-
function
|
|
2180
|
+
function fenceFor2(text) {
|
|
2167
2181
|
const longestRun = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
|
|
2168
2182
|
return "`".repeat(Math.max(MIN_FENCE_LENGTH, longestRun + 1));
|
|
2169
2183
|
}
|
|
@@ -2179,7 +2193,7 @@ var EXAMPLE_BLOCK = [
|
|
|
2179
2193
|
];
|
|
2180
2194
|
function renderSearchPrompt(input) {
|
|
2181
2195
|
const plural2 = `${input.kind}s`;
|
|
2182
|
-
const fence =
|
|
2196
|
+
const fence = fenceFor2(input.question);
|
|
2183
2197
|
const candidates = input.candidates.map((candidate) => `- ${candidate.name}: \`${candidate.file}\``);
|
|
2184
2198
|
return [
|
|
2185
2199
|
SEARCH_PROMPT_PREFIX,
|
|
@@ -3309,8 +3323,17 @@ async function addIssues(options, ref, issues) {
|
|
|
3309
3323
|
const project = await port.find(ref);
|
|
3310
3324
|
if (project === null)
|
|
3311
3325
|
return null;
|
|
3312
|
-
|
|
3313
|
-
|
|
3326
|
+
const phase = openPhase(options.progress, "adds", issues.length);
|
|
3327
|
+
let added = 0;
|
|
3328
|
+
try {
|
|
3329
|
+
for (const number of issues) {
|
|
3330
|
+
await port.addItem(project.id, { repository, number });
|
|
3331
|
+
added += 1;
|
|
3332
|
+
phase.advance(added);
|
|
3333
|
+
}
|
|
3334
|
+
} finally {
|
|
3335
|
+
phase.end({ done: added, refused: issues.length - added });
|
|
3336
|
+
}
|
|
3314
3337
|
return issues;
|
|
3315
3338
|
}
|
|
3316
3339
|
async function addMissing(options, first, synced) {
|
|
@@ -3327,6 +3350,7 @@ async function addMissing(options, first, synced) {
|
|
|
3327
3350
|
...synced,
|
|
3328
3351
|
changes: [...first.changes, ...second.changes],
|
|
3329
3352
|
added,
|
|
3353
|
+
refused: [...first.refused, ...second.refused],
|
|
3330
3354
|
writes: addedWrites(first.writes, second.writes),
|
|
3331
3355
|
warnings: [...first.warnings, ...second.warnings.filter((line) => !first.warnings.includes(line))]
|
|
3332
3356
|
};
|
|
@@ -3347,6 +3371,7 @@ async function syncProject(options) {
|
|
|
3347
3371
|
changes: first.changes,
|
|
3348
3372
|
missing: first.missing,
|
|
3349
3373
|
added: [],
|
|
3374
|
+
refused: first.refused,
|
|
3350
3375
|
writes: first.writes,
|
|
3351
3376
|
warnings: first.warnings
|
|
3352
3377
|
});
|
|
@@ -3364,17 +3389,21 @@ function counted(count, noun) {
|
|
|
3364
3389
|
function changeLine(change) {
|
|
3365
3390
|
return `#${String(change.issue)} ${change.name}: ${change.from ?? EMPTY_VALUE} \u2192 ${change.to ?? EMPTY_VALUE}`;
|
|
3366
3391
|
}
|
|
3392
|
+
function refusedPart(result) {
|
|
3393
|
+
return result.refused.length === 0 ? "" : `; ${counted(result.refused.length, "issue")} not refreshed`;
|
|
3394
|
+
}
|
|
3367
3395
|
function closingLine(result) {
|
|
3368
3396
|
const project = `project #${String(result.project.number)}`;
|
|
3369
3397
|
const adds = result.dryRun ? result.missing : result.added;
|
|
3370
3398
|
const changes = counted(result.changes.length, "change");
|
|
3371
3399
|
const issues = counted(adds.length, "issue");
|
|
3400
|
+
const inStep = result.changes.length === 0 && adds.length === 0 && result.refused.length === 0;
|
|
3372
3401
|
if (result.dryRun) {
|
|
3373
|
-
return
|
|
3402
|
+
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
3403
|
}
|
|
3375
3404
|
if (result.rateLimited)
|
|
3376
3405
|
return `Stopped on ${project}: ${String(result.written)} of ${changes} written; the rate limit refused the rest.`;
|
|
3377
|
-
return
|
|
3406
|
+
return inStep ? `Synced ${project}: in step, nothing to change.` : `Synced ${project}: ${changes} written and ${issues} added${refusedPart(result)}.`;
|
|
3378
3407
|
}
|
|
3379
3408
|
function syncLines(result) {
|
|
3380
3409
|
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 +3419,7 @@ function syncResultOf(synced) {
|
|
|
3390
3419
|
changes: synced.changes.map(plainChange),
|
|
3391
3420
|
missing: synced.missing,
|
|
3392
3421
|
added: synced.added,
|
|
3422
|
+
refused: synced.refused.map(({ number, reason }) => ({ issue: number, reason })),
|
|
3393
3423
|
written: synced.writes.written,
|
|
3394
3424
|
rateLimited: synced.writes.rateLimited,
|
|
3395
3425
|
notUpdated: synced.writes.notUpdated,
|
|
@@ -3407,9 +3437,10 @@ async function runBoardSync(context, seams) {
|
|
|
3407
3437
|
if (config.boardProjectNumber === null) {
|
|
3408
3438
|
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
3439
|
}
|
|
3410
|
-
const gh = seams.gh ?? createGhRunner({ cwd: project.root });
|
|
3440
|
+
const gh = openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep));
|
|
3411
3441
|
const dryRun = context.flags[DRY_RUN_FLAG] === true;
|
|
3412
|
-
const
|
|
3442
|
+
const progress = commandProgressFeed(context.output, context.outputMode, config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now });
|
|
3443
|
+
const synced = await syncProject(seams.sleep === undefined ? { config, gh, dryRun, progress } : { config, gh, dryRun, progress, sleep: seams.sleep }).catch((error) => {
|
|
3413
3444
|
throw new CommandExit(BOARD_SYNC_REFUSAL_EXIT, `\u274C Could not sync the project: ${messageOf(error)}`);
|
|
3414
3445
|
});
|
|
3415
3446
|
if (synced.kind !== "synced") {
|
|
@@ -3439,7 +3470,7 @@ function createBoardSyncCommand(seams = {}) {
|
|
|
3439
3470
|
subject: "board",
|
|
3440
3471
|
action: "sync",
|
|
3441
3472
|
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
|
|
3473
|
+
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
3474
|
args: [],
|
|
3444
3475
|
flags: [
|
|
3445
3476
|
{
|
|
@@ -8274,41 +8305,76 @@ async function missingIssues(options, repository, project) {
|
|
|
8274
8305
|
const held = heldIssues(await createGhProjectPort(gh).items(project.id), repository);
|
|
8275
8306
|
const ranked = board.closed.filter((issue) => rankOf(board.ranks, issue) !== null);
|
|
8276
8307
|
const unread = board.closed.filter((issue) => rankOf(board.ranks, issue) === null && !held.has(issue));
|
|
8277
|
-
const facts = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
|
|
8308
|
+
const { facts } = await readIssueFacts({ gh, fragments: config.releaseFragments }, unread);
|
|
8278
8309
|
const inReview = unread.filter((issue) => {
|
|
8279
8310
|
const read = facts.get(issue);
|
|
8280
8311
|
return read !== undefined && stageOf(read) === "In review";
|
|
8281
8312
|
});
|
|
8282
|
-
|
|
8313
|
+
const missing = [...new Set([...board.open, ...ranked, ...inReview])].filter((issue) => !held.has(issue)).sort((a, b) => a - b);
|
|
8314
|
+
return { held, missing };
|
|
8283
8315
|
}
|
|
8284
8316
|
async function itemsStep(options, repository, project) {
|
|
8285
8317
|
const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
|
|
8286
|
-
let
|
|
8318
|
+
let wanted;
|
|
8287
8319
|
try {
|
|
8288
|
-
|
|
8320
|
+
wanted = await missingIssues(options, repository, project);
|
|
8289
8321
|
} catch (error) {
|
|
8290
|
-
return partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`);
|
|
8322
|
+
return { part: partOf("items", ITEMS_PART, "refused", `the issues to add could not be read: ${refusalOf(error)}`), holds: null };
|
|
8291
8323
|
}
|
|
8324
|
+
const { held, missing } = wanted;
|
|
8292
8325
|
if (missing.length === 0)
|
|
8293
|
-
return partOf("items", ITEMS_PART, "present", "the project holds every issue it shows");
|
|
8326
|
+
return { part: partOf("items", ITEMS_PART, "present", "the project holds every issue it shows"), holds: held.size };
|
|
8294
8327
|
const port = createGhProjectPort(options.gh);
|
|
8328
|
+
const phase = openPhase(options.progress, "adds", missing.length);
|
|
8295
8329
|
let added = 0;
|
|
8296
8330
|
try {
|
|
8297
8331
|
for (const number of missing) {
|
|
8298
8332
|
if (added > 0)
|
|
8299
|
-
await sleep(
|
|
8333
|
+
await sleep(options.config.boardProjectWritePauseMs);
|
|
8300
8334
|
await port.addItem(project.id, { repository, number });
|
|
8301
8335
|
added += 1;
|
|
8336
|
+
phase.advance(added);
|
|
8302
8337
|
}
|
|
8303
8338
|
} catch (error) {
|
|
8339
|
+
phase.end({ done: added, refused: missing.length - added });
|
|
8304
8340
|
const went = `${issueCount(added)} of ${String(missing.length)} added`;
|
|
8305
|
-
|
|
8341
|
+
const why = `${went}, then #${String(missing[added])} was refused: ${refusalOf(error)}; run \`${PROJECT_STEP_FIX}\` to add the rest`;
|
|
8342
|
+
return { part: partOf("items", ITEMS_PART, "refused", why), holds: held.size + added };
|
|
8306
8343
|
}
|
|
8307
|
-
|
|
8344
|
+
phase.end({ done: added, refused: 0 });
|
|
8345
|
+
return { part: partOf("items", ITEMS_PART, "created", `added ${issueCount(added)} to the project`), holds: held.size + added };
|
|
8308
8346
|
}
|
|
8309
|
-
|
|
8347
|
+
function withListed(part, listed2) {
|
|
8348
|
+
return listed2.length === 0 ? part : { ...part, listed: listed2 };
|
|
8349
|
+
}
|
|
8350
|
+
function refusedClause(refused) {
|
|
8351
|
+
return refused === 0 ? "" : `; ${issueCount(refused)} not refreshed`;
|
|
8352
|
+
}
|
|
8353
|
+
function refreshedPart(refreshed, holds) {
|
|
8354
|
+
const { writes, changes, skipped, refused } = refreshed;
|
|
8355
|
+
const listed2 = refused.map(notRefreshedWarning);
|
|
8356
|
+
if (writes.rateLimited)
|
|
8357
|
+
return withListed(partOf("fields", FIELDS_PART, "refused", rateLimitWarning(writes.notUpdated)), listed2);
|
|
8358
|
+
if (changes.length === 0 && refused.length > 0 && (holds === null || holds <= refused.length)) {
|
|
8359
|
+
return withListed(partOf("fields", FIELDS_PART, "refused", `no item was filled: ${issueCount(refused.length)} not refreshed`), listed2);
|
|
8360
|
+
}
|
|
8361
|
+
if (changes.length === 0) {
|
|
8362
|
+
const held = skipped.length === 0 ? "every value is as the rules give it" : "every value written is as the rules give it";
|
|
8363
|
+
return withListed(partOf("fields", FIELDS_PART, "present", `${held}${refusedClause(refused.length)}`), listed2);
|
|
8364
|
+
}
|
|
8365
|
+
const issues = new Set(changes.map(({ issue }) => issue)).size;
|
|
8366
|
+
const values = writes.written === 1 ? "1 value" : `${String(writes.written)} values`;
|
|
8367
|
+
return withListed(partOf("fields", FIELDS_PART, "created", `wrote ${values} to ${issueCount(issues)}${refusedClause(refused.length)}`), listed2);
|
|
8368
|
+
}
|
|
8369
|
+
async function fieldsStep(options, project, holds) {
|
|
8310
8370
|
const config = { ...options.config, boardProjectNumber: project.number };
|
|
8311
|
-
const
|
|
8371
|
+
const { sleep, progress } = options;
|
|
8372
|
+
const refreshOptions = {
|
|
8373
|
+
config,
|
|
8374
|
+
gh: options.gh,
|
|
8375
|
+
...sleep === undefined ? {} : { sleep },
|
|
8376
|
+
...progress === undefined ? {} : { progress }
|
|
8377
|
+
};
|
|
8312
8378
|
let refreshed;
|
|
8313
8379
|
try {
|
|
8314
8380
|
refreshed = await refreshProjectItems(refreshOptions, [], { everyItem: true });
|
|
@@ -8320,24 +8386,13 @@ async function fieldsStep(options, project) {
|
|
|
8320
8386
|
if (refreshed.kind !== "refreshed") {
|
|
8321
8387
|
return { part: partOf("fields", FIELDS_PART, "refused", refreshed.warnings.join(" ") || "the refresh sent no call"), problems: [] };
|
|
8322
8388
|
}
|
|
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 };
|
|
8389
|
+
return { part: refreshedPart(refreshed, holds), problems: refreshed.skipped.map(skippedFieldWarning) };
|
|
8335
8390
|
}
|
|
8336
8391
|
async function onProject(options, repository, project) {
|
|
8337
8392
|
const items = await itemsStep(options, repository, project);
|
|
8338
|
-
const fields = await fieldsStep(options, project);
|
|
8393
|
+
const fields = await fieldsStep(options, project, items.holds);
|
|
8339
8394
|
const setting = writeProjectNumber(options.root, project.number);
|
|
8340
|
-
return { parts: [items, fields.part, setting], problems: fields.problems };
|
|
8395
|
+
return { parts: [items.part, fields.part, setting], problems: fields.problems };
|
|
8341
8396
|
}
|
|
8342
8397
|
async function setUpProject(options) {
|
|
8343
8398
|
const { gh, config } = options;
|
|
@@ -8369,12 +8424,16 @@ function projectSetupChanged(report2) {
|
|
|
8369
8424
|
}
|
|
8370
8425
|
var PROJECT_HEADING = "GitHub project:";
|
|
8371
8426
|
var OUTCOME_WIDTH3 = 7;
|
|
8427
|
+
var LISTED_INDENT = " ".repeat(2 + OUTCOME_WIDTH3 + 2);
|
|
8372
8428
|
function projectPartLine(part) {
|
|
8373
8429
|
const row = ` ${part.outcome.padEnd(OUTCOME_WIDTH3, " ")} ${part.name}`;
|
|
8374
8430
|
return part.outcome === "present" ? row : `${row}: ${part.detail}`;
|
|
8375
8431
|
}
|
|
8432
|
+
function projectPartLines(part) {
|
|
8433
|
+
return [projectPartLine(part), ...(part.listed ?? []).map((line) => `${LISTED_INDENT}${line}`)];
|
|
8434
|
+
}
|
|
8376
8435
|
function renderProjectSetup(report2) {
|
|
8377
|
-
return [PROJECT_HEADING, ...report2.parts.
|
|
8436
|
+
return [PROJECT_HEADING, ...report2.parts.flatMap((part) => projectPartLines(part)), ...report2.problems];
|
|
8378
8437
|
}
|
|
8379
8438
|
var PROJECT_QUESTION = "Also create a GitHub project with roadmap and kanban views? [y/N] ";
|
|
8380
8439
|
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 +8453,13 @@ function withWrittenRoadmap(root, config) {
|
|
|
8394
8453
|
const written = readRoadmapSetting(root).issue;
|
|
8395
8454
|
return written === null ? config : { ...config, roadmapIssue: written };
|
|
8396
8455
|
}
|
|
8456
|
+
function retrySeamsOf(options) {
|
|
8457
|
+
const { sleep, onRetry } = options;
|
|
8458
|
+
return {
|
|
8459
|
+
...sleep === undefined ? {} : { sleep },
|
|
8460
|
+
...onRetry === undefined ? {} : { onRetry }
|
|
8461
|
+
};
|
|
8462
|
+
}
|
|
8397
8463
|
async function runProjectStep(options) {
|
|
8398
8464
|
const { wanted, board, root, isTerminal, openPrompter } = options;
|
|
8399
8465
|
if (board.status !== "ran") {
|
|
@@ -8403,7 +8469,16 @@ async function runProjectStep(options) {
|
|
|
8403
8469
|
return noProject("declined", false);
|
|
8404
8470
|
const run = async (asked) => {
|
|
8405
8471
|
const setUp = options.setUp ?? setUpProject;
|
|
8406
|
-
const
|
|
8472
|
+
const config = withWrittenRoadmap(root, options.config);
|
|
8473
|
+
const gh = openProjectRunner(options.openGh(), config, retrySeamsOf(options));
|
|
8474
|
+
const { sleep, progress } = options;
|
|
8475
|
+
const report2 = await setUp({
|
|
8476
|
+
root,
|
|
8477
|
+
config,
|
|
8478
|
+
gh,
|
|
8479
|
+
...sleep === undefined ? {} : { sleep },
|
|
8480
|
+
...progress === undefined ? {} : { progress }
|
|
8481
|
+
});
|
|
8407
8482
|
return { status: "ran", asked, report: report2, warnings: [] };
|
|
8408
8483
|
};
|
|
8409
8484
|
if (wanted === true)
|
|
@@ -8481,9 +8556,10 @@ function fieldsRow(project, template) {
|
|
|
8481
8556
|
return rowOf2("fields", FIELDS_ROW, "missing", `${sentences} ${fix}`);
|
|
8482
8557
|
}
|
|
8483
8558
|
async function readDoctorProject(input) {
|
|
8484
|
-
const {
|
|
8485
|
-
if (gh === null || number === null)
|
|
8559
|
+
const { number, template } = input;
|
|
8560
|
+
if (input.gh === null || number === null)
|
|
8486
8561
|
return null;
|
|
8562
|
+
const gh = openProjectRunner(input.gh, input.config, input.retry);
|
|
8487
8563
|
const scope = await readScopeRow(gh);
|
|
8488
8564
|
if (scope.outcome !== "present") {
|
|
8489
8565
|
const project = notRead("project", `${PROJECT_ROW} ${String(number)}`, scope);
|
|
@@ -8753,7 +8829,7 @@ function optionalString(record, key) {
|
|
|
8753
8829
|
}
|
|
8754
8830
|
|
|
8755
8831
|
// src/commands/stretch/item.ts
|
|
8756
|
-
import { closeSync
|
|
8832
|
+
import { closeSync, mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
|
|
8757
8833
|
import { dirname as dirname6, join as join20, posix } from "path";
|
|
8758
8834
|
|
|
8759
8835
|
// src/stretch/operators.ts
|
|
@@ -8928,83 +9004,6 @@ function lineText(argv) {
|
|
|
8928
9004
|
// src/commands/stretch/item-merge.ts
|
|
8929
9005
|
import { readFileSync as readFileSync14 } from "fs";
|
|
8930
9006
|
|
|
8931
|
-
// src/loop/events-file.ts
|
|
8932
|
-
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
8933
|
-
var NEWLINE = 10;
|
|
8934
|
-
function eventsFileOf(root, record) {
|
|
8935
|
-
return eventsFilePath(root, record.sessionId);
|
|
8936
|
-
}
|
|
8937
|
-
function isPlainObject(value) {
|
|
8938
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8939
|
-
}
|
|
8940
|
-
function parseEventLine(line) {
|
|
8941
|
-
let value;
|
|
8942
|
-
try {
|
|
8943
|
-
value = JSON.parse(line);
|
|
8944
|
-
} catch {
|
|
8945
|
-
return null;
|
|
8946
|
-
}
|
|
8947
|
-
if (!isPlainObject(value))
|
|
8948
|
-
return null;
|
|
8949
|
-
const { name: name2, summary, data, ts } = value;
|
|
8950
|
-
if (typeof name2 !== "string" || typeof summary !== "string" || typeof ts !== "string")
|
|
8951
|
-
return null;
|
|
8952
|
-
if (!isPlainObject(data))
|
|
8953
|
-
return null;
|
|
8954
|
-
return Object.freeze({ name: name2, summary, data: Object.freeze({ ...data }), ts });
|
|
8955
|
-
}
|
|
8956
|
-
function bytesFrom(fd, offset) {
|
|
8957
|
-
const length = fstatSync(fd).size - offset;
|
|
8958
|
-
if (length <= 0)
|
|
8959
|
-
return null;
|
|
8960
|
-
const buffer = Buffer.alloc(length);
|
|
8961
|
-
let filled = 0;
|
|
8962
|
-
while (filled < length) {
|
|
8963
|
-
const read = readSync(fd, buffer, filled, length - filled, offset + filled);
|
|
8964
|
-
if (read === 0)
|
|
8965
|
-
break;
|
|
8966
|
-
filled += read;
|
|
8967
|
-
}
|
|
8968
|
-
return buffer.subarray(0, filled);
|
|
8969
|
-
}
|
|
8970
|
-
function readTail(file, offset) {
|
|
8971
|
-
let fd;
|
|
8972
|
-
try {
|
|
8973
|
-
fd = openSync(file, "r");
|
|
8974
|
-
} catch (error) {
|
|
8975
|
-
if (errorCode(error) === "ENOENT")
|
|
8976
|
-
return "absent";
|
|
8977
|
-
throw error;
|
|
8978
|
-
}
|
|
8979
|
-
try {
|
|
8980
|
-
return bytesFrom(fd, offset);
|
|
8981
|
-
} finally {
|
|
8982
|
-
closeSync(fd);
|
|
8983
|
-
}
|
|
8984
|
-
}
|
|
8985
|
-
function readEventsFrom(file, offset) {
|
|
8986
|
-
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
8987
|
-
throw new RangeError(`events file: unusable offset ${String(offset)}`);
|
|
8988
|
-
}
|
|
8989
|
-
const tail = readTail(file, offset);
|
|
8990
|
-
if (tail === "absent")
|
|
8991
|
-
return Object.freeze({ kind: "absent" });
|
|
8992
|
-
const end = tail?.lastIndexOf(NEWLINE) ?? -1;
|
|
8993
|
-
if (tail === null || end < 0) {
|
|
8994
|
-
return Object.freeze({ kind: "read", events: Object.freeze([]), malformed: 0, offset });
|
|
8995
|
-
}
|
|
8996
|
-
const lines = tail.subarray(0, end).toString("utf8").split(`
|
|
8997
|
-
`);
|
|
8998
|
-
const parsed = lines.map(parseEventLine);
|
|
8999
|
-
const events = parsed.filter((event) => event !== null);
|
|
9000
|
-
return Object.freeze({
|
|
9001
|
-
kind: "read",
|
|
9002
|
-
events: Object.freeze(events),
|
|
9003
|
-
malformed: parsed.length - events.length,
|
|
9004
|
-
offset: offset + end + 1
|
|
9005
|
-
});
|
|
9006
|
-
}
|
|
9007
|
-
|
|
9008
9007
|
// src/stretch/items.ts
|
|
9009
9008
|
import { appendFileSync, mkdirSync as mkdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
9010
9009
|
import { dirname as dirname3, join as join17 } from "path";
|
|
@@ -10127,7 +10126,7 @@ async function spawnOnTerminal(argv, cwd, env) {
|
|
|
10127
10126
|
function spawnDetached(argv, cwd, env, logPath) {
|
|
10128
10127
|
let fd;
|
|
10129
10128
|
try {
|
|
10130
|
-
fd =
|
|
10129
|
+
fd = openSync(logPath, "a");
|
|
10131
10130
|
} catch {
|
|
10132
10131
|
return null;
|
|
10133
10132
|
}
|
|
@@ -10138,7 +10137,7 @@ function spawnDetached(argv, cwd, env, logPath) {
|
|
|
10138
10137
|
} catch {
|
|
10139
10138
|
return null;
|
|
10140
10139
|
} finally {
|
|
10141
|
-
|
|
10140
|
+
closeSync(fd);
|
|
10142
10141
|
}
|
|
10143
10142
|
}
|
|
10144
10143
|
function defaultStretchItemSeams() {
|
|
@@ -10614,7 +10613,7 @@ async function runDoctor(context, seams) {
|
|
|
10614
10613
|
const modeSet = preflight.resolved.sources.boardRelationships !== "default";
|
|
10615
10614
|
const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue, preflight.config.boardRelationships, modeSet);
|
|
10616
10615
|
const { boardProjectNumber: number, boardProjectTemplate: template } = preflight.config;
|
|
10617
|
-
const projectRows = await readDoctorProject({ gh, number, template });
|
|
10616
|
+
const projectRows = await readDoctorProject({ gh, number, template, config: preflight.config, retry: commandRetrySeams(context.output, context.outputMode) });
|
|
10618
10617
|
const readings = { ...board, project: projectRows, cleanup, refs, release, tiers, effortSchema, effortSync };
|
|
10619
10618
|
writeText(context, renderDoctor(preflight));
|
|
10620
10619
|
await announceRisk(context, preflight, seams);
|
|
@@ -12002,7 +12001,8 @@ async function refreshProjectAfterEpic(context, seams, target) {
|
|
|
12002
12001
|
});
|
|
12003
12002
|
if (config.boardProjectNumber === null)
|
|
12004
12003
|
return null;
|
|
12005
|
-
const
|
|
12004
|
+
const retry = commandRetrySeams(context.output, context.outputMode, seams.sleep);
|
|
12005
|
+
const openGh = () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, retry);
|
|
12006
12006
|
warnings = await refreshLines(config, openGh, seams.projectRefresh ?? refreshIssueItems, target, named2);
|
|
12007
12007
|
} catch (error) {
|
|
12008
12008
|
warnings = [epicProjectProblemLine(named2, messageOf(error))];
|
|
@@ -13091,48 +13091,7 @@ var cancel_default = createEpicCancelCommand();
|
|
|
13091
13091
|
|
|
13092
13092
|
// src/commands/epic/close.ts
|
|
13093
13093
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
13094
|
-
import { join as
|
|
13095
|
-
|
|
13096
|
-
// src/board/epic-template.ts
|
|
13097
|
-
import { existsSync as existsSync8, readFileSync as readFileSync18 } from "fs";
|
|
13098
|
-
import { dirname as dirname8, join as join25 } from "path";
|
|
13099
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13100
|
-
var MODULE_DIR2 = dirname8(fileURLToPath2(import.meta.url));
|
|
13101
|
-
var EPIC_TEMPLATE_FILE = "epic.md";
|
|
13102
|
-
var CRITERIA_PLACEHOLDERS = Object.freeze([
|
|
13103
|
-
"- Placeholder: one line per thing that makes this epic finished," + " written so a check against main can tell whether it holds."
|
|
13104
|
-
]);
|
|
13105
|
-
var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
|
|
13106
|
-
function epicTemplateSource(moduleDir = MODULE_DIR2) {
|
|
13107
|
-
return join25(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
|
|
13108
|
-
}
|
|
13109
|
-
function readEpicTemplate(moduleDir = MODULE_DIR2) {
|
|
13110
|
-
const source = epicTemplateSource(moduleDir);
|
|
13111
|
-
if (!existsSync8(source)) {
|
|
13112
|
-
throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
|
|
13113
|
-
}
|
|
13114
|
-
return readFileSync18(source, "utf8");
|
|
13115
|
-
}
|
|
13116
|
-
function placeholderCriteria(criteria) {
|
|
13117
|
-
if (criteria === null)
|
|
13118
|
-
return Object.freeze([]);
|
|
13119
|
-
const held = criteria.split(/\r\n?|\n/u).map((line) => line.trim()).filter((line) => CRITERIA_PLACEHOLDERS.includes(line));
|
|
13120
|
-
return Object.freeze(held.map((text) => Object.freeze({ text, reason: PLACEHOLDER_REASON })));
|
|
13121
|
-
}
|
|
13122
|
-
function renderEpicBody(template = readEpicTemplate()) {
|
|
13123
|
-
const read = readEpicBody(template);
|
|
13124
|
-
if (read.criteria === null) {
|
|
13125
|
-
throw new Error("epic template: the template carries no acceptance criteria section with a placeholder under it");
|
|
13126
|
-
}
|
|
13127
|
-
const missing = CRITERIA_PLACEHOLDERS.length - placeholderCriteria(read.criteria).length;
|
|
13128
|
-
if (missing > 0) {
|
|
13129
|
-
throw new Error(`epic template: the acceptance criteria section has lost ${String(missing)} of its placeholder lines`);
|
|
13130
|
-
}
|
|
13131
|
-
if (read.lines.length > 0) {
|
|
13132
|
-
throw new Error(`epic template: the template carries ${String(read.lines.length)} checklist lines; a new epic has none`);
|
|
13133
|
-
}
|
|
13134
|
-
return template;
|
|
13135
|
-
}
|
|
13094
|
+
import { join as join26 } from "path";
|
|
13136
13095
|
|
|
13137
13096
|
// src/effort/epic-cost.ts
|
|
13138
13097
|
var MEMBERSHIP_NOTE = "cost follows membership at close time: it counts the sessions of the issues in the epic now, " + "so work on an issue moved out is not counted and work on an issue moved in is";
|
|
@@ -13201,211 +13160,9 @@ function renderEpicCost(cost, estimate) {
|
|
|
13201
13160
|
];
|
|
13202
13161
|
}
|
|
13203
13162
|
|
|
13204
|
-
// src/epic/verify-plan.ts
|
|
13205
|
-
import { existsSync as existsSync9, readFileSync as readFileSync19 } from "fs";
|
|
13206
|
-
import { dirname as dirname9, join as join26 } from "path";
|
|
13207
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13208
|
-
var MODULE_DIR3 = dirname9(fileURLToPath3(import.meta.url));
|
|
13209
|
-
var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
|
|
13210
|
-
var VERIFY_BLOCK_FENCE = "rafa:verify";
|
|
13211
|
-
var VERIFY_PROMPT_SLOTS = ["EPIC_NUMBER", "EPIC_TITLE", "CRITERIA"];
|
|
13212
|
-
var SKIPPED_REASON = "the verification plan did not answer it";
|
|
13213
|
-
var FENCE2 = /^\s*(?:`{3,}|~{3,})/u;
|
|
13214
|
-
var LIST_ITEM = /^( {0,3})(?:[-*+]|\d{1,9}[.)])(?:\s|$)/u;
|
|
13215
|
-
var INDENTED = /^\s/u;
|
|
13216
|
-
var MIN_FENCE_LENGTH2 = 3;
|
|
13217
|
-
var VERIFY_KIND = VERIFY_BLOCK_FENCE.slice("rafa:".length);
|
|
13218
|
-
var SLOT_PATTERN = new RegExp(`\\{(${VERIFY_PROMPT_SLOTS.join("|")})\\}`, "gu");
|
|
13219
|
-
var ANSWER_KEYS = ["check", "uncheckable"];
|
|
13220
|
-
function opensCriterion(text, state) {
|
|
13221
|
-
const item = LIST_ITEM.exec(text);
|
|
13222
|
-
if (item !== null) {
|
|
13223
|
-
const indent = item[1]?.length ?? 0;
|
|
13224
|
-
state.topIndent ??= indent;
|
|
13225
|
-
return indent <= state.topIndent;
|
|
13226
|
-
}
|
|
13227
|
-
return state.afterBlank && !INDENTED.test(text);
|
|
13228
|
-
}
|
|
13229
|
-
function splitCriteria(criteria) {
|
|
13230
|
-
if (criteria === null)
|
|
13231
|
-
return Object.freeze([]);
|
|
13232
|
-
const gathered = [];
|
|
13233
|
-
const state = { fenced: false, afterBlank: false, topIndent: null };
|
|
13234
|
-
for (const text of criteria.split(/\r\n?|\n/u)) {
|
|
13235
|
-
const insideFence = state.fenced;
|
|
13236
|
-
if (FENCE2.test(text))
|
|
13237
|
-
state.fenced = !state.fenced;
|
|
13238
|
-
if (!insideFence && text.trim() === "") {
|
|
13239
|
-
state.afterBlank = true;
|
|
13240
|
-
gathered.at(-1)?.push(text);
|
|
13241
|
-
continue;
|
|
13242
|
-
}
|
|
13243
|
-
const current = gathered.at(-1);
|
|
13244
|
-
const opens = !insideFence && opensCriterion(text, state);
|
|
13245
|
-
if (current === undefined || opens) {
|
|
13246
|
-
gathered.push([text]);
|
|
13247
|
-
} else {
|
|
13248
|
-
current.push(text);
|
|
13249
|
-
}
|
|
13250
|
-
state.afterBlank = false;
|
|
13251
|
-
}
|
|
13252
|
-
const numbered = gathered.map((lines, index) => {
|
|
13253
|
-
const text = lines.join(`
|
|
13254
|
-
`).trim();
|
|
13255
|
-
return Object.freeze({ number: index + 1, text, placeholder: CRITERIA_PLACEHOLDERS.includes(text) });
|
|
13256
|
-
});
|
|
13257
|
-
return Object.freeze(numbered);
|
|
13258
|
-
}
|
|
13259
|
-
function criteriaToAsk(criteria) {
|
|
13260
|
-
return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
|
|
13261
|
-
}
|
|
13262
|
-
function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
|
|
13263
|
-
return [join26(moduleDir, VERIFY_PROMPT_FILE), join26(dirname9(moduleDir), VERIFY_PROMPT_FILE)];
|
|
13264
|
-
}
|
|
13265
|
-
function readVerifyPrompt(moduleDir = MODULE_DIR3) {
|
|
13266
|
-
const candidates = verifyPromptCandidates(moduleDir);
|
|
13267
|
-
const found = candidates.find((candidate) => existsSync9(candidate));
|
|
13268
|
-
if (found === undefined) {
|
|
13269
|
-
throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
|
|
13270
|
-
}
|
|
13271
|
-
return readFileSync19(found, "utf8");
|
|
13272
|
-
}
|
|
13273
|
-
function fenceFor2(text) {
|
|
13274
|
-
const longestRun = Math.max(0, ...(text.match(/`+/gu) ?? []).map((run) => run.length));
|
|
13275
|
-
return "`".repeat(Math.max(MIN_FENCE_LENGTH2, longestRun + 1));
|
|
13276
|
-
}
|
|
13277
|
-
function renderCriterion(criterion) {
|
|
13278
|
-
const fence = fenceFor2(criterion.text);
|
|
13279
|
-
return [`### Criterion ${String(criterion.number)}`, "", `${fence}text`, criterion.text, fence].join(`
|
|
13280
|
-
`);
|
|
13281
|
-
}
|
|
13282
|
-
function buildVerifyPrompt(template, input) {
|
|
13283
|
-
const missing = VERIFY_PROMPT_SLOTS.find((slot) => !template.includes(`{${slot}}`));
|
|
13284
|
-
if (missing !== undefined) {
|
|
13285
|
-
throw new Error(`epic verify prompt: the template carries no {${missing}} slot`);
|
|
13286
|
-
}
|
|
13287
|
-
const values = {
|
|
13288
|
-
EPIC_NUMBER: String(input.epic),
|
|
13289
|
-
EPIC_TITLE: input.title.replace(/\s+/gu, " ").trim(),
|
|
13290
|
-
CRITERIA: criteriaToAsk(input.criteria).map(renderCriterion).join(`
|
|
13291
|
-
|
|
13292
|
-
`)
|
|
13293
|
-
};
|
|
13294
|
-
return template.replace(SLOT_PATTERN, (_whole, slot) => values[slot]);
|
|
13295
|
-
}
|
|
13296
|
-
function isMapping3(value) {
|
|
13297
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13298
|
-
}
|
|
13299
|
-
function fieldOf2(mapping, key) {
|
|
13300
|
-
return Object.entries(mapping).find(([own]) => own === key)?.[1];
|
|
13301
|
-
}
|
|
13302
|
-
function describeValue3(value) {
|
|
13303
|
-
if (typeof value === "string")
|
|
13304
|
-
return JSON.stringify(value);
|
|
13305
|
-
if (value === null || value === undefined)
|
|
13306
|
-
return "nothing";
|
|
13307
|
-
if (Array.isArray(value))
|
|
13308
|
-
return "a list";
|
|
13309
|
-
if (typeof value === "object")
|
|
13310
|
-
return "a mapping";
|
|
13311
|
-
return `the ${typeof value} ${String(value)}`;
|
|
13312
|
-
}
|
|
13313
|
-
function messageOf4(error) {
|
|
13314
|
-
return error instanceof Error ? error.message : String(error);
|
|
13315
|
-
}
|
|
13316
|
-
function absent2(reason, block, text) {
|
|
13317
|
-
return Object.freeze({ present: false, reason, block, text });
|
|
13318
|
-
}
|
|
13319
|
-
function readEntry(item, field, asked) {
|
|
13320
|
-
if (!isMapping3(item))
|
|
13321
|
-
return { problem: `${field} is ${describeValue3(item)}, not a mapping`, number: null };
|
|
13322
|
-
const number = fieldOf2(item, "criterion");
|
|
13323
|
-
if (typeof number !== "number" || !Number.isInteger(number) || number < 1) {
|
|
13324
|
-
return { problem: `${field}.criterion is ${describeValue3(number)}, not a criterion number from 1`, number: null };
|
|
13325
|
-
}
|
|
13326
|
-
if (!asked.has(number)) {
|
|
13327
|
-
return { problem: `${field}.criterion ${String(number)} is no criterion the plan was asked about`, number: null };
|
|
13328
|
-
}
|
|
13329
|
-
const carried = ANSWER_KEYS.filter((key2) => fieldOf2(item, key2) !== undefined);
|
|
13330
|
-
const [key] = carried;
|
|
13331
|
-
if (carried.length !== 1 || key === undefined) {
|
|
13332
|
-
const count = carried.length === 0 ? "neither" : "both";
|
|
13333
|
-
return { problem: `${field} carries ${count} check and uncheckable`, number };
|
|
13334
|
-
}
|
|
13335
|
-
const value = fieldOf2(item, key);
|
|
13336
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
13337
|
-
return { problem: `${field}.${key} is ${describeValue3(value)}, not a non-blank string`, number };
|
|
13338
|
-
}
|
|
13339
|
-
return { answer: { number, key, text: value.trim() } };
|
|
13340
|
-
}
|
|
13341
|
-
function verdictFor(criterion, answer, problem) {
|
|
13342
|
-
if (criterion.placeholder) {
|
|
13343
|
-
return { kind: "uncheckable", criterion, reason: PLACEHOLDER_REASON, source: "placeholder" };
|
|
13344
|
-
}
|
|
13345
|
-
if (answer?.key === "check")
|
|
13346
|
-
return { kind: "check", criterion, check: answer.text };
|
|
13347
|
-
if (answer?.key === "uncheckable")
|
|
13348
|
-
return { kind: "uncheckable", criterion, reason: answer.text, source: "answer" };
|
|
13349
|
-
const reason = problem === undefined ? SKIPPED_REASON : `${SKIPPED_REASON}: its entry was dropped, as ${problem}`;
|
|
13350
|
-
return { kind: "uncheckable", criterion, reason, source: "skipped" };
|
|
13351
|
-
}
|
|
13352
|
-
function readVerdicts(items, criteria, block) {
|
|
13353
|
-
const asked = new Set(criteriaToAsk(criteria).map((criterion) => criterion.number));
|
|
13354
|
-
const answers = new Map;
|
|
13355
|
-
const problems = new Map;
|
|
13356
|
-
const issues = [];
|
|
13357
|
-
for (const [index, item] of items.entries()) {
|
|
13358
|
-
const field = `criteria[${String(index)}]`;
|
|
13359
|
-
const reading = readEntry(item, field, asked);
|
|
13360
|
-
if ("problem" in reading) {
|
|
13361
|
-
issues.push(Object.freeze({ field, text: `${reading.problem}; dropped` }));
|
|
13362
|
-
if (reading.number !== null && !problems.has(reading.number))
|
|
13363
|
-
problems.set(reading.number, reading.problem);
|
|
13364
|
-
continue;
|
|
13365
|
-
}
|
|
13366
|
-
if (answers.has(reading.answer.number)) {
|
|
13367
|
-
const text = `${field} answers criterion ${String(reading.answer.number)} a second time; dropped`;
|
|
13368
|
-
issues.push(Object.freeze({ field, text }));
|
|
13369
|
-
continue;
|
|
13370
|
-
}
|
|
13371
|
-
answers.set(reading.answer.number, reading.answer);
|
|
13372
|
-
}
|
|
13373
|
-
const verdicts = criteria.map((criterion) => Object.freeze(verdictFor(criterion, answers.get(criterion.number), problems.get(criterion.number))));
|
|
13374
|
-
return Object.freeze({
|
|
13375
|
-
present: true,
|
|
13376
|
-
verdicts: Object.freeze(verdicts),
|
|
13377
|
-
block,
|
|
13378
|
-
issues: Object.freeze(issues)
|
|
13379
|
-
});
|
|
13380
|
-
}
|
|
13381
|
-
function parseVerifyPlan(output, criteria) {
|
|
13382
|
-
const block = readRafaBlocks(output).filter((each) => each.kind === VERIFY_KIND).at(-1);
|
|
13383
|
-
if (block === undefined) {
|
|
13384
|
-
return absent2("no-block", null, `the session output holds no ${VERIFY_BLOCK_FENCE} block`);
|
|
13385
|
-
}
|
|
13386
|
-
const at = `${VERIFY_BLOCK_FENCE} block at line ${String(block.span.first)}`;
|
|
13387
|
-
if (!block.closed) {
|
|
13388
|
-
return absent2("unclosed-block", block, `${at} is never closed, so it is not read`);
|
|
13389
|
-
}
|
|
13390
|
-
let document;
|
|
13391
|
-
try {
|
|
13392
|
-
document = Bun.YAML.parse(block.body);
|
|
13393
|
-
} catch (error) {
|
|
13394
|
-
return absent2("malformed-block", block, `${at} is not valid YAML (${messageOf4(error)})`);
|
|
13395
|
-
}
|
|
13396
|
-
if (!isMapping3(document)) {
|
|
13397
|
-
return absent2("malformed-block", block, `${at} holds ${describeValue3(document)}, not a mapping`);
|
|
13398
|
-
}
|
|
13399
|
-
const listed2 = fieldOf2(document, "criteria") ?? [];
|
|
13400
|
-
if (!Array.isArray(listed2)) {
|
|
13401
|
-
return absent2("malformed-block", block, `${at} has criteria ${describeValue3(listed2)}, not a list`);
|
|
13402
|
-
}
|
|
13403
|
-
return readVerdicts(listed2, criteria, block);
|
|
13404
|
-
}
|
|
13405
|
-
|
|
13406
13163
|
// src/epic/verify-run.ts
|
|
13407
13164
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13408
|
-
import { join as
|
|
13165
|
+
import { join as join25 } from "path";
|
|
13409
13166
|
var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
|
|
13410
13167
|
var DEFAULT_VERIFY_REMOTE = "origin";
|
|
13411
13168
|
var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
|
|
@@ -13414,7 +13171,7 @@ var CHECK_RESULTS = ["pass", "fail"];
|
|
|
13414
13171
|
var WORKTREE_PREFIX = "epic-";
|
|
13415
13172
|
var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
|
|
13416
13173
|
function verifyWorktreePath(home, epic) {
|
|
13417
|
-
return
|
|
13174
|
+
return join25(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${String(epic)}`);
|
|
13418
13175
|
}
|
|
13419
13176
|
function fetchStep(remote) {
|
|
13420
13177
|
return { id: "fetch", args: ["fetch", remote] };
|
|
@@ -13445,7 +13202,7 @@ function checkFlags(sessionId) {
|
|
|
13445
13202
|
return [SESSION_ID_FLAG, sessionId, "--tools", CHECK_TOOLS.join(",")];
|
|
13446
13203
|
}
|
|
13447
13204
|
function renderCheckPrompt(input) {
|
|
13448
|
-
const fence =
|
|
13205
|
+
const fence = fenceFor(input.check);
|
|
13449
13206
|
return [
|
|
13450
13207
|
CHECK_PROMPT_PREFIX,
|
|
13451
13208
|
"",
|
|
@@ -13486,13 +13243,13 @@ function renderCheckPrompt(input) {
|
|
|
13486
13243
|
].join(`
|
|
13487
13244
|
`);
|
|
13488
13245
|
}
|
|
13489
|
-
function
|
|
13246
|
+
function isMapping3(value) {
|
|
13490
13247
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13491
13248
|
}
|
|
13492
|
-
function
|
|
13249
|
+
function fieldOf2(mapping, key) {
|
|
13493
13250
|
return Object.entries(mapping).find(([own]) => own === key)?.[1];
|
|
13494
13251
|
}
|
|
13495
|
-
function
|
|
13252
|
+
function describeValue3(value) {
|
|
13496
13253
|
if (typeof value === "string")
|
|
13497
13254
|
return JSON.stringify(value);
|
|
13498
13255
|
if (value === null || value === undefined)
|
|
@@ -13506,7 +13263,7 @@ function describeValue4(value) {
|
|
|
13506
13263
|
function isResultWord(value) {
|
|
13507
13264
|
return CHECK_RESULTS.some((word) => word === value);
|
|
13508
13265
|
}
|
|
13509
|
-
function
|
|
13266
|
+
function absent2(text, block) {
|
|
13510
13267
|
return Object.freeze({ present: false, text, block });
|
|
13511
13268
|
}
|
|
13512
13269
|
function readVerdictBody(block, at) {
|
|
@@ -13514,29 +13271,29 @@ function readVerdictBody(block, at) {
|
|
|
13514
13271
|
try {
|
|
13515
13272
|
document = Bun.YAML.parse(block.body);
|
|
13516
13273
|
} catch (error) {
|
|
13517
|
-
return
|
|
13274
|
+
return absent2(`${at} is not valid YAML (${messageOf(error)})`, block);
|
|
13518
13275
|
}
|
|
13519
|
-
if (!
|
|
13520
|
-
return
|
|
13276
|
+
if (!isMapping3(document)) {
|
|
13277
|
+
return absent2(`${at} holds ${describeValue3(document)}, not a mapping`, block);
|
|
13521
13278
|
}
|
|
13522
|
-
const result2 =
|
|
13279
|
+
const result2 = fieldOf2(document, "result");
|
|
13523
13280
|
if (!isResultWord(result2)) {
|
|
13524
|
-
return
|
|
13281
|
+
return absent2(`${at} has result ${describeValue3(result2)}, not ${CHECK_RESULTS.join(" or ")}`, block);
|
|
13525
13282
|
}
|
|
13526
|
-
const evidence =
|
|
13283
|
+
const evidence = fieldOf2(document, "evidence");
|
|
13527
13284
|
if (typeof evidence !== "string" || evidence.trim() === "") {
|
|
13528
|
-
return
|
|
13285
|
+
return absent2(`${at} has evidence ${describeValue3(evidence)}, not a non-blank string`, block);
|
|
13529
13286
|
}
|
|
13530
13287
|
return Object.freeze({ present: true, result: result2, evidence: evidence.trim(), block });
|
|
13531
13288
|
}
|
|
13532
13289
|
function parseCheckVerdict(output) {
|
|
13533
13290
|
const block = readRafaBlocks(output).filter((each) => each.kind === VERDICT_KIND).at(-1);
|
|
13534
13291
|
if (block === undefined) {
|
|
13535
|
-
return
|
|
13292
|
+
return absent2(`the session output holds no ${VERDICT_BLOCK_FENCE} block`, null);
|
|
13536
13293
|
}
|
|
13537
13294
|
const at = `${VERDICT_BLOCK_FENCE} block at line ${String(block.span.first)}`;
|
|
13538
13295
|
if (!block.closed)
|
|
13539
|
-
return
|
|
13296
|
+
return absent2(`${at} is never closed, so it is not read`, block);
|
|
13540
13297
|
return readVerdictBody(block, at);
|
|
13541
13298
|
}
|
|
13542
13299
|
function readCheck(check, sessionId, session) {
|
|
@@ -13696,7 +13453,7 @@ async function planVerification(input) {
|
|
|
13696
13453
|
return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
|
|
13697
13454
|
}
|
|
13698
13455
|
function closeTriageFile(root, epic) {
|
|
13699
|
-
return
|
|
13456
|
+
return join26(root, ".rafa", `epic-${String(epic)}-close`);
|
|
13700
13457
|
}
|
|
13701
13458
|
function failedCheckArtifact(epic, criterion) {
|
|
13702
13459
|
return `epic ${ref7(epic)} acceptance criterion: ${oneLine3(criterion.text)}`;
|
|
@@ -14299,13 +14056,13 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
14299
14056
|
var promote_default = createEpicPromoteCommand();
|
|
14300
14057
|
|
|
14301
14058
|
// src/commands/init.ts
|
|
14302
|
-
import { existsSync as
|
|
14059
|
+
import { existsSync as existsSync10, readFileSync as readFileSync21 } from "fs";
|
|
14303
14060
|
import { homedir as homedir3 } from "os";
|
|
14304
|
-
import { join as
|
|
14061
|
+
import { join as join30, relative as relative6, sep as sep3 } from "path";
|
|
14305
14062
|
|
|
14306
14063
|
// src/agents/vendorable.ts
|
|
14307
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
14308
|
-
import { isAbsolute as isAbsolute3, join as
|
|
14064
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync18 } from "fs";
|
|
14065
|
+
import { isAbsolute as isAbsolute3, join as join27 } from "path";
|
|
14309
14066
|
function planFiles(dir) {
|
|
14310
14067
|
let entries;
|
|
14311
14068
|
try {
|
|
@@ -14317,7 +14074,7 @@ function planFiles(dir) {
|
|
|
14317
14074
|
}
|
|
14318
14075
|
function readText3(path) {
|
|
14319
14076
|
try {
|
|
14320
|
-
return
|
|
14077
|
+
return readFileSync18(path, "utf8");
|
|
14321
14078
|
} catch {
|
|
14322
14079
|
return null;
|
|
14323
14080
|
}
|
|
@@ -14327,7 +14084,7 @@ function rafaHolds(roster, name2) {
|
|
|
14327
14084
|
return item?.holders.some((holder) => holder.source === "rafa") === true;
|
|
14328
14085
|
}
|
|
14329
14086
|
function vendorableAgents(scan) {
|
|
14330
|
-
const dir = isAbsolute3(scan.planDir) ? scan.planDir :
|
|
14087
|
+
const dir = isAbsolute3(scan.planDir) ? scan.planDir : join27(scan.repoRoot, scan.planDir);
|
|
14331
14088
|
const roots = {
|
|
14332
14089
|
repoRoot: scan.repoRoot,
|
|
14333
14090
|
home: scan.home,
|
|
@@ -14336,7 +14093,7 @@ function vendorableAgents(scan) {
|
|
|
14336
14093
|
const roster = resolveAgentRoster(roots, scan.settings);
|
|
14337
14094
|
const found = [];
|
|
14338
14095
|
for (const file of planFiles(dir)) {
|
|
14339
|
-
const path =
|
|
14096
|
+
const path = join27(dir, file);
|
|
14340
14097
|
const markdown = readText3(path);
|
|
14341
14098
|
if (markdown === null)
|
|
14342
14099
|
continue;
|
|
@@ -14362,13 +14119,13 @@ function vendorableAgentWarnings(agents, root) {
|
|
|
14362
14119
|
|
|
14363
14120
|
// src/project/gitignore.ts
|
|
14364
14121
|
import { createHash } from "crypto";
|
|
14365
|
-
import { existsSync as
|
|
14366
|
-
import { dirname as
|
|
14122
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
|
|
14123
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join28 } from "path";
|
|
14367
14124
|
var GITIGNORE_FILE = ".gitignore";
|
|
14368
14125
|
var DIGEST_NAME = "tracking.digest";
|
|
14369
14126
|
var PRIVATE_TRIAGE = "triage/private";
|
|
14370
14127
|
var CACHE_DIR = "cache";
|
|
14371
|
-
var TRACKING_DIGEST_FILE =
|
|
14128
|
+
var TRACKING_DIGEST_FILE = join28(SCOPE_DIR, DIGEST_NAME);
|
|
14372
14129
|
var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
|
|
14373
14130
|
var BLOCK_END = "# <<< rafa tracking";
|
|
14374
14131
|
var SUB_PATHS = [
|
|
@@ -14457,13 +14214,13 @@ function attempt2(file, failure3, act) {
|
|
|
14457
14214
|
}
|
|
14458
14215
|
}
|
|
14459
14216
|
function readIfPresent2(file) {
|
|
14460
|
-
if (!
|
|
14217
|
+
if (!existsSync8(file))
|
|
14461
14218
|
return null;
|
|
14462
|
-
return attempt2(file, "cannot be read", () =>
|
|
14219
|
+
return attempt2(file, "cannot be read", () => readFileSync19(file, "utf8"));
|
|
14463
14220
|
}
|
|
14464
14221
|
function writeTrackingGitignore(root, flags) {
|
|
14465
14222
|
requireAbsolute(root);
|
|
14466
|
-
const file =
|
|
14223
|
+
const file = join28(root, GITIGNORE_FILE);
|
|
14467
14224
|
const text = readIfPresent2(file);
|
|
14468
14225
|
const next = withTrackingBlock(text, flags, file);
|
|
14469
14226
|
if (next === text)
|
|
@@ -14485,7 +14242,7 @@ function trackingDigest(flags) {
|
|
|
14485
14242
|
}
|
|
14486
14243
|
function exposureNotice(root) {
|
|
14487
14244
|
return [
|
|
14488
|
-
`tracking.all is true, so ${
|
|
14245
|
+
`tracking.all is true, so ${join28(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
|
|
14489
14246
|
" - unpatched issues described in specs and plans",
|
|
14490
14247
|
" - effort rows with model names and token counts",
|
|
14491
14248
|
" - instincts that may quote error output",
|
|
@@ -14498,7 +14255,7 @@ function printWarning(message) {
|
|
|
14498
14255
|
}
|
|
14499
14256
|
function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
14500
14257
|
requireAbsolute(root);
|
|
14501
|
-
const digestFile =
|
|
14258
|
+
const digestFile = join28(root, TRACKING_DIGEST_FILE);
|
|
14502
14259
|
const recorded = `${trackingDigest(flags)}
|
|
14503
14260
|
`;
|
|
14504
14261
|
if (readIfPresent2(digestFile) === recorded) {
|
|
@@ -14507,7 +14264,7 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
|
14507
14264
|
if (flags.trackingAll)
|
|
14508
14265
|
print2(exposureNotice(root));
|
|
14509
14266
|
attempt2(digestFile, "cannot be written", () => {
|
|
14510
|
-
mkdirSync7(
|
|
14267
|
+
mkdirSync7(dirname8(digestFile), { recursive: true });
|
|
14511
14268
|
writeFileSync6(digestFile, recorded);
|
|
14512
14269
|
});
|
|
14513
14270
|
return { digestFile, changed: true, printed: flags.trackingAll };
|
|
@@ -14597,14 +14354,14 @@ async function promptForRoot(found, prompter, seams) {
|
|
|
14597
14354
|
}
|
|
14598
14355
|
|
|
14599
14356
|
// src/project/scaffold.ts
|
|
14600
|
-
import { existsSync as
|
|
14601
|
-
import { join as
|
|
14357
|
+
import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync8, statSync as statSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
14358
|
+
import { join as join29 } from "path";
|
|
14602
14359
|
|
|
14603
14360
|
// src/commands/init-release.ts
|
|
14604
14361
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
14605
14362
|
|
|
14606
14363
|
// src/release/setting.ts
|
|
14607
|
-
import { readFileSync as
|
|
14364
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
14608
14365
|
var RELEASE_ENABLED_SETTING = "release.enabled";
|
|
14609
14366
|
function releaseEnabledIn(text, path) {
|
|
14610
14367
|
return parseConfigText(text, path).values.releaseEnabled ?? null;
|
|
@@ -14613,7 +14370,7 @@ function readReleaseSetting(root) {
|
|
|
14613
14370
|
const path = configFilePath(root);
|
|
14614
14371
|
let text;
|
|
14615
14372
|
try {
|
|
14616
|
-
text =
|
|
14373
|
+
text = readFileSync20(path, "utf8");
|
|
14617
14374
|
} catch (error) {
|
|
14618
14375
|
return { enabled: null, text: "", problem: `${path} could not be read: ${messageOf(error)}` };
|
|
14619
14376
|
}
|
|
@@ -14832,6 +14589,13 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
14832
14589
|
"# loop:",
|
|
14833
14590
|
`# settingSources: ${CONFIG_DEFAULTS.settingSources.join(",")} # a comma-separated subset of user, project, local`,
|
|
14834
14591
|
`# worktreeDir: ${CONFIG_DEFAULTS.loopWorktreeDir} # where a loop adds its worktrees, read from the project root`,
|
|
14592
|
+
`# retries: ${String(CONFIG_DEFAULTS.loopRetries)} # 1..3 | false, times in a row a run re-enters its loop after a retry-safe stop`,
|
|
14593
|
+
`# retriesOnContinue: ${String(CONFIG_DEFAULTS.loopRetriesOnContinue)} # 1..3 | false, the retries a --continue run opens when --retry is not typed`,
|
|
14594
|
+
"# continue:",
|
|
14595
|
+
`# criteria: ${CONFIG_DEFAULTS.loopContinueCriteria} # the project's own criteria for a --continue decision`,
|
|
14596
|
+
`# criteriaMode: ${CONFIG_DEFAULTS.loopContinueCriteriaMode} # extend | replace, how the project criteria meet the bundled base`,
|
|
14597
|
+
"# forceWrapUp:",
|
|
14598
|
+
`# maxNewFailures: ${String(CONFIG_DEFAULTS.loopForceWrapUpMaxNewFailures)} # 1..50 | false, new suite failures --force-wrap-up tolerates`,
|
|
14835
14599
|
"# wrapUp:",
|
|
14836
14600
|
`# retries: ${String(CONFIG_DEFAULTS.loopWrapUpRetries)} # 1..3 | false, wrap-up sessions rerun before rafa opens a missing PR itself`,
|
|
14837
14601
|
"# pr:",
|
|
@@ -14846,6 +14610,11 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
14846
14610
|
"# project:",
|
|
14847
14611
|
`# template: ${CONFIG_DEFAULTS.boardProjectTemplate} # the project URL rafa init --board --project copies`,
|
|
14848
14612
|
"# number: # the project rafa mirrors the board to; unset until rafa init --board --project",
|
|
14613
|
+
`# retries: ${String(CONFIG_DEFAULTS.boardProjectRetries)} # 1..10 | false, tries again after a failed project call`,
|
|
14614
|
+
`# retryWaitSeconds: ${String(CONFIG_DEFAULTS.boardProjectRetryWaitSeconds)} # 1..60, the first wait before a retry; each later one doubles`,
|
|
14615
|
+
`# progressSeconds: ${String(CONFIG_DEFAULTS.boardProjectProgressSeconds)} # 1..300 | false, the most seconds between two progress lines`,
|
|
14616
|
+
`# writeBatchSize: ${String(CONFIG_DEFAULTS.boardProjectWriteBatchSize)} # 1..100, field writes per request`,
|
|
14617
|
+
`# writePauseMs: ${String(CONFIG_DEFAULTS.boardProjectWritePauseMs)} # 0..60000, the pause between two write requests`,
|
|
14849
14618
|
"# roadmap:",
|
|
14850
14619
|
"# issue: # the issue plan create --next reads; unset is the one titled Roadmap",
|
|
14851
14620
|
"# claims:",
|
|
@@ -14922,7 +14691,7 @@ class ScaffoldError extends Error {
|
|
|
14922
14691
|
}
|
|
14923
14692
|
function layoutOf2(base, tree) {
|
|
14924
14693
|
const scope = scopeAt(base);
|
|
14925
|
-
return { dirs: [scope.dir, ...tree.map((name2) =>
|
|
14694
|
+
return { dirs: [scope.dir, ...tree.map((name2) => join29(scope.dir, name2))], configFile: configFilePath(base) };
|
|
14926
14695
|
}
|
|
14927
14696
|
function occupied(path) {
|
|
14928
14697
|
return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
@@ -14933,7 +14702,7 @@ function isDirectory2(path) {
|
|
|
14933
14702
|
function directoryConflict(dir) {
|
|
14934
14703
|
if (!occupied(dir) || isDirectory2(dir))
|
|
14935
14704
|
return null;
|
|
14936
|
-
return
|
|
14705
|
+
return existsSync9(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
|
|
14937
14706
|
}
|
|
14938
14707
|
function layoutConflicts(layout) {
|
|
14939
14708
|
const [scopeDir, ...tree] = layout.dirs;
|
|
@@ -14941,7 +14710,7 @@ function layoutConflicts(layout) {
|
|
|
14941
14710
|
if (opening !== null)
|
|
14942
14711
|
return [opening];
|
|
14943
14712
|
const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
|
|
14944
|
-
const file = occupied(layout.configFile) && !
|
|
14713
|
+
const file = occupied(layout.configFile) && !existsSync9(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
|
|
14945
14714
|
return [...dirs, ...file];
|
|
14946
14715
|
}
|
|
14947
14716
|
function scaffoldConflicts(root, home) {
|
|
@@ -14955,7 +14724,7 @@ function attempt3(path, failure3, act) {
|
|
|
14955
14724
|
}
|
|
14956
14725
|
}
|
|
14957
14726
|
function ensureDirectory(path) {
|
|
14958
|
-
if (
|
|
14727
|
+
if (existsSync9(path))
|
|
14959
14728
|
return { path, kind: "directory", change: "unchanged" };
|
|
14960
14729
|
attempt3(path, "cannot be created", () => {
|
|
14961
14730
|
mkdirSync8(path, { recursive: true });
|
|
@@ -14963,7 +14732,7 @@ function ensureDirectory(path) {
|
|
|
14963
14732
|
return { path, kind: "directory", change: "created" };
|
|
14964
14733
|
}
|
|
14965
14734
|
function ensureFile(path, text) {
|
|
14966
|
-
if (
|
|
14735
|
+
if (existsSync9(path))
|
|
14967
14736
|
return { path, kind: "file", change: "unchanged" };
|
|
14968
14737
|
attempt3(path, "cannot be written", () => {
|
|
14969
14738
|
writeFileSync8(path, text, { encoding: "utf8", flag: "wx" });
|
|
@@ -15108,9 +14877,9 @@ function resolvedConfig2(root, home, warn3) {
|
|
|
15108
14877
|
}
|
|
15109
14878
|
}
|
|
15110
14879
|
function checkGitignore(root, flags) {
|
|
15111
|
-
const file =
|
|
14880
|
+
const file = join30(root, GITIGNORE_FILE);
|
|
15112
14881
|
try {
|
|
15113
|
-
const text =
|
|
14882
|
+
const text = existsSync10(file) ? readFileSync21(file, "utf8") : null;
|
|
15114
14883
|
withTrackingBlock(text, flags, file);
|
|
15115
14884
|
} catch (error) {
|
|
15116
14885
|
const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
|
|
@@ -15142,8 +14911,8 @@ function initialise(root, start2, home, context, entry) {
|
|
|
15142
14911
|
const resolved = resolvedConfig2(root.path, home, warn3);
|
|
15143
14912
|
const config = resolved.config;
|
|
15144
14913
|
checkGitignore(root.path, config);
|
|
15145
|
-
const configExisted =
|
|
15146
|
-
const digestExisted =
|
|
14914
|
+
const configExisted = existsSync10(configFilePath(root.path));
|
|
14915
|
+
const digestExisted = existsSync10(join30(root.path, TRACKING_DIGEST_FILE));
|
|
15147
14916
|
const project = writeProjectScope(root.path);
|
|
15148
14917
|
const tracking = applyTracking(root.path, config, warn3);
|
|
15149
14918
|
const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
|
|
@@ -15248,7 +15017,9 @@ async function runInit(context, seams) {
|
|
|
15248
15017
|
config: scopes.config,
|
|
15249
15018
|
openGh: () => seams.gh(scopes.written.root),
|
|
15250
15019
|
isTerminal: seams.isTerminal,
|
|
15251
|
-
openPrompter: seams.openPrompter
|
|
15020
|
+
openPrompter: seams.openPrompter,
|
|
15021
|
+
...commandRetrySeams(context.output, context.outputMode, seams.sleep),
|
|
15022
|
+
progress: commandProgressFeed(context.output, context.outputMode, scopes.config.boardProjectProgressSeconds, seams.now === undefined ? {} : { now: seams.now })
|
|
15252
15023
|
});
|
|
15253
15024
|
const result2 = {
|
|
15254
15025
|
...scopes.written,
|
|
@@ -15285,7 +15056,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
15285
15056
|
subject: "init",
|
|
15286
15057
|
action: "init",
|
|
15287
15058
|
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.",
|
|
15059
|
+
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
15060
|
args: [],
|
|
15290
15061
|
flags: [
|
|
15291
15062
|
{
|
|
@@ -15310,7 +15081,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
|
|
|
15310
15081
|
},
|
|
15311
15082
|
{
|
|
15312
15083
|
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.",
|
|
15084
|
+
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
15085
|
type: "boolean"
|
|
15315
15086
|
},
|
|
15316
15087
|
{
|
|
@@ -16027,8 +15798,8 @@ function createInstinctShowCommand() {
|
|
|
16027
15798
|
var show_default3 = createInstinctShowCommand();
|
|
16028
15799
|
|
|
16029
15800
|
// src/commands/issue/check.ts
|
|
16030
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
16031
|
-
import { join as
|
|
15801
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync22, statSync as statSync8 } from "fs";
|
|
15802
|
+
import { join as join31, resolve as resolve9 } from "path";
|
|
16032
15803
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
16033
15804
|
var STAMP_FLAG = "stamp";
|
|
16034
15805
|
var ISSUE_NUMBER8 = /^[1-9]\d*$/u;
|
|
@@ -16064,7 +15835,7 @@ function copyNames(dir, issue) {
|
|
|
16064
15835
|
}
|
|
16065
15836
|
const prefix = `${boardId(issue)}-`;
|
|
16066
15837
|
const notes = notesFileName(issue);
|
|
16067
|
-
return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync8(
|
|
15838
|
+
return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync8(join31(dir, name2)).isFile()).sort();
|
|
16068
15839
|
}
|
|
16069
15840
|
function findSavedCopy(root, specsDir, issue) {
|
|
16070
15841
|
const names = copyNames(resolve9(root, specsDir), issue);
|
|
@@ -16074,11 +15845,11 @@ function findSavedCopy(root, specsDir, issue) {
|
|
|
16074
15845
|
if (names.length > 1) {
|
|
16075
15846
|
throw new CommandExit(1, [
|
|
16076
15847
|
`\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
|
|
16077
|
-
...names.map((name2) => ` ${
|
|
15848
|
+
...names.map((name2) => ` ${join31(specsDir, name2)}`)
|
|
16078
15849
|
].join(`
|
|
16079
15850
|
`));
|
|
16080
15851
|
}
|
|
16081
|
-
const path =
|
|
15852
|
+
const path = join31(specsDir, only);
|
|
16082
15853
|
return { path, absolute: resolve9(root, path) };
|
|
16083
15854
|
}
|
|
16084
15855
|
function liveText(live) {
|
|
@@ -16127,7 +15898,7 @@ async function readReferences(absolute, issue, verify, stamp) {
|
|
|
16127
15898
|
return rows.map((row2) => checkedRef(row2, row2.stamp));
|
|
16128
15899
|
}
|
|
16129
15900
|
const once2 = memoiseVerifier(verify);
|
|
16130
|
-
const before = await readRefsText({ copy:
|
|
15901
|
+
const before = await readRefsText({ copy: readFileSync22(absolute, "utf8"), issue, verify: once2 });
|
|
16131
15902
|
const after = await restampCopyRefs({ path: absolute, issue, verify: once2 });
|
|
16132
15903
|
return before.rows.map((row2, index) => checkedRef(row2, after.rows[index]?.stamp ?? null));
|
|
16133
15904
|
}
|
|
@@ -16400,7 +16171,7 @@ async function addCreatedToProject(context, seams, ref8) {
|
|
|
16400
16171
|
});
|
|
16401
16172
|
const lines = await addAndRefreshIssue({
|
|
16402
16173
|
config,
|
|
16403
|
-
openGh: () => seams.gh ?? createGhRunner({ cwd: project.root })
|
|
16174
|
+
openGh: () => openProjectRunner(seams.gh ?? createGhRunner({ cwd: project.root }), config, commandRetrySeams(context.output, context.outputMode, seams.sleep))
|
|
16404
16175
|
}, issue);
|
|
16405
16176
|
for (const line of lines)
|
|
16406
16177
|
context.output.warn(line);
|
|
@@ -16480,8 +16251,8 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
16480
16251
|
var create_default = createIssueCreateCommand();
|
|
16481
16252
|
|
|
16482
16253
|
// src/commands/issue/edit-run.ts
|
|
16483
|
-
import { existsSync as
|
|
16484
|
-
import { join as
|
|
16254
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
16255
|
+
import { join as join33 } from "path";
|
|
16485
16256
|
|
|
16486
16257
|
// src/board/issue-edit-body.ts
|
|
16487
16258
|
var TRAILING_WHITESPACE = /\s+$/u;
|
|
@@ -16536,7 +16307,7 @@ function originalPartHolds(before, after, block) {
|
|
|
16536
16307
|
|
|
16537
16308
|
// src/board/issue-edit-state.ts
|
|
16538
16309
|
import { readdirSync as readdirSync6, statSync as statSync9 } from "fs";
|
|
16539
|
-
import { join as
|
|
16310
|
+
import { join as join32, resolve as resolve10 } from "path";
|
|
16540
16311
|
function isClaimed(labels) {
|
|
16541
16312
|
return labels.includes(CLAIMED_LABEL) || labels.includes(IN_DEVELOPMENT_LABEL);
|
|
16542
16313
|
}
|
|
@@ -16609,7 +16380,7 @@ function findSavedCopies(root, specsDir, issue) {
|
|
|
16609
16380
|
return [];
|
|
16610
16381
|
throw error;
|
|
16611
16382
|
}
|
|
16612
|
-
return names.filter((name2) => isSavedCopyName(name2, issue)).filter((name2) => statSync9(
|
|
16383
|
+
return names.filter((name2) => isSavedCopyName(name2, issue)).filter((name2) => statSync9(join32(dir, name2)).isFile()).sort().map((name2) => join32(specsDir, name2));
|
|
16613
16384
|
}
|
|
16614
16385
|
|
|
16615
16386
|
// src/board/issue-edit-text.ts
|
|
@@ -16633,7 +16404,7 @@ function requireEditText(input) {
|
|
|
16633
16404
|
var EDIT_USAGE = "rafa issue edit <n> (--append-file=<file> | --append=<text>) --reason=<text>" + " [--while-in-development] | (--replace-file=<file> [--title=<text>] | --title=<text>) [--dry-run]";
|
|
16634
16405
|
var EDIT_REFUSAL_EXIT = TRUST_REFUSAL_EXIT;
|
|
16635
16406
|
var EDIT_CONFLICT_EXIT = 1;
|
|
16636
|
-
var EDIT_SCRATCH_DIR =
|
|
16407
|
+
var EDIT_SCRATCH_DIR = join33(".rafa", "scratch");
|
|
16637
16408
|
var STDIN_PATH2 = "-";
|
|
16638
16409
|
var ISSUE_NUMBER9 = /^[1-9]\d*$/u;
|
|
16639
16410
|
var TRAILING_WHITESPACE2 = /\s+$/u;
|
|
@@ -16882,20 +16653,20 @@ function writeHeld(found, planned, reread) {
|
|
|
16882
16653
|
}
|
|
16883
16654
|
function conflictPaths(issue, stamp, attempt4) {
|
|
16884
16655
|
const suffix = attempt4 === 1 ? "" : `-${String(attempt4)}`;
|
|
16885
|
-
const name2 = (side) =>
|
|
16656
|
+
const name2 = (side) => join33(EDIT_SCRATCH_DIR, `${boardId(issue)}-edit-${side}-${stamp}${suffix}.md`);
|
|
16886
16657
|
return { before: name2("before"), after: name2("after") };
|
|
16887
16658
|
}
|
|
16888
16659
|
function saveConflictBodies(root, issue, now, before, after) {
|
|
16889
|
-
mkdirSync9(
|
|
16660
|
+
mkdirSync9(join33(root, EDIT_SCRATCH_DIR), { recursive: true });
|
|
16890
16661
|
const stamp = fileStamp(now);
|
|
16891
16662
|
let attempt4 = 1;
|
|
16892
16663
|
let paths = conflictPaths(issue, stamp, attempt4);
|
|
16893
|
-
while (
|
|
16664
|
+
while (existsSync11(join33(root, paths.before)) || existsSync11(join33(root, paths.after))) {
|
|
16894
16665
|
attempt4 += 1;
|
|
16895
16666
|
paths = conflictPaths(issue, stamp, attempt4);
|
|
16896
16667
|
}
|
|
16897
|
-
writeFileSync9(
|
|
16898
|
-
writeFileSync9(
|
|
16668
|
+
writeFileSync9(join33(root, paths.before), before, { flag: "wx" });
|
|
16669
|
+
writeFileSync9(join33(root, paths.after), after, { flag: "wx" });
|
|
16899
16670
|
return paths;
|
|
16900
16671
|
}
|
|
16901
16672
|
function heldOutcome(request, state) {
|
|
@@ -17421,7 +17192,7 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
17421
17192
|
subject: "loop",
|
|
17422
17193
|
action: "start",
|
|
17423
17194
|
summary: "run a plan task by task, then wrap up, open the PR and wait for CI",
|
|
17424
|
-
description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. `--as-worktree` runs the plan in a worktree of its own on" + " that branch instead, leaving the main checkout as it is, and is refused beside" + " `--create-branch`. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task, and under `--roadmap`" + " the hop away, when one is. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
|
|
17195
|
+
description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. `--as-worktree` runs the plan in a worktree of its own on" + " that branch instead, leaving the main checkout as it is, and is refused beside" + " `--create-branch`. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task, and under `--roadmap`" + " the hop away, when one is. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. With `--retry` or" + " `loop.retries` it goes back into its loop instead of stopping, at most that many times in a row," + " after a red suite step, a task session that exited nonzero, or one that left neither a report nor" + " a commit; a later task done, neither the stopped one nor a repair, starts the count over. With `--continue` a stop that would end the run is handed to a decision instead: retry the" + " task with a new approach, stop with exit code 20, jump over it or defer it until a later task is" + " done; a run left with only passed-over tasks ends with exit code 22 and no wrap-up unless" + " `--force-wrap-up`, and under `--output=json` a decision the line does not name ends the run with" + " exit code 21 and the decision prompt. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
|
|
17425
17196
|
args: [],
|
|
17426
17197
|
flags: [
|
|
17427
17198
|
{
|
|
@@ -17467,6 +17238,36 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
17467
17238
|
type: "number",
|
|
17468
17239
|
default: DEFAULT_CI_ATTEMPTS
|
|
17469
17240
|
},
|
|
17241
|
+
{
|
|
17242
|
+
name: "retry",
|
|
17243
|
+
description: "Goes back into the loop instead of stopping, at most this many times in a row, 1 to 3," + " after a red suite step, a task session that exited nonzero but not on its budget, or one that" + " left neither a report nor a commit. The count starts over once a later task is done, neither" + " the stopped one nor a repair. A report that blocks its task, a refused commit, a moved" + " checkout and an interrupt still stop the run. Outranks `loop.retries` in" + " `.rafa/config.yaml`, `false` unless the config names a count; any other value is refused" + " before the run starts.",
|
|
17244
|
+
type: "number"
|
|
17245
|
+
},
|
|
17246
|
+
{
|
|
17247
|
+
name: "continue",
|
|
17248
|
+
description: "Hands a stop that would end the run to a decision instead: a report that holds its task at" + " once, and a retry-safe stop once its retries are spent. One read-only decision session picks" + " `retry` (with a new approach), `stop`, `jump` (pass the task over this run) or `defer` (pass" + " it over until a later task is done), by the bundled criteria and `loop.continue.criteria`." + " The retries are `loop.retriesOnContinue` unless `--retry` names a count.",
|
|
17249
|
+
type: "boolean"
|
|
17250
|
+
},
|
|
17251
|
+
{
|
|
17252
|
+
name: "decide",
|
|
17253
|
+
description: "Names the decision on the line, `retry`, `stop`, `jump` or `defer`, applied once to the first" + " decision point the run meets, so no decision session is spawned for it. Needs `--continue`.",
|
|
17254
|
+
type: "string"
|
|
17255
|
+
},
|
|
17256
|
+
{
|
|
17257
|
+
name: "approach",
|
|
17258
|
+
description: "The new approach a `--decide=retry` hands the task's next session; required by it and" + " read beside nothing else.",
|
|
17259
|
+
type: "string"
|
|
17260
|
+
},
|
|
17261
|
+
{
|
|
17262
|
+
name: "after",
|
|
17263
|
+
description: "The tracker line, counted from 1, of the task a `--decide=defer` waits on; required by it" + " and read beside nothing else.",
|
|
17264
|
+
type: "number"
|
|
17265
|
+
},
|
|
17266
|
+
{
|
|
17267
|
+
name: "force-wrap-up",
|
|
17268
|
+
description: "Wraps up a `--continue` run left with only passed-over tasks anyway, as a draft pull request" + " listing them, once the pre-wrap-up suite step finds no more new failures than" + " `loop.forceWrapUp.maxNewFailures` tolerates. Needs `--continue`.",
|
|
17269
|
+
type: "boolean"
|
|
17270
|
+
},
|
|
17470
17271
|
{
|
|
17471
17272
|
name: "create-branch",
|
|
17472
17273
|
description: "On `main` or `master`, creates `feat/<plan-stub>` from the latest `origin/<base>`" + " and runs the plan there without asking, or switches to that branch when it already" + " exists. Refuses rather than move when a tracked file is modified, the fetch fails or the" + " base has diverged from its remote.",
|
|
@@ -17513,7 +17314,7 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
17513
17314
|
outputs: ["text", "json", "events"],
|
|
17514
17315
|
spends: {
|
|
17515
17316
|
when: "always",
|
|
17516
|
-
what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request,
|
|
17317
|
+
what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request," + " repair sessions while CI is red, the sessions of up to `--retry` (`loop.retries`) more passes of the loop" + " in a row after the stops it retries, and under `--continue` one read-only decision session per decision"
|
|
17517
17318
|
}
|
|
17518
17319
|
}, start);
|
|
17519
17320
|
var loopStart = Object.freeze({
|
|
@@ -18754,7 +18555,7 @@ var wrapped2 = wrapPhaseZeroCommand({
|
|
|
18754
18555
|
var create_default2 = endingWith(wrapped2);
|
|
18755
18556
|
|
|
18756
18557
|
// src/commands/plan/risk.ts
|
|
18757
|
-
import { readFileSync as
|
|
18558
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
18758
18559
|
import { resolve as resolve11 } from "path";
|
|
18759
18560
|
var COMMAND6 = "rafa plan risk";
|
|
18760
18561
|
var USAGE34 = "rafa plan risk [<plan>] [--strict]";
|
|
@@ -18782,7 +18583,7 @@ function strictRefusal(report3) {
|
|
|
18782
18583
|
}
|
|
18783
18584
|
async function readRisk(path, project, config, context, seams) {
|
|
18784
18585
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
18785
|
-
return assessPlanRisk({ path, text:
|
|
18586
|
+
return assessPlanRisk({ path, text: readFileSync23(path, "utf8") }, {
|
|
18786
18587
|
repoRoot: project.root,
|
|
18787
18588
|
home: project.home,
|
|
18788
18589
|
settingSources: config.settingSources,
|
|
@@ -18865,8 +18666,8 @@ function createPlanRiskCommand(seams = {}) {
|
|
|
18865
18666
|
var risk_default = createPlanRiskCommand();
|
|
18866
18667
|
|
|
18867
18668
|
// src/commands/plan/show.ts
|
|
18868
|
-
import { readFileSync as
|
|
18869
|
-
import { join as
|
|
18669
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
18670
|
+
import { join as join34 } from "path";
|
|
18870
18671
|
var USAGE35 = "rafa plan show <stub> [--tracker]";
|
|
18871
18672
|
var COMMAND7 = "rafa plan show";
|
|
18872
18673
|
function readTrackerFlag(value) {
|
|
@@ -18884,10 +18685,10 @@ function showPlan(plans, stub, tracker) {
|
|
|
18884
18685
|
throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
|
|
18885
18686
|
}
|
|
18886
18687
|
const name2 = planFileName(stub, tracker);
|
|
18887
|
-
const file =
|
|
18688
|
+
const file = join34(plans.path, name2);
|
|
18888
18689
|
if (!isFile(file))
|
|
18889
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
18890
|
-
const model = parsePlan(
|
|
18690
|
+
throw new CommandExit(1, missingText(stub, tracker, join34(plans.label, name2)));
|
|
18691
|
+
const model = parsePlan(readFileSync24(file, "utf8"));
|
|
18891
18692
|
const { header } = model;
|
|
18892
18693
|
return {
|
|
18893
18694
|
stub,
|
|
@@ -18910,7 +18711,7 @@ function taskLine(task) {
|
|
|
18910
18711
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
18911
18712
|
}
|
|
18912
18713
|
function renderShownPlan(shown, dirLabel) {
|
|
18913
|
-
const relative9 =
|
|
18714
|
+
const relative9 = join34(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
18914
18715
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
18915
18716
|
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative9}`];
|
|
18916
18717
|
if (shown.header.issue !== null)
|
|
@@ -19075,6 +18876,10 @@ function openPrContext(context, seams = DEFAULT_PR_SEAMS) {
|
|
|
19075
18876
|
versionGuard: mergeGuardSettings(config),
|
|
19076
18877
|
projectRefresh: {
|
|
19077
18878
|
boardProjectNumber: config.boardProjectNumber,
|
|
18879
|
+
boardProjectRetries: config.boardProjectRetries,
|
|
18880
|
+
boardProjectRetryWaitSeconds: config.boardProjectRetryWaitSeconds,
|
|
18881
|
+
boardProjectWriteBatchSize: config.boardProjectWriteBatchSize,
|
|
18882
|
+
boardProjectWritePauseMs: config.boardProjectWritePauseMs,
|
|
19078
18883
|
boardRelationships: config.boardRelationships,
|
|
19079
18884
|
roadmapIssue: config.roadmapIssue,
|
|
19080
18885
|
releaseFragments: config.releaseFragments
|
|
@@ -19879,7 +19684,7 @@ async function refreshProjectAfterMerge(options) {
|
|
|
19879
19684
|
const closed = [...new Set(closedIssuesIn(options.body))];
|
|
19880
19685
|
if (config.boardProjectNumber === null || closed.length === 0)
|
|
19881
19686
|
return null;
|
|
19882
|
-
const gh = options.openGh();
|
|
19687
|
+
const gh = openProjectRunner(options.openGh(), config, options.retry);
|
|
19883
19688
|
const wanted = new Set(closed);
|
|
19884
19689
|
const warnings = [];
|
|
19885
19690
|
let listed2 = [];
|
|
@@ -19902,7 +19707,7 @@ async function refreshProjectAfterMerge(options) {
|
|
|
19902
19707
|
|
|
19903
19708
|
// src/commands/pr/merge-loop-worktree.ts
|
|
19904
19709
|
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync10, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
19905
|
-
import { dirname as
|
|
19710
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join35, relative as relative9, resolve as resolve12, sep as sep4 } from "path";
|
|
19906
19711
|
var REFUSAL_PREFIX = "rafa pr merge refuses";
|
|
19907
19712
|
var INDENT4 = " ";
|
|
19908
19713
|
var LIVE_STATES = new Set(["running", "paused"]);
|
|
@@ -19912,7 +19717,7 @@ function defaultLoopWorktreeSeams(mainCheckout) {
|
|
|
19912
19717
|
gitAt: createGitRunner,
|
|
19913
19718
|
modifiedAt: (path) => statSync10(path, { throwIfNoEntry: false })?.mtime ?? null,
|
|
19914
19719
|
copyFile: (from, to) => {
|
|
19915
|
-
mkdirSync10(
|
|
19720
|
+
mkdirSync10(dirname9(to), { recursive: true });
|
|
19916
19721
|
copyFileSync3(from, to);
|
|
19917
19722
|
},
|
|
19918
19723
|
realPath: (path) => {
|
|
@@ -19947,7 +19752,7 @@ function freeLoopWorktree(seams, reading, holder) {
|
|
|
19947
19752
|
const files = [];
|
|
19948
19753
|
for (const name2 of planFileNames(holder.stub)) {
|
|
19949
19754
|
const from = resolve12(holder.path, reading.planDir, name2);
|
|
19950
|
-
const to =
|
|
19755
|
+
const to = join35(into, name2);
|
|
19951
19756
|
try {
|
|
19952
19757
|
files.push({ name: name2, from, to, outcome: copyPlanFile(seams, from, to) });
|
|
19953
19758
|
} catch (error) {
|
|
@@ -20202,7 +20007,7 @@ function workflowNamesBase(workflowText, base) {
|
|
|
20202
20007
|
return readWorkflow(document, base) !== false;
|
|
20203
20008
|
}
|
|
20204
20009
|
function readWorkflow(document, base) {
|
|
20205
|
-
if (!
|
|
20010
|
+
if (!isPlainObject(document) || !("on" in document))
|
|
20206
20011
|
return UNKNOWN;
|
|
20207
20012
|
const on = document.on;
|
|
20208
20013
|
if (typeof on === "string")
|
|
@@ -20212,7 +20017,7 @@ function readWorkflow(document, base) {
|
|
|
20212
20017
|
return UNKNOWN;
|
|
20213
20018
|
return on.includes(PULL_REQUEST_EVENT);
|
|
20214
20019
|
}
|
|
20215
|
-
if (!
|
|
20020
|
+
if (!isPlainObject(on))
|
|
20216
20021
|
return UNKNOWN;
|
|
20217
20022
|
if (!(PULL_REQUEST_EVENT in on))
|
|
20218
20023
|
return false;
|
|
@@ -20221,7 +20026,7 @@ function readWorkflow(document, base) {
|
|
|
20221
20026
|
function readPullRequestTrigger(trigger, base) {
|
|
20222
20027
|
if (trigger === null || trigger === undefined)
|
|
20223
20028
|
return true;
|
|
20224
|
-
if (!
|
|
20029
|
+
if (!isPlainObject(trigger))
|
|
20225
20030
|
return UNKNOWN;
|
|
20226
20031
|
const branches = trigger.branches;
|
|
20227
20032
|
const ignored = trigger["branches-ignore"];
|
|
@@ -20322,7 +20127,7 @@ function regexSourceOf(pattern) {
|
|
|
20322
20127
|
function escapeRegex(char) {
|
|
20323
20128
|
return char.replace(REGEX_SPECIAL, "\\$&");
|
|
20324
20129
|
}
|
|
20325
|
-
function
|
|
20130
|
+
function isPlainObject(value) {
|
|
20326
20131
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20327
20132
|
}
|
|
20328
20133
|
|
|
@@ -20646,7 +20451,14 @@ async function runMerge2(context, seams) {
|
|
|
20646
20451
|
const roadmapTicks = await reportTick(context, pr, seams, detail);
|
|
20647
20452
|
const steps = cleanUpAfterMerge(git, detail, { info: info2, warn: warn3 });
|
|
20648
20453
|
const board = await reportAfterCleanUp(context, pr, seams, detail);
|
|
20649
|
-
await refreshProjectAfterMerge({
|
|
20454
|
+
await refreshProjectAfterMerge({
|
|
20455
|
+
body: detail.body,
|
|
20456
|
+
config: pr.projectRefresh,
|
|
20457
|
+
openGh: () => openGh(pr, seams),
|
|
20458
|
+
unblocked: board.unblocked,
|
|
20459
|
+
warn: warn3,
|
|
20460
|
+
retry: commandRetrySeams(context.output, context.outputMode)
|
|
20461
|
+
});
|
|
20650
20462
|
const followUps = reportFollowUps({
|
|
20651
20463
|
root: pr.project.root,
|
|
20652
20464
|
home: pr.project.home,
|
|
@@ -20909,7 +20721,7 @@ var FOLLOW_UP_TASKS = Object.freeze({
|
|
|
20909
20721
|
"ci-test": "A test failed. Read the excerpt below, reproduce the named test" + " locally, and fix the implementation rather than the assertion unless the" + " assertion is itself wrong. Run the gates and push.",
|
|
20910
20722
|
"ci-other": "A step nothing recognised failed. Read the excerpt below, work out" + " from the step name what that job does, and fix it; there is no pinned plan" + " for this class, so nothing has been assumed about the fix."
|
|
20911
20723
|
});
|
|
20912
|
-
var
|
|
20724
|
+
var MIN_FENCE_LENGTH2 = 3;
|
|
20913
20725
|
function isUsableCap(value) {
|
|
20914
20726
|
return value !== undefined && Number.isInteger(value) && value > 0;
|
|
20915
20727
|
}
|
|
@@ -20924,7 +20736,7 @@ function excerptLines(evidence, maxLines) {
|
|
|
20924
20736
|
}
|
|
20925
20737
|
function fencedBlock(body, language) {
|
|
20926
20738
|
const runs = Array.from(body.matchAll(/`+/g), (run) => run[0].length);
|
|
20927
|
-
const fence = "`".repeat(Math.max(
|
|
20739
|
+
const fence = "`".repeat(Math.max(MIN_FENCE_LENGTH2, ...runs.map((length) => length + 1)));
|
|
20928
20740
|
const text = body.endsWith(`
|
|
20929
20741
|
`) || body === "" ? body : `${body}
|
|
20930
20742
|
`;
|
|
@@ -21024,7 +20836,7 @@ function findTriageComment(comments) {
|
|
|
21024
20836
|
function read(value) {
|
|
21025
20837
|
return { value, problem: null };
|
|
21026
20838
|
}
|
|
21027
|
-
function
|
|
20839
|
+
function absent3() {
|
|
21028
20840
|
return { value: null, problem: null };
|
|
21029
20841
|
}
|
|
21030
20842
|
function unusable(key, value, expected) {
|
|
@@ -21033,7 +20845,7 @@ function unusable(key, value, expected) {
|
|
|
21033
20845
|
function readText4(fields, key) {
|
|
21034
20846
|
const value = fields[key];
|
|
21035
20847
|
if (value === undefined)
|
|
21036
|
-
return
|
|
20848
|
+
return absent3();
|
|
21037
20849
|
if (typeof value !== "string")
|
|
21038
20850
|
return unusable(key, value, `text; write it quoted, as ${key}: "..."`);
|
|
21039
20851
|
const trimmed = value.trim();
|
|
@@ -21042,19 +20854,19 @@ function readText4(fields, key) {
|
|
|
21042
20854
|
function readBoolean(fields, key) {
|
|
21043
20855
|
const value = fields[key];
|
|
21044
20856
|
if (value === undefined)
|
|
21045
|
-
return
|
|
20857
|
+
return absent3();
|
|
21046
20858
|
return typeof value === "boolean" ? read(value) : unusable(key, value, "true or false");
|
|
21047
20859
|
}
|
|
21048
20860
|
function readCount(fields, key) {
|
|
21049
20861
|
const value = fields[key];
|
|
21050
20862
|
if (value === undefined)
|
|
21051
|
-
return
|
|
20863
|
+
return absent3();
|
|
21052
20864
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? read(value) : unusable(key, value, "a whole number from 0");
|
|
21053
20865
|
}
|
|
21054
20866
|
function readPaths(fields, key) {
|
|
21055
20867
|
const value = fields[key];
|
|
21056
20868
|
if (value === undefined)
|
|
21057
|
-
return
|
|
20869
|
+
return absent3();
|
|
21058
20870
|
if (!Array.isArray(value))
|
|
21059
20871
|
return unusable(key, value, "a list of quoted paths");
|
|
21060
20872
|
const paths = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
@@ -22254,10 +22066,10 @@ function withTaskBudget(plan2, usd) {
|
|
|
22254
22066
|
}
|
|
22255
22067
|
|
|
22256
22068
|
// src/pr/plans/load.ts
|
|
22257
|
-
import { existsSync as
|
|
22258
|
-
import { dirname as
|
|
22259
|
-
import { fileURLToPath as
|
|
22260
|
-
var
|
|
22069
|
+
import { existsSync as existsSync12, readFileSync as readFileSync25 } from "fs";
|
|
22070
|
+
import { dirname as dirname10, join as join36 } from "path";
|
|
22071
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22072
|
+
var MODULE_DIR2 = dirname10(fileURLToPath2(import.meta.url));
|
|
22261
22073
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
22262
22074
|
var PINNED_PLAN_CLASSES = Object.freeze([
|
|
22263
22075
|
...SIMPLE_TRIAGE_CLASSES,
|
|
@@ -22272,22 +22084,22 @@ function hasPinnedPlan(triageClass) {
|
|
|
22272
22084
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
22273
22085
|
const name2 = pinnedPlanFileName(triageClass);
|
|
22274
22086
|
return [
|
|
22275
|
-
|
|
22276
|
-
|
|
22087
|
+
join36(moduleDir, PINNED_PLANS_DIRNAME, name2),
|
|
22088
|
+
join36(moduleDir, name2)
|
|
22277
22089
|
];
|
|
22278
22090
|
}
|
|
22279
22091
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
22280
22092
|
if (!hasPinnedPlan(triageClass)) {
|
|
22281
22093
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
22282
22094
|
}
|
|
22283
|
-
const candidates = pinnedPlanCandidates(lookup.moduleDir ??
|
|
22284
|
-
const found = candidates.find((candidate) =>
|
|
22095
|
+
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR2, triageClass);
|
|
22096
|
+
const found = candidates.find((candidate) => existsSync12(candidate));
|
|
22285
22097
|
if (found === undefined) {
|
|
22286
22098
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
22287
22099
|
}
|
|
22288
|
-
return
|
|
22100
|
+
return readFileSync25(found, "utf8");
|
|
22289
22101
|
}
|
|
22290
|
-
var
|
|
22102
|
+
var SLOT_PATTERN = /\{[A-Z][A-Z0-9_]*\}/g;
|
|
22291
22103
|
var CONFLICT_SENTENCE_SLOT = "CONFLICT_SENTENCE";
|
|
22292
22104
|
var CONFLICT_FILES_SLOT = "CONFLICT_FILES";
|
|
22293
22105
|
var NO_CONFLICT_FILES = "the paths `git status` reports as conflicted";
|
|
@@ -22297,7 +22109,7 @@ var FAILING_LOG_EXCERPT_LINES = FOLLOW_UP_EXCERPT_LINES;
|
|
|
22297
22109
|
var NO_FAILING_LOG = "No failing-job log was carried into this plan. Read it yourself with" + " `gh run view <run-id> --log-failed` before changing anything, and say that" + " the excerpt was missing rather than guessing what failed.";
|
|
22298
22110
|
function fillPinnedPlan(template, values) {
|
|
22299
22111
|
const missing = [];
|
|
22300
|
-
const filled = template.replaceAll(
|
|
22112
|
+
const filled = template.replaceAll(SLOT_PATTERN, (token) => {
|
|
22301
22113
|
const name2 = token.slice(1, -1);
|
|
22302
22114
|
const value = values[name2];
|
|
22303
22115
|
if (value === undefined) {
|
|
@@ -22428,19 +22240,19 @@ function readAttemptRepeat(input) {
|
|
|
22428
22240
|
|
|
22429
22241
|
// src/commands/pr/resolve-loop.ts
|
|
22430
22242
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
22431
|
-
import { join as
|
|
22432
|
-
var RESOLVE_SUBDIR =
|
|
22243
|
+
import { join as join37 } from "path";
|
|
22244
|
+
var RESOLVE_SUBDIR = join37(".rafa", "resolve");
|
|
22433
22245
|
var RUN_PREFIX = "pr-";
|
|
22434
22246
|
var ATTEMPT_PREFIX = "attempt-";
|
|
22435
22247
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
22436
22248
|
var CHILD_OUTPUT = "text";
|
|
22437
22249
|
function resolveRunDir(home, number, attempt4) {
|
|
22438
|
-
return
|
|
22250
|
+
return join37(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt4}`);
|
|
22439
22251
|
}
|
|
22440
22252
|
function writeResolvePlan(write) {
|
|
22441
22253
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
22442
22254
|
mkdirSync11(dir, { recursive: true });
|
|
22443
|
-
const path =
|
|
22255
|
+
const path = join37(dir, pinnedPlanFileName(write.triageClass));
|
|
22444
22256
|
writeFileSync10(path, write.plan, "utf8");
|
|
22445
22257
|
return path;
|
|
22446
22258
|
}
|
|
@@ -22481,8 +22293,8 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
22481
22293
|
}
|
|
22482
22294
|
|
|
22483
22295
|
// src/pr/triage/version-convert.ts
|
|
22484
|
-
import { mkdirSync as mkdirSync12, readFileSync as
|
|
22485
|
-
import { dirname as
|
|
22296
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
|
|
22297
|
+
import { dirname as dirname11, join as join38, posix as posix2 } from "path";
|
|
22486
22298
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
22487
22299
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
22488
22300
|
var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
|
|
@@ -22594,7 +22406,7 @@ function planConversion(input, problems) {
|
|
|
22594
22406
|
}
|
|
22595
22407
|
let branchVersionText;
|
|
22596
22408
|
try {
|
|
22597
|
-
branchVersionText =
|
|
22409
|
+
branchVersionText = readFileSync26(join38(input.worktree, settings.releaseVersionFile), "utf8");
|
|
22598
22410
|
} catch (error) {
|
|
22599
22411
|
return refused2(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
22600
22412
|
}
|
|
@@ -22620,9 +22432,9 @@ function writeConversion(input, plan2) {
|
|
|
22620
22432
|
[input.settings.releaseChangelog, plan2.changelogText]
|
|
22621
22433
|
];
|
|
22622
22434
|
for (const [path, text] of writes) {
|
|
22623
|
-
const target =
|
|
22435
|
+
const target = join38(input.worktree, path);
|
|
22624
22436
|
try {
|
|
22625
|
-
mkdirSync12(
|
|
22437
|
+
mkdirSync12(dirname11(target), { recursive: true });
|
|
22626
22438
|
writeFileSync11(target, text);
|
|
22627
22439
|
} catch (error) {
|
|
22628
22440
|
return `${path} could not be written in the worktree, which may hold part of the conversion: ${messageOf(error)}`;
|
|
@@ -23621,9 +23433,9 @@ function tagSettle(worktree, mode, delivered) {
|
|
|
23621
23433
|
}
|
|
23622
23434
|
|
|
23623
23435
|
// src/release/settle-worktree.ts
|
|
23624
|
-
import { existsSync as
|
|
23436
|
+
import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, realpathSync as realpathSync4, rmSync as rmSync3 } from "fs";
|
|
23625
23437
|
import { tmpdir as tmpdir2 } from "os";
|
|
23626
|
-
import { join as
|
|
23438
|
+
import { join as join39 } from "path";
|
|
23627
23439
|
var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
|
|
23628
23440
|
var WORKTREE_DIRECTORY = "base";
|
|
23629
23441
|
function removeScratch(git, scratch, path) {
|
|
@@ -23640,14 +23452,14 @@ function removeScratch(git, scratch, path) {
|
|
|
23640
23452
|
problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
|
|
23641
23453
|
}
|
|
23642
23454
|
}
|
|
23643
|
-
if (problems.length === 0 &&
|
|
23455
|
+
if (problems.length === 0 && existsSync13(scratch)) {
|
|
23644
23456
|
problems.push(`the scratch directory ${scratch} is still there`);
|
|
23645
23457
|
}
|
|
23646
23458
|
return problems.length === 0 ? null : problems.join("; ");
|
|
23647
23459
|
}
|
|
23648
23460
|
function makeScratch(root) {
|
|
23649
23461
|
try {
|
|
23650
|
-
return realpathSync4(mkdtempSync2(
|
|
23462
|
+
return realpathSync4(mkdtempSync2(join39(root, SETTLE_SCRATCH_PREFIX)));
|
|
23651
23463
|
} catch (error) {
|
|
23652
23464
|
return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
|
|
23653
23465
|
}
|
|
@@ -23669,7 +23481,7 @@ function addWorktree(options) {
|
|
|
23669
23481
|
const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
|
|
23670
23482
|
if (typeof scratch !== "string")
|
|
23671
23483
|
return scratch;
|
|
23672
|
-
const path =
|
|
23484
|
+
const path = join39(scratch, WORKTREE_DIRECTORY);
|
|
23673
23485
|
const added = git(["worktree", "add", "--detach", path, commit]);
|
|
23674
23486
|
if (!added.ok) {
|
|
23675
23487
|
return {
|
|
@@ -23822,7 +23634,7 @@ async function refreshProjectAfterSettle(options) {
|
|
|
23822
23634
|
const commits = releasedCommits(options.delivered);
|
|
23823
23635
|
if (config.boardProjectNumber === null || commits === null || commits.length === 0)
|
|
23824
23636
|
return null;
|
|
23825
|
-
const gh = options.openGh();
|
|
23637
|
+
const gh = openProjectRunner(options.openGh(), config, options.retry);
|
|
23826
23638
|
let pulls;
|
|
23827
23639
|
try {
|
|
23828
23640
|
pulls = await readPulls(gh, commits, branch);
|
|
@@ -23998,7 +23810,8 @@ async function runSettle(context, seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
23998
23810
|
branch,
|
|
23999
23811
|
config,
|
|
24000
23812
|
openGh: () => (seams.gh ?? ((cwd) => createGhRunner({ cwd })))(project.root),
|
|
24001
|
-
refresh: seams.refresh
|
|
23813
|
+
refresh: seams.refresh,
|
|
23814
|
+
retry: commandRetrySeams(context.output, context.outputMode)
|
|
24002
23815
|
});
|
|
24003
23816
|
return { ...ran.value, project: refreshed };
|
|
24004
23817
|
}
|
|
@@ -24057,8 +23870,8 @@ function createReleaseSettleCommand(seams = DEFAULT_RELEASE_SETTLE_SEAMS) {
|
|
|
24057
23870
|
var settle_default = createReleaseSettleCommand();
|
|
24058
23871
|
|
|
24059
23872
|
// src/commands/release/tag.ts
|
|
24060
|
-
import { readFileSync as
|
|
24061
|
-
import { join as
|
|
23873
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
23874
|
+
import { join as join40 } from "path";
|
|
24062
23875
|
|
|
24063
23876
|
// src/release/tag-push.ts
|
|
24064
23877
|
var LOCAL_REMOTE = ".";
|
|
@@ -24134,13 +23947,13 @@ function readBranch2(git) {
|
|
|
24134
23947
|
}
|
|
24135
23948
|
function textOf(path) {
|
|
24136
23949
|
try {
|
|
24137
|
-
return
|
|
23950
|
+
return readFileSync27(path, "utf8");
|
|
24138
23951
|
} catch {
|
|
24139
23952
|
return null;
|
|
24140
23953
|
}
|
|
24141
23954
|
}
|
|
24142
23955
|
function readVersion(root, configured) {
|
|
24143
|
-
const resolved =
|
|
23956
|
+
const resolved = join40(root, configured);
|
|
24144
23957
|
const text = textOf(resolved);
|
|
24145
23958
|
if (text === null) {
|
|
24146
23959
|
return {
|
|
@@ -24154,7 +23967,7 @@ function readVersion(root, configured) {
|
|
|
24154
23967
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
24155
23968
|
}
|
|
24156
23969
|
function readNewestRelease(root, configured) {
|
|
24157
|
-
const resolved =
|
|
23970
|
+
const resolved = join40(root, configured);
|
|
24158
23971
|
const text = textOf(resolved);
|
|
24159
23972
|
if (text === null) {
|
|
24160
23973
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -24438,16 +24251,16 @@ var roadmap_default = createRoadmapCommand();
|
|
|
24438
24251
|
|
|
24439
24252
|
// src/commands/skill/backfill.ts
|
|
24440
24253
|
import { statSync as statSync12 } from "fs";
|
|
24441
|
-
import { join as
|
|
24254
|
+
import { join as join45, resolve as resolve15 } from "path";
|
|
24442
24255
|
|
|
24443
24256
|
// src/backfill/backup.ts
|
|
24444
|
-
import { copyFileSync as copyFileSync4, existsSync as
|
|
24445
|
-
import { dirname as
|
|
24257
|
+
import { copyFileSync as copyFileSync4, existsSync as existsSync14, mkdirSync as mkdirSync13 } from "fs";
|
|
24258
|
+
import { dirname as dirname12, isAbsolute as isAbsolute7, join as join42, relative as relative10, resolve as resolve13, sep as sep5 } from "path";
|
|
24446
24259
|
|
|
24447
24260
|
// src/backfill/proposal-file.ts
|
|
24448
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
24449
|
-
import { join as
|
|
24450
|
-
var BACKFILL_PATH =
|
|
24261
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync28 } from "fs";
|
|
24262
|
+
import { join as join41 } from "path";
|
|
24263
|
+
var BACKFILL_PATH = join41(".rafa", "backfill");
|
|
24451
24264
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
24452
24265
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
24453
24266
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -24473,7 +24286,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
24473
24286
|
].join(`
|
|
24474
24287
|
`);
|
|
24475
24288
|
function proposalPath(dir, batch) {
|
|
24476
|
-
return
|
|
24289
|
+
return join41(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
24477
24290
|
}
|
|
24478
24291
|
function isProposalFileName(name2) {
|
|
24479
24292
|
return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -24609,9 +24422,9 @@ function readProposalFiles(dir) {
|
|
|
24609
24422
|
return [];
|
|
24610
24423
|
}
|
|
24611
24424
|
return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
|
|
24612
|
-
const path =
|
|
24425
|
+
const path = join41(dir, name2);
|
|
24613
24426
|
try {
|
|
24614
|
-
return { path, result: parseProposalFile(
|
|
24427
|
+
return { path, result: parseProposalFile(readFileSync28(path, "utf8")) };
|
|
24615
24428
|
} catch (error) {
|
|
24616
24429
|
return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
|
|
24617
24430
|
}
|
|
@@ -24619,10 +24432,10 @@ function readProposalFiles(dir) {
|
|
|
24619
24432
|
}
|
|
24620
24433
|
|
|
24621
24434
|
// src/backfill/backup.ts
|
|
24622
|
-
var BACKUP_PATH =
|
|
24435
|
+
var BACKUP_PATH = join42(BACKFILL_PATH, "backup");
|
|
24623
24436
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
24624
24437
|
function backupDirectory(base) {
|
|
24625
|
-
return
|
|
24438
|
+
return join42(base, BACKUP_PATH);
|
|
24626
24439
|
}
|
|
24627
24440
|
function isUnder(root, path) {
|
|
24628
24441
|
const step = relative10(resolve13(root), resolve13(path));
|
|
@@ -24631,7 +24444,7 @@ function isUnder(root, path) {
|
|
|
24631
24444
|
function backupPathFor(path, root, backupDir) {
|
|
24632
24445
|
if (!isUnder(root, path))
|
|
24633
24446
|
return null;
|
|
24634
|
-
return
|
|
24447
|
+
return join42(backupDir, relative10(resolve13(root), resolve13(path)));
|
|
24635
24448
|
}
|
|
24636
24449
|
function backupFile(path, options) {
|
|
24637
24450
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -24640,11 +24453,11 @@ function backupFile(path, options) {
|
|
|
24640
24453
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
24641
24454
|
if (backup === null)
|
|
24642
24455
|
return { path, kind: "outside", backup: null };
|
|
24643
|
-
if (!
|
|
24456
|
+
if (!existsSync14(path))
|
|
24644
24457
|
return { path, kind: "missing", backup: null };
|
|
24645
|
-
if (
|
|
24458
|
+
if (existsSync14(backup))
|
|
24646
24459
|
return { path, kind: "kept", backup };
|
|
24647
|
-
mkdirSync13(
|
|
24460
|
+
mkdirSync13(dirname12(backup), { recursive: true });
|
|
24648
24461
|
copyFileSync4(path, backup);
|
|
24649
24462
|
return { path, kind: "copied", backup };
|
|
24650
24463
|
}
|
|
@@ -24667,7 +24480,7 @@ function countBackups(results) {
|
|
|
24667
24480
|
}
|
|
24668
24481
|
|
|
24669
24482
|
// src/backfill/derive.ts
|
|
24670
|
-
import { readFileSync as
|
|
24483
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync12 } from "fs";
|
|
24671
24484
|
import { basename as basename7 } from "path";
|
|
24672
24485
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
24673
24486
|
function renderWhenToUse(trigger, prevents) {
|
|
@@ -24793,7 +24606,7 @@ function planFile(entry, options) {
|
|
|
24793
24606
|
}
|
|
24794
24607
|
let original;
|
|
24795
24608
|
try {
|
|
24796
|
-
original =
|
|
24609
|
+
original = readFileSync29(entry.path, "utf8");
|
|
24797
24610
|
} catch (error) {
|
|
24798
24611
|
return inert(entry, "skipped", `the file cannot be read (${error instanceof Error ? error.message : String(error)})`);
|
|
24799
24612
|
}
|
|
@@ -24837,7 +24650,7 @@ function planDerivation(root, options) {
|
|
|
24837
24650
|
function applyFile(action, options) {
|
|
24838
24651
|
if (action.text === null || action.original === null)
|
|
24839
24652
|
return action;
|
|
24840
|
-
const current =
|
|
24653
|
+
const current = readFileSync29(action.path, "utf8");
|
|
24841
24654
|
if (current !== action.original) {
|
|
24842
24655
|
return {
|
|
24843
24656
|
...action,
|
|
@@ -24868,7 +24681,7 @@ function applyDerivation(plan2, options) {
|
|
|
24868
24681
|
}
|
|
24869
24682
|
|
|
24870
24683
|
// src/backfill/proposal-batch.ts
|
|
24871
|
-
import { readFileSync as
|
|
24684
|
+
import { readFileSync as readFileSync30 } from "fs";
|
|
24872
24685
|
import { relative as relative11 } from "path";
|
|
24873
24686
|
|
|
24874
24687
|
// src/demote/report.ts
|
|
@@ -25167,7 +24980,7 @@ function readCandidate(root, entry) {
|
|
|
25167
24980
|
return null;
|
|
25168
24981
|
let text;
|
|
25169
24982
|
try {
|
|
25170
|
-
text =
|
|
24983
|
+
text = readFileSync30(entry.path, "utf8");
|
|
25171
24984
|
} catch {
|
|
25172
24985
|
return null;
|
|
25173
24986
|
}
|
|
@@ -25273,8 +25086,8 @@ function parseSessionAnswer(stdout) {
|
|
|
25273
25086
|
}
|
|
25274
25087
|
|
|
25275
25088
|
// src/backfill/propose.ts
|
|
25276
|
-
import { mkdirSync as mkdirSync14, readFileSync as
|
|
25277
|
-
import { join as
|
|
25089
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync31, writeFileSync as writeFileSync13 } from "fs";
|
|
25090
|
+
import { join as join43, resolve as resolve14 } from "path";
|
|
25278
25091
|
var PROPOSAL_ACTION_KINDS = [
|
|
25279
25092
|
"applied",
|
|
25280
25093
|
"unchanged",
|
|
@@ -25423,7 +25236,7 @@ function writeRow(path, row2, original, options) {
|
|
|
25423
25236
|
};
|
|
25424
25237
|
}
|
|
25425
25238
|
function applyRow(root, row2, options) {
|
|
25426
|
-
const path =
|
|
25239
|
+
const path = join43(root, ...row2.path.split("/"));
|
|
25427
25240
|
if (row2.status === UNANSWERED_ROW) {
|
|
25428
25241
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
25429
25242
|
}
|
|
@@ -25432,7 +25245,7 @@ function applyRow(root, row2, options) {
|
|
|
25432
25245
|
}
|
|
25433
25246
|
let original;
|
|
25434
25247
|
try {
|
|
25435
|
-
original =
|
|
25248
|
+
original = readFileSync31(path, "utf8");
|
|
25436
25249
|
} catch {
|
|
25437
25250
|
return inert2(path, row2, "refused", "the file the row names is gone");
|
|
25438
25251
|
}
|
|
@@ -25460,7 +25273,7 @@ function applyProposals(files, root, options) {
|
|
|
25460
25273
|
for (const file of files) {
|
|
25461
25274
|
const refusal19 = fileRefusal(file, root);
|
|
25462
25275
|
for (const row2 of file.rows) {
|
|
25463
|
-
const action = refusal19 === null ? applyRow(root, row2, options) : inert2(
|
|
25276
|
+
const action = refusal19 === null ? applyRow(root, row2, options) : inert2(join43(root, ...row2.path.split("/")), row2, "refused", refusal19);
|
|
25464
25277
|
actions.push(action);
|
|
25465
25278
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
25466
25279
|
triggers[row2.name] = row2.trigger;
|
|
@@ -25471,13 +25284,13 @@ function applyProposals(files, root, options) {
|
|
|
25471
25284
|
}
|
|
25472
25285
|
|
|
25473
25286
|
// src/demote/select.ts
|
|
25474
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
25475
|
-
import { basename as basename8, dirname as
|
|
25287
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync32, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
|
|
25288
|
+
import { basename as basename8, dirname as dirname13, join as join44 } from "path";
|
|
25476
25289
|
var SKILLS_DIRECTORY = "skills";
|
|
25477
25290
|
var CLAUDE_DIRECTORY = ".claude";
|
|
25478
25291
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
25479
|
-
var DEMOTED_PATH =
|
|
25480
|
-
var INSTINCTS_PATH =
|
|
25292
|
+
var DEMOTED_PATH = join44(".rafa", "demoted");
|
|
25293
|
+
var INSTINCTS_PATH = join44(".rafa", "instincts");
|
|
25481
25294
|
var REPORT_FILE = "report.md";
|
|
25482
25295
|
function realOrSelf(path) {
|
|
25483
25296
|
try {
|
|
@@ -25496,18 +25309,18 @@ function samePath(left, right) {
|
|
|
25496
25309
|
function resolveDemotionScope(dir, home) {
|
|
25497
25310
|
if (basename8(dir) !== SKILLS_DIRECTORY)
|
|
25498
25311
|
return null;
|
|
25499
|
-
const claude =
|
|
25312
|
+
const claude = dirname13(dir);
|
|
25500
25313
|
if (basename8(claude) !== CLAUDE_DIRECTORY)
|
|
25501
25314
|
return null;
|
|
25502
|
-
const base =
|
|
25503
|
-
const demotedDir =
|
|
25315
|
+
const base = dirname13(claude);
|
|
25316
|
+
const demotedDir = join44(base, DEMOTED_PATH);
|
|
25504
25317
|
return {
|
|
25505
25318
|
scope: samePath(base, home) ? "user" : "project",
|
|
25506
25319
|
dir,
|
|
25507
25320
|
base,
|
|
25508
25321
|
demotedDir,
|
|
25509
|
-
instinctsDir:
|
|
25510
|
-
reportPath:
|
|
25322
|
+
instinctsDir: join44(base, INSTINCTS_PATH),
|
|
25323
|
+
reportPath: join44(demotedDir, REPORT_FILE)
|
|
25511
25324
|
};
|
|
25512
25325
|
}
|
|
25513
25326
|
function entryKind(path) {
|
|
@@ -25533,7 +25346,7 @@ function readSelected(absolute, path, entries) {
|
|
|
25533
25346
|
let text;
|
|
25534
25347
|
let mtime;
|
|
25535
25348
|
try {
|
|
25536
|
-
text =
|
|
25349
|
+
text = readFileSync32(absolute, "utf8");
|
|
25537
25350
|
mtime = statSync11(absolute).mtime.toISOString();
|
|
25538
25351
|
} catch {
|
|
25539
25352
|
return null;
|
|
@@ -25549,14 +25362,14 @@ function selectsLearned(text, scope) {
|
|
|
25549
25362
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
25550
25363
|
}
|
|
25551
25364
|
function selectLearned(scope) {
|
|
25552
|
-
const dir =
|
|
25365
|
+
const dir = join44(scope.dir, LEARNED_DIRECTORY);
|
|
25553
25366
|
if (entryKind(dir) !== "directory")
|
|
25554
25367
|
return [];
|
|
25555
25368
|
const selected = [];
|
|
25556
25369
|
for (const name2 of visibleNames(dir)) {
|
|
25557
25370
|
if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
|
|
25558
25371
|
continue;
|
|
25559
|
-
const absolute =
|
|
25372
|
+
const absolute = join44(dir, name2);
|
|
25560
25373
|
if (entryKind(absolute) !== "file")
|
|
25561
25374
|
continue;
|
|
25562
25375
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
|
|
@@ -25570,10 +25383,10 @@ function selectSkillFiles(scope) {
|
|
|
25570
25383
|
for (const name2 of visibleNames(scope.dir)) {
|
|
25571
25384
|
if (name2 === LEARNED_DIRECTORY)
|
|
25572
25385
|
continue;
|
|
25573
|
-
const skillDir =
|
|
25386
|
+
const skillDir = join44(scope.dir, name2);
|
|
25574
25387
|
if (entryKind(skillDir) !== "directory")
|
|
25575
25388
|
continue;
|
|
25576
|
-
const absolute =
|
|
25389
|
+
const absolute = join44(skillDir, SKILL_FILE);
|
|
25577
25390
|
if (entryKind(absolute) !== "file")
|
|
25578
25391
|
continue;
|
|
25579
25392
|
const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -25758,7 +25571,7 @@ function reviewedProposals(dir) {
|
|
|
25758
25571
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
25759
25572
|
}
|
|
25760
25573
|
function rowPaths(files, root) {
|
|
25761
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
25574
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join45(root, ...row2.path.split("/"))));
|
|
25762
25575
|
}
|
|
25763
25576
|
function runApply(context, scope, backfillDir, projectRoot2) {
|
|
25764
25577
|
const options = checkOptions(context, projectRoot2);
|
|
@@ -25804,7 +25617,7 @@ async function runBackfill(context, seams) {
|
|
|
25804
25617
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
25805
25618
|
const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
25806
25619
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
25807
|
-
const backfillDir =
|
|
25620
|
+
const backfillDir = join45(scope.base, BACKFILL_PATH);
|
|
25808
25621
|
if (mode === "propose") {
|
|
25809
25622
|
await runPropose(context, scope, backfillDir, seams);
|
|
25810
25623
|
return;
|
|
@@ -25918,13 +25731,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
25918
25731
|
var check_default3 = createSkillCheckCommand();
|
|
25919
25732
|
|
|
25920
25733
|
// src/commands/skill/demote.ts
|
|
25921
|
-
import { existsSync as
|
|
25922
|
-
import { dirname as
|
|
25734
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync16, readFileSync as readFileSync34, statSync as statSync14, writeFileSync as writeFileSync15 } from "fs";
|
|
25735
|
+
import { dirname as dirname15, resolve as resolve16 } from "path";
|
|
25923
25736
|
|
|
25924
25737
|
// src/demote/apply.ts
|
|
25925
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
25738
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync15, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readdirSync as readdirSync9, readFileSync as readFileSync33, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync13, unlinkSync, writeFileSync as writeFileSync14 } from "fs";
|
|
25926
25739
|
import { tmpdir as tmpdir3 } from "os";
|
|
25927
|
-
import { dirname as
|
|
25740
|
+
import { dirname as dirname14, join as join46 } from "path";
|
|
25928
25741
|
var DEMOTION_ACTION_KINDS = [
|
|
25929
25742
|
"demoted",
|
|
25930
25743
|
"kept",
|
|
@@ -25945,13 +25758,13 @@ function learnedSkillName(path) {
|
|
|
25945
25758
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
25946
25759
|
}
|
|
25947
25760
|
function sourcePath(scope, path) {
|
|
25948
|
-
return
|
|
25761
|
+
return join46(scope.dir, ...path.split("/"));
|
|
25949
25762
|
}
|
|
25950
25763
|
function demotedPath(scope, path) {
|
|
25951
|
-
return
|
|
25764
|
+
return join46(scope.demotedDir, ...path.split("/"));
|
|
25952
25765
|
}
|
|
25953
25766
|
function promotedPath(scope, path) {
|
|
25954
|
-
return
|
|
25767
|
+
return join46(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
25955
25768
|
}
|
|
25956
25769
|
function takenInstinctIds(dir) {
|
|
25957
25770
|
let names;
|
|
@@ -25964,7 +25777,7 @@ function takenInstinctIds(dir) {
|
|
|
25964
25777
|
}
|
|
25965
25778
|
function readText5(path) {
|
|
25966
25779
|
try {
|
|
25967
|
-
return statSync13(path).isFile() ?
|
|
25780
|
+
return statSync13(path).isFile() ? readFileSync33(path, "utf8") : null;
|
|
25968
25781
|
} catch {
|
|
25969
25782
|
return null;
|
|
25970
25783
|
}
|
|
@@ -25992,7 +25805,7 @@ function planKept(scope, row2, file) {
|
|
|
25992
25805
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
25993
25806
|
}
|
|
25994
25807
|
const to = promotedPath(scope, row2.path);
|
|
25995
|
-
if (
|
|
25808
|
+
if (existsSync15(to) || existsSync15(dirname14(to))) {
|
|
25996
25809
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
25997
25810
|
}
|
|
25998
25811
|
return {
|
|
@@ -26006,7 +25819,7 @@ function planKept(scope, row2, file) {
|
|
|
26006
25819
|
};
|
|
26007
25820
|
}
|
|
26008
25821
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
26009
|
-
const path =
|
|
25822
|
+
const path = join46(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
26010
25823
|
writeFileSync14(path, text, "utf8");
|
|
26011
25824
|
const report5 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
26012
25825
|
return failureLines2(report5.issues);
|
|
@@ -26027,11 +25840,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
26027
25840
|
if (failures.length > 0) {
|
|
26028
25841
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
26029
25842
|
}
|
|
26030
|
-
const destination =
|
|
25843
|
+
const destination = join46(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
26031
25844
|
const kept = demotedPath(scope, row2.path);
|
|
26032
|
-
if (
|
|
25845
|
+
if (existsSync15(destination))
|
|
26033
25846
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
26034
|
-
if (
|
|
25847
|
+
if (existsSync15(kept))
|
|
26035
25848
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
26036
25849
|
context.taken.push(id2);
|
|
26037
25850
|
return {
|
|
@@ -26074,7 +25887,7 @@ function countActions(actions) {
|
|
|
26074
25887
|
return counts;
|
|
26075
25888
|
}
|
|
26076
25889
|
function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
26077
|
-
const temp = mkdtempSync3(
|
|
25890
|
+
const temp = mkdtempSync3(join46(tmpdir3(), "rafa-demote-"));
|
|
26078
25891
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
26079
25892
|
try {
|
|
26080
25893
|
const actions = report5.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
@@ -26084,7 +25897,7 @@ function planDemotion(report5, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
|
26084
25897
|
}
|
|
26085
25898
|
}
|
|
26086
25899
|
function moveFile(move3) {
|
|
26087
|
-
mkdirSync15(
|
|
25900
|
+
mkdirSync15(dirname14(move3.to), { recursive: true });
|
|
26088
25901
|
try {
|
|
26089
25902
|
renameSync2(move3.from, move3.to);
|
|
26090
25903
|
} catch {
|
|
@@ -26095,7 +25908,7 @@ function moveFile(move3) {
|
|
|
26095
25908
|
function applyDemotion(plan2) {
|
|
26096
25909
|
for (const action of plan2.actions) {
|
|
26097
25910
|
if (action.write !== null) {
|
|
26098
|
-
mkdirSync15(
|
|
25911
|
+
mkdirSync15(dirname14(action.write.path), { recursive: true });
|
|
26099
25912
|
writeFileSync14(action.write.path, action.write.text, "utf8");
|
|
26100
25913
|
}
|
|
26101
25914
|
if (action.move !== null)
|
|
@@ -26193,9 +26006,9 @@ function verdictLine2(report5) {
|
|
|
26193
26006
|
return `${String(report5.rows.length)} file(s): ${parts.join(", ")}`;
|
|
26194
26007
|
}
|
|
26195
26008
|
function previousReport(path, context) {
|
|
26196
|
-
if (!
|
|
26009
|
+
if (!existsSync16(path))
|
|
26197
26010
|
return null;
|
|
26198
|
-
const parsed = parseDemotionReport(
|
|
26011
|
+
const parsed = parseDemotionReport(readFileSync34(path, "utf8"));
|
|
26199
26012
|
if (parsed.report !== null)
|
|
26200
26013
|
return parsed.report;
|
|
26201
26014
|
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(", ")}`);
|
|
@@ -26204,7 +26017,7 @@ function previousReport(path, context) {
|
|
|
26204
26017
|
function runWrite(context, scope) {
|
|
26205
26018
|
const files = selectSources(scope);
|
|
26206
26019
|
const report5 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
26207
|
-
mkdirSync16(
|
|
26020
|
+
mkdirSync16(dirname15(scope.reportPath), { recursive: true });
|
|
26208
26021
|
writeFileSync15(scope.reportPath, writeDemotionReport(report5), "utf8");
|
|
26209
26022
|
const result2 = {
|
|
26210
26023
|
applied: false,
|
|
@@ -26228,10 +26041,10 @@ function runWrite(context, scope) {
|
|
|
26228
26041
|
context.output.info(line);
|
|
26229
26042
|
}
|
|
26230
26043
|
function reviewedReport(scope) {
|
|
26231
|
-
if (!
|
|
26044
|
+
if (!existsSync16(scope.reportPath)) {
|
|
26232
26045
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
26233
26046
|
}
|
|
26234
|
-
const parsed = parseDemotionReport(
|
|
26047
|
+
const parsed = parseDemotionReport(readFileSync34(scope.reportPath, "utf8"));
|
|
26235
26048
|
if (parsed.report === null) {
|
|
26236
26049
|
const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
|
|
26237
26050
|
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
|
|
@@ -26604,7 +26417,7 @@ function statusData(sections) {
|
|
|
26604
26417
|
}
|
|
26605
26418
|
|
|
26606
26419
|
// src/status/sections.ts
|
|
26607
|
-
import { existsSync as
|
|
26420
|
+
import { existsSync as existsSync17 } from "fs";
|
|
26608
26421
|
|
|
26609
26422
|
// src/status/blocked-count.ts
|
|
26610
26423
|
async function labelsCount(options) {
|
|
@@ -26864,7 +26677,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
26864
26677
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
26865
26678
|
}
|
|
26866
26679
|
function hasBoards(root, listing2) {
|
|
26867
|
-
if (
|
|
26680
|
+
if (existsSync17(positionFilePath(root)))
|
|
26868
26681
|
return true;
|
|
26869
26682
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
26870
26683
|
}
|
|
@@ -27039,8 +26852,8 @@ function createStatusCommand(seams = DEFAULT_STATUS_SEAMS) {
|
|
|
27039
26852
|
var status_default4 = createStatusCommand();
|
|
27040
26853
|
|
|
27041
26854
|
// src/commands/stretch/end.ts
|
|
27042
|
-
import { existsSync as
|
|
27043
|
-
import { dirname as
|
|
26855
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
|
|
26856
|
+
import { dirname as dirname16, join as join47 } from "path";
|
|
27044
26857
|
var COMMAND_NAME19 = "rafa stretch end";
|
|
27045
26858
|
var STRETCH_END_USAGE = "rafa stretch end [--dry-run]";
|
|
27046
26859
|
var REPORT_FILE2 = "report.md";
|
|
@@ -27048,10 +26861,10 @@ var BODY_FILE = "pr-body.md";
|
|
|
27048
26861
|
var REFUSED_EXIT10 = 1;
|
|
27049
26862
|
var FAILED_EXIT6 = 2;
|
|
27050
26863
|
function reportPath(root, n) {
|
|
27051
|
-
return
|
|
26864
|
+
return join47(stretchFolder(root, n), REPORT_FILE2);
|
|
27052
26865
|
}
|
|
27053
26866
|
function bodyPath(root, n) {
|
|
27054
|
-
return
|
|
26867
|
+
return join47(stretchFolder(root, n), BODY_FILE);
|
|
27055
26868
|
}
|
|
27056
26869
|
function endTitle(n) {
|
|
27057
26870
|
return `Stretch ${String(n)}`;
|
|
@@ -27087,11 +26900,11 @@ function requireStretchBase2(prBase) {
|
|
|
27087
26900
|
}
|
|
27088
26901
|
function readReport(root, n) {
|
|
27089
26902
|
const path = reportPath(root, n);
|
|
27090
|
-
if (!
|
|
26903
|
+
if (!existsSync18(path)) {
|
|
27091
26904
|
throw refusal19(`there is no ${path}: write the stretch report first, since it is the pull request's body. Nothing was run.`);
|
|
27092
26905
|
}
|
|
27093
26906
|
try {
|
|
27094
|
-
return
|
|
26907
|
+
return readFileSync35(path, "utf8");
|
|
27095
26908
|
} catch (error) {
|
|
27096
26909
|
throw refusal19(`${path} could not be read: ${messageOf(error)}. Nothing was run.`);
|
|
27097
26910
|
}
|
|
@@ -27104,11 +26917,11 @@ function recordText(record, name2, path) {
|
|
|
27104
26917
|
}
|
|
27105
26918
|
function readStretchRecord(root, n) {
|
|
27106
26919
|
const path = stretchRecordPath(root, n);
|
|
27107
|
-
if (!
|
|
26920
|
+
if (!existsSync18(path))
|
|
27108
26921
|
return null;
|
|
27109
26922
|
let value;
|
|
27110
26923
|
try {
|
|
27111
|
-
value = JSON.parse(
|
|
26924
|
+
value = JSON.parse(readFileSync35(path, "utf8"));
|
|
27112
26925
|
} catch (error) {
|
|
27113
26926
|
throw refusal19(`${path} could not be read as JSON: ${messageOf(error)}. Nothing was run.`);
|
|
27114
26927
|
}
|
|
@@ -27153,7 +26966,7 @@ function putBack(context, input, merged2) {
|
|
|
27153
26966
|
const path = configFilePath(input.root);
|
|
27154
26967
|
let edit;
|
|
27155
26968
|
try {
|
|
27156
|
-
edit = withConfigSetting(
|
|
26969
|
+
edit = withConfigSetting(readFileSync35(path, "utf8"), PR_BASE_KEY, value, path);
|
|
27157
26970
|
} catch (error) {
|
|
27158
26971
|
const why = error instanceof ConfigSetRefusal ? `${PR_BASE_KEY} cannot be set: ${error.message}` : `${path} could not be read: ${messageOf(error)}`;
|
|
27159
26972
|
throw refusal19(`${why}. Nothing was set.`);
|
|
@@ -27190,7 +27003,7 @@ async function openEnd(context, input, body, seams) {
|
|
|
27190
27003
|
return;
|
|
27191
27004
|
}
|
|
27192
27005
|
try {
|
|
27193
|
-
mkdirSync17(
|
|
27006
|
+
mkdirSync17(dirname16(path), { recursive: true });
|
|
27194
27007
|
writeFileSync16(path, body, "utf8");
|
|
27195
27008
|
} catch (error) {
|
|
27196
27009
|
throw failure4(`${path} could not be written: ${messageOf(error)}. No pull request was opened.`);
|
|
@@ -27275,8 +27088,8 @@ function createUpdateStub(spec) {
|
|
|
27275
27088
|
var board_default = createUpdateStub({ action: "board", summary: "apply the board's newest setup and its migrations", issue: 717 });
|
|
27276
27089
|
|
|
27277
27090
|
// src/project/lock.ts
|
|
27278
|
-
import { existsSync as
|
|
27279
|
-
import { join as
|
|
27091
|
+
import { existsSync as existsSync19, lstatSync as lstatSync2, readFileSync as readFileSync36, writeFileSync as writeFileSync17 } from "fs";
|
|
27092
|
+
import { join as join48 } from "path";
|
|
27280
27093
|
var LOCK_FILE = "rafa.lock";
|
|
27281
27094
|
var LOCKFILE_VERSION = 1;
|
|
27282
27095
|
|
|
@@ -27284,7 +27097,7 @@ class ProjectLockError extends Error {
|
|
|
27284
27097
|
name = "ProjectLockError";
|
|
27285
27098
|
}
|
|
27286
27099
|
function lockPath(root) {
|
|
27287
|
-
return
|
|
27100
|
+
return join48(root, LOCK_FILE);
|
|
27288
27101
|
}
|
|
27289
27102
|
function projectLockText(version2) {
|
|
27290
27103
|
const lock = { lockfileVersion: LOCKFILE_VERSION, rafa: version2 };
|
|
@@ -27317,14 +27130,14 @@ function lockOf(value) {
|
|
|
27317
27130
|
}
|
|
27318
27131
|
function readProjectLock(root) {
|
|
27319
27132
|
const path = lockPath(root);
|
|
27320
|
-
if (!
|
|
27133
|
+
if (!existsSync19(path)) {
|
|
27321
27134
|
if (isLink(path))
|
|
27322
27135
|
refuse10("is a link to nothing");
|
|
27323
27136
|
return null;
|
|
27324
27137
|
}
|
|
27325
27138
|
let text;
|
|
27326
27139
|
try {
|
|
27327
|
-
text =
|
|
27140
|
+
text = readFileSync36(path, "utf8");
|
|
27328
27141
|
} catch (error) {
|
|
27329
27142
|
refuse10(`could not be read: ${messageOf(error)}`);
|
|
27330
27143
|
}
|
|
@@ -27341,11 +27154,11 @@ function writeProjectLock(root, version2) {
|
|
|
27341
27154
|
}
|
|
27342
27155
|
|
|
27343
27156
|
// src/project/update-current.ts
|
|
27344
|
-
import { existsSync as
|
|
27345
|
-
import { join as
|
|
27157
|
+
import { existsSync as existsSync20 } from "fs";
|
|
27158
|
+
import { join as join49 } from "path";
|
|
27346
27159
|
var DEPRECATIONS = Object.freeze([]);
|
|
27347
27160
|
function missingFolders(root) {
|
|
27348
|
-
return PROJECT_TREE.map((name2) =>
|
|
27161
|
+
return PROJECT_TREE.map((name2) => join49(SCOPE_DIR, name2)).filter((relative12) => !existsSync20(join49(root, relative12)));
|
|
27349
27162
|
}
|
|
27350
27163
|
async function readBoard3(access) {
|
|
27351
27164
|
if (access.gh === null)
|
|
@@ -27398,7 +27211,7 @@ class UpdateStepError extends Error {
|
|
|
27398
27211
|
}
|
|
27399
27212
|
}
|
|
27400
27213
|
function foldersNowThere(plan2) {
|
|
27401
|
-
return plan2.folders.filter((relative12) =>
|
|
27214
|
+
return plan2.folders.filter((relative12) => existsSync20(join49(plan2.root, relative12)));
|
|
27402
27215
|
}
|
|
27403
27216
|
function writeFolders(plan2) {
|
|
27404
27217
|
if (plan2.folders.length === 0)
|