@open-tomato/rafa 0.32.0 → 0.34.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 +134 -4
- package/dist/bundled/agents/build-error-resolver.md +22 -14
- package/dist/bundled/agents/code-reviewer.md +22 -14
- package/dist/bundled/agents/loop-implementer.md +22 -8
- package/dist/bundled/agents/tdd-guide.md +23 -15
- package/dist/bundled/operators/agents/rafa-stretch-engineer.md +177 -0
- package/dist/bundled/operators/agents/rafa-stretch-watchtower.md +91 -0
- package/dist/bundled/operators/skills/rafa-stretch-gap-log/SKILL.md +72 -0
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +77 -0
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +111 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +22 -4
- package/dist/bundled/skills/git-workflow/SKILL.md +1 -1
- package/dist/cli.js +4495 -1789
- package/dist/{describe-d31jy6dk.js → describe-5hqfwbjc.js} +3 -3
- package/dist/effort/store/index.js +24 -4
- package/dist/{index-mt6hpsry.js → index-3mzw3kzh.js} +2 -2
- package/dist/{index-qgayjcym.js → index-4b3m7s0z.js} +7148 -4603
- package/dist/{index-22stnzde.js → index-b8t7q5hc.js} +666 -1137
- package/dist/{index-wvm2s4pr.js → index-bvfbfa63.js} +699 -132
- package/dist/{index-rs79dq9e.js → index-fzhd3d4y.js} +22 -4
- package/dist/{index-5tftj69y.js → index-k6sd4ym5.js} +2 -2
- package/dist/{index-pbeyv281.js → index-v8d7c6sw.js} +2 -2
- package/dist/{index-9aph6ft9.js → index-vp0xghk2.js} +167 -5
- package/dist/index.js +27 -7
- package/dist/plan/index.js +3 -3
- package/dist/plan-prompt.md +12 -0
- package/package.json +5 -2
|
@@ -60,9 +60,11 @@ import {
|
|
|
60
60
|
TRIAGE_MODULE,
|
|
61
61
|
VENDOR_COMMAND,
|
|
62
62
|
WHEN_TO_USE_HEADING,
|
|
63
|
+
WORKTREES_SUBDIR,
|
|
63
64
|
actionInvocation,
|
|
64
65
|
actionOutput,
|
|
65
66
|
addBlockerToBody,
|
|
67
|
+
addResolveWorktree,
|
|
66
68
|
alignRows,
|
|
67
69
|
allocateFragmentName,
|
|
68
70
|
announceRiskTotal,
|
|
@@ -92,6 +94,8 @@ import {
|
|
|
92
94
|
claimBranchIssue,
|
|
93
95
|
classifySkill,
|
|
94
96
|
cleanUpAfterMerge,
|
|
97
|
+
cleanupCounts,
|
|
98
|
+
cleanupSteps,
|
|
95
99
|
closedIssuesIn,
|
|
96
100
|
collect,
|
|
97
101
|
collectSessionRow,
|
|
@@ -104,7 +108,6 @@ import {
|
|
|
104
108
|
createGhBoardListing,
|
|
105
109
|
createGhIssueBoard,
|
|
106
110
|
createGhOpenPullRequests,
|
|
107
|
-
createGhPermissions,
|
|
108
111
|
createGhPullRequests,
|
|
109
112
|
createGhRoadmapBody,
|
|
110
113
|
createGhRoadmapSearch,
|
|
@@ -116,9 +119,12 @@ import {
|
|
|
116
119
|
createPlanDirNames,
|
|
117
120
|
createPlanRefsVerifier,
|
|
118
121
|
createStartPreflightClaim,
|
|
122
|
+
crossRepositoryRefusal,
|
|
123
|
+
defaultCleanupSeams,
|
|
119
124
|
developmentApplied,
|
|
120
125
|
directoryPrefix,
|
|
121
126
|
driftLines,
|
|
127
|
+
dryRunLines,
|
|
122
128
|
editChecklist,
|
|
123
129
|
effortSyncRefusal,
|
|
124
130
|
endWithNextStep,
|
|
@@ -205,16 +211,19 @@ import {
|
|
|
205
211
|
parseWorkingTree,
|
|
206
212
|
parseWorktrees,
|
|
207
213
|
pathDirectories,
|
|
214
|
+
phaseNote,
|
|
215
|
+
phasedCounts,
|
|
208
216
|
pickSession,
|
|
209
217
|
plan,
|
|
210
218
|
planLabel,
|
|
211
219
|
previousDir,
|
|
212
220
|
projectRoot,
|
|
213
221
|
provenanceBlock,
|
|
222
|
+
pullBeforeRead,
|
|
214
223
|
pushOwnershipCommit,
|
|
224
|
+
pushResolved,
|
|
215
225
|
rankCandidates,
|
|
216
226
|
readAgentDirectory,
|
|
217
|
-
readAuthorTrust,
|
|
218
227
|
readBinPath,
|
|
219
228
|
readBlockedBy,
|
|
220
229
|
readBlockedIssues,
|
|
@@ -226,6 +235,7 @@ import {
|
|
|
226
235
|
readChoiceFlag,
|
|
227
236
|
readClaimBranch,
|
|
228
237
|
readClaimState,
|
|
238
|
+
readCleanup,
|
|
229
239
|
readConfiguredRelations,
|
|
230
240
|
readCopyRefs,
|
|
231
241
|
readDoctorEffortSync,
|
|
@@ -268,6 +278,7 @@ import {
|
|
|
268
278
|
refusal,
|
|
269
279
|
refuseMisplacedRuntime,
|
|
270
280
|
releaseCommitSubject,
|
|
281
|
+
removeResolveWorktree,
|
|
271
282
|
renderBlockedIssues,
|
|
272
283
|
renderCancelComment,
|
|
273
284
|
renderCloseComment,
|
|
@@ -283,7 +294,6 @@ import {
|
|
|
283
294
|
report,
|
|
284
295
|
reportFollowUps,
|
|
285
296
|
requireGhProvider,
|
|
286
|
-
requireTrustedAuthor,
|
|
287
297
|
resolveAgentRoster,
|
|
288
298
|
resolveCreateSpec,
|
|
289
299
|
resolveDefaultBoard,
|
|
@@ -294,10 +304,12 @@ import {
|
|
|
294
304
|
resolvePrProvider,
|
|
295
305
|
resolveReleaseEnabled,
|
|
296
306
|
resolveTracker,
|
|
307
|
+
resolveWorktreePath,
|
|
297
308
|
restampCopyRefs,
|
|
298
309
|
rootCandidates,
|
|
299
310
|
rootRefusal,
|
|
300
311
|
runAction,
|
|
312
|
+
runCleanupSteps,
|
|
301
313
|
runIssueList,
|
|
302
314
|
samePlace,
|
|
303
315
|
scanClaimBranches,
|
|
@@ -324,7 +336,6 @@ import {
|
|
|
324
336
|
toolOf,
|
|
325
337
|
trendReportOf,
|
|
326
338
|
triageReport,
|
|
327
|
-
trustRefusalClause,
|
|
328
339
|
unaskedReasonMessage,
|
|
329
340
|
unblockAfterMerge,
|
|
330
341
|
unblock_default,
|
|
@@ -342,9 +353,10 @@ import {
|
|
|
342
353
|
waitForChecks,
|
|
343
354
|
waitingSettingsOf,
|
|
344
355
|
watchDryEpic,
|
|
356
|
+
worktreeAt,
|
|
345
357
|
writeSession,
|
|
346
358
|
writeShippedFile
|
|
347
|
-
} from "./index-
|
|
359
|
+
} from "./index-4b3m7s0z.js";
|
|
348
360
|
import {
|
|
349
361
|
PROBE_TIMEOUT_MS,
|
|
350
362
|
loadPlanPrerequisites,
|
|
@@ -356,7 +368,7 @@ import {
|
|
|
356
368
|
runPreflight,
|
|
357
369
|
splitBlockerComment,
|
|
358
370
|
trackerPathFor
|
|
359
|
-
} from "./index-
|
|
371
|
+
} from "./index-v8d7c6sw.js";
|
|
360
372
|
import {
|
|
361
373
|
ACTION_HEADING,
|
|
362
374
|
CAUSE_HEADING,
|
|
@@ -389,6 +401,7 @@ import {
|
|
|
389
401
|
copyEffortStore,
|
|
390
402
|
countTasks,
|
|
391
403
|
createFileSync,
|
|
404
|
+
createGhPermissions,
|
|
392
405
|
createGhRunner,
|
|
393
406
|
createGitRunner,
|
|
394
407
|
effortStoreDir,
|
|
@@ -419,6 +432,7 @@ import {
|
|
|
419
432
|
plansDirAt,
|
|
420
433
|
plural,
|
|
421
434
|
positionFilePath,
|
|
435
|
+
readAuthorTrust,
|
|
422
436
|
readFrontmatter,
|
|
423
437
|
readFrontmatterDocument,
|
|
424
438
|
readHopRecord,
|
|
@@ -430,22 +444,24 @@ import {
|
|
|
430
444
|
rehome,
|
|
431
445
|
renderFrontmatter,
|
|
432
446
|
requireProject,
|
|
447
|
+
requireTrustedAuthor,
|
|
433
448
|
resolvePlansDir,
|
|
434
449
|
resolveProjectConfig,
|
|
435
450
|
runClaudeCaptured,
|
|
436
|
-
runsDir,
|
|
437
451
|
scanLayout,
|
|
438
452
|
scopeAt,
|
|
439
453
|
selectEffortStore,
|
|
454
|
+
sessionPhase,
|
|
440
455
|
sessionSpawnEnv,
|
|
441
456
|
spawnClaudeCaptured,
|
|
442
457
|
staleAgainst,
|
|
443
458
|
toHeldRecords,
|
|
459
|
+
trustRefusalClause,
|
|
444
460
|
version,
|
|
445
461
|
versionLine,
|
|
446
462
|
writeFrontmatter,
|
|
447
463
|
writePositionFile
|
|
448
|
-
} from "./index-
|
|
464
|
+
} from "./index-bvfbfa63.js";
|
|
449
465
|
import {
|
|
450
466
|
CONFIG_DEFAULTS,
|
|
451
467
|
CONFIG_FILE,
|
|
@@ -460,7 +476,7 @@ import {
|
|
|
460
476
|
readItemBytes,
|
|
461
477
|
readRafaBlocks,
|
|
462
478
|
resolveTiers
|
|
463
|
-
} from "./index-
|
|
479
|
+
} from "./index-vp0xghk2.js";
|
|
464
480
|
import {
|
|
465
481
|
promotable
|
|
466
482
|
} from "./index-d6d0942q.js";
|
|
@@ -468,10 +484,10 @@ import {
|
|
|
468
484
|
createCommandRegistry,
|
|
469
485
|
describeRegistry,
|
|
470
486
|
mountKey
|
|
471
|
-
} from "./index-
|
|
487
|
+
} from "./index-k6sd4ym5.js";
|
|
472
488
|
import {
|
|
473
489
|
CommandExit
|
|
474
|
-
} from "./index-
|
|
490
|
+
} from "./index-3mzw3kzh.js";
|
|
475
491
|
import {
|
|
476
492
|
CLAIMS_STALE_DISABLED,
|
|
477
493
|
MERGE_METHODS,
|
|
@@ -484,7 +500,7 @@ import {
|
|
|
484
500
|
isSkillTier,
|
|
485
501
|
messageOf,
|
|
486
502
|
parseBudgetUsd
|
|
487
|
-
} from "./index-
|
|
503
|
+
} from "./index-fzhd3d4y.js";
|
|
488
504
|
import {
|
|
489
505
|
triggerKey
|
|
490
506
|
} from "./index-wp8c28fd.js";
|
|
@@ -3797,710 +3813,6 @@ function createClaimTakeCommand(makeSeams = DEFAULT_SEAMS4) {
|
|
|
3797
3813
|
}
|
|
3798
3814
|
var take_default = createClaimTakeCommand();
|
|
3799
3815
|
|
|
3800
|
-
// src/cleanup/branches.ts
|
|
3801
|
-
var BRANCH_FORMAT = [
|
|
3802
|
-
"%(refname:lstrip=2)",
|
|
3803
|
-
"%(upstream:short)",
|
|
3804
|
-
"%(upstream:track,nobracket)",
|
|
3805
|
-
"%(committerdate:unix)",
|
|
3806
|
-
"%(HEAD)"
|
|
3807
|
-
].join("%09");
|
|
3808
|
-
var REMOTE2 = "origin";
|
|
3809
|
-
var FIELD_COUNT = 5;
|
|
3810
|
-
var MS_PER_SECOND = 1000;
|
|
3811
|
-
var GONE_TRACK = "gone";
|
|
3812
|
-
function resolveBaseBranch(git, configured) {
|
|
3813
|
-
if (configured !== null) {
|
|
3814
|
-
return configured;
|
|
3815
|
-
}
|
|
3816
|
-
const result = git(["symbolic-ref", "--quiet", "--short", `refs/remotes/${REMOTE2}/HEAD`]);
|
|
3817
|
-
const target = result.stdout.trim();
|
|
3818
|
-
const prefix = `${REMOTE2}/`;
|
|
3819
|
-
if (!result.ok || !target.startsWith(prefix) || target.length === prefix.length) {
|
|
3820
|
-
return DEFAULT_BASE_BRANCH;
|
|
3821
|
-
}
|
|
3822
|
-
return target.slice(prefix.length);
|
|
3823
|
-
}
|
|
3824
|
-
function isKept(name, keep) {
|
|
3825
|
-
return keep.some((pattern) => new Bun.Glob(pattern).match(name));
|
|
3826
|
-
}
|
|
3827
|
-
function readBranches(git, settings) {
|
|
3828
|
-
const base = resolveBaseBranch(git, settings.base);
|
|
3829
|
-
const result = git(["for-each-ref", `--format=${BRANCH_FORMAT}`, "refs/heads"]);
|
|
3830
|
-
if (!result.ok) {
|
|
3831
|
-
const said = gitSaid(result);
|
|
3832
|
-
const detail = said === "" ? "git for-each-ref failed" : `git for-each-ref failed: ${said}`;
|
|
3833
|
-
return { ok: false, detail };
|
|
3834
|
-
}
|
|
3835
|
-
const branches = [];
|
|
3836
|
-
for (const line of result.stdout.split(`
|
|
3837
|
-
`)) {
|
|
3838
|
-
if (line === "") {
|
|
3839
|
-
continue;
|
|
3840
|
-
}
|
|
3841
|
-
const parsed = parseBranchLine(line);
|
|
3842
|
-
if (typeof parsed === "string") {
|
|
3843
|
-
return { ok: false, detail: parsed };
|
|
3844
|
-
}
|
|
3845
|
-
if (!parsed.current && parsed.branch.name !== base && !isKept(parsed.branch.name, settings.keep)) {
|
|
3846
|
-
branches.push(parsed.branch);
|
|
3847
|
-
}
|
|
3848
|
-
}
|
|
3849
|
-
return { ok: true, base, branches };
|
|
3850
|
-
}
|
|
3851
|
-
function parseBranchLine(line) {
|
|
3852
|
-
const fields = line.split("\t");
|
|
3853
|
-
const [name = "", upstreamField = "", track = "", unix = "", head = ""] = fields;
|
|
3854
|
-
if (fields.length !== FIELD_COUNT || name === "") {
|
|
3855
|
-
return `git for-each-ref wrote a line with ${String(fields.length)} fields, expected ${String(FIELD_COUNT)}: ${JSON.stringify(line)}`;
|
|
3856
|
-
}
|
|
3857
|
-
const seconds = Number(unix);
|
|
3858
|
-
if (unix === "" || !Number.isInteger(seconds)) {
|
|
3859
|
-
return `git for-each-ref wrote no commit date for ${name}: ${JSON.stringify(unix)}`;
|
|
3860
|
-
}
|
|
3861
|
-
const lastCommit = new Date(seconds * MS_PER_SECOND);
|
|
3862
|
-
const current = head === "*";
|
|
3863
|
-
if (upstreamField === "") {
|
|
3864
|
-
return { current, branch: { name, upstream: null, gone: false, ahead: null, lastCommit } };
|
|
3865
|
-
}
|
|
3866
|
-
if (track === GONE_TRACK) {
|
|
3867
|
-
return { current, branch: { name, upstream: upstreamField, gone: true, ahead: null, lastCommit } };
|
|
3868
|
-
}
|
|
3869
|
-
return { current, branch: { name, upstream: upstreamField, gone: false, ahead: aheadOf(track), lastCommit } };
|
|
3870
|
-
}
|
|
3871
|
-
function aheadOf(track) {
|
|
3872
|
-
const count = /\bahead (\d+)/.exec(track)?.[1];
|
|
3873
|
-
if (count === undefined) {
|
|
3874
|
-
return 0;
|
|
3875
|
-
}
|
|
3876
|
-
return Number(count);
|
|
3877
|
-
}
|
|
3878
|
-
|
|
3879
|
-
// src/cleanup/groups.ts
|
|
3880
|
-
var MERGED_STATE_UNKNOWN = "merged state unknown";
|
|
3881
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
3882
|
-
function unreachableNote(detail) {
|
|
3883
|
-
return `pull request provider unreachable (${detail}); merged pull requests not read, grouped from git alone`;
|
|
3884
|
-
}
|
|
3885
|
-
function baseUnreadNote(base, detail) {
|
|
3886
|
-
return `could not read which branches are merged into ${base} (${detail}); none is listed as merged into it`;
|
|
3887
|
-
}
|
|
3888
|
-
async function readProviderMerges(pulls) {
|
|
3889
|
-
if (pulls === null) {
|
|
3890
|
-
return { state: "none" };
|
|
3891
|
-
}
|
|
3892
|
-
try {
|
|
3893
|
-
return { state: "read", pullRequests: await pulls.listMerged() };
|
|
3894
|
-
} catch (error) {
|
|
3895
|
-
return { state: "unreachable", detail: firstLine(messageOf(error)) };
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
function classifyBranches(git, read, provider, settings) {
|
|
3899
|
-
const notes = provider.state === "unreachable" ? [unreachableNote(provider.detail)] : [];
|
|
3900
|
-
const baseRef = `refs/heads/${read.base}`;
|
|
3901
|
-
const mergedRead = git(["branch", "--merged", baseRef, "--format=%(refname:lstrip=2)"]);
|
|
3902
|
-
const reachable = new Set(mergedRead.ok ? lines(mergedRead.stdout) : []);
|
|
3903
|
-
if (!mergedRead.ok) {
|
|
3904
|
-
notes.push(baseUnreadNote(read.base, failure("git branch --merged", mergedRead)));
|
|
3905
|
-
}
|
|
3906
|
-
const named = provider.state === "read" ? namedPullRequests(provider.pullRequests, read.branches) : new Map;
|
|
3907
|
-
const tips = readTips(git, named);
|
|
3908
|
-
if (typeof tips === "string") {
|
|
3909
|
-
return { ok: false, detail: tips };
|
|
3910
|
-
}
|
|
3911
|
-
const merged = [];
|
|
3912
|
-
const stale = [];
|
|
3913
|
-
const notPushed = [];
|
|
3914
|
-
const context = {
|
|
3915
|
-
unknown: provider.state === "none",
|
|
3916
|
-
exclude: mergedRead.ok ? [baseRef] : []
|
|
3917
|
-
};
|
|
3918
|
-
for (const branch of read.branches) {
|
|
3919
|
-
const pulls = named.get(branch.name) ?? [];
|
|
3920
|
-
const atTip = pulls.find((pull) => pull.headRefOid === tips.get(branch.name)) ?? null;
|
|
3921
|
-
const mergedBy = mergedReadings(branch, reachable.has(branch.name), atTip);
|
|
3922
|
-
if (mergedBy.length > 0) {
|
|
3923
|
-
merged.push(mergedRow(branch, mergedBy, atTip, read.base));
|
|
3924
|
-
continue;
|
|
3925
|
-
}
|
|
3926
|
-
const suffix = rowSuffix(context, pulls[0] ?? null);
|
|
3927
|
-
if (branch.upstream === null || (branch.ahead ?? 0) > 0) {
|
|
3928
|
-
const commits = countLost(git, branch.name, context.exclude);
|
|
3929
|
-
if (typeof commits === "string") {
|
|
3930
|
-
return { ok: false, detail: commits };
|
|
3931
|
-
}
|
|
3932
|
-
notPushed.push(notPushedRow(branch, commits, suffix));
|
|
3933
|
-
continue;
|
|
3934
|
-
}
|
|
3935
|
-
const age = settings.now.getTime() - branch.lastCommit.getTime();
|
|
3936
|
-
if (age > settings.staleDays * MS_PER_DAY) {
|
|
3937
|
-
const idleDays = Math.floor(age / MS_PER_DAY);
|
|
3938
|
-
stale.push({ group: "stale", branch, ticked: false, idleDays, reason: `no commit in ${plural2(idleDays, "day")}${suffix}` });
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
return { ok: true, base: read.base, merged, stale, notPushed, notes };
|
|
3942
|
-
}
|
|
3943
|
-
function namedPullRequests(pullRequests, branches) {
|
|
3944
|
-
const listed = new Set(branches.map((branch) => branch.name));
|
|
3945
|
-
const named = new Map;
|
|
3946
|
-
for (const pull of pullRequests) {
|
|
3947
|
-
if (listed.has(pull.headRefName)) {
|
|
3948
|
-
named.set(pull.headRefName, [...named.get(pull.headRefName) ?? [], pull]);
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
return named;
|
|
3952
|
-
}
|
|
3953
|
-
function readTips(git, named) {
|
|
3954
|
-
const tips = new Map;
|
|
3955
|
-
if (named.size === 0) {
|
|
3956
|
-
return tips;
|
|
3957
|
-
}
|
|
3958
|
-
const result = git(["for-each-ref", "--format=%(refname:lstrip=2)%09%(objectname)", "refs/heads"]);
|
|
3959
|
-
if (!result.ok) {
|
|
3960
|
-
return failure("git for-each-ref", result);
|
|
3961
|
-
}
|
|
3962
|
-
for (const line of lines(result.stdout)) {
|
|
3963
|
-
const [name = "", oid = ""] = line.split("\t");
|
|
3964
|
-
tips.set(name, oid);
|
|
3965
|
-
}
|
|
3966
|
-
return tips;
|
|
3967
|
-
}
|
|
3968
|
-
function mergedReadings(branch, reachable, atTip) {
|
|
3969
|
-
const readings = [];
|
|
3970
|
-
if (reachable) {
|
|
3971
|
-
readings.push("base");
|
|
3972
|
-
}
|
|
3973
|
-
if (atTip !== null) {
|
|
3974
|
-
readings.push("pull-request");
|
|
3975
|
-
}
|
|
3976
|
-
if (branch.gone) {
|
|
3977
|
-
readings.push("gone");
|
|
3978
|
-
}
|
|
3979
|
-
return readings;
|
|
3980
|
-
}
|
|
3981
|
-
function mergedRow(branch, mergedBy, pullRequest, base) {
|
|
3982
|
-
const reasons = mergedBy.map((by) => {
|
|
3983
|
-
if (by === "base") {
|
|
3984
|
-
return `merged into ${base}`;
|
|
3985
|
-
}
|
|
3986
|
-
if (by === "pull-request") {
|
|
3987
|
-
return `pull request #${String(pullRequest?.number)} merged`;
|
|
3988
|
-
}
|
|
3989
|
-
return `upstream ${String(branch.upstream)} is gone`;
|
|
3990
|
-
});
|
|
3991
|
-
return { group: "merged", branch, ticked: true, mergedBy, pullRequest, reason: reasons.join("; ") };
|
|
3992
|
-
}
|
|
3993
|
-
function notPushedRow(branch, commits, suffix) {
|
|
3994
|
-
const why = branch.upstream === null ? "no upstream" : `${String(branch.ahead)} ahead of ${branch.upstream}`;
|
|
3995
|
-
const reason = `${why}; ${plural2(commits, "commit")} not on any remote${suffix}`;
|
|
3996
|
-
return { group: "not-pushed", branch, ticked: false, commits, reason };
|
|
3997
|
-
}
|
|
3998
|
-
function rowSuffix(context, elsewhere) {
|
|
3999
|
-
if (context.unknown) {
|
|
4000
|
-
return `; ${MERGED_STATE_UNKNOWN}`;
|
|
4001
|
-
}
|
|
4002
|
-
if (elsewhere !== null) {
|
|
4003
|
-
return `; pull request #${String(elsewhere.number)} merged at another commit`;
|
|
4004
|
-
}
|
|
4005
|
-
return "";
|
|
4006
|
-
}
|
|
4007
|
-
function countLost(git, name, exclude) {
|
|
4008
|
-
const result = git(["rev-list", "--count", `refs/heads/${name}`, "--not", "--remotes", ...exclude, "--"]);
|
|
4009
|
-
const count = Number(result.stdout.trim());
|
|
4010
|
-
if (!result.ok || result.stdout.trim() === "" || !Number.isInteger(count)) {
|
|
4011
|
-
return failure(`git rev-list --count ${name}`, result);
|
|
4012
|
-
}
|
|
4013
|
-
return count;
|
|
4014
|
-
}
|
|
4015
|
-
function failure(what, result) {
|
|
4016
|
-
const said = firstLine(gitSaid(result));
|
|
4017
|
-
return said === "" ? `${what} failed` : `${what} failed: ${said}`;
|
|
4018
|
-
}
|
|
4019
|
-
function plural2(count, noun) {
|
|
4020
|
-
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
4021
|
-
}
|
|
4022
|
-
function lines(text) {
|
|
4023
|
-
return text.split(`
|
|
4024
|
-
`).filter((line) => line !== "");
|
|
4025
|
-
}
|
|
4026
|
-
function firstLine(text) {
|
|
4027
|
-
return text.split(`
|
|
4028
|
-
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
// src/cleanup/worktrees.ts
|
|
4032
|
-
import { realpathSync as realpathSync3, statSync as statSync3 } from "fs";
|
|
4033
|
-
import { join as join7, sep } from "path";
|
|
4034
|
-
|
|
4035
|
-
// src/pr/worktree.ts
|
|
4036
|
-
import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
|
|
4037
|
-
import { join as join6 } from "path";
|
|
4038
|
-
var WORKTREES_SUBDIR = join6(".rafa", "worktrees");
|
|
4039
|
-
var WORKTREE_PREFIX = "pr-";
|
|
4040
|
-
var DEFAULT_REMOTE = "origin";
|
|
4041
|
-
var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
|
|
4042
|
-
var FORCING_FLAG = /^(?:-f|--force(?:-with-lease|-if-includes)?(?:=.*)?|--mirror)$/;
|
|
4043
|
-
var FORCING_REFSPEC_MARK = "+";
|
|
4044
|
-
function resolveWorktreePath(home, number) {
|
|
4045
|
-
return join6(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${number}`);
|
|
4046
|
-
}
|
|
4047
|
-
function crossRepositoryRefusal(pr) {
|
|
4048
|
-
if (!pr.isCrossRepository)
|
|
4049
|
-
return null;
|
|
4050
|
-
return `${REFUSAL_PREFIX} #${pr.number}: its head branch \`${pr.headRefName}\` is on a fork.` + " There is no branch in this repository to check a worktree out of, and the resolution" + " would have to be pushed to a repository this checkout does not own." + ` Assess it with \`rafa pr triage ${pr.number}\`, which needs no worktree, and resolve it` + " where the fork is.";
|
|
4051
|
-
}
|
|
4052
|
-
function frozenStep(step) {
|
|
4053
|
-
return Object.freeze({ ...step, argv: Object.freeze(step.argv) });
|
|
4054
|
-
}
|
|
4055
|
-
function addWorktreeStep(path, branch) {
|
|
4056
|
-
return frozenStep({
|
|
4057
|
-
id: "add",
|
|
4058
|
-
label: `add a worktree for ${branch} at ${path}`,
|
|
4059
|
-
argv: ["git", "worktree", "add", path, branch]
|
|
4060
|
-
});
|
|
4061
|
-
}
|
|
4062
|
-
function removeWorktreeStep(path) {
|
|
4063
|
-
return frozenStep({
|
|
4064
|
-
id: "remove",
|
|
4065
|
-
label: `remove the worktree at ${path}`,
|
|
4066
|
-
argv: ["git", "worktree", "remove", path]
|
|
4067
|
-
});
|
|
4068
|
-
}
|
|
4069
|
-
function pushResolvedStep(branch, remote = DEFAULT_REMOTE) {
|
|
4070
|
-
return frozenStep({
|
|
4071
|
-
id: "push",
|
|
4072
|
-
label: `push ${branch} to ${remote}`,
|
|
4073
|
-
argv: ["git", "push", remote, branch]
|
|
4074
|
-
});
|
|
4075
|
-
}
|
|
4076
|
-
function worktreeCommandLine(step) {
|
|
4077
|
-
return step.argv.map((word) => shellQuote(word)).join(" ");
|
|
4078
|
-
}
|
|
4079
|
-
function forcingWords(argv) {
|
|
4080
|
-
return argv.filter((word) => FORCING_FLAG.test(word) || word.startsWith(FORCING_REFSPEC_MARK));
|
|
4081
|
-
}
|
|
4082
|
-
function forcedPushRefusal(argv) {
|
|
4083
|
-
const forcing = forcingWords(argv);
|
|
4084
|
-
if (forcing.length === 0)
|
|
4085
|
-
return null;
|
|
4086
|
-
const words = forcing.map((word) => `\`${word}\``).join(", ");
|
|
4087
|
-
return `${REFUSAL_PREFIX} a push that git would force: ${words}.` + " A resolve run never overwrites what is on the remote, so nothing was pushed.";
|
|
4088
|
-
}
|
|
4089
|
-
function runStep(git, step) {
|
|
4090
|
-
const result = git(step.argv.slice(1));
|
|
4091
|
-
return {
|
|
4092
|
-
id: step.id,
|
|
4093
|
-
ok: result.ok,
|
|
4094
|
-
command: worktreeCommandLine(step),
|
|
4095
|
-
said: gitSaid(result)
|
|
4096
|
-
};
|
|
4097
|
-
}
|
|
4098
|
-
function addResolveWorktree(git, path, branch) {
|
|
4099
|
-
return runStep(git, addWorktreeStep(path, branch));
|
|
4100
|
-
}
|
|
4101
|
-
function removeResolveWorktree(git, path) {
|
|
4102
|
-
return runStep(git, removeWorktreeStep(path));
|
|
4103
|
-
}
|
|
4104
|
-
function pushResolved(git, branch, remote = DEFAULT_REMOTE) {
|
|
4105
|
-
const step = pushResolvedStep(branch, remote);
|
|
4106
|
-
const refusal3 = forcedPushRefusal(step.argv);
|
|
4107
|
-
if (refusal3 === null)
|
|
4108
|
-
return runStep(git, step);
|
|
4109
|
-
return { id: step.id, ok: false, command: worktreeCommandLine(step), said: refusal3 };
|
|
4110
|
-
}
|
|
4111
|
-
function pathForms(path) {
|
|
4112
|
-
if (!existsSync3(path))
|
|
4113
|
-
return [path];
|
|
4114
|
-
return [path, realpathSync2(path)];
|
|
4115
|
-
}
|
|
4116
|
-
function worktreeAt(git, path) {
|
|
4117
|
-
const listed = git(["worktree", "list", "--porcelain"]);
|
|
4118
|
-
if (!listed.ok)
|
|
4119
|
-
return null;
|
|
4120
|
-
const forms = pathForms(path);
|
|
4121
|
-
return parseWorktrees(listed.stdout).find((entry) => forms.includes(entry.path)) ?? null;
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
// src/cleanup/worktrees.ts
|
|
4125
|
-
var CLAUDE_WORKTREES_SUBDIR = join7(".claude", "worktrees");
|
|
4126
|
-
var WORKTREE_LIST = Object.freeze(["worktree", "list", "--porcelain"]);
|
|
4127
|
-
var GIT_DIR = Object.freeze(["rev-parse", "--absolute-git-dir"]);
|
|
4128
|
-
var WORKTREE_STATUS = Object.freeze([
|
|
4129
|
-
"--no-optional-locks",
|
|
4130
|
-
"status",
|
|
4131
|
-
"--porcelain=v1",
|
|
4132
|
-
"--untracked-files=all"
|
|
4133
|
-
]);
|
|
4134
|
-
var ADMIN_FILES = Object.freeze(["HEAD", join7("logs", "HEAD"), "index"]);
|
|
4135
|
-
var MS_PER_DAY2 = 24 * 60 * 60 * 1000;
|
|
4136
|
-
var UNTRACKED_MARK = "?? ";
|
|
4137
|
-
var LOCKED_LINE = "locked";
|
|
4138
|
-
var LIVE_STATES = new Set(["running", "paused"]);
|
|
4139
|
-
function defaultWorktreeSeams(cwd) {
|
|
4140
|
-
return {
|
|
4141
|
-
git: createGitRunner(cwd),
|
|
4142
|
-
gitAt: createGitRunner,
|
|
4143
|
-
sessions: (root) => readSessions(root),
|
|
4144
|
-
modifiedAt: (path) => {
|
|
4145
|
-
try {
|
|
4146
|
-
return statSync3(path).mtime;
|
|
4147
|
-
} catch {
|
|
4148
|
-
return null;
|
|
4149
|
-
}
|
|
4150
|
-
},
|
|
4151
|
-
realPath: (path) => {
|
|
4152
|
-
try {
|
|
4153
|
-
return realpathSync3(path);
|
|
4154
|
-
} catch {
|
|
4155
|
-
return path;
|
|
4156
|
-
}
|
|
4157
|
-
}
|
|
4158
|
-
};
|
|
4159
|
-
}
|
|
4160
|
-
function readWorktrees(seams, read) {
|
|
4161
|
-
const listed = seams.git(WORKTREE_LIST);
|
|
4162
|
-
if (!listed.ok) {
|
|
4163
|
-
return { ok: false, detail: failure2("git worktree list", listed) };
|
|
4164
|
-
}
|
|
4165
|
-
const entries = parseWorktrees(listed.stdout);
|
|
4166
|
-
const main = entries[0]?.path;
|
|
4167
|
-
if (main === undefined) {
|
|
4168
|
-
return { ok: true, worktrees: [] };
|
|
4169
|
-
}
|
|
4170
|
-
const roots = [
|
|
4171
|
-
...pathForms2(seams, join7(main, CLAUDE_WORKTREES_SUBDIR)),
|
|
4172
|
-
...pathForms2(seams, join7(read.home, WORKTREES_SUBDIR))
|
|
4173
|
-
];
|
|
4174
|
-
const locks = parseLocks(listed.stdout);
|
|
4175
|
-
const context = {
|
|
4176
|
-
seams,
|
|
4177
|
-
read,
|
|
4178
|
-
cwd: pathForms2(seams, read.cwd),
|
|
4179
|
-
projectSessions: readLive(seams, read.projectRoot),
|
|
4180
|
-
merged: new Set(read.mergedBranches)
|
|
4181
|
-
};
|
|
4182
|
-
const worktrees = entries.slice(1).filter((entry) => roots.some((root) => isInside(entry.path, root))).map((entry) => worktreeRow(context, entry.path, entry.branch, locks.get(entry.path) ?? null));
|
|
4183
|
-
return { ok: true, worktrees };
|
|
4184
|
-
}
|
|
4185
|
-
function parseLocks(stdout) {
|
|
4186
|
-
const locks = new Map;
|
|
4187
|
-
let path = null;
|
|
4188
|
-
for (const raw of stdout.split(`
|
|
4189
|
-
`)) {
|
|
4190
|
-
const line = raw.replace(/\r$/, "");
|
|
4191
|
-
if (line.startsWith("worktree ")) {
|
|
4192
|
-
path = line.slice("worktree ".length).trim();
|
|
4193
|
-
continue;
|
|
4194
|
-
}
|
|
4195
|
-
if (path !== null && (line === LOCKED_LINE || line.startsWith(`${LOCKED_LINE} `))) {
|
|
4196
|
-
locks.set(path, line.slice(LOCKED_LINE.length).trim());
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
return locks;
|
|
4200
|
-
}
|
|
4201
|
-
function countChanges(stdout) {
|
|
4202
|
-
const entries = stdout.split(`
|
|
4203
|
-
`).filter((line) => line.trim() !== "");
|
|
4204
|
-
const untracked = entries.filter((line) => line.startsWith(UNTRACKED_MARK)).length;
|
|
4205
|
-
return { changed: entries.length - untracked, untracked };
|
|
4206
|
-
}
|
|
4207
|
-
function worktreeRow(context, path, branch, lock) {
|
|
4208
|
-
const { seams, read } = context;
|
|
4209
|
-
const git = seams.gitAt(path);
|
|
4210
|
-
const modified = lastModified(seams, git, path);
|
|
4211
|
-
const blockers = [];
|
|
4212
|
-
blockers.push(...dirtyBlockers(git));
|
|
4213
|
-
if (lock !== null) {
|
|
4214
|
-
const reason = lock === "" ? LOCKED_LINE : `${LOCKED_LINE}: ${lock}`;
|
|
4215
|
-
blockers.push({ kind: "locked", reason });
|
|
4216
|
-
}
|
|
4217
|
-
if (context.cwd.some((dir) => dir === path || isInside(dir, path))) {
|
|
4218
|
-
blockers.push({ kind: "current", reason: "rafa cleanup runs from it" });
|
|
4219
|
-
}
|
|
4220
|
-
blockers.push(...sessionBlockers(context, path, branch));
|
|
4221
|
-
blockers.push(...recentBlockers(modified, read));
|
|
4222
|
-
const ordered = orderBlockers(blockers);
|
|
4223
|
-
const branchMerged = branch !== null && context.merged.has(branch);
|
|
4224
|
-
const tickable = ordered.length === 0;
|
|
4225
|
-
return {
|
|
4226
|
-
path,
|
|
4227
|
-
branch,
|
|
4228
|
-
lastModified: typeof modified === "string" ? null : modified,
|
|
4229
|
-
branchMerged,
|
|
4230
|
-
blockers: ordered,
|
|
4231
|
-
tickable,
|
|
4232
|
-
ticked: tickable && branchMerged,
|
|
4233
|
-
reason: rowReason(ordered, branch, branchMerged)
|
|
4234
|
-
};
|
|
4235
|
-
}
|
|
4236
|
-
function lastModified(seams, git, path) {
|
|
4237
|
-
const gitDir = git(GIT_DIR);
|
|
4238
|
-
const adminDir = gitDir.stdout.trim();
|
|
4239
|
-
if (!gitDir.ok || adminDir === "") {
|
|
4240
|
-
return failure2("git rev-parse --absolute-git-dir", gitDir);
|
|
4241
|
-
}
|
|
4242
|
-
const times = [path, ...ADMIN_FILES.map((file) => join7(adminDir, file))].map((file) => seams.modifiedAt(file)).filter((time) => time !== null).map((time) => time.getTime());
|
|
4243
|
-
if (times.length === 0) {
|
|
4244
|
-
return `no modification time could be read under ${path} or ${adminDir}`;
|
|
4245
|
-
}
|
|
4246
|
-
return new Date(Math.max(...times));
|
|
4247
|
-
}
|
|
4248
|
-
function dirtyBlockers(git) {
|
|
4249
|
-
const status = git(WORKTREE_STATUS);
|
|
4250
|
-
if (!status.ok) {
|
|
4251
|
-
return [{ kind: "unreadable", reason: `its changes could not be read: ${failure2("git status", status)}` }];
|
|
4252
|
-
}
|
|
4253
|
-
const { changed, untracked } = countChanges(status.stdout);
|
|
4254
|
-
const parts = [
|
|
4255
|
-
...changed > 0 ? [plural3(changed, "uncommitted change")] : [],
|
|
4256
|
-
...untracked > 0 ? [plural3(untracked, "untracked file")] : []
|
|
4257
|
-
];
|
|
4258
|
-
return parts.length === 0 ? [] : [{ kind: "dirty", reason: parts.join(", ") }];
|
|
4259
|
-
}
|
|
4260
|
-
function sessionBlockers(context, path, branch) {
|
|
4261
|
-
const own = readLive(context.seams, path);
|
|
4262
|
-
const readings = [own];
|
|
4263
|
-
if (typeof context.projectSessions === "string") {
|
|
4264
|
-
readings.push(context.projectSessions);
|
|
4265
|
-
} else if (branch !== null) {
|
|
4266
|
-
readings.push(context.projectSessions.filter((record) => record.branch === branch));
|
|
4267
|
-
}
|
|
4268
|
-
const blockers = [];
|
|
4269
|
-
for (const reading of readings) {
|
|
4270
|
-
if (typeof reading === "string") {
|
|
4271
|
-
blockers.push({ kind: "unreadable", reason: reading });
|
|
4272
|
-
continue;
|
|
4273
|
-
}
|
|
4274
|
-
for (const record of reading) {
|
|
4275
|
-
blockers.push({ kind: "session", reason: `loop session ${record.sessionId} is ${record.state} in it` });
|
|
4276
|
-
}
|
|
4277
|
-
}
|
|
4278
|
-
return blockers;
|
|
4279
|
-
}
|
|
4280
|
-
function recentBlockers(modified, read) {
|
|
4281
|
-
if (typeof modified === "string") {
|
|
4282
|
-
return [{ kind: "unreadable", reason: `when it was last modified could not be read: ${modified}` }];
|
|
4283
|
-
}
|
|
4284
|
-
const age = read.now.getTime() - modified.getTime();
|
|
4285
|
-
if (age >= read.idleDays * MS_PER_DAY2) {
|
|
4286
|
-
return [];
|
|
4287
|
-
}
|
|
4288
|
-
const days = Math.max(0, Math.floor(age / MS_PER_DAY2));
|
|
4289
|
-
const when = days === 0 ? "today" : `${plural3(days, "day")} ago`;
|
|
4290
|
-
return [{ kind: "recent", reason: `modified ${when}, within cleanup.worktreeIdleDays (${String(read.idleDays)})` }];
|
|
4291
|
-
}
|
|
4292
|
-
function readLive(seams, root) {
|
|
4293
|
-
try {
|
|
4294
|
-
return seams.sessions(root).filter((record) => LIVE_STATES.has(record.state));
|
|
4295
|
-
} catch (error) {
|
|
4296
|
-
return `the session records under ${runsDir(root)} could not be read: ${messageOf(error)}`;
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
var BLOCK_ORDER = ["dirty", "locked", "current", "session", "recent", "unreadable"];
|
|
4300
|
-
function orderBlockers(blockers) {
|
|
4301
|
-
return BLOCK_ORDER.flatMap((kind) => blockers.filter((blocker) => blocker.kind === kind));
|
|
4302
|
-
}
|
|
4303
|
-
function rowReason(blockers, branch, merged) {
|
|
4304
|
-
if (blockers.length > 0) {
|
|
4305
|
-
return blockers.map((blocker) => blocker.reason).join("; ");
|
|
4306
|
-
}
|
|
4307
|
-
if (branch === null) {
|
|
4308
|
-
return "clean; detached HEAD";
|
|
4309
|
-
}
|
|
4310
|
-
return merged ? `clean; branch ${branch} is merged` : `clean; branch ${branch} is not merged`;
|
|
4311
|
-
}
|
|
4312
|
-
function pathForms2(seams, path) {
|
|
4313
|
-
const resolved = seams.realPath(path);
|
|
4314
|
-
return resolved === path ? [path] : [path, resolved];
|
|
4315
|
-
}
|
|
4316
|
-
function isInside(path, dir) {
|
|
4317
|
-
const prefix = dir.endsWith(sep) ? dir : `${dir}${sep}`;
|
|
4318
|
-
return path.startsWith(prefix);
|
|
4319
|
-
}
|
|
4320
|
-
function failure2(what, result) {
|
|
4321
|
-
const said = gitSaid(result).split(`
|
|
4322
|
-
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
4323
|
-
return said === "" ? `${what} failed` : `${what} failed: ${said}`;
|
|
4324
|
-
}
|
|
4325
|
-
function plural3(count, noun) {
|
|
4326
|
-
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
4327
|
-
}
|
|
4328
|
-
// src/cleanup/steps.ts
|
|
4329
|
-
var FORCING_FLAG2 = /^(?:--force(?:=.*)?|-[^-]*f[^-]*)$/;
|
|
4330
|
-
function frozenStep2(step) {
|
|
4331
|
-
return Object.freeze({ ...step, argv: Object.freeze([...step.argv]) });
|
|
4332
|
-
}
|
|
4333
|
-
function removeWorktreeStep2(path) {
|
|
4334
|
-
return frozenStep2({
|
|
4335
|
-
kind: "remove-worktree",
|
|
4336
|
-
subject: path,
|
|
4337
|
-
argv: ["git", "worktree", "remove", path],
|
|
4338
|
-
after: null
|
|
4339
|
-
});
|
|
4340
|
-
}
|
|
4341
|
-
function deleteBranchStep(branch, forced, after = null) {
|
|
4342
|
-
const flag = forced ? "-D" : "-d";
|
|
4343
|
-
return frozenStep2({
|
|
4344
|
-
kind: "delete-branch",
|
|
4345
|
-
subject: branch,
|
|
4346
|
-
argv: ["git", "branch", flag, branch],
|
|
4347
|
-
after
|
|
4348
|
-
});
|
|
4349
|
-
}
|
|
4350
|
-
function needsForcedDelete(row) {
|
|
4351
|
-
return !row.mergedBy.includes("base") && row.mergedBy.includes("pull-request") && row.pullRequest !== null;
|
|
4352
|
-
}
|
|
4353
|
-
function branchPicks(selection) {
|
|
4354
|
-
return [
|
|
4355
|
-
...selection.merged.map((row) => ({ name: row.branch.name, merged: true, forced: needsForcedDelete(row) })),
|
|
4356
|
-
...selection.stale.map((row) => ({ name: row.branch.name, merged: false, forced: true })),
|
|
4357
|
-
...selection.notPushed.map((row) => ({ name: row.branch.name, merged: false, forced: true }))
|
|
4358
|
-
];
|
|
4359
|
-
}
|
|
4360
|
-
function heldReason(path) {
|
|
4361
|
-
return `held by the worktree at ${path}; only a Merged branch is deleted with its worktree`;
|
|
4362
|
-
}
|
|
4363
|
-
function blockedHolderReason(path) {
|
|
4364
|
-
return `held by the worktree at ${path}, which is not removed`;
|
|
4365
|
-
}
|
|
4366
|
-
function branchStep(pick, removed, blocked) {
|
|
4367
|
-
const blockedPath = blocked.get(pick.name);
|
|
4368
|
-
if (blockedPath !== undefined) {
|
|
4369
|
-
return { kind: "delete-branch", subject: pick.name, reason: blockedHolderReason(blockedPath) };
|
|
4370
|
-
}
|
|
4371
|
-
const path = removed.get(pick.name) ?? null;
|
|
4372
|
-
if (path !== null && !pick.merged) {
|
|
4373
|
-
return { kind: "delete-branch", subject: pick.name, reason: heldReason(path) };
|
|
4374
|
-
}
|
|
4375
|
-
return deleteBranchStep(pick.name, pick.forced, path);
|
|
4376
|
-
}
|
|
4377
|
-
function isStep(entry) {
|
|
4378
|
-
return "argv" in entry;
|
|
4379
|
-
}
|
|
4380
|
-
function holders(rows) {
|
|
4381
|
-
return new Map(rows.flatMap((row) => row.branch === null ? [] : [[row.branch, row.path]]));
|
|
4382
|
-
}
|
|
4383
|
-
function cleanupSteps(selection) {
|
|
4384
|
-
const removable = selection.worktrees.filter((row) => row.tickable);
|
|
4385
|
-
const untickable = selection.worktrees.filter((row) => !row.tickable);
|
|
4386
|
-
const branchEntries = branchPicks(selection).map((pick) => branchStep(pick, holders(removable), holders(untickable)));
|
|
4387
|
-
return Object.freeze({
|
|
4388
|
-
steps: Object.freeze([
|
|
4389
|
-
...removable.map((row) => removeWorktreeStep2(row.path)),
|
|
4390
|
-
...branchEntries.filter(isStep)
|
|
4391
|
-
]),
|
|
4392
|
-
withheld: Object.freeze([
|
|
4393
|
-
...untickable.map((row) => ({ kind: "remove-worktree", subject: row.path, reason: row.reason })),
|
|
4394
|
-
...branchEntries.filter((entry) => !isStep(entry))
|
|
4395
|
-
])
|
|
4396
|
-
});
|
|
4397
|
-
}
|
|
4398
|
-
function cleanupCommandLine(step) {
|
|
4399
|
-
return step.argv.map((word) => shellQuote(word)).join(" ");
|
|
4400
|
-
}
|
|
4401
|
-
function dryRunLines(plan2) {
|
|
4402
|
-
return plan2.steps.map((step) => cleanupCommandLine(step));
|
|
4403
|
-
}
|
|
4404
|
-
function forcingWords2(argv) {
|
|
4405
|
-
return argv.filter((word) => FORCING_FLAG2.test(word));
|
|
4406
|
-
}
|
|
4407
|
-
function forcedFlagRefusal(argv) {
|
|
4408
|
-
const forcing = forcingWords2(argv);
|
|
4409
|
-
if (forcing.length === 0)
|
|
4410
|
-
return null;
|
|
4411
|
-
const words = forcing.map((word) => `\`${word}\``).join(", ");
|
|
4412
|
-
return `rafa cleanup refuses a forced removal: ${words}. Nothing was run.`;
|
|
4413
|
-
}
|
|
4414
|
-
function afterFailedReason(path) {
|
|
4415
|
-
return `not run: the worktree at ${path} was not removed`;
|
|
4416
|
-
}
|
|
4417
|
-
function notRun(step, said) {
|
|
4418
|
-
return { step, ran: false, ok: false, command: cleanupCommandLine(step), said };
|
|
4419
|
-
}
|
|
4420
|
-
function runStep2(git, step, removed) {
|
|
4421
|
-
const refusal3 = forcedFlagRefusal(step.argv);
|
|
4422
|
-
if (refusal3 !== null)
|
|
4423
|
-
return notRun(step, refusal3);
|
|
4424
|
-
if (step.after !== null && !removed.has(step.after))
|
|
4425
|
-
return notRun(step, afterFailedReason(step.after));
|
|
4426
|
-
const result = git(step.argv.slice(1));
|
|
4427
|
-
return { step, ran: true, ok: result.ok, command: cleanupCommandLine(step), said: gitSaid(result) };
|
|
4428
|
-
}
|
|
4429
|
-
function runCleanupSteps(git, plan2) {
|
|
4430
|
-
const removed = new Set;
|
|
4431
|
-
return plan2.steps.map((step) => {
|
|
4432
|
-
const outcome = runStep2(git, step, removed);
|
|
4433
|
-
if (outcome.ok && step.kind === "remove-worktree")
|
|
4434
|
-
removed.add(step.subject);
|
|
4435
|
-
return outcome;
|
|
4436
|
-
});
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
// src/cleanup/index.ts
|
|
4440
|
-
var FETCH_PRUNE = Object.freeze(["fetch", "--prune"]);
|
|
4441
|
-
function defaultCleanupSeams(cwd, pulls) {
|
|
4442
|
-
return { ...defaultWorktreeSeams(cwd), pulls };
|
|
4443
|
-
}
|
|
4444
|
-
function fetchFailedNote(detail) {
|
|
4445
|
-
return `git fetch --prune failed (${detail}); upstreams read as gone as of the last fetch`;
|
|
4446
|
-
}
|
|
4447
|
-
async function readCleanup(seams, settings) {
|
|
4448
|
-
const notes = [];
|
|
4449
|
-
let fetched = false;
|
|
4450
|
-
if (settings.fetch) {
|
|
4451
|
-
const result = seams.git(FETCH_PRUNE);
|
|
4452
|
-
fetched = result.ok;
|
|
4453
|
-
if (!result.ok) {
|
|
4454
|
-
notes.push(fetchFailedNote(firstLine2(gitSaid(result)) || "git said nothing"));
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
const branches = readBranches(seams.git, { base: settings.base, keep: settings.keep });
|
|
4458
|
-
if (!branches.ok) {
|
|
4459
|
-
return branches;
|
|
4460
|
-
}
|
|
4461
|
-
const provider = await readProviderMerges(seams.pulls);
|
|
4462
|
-
const groups = classifyBranches(seams.git, branches, provider, {
|
|
4463
|
-
staleDays: settings.staleDays,
|
|
4464
|
-
now: settings.now
|
|
4465
|
-
});
|
|
4466
|
-
if (!groups.ok) {
|
|
4467
|
-
return groups;
|
|
4468
|
-
}
|
|
4469
|
-
const worktrees = readWorktrees(seams, {
|
|
4470
|
-
home: settings.home,
|
|
4471
|
-
cwd: settings.cwd,
|
|
4472
|
-
projectRoot: settings.projectRoot,
|
|
4473
|
-
idleDays: settings.worktreeIdleDays,
|
|
4474
|
-
now: settings.now,
|
|
4475
|
-
mergedBranches: groups.merged.map((row) => row.branch.name)
|
|
4476
|
-
});
|
|
4477
|
-
if (!worktrees.ok) {
|
|
4478
|
-
return worktrees;
|
|
4479
|
-
}
|
|
4480
|
-
return {
|
|
4481
|
-
ok: true,
|
|
4482
|
-
base: groups.base,
|
|
4483
|
-
fetched,
|
|
4484
|
-
merged: groups.merged,
|
|
4485
|
-
stale: groups.stale,
|
|
4486
|
-
notPushed: groups.notPushed,
|
|
4487
|
-
worktrees: worktrees.worktrees,
|
|
4488
|
-
notes: [...notes, ...groups.notes]
|
|
4489
|
-
};
|
|
4490
|
-
}
|
|
4491
|
-
function cleanupCounts(read) {
|
|
4492
|
-
return {
|
|
4493
|
-
merged: read.merged.length,
|
|
4494
|
-
stale: read.stale.length,
|
|
4495
|
-
notPushed: read.notPushed.length,
|
|
4496
|
-
worktrees: read.worktrees.length
|
|
4497
|
-
};
|
|
4498
|
-
}
|
|
4499
|
-
function firstLine2(text) {
|
|
4500
|
-
return text.split(`
|
|
4501
|
-
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
4502
|
-
}
|
|
4503
|
-
|
|
4504
3816
|
// src/cli/prompt/multi-select.ts
|
|
4505
3817
|
var NONE_CHECKED_TEXT = "(none)";
|
|
4506
3818
|
var CHECKED_MARK = "\u25C9";
|
|
@@ -4531,8 +3843,8 @@ function scrollTo2(top, cursor, pageSize) {
|
|
|
4531
3843
|
return cursor - pageSize + 1;
|
|
4532
3844
|
return top;
|
|
4533
3845
|
}
|
|
4534
|
-
function eraseFrame3(
|
|
4535
|
-
return
|
|
3846
|
+
function eraseFrame3(lines) {
|
|
3847
|
+
return lines > 1 ? `\x1B[${lines - 1}A${CLEAR_DOWN3}` : CLEAR_DOWN3;
|
|
4536
3848
|
}
|
|
4537
3849
|
function move2(state, step, pageSize) {
|
|
4538
3850
|
const count = state.matches.length;
|
|
@@ -4602,19 +3914,19 @@ function layoutOf(groups) {
|
|
|
4602
3914
|
const choices = [];
|
|
4603
3915
|
const groupOf = [];
|
|
4604
3916
|
const lineOf = [];
|
|
4605
|
-
const
|
|
3917
|
+
const lines = [];
|
|
4606
3918
|
groups.forEach((group, groupIndex) => {
|
|
4607
3919
|
if (group.choices.length === 0)
|
|
4608
3920
|
return;
|
|
4609
|
-
|
|
3921
|
+
lines.push({ heading: group.title });
|
|
4610
3922
|
for (const choice of group.choices) {
|
|
4611
|
-
lineOf.push(
|
|
4612
|
-
|
|
3923
|
+
lineOf.push(lines.length);
|
|
3924
|
+
lines.push({ index: choices.length });
|
|
4613
3925
|
groupOf.push(groupIndex);
|
|
4614
3926
|
choices.push(choice);
|
|
4615
3927
|
}
|
|
4616
3928
|
});
|
|
4617
|
-
return { choices, groupOf, lineOf, lines
|
|
3929
|
+
return { choices, groupOf, lineOf, lines };
|
|
4618
3930
|
}
|
|
4619
3931
|
function tickable(choice) {
|
|
4620
3932
|
return choice !== undefined && choice.disabled === undefined;
|
|
@@ -4749,7 +4061,7 @@ var CLEANUP_GROUP_TITLES = Object.freeze({
|
|
|
4749
4061
|
});
|
|
4750
4062
|
var NO_ROWS_TEXT = "(none)";
|
|
4751
4063
|
var UNKNOWN_DATE = "unknown";
|
|
4752
|
-
var
|
|
4064
|
+
var DEFAULT_REMOTE = "origin";
|
|
4753
4065
|
var ROW_INDENT = " ";
|
|
4754
4066
|
var COLUMN_GAP = " ";
|
|
4755
4067
|
function cleanupDate(date) {
|
|
@@ -4758,7 +4070,7 @@ function cleanupDate(date) {
|
|
|
4758
4070
|
function remoteDeleteLine(branch, upstream) {
|
|
4759
4071
|
const slash = upstream === null ? -1 : upstream.indexOf("/");
|
|
4760
4072
|
if (upstream === null || slash <= 0 || slash === upstream.length - 1) {
|
|
4761
|
-
return `git push ${
|
|
4073
|
+
return `git push ${DEFAULT_REMOTE} --delete ${shellQuote(branch)}`;
|
|
4762
4074
|
}
|
|
4763
4075
|
const remote = upstream.slice(0, slash);
|
|
4764
4076
|
const name = upstream.slice(slash + 1);
|
|
@@ -5097,8 +4409,8 @@ var describe_default = Object.freeze(describeCommand);
|
|
|
5097
4409
|
import { basename as basename5, resolve as resolve3 } from "path";
|
|
5098
4410
|
|
|
5099
4411
|
// src/board/status.ts
|
|
5100
|
-
import { statSync as
|
|
5101
|
-
import { join as
|
|
4412
|
+
import { statSync as statSync3 } from "fs";
|
|
4413
|
+
import { join as join6 } from "path";
|
|
5102
4414
|
var ROADMAP_ROW_NAME = `${ROADMAP_TITLE} issue`;
|
|
5103
4415
|
function rowOf(kind, name, outcome, detail) {
|
|
5104
4416
|
return { kind, name, outcome, detail };
|
|
@@ -5118,11 +4430,11 @@ async function readLabelRows(gh) {
|
|
|
5118
4430
|
return Object.freeze(BOARD_LABELS.map((label) => missing.includes(label) ? rowOf("label", label.name, "missing", "the repository carries no label of that name") : rowOf("label", label.name, "present", "the repository carries it")));
|
|
5119
4431
|
}
|
|
5120
4432
|
function readTemplateRow(root) {
|
|
5121
|
-
const path =
|
|
4433
|
+
const path = join6(root, SPEC_TEMPLATE_PATH);
|
|
5122
4434
|
const name = SPEC_TEMPLATE_PATH;
|
|
5123
4435
|
let found;
|
|
5124
4436
|
try {
|
|
5125
|
-
found =
|
|
4437
|
+
found = statSync3(path);
|
|
5126
4438
|
} catch {
|
|
5127
4439
|
return rowOf("template", name, "missing", "the repository carries no spec issue template");
|
|
5128
4440
|
}
|
|
@@ -5320,21 +4632,21 @@ function renderDoctorEpics(report2) {
|
|
|
5320
4632
|
}
|
|
5321
4633
|
|
|
5322
4634
|
// src/commands/init-board.ts
|
|
5323
|
-
import { join as
|
|
4635
|
+
import { join as join8 } from "path";
|
|
5324
4636
|
|
|
5325
4637
|
// src/board/epic-guard.ts
|
|
5326
|
-
import { existsSync as
|
|
5327
|
-
import { dirname, join as
|
|
4638
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
4639
|
+
import { dirname, join as join7 } from "path";
|
|
5328
4640
|
import { fileURLToPath } from "url";
|
|
5329
4641
|
var MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
5330
|
-
var EPIC_GUARD_PATH =
|
|
4642
|
+
var EPIC_GUARD_PATH = join7(".github", "workflows", "epic-guard.yml");
|
|
5331
4643
|
var EPIC_GUARD_FILE = "epic-guard.yml";
|
|
5332
4644
|
function epicGuardSource(moduleDir = MODULE_DIR) {
|
|
5333
|
-
return
|
|
4645
|
+
return join7(moduleDir, TEMPLATES_DIRNAME, EPIC_GUARD_FILE);
|
|
5334
4646
|
}
|
|
5335
4647
|
function readEpicGuard(moduleDir = MODULE_DIR) {
|
|
5336
4648
|
const source = epicGuardSource(moduleDir);
|
|
5337
|
-
if (!
|
|
4649
|
+
if (!existsSync3(source)) {
|
|
5338
4650
|
throw new Error(`board setup: the epic guard workflow is missing: no file at ${source}`);
|
|
5339
4651
|
}
|
|
5340
4652
|
return readFileSync5(source, "utf8");
|
|
@@ -5434,14 +4746,14 @@ function toNativeMembers(readings, epic) {
|
|
|
5434
4746
|
}
|
|
5435
4747
|
function checklistWrite(epic, members) {
|
|
5436
4748
|
const named = new Set(parseRoadmapBody(epic.body).map((line) => line.issue));
|
|
5437
|
-
const
|
|
5438
|
-
if (
|
|
4749
|
+
const lines = members.filter((member) => !named.has(member.number)).map((member) => Object.freeze({ issue: member.number, why: member.title, ticked: member.state === "CLOSED" }));
|
|
4750
|
+
if (lines.length === 0)
|
|
5439
4751
|
return [];
|
|
5440
4752
|
return [Object.freeze({
|
|
5441
4753
|
kind: "checklist",
|
|
5442
4754
|
epic: epic.number,
|
|
5443
|
-
lines: Object.freeze(
|
|
5444
|
-
what: `put ${
|
|
4755
|
+
lines: Object.freeze(lines),
|
|
4756
|
+
what: `put ${lines.map((line) => ref(line.issue)).join(", ")} on epic ${ref(epic.number)}'s checklist`
|
|
5445
4757
|
})];
|
|
5446
4758
|
}
|
|
5447
4759
|
function toLabelsMembers(readings, epic) {
|
|
@@ -5567,9 +4879,9 @@ function withoutBlockedLine(issue, body) {
|
|
|
5567
4879
|
const { line } = readBlockedBy(issue, body);
|
|
5568
4880
|
if (line === null)
|
|
5569
4881
|
return body;
|
|
5570
|
-
const
|
|
4882
|
+
const lines = body.split(`
|
|
5571
4883
|
`);
|
|
5572
|
-
return [...
|
|
4884
|
+
return [...lines.slice(0, line - 1), ...lines.slice(line)].join(`
|
|
5573
4885
|
`);
|
|
5574
4886
|
}
|
|
5575
4887
|
async function sendWrite(gh, bodies, write) {
|
|
@@ -5806,7 +5118,7 @@ async function runEpicGuardStep(options) {
|
|
|
5806
5118
|
part: writeEpicGuard(root, moduleDir),
|
|
5807
5119
|
warnings: []
|
|
5808
5120
|
});
|
|
5809
|
-
if (anythingAt(
|
|
5121
|
+
if (anythingAt(join8(root, EPIC_GUARD_PATH)))
|
|
5810
5122
|
return write(false);
|
|
5811
5123
|
if (wanted === true)
|
|
5812
5124
|
return write(false);
|
|
@@ -5838,9 +5150,9 @@ function moveQuestion(plan2) {
|
|
|
5838
5150
|
function moveUnreadWarning(to, problem) {
|
|
5839
5151
|
return `board.relationships is ${to}: the board could not be read for relationships to move, so nothing` + ` was moved: ${problem}; run ${MOVE_FIX} again.`;
|
|
5840
5152
|
}
|
|
5841
|
-
function moveFailureWarning(result2,
|
|
5153
|
+
function moveFailureWarning(result2, failure) {
|
|
5842
5154
|
const left = result2.left.length;
|
|
5843
|
-
return `the board refused "${
|
|
5155
|
+
return `the board refused "${failure.what}": ${failure.problem}. ${String(result2.done.length)} write(s) went` + ` through and ${String(left)} did not, and the old marks were kept; every write is idempotent, so run` + ` ${MOVE_FIX} again to finish the move.`;
|
|
5844
5156
|
}
|
|
5845
5157
|
function planLines(plan2) {
|
|
5846
5158
|
return [
|
|
@@ -6071,9 +5383,9 @@ function renderDoctorRelations(report2) {
|
|
|
6071
5383
|
}
|
|
6072
5384
|
|
|
6073
5385
|
// src/commands/doctor-render.ts
|
|
6074
|
-
import { basename as basename3, relative as relative3, sep
|
|
5386
|
+
import { basename as basename3, relative as relative3, sep } from "path";
|
|
6075
5387
|
function shownPath(path, root) {
|
|
6076
|
-
return path.startsWith(`${root}${
|
|
5388
|
+
return path.startsWith(`${root}${sep}`) ? relative3(root, path) : path;
|
|
6077
5389
|
}
|
|
6078
5390
|
function checkedPhrase(count, automatic, whose) {
|
|
6079
5391
|
if (count === 0)
|
|
@@ -6368,11 +5680,11 @@ function readSessionEnv(seams) {
|
|
|
6368
5680
|
}
|
|
6369
5681
|
|
|
6370
5682
|
// src/plan/needs.ts
|
|
6371
|
-
import { statSync as
|
|
6372
|
-
import { join as
|
|
5683
|
+
import { statSync as statSync4 } from "fs";
|
|
5684
|
+
import { join as join10 } from "path";
|
|
6373
5685
|
|
|
6374
5686
|
// src/inventory/mcp.ts
|
|
6375
|
-
import { join as
|
|
5687
|
+
import { join as join9 } from "path";
|
|
6376
5688
|
var MCP_JSON_FILE = ".mcp.json";
|
|
6377
5689
|
var CLAUDE_JSON_FILE = ".claude.json";
|
|
6378
5690
|
var MCP_SERVERS_KEY = "mcpServers";
|
|
@@ -6436,12 +5748,12 @@ function readRejections(seams, warnings) {
|
|
|
6436
5748
|
}
|
|
6437
5749
|
function readMcpServers(seams) {
|
|
6438
5750
|
const warnings = [];
|
|
6439
|
-
const claudeJsonPath =
|
|
5751
|
+
const claudeJsonPath = join9(seams.home, CLAUDE_JSON_FILE);
|
|
6440
5752
|
const claudeJson = readFile(claudeJsonPath, warnings);
|
|
6441
5753
|
const projects = claudeJson === null ? null : readMapping(claudeJson, PROJECTS_KEY, claudeJsonPath, warnings);
|
|
6442
5754
|
const root = seams.projectRoot;
|
|
6443
5755
|
const projectEntry = projects === null || root === null || !Object.hasOwn(projects, root) ? null : readMapping(projects, root, claudeJsonPath, warnings);
|
|
6444
|
-
const mcpJsonPath = root === null ? null :
|
|
5756
|
+
const mcpJsonPath = root === null ? null : join9(root, MCP_JSON_FILE);
|
|
6445
5757
|
const projectNames = mcpJsonPath === null ? [] : serverNames(readFile(mcpJsonPath, warnings), mcpJsonPath, warnings);
|
|
6446
5758
|
const held = {
|
|
6447
5759
|
local: { path: claudeJsonPath, names: serverNames(projectEntry, claudeJsonPath, warnings) },
|
|
@@ -6537,7 +5849,7 @@ function fencePrograms(text) {
|
|
|
6537
5849
|
function programDirectory(name, pathDirs) {
|
|
6538
5850
|
return pathDirs.find((dir) => {
|
|
6539
5851
|
try {
|
|
6540
|
-
const stats =
|
|
5852
|
+
const stats = statSync4(join10(dir, name));
|
|
6541
5853
|
return stats.isFile() && (stats.mode & 73) !== 0;
|
|
6542
5854
|
} catch {
|
|
6543
5855
|
return false;
|
|
@@ -6545,13 +5857,13 @@ function programDirectory(name, pathDirs) {
|
|
|
6545
5857
|
}) ?? null;
|
|
6546
5858
|
}
|
|
6547
5859
|
function holdersOf2(records) {
|
|
6548
|
-
const
|
|
5860
|
+
const holders = new Map;
|
|
6549
5861
|
for (const record of records) {
|
|
6550
5862
|
const key = needKey(record.kind, record.name);
|
|
6551
|
-
if (!
|
|
6552
|
-
|
|
5863
|
+
if (!holders.has(key))
|
|
5864
|
+
holders.set(key, record);
|
|
6553
5865
|
}
|
|
6554
|
-
return
|
|
5866
|
+
return holders;
|
|
6555
5867
|
}
|
|
6556
5868
|
function needKey(kind, name) {
|
|
6557
5869
|
return `${kind}\x00${name}`;
|
|
@@ -6677,9 +5989,9 @@ function programSearchPath(program, seams) {
|
|
|
6677
5989
|
function stackMarker(tool, projectRoot2) {
|
|
6678
5990
|
if (projectRoot2 === null)
|
|
6679
5991
|
return null;
|
|
6680
|
-
const found = tool.markers.map((marker) =>
|
|
5992
|
+
const found = tool.markers.map((marker) => join10(projectRoot2, marker)).find((path) => {
|
|
6681
5993
|
try {
|
|
6682
|
-
return
|
|
5994
|
+
return statSync4(path).isFile();
|
|
6683
5995
|
} catch {
|
|
6684
5996
|
return false;
|
|
6685
5997
|
}
|
|
@@ -6766,17 +6078,17 @@ function wholeWord(name) {
|
|
|
6766
6078
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6767
6079
|
return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
|
|
6768
6080
|
}
|
|
6769
|
-
function firstMention(
|
|
6081
|
+
function firstMention(lines, name) {
|
|
6770
6082
|
const pattern = wholeWord(name);
|
|
6771
|
-
const index =
|
|
6083
|
+
const index = lines.findIndex((line) => pattern.test(line));
|
|
6772
6084
|
return index === -1 ? null : index + 1;
|
|
6773
6085
|
}
|
|
6774
6086
|
async function readSpecNeeds(specPath, seams) {
|
|
6775
|
-
const
|
|
6087
|
+
const lines = (await readRequired(specPath)).split(`
|
|
6776
6088
|
`);
|
|
6777
6089
|
return readAgainstMachine(seams, (machine) => {
|
|
6778
6090
|
const named = [...machine.holders.values()].flatMap((holder) => {
|
|
6779
|
-
const line = firstMention(
|
|
6091
|
+
const line = firstMention(lines, holder.name);
|
|
6780
6092
|
return line === null ? [] : [{ kind: holder.kind, name: holder.name, origin: { by: "mentioned", line } }];
|
|
6781
6093
|
});
|
|
6782
6094
|
return Promise.resolve({ named, stacks: [], warnings: [] });
|
|
@@ -7176,11 +6488,11 @@ var UNREAD_NONE = Object.freeze({ provider: "none", source: "config", remote: nu
|
|
|
7176
6488
|
function trackerChain(config) {
|
|
7177
6489
|
return [...new Set([config.trackerDefault, ...config.trackerFallback])];
|
|
7178
6490
|
}
|
|
7179
|
-
function
|
|
7180
|
-
const
|
|
6491
|
+
function firstLine(result2) {
|
|
6492
|
+
const lines = `${result2.stderr}
|
|
7181
6493
|
${result2.stdout}`.split(`
|
|
7182
6494
|
`);
|
|
7183
|
-
return
|
|
6495
|
+
return lines.map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
7184
6496
|
}
|
|
7185
6497
|
function activeLoginOf(stdout, host) {
|
|
7186
6498
|
let payload;
|
|
@@ -7197,7 +6509,7 @@ function activeLoginOf(stdout, host) {
|
|
|
7197
6509
|
}
|
|
7198
6510
|
async function readLoginAndPermission(gh, host) {
|
|
7199
6511
|
const status = await gh(["auth", "status", "--active", "--hostname", host, "--json", "hosts"]);
|
|
7200
|
-
const login = status.ok ? activeLoginOf(status.stdout, host) : { login: null, detail: `gh auth status --json hosts failed: ${
|
|
6512
|
+
const login = status.ok ? activeLoginOf(status.stdout, host) : { login: null, detail: `gh auth status --json hosts failed: ${firstLine(status) || "it wrote nothing"}` };
|
|
7201
6513
|
if (login.login === null)
|
|
7202
6514
|
return { login, permission: null };
|
|
7203
6515
|
const permission = await readAuthorTrust({
|
|
@@ -7215,7 +6527,7 @@ async function probeGh(seams, host) {
|
|
|
7215
6527
|
const openGh = seams.openGh ?? createGhRunner;
|
|
7216
6528
|
const gh = openGh({ cwd, command: found, env });
|
|
7217
6529
|
const status = await gh(["auth", "status", "--hostname", host]);
|
|
7218
|
-
const auth = { ok: status.ok, detail:
|
|
6530
|
+
const auth = { ok: status.ok, detail: firstLine(status) };
|
|
7219
6531
|
if (!auth.ok)
|
|
7220
6532
|
return { host, found, auth, login: null, permission: null };
|
|
7221
6533
|
return { host, found, auth, ...await readLoginAndPermission(gh, host) };
|
|
@@ -7299,7 +6611,7 @@ function renderDeepSection(section) {
|
|
|
7299
6611
|
}
|
|
7300
6612
|
|
|
7301
6613
|
// src/commands/doctor-deep-settings.ts
|
|
7302
|
-
import { join as
|
|
6614
|
+
import { join as join11 } from "path";
|
|
7303
6615
|
var SETTINGS_SECTION_TITLE = "Settings";
|
|
7304
6616
|
var SETTING_SOURCES_KEY = "loop.settingSources";
|
|
7305
6617
|
function addSourceFix(source) {
|
|
@@ -7364,7 +6676,7 @@ function declarationReason(declaration, answering, seams) {
|
|
|
7364
6676
|
if (!sources.includes(scope))
|
|
7365
6677
|
return { why: sourceOffWhy(scope, sources), fix: addSourceFix(scope) };
|
|
7366
6678
|
if (declaration.disabled) {
|
|
7367
|
-
const file =
|
|
6679
|
+
const file = join11(seams.home, CLAUDE_JSON_FILE);
|
|
7368
6680
|
const entry = `${PROJECTS_KEY}["${seams.projectRoot ?? ""}"]`;
|
|
7369
6681
|
return {
|
|
7370
6682
|
why: `${DISABLE_KEY} names it in ${file}`,
|
|
@@ -7592,7 +6904,7 @@ function renderDeep(reading) {
|
|
|
7592
6904
|
var DOCTOR_DESCRIPTION = "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It prints its `effort sync` row, the strategy `effort.sync` names (`local` when unset) and" + " whether core or a loaded module serves it, and exits 1 when no adapter serves it, naming for" + " `git`, `service` and `p2p` the `modules:` and `allowList:` lines that load a module providing it." + " It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. Where the release is on, it then prints one `Release:` row naming" + " the version `origin/<pr.base>` declares as last fetched, the latest release tag, the version the changelog's" + " top heading names and the change fragments waiting on the base, as a warning naming `rafa release settle`" + " while any wait; it fetches nothing and never changes the exit code." + " It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.";
|
|
7593
6905
|
|
|
7594
6906
|
// src/commands/doctor-effort-schema.ts
|
|
7595
|
-
import { join as
|
|
6907
|
+
import { join as join12 } from "path";
|
|
7596
6908
|
var EFFORT_SCHEMA_ROW = "effort store schema";
|
|
7597
6909
|
var SCHEMA_COMMAND = "rafa effort schema";
|
|
7598
6910
|
function developmentAppliedWarning(path, id2, appliedBy) {
|
|
@@ -7617,7 +6929,7 @@ function readDoctorEffortSchema(root, env, options = {}) {
|
|
|
7617
6929
|
const live = override === undefined || override === "";
|
|
7618
6930
|
let path;
|
|
7619
6931
|
try {
|
|
7620
|
-
path =
|
|
6932
|
+
path = join12(effortStoreDir(root, env), SQLITE_STORE_FILE_NAME);
|
|
7621
6933
|
} catch (error) {
|
|
7622
6934
|
return unreadable(null, live, messageOf(error));
|
|
7623
6935
|
}
|
|
@@ -7655,9 +6967,9 @@ function effortSchemaRefusal(reading) {
|
|
|
7655
6967
|
}
|
|
7656
6968
|
|
|
7657
6969
|
// src/effort/store/legacy.ts
|
|
7658
|
-
import { statSync as
|
|
7659
|
-
import { join as
|
|
7660
|
-
var LEGACY_EFFORT_STORE_DIR =
|
|
6970
|
+
import { statSync as statSync5 } from "fs";
|
|
6971
|
+
import { join as join13 } from "path";
|
|
6972
|
+
var LEGACY_EFFORT_STORE_DIR = join13(".ralph", "effort");
|
|
7661
6973
|
var NDJSON_KINDS = { sessions: true, commits: true };
|
|
7662
6974
|
var STORE_FILE_NAMES = Object.freeze([
|
|
7663
6975
|
SQLITE_STORE_FILE_NAME,
|
|
@@ -7670,7 +6982,7 @@ function isAbsence(error) {
|
|
|
7670
6982
|
}
|
|
7671
6983
|
function isStoreFile(path) {
|
|
7672
6984
|
try {
|
|
7673
|
-
return
|
|
6985
|
+
return statSync5(path).isFile();
|
|
7674
6986
|
} catch (error) {
|
|
7675
6987
|
if (isAbsence(error))
|
|
7676
6988
|
return false;
|
|
@@ -7678,7 +6990,7 @@ function isStoreFile(path) {
|
|
|
7678
6990
|
}
|
|
7679
6991
|
}
|
|
7680
6992
|
function storeFilesIn(dir) {
|
|
7681
|
-
return Object.freeze(STORE_FILE_NAMES.filter((name) => isStoreFile(
|
|
6993
|
+
return Object.freeze(STORE_FILE_NAMES.filter((name) => isStoreFile(join13(dir, name))));
|
|
7682
6994
|
}
|
|
7683
6995
|
function shellQuoted(path) {
|
|
7684
6996
|
return `'${path.replaceAll("'", "'\\''")}'`;
|
|
@@ -7688,8 +7000,8 @@ function legacyWarning(legacyDir, storeDir, legacyFiles) {
|
|
|
7688
7000
|
return `${legacyDir} holds an effort store (${legacyFiles.join(", ")}) and ${storeDir} holds none;` + ` rafa reads ${storeDir} alone, so its reports start from an empty store. Once no loop is running,` + ` move it: ${move3}`;
|
|
7689
7001
|
}
|
|
7690
7002
|
function readLegacyStore(root) {
|
|
7691
|
-
const legacyDir =
|
|
7692
|
-
const storeDir =
|
|
7003
|
+
const legacyDir = join13(root, LEGACY_EFFORT_STORE_DIR);
|
|
7004
|
+
const storeDir = join13(root, EFFORT_STORE_DIR);
|
|
7693
7005
|
const legacyFiles = storeFilesIn(legacyDir);
|
|
7694
7006
|
const storeFiles = storeFilesIn(storeDir);
|
|
7695
7007
|
const warning = legacyFiles.length > 0 && storeFiles.length === 0 ? legacyWarning(legacyDir, storeDir, legacyFiles) : null;
|
|
@@ -7697,7 +7009,7 @@ function readLegacyStore(root) {
|
|
|
7697
7009
|
}
|
|
7698
7010
|
|
|
7699
7011
|
// src/project/pre-init-dirs.ts
|
|
7700
|
-
import { normalize, sep as
|
|
7012
|
+
import { normalize, sep as sep2 } from "path";
|
|
7701
7013
|
var PRE_INIT_DIRS = Object.freeze([
|
|
7702
7014
|
Object.freeze({
|
|
7703
7015
|
setting: "planDir",
|
|
@@ -7714,7 +7026,7 @@ var PRE_INIT_DIRS = Object.freeze([
|
|
|
7714
7026
|
]);
|
|
7715
7027
|
function compared(value) {
|
|
7716
7028
|
const normalized = normalize(value);
|
|
7717
|
-
return normalized.endsWith(
|
|
7029
|
+
return normalized.endsWith(sep2) && normalized.length > sep2.length ? normalized.slice(0, -sep2.length) : normalized;
|
|
7718
7030
|
}
|
|
7719
7031
|
function preInitWarning(found) {
|
|
7720
7032
|
if (found.length === 0)
|
|
@@ -7792,11 +7104,11 @@ function writeInstall(context, install) {
|
|
|
7792
7104
|
|
|
7793
7105
|
// src/commands/doctor-release.ts
|
|
7794
7106
|
import { readFileSync as readFileSync6 } from "fs";
|
|
7795
|
-
import { join as
|
|
7107
|
+
import { join as join14 } from "path";
|
|
7796
7108
|
var SETTLE_COMMAND = "rafa release settle";
|
|
7797
7109
|
var UNREAD = "?";
|
|
7798
7110
|
function readTopHeading(root, configured) {
|
|
7799
|
-
const resolved =
|
|
7111
|
+
const resolved = join14(root, configured);
|
|
7800
7112
|
try {
|
|
7801
7113
|
const text = readFileSync6(resolved, "utf8");
|
|
7802
7114
|
return { path: configured, version: changelogVersions(text)[0] ?? null, problem: null };
|
|
@@ -7880,7 +7192,7 @@ function writeDoctorRelease(context, reading) {
|
|
|
7880
7192
|
}
|
|
7881
7193
|
|
|
7882
7194
|
// src/commands/doctor-tiers.ts
|
|
7883
|
-
import { readFileSync as readFileSync7, realpathSync as
|
|
7195
|
+
import { readFileSync as readFileSync7, realpathSync as realpathSync2 } from "fs";
|
|
7884
7196
|
import { basename as basename4, dirname as dirname2 } from "path";
|
|
7885
7197
|
var TIERS_SECTION_TITLE = "Skill tiers";
|
|
7886
7198
|
var CLAUDE_VERSION_TIMEOUT_MS = 1e4;
|
|
@@ -7894,7 +7206,7 @@ function readTextFile(path) {
|
|
|
7894
7206
|
}
|
|
7895
7207
|
function realPathOf(path) {
|
|
7896
7208
|
try {
|
|
7897
|
-
return
|
|
7209
|
+
return realpathSync2(path);
|
|
7898
7210
|
} catch {
|
|
7899
7211
|
return path;
|
|
7900
7212
|
}
|
|
@@ -8023,8 +7335,8 @@ function renderDoctorTiers(reading) {
|
|
|
8023
7335
|
// src/commands/doctor.ts
|
|
8024
7336
|
var DEFAULT_DOCTOR_SEAMS = Object.freeze({ checks: Object.freeze({}) });
|
|
8025
7337
|
var NOTHING_CHECKED = "Nothing was checked.";
|
|
8026
|
-
function refusal3(
|
|
8027
|
-
return new CommandExit(1, [...
|
|
7338
|
+
function refusal3(lines) {
|
|
7339
|
+
return new CommandExit(1, [...lines, NOTHING_CHECKED].join(`
|
|
8028
7340
|
`));
|
|
8029
7341
|
}
|
|
8030
7342
|
function expectNoArgument2(args) {
|
|
@@ -8156,8 +7468,8 @@ function doctorRefusal(halt, rows) {
|
|
|
8156
7468
|
`rafa doctor: ${String(halt)}`,
|
|
8157
7469
|
"rafa loop start would halt here, before any session. No run was started and nothing was stored."
|
|
8158
7470
|
];
|
|
8159
|
-
const
|
|
8160
|
-
const all = [...
|
|
7471
|
+
const lines = halt === null ? [] : halted;
|
|
7472
|
+
const all = [...lines, ...failed2];
|
|
8161
7473
|
return all.length === 0 ? null : new CommandExit(1, all.join(`
|
|
8162
7474
|
`));
|
|
8163
7475
|
}
|
|
@@ -8200,10 +7512,10 @@ function projectOf7(context) {
|
|
|
8200
7512
|
throw new Error("rafa doctor runs inside a project, and was handed none");
|
|
8201
7513
|
return context.project;
|
|
8202
7514
|
}
|
|
8203
|
-
function writeText(context,
|
|
7515
|
+
function writeText(context, lines) {
|
|
8204
7516
|
if (context.outputMode === "json")
|
|
8205
7517
|
return;
|
|
8206
|
-
for (const line of
|
|
7518
|
+
for (const line of lines)
|
|
8207
7519
|
context.output.info(line);
|
|
8208
7520
|
}
|
|
8209
7521
|
async function announceRisk(context, preflight, seams) {
|
|
@@ -8389,13 +7701,13 @@ var collect_default = wrapPhaseZeroCommand({
|
|
|
8389
7701
|
}, collect);
|
|
8390
7702
|
|
|
8391
7703
|
// src/commands/effort/copy.ts
|
|
8392
|
-
import { join as
|
|
7704
|
+
import { join as join15, resolve as resolve4 } from "path";
|
|
8393
7705
|
var COMMAND_NAME5 = "rafa effort copy";
|
|
8394
7706
|
var COPY_USAGE = "rafa effort copy [--to=<dir>]";
|
|
8395
7707
|
var TO_FLAG2 = "to";
|
|
8396
7708
|
var REFUSED_EXIT = 1;
|
|
8397
7709
|
var FAILED_EXIT = 2;
|
|
8398
|
-
var SCRATCH_DIR =
|
|
7710
|
+
var SCRATCH_DIR = join15(".rafa", "scratch");
|
|
8399
7711
|
function effortDirLine(directory) {
|
|
8400
7712
|
return `${EFFORT_DIR_VARIABLE}=${directory}`;
|
|
8401
7713
|
}
|
|
@@ -8427,10 +7739,10 @@ function runCopy(context, seams) {
|
|
|
8427
7739
|
const root = requireProject(context, COMMAND_NAME5).root;
|
|
8428
7740
|
const named = readTarget(context, root);
|
|
8429
7741
|
refuseEffortDirOverride(context);
|
|
8430
|
-
const target = named ??
|
|
7742
|
+
const target = named ?? join15(root, SCRATCH_DIR, `effort-${fileStamp((seams.now ?? (() => new Date))())}`);
|
|
8431
7743
|
let result2;
|
|
8432
7744
|
try {
|
|
8433
|
-
result2 = copyEffortStore({ source:
|
|
7745
|
+
result2 = copyEffortStore({ source: join15(root, EFFORT_STORE_DIR), target });
|
|
8434
7746
|
} catch (error) {
|
|
8435
7747
|
if (error instanceof EffortCopyRefusal)
|
|
8436
7748
|
throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME5}: ${error.message}`);
|
|
@@ -8574,7 +7886,7 @@ function formatDashboard(dashboard) {
|
|
|
8574
7886
|
}
|
|
8575
7887
|
|
|
8576
7888
|
// src/effort/dashboard-status.ts
|
|
8577
|
-
var
|
|
7889
|
+
var MS_PER_SECOND = 1000;
|
|
8578
7890
|
var SECONDS_PER_MINUTE = 60;
|
|
8579
7891
|
function tasksLeft(counts) {
|
|
8580
7892
|
return counts.open + counts.blocked;
|
|
@@ -8592,7 +7904,7 @@ function byTaskEstimate(row2, counts) {
|
|
|
8592
7904
|
function byProgressEstimate(planStartedAt, counts, now) {
|
|
8593
7905
|
if (counts.done === 0)
|
|
8594
7906
|
return null;
|
|
8595
|
-
const elapsedSeconds = Math.max(0, (now.getTime() - Date.parse(planStartedAt)) /
|
|
7907
|
+
const elapsedSeconds = Math.max(0, (now.getTime() - Date.parse(planStartedAt)) / MS_PER_SECOND);
|
|
8596
7908
|
const estimate = estimateFrom(elapsedSeconds / counts.done, tasksLeft(counts));
|
|
8597
7909
|
return estimate === null ? null : { ...estimate, planStartedAt, elapsedSeconds: Math.round(elapsedSeconds) };
|
|
8598
7910
|
}
|
|
@@ -8802,7 +8114,7 @@ var dashboard_default = createEffortDashboardCommand();
|
|
|
8802
8114
|
import { resolve as resolve6 } from "path";
|
|
8803
8115
|
|
|
8804
8116
|
// src/commands/effort/merge.ts
|
|
8805
|
-
import { join as
|
|
8117
|
+
import { join as join16, resolve as resolve5 } from "path";
|
|
8806
8118
|
var COMMAND_NAME7 = "rafa effort merge";
|
|
8807
8119
|
var MERGE_USAGE = "rafa effort merge <file> [--dry-run]";
|
|
8808
8120
|
var DRY_RUN_FLAG2 = "dry-run";
|
|
@@ -8841,7 +8153,7 @@ function renderMerge(result2, command = COMMAND_NAME7) {
|
|
|
8841
8153
|
}
|
|
8842
8154
|
function mergeTargetPath(context, root, command = COMMAND_NAME7) {
|
|
8843
8155
|
try {
|
|
8844
|
-
return
|
|
8156
|
+
return join16(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8845
8157
|
} catch (error) {
|
|
8846
8158
|
throw new CommandExit(REFUSED_EXIT2, `\u274C ${command}: ${messageOf(error)}`);
|
|
8847
8159
|
}
|
|
@@ -9012,7 +8324,7 @@ function createImportCommand(seams = {}) {
|
|
|
9012
8324
|
var import_default = createImportCommand();
|
|
9013
8325
|
|
|
9014
8326
|
// src/commands/effort/migrate.ts
|
|
9015
|
-
import { join as
|
|
8327
|
+
import { join as join17 } from "path";
|
|
9016
8328
|
var COMMAND_NAME9 = "rafa effort migrate";
|
|
9017
8329
|
var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
|
|
9018
8330
|
var DRY_RUN_FLAG4 = "dry-run";
|
|
@@ -9045,7 +8357,7 @@ function renderMigrate(result2) {
|
|
|
9045
8357
|
}
|
|
9046
8358
|
function storePath(context, root) {
|
|
9047
8359
|
try {
|
|
9048
|
-
return
|
|
8360
|
+
return join17(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
9049
8361
|
} catch (error) {
|
|
9050
8362
|
throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME9}: ${messageOf(error)}`);
|
|
9051
8363
|
}
|
|
@@ -9141,17 +8453,17 @@ function namesStore(text) {
|
|
|
9141
8453
|
return isMapping(document) && Object.hasOwn(document, "store");
|
|
9142
8454
|
}
|
|
9143
8455
|
function withSqliteStoreSetting(text) {
|
|
9144
|
-
const
|
|
8456
|
+
const lines = text.split(`
|
|
9145
8457
|
`);
|
|
9146
|
-
const at =
|
|
9147
|
-
const commented =
|
|
8458
|
+
const at = lines.findIndex((line) => STORE_KEY.test(line));
|
|
8459
|
+
const commented = lines.findIndex((line) => COMMENTED_STORE_KEY.test(line));
|
|
9148
8460
|
const target = at >= 0 ? at : commented;
|
|
9149
8461
|
if (target >= 0)
|
|
9150
|
-
return [...
|
|
8462
|
+
return [...lines.slice(0, target), STORE_LINE, ...lines.slice(target + 1)].join(`
|
|
9151
8463
|
`);
|
|
9152
8464
|
if (namesStore(text))
|
|
9153
8465
|
return null;
|
|
9154
|
-
const body =
|
|
8466
|
+
const body = lines.at(-1) === "" ? lines.slice(0, -1) : lines;
|
|
9155
8467
|
return [...body, STORE_LINE, ""].join(`
|
|
9156
8468
|
`);
|
|
9157
8469
|
}
|
|
@@ -9401,7 +8713,7 @@ var report_default = wrapPhaseZeroCommand({
|
|
|
9401
8713
|
}, report);
|
|
9402
8714
|
|
|
9403
8715
|
// src/commands/effort/schema.ts
|
|
9404
|
-
import { join as
|
|
8716
|
+
import { join as join18 } from "path";
|
|
9405
8717
|
var COMMAND_NAME11 = "rafa effort schema";
|
|
9406
8718
|
var SCHEMA_USAGE = "rafa effort schema [--check]";
|
|
9407
8719
|
var CHECK_FLAG = "check";
|
|
@@ -9447,7 +8759,7 @@ function renderSchema(report2) {
|
|
|
9447
8759
|
}
|
|
9448
8760
|
function storePath2(context, root) {
|
|
9449
8761
|
try {
|
|
9450
|
-
return
|
|
8762
|
+
return join18(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
9451
8763
|
} catch (error) {
|
|
9452
8764
|
throw new CommandExit(REFUSED_EXIT5, `\u274C ${COMMAND_NAME11}: ${messageOf(error)}`);
|
|
9453
8765
|
}
|
|
@@ -9811,9 +9123,9 @@ function workLines(result2) {
|
|
|
9811
9123
|
return [info(`No terminal to ask on, so its open work is kept: ${workPhrase(work)}.`)];
|
|
9812
9124
|
}
|
|
9813
9125
|
case "closed": {
|
|
9814
|
-
const
|
|
9126
|
+
const lines = result2.closed.map((pull2) => pull2.status === "closed" ? info(`Closed pull request ${ref4(pull2.number)} with a comment.`) : { text: `Could not close pull request ${ref4(pull2.number)}: ${pull2.problem}`, warn: true });
|
|
9815
9127
|
const kept = work.branches.length === 0 ? "No branch was deleted." : `No branch was deleted: ${work.branches.join(", ")}.`;
|
|
9816
|
-
return [...
|
|
9128
|
+
return [...lines, info(kept)];
|
|
9817
9129
|
}
|
|
9818
9130
|
}
|
|
9819
9131
|
}
|
|
@@ -9830,10 +9142,10 @@ function closeFailure(result2) {
|
|
|
9830
9142
|
}
|
|
9831
9143
|
async function runEpicHorizon(context, action, seams) {
|
|
9832
9144
|
const result2 = await changeEpicHorizon(context, action, seams);
|
|
9833
|
-
const
|
|
9145
|
+
const failure = closeFailure(result2);
|
|
9834
9146
|
if (context.outputMode === "json") {
|
|
9835
|
-
if (
|
|
9836
|
-
throw
|
|
9147
|
+
if (failure !== null)
|
|
9148
|
+
throw failure;
|
|
9837
9149
|
context.output.result(result2);
|
|
9838
9150
|
return;
|
|
9839
9151
|
}
|
|
@@ -9843,8 +9155,8 @@ async function runEpicHorizon(context, action, seams) {
|
|
|
9843
9155
|
else
|
|
9844
9156
|
context.output.info(line.text);
|
|
9845
9157
|
}
|
|
9846
|
-
if (
|
|
9847
|
-
throw
|
|
9158
|
+
if (failure !== null)
|
|
9159
|
+
throw failure;
|
|
9848
9160
|
}
|
|
9849
9161
|
|
|
9850
9162
|
// src/commands/epic/move.ts
|
|
@@ -10130,8 +9442,8 @@ function movedLines2(result2, outcome) {
|
|
|
10130
9442
|
};
|
|
10131
9443
|
const work = result2.work === null || result2.work.branches.length + result2.work.pullRequests.length === 0 ? [] : [{ text: `Its open work stays as it is: ${workPhrase(result2.work)}.`, warn: false }];
|
|
10132
9444
|
const comment = outcome.commentProblem === "" ? [] : [{ text: `Could not comment on ${ref5(result2.issue)}: ${outcome.commentProblem}`, warn: true }];
|
|
10133
|
-
const
|
|
10134
|
-
return [head, ...
|
|
9445
|
+
const lines = outcome.added === undefined || outcome.removed === undefined ? [{ text: nativeParentLine(result2.to), warn: false }] : [addedLine(result2, outcome.added), removedLine(result2, outcome.removed)];
|
|
9446
|
+
return [head, ...lines, ...work, ...comment];
|
|
10135
9447
|
}
|
|
10136
9448
|
function renderEpicMove(result2) {
|
|
10137
9449
|
const problems = result2.problems.map((problem) => ({ text: `The open work named may be short: ${problem}`, warn: true }));
|
|
@@ -10158,10 +9470,10 @@ function moveFailure(result2) {
|
|
|
10158
9470
|
}
|
|
10159
9471
|
async function runEpicMove(context, seams) {
|
|
10160
9472
|
const result2 = await moveEpicIssue(context, seams);
|
|
10161
|
-
const
|
|
9473
|
+
const failure = moveFailure(result2);
|
|
10162
9474
|
if (context.outputMode === "json") {
|
|
10163
|
-
if (
|
|
10164
|
-
throw
|
|
9475
|
+
if (failure !== null)
|
|
9476
|
+
throw failure;
|
|
10165
9477
|
context.output.result(result2);
|
|
10166
9478
|
return;
|
|
10167
9479
|
}
|
|
@@ -10171,8 +9483,8 @@ async function runEpicMove(context, seams) {
|
|
|
10171
9483
|
else
|
|
10172
9484
|
context.output.info(out.text);
|
|
10173
9485
|
}
|
|
10174
|
-
if (
|
|
10175
|
-
throw
|
|
9486
|
+
if (failure !== null)
|
|
9487
|
+
throw failure;
|
|
10176
9488
|
}
|
|
10177
9489
|
function createEpicMoveCommand(seams = {}) {
|
|
10178
9490
|
const command = {
|
|
@@ -10370,21 +9682,21 @@ async function applyMove(input, dependent, reading) {
|
|
|
10370
9682
|
try {
|
|
10371
9683
|
const writes = { board: input.writes.board, relations: input.writes.moves, issues: input.issues };
|
|
10372
9684
|
const outcome = await applyEpicMove(writes, reading, cancelMoveReason(input.epic));
|
|
10373
|
-
const
|
|
9685
|
+
const lines = [info(`Moved ${ref6(change.issue)} from epic ${ref6(change.from)} to ${ref6(change.to)}.`)];
|
|
10374
9686
|
const left = [];
|
|
10375
9687
|
if (outcome.added?.status === "failed") {
|
|
10376
|
-
|
|
9688
|
+
lines.push(warn(`Could not add its line to epic ${ref6(change.to)}'s checklist: ${outcome.added.problem}`));
|
|
10377
9689
|
left.push(`add ${ref6(change.issue)}'s line to epic ${ref6(change.to)}'s checklist`);
|
|
10378
9690
|
}
|
|
10379
9691
|
if (outcome.removed?.status === "failed") {
|
|
10380
|
-
|
|
9692
|
+
lines.push(warn(`Could not take its line off epic ${ref6(change.from)}'s checklist: ${outcome.removed.problem}`));
|
|
10381
9693
|
left.push(`take ${ref6(change.issue)}'s line off epic ${ref6(change.from)}'s checklist`);
|
|
10382
9694
|
}
|
|
10383
9695
|
if (outcome.commentProblem !== "") {
|
|
10384
|
-
|
|
9696
|
+
lines.push(warn(`Could not comment on ${ref6(change.issue)}: ${outcome.commentProblem}`));
|
|
10385
9697
|
left.push(`comment the move on ${ref6(change.issue)}`);
|
|
10386
9698
|
}
|
|
10387
|
-
return Object.freeze({ ...base, landed: true, lines
|
|
9699
|
+
return Object.freeze({ ...base, landed: true, lines, left });
|
|
10388
9700
|
} catch (error) {
|
|
10389
9701
|
return Object.freeze({
|
|
10390
9702
|
...base,
|
|
@@ -10416,26 +9728,26 @@ async function applyUnblock(input, dependent) {
|
|
|
10416
9728
|
const still = readUnblockStill(input.relations, input.issues, issue, waitsOn);
|
|
10417
9729
|
const note = renderUnblockNote(input.day, input.epic, waitsOn, still.named);
|
|
10418
9730
|
const edit = await editChecklist({ issue: issue.number, edit: (body) => appendNote(body, note), board: input.writes.bodies });
|
|
10419
|
-
const
|
|
9731
|
+
const lines = [];
|
|
10420
9732
|
const left = [];
|
|
10421
9733
|
if (edit.status === "failed") {
|
|
10422
|
-
|
|
9734
|
+
lines.push(warn(`Could not add the note to ${ref6(issue.number)}'s body: ${edit.problem}`));
|
|
10423
9735
|
left.push(`add the note to ${ref6(issue.number)}'s body: ${note}`);
|
|
10424
9736
|
} else {
|
|
10425
9737
|
const verb = waitsOn.length === 1 ? "blocks" : "block";
|
|
10426
|
-
|
|
9738
|
+
lines.push(info(edit.status === "edited" ? `Unblocked ${ref6(issue.number)}: noted below its body that ${refs(waitsOn)} no longer ${verb} it.` : `Unblocked ${ref6(issue.number)}: its body carries the note already.`));
|
|
10427
9739
|
}
|
|
10428
9740
|
const label = input.relations.mode === "native" ? { line: info(keptLinksLine(issue.number, waitsOn)), left: null } : await unblockLabel(input, issue, still.maybeOpen);
|
|
10429
9741
|
if (label.line !== null)
|
|
10430
|
-
|
|
9742
|
+
lines.push(label.line);
|
|
10431
9743
|
if (label.left !== null)
|
|
10432
9744
|
left.push(label.left);
|
|
10433
9745
|
const problem = await commentOn(input.writes, issue.number, renderDependentComment("unblocked", input.epic, waitsOn));
|
|
10434
9746
|
if (problem !== "") {
|
|
10435
|
-
|
|
9747
|
+
lines.push(warn(`Could not comment on ${ref6(issue.number)}: ${problem}`));
|
|
10436
9748
|
left.push(`comment the unblock on ${ref6(issue.number)}`);
|
|
10437
9749
|
}
|
|
10438
|
-
return Object.freeze({ issue: issue.number, waitsOn, answer: { kind: "unblocked" }, landed: edit.status !== "failed", lines
|
|
9750
|
+
return Object.freeze({ issue: issue.number, waitsOn, answer: { kind: "unblocked" }, landed: edit.status !== "failed", lines, left });
|
|
10439
9751
|
}
|
|
10440
9752
|
async function applyCancel(input, dependent) {
|
|
10441
9753
|
const { issue, waitsOn } = dependent;
|
|
@@ -10576,9 +9888,9 @@ function cancelFailure(result2) {
|
|
|
10576
9888
|
}
|
|
10577
9889
|
async function runEpicCancel(context, seams) {
|
|
10578
9890
|
const result2 = await cancelEpic(context, seams);
|
|
10579
|
-
const
|
|
10580
|
-
if (
|
|
10581
|
-
throw
|
|
9891
|
+
const failure = cancelFailure(result2);
|
|
9892
|
+
if (failure !== null)
|
|
9893
|
+
throw failure;
|
|
10582
9894
|
if (context.outputMode === "json")
|
|
10583
9895
|
context.output.result(result2);
|
|
10584
9896
|
}
|
|
@@ -10619,11 +9931,11 @@ var cancel_default = createEpicCancelCommand();
|
|
|
10619
9931
|
|
|
10620
9932
|
// src/commands/epic/close.ts
|
|
10621
9933
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10622
|
-
import { join as
|
|
9934
|
+
import { join as join22 } from "path";
|
|
10623
9935
|
|
|
10624
9936
|
// src/board/epic-template.ts
|
|
10625
|
-
import { existsSync as
|
|
10626
|
-
import { dirname as dirname3, join as
|
|
9937
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
|
|
9938
|
+
import { dirname as dirname3, join as join19 } from "path";
|
|
10627
9939
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10628
9940
|
var MODULE_DIR2 = dirname3(fileURLToPath2(import.meta.url));
|
|
10629
9941
|
var EPIC_TEMPLATE_FILE = "epic.md";
|
|
@@ -10632,11 +9944,11 @@ var CRITERIA_PLACEHOLDERS = Object.freeze([
|
|
|
10632
9944
|
]);
|
|
10633
9945
|
var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
|
|
10634
9946
|
function epicTemplateSource(moduleDir = MODULE_DIR2) {
|
|
10635
|
-
return
|
|
9947
|
+
return join19(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
|
|
10636
9948
|
}
|
|
10637
9949
|
function readEpicTemplate(moduleDir = MODULE_DIR2) {
|
|
10638
9950
|
const source = epicTemplateSource(moduleDir);
|
|
10639
|
-
if (!
|
|
9951
|
+
if (!existsSync4(source)) {
|
|
10640
9952
|
throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
|
|
10641
9953
|
}
|
|
10642
9954
|
return readFileSync9(source, "utf8");
|
|
@@ -10730,8 +10042,8 @@ function renderEpicCost(cost, estimate) {
|
|
|
10730
10042
|
}
|
|
10731
10043
|
|
|
10732
10044
|
// src/epic/verify-plan.ts
|
|
10733
|
-
import { existsSync as
|
|
10734
|
-
import { dirname as dirname4, join as
|
|
10045
|
+
import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
|
|
10046
|
+
import { dirname as dirname4, join as join20 } from "path";
|
|
10735
10047
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10736
10048
|
var MODULE_DIR3 = dirname4(fileURLToPath3(import.meta.url));
|
|
10737
10049
|
var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
|
|
@@ -10777,8 +10089,8 @@ function splitCriteria(criteria) {
|
|
|
10777
10089
|
}
|
|
10778
10090
|
state.afterBlank = false;
|
|
10779
10091
|
}
|
|
10780
|
-
const numbered = gathered.map((
|
|
10781
|
-
const text =
|
|
10092
|
+
const numbered = gathered.map((lines, index) => {
|
|
10093
|
+
const text = lines.join(`
|
|
10782
10094
|
`).trim();
|
|
10783
10095
|
return Object.freeze({ number: index + 1, text, placeholder: CRITERIA_PLACEHOLDERS.includes(text) });
|
|
10784
10096
|
});
|
|
@@ -10788,11 +10100,11 @@ function criteriaToAsk(criteria) {
|
|
|
10788
10100
|
return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
|
|
10789
10101
|
}
|
|
10790
10102
|
function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
|
|
10791
|
-
return [
|
|
10103
|
+
return [join20(moduleDir, VERIFY_PROMPT_FILE), join20(dirname4(moduleDir), VERIFY_PROMPT_FILE)];
|
|
10792
10104
|
}
|
|
10793
10105
|
function readVerifyPrompt(moduleDir = MODULE_DIR3) {
|
|
10794
10106
|
const candidates = verifyPromptCandidates(moduleDir);
|
|
10795
|
-
const found = candidates.find((candidate) =>
|
|
10107
|
+
const found = candidates.find((candidate) => existsSync5(candidate));
|
|
10796
10108
|
if (found === undefined) {
|
|
10797
10109
|
throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
|
|
10798
10110
|
}
|
|
@@ -10933,16 +10245,16 @@ function parseVerifyPlan(output, criteria) {
|
|
|
10933
10245
|
|
|
10934
10246
|
// src/epic/verify-run.ts
|
|
10935
10247
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
10936
|
-
import { join as
|
|
10248
|
+
import { join as join21 } from "path";
|
|
10937
10249
|
var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
|
|
10938
10250
|
var DEFAULT_VERIFY_REMOTE = "origin";
|
|
10939
10251
|
var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
|
|
10940
10252
|
var VERDICT_BLOCK_FENCE = "rafa:verdict";
|
|
10941
10253
|
var CHECK_RESULTS = ["pass", "fail"];
|
|
10942
|
-
var
|
|
10254
|
+
var WORKTREE_PREFIX = "epic-";
|
|
10943
10255
|
var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
|
|
10944
10256
|
function verifyWorktreePath(home, epic) {
|
|
10945
|
-
return
|
|
10257
|
+
return join21(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${String(epic)}`);
|
|
10946
10258
|
}
|
|
10947
10259
|
function fetchStep(remote) {
|
|
10948
10260
|
return { id: "fetch", args: ["fetch", remote] };
|
|
@@ -10956,7 +10268,7 @@ function addStep(path, commit) {
|
|
|
10956
10268
|
function removeStep(path) {
|
|
10957
10269
|
return { id: "remove", args: ["worktree", "remove", "--force", path] };
|
|
10958
10270
|
}
|
|
10959
|
-
function
|
|
10271
|
+
function runStep(git, step) {
|
|
10960
10272
|
const result2 = git(step.args);
|
|
10961
10273
|
return {
|
|
10962
10274
|
id: step.id,
|
|
@@ -11089,7 +10401,7 @@ async function runChecks(options, worktree, prompt) {
|
|
|
11089
10401
|
}
|
|
11090
10402
|
return Object.freeze(results);
|
|
11091
10403
|
}
|
|
11092
|
-
function
|
|
10404
|
+
function notRun(step, worktree) {
|
|
11093
10405
|
return Object.freeze({ status: "not-run", step: reported(step), worktree });
|
|
11094
10406
|
}
|
|
11095
10407
|
async function runVerification(options) {
|
|
@@ -11098,22 +10410,22 @@ async function runVerification(options) {
|
|
|
11098
10410
|
const remote = options.remote ?? DEFAULT_VERIFY_REMOTE;
|
|
11099
10411
|
const base = options.base ?? DEFAULT_BASE_BRANCH;
|
|
11100
10412
|
const worktree = verifyWorktreePath(options.home, options.epic);
|
|
11101
|
-
const fetched =
|
|
10413
|
+
const fetched = runStep(options.git, fetchStep(remote));
|
|
11102
10414
|
if (!fetched.ok)
|
|
11103
|
-
return
|
|
11104
|
-
const resolved =
|
|
10415
|
+
return notRun(fetched, worktree);
|
|
10416
|
+
const resolved = runStep(options.git, resolveStep(remote, base));
|
|
11105
10417
|
const commit = resolved.stdout.trim();
|
|
11106
10418
|
if (!resolved.ok || commit === "")
|
|
11107
|
-
return
|
|
11108
|
-
const added =
|
|
10419
|
+
return notRun(resolved, worktree);
|
|
10420
|
+
const added = runStep(options.git, addStep(worktree, commit));
|
|
11109
10421
|
if (!added.ok)
|
|
11110
|
-
return
|
|
10422
|
+
return notRun(added, worktree);
|
|
11111
10423
|
let results;
|
|
11112
10424
|
let removal;
|
|
11113
10425
|
try {
|
|
11114
10426
|
results = await runChecks(options, worktree, { commit, ref: `${remote}/${base}` });
|
|
11115
10427
|
} finally {
|
|
11116
|
-
removal = reported(
|
|
10428
|
+
removal = reported(runStep(options.git, removeStep(worktree)));
|
|
11117
10429
|
}
|
|
11118
10430
|
return Object.freeze({ status: "ran", commit, worktree, results, removal });
|
|
11119
10431
|
}
|
|
@@ -11224,13 +10536,13 @@ async function planVerification(input) {
|
|
|
11224
10536
|
return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
|
|
11225
10537
|
}
|
|
11226
10538
|
function closeTriageFile(root, epic) {
|
|
11227
|
-
return
|
|
10539
|
+
return join22(root, ".rafa", `epic-${String(epic)}-close`);
|
|
11228
10540
|
}
|
|
11229
10541
|
function failedCheckArtifact(epic, criterion) {
|
|
11230
10542
|
return `epic ${ref7(epic)} acceptance criterion: ${oneLine(criterion.text)}`;
|
|
11231
10543
|
}
|
|
11232
|
-
function failedCheckReport(epic,
|
|
11233
|
-
const { criterion } =
|
|
10544
|
+
function failedCheckReport(epic, failure, commit) {
|
|
10545
|
+
const { criterion } = failure.check;
|
|
11234
10546
|
const feedback = [
|
|
11235
10547
|
`Epic ${ref7(epic)} was being closed through the closing gate, and this acceptance criterion's check failed.`,
|
|
11236
10548
|
"",
|
|
@@ -11238,10 +10550,10 @@ function failedCheckReport(epic, failure3, commit) {
|
|
|
11238
10550
|
criterion.text,
|
|
11239
10551
|
"",
|
|
11240
10552
|
"Check:",
|
|
11241
|
-
|
|
10553
|
+
failure.check.check,
|
|
11242
10554
|
"",
|
|
11243
10555
|
`Evidence, against commit ${commit}:`,
|
|
11244
|
-
|
|
10556
|
+
failure.evidence
|
|
11245
10557
|
].join(`
|
|
11246
10558
|
`);
|
|
11247
10559
|
return Object.freeze({
|
|
@@ -11272,6 +10584,9 @@ function bugLine(criterion, bug) {
|
|
|
11272
10584
|
case "commented": {
|
|
11273
10585
|
return { text: `${which}: recurs in ${bug.ref === null ? "an issue" : issueName2(bug.ref)} on the ${bug.channel} tracker; commented on it.`, warn: false };
|
|
11274
10586
|
}
|
|
10587
|
+
case "inherited": {
|
|
10588
|
+
return { text: `${which}: not filed: a test failure the run started with.`, warn: false };
|
|
10589
|
+
}
|
|
11275
10590
|
case "skipped": {
|
|
11276
10591
|
return { text: `${which}: not filed: ${bug.problem ?? "no reason given"}.`, warn: true };
|
|
11277
10592
|
}
|
|
@@ -11296,20 +10611,21 @@ async function fileFailures(run, epic, failures, commit) {
|
|
|
11296
10611
|
warn2(run, `No tracker resolved, so no failed check is filed: ${messageOf(error)}`);
|
|
11297
10612
|
tracker = unresolvedTracker(run.config.trackerDefault, messageOf(error));
|
|
11298
10613
|
}
|
|
11299
|
-
for (const
|
|
11300
|
-
const { criterion } =
|
|
10614
|
+
for (const failure of failures) {
|
|
10615
|
+
const { criterion } = failure.check;
|
|
11301
10616
|
try {
|
|
11302
10617
|
const result2 = await triageReport({
|
|
11303
10618
|
repoRoot: run.root,
|
|
11304
10619
|
trackerPath: closeTriageFile(run.root, epic),
|
|
11305
10620
|
lineNum: 0,
|
|
11306
|
-
dispatch: { sessionId:
|
|
10621
|
+
dispatch: { sessionId: failure.sessionId, planStub: null, taskLine: `rafa epic close ${String(epic)}` },
|
|
11307
10622
|
outcome: "failed",
|
|
11308
|
-
report: failedCheckReport(epic,
|
|
10623
|
+
report: failedCheckReport(epic, failure, commit),
|
|
11309
10624
|
tracker,
|
|
11310
10625
|
privateTracker: run.seams.privateTracker,
|
|
11311
10626
|
secrets,
|
|
11312
|
-
seams: run.seams.findings
|
|
10627
|
+
seams: run.seams.findings,
|
|
10628
|
+
similarity: run.config
|
|
11313
10629
|
});
|
|
11314
10630
|
for (const bug of result2.bugs) {
|
|
11315
10631
|
const line = bugLine(criterion, bug);
|
|
@@ -11821,13 +11137,13 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
11821
11137
|
var promote_default = createEpicPromoteCommand();
|
|
11822
11138
|
|
|
11823
11139
|
// src/commands/init.ts
|
|
11824
|
-
import { existsSync as
|
|
11140
|
+
import { existsSync as existsSync8, readFileSync as readFileSync14 } from "fs";
|
|
11825
11141
|
import { homedir as homedir2 } from "os";
|
|
11826
|
-
import { join as
|
|
11142
|
+
import { join as join26, relative as relative5, sep as sep3 } from "path";
|
|
11827
11143
|
|
|
11828
11144
|
// src/agents/vendorable.ts
|
|
11829
11145
|
import { readdirSync, readFileSync as readFileSync11 } from "fs";
|
|
11830
|
-
import { isAbsolute as isAbsolute3, join as
|
|
11146
|
+
import { isAbsolute as isAbsolute3, join as join23 } from "path";
|
|
11831
11147
|
function planFiles(dir) {
|
|
11832
11148
|
let entries;
|
|
11833
11149
|
try {
|
|
@@ -11849,7 +11165,7 @@ function rafaHolds(roster, name) {
|
|
|
11849
11165
|
return item?.holders.some((holder) => holder.source === "rafa") === true;
|
|
11850
11166
|
}
|
|
11851
11167
|
function vendorableAgents(scan) {
|
|
11852
|
-
const dir = isAbsolute3(scan.planDir) ? scan.planDir :
|
|
11168
|
+
const dir = isAbsolute3(scan.planDir) ? scan.planDir : join23(scan.repoRoot, scan.planDir);
|
|
11853
11169
|
const roots = {
|
|
11854
11170
|
repoRoot: scan.repoRoot,
|
|
11855
11171
|
home: scan.home,
|
|
@@ -11858,7 +11174,7 @@ function vendorableAgents(scan) {
|
|
|
11858
11174
|
const roster = resolveAgentRoster(roots, scan.settings);
|
|
11859
11175
|
const found = [];
|
|
11860
11176
|
for (const file of planFiles(dir)) {
|
|
11861
|
-
const path =
|
|
11177
|
+
const path = join23(dir, file);
|
|
11862
11178
|
const markdown = readText2(path);
|
|
11863
11179
|
if (markdown === null)
|
|
11864
11180
|
continue;
|
|
@@ -11884,13 +11200,13 @@ function vendorableAgentWarnings(agents, root) {
|
|
|
11884
11200
|
|
|
11885
11201
|
// src/project/gitignore.ts
|
|
11886
11202
|
import { createHash } from "crypto";
|
|
11887
|
-
import { existsSync as
|
|
11888
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as
|
|
11203
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
|
|
11204
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join24 } from "path";
|
|
11889
11205
|
var GITIGNORE_FILE = ".gitignore";
|
|
11890
11206
|
var DIGEST_NAME = "tracking.digest";
|
|
11891
11207
|
var PRIVATE_TRIAGE = "triage/private";
|
|
11892
11208
|
var CACHE_DIR = "cache";
|
|
11893
|
-
var TRACKING_DIGEST_FILE =
|
|
11209
|
+
var TRACKING_DIGEST_FILE = join24(SCOPE_DIR, DIGEST_NAME);
|
|
11894
11210
|
var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
|
|
11895
11211
|
var BLOCK_END = "# <<< rafa tracking";
|
|
11896
11212
|
var SUB_PATHS = [
|
|
@@ -11924,8 +11240,8 @@ function lineBreakOf(text) {
|
|
|
11924
11240
|
return /\r?\n/.exec(text)?.[0] ?? `
|
|
11925
11241
|
`;
|
|
11926
11242
|
}
|
|
11927
|
-
function indicesOf(
|
|
11928
|
-
return
|
|
11243
|
+
function indicesOf(lines, marker) {
|
|
11244
|
+
return lines.flatMap((line, index) => line.replace(/\r$/, "") === marker ? [index] : []);
|
|
11929
11245
|
}
|
|
11930
11246
|
function whereLines(indices) {
|
|
11931
11247
|
if (indices.length === 0)
|
|
@@ -11933,9 +11249,9 @@ function whereLines(indices) {
|
|
|
11933
11249
|
const numbers = indices.map((index) => String(index + 1));
|
|
11934
11250
|
return indices.length === 1 ? `line ${numbers.join("")}` : `lines ${numbers.join(", ")}`;
|
|
11935
11251
|
}
|
|
11936
|
-
function blockSpan(
|
|
11937
|
-
const begins = indicesOf(
|
|
11938
|
-
const ends = indicesOf(
|
|
11252
|
+
function blockSpan(lines, file) {
|
|
11253
|
+
const begins = indicesOf(lines, BLOCK_BEGIN);
|
|
11254
|
+
const ends = indicesOf(lines, BLOCK_END);
|
|
11939
11255
|
if (begins.length === 0 && ends.length === 0)
|
|
11940
11256
|
return null;
|
|
11941
11257
|
const [begin] = begins;
|
|
@@ -11952,17 +11268,17 @@ function withTrackingBlock(text, flags, file = GITIGNORE_FILE) {
|
|
|
11952
11268
|
const block = [BLOCK_BEGIN, ...trackingEntry(flags), BLOCK_END].map((line) => `${line}${eol}`).join("");
|
|
11953
11269
|
if (source === "")
|
|
11954
11270
|
return block;
|
|
11955
|
-
const
|
|
11271
|
+
const lines = source.split(`
|
|
11956
11272
|
`);
|
|
11957
|
-
const span = blockSpan(
|
|
11273
|
+
const span = blockSpan(lines, file);
|
|
11958
11274
|
if (span === null) {
|
|
11959
11275
|
const separator = source.endsWith(`
|
|
11960
11276
|
`) ? eol : `${eol}${eol}`;
|
|
11961
11277
|
return `${source}${separator}${block}`;
|
|
11962
11278
|
}
|
|
11963
|
-
const before =
|
|
11279
|
+
const before = lines.slice(0, span.begin).map((line) => `${line}
|
|
11964
11280
|
`).join("");
|
|
11965
|
-
const after =
|
|
11281
|
+
const after = lines.slice(span.end + 1).join(`
|
|
11966
11282
|
`);
|
|
11967
11283
|
return `${before}${block}${after}`;
|
|
11968
11284
|
}
|
|
@@ -11971,21 +11287,21 @@ function requireAbsolute(root) {
|
|
|
11971
11287
|
throw new GitignoreError(`project root is ${describeValue(root)}, expected an absolute path`);
|
|
11972
11288
|
}
|
|
11973
11289
|
}
|
|
11974
|
-
function attempt(file,
|
|
11290
|
+
function attempt(file, failure, act) {
|
|
11975
11291
|
try {
|
|
11976
11292
|
return act();
|
|
11977
11293
|
} catch (error) {
|
|
11978
|
-
throw new GitignoreError(`${file} ${
|
|
11294
|
+
throw new GitignoreError(`${file} ${failure} (${messageOf(error)})`, { cause: error });
|
|
11979
11295
|
}
|
|
11980
11296
|
}
|
|
11981
11297
|
function readIfPresent2(file) {
|
|
11982
|
-
if (!
|
|
11298
|
+
if (!existsSync6(file))
|
|
11983
11299
|
return null;
|
|
11984
11300
|
return attempt(file, "cannot be read", () => readFileSync12(file, "utf8"));
|
|
11985
11301
|
}
|
|
11986
11302
|
function writeTrackingGitignore(root, flags) {
|
|
11987
11303
|
requireAbsolute(root);
|
|
11988
|
-
const file =
|
|
11304
|
+
const file = join24(root, GITIGNORE_FILE);
|
|
11989
11305
|
const text = readIfPresent2(file);
|
|
11990
11306
|
const next = withTrackingBlock(text, flags, file);
|
|
11991
11307
|
if (next === text)
|
|
@@ -12007,7 +11323,7 @@ function trackingDigest(flags) {
|
|
|
12007
11323
|
}
|
|
12008
11324
|
function exposureNotice(root) {
|
|
12009
11325
|
return [
|
|
12010
|
-
`tracking.all is true, so ${
|
|
11326
|
+
`tracking.all is true, so ${join24(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
|
|
12011
11327
|
" - unpatched issues described in specs and plans",
|
|
12012
11328
|
" - effort rows with model names and token counts",
|
|
12013
11329
|
" - instincts that may quote error output",
|
|
@@ -12020,7 +11336,7 @@ function printWarning(message) {
|
|
|
12020
11336
|
}
|
|
12021
11337
|
function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
12022
11338
|
requireAbsolute(root);
|
|
12023
|
-
const digestFile =
|
|
11339
|
+
const digestFile = join24(root, TRACKING_DIGEST_FILE);
|
|
12024
11340
|
const recorded = `${trackingDigest(flags)}
|
|
12025
11341
|
`;
|
|
12026
11342
|
if (readIfPresent2(digestFile) === recorded) {
|
|
@@ -12119,8 +11435,8 @@ async function promptForRoot(found, prompter, seams) {
|
|
|
12119
11435
|
}
|
|
12120
11436
|
|
|
12121
11437
|
// src/project/scaffold.ts
|
|
12122
|
-
import { existsSync as
|
|
12123
|
-
import { join as
|
|
11438
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
11439
|
+
import { join as join25 } from "path";
|
|
12124
11440
|
|
|
12125
11441
|
// src/commands/init-release.ts
|
|
12126
11442
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -12150,9 +11466,9 @@ var COMMENTED_RELEASE_KEY = /^#\s*release:\s*$/u;
|
|
|
12150
11466
|
var COMMENTED_ENABLED_KEY = /^#\s+enabled:/u;
|
|
12151
11467
|
var ENABLED_KEY = /^\s+enabled\s*:/u;
|
|
12152
11468
|
var SKIPPABLE = /^(\s*#|\s*$)/u;
|
|
12153
|
-
function enabledLineIn(
|
|
12154
|
-
for (let index = open + 1;index <
|
|
12155
|
-
const line =
|
|
11469
|
+
function enabledLineIn(lines, open) {
|
|
11470
|
+
for (let index = open + 1;index < lines.length; index += 1) {
|
|
11471
|
+
const line = lines[index] ?? "";
|
|
12156
11472
|
if (SKIPPABLE.test(line))
|
|
12157
11473
|
continue;
|
|
12158
11474
|
if (ENABLED_KEY.test(line))
|
|
@@ -12162,8 +11478,8 @@ function enabledLineIn(lines2, open) {
|
|
|
12162
11478
|
}
|
|
12163
11479
|
return null;
|
|
12164
11480
|
}
|
|
12165
|
-
function appended(
|
|
12166
|
-
const body =
|
|
11481
|
+
function appended(lines, block) {
|
|
11482
|
+
const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
|
|
12167
11483
|
return [...body, ...block, ""];
|
|
12168
11484
|
}
|
|
12169
11485
|
function namesRelease(text) {
|
|
@@ -12177,28 +11493,28 @@ function namesRelease(text) {
|
|
|
12177
11493
|
}
|
|
12178
11494
|
function withReleaseEnabled(text, enabled) {
|
|
12179
11495
|
const setting = ` enabled: ${String(enabled)}`;
|
|
12180
|
-
const
|
|
11496
|
+
const lines = text.split(`
|
|
12181
11497
|
`);
|
|
12182
|
-
const open =
|
|
11498
|
+
const open = lines.findIndex((line) => RELEASE_KEY.test(line));
|
|
12183
11499
|
if (open >= 0) {
|
|
12184
|
-
const at = enabledLineIn(
|
|
11500
|
+
const at = enabledLineIn(lines, open);
|
|
12185
11501
|
if (at === null) {
|
|
12186
|
-
return [...
|
|
11502
|
+
return [...lines.slice(0, open + 1), setting, ...lines.slice(open + 1)].join(`
|
|
12187
11503
|
`);
|
|
12188
11504
|
}
|
|
12189
|
-
return [...
|
|
11505
|
+
return [...lines.slice(0, at), setting, ...lines.slice(at + 1)].join(`
|
|
12190
11506
|
`);
|
|
12191
11507
|
}
|
|
12192
|
-
const commented =
|
|
11508
|
+
const commented = lines.findIndex((line) => COMMENTED_RELEASE_KEY.test(line));
|
|
12193
11509
|
if (commented >= 0) {
|
|
12194
|
-
const next =
|
|
11510
|
+
const next = lines[commented + 1] ?? "";
|
|
12195
11511
|
const after = COMMENTED_ENABLED_KEY.test(next) ? commented + 2 : commented + 1;
|
|
12196
|
-
return [...
|
|
11512
|
+
return [...lines.slice(0, commented), "release:", setting, ...lines.slice(after)].join(`
|
|
12197
11513
|
`);
|
|
12198
11514
|
}
|
|
12199
11515
|
if (namesRelease(text))
|
|
12200
11516
|
return null;
|
|
12201
|
-
return appended(
|
|
11517
|
+
return appended(lines, ["release:", setting]).join(`
|
|
12202
11518
|
`);
|
|
12203
11519
|
}
|
|
12204
11520
|
|
|
@@ -12313,11 +11629,18 @@ function renderReleaseStep(result2) {
|
|
|
12313
11629
|
// src/project/scaffold.ts
|
|
12314
11630
|
var PROJECT_TREE = Object.freeze(["specs", "plans", "runs", "effort", "instincts"]);
|
|
12315
11631
|
var USER_TREE = Object.freeze(["instincts"]);
|
|
11632
|
+
function globsLine(patterns) {
|
|
11633
|
+
return `[${patterns.map((pattern) => JSON.stringify(pattern)).join(", ")}]`;
|
|
11634
|
+
}
|
|
12316
11635
|
var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
12317
11636
|
`# store: ${CONFIG_DEFAULTS.store} # sqlite | ndjson`,
|
|
12318
11637
|
"# effort:",
|
|
12319
11638
|
`# busyTimeoutMs: ${String(CONFIG_DEFAULTS.effortBusyTimeoutMs)} # 1..60000 ms; how long a store open waits for another process's lock`,
|
|
12320
11639
|
`# sync: ${CONFIG_DEFAULTS.effortSync} # local | file | git | service | p2p; how the store travels between devices`,
|
|
11640
|
+
"# hub:",
|
|
11641
|
+
"# url: # the hub effort.sync: service reaches, required with it",
|
|
11642
|
+
"# tokenSecret: # the secret store name the hub token is kept under, never the token",
|
|
11643
|
+
`# timeout: ${CONFIG_DEFAULTS.hubTimeout} # 1s..30s; how long one request to the hub may take`,
|
|
12321
11644
|
"# plan:",
|
|
12322
11645
|
`# inject: ${CONFIG_DEFAULTS.inject} # full | stage | task`,
|
|
12323
11646
|
`# dir: ${CONFIG_DEFAULTS.planDir}`,
|
|
@@ -12347,6 +11670,8 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
12347
11670
|
"# loop:",
|
|
12348
11671
|
`# settingSources: ${CONFIG_DEFAULTS.settingSources.join(",")} # a comma-separated subset of user, project, local`,
|
|
12349
11672
|
`# worktreeDir: ${CONFIG_DEFAULTS.loopWorktreeDir} # where a loop adds its worktrees, read from the project root`,
|
|
11673
|
+
"# wrapUp:",
|
|
11674
|
+
`# retries: ${String(CONFIG_DEFAULTS.loopWrapUpRetries)} # 1..3 | false, wrap-up sessions rerun before rafa opens a missing PR itself`,
|
|
12350
11675
|
"# pr:",
|
|
12351
11676
|
"# provider: # gh | none; unset reads it off the origin remote",
|
|
12352
11677
|
`# mergeMethod: ${CONFIG_DEFAULTS.prMergeMethod} # squash | merge | rebase`,
|
|
@@ -12361,6 +11686,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
12361
11686
|
"# claims:",
|
|
12362
11687
|
`# staleAfter: ${CONFIG_DEFAULTS.claimsStaleAfter} # 36h | 3d | disabled; how long a rafa:claimed claim stands before a takeover`,
|
|
12363
11688
|
`# ahead: ${CONFIG_DEFAULTS.claimsAhead} # off | allow; whether a claim may reach one issue ahead`,
|
|
11689
|
+
"# triage:",
|
|
11690
|
+
"# similarity:",
|
|
11691
|
+
`# threshold: ${String(CONFIG_DEFAULTS.triageSimilarityThreshold)} # above 0 and at most 1 | false; the least score at which triage comments on the nearest open bug`,
|
|
11692
|
+
`# candidates: ${String(CONFIG_DEFAULTS.triageSimilarityCandidates)} # 1..10, the nearest issues a new bug lists as Possible duplicates`,
|
|
12364
11693
|
"# release:",
|
|
12365
11694
|
`# enabled: ${String(CONFIG_DEFAULTS.releaseEnabled)} # true | false | auto, which is on when both files below exist`,
|
|
12366
11695
|
`# versionFile: ${CONFIG_DEFAULTS.releaseVersionFile}`,
|
|
@@ -12386,7 +11715,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
12386
11715
|
...DEFAULT_ROUTES.map(([shape, agent]) => `# ${shape}: ${agent}`),
|
|
12387
11716
|
"# task:",
|
|
12388
11717
|
`# skills: ${CONFIG_DEFAULTS.taskSkills} # planner | tag | none, the resolver that picks a task's skills`,
|
|
12389
|
-
`# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt
|
|
11718
|
+
`# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`,
|
|
11719
|
+
"# tests:",
|
|
11720
|
+
`# fullSuiteTriggers: ${globsLine(CONFIG_DEFAULTS.testsFullSuiteTriggers)} # a task whose diff matches one runs the full suite`,
|
|
11721
|
+
`# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`
|
|
12390
11722
|
]);
|
|
12391
11723
|
var VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
|
|
12392
11724
|
var PROJECT_HEADER = Object.freeze([
|
|
@@ -12424,18 +11756,18 @@ class ScaffoldError extends Error {
|
|
|
12424
11756
|
}
|
|
12425
11757
|
function layoutOf2(base, tree) {
|
|
12426
11758
|
const scope = scopeAt(base);
|
|
12427
|
-
return { dirs: [scope.dir, ...tree.map((name) =>
|
|
11759
|
+
return { dirs: [scope.dir, ...tree.map((name) => join25(scope.dir, name))], configFile: configFilePath(base) };
|
|
12428
11760
|
}
|
|
12429
11761
|
function occupied(path) {
|
|
12430
11762
|
return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
12431
11763
|
}
|
|
12432
11764
|
function isDirectory2(path) {
|
|
12433
|
-
return
|
|
11765
|
+
return statSync6(path, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
12434
11766
|
}
|
|
12435
11767
|
function directoryConflict(dir) {
|
|
12436
11768
|
if (!occupied(dir) || isDirectory2(dir))
|
|
12437
11769
|
return null;
|
|
12438
|
-
return
|
|
11770
|
+
return existsSync7(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
|
|
12439
11771
|
}
|
|
12440
11772
|
function layoutConflicts(layout) {
|
|
12441
11773
|
const [scopeDir, ...tree] = layout.dirs;
|
|
@@ -12443,21 +11775,21 @@ function layoutConflicts(layout) {
|
|
|
12443
11775
|
if (opening !== null)
|
|
12444
11776
|
return [opening];
|
|
12445
11777
|
const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
|
|
12446
|
-
const file = occupied(layout.configFile) && !
|
|
11778
|
+
const file = occupied(layout.configFile) && !existsSync7(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
|
|
12447
11779
|
return [...dirs, ...file];
|
|
12448
11780
|
}
|
|
12449
11781
|
function scaffoldConflicts(root, home) {
|
|
12450
11782
|
return [...layoutConflicts(layoutOf2(root, PROJECT_TREE)), ...layoutConflicts(layoutOf2(home, USER_TREE))];
|
|
12451
11783
|
}
|
|
12452
|
-
function attempt2(path,
|
|
11784
|
+
function attempt2(path, failure, act) {
|
|
12453
11785
|
try {
|
|
12454
11786
|
act();
|
|
12455
11787
|
} catch (error) {
|
|
12456
|
-
throw new ScaffoldError(`${path} ${
|
|
11788
|
+
throw new ScaffoldError(`${path} ${failure} (${messageOf(error)})`, { cause: error });
|
|
12457
11789
|
}
|
|
12458
11790
|
}
|
|
12459
11791
|
function ensureDirectory(path) {
|
|
12460
|
-
if (
|
|
11792
|
+
if (existsSync7(path))
|
|
12461
11793
|
return { path, kind: "directory", change: "unchanged" };
|
|
12462
11794
|
attempt2(path, "cannot be created", () => {
|
|
12463
11795
|
mkdirSync4(path, { recursive: true });
|
|
@@ -12465,7 +11797,7 @@ function ensureDirectory(path) {
|
|
|
12465
11797
|
return { path, kind: "directory", change: "created" };
|
|
12466
11798
|
}
|
|
12467
11799
|
function ensureFile(path, text) {
|
|
12468
|
-
if (
|
|
11800
|
+
if (existsSync7(path))
|
|
12469
11801
|
return { path, kind: "file", change: "unchanged" };
|
|
12470
11802
|
attempt2(path, "cannot be written", () => {
|
|
12471
11803
|
writeFileSync5(path, text, { encoding: "utf8", flag: "wx" });
|
|
@@ -12504,8 +11836,8 @@ var SOURCE_PHRASES = {
|
|
|
12504
11836
|
"root-flag": "named by --root",
|
|
12505
11837
|
typed: "typed at the prompt"
|
|
12506
11838
|
};
|
|
12507
|
-
function refusal9(
|
|
12508
|
-
return new CommandExit(1, [...
|
|
11839
|
+
function refusal9(lines) {
|
|
11840
|
+
return new CommandExit(1, [...lines, NOTHING_WRITTEN2].join(`
|
|
12509
11841
|
`));
|
|
12510
11842
|
}
|
|
12511
11843
|
function expectNoArgument3(args) {
|
|
@@ -12601,9 +11933,9 @@ function resolvedConfig2(root, home, warn3) {
|
|
|
12601
11933
|
}
|
|
12602
11934
|
}
|
|
12603
11935
|
function checkGitignore(root, flags) {
|
|
12604
|
-
const file =
|
|
11936
|
+
const file = join26(root, GITIGNORE_FILE);
|
|
12605
11937
|
try {
|
|
12606
|
-
const text =
|
|
11938
|
+
const text = existsSync8(file) ? readFileSync14(file, "utf8") : null;
|
|
12607
11939
|
withTrackingBlock(text, flags, file);
|
|
12608
11940
|
} catch (error) {
|
|
12609
11941
|
const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
|
|
@@ -12635,8 +11967,8 @@ function initialise(root, start2, home, context, entry) {
|
|
|
12635
11967
|
const resolved = resolvedConfig2(root.path, home, warn3);
|
|
12636
11968
|
const config = resolved.config;
|
|
12637
11969
|
checkGitignore(root.path, config);
|
|
12638
|
-
const configExisted =
|
|
12639
|
-
const digestExisted =
|
|
11970
|
+
const configExisted = existsSync8(configFilePath(root.path));
|
|
11971
|
+
const digestExisted = existsSync8(join26(root.path, TRACKING_DIGEST_FILE));
|
|
12640
11972
|
const project = writeProjectScope(root.path);
|
|
12641
11973
|
const tracking = applyTracking(root.path, config, warn3);
|
|
12642
11974
|
const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
|
|
@@ -12689,7 +12021,7 @@ async function releaseStep(scopes, wanted, yes, seams) {
|
|
|
12689
12021
|
});
|
|
12690
12022
|
}
|
|
12691
12023
|
function shownPath2(write, root) {
|
|
12692
|
-
const shown = write.path.startsWith(`${root}${
|
|
12024
|
+
const shown = write.path.startsWith(`${root}${sep3}`) ? relative5(root, write.path) : write.path;
|
|
12693
12025
|
return write.kind === "directory" ? `${shown}/` : shown;
|
|
12694
12026
|
}
|
|
12695
12027
|
function renderInit(result2) {
|
|
@@ -12921,15 +12253,15 @@ async function runCheckCommand(context, spec, seams) {
|
|
|
12921
12253
|
fix
|
|
12922
12254
|
}, spec.usage);
|
|
12923
12255
|
const result2 = checkCommandResult(report2, projectRoot2);
|
|
12924
|
-
const
|
|
12256
|
+
const lines = renderCheck(result2);
|
|
12925
12257
|
if (report2.exitCode > 0)
|
|
12926
|
-
throw new CommandExit(report2.exitCode,
|
|
12258
|
+
throw new CommandExit(report2.exitCode, lines.join(`
|
|
12927
12259
|
`));
|
|
12928
12260
|
if (context.outputMode === "json") {
|
|
12929
12261
|
context.output.result(result2);
|
|
12930
12262
|
return;
|
|
12931
12263
|
}
|
|
12932
|
-
for (const line of
|
|
12264
|
+
for (const line of lines)
|
|
12933
12265
|
context.output.info(line);
|
|
12934
12266
|
}
|
|
12935
12267
|
function checkCommandRun(spec, seams) {
|
|
@@ -13290,12 +12622,12 @@ function everyRecord(context) {
|
|
|
13290
12622
|
clean: records.filter((entry) => entry.instinct !== null).length
|
|
13291
12623
|
};
|
|
13292
12624
|
}
|
|
13293
|
-
function answer(context, result2,
|
|
12625
|
+
function answer(context, result2, lines) {
|
|
13294
12626
|
if (context.outputMode === "json") {
|
|
13295
12627
|
context.output.result(result2);
|
|
13296
12628
|
return;
|
|
13297
12629
|
}
|
|
13298
|
-
for (const line of
|
|
12630
|
+
for (const line of lines())
|
|
13299
12631
|
context.output.info(line);
|
|
13300
12632
|
}
|
|
13301
12633
|
async function runList4(context, seams) {
|
|
@@ -13497,8 +12829,8 @@ function createInstinctShowCommand() {
|
|
|
13497
12829
|
var show_default3 = createInstinctShowCommand();
|
|
13498
12830
|
|
|
13499
12831
|
// src/commands/issue/check.ts
|
|
13500
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as
|
|
13501
|
-
import { join as
|
|
12832
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
|
|
12833
|
+
import { join as join27, resolve as resolve9 } from "path";
|
|
13502
12834
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
13503
12835
|
var STAMP_FLAG = "stamp";
|
|
13504
12836
|
var ISSUE_NUMBER7 = /^[1-9]\d*$/u;
|
|
@@ -13530,7 +12862,7 @@ function copyNames(dir, issue) {
|
|
|
13530
12862
|
}
|
|
13531
12863
|
const prefix = `${boardId(issue)}-`;
|
|
13532
12864
|
const notes = notesFileName(issue);
|
|
13533
|
-
return names.filter((name) => name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name !== notes).filter((name) => name.length > prefix.length + SPEC_EXTENSION.length).filter((name) =>
|
|
12865
|
+
return names.filter((name) => name.startsWith(prefix) && name.endsWith(SPEC_EXTENSION) && name !== notes).filter((name) => name.length > prefix.length + SPEC_EXTENSION.length).filter((name) => statSync7(join27(dir, name)).isFile()).sort();
|
|
13534
12866
|
}
|
|
13535
12867
|
function findSavedCopy(root, specsDir, issue) {
|
|
13536
12868
|
const names = copyNames(resolve9(root, specsDir), issue);
|
|
@@ -13540,11 +12872,11 @@ function findSavedCopy(root, specsDir, issue) {
|
|
|
13540
12872
|
if (names.length > 1) {
|
|
13541
12873
|
throw new CommandExit(1, [
|
|
13542
12874
|
`\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
|
|
13543
|
-
...names.map((name) => ` ${
|
|
12875
|
+
...names.map((name) => ` ${join27(specsDir, name)}`)
|
|
13544
12876
|
].join(`
|
|
13545
12877
|
`));
|
|
13546
12878
|
}
|
|
13547
|
-
const path =
|
|
12879
|
+
const path = join27(specsDir, only);
|
|
13548
12880
|
return { path, absolute: resolve9(root, path) };
|
|
13549
12881
|
}
|
|
13550
12882
|
function liveText(live) {
|
|
@@ -13562,7 +12894,7 @@ function checkedRef(row2, stamp) {
|
|
|
13562
12894
|
unblock: row2.unblock
|
|
13563
12895
|
});
|
|
13564
12896
|
}
|
|
13565
|
-
function
|
|
12897
|
+
function rowSuffix(ref8) {
|
|
13566
12898
|
const noun = ref8.changedHeadings.length === 1 ? "heading" : "headings";
|
|
13567
12899
|
const headings = ref8.changedHeadings.length === 0 ? "" : `: ${noun} ${ref8.changedHeadings.map((heading) => `"${heading}"`).join(", ")} changed`;
|
|
13568
12900
|
const after = ref8.unblock === null ? "" : ` \u2014 ${ref8.unblock}`;
|
|
@@ -13570,7 +12902,7 @@ function rowSuffix2(ref8) {
|
|
|
13570
12902
|
return `${headings} (line ${String(ref8.line)})${after}${unknown}`;
|
|
13571
12903
|
}
|
|
13572
12904
|
function checkedRefLine(ref8) {
|
|
13573
|
-
return `${ref8.state} ${ref8.kind} ${ref8.text}${
|
|
12905
|
+
return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix(ref8)}`;
|
|
13574
12906
|
}
|
|
13575
12907
|
function countLine(references) {
|
|
13576
12908
|
const noun = references.length === 1 ? "reference" : "references";
|
|
@@ -13961,7 +13293,7 @@ function renderSessionList(root, list) {
|
|
|
13961
13293
|
return [
|
|
13962
13294
|
"Running sessions:",
|
|
13963
13295
|
...list.sessions.map(({ session, tasks }) => {
|
|
13964
|
-
const counts = tasks === null ?
|
|
13296
|
+
const counts = tasks === null ? `no plan or tracker to count ${phaseNote(session)}` : phasedCounts(tasks, session);
|
|
13965
13297
|
return ` ${sessionLine(session)}; ${counts}; ${checkoutColumn(root, session)}`;
|
|
13966
13298
|
})
|
|
13967
13299
|
];
|
|
@@ -13973,6 +13305,7 @@ async function runList5(context, seams) {
|
|
|
13973
13305
|
const checklist = readSessionChecklist(root, session);
|
|
13974
13306
|
return {
|
|
13975
13307
|
session,
|
|
13308
|
+
phase: sessionPhase(session),
|
|
13976
13309
|
tasks: checklist === null ? null : countTasks(checklist.tasks)
|
|
13977
13310
|
};
|
|
13978
13311
|
});
|
|
@@ -13991,7 +13324,7 @@ function createLoopListCommand(seams = {}) {
|
|
|
13991
13324
|
subject: "loop",
|
|
13992
13325
|
action: "list",
|
|
13993
13326
|
summary: "list the running sessions, with the tasks done in each plan",
|
|
13994
|
-
description: "Lists every session record under `.rafa/runs/` that reads `running` or `paused`, oldest" + " first: its id, plan, branch, state, pid and start, the tasks of its plan done, blocked and" + " open, and the worktree it runs in, or the main checkout. A record whose pid is gone reads `stopped` and is not listed. Until phase 6 a plan runs in" + " one session at a time, so each row is a plan running. With `--output=json` the sessions are the" + " data of the terminal result event.",
|
|
13327
|
+
description: "Lists every session record under `.rafa/runs/` that reads `running` or `paused`, oldest" + " first: its id, plan, branch, state, pid and start, the tasks of its plan done, blocked and" + " open with the phase its run is in beside them, and the worktree it runs in, or the main checkout. A record whose pid is gone reads `stopped` and is not listed. Until phase 6 a plan runs in" + " one session at a time, so each row is a plan running. With `--output=json` the sessions are the" + " data of the terminal result event.",
|
|
13995
13328
|
args: [],
|
|
13996
13329
|
flags: [],
|
|
13997
13330
|
examples: [
|
|
@@ -14205,10 +13538,10 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
14205
13538
|
}
|
|
14206
13539
|
],
|
|
14207
13540
|
aliases: ["start"],
|
|
14208
|
-
outputs: ["text", "json"],
|
|
13541
|
+
outputs: ["text", "json", "events"],
|
|
14209
13542
|
spends: {
|
|
14210
13543
|
when: "always",
|
|
14211
|
-
what: "one session per task, one for the wrap-up, and repair sessions while CI is red"
|
|
13544
|
+
what: "one session per task, one for the wrap-up and up to `loop.wrapUp.retries` more when it opens no pull request, and repair sessions while CI is red"
|
|
14212
13545
|
}
|
|
14213
13546
|
}, start);
|
|
14214
13547
|
var loopStart = Object.freeze({
|
|
@@ -14250,7 +13583,7 @@ function renderStatus(status) {
|
|
|
14250
13583
|
const pending = session.state === "paused" && task !== null ? " (the run holds once it ends)" : "";
|
|
14251
13584
|
return [
|
|
14252
13585
|
`Session ${sessionLine(session)}`,
|
|
14253
|
-
tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count` : ` Tasks: ${
|
|
13586
|
+
tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count ${phaseNote(session)}` : ` Tasks: ${phasedCounts(tasks, session)}`,
|
|
14254
13587
|
...blocked.flatMap(blockedLines),
|
|
14255
13588
|
...task === null ? [] : [` Task: line ${task.line}, ${task.text}${pending}`],
|
|
14256
13589
|
...eta === null ? [] : [` ${etaLine(eta)}`]
|
|
@@ -14271,6 +13604,7 @@ async function runStatus(context, seams) {
|
|
|
14271
13604
|
}
|
|
14272
13605
|
const status = {
|
|
14273
13606
|
session: record,
|
|
13607
|
+
phase: sessionPhase(record),
|
|
14274
13608
|
checklist: checklist?.file ?? null,
|
|
14275
13609
|
tasks,
|
|
14276
13610
|
blocked: blockedTasks(checklist),
|
|
@@ -14290,13 +13624,13 @@ function createLoopStatusCommand(seams = {}) {
|
|
|
14290
13624
|
subject: "loop",
|
|
14291
13625
|
action: "status",
|
|
14292
13626
|
summary: "show a session: its state, tasks done over total and a rough ETA",
|
|
14293
|
-
description: "Reads a session record and its plan's tracker: the state, the pid, the start, the task" + " it names,
|
|
13627
|
+
description: "Reads a session record and its plan's tracker: the state, the pid, the start, the task" + " it names, the tasks done, blocked and open over the whole plan, and beside them the phase the" + " run is in: task, wrap-up, pull-request, ci or repair. A running or paused session" + " also gets a rough ETA from the effort store: the time from its start to the last task it" + " finished, per task finished, times the open and blocked tasks left, and none before its first" + " task finishes. Every blocked task of the checklist is shown with the blocker comment its line" + " trails, which is what the run wrote when that task ended blocked. Without `--session-id` it shows the session running on the branch checked out at" + " the project root, or the newest one that ran there. With `--output=json` the record, the counts" + " and the ETA are the data of the terminal result event.",
|
|
14294
13628
|
args: [],
|
|
14295
13629
|
flags: [sessionIdFlag("to show")],
|
|
14296
13630
|
examples: [
|
|
14297
13631
|
{
|
|
14298
13632
|
cmd: "rafa loop status",
|
|
14299
|
-
note: "Shows the session on the branch checked out here: its state, tasks done over total and ETA."
|
|
13633
|
+
note: "Shows the session on the branch checked out here: its state, tasks done over total, phase and ETA."
|
|
14300
13634
|
},
|
|
14301
13635
|
{
|
|
14302
13636
|
cmd: "rafa loop status --session-id=9185b41c-65f7-4dd6-a0c1-6494c4028f0f --output=json",
|
|
@@ -14686,8 +14020,8 @@ function dryRunOf(flag, ceiling, hasTerminal) {
|
|
|
14686
14020
|
|
|
14687
14021
|
// src/next/sync.ts
|
|
14688
14022
|
var INDENT2 = " ";
|
|
14689
|
-
function refuse6(
|
|
14690
|
-
return Object.freeze({ kind: "refused", message:
|
|
14023
|
+
function refuse6(lines) {
|
|
14024
|
+
return Object.freeze({ kind: "refused", message: lines.join(`
|
|
14691
14025
|
`) });
|
|
14692
14026
|
}
|
|
14693
14027
|
function quotedLines(said) {
|
|
@@ -14868,6 +14202,7 @@ async function runNext(context, seams) {
|
|
|
14868
14202
|
...roadmap ? { roadmap } : {},
|
|
14869
14203
|
...relations === undefined ? {} : { relations }
|
|
14870
14204
|
});
|
|
14205
|
+
await (seams.pull ?? pullBeforeRead)({ roots: sources.roots, warn: (line) => context.output.warn(line) });
|
|
14871
14206
|
const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
|
|
14872
14207
|
const prompter = lazyPrompter(openPrompter);
|
|
14873
14208
|
const dry = watchDryEpic();
|
|
@@ -15187,7 +14522,7 @@ var risk_default = createPlanRiskCommand();
|
|
|
15187
14522
|
|
|
15188
14523
|
// src/commands/plan/show.ts
|
|
15189
14524
|
import { readFileSync as readFileSync17 } from "fs";
|
|
15190
|
-
import { join as
|
|
14525
|
+
import { join as join28 } from "path";
|
|
15191
14526
|
var USAGE33 = "rafa plan show <stub> [--tracker]";
|
|
15192
14527
|
var COMMAND7 = "rafa plan show";
|
|
15193
14528
|
function readTrackerFlag(value) {
|
|
@@ -15205,9 +14540,9 @@ function showPlan(plans, stub, tracker) {
|
|
|
15205
14540
|
throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
|
|
15206
14541
|
}
|
|
15207
14542
|
const name = planFileName(stub, tracker);
|
|
15208
|
-
const file =
|
|
14543
|
+
const file = join28(plans.path, name);
|
|
15209
14544
|
if (!isFile(file))
|
|
15210
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
14545
|
+
throw new CommandExit(1, missingText(stub, tracker, join28(plans.label, name)));
|
|
15211
14546
|
const model = parsePlan(readFileSync17(file, "utf8"));
|
|
15212
14547
|
const { header } = model;
|
|
15213
14548
|
return {
|
|
@@ -15231,24 +14566,24 @@ function taskLine(task) {
|
|
|
15231
14566
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
15232
14567
|
}
|
|
15233
14568
|
function renderShownPlan(shown, dirLabel) {
|
|
15234
|
-
const relative7 =
|
|
14569
|
+
const relative7 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
15235
14570
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
15236
|
-
const
|
|
14571
|
+
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
|
|
15237
14572
|
if (shown.header.issue !== null)
|
|
15238
|
-
|
|
14573
|
+
lines.push(`Issue: ${shown.header.issue}`);
|
|
15239
14574
|
if (shown.header.spec !== null)
|
|
15240
|
-
|
|
15241
|
-
|
|
14575
|
+
lines.push(`Spec: ${shown.header.spec}`);
|
|
14576
|
+
lines.push(`Tasks: ${formatCounts(shown.counts)}`);
|
|
15242
14577
|
const unstaged = shown.tasks.filter((task) => task.stage === null);
|
|
15243
14578
|
if (unstaged.length > 0)
|
|
15244
|
-
|
|
14579
|
+
lines.push("", ...unstaged.map(taskLine));
|
|
15245
14580
|
for (const [index, stage] of shown.stages.entries()) {
|
|
15246
|
-
|
|
14581
|
+
lines.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
|
|
15247
14582
|
}
|
|
15248
14583
|
if (shown.issues.length > 0) {
|
|
15249
|
-
|
|
14584
|
+
lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative7, issue)}`));
|
|
15250
14585
|
}
|
|
15251
|
-
return
|
|
14586
|
+
return lines;
|
|
15252
14587
|
}
|
|
15253
14588
|
var planShowCommand = {
|
|
15254
14589
|
name: "plan show",
|
|
@@ -15820,19 +15155,19 @@ var YES_ANSWERS2 = ["y", "yes"];
|
|
|
15820
15155
|
function answerOf(reading) {
|
|
15821
15156
|
return reading.ok ? reading.verdict.answer : "unread";
|
|
15822
15157
|
}
|
|
15823
|
-
function guardRefusal(head,
|
|
15824
|
-
return new CommandExit(1, [`\u274C ${head}`, ...
|
|
15158
|
+
function guardRefusal(head, lines) {
|
|
15159
|
+
return new CommandExit(1, [`\u274C ${head}`, ...lines.map((line) => ` ${line}`)].join(`
|
|
15825
15160
|
`));
|
|
15826
15161
|
}
|
|
15827
15162
|
function refusalHead(number, answer2) {
|
|
15828
15163
|
return answer2 === "collision" ? `rafa pr merge refuses #${number}: its release guard reads collision, which only dangerous.acceptVersionCollision lets through.` : `rafa pr merge refuses #${number}: its release guard reads ${answer2}, and pr.versionCollision is refuse.`;
|
|
15829
15164
|
}
|
|
15830
|
-
async function askGuard(step, answer2,
|
|
15165
|
+
async function askGuard(step, answer2, lines) {
|
|
15831
15166
|
const { number } = step.detail;
|
|
15832
15167
|
if (!step.isTerminal()) {
|
|
15833
|
-
throw guardRefusal(`rafa pr merge asks before merging #${number} with its release guard reading ${answer2} (pr.versionCollision is ask),` + " and standard input is no terminal; --yes does not answer it.",
|
|
15168
|
+
throw guardRefusal(`rafa pr merge asks before merging #${number} with its release guard reading ${answer2} (pr.versionCollision is ask),` + " and standard input is no terminal; --yes does not answer it.", lines);
|
|
15834
15169
|
}
|
|
15835
|
-
for (const line of
|
|
15170
|
+
for (const line of lines)
|
|
15836
15171
|
step.warn(line);
|
|
15837
15172
|
const prompter = step.openPrompter();
|
|
15838
15173
|
try {
|
|
@@ -15842,11 +15177,11 @@ async function askGuard(step, answer2, lines2) {
|
|
|
15842
15177
|
prompter.close();
|
|
15843
15178
|
}
|
|
15844
15179
|
}
|
|
15845
|
-
function printLines(step, reaction,
|
|
15180
|
+
function printLines(step, reaction, lines) {
|
|
15846
15181
|
if (reaction === "silent")
|
|
15847
15182
|
return;
|
|
15848
15183
|
const write = reaction === "print" ? step.info : step.warn;
|
|
15849
|
-
for (const line of
|
|
15184
|
+
for (const line of lines)
|
|
15850
15185
|
write(line);
|
|
15851
15186
|
if (reaction === "accept") {
|
|
15852
15187
|
step.warn(`#${step.detail.number} merges anyway: dangerous.acceptVersionCollision is true.`);
|
|
@@ -15868,13 +15203,13 @@ async function guardBeforeMerge(step) {
|
|
|
15868
15203
|
});
|
|
15869
15204
|
const answer2 = answerOf(reading);
|
|
15870
15205
|
const reaction = guardReaction(reading, settings);
|
|
15871
|
-
const
|
|
15872
|
-
const report3 = { answer: answer2, reaction, lines
|
|
15206
|
+
const lines = guardLines(reading, settings.releaseVersionFile);
|
|
15207
|
+
const report3 = { answer: answer2, reaction, lines };
|
|
15873
15208
|
if (reaction === "refuse")
|
|
15874
|
-
throw guardRefusal(refusalHead(step.detail.number, answer2),
|
|
15209
|
+
throw guardRefusal(refusalHead(step.detail.number, answer2), lines);
|
|
15875
15210
|
if (reaction === "ask")
|
|
15876
|
-
return { report: report3, go: await askGuard(step, answer2,
|
|
15877
|
-
printLines(step, reaction,
|
|
15211
|
+
return { report: report3, go: await askGuard(step, answer2, lines) };
|
|
15212
|
+
printLines(step, reaction, lines);
|
|
15878
15213
|
return { report: report3, go: true };
|
|
15879
15214
|
}
|
|
15880
15215
|
|
|
@@ -15882,8 +15217,16 @@ async function guardBeforeMerge(step) {
|
|
|
15882
15217
|
var NO_WORKFLOW_WARNING = "nothing on GitHub has tested this branch; you are relying on the checks run locally";
|
|
15883
15218
|
var WORKFLOWS_EXIST_WARNING = "CI may not have started (a path filter, a draft, Actions disabled, or it has not registered yet); this is probably not what you want";
|
|
15884
15219
|
var UNCHECKED_MERGE_SENTENCE = "Merged with no checks reported, by rafa pr merge --skip-checks.";
|
|
15885
|
-
function uncheckedCaseOf(workflowCount) {
|
|
15886
|
-
|
|
15220
|
+
function uncheckedCaseOf(workflowCount, baseWorkflows = null) {
|
|
15221
|
+
if (workflowCount === 0)
|
|
15222
|
+
return "no-workflow";
|
|
15223
|
+
if (workflowCount !== null && baseWorkflows !== null && !baseWorkflows.runsOnPullRequests) {
|
|
15224
|
+
return "no-pull-request-workflow";
|
|
15225
|
+
}
|
|
15226
|
+
return "workflows-exist";
|
|
15227
|
+
}
|
|
15228
|
+
function noPullRequestWorkflowLine(base) {
|
|
15229
|
+
return `no workflow runs on pull requests into ${base}`;
|
|
15887
15230
|
}
|
|
15888
15231
|
function workflowCountLine(workflowCount) {
|
|
15889
15232
|
if (workflowCount === null)
|
|
@@ -15897,26 +15240,181 @@ function skipChecksCommand(number) {
|
|
|
15897
15240
|
function uncheckedQuestion(number) {
|
|
15898
15241
|
return `Merge #${number} with no checks? [y/N]`;
|
|
15899
15242
|
}
|
|
15900
|
-
function uncheckedComment(workflowCount) {
|
|
15901
|
-
|
|
15243
|
+
function uncheckedComment(workflowCount, baseLine = null) {
|
|
15244
|
+
const countLine2 = workflowCountLine(workflowCount);
|
|
15245
|
+
const paragraphs = baseLine === null ? [UNCHECKED_MERGE_SENTENCE, countLine2] : [UNCHECKED_MERGE_SENTENCE, countLine2, baseLine];
|
|
15246
|
+
return paragraphs.join(`
|
|
15902
15247
|
|
|
15903
|
-
|
|
15248
|
+
`);
|
|
15904
15249
|
}
|
|
15905
|
-
function
|
|
15906
|
-
|
|
15907
|
-
|
|
15250
|
+
function warningOf(which, baseWorkflows) {
|
|
15251
|
+
if (which === "no-workflow")
|
|
15252
|
+
return NO_WORKFLOW_WARNING;
|
|
15253
|
+
if (which === "no-pull-request-workflow" && baseWorkflows !== null)
|
|
15254
|
+
return noPullRequestWorkflowLine(baseWorkflows.base);
|
|
15255
|
+
return WORKFLOWS_EXIST_WARNING;
|
|
15256
|
+
}
|
|
15257
|
+
function readUnchecked(number, workflowCount, baseWorkflows = null) {
|
|
15258
|
+
const which = uncheckedCaseOf(workflowCount, baseWorkflows);
|
|
15259
|
+
const warning = warningOf(which, baseWorkflows);
|
|
15260
|
+
const isBaseCase = which === "no-pull-request-workflow";
|
|
15908
15261
|
return Object.freeze({
|
|
15909
15262
|
case: which,
|
|
15910
15263
|
warning: Object.freeze([workflowCountLine(workflowCount), warning]),
|
|
15911
|
-
yesMayAnswer: which
|
|
15264
|
+
yesMayAnswer: which !== "workflows-exist",
|
|
15912
15265
|
question: uncheckedQuestion(number),
|
|
15913
|
-
comment: uncheckedComment(workflowCount)
|
|
15266
|
+
comment: uncheckedComment(workflowCount, isBaseCase ? warning : null)
|
|
15914
15267
|
});
|
|
15915
15268
|
}
|
|
15916
15269
|
|
|
15270
|
+
// src/pr/workflow-triggers.ts
|
|
15271
|
+
var PULL_REQUEST_EVENT = "pull_request";
|
|
15272
|
+
var UNKNOWN = "unknown";
|
|
15273
|
+
var BRACKET_BODY = /^[A-Za-z0-9-]+$/;
|
|
15274
|
+
var REGEX_SPECIAL = /[.*+?^${}()|[\]\\/]/g;
|
|
15275
|
+
function workflowsRunOnPullRequestsInto(workflowTexts, base) {
|
|
15276
|
+
return workflowTexts.some((text) => workflowNamesBase(text, base));
|
|
15277
|
+
}
|
|
15278
|
+
function workflowNamesBase(workflowText, base) {
|
|
15279
|
+
let document;
|
|
15280
|
+
try {
|
|
15281
|
+
document = Bun.YAML.parse(workflowText);
|
|
15282
|
+
} catch {
|
|
15283
|
+
return true;
|
|
15284
|
+
}
|
|
15285
|
+
return readWorkflow(document, base) !== false;
|
|
15286
|
+
}
|
|
15287
|
+
function readWorkflow(document, base) {
|
|
15288
|
+
if (!isPlainObject(document) || !("on" in document))
|
|
15289
|
+
return UNKNOWN;
|
|
15290
|
+
const on = document.on;
|
|
15291
|
+
if (typeof on === "string")
|
|
15292
|
+
return on === PULL_REQUEST_EVENT;
|
|
15293
|
+
if (Array.isArray(on)) {
|
|
15294
|
+
if (!on.every((event) => typeof event === "string"))
|
|
15295
|
+
return UNKNOWN;
|
|
15296
|
+
return on.includes(PULL_REQUEST_EVENT);
|
|
15297
|
+
}
|
|
15298
|
+
if (!isPlainObject(on))
|
|
15299
|
+
return UNKNOWN;
|
|
15300
|
+
if (!(PULL_REQUEST_EVENT in on))
|
|
15301
|
+
return false;
|
|
15302
|
+
return readPullRequestTrigger(on[PULL_REQUEST_EVENT], base);
|
|
15303
|
+
}
|
|
15304
|
+
function readPullRequestTrigger(trigger, base) {
|
|
15305
|
+
if (trigger === null || trigger === undefined)
|
|
15306
|
+
return true;
|
|
15307
|
+
if (!isPlainObject(trigger))
|
|
15308
|
+
return UNKNOWN;
|
|
15309
|
+
const branches = trigger.branches;
|
|
15310
|
+
const ignored = trigger["branches-ignore"];
|
|
15311
|
+
if (branches !== undefined && ignored !== undefined)
|
|
15312
|
+
return UNKNOWN;
|
|
15313
|
+
if (branches !== undefined)
|
|
15314
|
+
return readBranches(branches, base);
|
|
15315
|
+
if (ignored !== undefined)
|
|
15316
|
+
return readBranchesIgnore(ignored, base);
|
|
15317
|
+
return true;
|
|
15318
|
+
}
|
|
15319
|
+
function readBranches(filter, base) {
|
|
15320
|
+
const patterns = patternListOf(filter);
|
|
15321
|
+
if (patterns === null)
|
|
15322
|
+
return UNKNOWN;
|
|
15323
|
+
let named2 = false;
|
|
15324
|
+
for (const pattern of patterns) {
|
|
15325
|
+
const negated = pattern.startsWith("!");
|
|
15326
|
+
const matches = branchPatternMatches(negated ? pattern.slice(1) : pattern, base);
|
|
15327
|
+
if (matches === null)
|
|
15328
|
+
return UNKNOWN;
|
|
15329
|
+
if (matches)
|
|
15330
|
+
named2 = !negated;
|
|
15331
|
+
}
|
|
15332
|
+
return named2;
|
|
15333
|
+
}
|
|
15334
|
+
function readBranchesIgnore(filter, base) {
|
|
15335
|
+
const patterns = patternListOf(filter);
|
|
15336
|
+
if (patterns === null || patterns.some((pattern) => pattern.startsWith("!")))
|
|
15337
|
+
return UNKNOWN;
|
|
15338
|
+
let anyMatch = false;
|
|
15339
|
+
for (const pattern of patterns) {
|
|
15340
|
+
const matches = branchPatternMatches(pattern, base);
|
|
15341
|
+
if (matches === null)
|
|
15342
|
+
return UNKNOWN;
|
|
15343
|
+
anyMatch = anyMatch || matches;
|
|
15344
|
+
}
|
|
15345
|
+
return !anyMatch;
|
|
15346
|
+
}
|
|
15347
|
+
function patternListOf(filter) {
|
|
15348
|
+
if (typeof filter === "string")
|
|
15349
|
+
return [filter];
|
|
15350
|
+
if (Array.isArray(filter) && filter.every((pattern) => typeof pattern === "string"))
|
|
15351
|
+
return filter;
|
|
15352
|
+
return null;
|
|
15353
|
+
}
|
|
15354
|
+
function branchPatternMatches(pattern, branch) {
|
|
15355
|
+
const source = regexSourceOf(pattern);
|
|
15356
|
+
if (source === null)
|
|
15357
|
+
return null;
|
|
15358
|
+
try {
|
|
15359
|
+
return new RegExp(`^${source}$`).test(branch);
|
|
15360
|
+
} catch {
|
|
15361
|
+
return null;
|
|
15362
|
+
}
|
|
15363
|
+
}
|
|
15364
|
+
function regexSourceOf(pattern) {
|
|
15365
|
+
let source = "";
|
|
15366
|
+
let quantifiable = false;
|
|
15367
|
+
let index = 0;
|
|
15368
|
+
while (index < pattern.length) {
|
|
15369
|
+
const char = pattern.charAt(index);
|
|
15370
|
+
if (char === "*") {
|
|
15371
|
+
const isDouble = pattern.charAt(index + 1) === "*";
|
|
15372
|
+
source += isDouble ? ".*" : "[^/]*";
|
|
15373
|
+
index += isDouble ? 2 : 1;
|
|
15374
|
+
quantifiable = false;
|
|
15375
|
+
} else if (char === "?" || char === "+") {
|
|
15376
|
+
if (!quantifiable)
|
|
15377
|
+
return null;
|
|
15378
|
+
source += char;
|
|
15379
|
+
index += 1;
|
|
15380
|
+
quantifiable = false;
|
|
15381
|
+
} else if (char === "[") {
|
|
15382
|
+
const close = pattern.indexOf("]", index + 1);
|
|
15383
|
+
if (close === -1)
|
|
15384
|
+
return null;
|
|
15385
|
+
const body = pattern.slice(index + 1, close);
|
|
15386
|
+
if (!BRACKET_BODY.test(body))
|
|
15387
|
+
return null;
|
|
15388
|
+
source += `[${body}]`;
|
|
15389
|
+
index = close + 1;
|
|
15390
|
+
quantifiable = true;
|
|
15391
|
+
} else if (char === "\\") {
|
|
15392
|
+
if (index + 1 >= pattern.length)
|
|
15393
|
+
return null;
|
|
15394
|
+
source += escapeRegex(pattern.charAt(index + 1));
|
|
15395
|
+
index += 2;
|
|
15396
|
+
quantifiable = true;
|
|
15397
|
+
} else {
|
|
15398
|
+
source += escapeRegex(char);
|
|
15399
|
+
index += 1;
|
|
15400
|
+
quantifiable = true;
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15403
|
+
return source;
|
|
15404
|
+
}
|
|
15405
|
+
function escapeRegex(char) {
|
|
15406
|
+
return char.replace(REGEX_SPECIAL, "\\$&");
|
|
15407
|
+
}
|
|
15408
|
+
function isPlainObject(value) {
|
|
15409
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15410
|
+
}
|
|
15411
|
+
|
|
15917
15412
|
// src/commands/pr/merge-unchecked.ts
|
|
15918
15413
|
var INDENT4 = " ";
|
|
15919
15414
|
var YES_ANSWERS3 = ["y", "yes"];
|
|
15415
|
+
var WORKFLOWS_DIR = ".github/workflows/";
|
|
15416
|
+
var WORKFLOW_ENDINGS = [".yml", ".yaml"];
|
|
15417
|
+
var LS_TREE_ENTRY = /^\d+ (\w+) ([0-9a-f]+)\t(.+)$/s;
|
|
15920
15418
|
function refusal10(head, reading, summary, tail) {
|
|
15921
15419
|
return new CommandExit(1, [
|
|
15922
15420
|
`\u274C ${head}`,
|
|
@@ -15933,10 +15431,37 @@ async function workflowCountOf(pulls) {
|
|
|
15933
15431
|
return null;
|
|
15934
15432
|
}
|
|
15935
15433
|
}
|
|
15434
|
+
function workflowBlobsOf(listing) {
|
|
15435
|
+
return listing.split("\x00").map((entry) => LS_TREE_ENTRY.exec(entry)).filter((match) => match !== null).filter(([, type, , path]) => type === "blob" && WORKFLOW_ENDINGS.some((ending) => path?.endsWith(ending) === true)).map(([, , oid]) => oid ?? "");
|
|
15436
|
+
}
|
|
15437
|
+
function readBaseWorkflows(git, base) {
|
|
15438
|
+
if (base === "" || base.startsWith("-"))
|
|
15439
|
+
return null;
|
|
15440
|
+
if (!git(["fetch", "origin", base]).ok)
|
|
15441
|
+
return null;
|
|
15442
|
+
const listed2 = git(["ls-tree", "--full-tree", "-z", `origin/${base}`, WORKFLOWS_DIR]);
|
|
15443
|
+
if (!listed2.ok)
|
|
15444
|
+
return null;
|
|
15445
|
+
const blobs = workflowBlobsOf(listed2.stdout);
|
|
15446
|
+
if (blobs.length === 0)
|
|
15447
|
+
return null;
|
|
15448
|
+
const shown = blobs.map((oid) => git(["cat-file", "blob", oid]));
|
|
15449
|
+
if (shown.some((result2) => !result2.ok))
|
|
15450
|
+
return null;
|
|
15451
|
+
const texts = shown.map((result2) => result2.stdout);
|
|
15452
|
+
return Object.freeze({ base, runsOnPullRequests: workflowsRunOnPullRequestsInto(texts, base) });
|
|
15453
|
+
}
|
|
15454
|
+
function baseWorkflowsOf(options, workflowCount) {
|
|
15455
|
+
const { git, base } = options;
|
|
15456
|
+
if (workflowCount === null || workflowCount === 0 || git === undefined || base === undefined)
|
|
15457
|
+
return null;
|
|
15458
|
+
return readBaseWorkflows(git, base);
|
|
15459
|
+
}
|
|
15936
15460
|
async function readUncheckedMerge(options) {
|
|
15937
15461
|
const { pulls, number, yes, summary } = options;
|
|
15938
15462
|
const workflowCount = await workflowCountOf(pulls);
|
|
15939
|
-
const
|
|
15463
|
+
const baseWorkflows = baseWorkflowsOf(options, workflowCount);
|
|
15464
|
+
const reading = readUnchecked(number, workflowCount, baseWorkflows);
|
|
15940
15465
|
if (yes && !reading.yesMayAnswer) {
|
|
15941
15466
|
throw refusal10(`rafa pr merge refuses --yes for #${number} with no checks: workflows exist, or their count could not be read.`, reading, summary, "That is more likely a fault than a choice, so a person has to answer. Run it again without --yes on a terminal.");
|
|
15942
15467
|
}
|
|
@@ -15945,7 +15470,7 @@ async function readUncheckedMerge(options) {
|
|
|
15945
15470
|
const tail = reading.yesMayAnswer ? "Merge it without the question with --yes." : "--yes is refused here, so run it on a terminal and answer it.";
|
|
15946
15471
|
throw refusal10(`rafa pr merge asks before merging #${number} with no checks, and standard input is no terminal.`, reading, summary, tail);
|
|
15947
15472
|
}
|
|
15948
|
-
return Object.freeze({ workflowCount, reading, yes });
|
|
15473
|
+
return Object.freeze({ workflowCount, baseWorkflows, reading, yes });
|
|
15949
15474
|
}
|
|
15950
15475
|
function isYes(answer2) {
|
|
15951
15476
|
return answer2 !== null && YES_ANSWERS3.includes(answer2.trim().toLowerCase());
|
|
@@ -15984,8 +15509,8 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
|
|
|
15984
15509
|
var USAGE36 = PR_USAGE.merge;
|
|
15985
15510
|
var YES_ANSWERS4 = ["y", "yes"];
|
|
15986
15511
|
var DEFAULT_MERGE_SEAMS = Object.freeze({});
|
|
15987
|
-
function refusal11(
|
|
15988
|
-
return new CommandExit(1,
|
|
15512
|
+
function refusal11(lines) {
|
|
15513
|
+
return new CommandExit(1, lines.join(`
|
|
15989
15514
|
`));
|
|
15990
15515
|
}
|
|
15991
15516
|
function readMethodFlag(flags, usage2) {
|
|
@@ -16127,8 +15652,8 @@ async function reportAfterCleanUp(context, pr, seams, detail) {
|
|
|
16127
15652
|
return { unblocked: null, freed };
|
|
16128
15653
|
}
|
|
16129
15654
|
async function askToMerge(context, pr, seams, ask) {
|
|
16130
|
-
const { number, summary, yes, skipChecks } = ask;
|
|
16131
|
-
const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal }) : null;
|
|
15655
|
+
const { number, summary, yes, skipChecks, base, git } = ask;
|
|
15656
|
+
const unchecked = skipChecks ? await readUncheckedMerge({ pulls: pr.pulls, number, yes, summary, isTerminal: seams.isTerminal, base, git }) : null;
|
|
16132
15657
|
if (unchecked === null && !yes)
|
|
16133
15658
|
requireTerminal(seams, summary);
|
|
16134
15659
|
context.output.info(summary);
|
|
@@ -16193,7 +15718,7 @@ async function runMerge2(context, seams) {
|
|
|
16193
15718
|
openPrompter: prompterOf(seams)
|
|
16194
15719
|
});
|
|
16195
15720
|
const summary = summaryLine2(detail, method);
|
|
16196
|
-
const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks }) : { go: false, unchecked: null };
|
|
15721
|
+
const answer2 = guard.go ? await askToMerge(context, pr, seams, { number: detail.number, summary, yes, skipChecks, base: detail.baseRefName, git }) : { go: false, unchecked: null };
|
|
16197
15722
|
const answered3 = {
|
|
16198
15723
|
number: detail.number,
|
|
16199
15724
|
source: pick.source,
|
|
@@ -16257,7 +15782,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
16257
15782
|
subject: "pr",
|
|
16258
15783
|
action: "merge",
|
|
16259
15784
|
summary: "merge a pull request and clean up both branches",
|
|
16260
|
-
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15785
|
+
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow, or" + " where no workflow file on the base has a `pull_request` trigger naming it)," + " and after the merge posts one comment on the pull request saying so. Where the release runs, the release" + " guard reads the branch before the question: a `collision` is refused unless" + " `dangerous.acceptVersionCollision` is true, and `pr.versionCollision` sets whether a `missing` or" + " `stale` branch merges silently, with a warning, after its own question, or not at all. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " board.relationships set to native it ticks no epic checklist and, in place of that reading, prints the" + " open issues the merge freed, whose blockers GitHub clears by itself, asking nothing and writing nothing. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading, or the freed issues in native mode, are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
16261
15786
|
args: [
|
|
16262
15787
|
{
|
|
16263
15788
|
name: "n",
|
|
@@ -16273,7 +15798,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
16273
15798
|
},
|
|
16274
15799
|
{
|
|
16275
15800
|
name: "skip-checks",
|
|
16276
|
-
description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow or its workflow count could not be read.",
|
|
15801
|
+
description: "Merge a pull request that reports no checks at all, after a warning and its own question." + " Refused where the pull request reports any check; `--yes` beside it is refused where the repository" + " defines a workflow that may run on pull requests into the base, or its workflow count could not be read.",
|
|
16277
15802
|
type: "boolean"
|
|
16278
15803
|
},
|
|
16279
15804
|
{
|
|
@@ -16359,9 +15884,9 @@ function parseFailedLog(text) {
|
|
|
16359
15884
|
end -= 1;
|
|
16360
15885
|
return raw.slice(0, end).map((line) => parseFailedLogLine(line));
|
|
16361
15886
|
}
|
|
16362
|
-
function readFailedStep(
|
|
15887
|
+
function readFailedStep(lines) {
|
|
16363
15888
|
let group;
|
|
16364
|
-
for (const line of
|
|
15889
|
+
for (const line of lines) {
|
|
16365
15890
|
const head = groupStep(line.text);
|
|
16366
15891
|
if (head !== undefined)
|
|
16367
15892
|
group = head;
|
|
@@ -16372,14 +15897,14 @@ function readFailedStep(lines2) {
|
|
|
16372
15897
|
if (group !== undefined)
|
|
16373
15898
|
return { name: group, source: "group-marker" };
|
|
16374
15899
|
}
|
|
16375
|
-
const column =
|
|
15900
|
+
const column = lines.find((line) => line.step !== undefined)?.step;
|
|
16376
15901
|
if (column !== undefined)
|
|
16377
15902
|
return { name: column, source: "step-column" };
|
|
16378
15903
|
return group === undefined ? undefined : { name: group, source: "group-marker" };
|
|
16379
15904
|
}
|
|
16380
|
-
function readJobs(
|
|
15905
|
+
function readJobs(lines) {
|
|
16381
15906
|
const jobs = [];
|
|
16382
|
-
for (const line of
|
|
15907
|
+
for (const line of lines) {
|
|
16383
15908
|
if (line.job !== undefined && !jobs.includes(line.job))
|
|
16384
15909
|
jobs.push(line.job);
|
|
16385
15910
|
}
|
|
@@ -16395,14 +15920,14 @@ function readCap(value) {
|
|
|
16395
15920
|
}
|
|
16396
15921
|
function readFailedLog(text, options = {}) {
|
|
16397
15922
|
const cap = readCap(options.maxLines);
|
|
16398
|
-
const
|
|
16399
|
-
const kept =
|
|
15923
|
+
const lines = parseFailedLog(text);
|
|
15924
|
+
const kept = lines.slice(Math.max(0, lines.length - cap));
|
|
16400
15925
|
return {
|
|
16401
|
-
jobs: readJobs(
|
|
16402
|
-
step: readFailedStep(
|
|
15926
|
+
jobs: readJobs(lines),
|
|
15927
|
+
step: readFailedStep(lines),
|
|
16403
15928
|
lines: kept.map((line) => line.text),
|
|
16404
|
-
omitted:
|
|
16405
|
-
total:
|
|
15929
|
+
omitted: lines.length - kept.length,
|
|
15930
|
+
total: lines.length
|
|
16406
15931
|
};
|
|
16407
15932
|
}
|
|
16408
15933
|
|
|
@@ -17277,7 +16802,7 @@ var TRIAGE_FRESH_WINDOW_MS = TRIAGE_FRESH_HOURS * HOUR_MS2;
|
|
|
17277
16802
|
var MAX_TRIAGE_CANDIDATES = 3;
|
|
17278
16803
|
var REFUSE_EXIT_CODE = 2;
|
|
17279
16804
|
var DAYS_FROM_HOURS = 48;
|
|
17280
|
-
var
|
|
16805
|
+
var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
|
|
17281
16806
|
var INDENT6 = " ";
|
|
17282
16807
|
function ageMs(updatedAt, now) {
|
|
17283
16808
|
const then = Date.parse(updatedAt);
|
|
@@ -17290,7 +16815,7 @@ function isFreshCandidate(updatedAt, now) {
|
|
|
17290
16815
|
const age = ageMs(updatedAt, now);
|
|
17291
16816
|
return age !== null && age <= TRIAGE_FRESH_WINDOW_MS;
|
|
17292
16817
|
}
|
|
17293
|
-
function
|
|
16818
|
+
function plural2(count, singular, many) {
|
|
17294
16819
|
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
17295
16820
|
}
|
|
17296
16821
|
function describeAge(updatedAt, now) {
|
|
@@ -17303,8 +16828,8 @@ function describeAge(updatedAt, now) {
|
|
|
17303
16828
|
if (hours < 1)
|
|
17304
16829
|
return "less than an hour ago";
|
|
17305
16830
|
if (hours < DAYS_FROM_HOURS)
|
|
17306
|
-
return `${
|
|
17307
|
-
return `${
|
|
16831
|
+
return `${plural2(hours, "hour", "hours")} ago`;
|
|
16832
|
+
return `${plural2(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
17308
16833
|
}
|
|
17309
16834
|
function triageCommand(number, resolve11 = false) {
|
|
17310
16835
|
return resolve11 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
@@ -17343,24 +16868,24 @@ function plain(decision, assess, headline) {
|
|
|
17343
16868
|
});
|
|
17344
16869
|
}
|
|
17345
16870
|
function listing(decision, assess, rest, headline, exitCode = 0) {
|
|
17346
|
-
const
|
|
16871
|
+
const lines = Object.freeze(rest.map((one) => skippedLine(one)));
|
|
17347
16872
|
return Object.freeze({
|
|
17348
16873
|
decision,
|
|
17349
16874
|
assess,
|
|
17350
16875
|
skipped: Object.freeze([...rest]),
|
|
17351
16876
|
exitCode,
|
|
17352
16877
|
headline,
|
|
17353
|
-
lines
|
|
17354
|
-
message: [headline, ...
|
|
16878
|
+
lines,
|
|
16879
|
+
message: [headline, ...lines].join(`
|
|
17355
16880
|
`)
|
|
17356
16881
|
});
|
|
17357
16882
|
}
|
|
17358
16883
|
function refusal12(rest, count) {
|
|
17359
|
-
const headline = `${
|
|
16884
|
+
const headline = `${REFUSAL_PREFIX}: ${plural2(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
|
|
17360
16885
|
return listing("refuse", Object.freeze([]), rest, headline, REFUSE_EXIT_CODE);
|
|
17361
16886
|
}
|
|
17362
16887
|
function someReading(fresh, stale, count) {
|
|
17363
|
-
const older = stale.length === 0 ? "" : `; ${
|
|
16888
|
+
const older = stale.length === 0 ? "" : `; ${plural2(stale.length, "older one is", "older ones are")} skipped`;
|
|
17364
16889
|
const headline = `${count} red: assessing ${joinNumbers(fresh)}${older}`;
|
|
17365
16890
|
return listing("some", fresh, stale, headline);
|
|
17366
16891
|
}
|
|
@@ -17376,9 +16901,9 @@ function selectTriagePullRequests(input) {
|
|
|
17376
16901
|
}
|
|
17377
16902
|
const candidates = byNumber2(input.candidates);
|
|
17378
16903
|
const count = candidates.length;
|
|
17379
|
-
const
|
|
16904
|
+
const lines = candidates.map((pull2) => skipped(pull2, now, resolve11));
|
|
17380
16905
|
if (resolve11 && count > 1)
|
|
17381
|
-
return refusal12(
|
|
16906
|
+
return refusal12(lines, count);
|
|
17382
16907
|
if (count === 0) {
|
|
17383
16908
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
17384
16909
|
}
|
|
@@ -17387,9 +16912,9 @@ function selectTriagePullRequests(input) {
|
|
|
17387
16912
|
return plain("single", Object.freeze([only]), `1 red: assessing #${only.number}, last moved ${describeAge(only.updatedAt, now)}`);
|
|
17388
16913
|
}
|
|
17389
16914
|
if (count > MAX_TRIAGE_CANDIDATES)
|
|
17390
|
-
return listReading(
|
|
16915
|
+
return listReading(lines, count);
|
|
17391
16916
|
const fresh = candidates.filter((pull2) => isFreshCandidate(pull2.updatedAt, now));
|
|
17392
|
-
const stale =
|
|
16917
|
+
const stale = lines.filter((one) => !one.fresh);
|
|
17393
16918
|
if (fresh.length === 0)
|
|
17394
16919
|
return listReading(stale, count);
|
|
17395
16920
|
return someReading(Object.freeze(fresh), stale, count);
|
|
@@ -17462,7 +16987,7 @@ function readConflict(git, base, head) {
|
|
|
17462
16987
|
// src/commands/pr/triage-read.ts
|
|
17463
16988
|
var RUN_URL = /\/actions\/runs\/(\d+)(?:[/?#]|$)/;
|
|
17464
16989
|
var CONFLICT_UNREAD = "unread";
|
|
17465
|
-
var
|
|
16990
|
+
var REMOTE2 = "origin";
|
|
17466
16991
|
function runIdOf(link) {
|
|
17467
16992
|
return RUN_URL.exec(link)?.[1] ?? null;
|
|
17468
16993
|
}
|
|
@@ -17510,10 +17035,10 @@ async function readWorkflowCount(pulls, rows) {
|
|
|
17510
17035
|
}
|
|
17511
17036
|
}
|
|
17512
17037
|
function baseCandidates(refs2) {
|
|
17513
|
-
return [`${
|
|
17038
|
+
return [`${REMOTE2}/${refs2.baseRefName}`, refs2.baseRefName];
|
|
17514
17039
|
}
|
|
17515
17040
|
function headCandidates(refs2) {
|
|
17516
|
-
return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${
|
|
17041
|
+
return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${REMOTE2}/${refs2.headRefName}`];
|
|
17517
17042
|
}
|
|
17518
17043
|
function resolvesRef(git, ref8) {
|
|
17519
17044
|
return git(["rev-parse", "--verify", "--quiet", `${ref8}^{commit}`]).ok;
|
|
@@ -17737,8 +17262,8 @@ function withTaskBudget(plan2, usd) {
|
|
|
17737
17262
|
}
|
|
17738
17263
|
|
|
17739
17264
|
// src/pr/plans/load.ts
|
|
17740
|
-
import { existsSync as
|
|
17741
|
-
import { dirname as dirname6, join as
|
|
17265
|
+
import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
|
|
17266
|
+
import { dirname as dirname6, join as join29 } from "path";
|
|
17742
17267
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17743
17268
|
var MODULE_DIR4 = dirname6(fileURLToPath4(import.meta.url));
|
|
17744
17269
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
@@ -17755,8 +17280,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
17755
17280
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
17756
17281
|
const name = pinnedPlanFileName(triageClass);
|
|
17757
17282
|
return [
|
|
17758
|
-
|
|
17759
|
-
|
|
17283
|
+
join29(moduleDir, PINNED_PLANS_DIRNAME, name),
|
|
17284
|
+
join29(moduleDir, name)
|
|
17760
17285
|
];
|
|
17761
17286
|
}
|
|
17762
17287
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -17764,7 +17289,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
17764
17289
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
17765
17290
|
}
|
|
17766
17291
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
17767
|
-
const found = candidates.find((candidate) =>
|
|
17292
|
+
const found = candidates.find((candidate) => existsSync9(candidate));
|
|
17768
17293
|
if (found === undefined) {
|
|
17769
17294
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
17770
17295
|
}
|
|
@@ -17909,19 +17434,19 @@ function readAttemptRepeat(input) {
|
|
|
17909
17434
|
|
|
17910
17435
|
// src/commands/pr/resolve-loop.ts
|
|
17911
17436
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
17912
|
-
import { join as
|
|
17913
|
-
var RESOLVE_SUBDIR =
|
|
17437
|
+
import { join as join30 } from "path";
|
|
17438
|
+
var RESOLVE_SUBDIR = join30(".rafa", "resolve");
|
|
17914
17439
|
var RUN_PREFIX = "pr-";
|
|
17915
17440
|
var ATTEMPT_PREFIX = "attempt-";
|
|
17916
17441
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
17917
17442
|
var CHILD_OUTPUT = "text";
|
|
17918
17443
|
function resolveRunDir(home, number, attempt3) {
|
|
17919
|
-
return
|
|
17444
|
+
return join30(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
17920
17445
|
}
|
|
17921
17446
|
function writeResolvePlan(write) {
|
|
17922
17447
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
17923
17448
|
mkdirSync5(dir, { recursive: true });
|
|
17924
|
-
const path =
|
|
17449
|
+
const path = join30(dir, pinnedPlanFileName(write.triageClass));
|
|
17925
17450
|
writeFileSync6(path, write.plan, "utf8");
|
|
17926
17451
|
return path;
|
|
17927
17452
|
}
|
|
@@ -17933,10 +17458,10 @@ async function forwardLines(stream, onLine) {
|
|
|
17933
17458
|
let pending = "";
|
|
17934
17459
|
for await (const chunk of stream) {
|
|
17935
17460
|
pending += decoder.decode(chunk, { stream: true });
|
|
17936
|
-
const
|
|
17461
|
+
const lines = pending.split(`
|
|
17937
17462
|
`);
|
|
17938
|
-
pending =
|
|
17939
|
-
for (const line of
|
|
17463
|
+
pending = lines.pop() ?? "";
|
|
17464
|
+
for (const line of lines)
|
|
17940
17465
|
onLine(line);
|
|
17941
17466
|
}
|
|
17942
17467
|
if (pending !== "")
|
|
@@ -17963,7 +17488,7 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
17963
17488
|
|
|
17964
17489
|
// src/pr/triage/version-convert.ts
|
|
17965
17490
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
|
|
17966
|
-
import { dirname as dirname7, join as
|
|
17491
|
+
import { dirname as dirname7, join as join31, posix } from "path";
|
|
17967
17492
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
17968
17493
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
17969
17494
|
var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
|
|
@@ -18075,7 +17600,7 @@ function planConversion(input, problems) {
|
|
|
18075
17600
|
}
|
|
18076
17601
|
let branchVersionText;
|
|
18077
17602
|
try {
|
|
18078
|
-
branchVersionText = readFileSync19(
|
|
17603
|
+
branchVersionText = readFileSync19(join31(input.worktree, settings.releaseVersionFile), "utf8");
|
|
18079
17604
|
} catch (error) {
|
|
18080
17605
|
return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
18081
17606
|
}
|
|
@@ -18101,7 +17626,7 @@ function writeConversion(input, plan2) {
|
|
|
18101
17626
|
[input.settings.releaseChangelog, plan2.changelogText]
|
|
18102
17627
|
];
|
|
18103
17628
|
for (const [path, text] of writes) {
|
|
18104
|
-
const target =
|
|
17629
|
+
const target = join31(input.worktree, path);
|
|
18105
17630
|
try {
|
|
18106
17631
|
mkdirSync6(dirname7(target), { recursive: true });
|
|
18107
17632
|
writeFileSync7(target, text);
|
|
@@ -18913,7 +18438,7 @@ var USAGE40 = PR_USAGE.wait;
|
|
|
18913
18438
|
var TIMEOUT_FLAG = "timeout";
|
|
18914
18439
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
18915
18440
|
var MS_PER_MIN = 60000;
|
|
18916
|
-
var
|
|
18441
|
+
var MS_PER_SECOND2 = 1000;
|
|
18917
18442
|
var WAIT_EXIT_CODES = Object.freeze({
|
|
18918
18443
|
green: 0,
|
|
18919
18444
|
red: 1,
|
|
@@ -18942,7 +18467,7 @@ function pollWord(polls) {
|
|
|
18942
18467
|
return polls === 1 ? "1 poll" : `${polls} polls`;
|
|
18943
18468
|
}
|
|
18944
18469
|
function secondsWord(elapsedMs) {
|
|
18945
|
-
return `${Math.round(elapsedMs /
|
|
18470
|
+
return `${Math.round(elapsedMs / MS_PER_SECOND2)}s`;
|
|
18946
18471
|
}
|
|
18947
18472
|
function verdictWord(verdict) {
|
|
18948
18473
|
return verdict === "timeout" || verdict === "pending" ? "still running" : verdict;
|
|
@@ -19333,9 +18858,9 @@ function tagSettle(worktree, mode, delivered) {
|
|
|
19333
18858
|
}
|
|
19334
18859
|
|
|
19335
18860
|
// src/release/settle-worktree.ts
|
|
19336
|
-
import { existsSync as
|
|
18861
|
+
import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync3, rmSync as rmSync2 } from "fs";
|
|
19337
18862
|
import { tmpdir as tmpdir2 } from "os";
|
|
19338
|
-
import { join as
|
|
18863
|
+
import { join as join32 } from "path";
|
|
19339
18864
|
var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
|
|
19340
18865
|
var WORKTREE_DIRECTORY = "base";
|
|
19341
18866
|
function removeScratch(git, scratch, path) {
|
|
@@ -19352,14 +18877,14 @@ function removeScratch(git, scratch, path) {
|
|
|
19352
18877
|
problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
|
|
19353
18878
|
}
|
|
19354
18879
|
}
|
|
19355
|
-
if (problems.length === 0 &&
|
|
18880
|
+
if (problems.length === 0 && existsSync10(scratch)) {
|
|
19356
18881
|
problems.push(`the scratch directory ${scratch} is still there`);
|
|
19357
18882
|
}
|
|
19358
18883
|
return problems.length === 0 ? null : problems.join("; ");
|
|
19359
18884
|
}
|
|
19360
18885
|
function makeScratch(root) {
|
|
19361
18886
|
try {
|
|
19362
|
-
return
|
|
18887
|
+
return realpathSync3(mkdtempSync2(join32(root, SETTLE_SCRATCH_PREFIX)));
|
|
19363
18888
|
} catch (error) {
|
|
19364
18889
|
return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
|
|
19365
18890
|
}
|
|
@@ -19381,7 +18906,7 @@ function addWorktree(options) {
|
|
|
19381
18906
|
const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
|
|
19382
18907
|
if (typeof scratch !== "string")
|
|
19383
18908
|
return scratch;
|
|
19384
|
-
const path =
|
|
18909
|
+
const path = join32(scratch, WORKTREE_DIRECTORY);
|
|
19385
18910
|
const added = git(["worktree", "add", "--detach", path, commit]);
|
|
19386
18911
|
if (!added.ok) {
|
|
19387
18912
|
return {
|
|
@@ -19435,26 +18960,26 @@ function short(hash) {
|
|
|
19435
18960
|
return hash.slice(0, SHORT_HASH4);
|
|
19436
18961
|
}
|
|
19437
18962
|
function readingLines(reading, ref8) {
|
|
19438
|
-
const
|
|
18963
|
+
const lines = [`Strategy: ${reading.strategy}`];
|
|
19439
18964
|
if (reading.outcome === "unread")
|
|
19440
|
-
return
|
|
18965
|
+
return lines;
|
|
19441
18966
|
if (reading.outcome === "malformed") {
|
|
19442
|
-
return [...
|
|
18967
|
+
return [...lines, `Base: ${ref8} at ${short(reading.commit)}`];
|
|
19443
18968
|
}
|
|
19444
|
-
|
|
18969
|
+
lines.push(`Base: ${ref8} at ${short(reading.commit)}, version ${reading.baseVersion}`);
|
|
19445
18970
|
if (reading.fragments.length === 0) {
|
|
19446
|
-
|
|
18971
|
+
lines.push("Fragments: none waiting");
|
|
19447
18972
|
} else {
|
|
19448
|
-
|
|
18973
|
+
lines.push("Fragments, in the order the base received them:");
|
|
19449
18974
|
reading.fragments.forEach((each, index) => {
|
|
19450
18975
|
const { level, title } = each.fragment;
|
|
19451
|
-
|
|
18976
|
+
lines.push(`${INDENT8}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
|
|
19452
18977
|
});
|
|
19453
18978
|
}
|
|
19454
18979
|
if (reading.outcome === "folded" || reading.outcome === "built" || reading.outcome === "unbuilt") {
|
|
19455
|
-
|
|
18980
|
+
lines.push(`Version: ${reading.baseVersion} \u2192 ${reading.version}`);
|
|
19456
18981
|
}
|
|
19457
|
-
return
|
|
18982
|
+
return lines;
|
|
19458
18983
|
}
|
|
19459
18984
|
function unwrittenEnding(reading) {
|
|
19460
18985
|
switch (reading.outcome) {
|
|
@@ -19468,19 +18993,19 @@ function unwrittenEnding(reading) {
|
|
|
19468
18993
|
return { line: "Nothing to settle: no fragment waits that ships a release.", refuses: false };
|
|
19469
18994
|
}
|
|
19470
18995
|
}
|
|
19471
|
-
function resultOf5(base,
|
|
18996
|
+
function resultOf5(base, lines, problem2) {
|
|
19472
18997
|
const exitCode = problem2 === null ? 0 : 1;
|
|
19473
|
-
return { ...base, lines
|
|
18998
|
+
return { ...base, lines, problem: problem2, exitCode };
|
|
19474
18999
|
}
|
|
19475
19000
|
function dryRunResult(reading, ref8, branch) {
|
|
19476
19001
|
const base = { dryRun: true, delivery: null, reading, delivered: null, tag: null };
|
|
19477
|
-
const
|
|
19002
|
+
const lines = [...readingLines(reading, ref8)];
|
|
19478
19003
|
if (reading.outcome === "folded") {
|
|
19479
|
-
|
|
19480
|
-
return resultOf5(base,
|
|
19004
|
+
lines.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
|
|
19005
|
+
return resultOf5(base, lines, null);
|
|
19481
19006
|
}
|
|
19482
19007
|
const ending = unwrittenEnding(reading);
|
|
19483
|
-
return ending.refuses ? resultOf5(base,
|
|
19008
|
+
return ending.refuses ? resultOf5(base, lines, ending.line) : resultOf5(base, [...lines, ending.line], null);
|
|
19484
19009
|
}
|
|
19485
19010
|
function deliveryEnding(delivered, branch) {
|
|
19486
19011
|
const { outcome } = delivered;
|
|
@@ -19509,18 +19034,18 @@ function deliveryEnding(delivered, branch) {
|
|
|
19509
19034
|
function settledResult(delivered, tag, ref8, branch) {
|
|
19510
19035
|
const reading = delivered.outcome.build;
|
|
19511
19036
|
const base = { dryRun: false, delivery: delivered.delivery, reading, delivered, tag };
|
|
19512
|
-
const
|
|
19037
|
+
const lines = [...readingLines(reading, ref8)];
|
|
19513
19038
|
const ending = deliveryEnding(delivered, branch);
|
|
19514
19039
|
if (ending.refuses)
|
|
19515
|
-
return resultOf5(base,
|
|
19516
|
-
|
|
19040
|
+
return resultOf5(base, lines, ending.line);
|
|
19041
|
+
lines.push(ending.line);
|
|
19517
19042
|
if (tag.outcome === "failed")
|
|
19518
|
-
return resultOf5(base,
|
|
19043
|
+
return resultOf5(base, lines, `\u274C ${tag.sentence}`);
|
|
19519
19044
|
if (tag.outcome === "tagged")
|
|
19520
|
-
|
|
19045
|
+
lines.push(`\u2705 ${tag.sentence}`);
|
|
19521
19046
|
if (tag.outcome === "skipped" && tag.sentence !== null)
|
|
19522
|
-
|
|
19523
|
-
return resultOf5(base,
|
|
19047
|
+
lines.push(`Tag: ${tag.sentence}`);
|
|
19048
|
+
return resultOf5(base, lines, null);
|
|
19524
19049
|
}
|
|
19525
19050
|
function providerFor(project, config, seams) {
|
|
19526
19051
|
requireGhProvider(resolvePrProvider({ configured: config.prProvider, dir: project.root, readRemote: seams.readRemote }));
|
|
@@ -19604,7 +19129,7 @@ var settle_default = createReleaseSettleCommand();
|
|
|
19604
19129
|
|
|
19605
19130
|
// src/commands/release/tag.ts
|
|
19606
19131
|
import { readFileSync as readFileSync20 } from "fs";
|
|
19607
|
-
import { join as
|
|
19132
|
+
import { join as join33 } from "path";
|
|
19608
19133
|
|
|
19609
19134
|
// src/commands/release/release-commit.ts
|
|
19610
19135
|
function unread(reason, message) {
|
|
@@ -19668,7 +19193,7 @@ function textOf(path) {
|
|
|
19668
19193
|
}
|
|
19669
19194
|
}
|
|
19670
19195
|
function readVersion(root, configured) {
|
|
19671
|
-
const resolved =
|
|
19196
|
+
const resolved = join33(root, configured);
|
|
19672
19197
|
const text = textOf(resolved);
|
|
19673
19198
|
if (text === null) {
|
|
19674
19199
|
return {
|
|
@@ -19682,7 +19207,7 @@ function readVersion(root, configured) {
|
|
|
19682
19207
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
19683
19208
|
}
|
|
19684
19209
|
function readNewestRelease(root, configured) {
|
|
19685
|
-
const resolved =
|
|
19210
|
+
const resolved = join33(root, configured);
|
|
19686
19211
|
const text = textOf(resolved);
|
|
19687
19212
|
if (text === null) {
|
|
19688
19213
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -19893,8 +19418,8 @@ function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
19893
19418
|
context.output.result(result2);
|
|
19894
19419
|
return;
|
|
19895
19420
|
}
|
|
19896
|
-
const
|
|
19897
|
-
for (const line of
|
|
19421
|
+
const lines = renderTagged(result2.written, result2.inputs.releaseBranch, result2.followUps);
|
|
19422
|
+
for (const line of lines)
|
|
19898
19423
|
context.output.info(line);
|
|
19899
19424
|
}
|
|
19900
19425
|
};
|
|
@@ -19952,17 +19477,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
19952
19477
|
var roadmap_default = createRoadmapCommand();
|
|
19953
19478
|
|
|
19954
19479
|
// src/commands/skill/backfill.ts
|
|
19955
|
-
import { statSync as
|
|
19956
|
-
import { join as
|
|
19480
|
+
import { statSync as statSync9 } from "fs";
|
|
19481
|
+
import { join as join38, resolve as resolve13 } from "path";
|
|
19957
19482
|
|
|
19958
19483
|
// src/backfill/backup.ts
|
|
19959
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
19960
|
-
import { dirname as dirname8, isAbsolute as isAbsolute6, join as
|
|
19484
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
|
|
19485
|
+
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join35, relative as relative7, resolve as resolve11, sep as sep4 } from "path";
|
|
19961
19486
|
|
|
19962
19487
|
// src/backfill/proposal-file.ts
|
|
19963
19488
|
import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
|
|
19964
|
-
import { join as
|
|
19965
|
-
var BACKFILL_PATH =
|
|
19489
|
+
import { join as join34 } from "path";
|
|
19490
|
+
var BACKFILL_PATH = join34(".rafa", "backfill");
|
|
19966
19491
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
19967
19492
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
19968
19493
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -19988,7 +19513,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
19988
19513
|
].join(`
|
|
19989
19514
|
`);
|
|
19990
19515
|
function proposalPath(dir, batch) {
|
|
19991
|
-
return
|
|
19516
|
+
return join34(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
19992
19517
|
}
|
|
19993
19518
|
function isProposalFileName(name) {
|
|
19994
19519
|
return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -20124,7 +19649,7 @@ function readProposalFiles(dir) {
|
|
|
20124
19649
|
return [];
|
|
20125
19650
|
}
|
|
20126
19651
|
return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
20127
|
-
const path =
|
|
19652
|
+
const path = join34(dir, name);
|
|
20128
19653
|
try {
|
|
20129
19654
|
return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
|
|
20130
19655
|
} catch (error) {
|
|
@@ -20134,19 +19659,19 @@ function readProposalFiles(dir) {
|
|
|
20134
19659
|
}
|
|
20135
19660
|
|
|
20136
19661
|
// src/backfill/backup.ts
|
|
20137
|
-
var BACKUP_PATH =
|
|
19662
|
+
var BACKUP_PATH = join35(BACKFILL_PATH, "backup");
|
|
20138
19663
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
20139
19664
|
function backupDirectory(base) {
|
|
20140
|
-
return
|
|
19665
|
+
return join35(base, BACKUP_PATH);
|
|
20141
19666
|
}
|
|
20142
19667
|
function isUnder(root, path) {
|
|
20143
19668
|
const step = relative7(resolve11(root), resolve11(path));
|
|
20144
|
-
return step === "" || !step.startsWith(`..${
|
|
19669
|
+
return step === "" || !step.startsWith(`..${sep4}`) && step !== ".." && !isAbsolute6(step);
|
|
20145
19670
|
}
|
|
20146
19671
|
function backupPathFor(path, root, backupDir) {
|
|
20147
19672
|
if (!isUnder(root, path))
|
|
20148
19673
|
return null;
|
|
20149
|
-
return
|
|
19674
|
+
return join35(backupDir, relative7(resolve11(root), resolve11(path)));
|
|
20150
19675
|
}
|
|
20151
19676
|
function backupFile(path, options) {
|
|
20152
19677
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -20155,9 +19680,9 @@ function backupFile(path, options) {
|
|
|
20155
19680
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
20156
19681
|
if (backup === null)
|
|
20157
19682
|
return { path, kind: "outside", backup: null };
|
|
20158
|
-
if (!
|
|
19683
|
+
if (!existsSync11(path))
|
|
20159
19684
|
return { path, kind: "missing", backup: null };
|
|
20160
|
-
if (
|
|
19685
|
+
if (existsSync11(backup))
|
|
20161
19686
|
return { path, kind: "kept", backup };
|
|
20162
19687
|
mkdirSync7(dirname8(backup), { recursive: true });
|
|
20163
19688
|
copyFileSync3(path, backup);
|
|
@@ -20516,12 +20041,12 @@ function readVerdict(cell) {
|
|
|
20516
20041
|
function isEmptyCell(cell) {
|
|
20517
20042
|
return cell === "" || cell === EMPTY_CELL;
|
|
20518
20043
|
}
|
|
20519
|
-
function findTable(
|
|
20520
|
-
for (let index = 0;index + 1 <
|
|
20521
|
-
const header = splitTableRow(
|
|
20044
|
+
function findTable(lines) {
|
|
20045
|
+
for (let index = 0;index + 1 < lines.length; index += 1) {
|
|
20046
|
+
const header = splitTableRow(lines[index] ?? "");
|
|
20522
20047
|
if (header === null)
|
|
20523
20048
|
continue;
|
|
20524
|
-
const delimiter = splitTableRow(
|
|
20049
|
+
const delimiter = splitTableRow(lines[index + 1] ?? "");
|
|
20525
20050
|
if (delimiter !== null && isDelimiterRow(delimiter)) {
|
|
20526
20051
|
return { header, start: index };
|
|
20527
20052
|
}
|
|
@@ -20582,11 +20107,11 @@ function parseDemotionReport(text) {
|
|
|
20582
20107
|
const status = readStatus(document.data);
|
|
20583
20108
|
if (typeof status !== "string")
|
|
20584
20109
|
issues.push(status);
|
|
20585
|
-
const
|
|
20110
|
+
const lines = text.split(`
|
|
20586
20111
|
`).map((line) => line.replace(/\r$/, ""));
|
|
20587
|
-
const bodyStart =
|
|
20112
|
+
const bodyStart = lines.length - document.body.split(`
|
|
20588
20113
|
`).length;
|
|
20589
|
-
const table = findTable(
|
|
20114
|
+
const table = findTable(lines.slice(bodyStart));
|
|
20590
20115
|
if (table === null) {
|
|
20591
20116
|
issues.push(issue("missing-table", "file", "the report holds no row table", 1));
|
|
20592
20117
|
return { issues, report: null };
|
|
@@ -20599,8 +20124,8 @@ function parseDemotionReport(text) {
|
|
|
20599
20124
|
}
|
|
20600
20125
|
const rows = [];
|
|
20601
20126
|
const seen = new Set;
|
|
20602
|
-
for (let index = bodyStart + table.start + 2;index <
|
|
20603
|
-
const cells = splitTableRow(
|
|
20127
|
+
for (let index = bodyStart + table.start + 2;index < lines.length; index += 1) {
|
|
20128
|
+
const cells = splitTableRow(lines[index] ?? "");
|
|
20604
20129
|
if (cells === null)
|
|
20605
20130
|
break;
|
|
20606
20131
|
if (cells.length !== DEMOTION_REPORT_COLUMNS.length) {
|
|
@@ -20789,7 +20314,7 @@ function parseSessionAnswer(stdout) {
|
|
|
20789
20314
|
|
|
20790
20315
|
// src/backfill/propose.ts
|
|
20791
20316
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
|
|
20792
|
-
import { join as
|
|
20317
|
+
import { join as join36, resolve as resolve12 } from "path";
|
|
20793
20318
|
var PROPOSAL_ACTION_KINDS = [
|
|
20794
20319
|
"applied",
|
|
20795
20320
|
"unchanged",
|
|
@@ -20938,7 +20463,7 @@ function writeRow(path, row2, original, options) {
|
|
|
20938
20463
|
};
|
|
20939
20464
|
}
|
|
20940
20465
|
function applyRow(root, row2, options) {
|
|
20941
|
-
const path =
|
|
20466
|
+
const path = join36(root, ...row2.path.split("/"));
|
|
20942
20467
|
if (row2.status === UNANSWERED_ROW) {
|
|
20943
20468
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
20944
20469
|
}
|
|
@@ -20975,7 +20500,7 @@ function applyProposals(files, root, options) {
|
|
|
20975
20500
|
for (const file of files) {
|
|
20976
20501
|
const refusal13 = fileRefusal(file, root);
|
|
20977
20502
|
for (const row2 of file.rows) {
|
|
20978
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
20503
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join36(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
20979
20504
|
actions.push(action);
|
|
20980
20505
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
20981
20506
|
triggers[row2.name] = row2.trigger;
|
|
@@ -20986,17 +20511,17 @@ function applyProposals(files, root, options) {
|
|
|
20986
20511
|
}
|
|
20987
20512
|
|
|
20988
20513
|
// src/demote/select.ts
|
|
20989
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as
|
|
20990
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
20514
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync8 } from "fs";
|
|
20515
|
+
import { basename as basename7, dirname as dirname9, join as join37 } from "path";
|
|
20991
20516
|
var SKILLS_DIRECTORY = "skills";
|
|
20992
20517
|
var CLAUDE_DIRECTORY = ".claude";
|
|
20993
20518
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
20994
|
-
var DEMOTED_PATH =
|
|
20995
|
-
var INSTINCTS_PATH =
|
|
20519
|
+
var DEMOTED_PATH = join37(".rafa", "demoted");
|
|
20520
|
+
var INSTINCTS_PATH = join37(".rafa", "instincts");
|
|
20996
20521
|
var REPORT_FILE = "report.md";
|
|
20997
20522
|
function realOrSelf(path) {
|
|
20998
20523
|
try {
|
|
20999
|
-
return
|
|
20524
|
+
return realpathSync4(path);
|
|
21000
20525
|
} catch {
|
|
21001
20526
|
return path;
|
|
21002
20527
|
}
|
|
@@ -21015,19 +20540,19 @@ function resolveDemotionScope(dir, home) {
|
|
|
21015
20540
|
if (basename7(claude) !== CLAUDE_DIRECTORY)
|
|
21016
20541
|
return null;
|
|
21017
20542
|
const base = dirname9(claude);
|
|
21018
|
-
const demotedDir =
|
|
20543
|
+
const demotedDir = join37(base, DEMOTED_PATH);
|
|
21019
20544
|
return {
|
|
21020
20545
|
scope: samePath(base, home) ? "user" : "project",
|
|
21021
20546
|
dir,
|
|
21022
20547
|
base,
|
|
21023
20548
|
demotedDir,
|
|
21024
|
-
instinctsDir:
|
|
21025
|
-
reportPath:
|
|
20549
|
+
instinctsDir: join37(base, INSTINCTS_PATH),
|
|
20550
|
+
reportPath: join37(demotedDir, REPORT_FILE)
|
|
21026
20551
|
};
|
|
21027
20552
|
}
|
|
21028
20553
|
function entryKind(path) {
|
|
21029
20554
|
try {
|
|
21030
|
-
const stats =
|
|
20555
|
+
const stats = statSync8(path);
|
|
21031
20556
|
if (stats.isFile())
|
|
21032
20557
|
return "file";
|
|
21033
20558
|
if (stats.isDirectory())
|
|
@@ -21049,7 +20574,7 @@ function readSelected(absolute, path, entries) {
|
|
|
21049
20574
|
let mtime;
|
|
21050
20575
|
try {
|
|
21051
20576
|
text = readFileSync25(absolute, "utf8");
|
|
21052
|
-
mtime =
|
|
20577
|
+
mtime = statSync8(absolute).mtime.toISOString();
|
|
21053
20578
|
} catch {
|
|
21054
20579
|
return null;
|
|
21055
20580
|
}
|
|
@@ -21064,14 +20589,14 @@ function selectsLearned(text, scope) {
|
|
|
21064
20589
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
21065
20590
|
}
|
|
21066
20591
|
function selectLearned(scope) {
|
|
21067
|
-
const dir =
|
|
20592
|
+
const dir = join37(scope.dir, LEARNED_DIRECTORY);
|
|
21068
20593
|
if (entryKind(dir) !== "directory")
|
|
21069
20594
|
return [];
|
|
21070
20595
|
const selected = [];
|
|
21071
20596
|
for (const name of visibleNames(dir)) {
|
|
21072
20597
|
if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
|
|
21073
20598
|
continue;
|
|
21074
|
-
const absolute =
|
|
20599
|
+
const absolute = join37(dir, name);
|
|
21075
20600
|
if (entryKind(absolute) !== "file")
|
|
21076
20601
|
continue;
|
|
21077
20602
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
|
|
@@ -21085,10 +20610,10 @@ function selectSkillFiles(scope) {
|
|
|
21085
20610
|
for (const name of visibleNames(scope.dir)) {
|
|
21086
20611
|
if (name === LEARNED_DIRECTORY)
|
|
21087
20612
|
continue;
|
|
21088
|
-
const skillDir =
|
|
20613
|
+
const skillDir = join37(scope.dir, name);
|
|
21089
20614
|
if (entryKind(skillDir) !== "directory")
|
|
21090
20615
|
continue;
|
|
21091
|
-
const absolute =
|
|
20616
|
+
const absolute = join37(skillDir, SKILL_FILE);
|
|
21092
20617
|
if (entryKind(absolute) !== "file")
|
|
21093
20618
|
continue;
|
|
21094
20619
|
const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -21149,7 +20674,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
21149
20674
|
}
|
|
21150
20675
|
function isDirectory3(path) {
|
|
21151
20676
|
try {
|
|
21152
|
-
return
|
|
20677
|
+
return statSync9(path).isDirectory();
|
|
21153
20678
|
} catch {
|
|
21154
20679
|
return false;
|
|
21155
20680
|
}
|
|
@@ -21198,12 +20723,12 @@ function actionLines(actions) {
|
|
|
21198
20723
|
function checkOptions(context, projectRoot2) {
|
|
21199
20724
|
return { projectRoot: projectRoot2, pathDirs: pathDirectories(context.env["PATH"]) };
|
|
21200
20725
|
}
|
|
21201
|
-
function report3(context, result2,
|
|
20726
|
+
function report3(context, result2, lines) {
|
|
21202
20727
|
if (context.outputMode === "json") {
|
|
21203
20728
|
context.output.result(result2);
|
|
21204
20729
|
return;
|
|
21205
20730
|
}
|
|
21206
|
-
for (const line of
|
|
20731
|
+
for (const line of lines)
|
|
21207
20732
|
context.output.info(line);
|
|
21208
20733
|
}
|
|
21209
20734
|
function runPlan(context, scope, backfillDir, projectRoot2) {
|
|
@@ -21273,7 +20798,7 @@ function reviewedProposals(dir) {
|
|
|
21273
20798
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
21274
20799
|
}
|
|
21275
20800
|
function rowPaths(files, root) {
|
|
21276
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
20801
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join38(root, ...row2.path.split("/"))));
|
|
21277
20802
|
}
|
|
21278
20803
|
function runApply(context, scope, backfillDir, projectRoot2) {
|
|
21279
20804
|
const options = checkOptions(context, projectRoot2);
|
|
@@ -21300,7 +20825,7 @@ function runApply(context, scope, backfillDir, projectRoot2) {
|
|
|
21300
20825
|
derivationCounts: countDerivations(derived.actions),
|
|
21301
20826
|
backups: copied
|
|
21302
20827
|
};
|
|
21303
|
-
const
|
|
20828
|
+
const lines = [
|
|
21304
20829
|
`${scope.scope} scope: ${scope.dir}`,
|
|
21305
20830
|
...actionLines(proposalActions),
|
|
21306
20831
|
proposalLine2(proposals.actions.length, proposals.counts),
|
|
@@ -21310,16 +20835,16 @@ function runApply(context, scope, backfillDir, projectRoot2) {
|
|
|
21310
20835
|
];
|
|
21311
20836
|
const refused2 = proposals.counts.refused + derived.counts.refused;
|
|
21312
20837
|
if (refused2 > 0)
|
|
21313
|
-
throw new CommandExit(Math.min(refused2, REFUSED_CAP),
|
|
20838
|
+
throw new CommandExit(Math.min(refused2, REFUSED_CAP), lines.join(`
|
|
21314
20839
|
`));
|
|
21315
|
-
report3(context, result2,
|
|
20840
|
+
report3(context, result2, lines);
|
|
21316
20841
|
}
|
|
21317
20842
|
async function runBackfill(context, seams) {
|
|
21318
20843
|
const mode = readMode(context.flags);
|
|
21319
20844
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
21320
20845
|
const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
21321
20846
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
21322
|
-
const backfillDir =
|
|
20847
|
+
const backfillDir = join38(scope.base, BACKFILL_PATH);
|
|
21323
20848
|
if (mode === "propose") {
|
|
21324
20849
|
await runPropose(context, scope, backfillDir, seams);
|
|
21325
20850
|
return;
|
|
@@ -21433,13 +20958,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
21433
20958
|
var check_default3 = createSkillCheckCommand();
|
|
21434
20959
|
|
|
21435
20960
|
// src/commands/skill/demote.ts
|
|
21436
|
-
import { existsSync as
|
|
20961
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync27, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
21437
20962
|
import { dirname as dirname11, resolve as resolve14 } from "path";
|
|
21438
20963
|
|
|
21439
20964
|
// src/demote/apply.ts
|
|
21440
|
-
import { copyFileSync as copyFileSync4, existsSync as
|
|
20965
|
+
import { copyFileSync as copyFileSync4, existsSync as existsSync12, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync26, renameSync, rmSync as rmSync3, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync10 } from "fs";
|
|
21441
20966
|
import { tmpdir as tmpdir3 } from "os";
|
|
21442
|
-
import { dirname as dirname10, join as
|
|
20967
|
+
import { dirname as dirname10, join as join39 } from "path";
|
|
21443
20968
|
var DEMOTION_ACTION_KINDS = [
|
|
21444
20969
|
"demoted",
|
|
21445
20970
|
"kept",
|
|
@@ -21460,13 +20985,13 @@ function learnedSkillName(path) {
|
|
|
21460
20985
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
21461
20986
|
}
|
|
21462
20987
|
function sourcePath(scope, path) {
|
|
21463
|
-
return
|
|
20988
|
+
return join39(scope.dir, ...path.split("/"));
|
|
21464
20989
|
}
|
|
21465
20990
|
function demotedPath(scope, path) {
|
|
21466
|
-
return
|
|
20991
|
+
return join39(scope.demotedDir, ...path.split("/"));
|
|
21467
20992
|
}
|
|
21468
20993
|
function promotedPath(scope, path) {
|
|
21469
|
-
return
|
|
20994
|
+
return join39(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
21470
20995
|
}
|
|
21471
20996
|
function takenInstinctIds(dir) {
|
|
21472
20997
|
let names;
|
|
@@ -21479,7 +21004,7 @@ function takenInstinctIds(dir) {
|
|
|
21479
21004
|
}
|
|
21480
21005
|
function readText4(path) {
|
|
21481
21006
|
try {
|
|
21482
|
-
return
|
|
21007
|
+
return statSync10(path).isFile() ? readFileSync26(path, "utf8") : null;
|
|
21483
21008
|
} catch {
|
|
21484
21009
|
return null;
|
|
21485
21010
|
}
|
|
@@ -21507,7 +21032,7 @@ function planKept(scope, row2, file) {
|
|
|
21507
21032
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
21508
21033
|
}
|
|
21509
21034
|
const to = promotedPath(scope, row2.path);
|
|
21510
|
-
if (
|
|
21035
|
+
if (existsSync12(to) || existsSync12(dirname10(to))) {
|
|
21511
21036
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
21512
21037
|
}
|
|
21513
21038
|
return {
|
|
@@ -21521,7 +21046,7 @@ function planKept(scope, row2, file) {
|
|
|
21521
21046
|
};
|
|
21522
21047
|
}
|
|
21523
21048
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
21524
|
-
const path =
|
|
21049
|
+
const path = join39(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21525
21050
|
writeFileSync10(path, text, "utf8");
|
|
21526
21051
|
const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
21527
21052
|
return failureLines2(report4.issues);
|
|
@@ -21542,11 +21067,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
21542
21067
|
if (failures.length > 0) {
|
|
21543
21068
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
21544
21069
|
}
|
|
21545
|
-
const destination =
|
|
21070
|
+
const destination = join39(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21546
21071
|
const kept = demotedPath(scope, row2.path);
|
|
21547
|
-
if (
|
|
21072
|
+
if (existsSync12(destination))
|
|
21548
21073
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
21549
|
-
if (
|
|
21074
|
+
if (existsSync12(kept))
|
|
21550
21075
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
21551
21076
|
context.taken.push(id2);
|
|
21552
21077
|
return {
|
|
@@ -21578,7 +21103,7 @@ function planRow(scope, row2, context) {
|
|
|
21578
21103
|
entries: undefined,
|
|
21579
21104
|
absolute,
|
|
21580
21105
|
hash,
|
|
21581
|
-
mtime:
|
|
21106
|
+
mtime: statSync10(absolute).mtime.toISOString()
|
|
21582
21107
|
};
|
|
21583
21108
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
21584
21109
|
}
|
|
@@ -21589,7 +21114,7 @@ function countActions(actions) {
|
|
|
21589
21114
|
return counts;
|
|
21590
21115
|
}
|
|
21591
21116
|
function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
21592
|
-
const temp = mkdtempSync3(
|
|
21117
|
+
const temp = mkdtempSync3(join39(tmpdir3(), "rafa-demote-"));
|
|
21593
21118
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
21594
21119
|
try {
|
|
21595
21120
|
const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
@@ -21680,7 +21205,7 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
21680
21205
|
}
|
|
21681
21206
|
function isDirectory4(path) {
|
|
21682
21207
|
try {
|
|
21683
|
-
return
|
|
21208
|
+
return statSync11(path).isDirectory();
|
|
21684
21209
|
} catch {
|
|
21685
21210
|
return false;
|
|
21686
21211
|
}
|
|
@@ -21708,7 +21233,7 @@ function verdictLine2(report4) {
|
|
|
21708
21233
|
return `${String(report4.rows.length)} file(s): ${parts.join(", ")}`;
|
|
21709
21234
|
}
|
|
21710
21235
|
function previousReport(path, context) {
|
|
21711
|
-
if (!
|
|
21236
|
+
if (!existsSync13(path))
|
|
21712
21237
|
return null;
|
|
21713
21238
|
const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
|
|
21714
21239
|
if (parsed.report !== null)
|
|
@@ -21743,13 +21268,13 @@ function runWrite(context, scope) {
|
|
|
21743
21268
|
context.output.info(line);
|
|
21744
21269
|
}
|
|
21745
21270
|
function reviewedReport(scope) {
|
|
21746
|
-
if (!
|
|
21271
|
+
if (!existsSync13(scope.reportPath)) {
|
|
21747
21272
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
21748
21273
|
}
|
|
21749
21274
|
const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
|
|
21750
21275
|
if (parsed.report === null) {
|
|
21751
|
-
const
|
|
21752
|
-
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...
|
|
21276
|
+
const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
|
|
21277
|
+
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
|
|
21753
21278
|
`));
|
|
21754
21279
|
}
|
|
21755
21280
|
if (parsed.report.status !== REVIEWED_STATUS) {
|
|
@@ -21787,16 +21312,16 @@ function runApply2(context, scope) {
|
|
|
21787
21312
|
actions: plan2.actions,
|
|
21788
21313
|
counts: plan2.counts
|
|
21789
21314
|
};
|
|
21790
|
-
const
|
|
21315
|
+
const lines = renderApply(result2);
|
|
21791
21316
|
if (plan2.counts.refused > 0) {
|
|
21792
|
-
throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP),
|
|
21317
|
+
throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP), lines.join(`
|
|
21793
21318
|
`));
|
|
21794
21319
|
}
|
|
21795
21320
|
if (context.outputMode === "json") {
|
|
21796
21321
|
context.output.result(result2);
|
|
21797
21322
|
return;
|
|
21798
21323
|
}
|
|
21799
|
-
for (const line of
|
|
21324
|
+
for (const line of lines)
|
|
21800
21325
|
context.output.info(line);
|
|
21801
21326
|
}
|
|
21802
21327
|
function runDemote(context, seams) {
|
|
@@ -21990,8 +21515,8 @@ var VERDICT_WORDS = Object.freeze({
|
|
|
21990
21515
|
pending: "checks pending",
|
|
21991
21516
|
none: "no checks"
|
|
21992
21517
|
});
|
|
21993
|
-
function counted3(count, noun,
|
|
21994
|
-
return `${String(count)} ${count === 1 ? noun :
|
|
21518
|
+
function counted3(count, noun, plural3 = `${noun}s`) {
|
|
21519
|
+
return `${String(count)} ${count === 1 ? noun : plural3}`;
|
|
21995
21520
|
}
|
|
21996
21521
|
function info2(text) {
|
|
21997
21522
|
return { level: "info", text };
|
|
@@ -22119,7 +21644,7 @@ function statusData(sections) {
|
|
|
22119
21644
|
}
|
|
22120
21645
|
|
|
22121
21646
|
// src/status/sections.ts
|
|
22122
|
-
import { existsSync as
|
|
21647
|
+
import { existsSync as existsSync14 } from "fs";
|
|
22123
21648
|
|
|
22124
21649
|
// src/status/blocked-count.ts
|
|
22125
21650
|
async function labelsCount(options) {
|
|
@@ -22379,7 +21904,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
22379
21904
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
22380
21905
|
}
|
|
22381
21906
|
function hasBoards(root, listing2) {
|
|
22382
|
-
if (
|
|
21907
|
+
if (existsSync14(positionFilePath(root)))
|
|
22383
21908
|
return true;
|
|
22384
21909
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
22385
21910
|
}
|
|
@@ -22510,6 +22035,10 @@ async function runStatus2(context, seams) {
|
|
|
22510
22035
|
const config = resolveProjectConfig(project, COMMAND_NAME14, (message) => {
|
|
22511
22036
|
context.output.warn(message);
|
|
22512
22037
|
});
|
|
22038
|
+
const warn3 = (message) => {
|
|
22039
|
+
context.output.warn(message);
|
|
22040
|
+
};
|
|
22041
|
+
await (seams.pull ?? pullBeforeRead)({ roots: { root: project.root, home: project.home }, warn: warn3 });
|
|
22513
22042
|
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
22514
22043
|
const sections = await read3({ root: project.root, home: project.home, config });
|
|
22515
22044
|
if (context.outputMode === "json") {
|