@open-tomato/rafa 0.32.0 → 0.33.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 +109 -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 +172 -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 +71 -0
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +111 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +19 -1
- package/dist/cli.js +3019 -1128
- package/dist/{describe-d31jy6dk.js → describe-5hqfwbjc.js} +3 -3
- package/dist/effort/store/index.js +24 -4
- package/dist/{index-22stnzde.js → index-13caphhp.js} +450 -1119
- package/dist/{index-mt6hpsry.js → index-3mzw3kzh.js} +2 -2
- package/dist/{index-9aph6ft9.js → index-dv0ycmp8.js} +127 -5
- package/dist/{index-rs79dq9e.js → index-fzhd3d4y.js} +22 -4
- package/dist/{index-5tftj69y.js → index-k6sd4ym5.js} +2 -2
- package/dist/{index-wvm2s4pr.js → index-kz7e4gtt.js} +593 -80
- package/dist/{index-qgayjcym.js → index-w3vyb08t.js} +6141 -4087
- package/dist/{index-pbeyv281.js → index-zn08ywsv.js} +2 -2
- 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-w3vyb08t.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-zn08ywsv.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-kz7e4gtt.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-dv0ycmp8.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({
|
|
@@ -11296,16 +10608,16 @@ async function fileFailures(run, epic, failures, commit) {
|
|
|
11296
10608
|
warn2(run, `No tracker resolved, so no failed check is filed: ${messageOf(error)}`);
|
|
11297
10609
|
tracker = unresolvedTracker(run.config.trackerDefault, messageOf(error));
|
|
11298
10610
|
}
|
|
11299
|
-
for (const
|
|
11300
|
-
const { criterion } =
|
|
10611
|
+
for (const failure of failures) {
|
|
10612
|
+
const { criterion } = failure.check;
|
|
11301
10613
|
try {
|
|
11302
10614
|
const result2 = await triageReport({
|
|
11303
10615
|
repoRoot: run.root,
|
|
11304
10616
|
trackerPath: closeTriageFile(run.root, epic),
|
|
11305
10617
|
lineNum: 0,
|
|
11306
|
-
dispatch: { sessionId:
|
|
10618
|
+
dispatch: { sessionId: failure.sessionId, planStub: null, taskLine: `rafa epic close ${String(epic)}` },
|
|
11307
10619
|
outcome: "failed",
|
|
11308
|
-
report: failedCheckReport(epic,
|
|
10620
|
+
report: failedCheckReport(epic, failure, commit),
|
|
11309
10621
|
tracker,
|
|
11310
10622
|
privateTracker: run.seams.privateTracker,
|
|
11311
10623
|
secrets,
|
|
@@ -11821,13 +11133,13 @@ function createEpicPromoteCommand(seams = {}) {
|
|
|
11821
11133
|
var promote_default = createEpicPromoteCommand();
|
|
11822
11134
|
|
|
11823
11135
|
// src/commands/init.ts
|
|
11824
|
-
import { existsSync as
|
|
11136
|
+
import { existsSync as existsSync8, readFileSync as readFileSync14 } from "fs";
|
|
11825
11137
|
import { homedir as homedir2 } from "os";
|
|
11826
|
-
import { join as
|
|
11138
|
+
import { join as join26, relative as relative5, sep as sep3 } from "path";
|
|
11827
11139
|
|
|
11828
11140
|
// src/agents/vendorable.ts
|
|
11829
11141
|
import { readdirSync, readFileSync as readFileSync11 } from "fs";
|
|
11830
|
-
import { isAbsolute as isAbsolute3, join as
|
|
11142
|
+
import { isAbsolute as isAbsolute3, join as join23 } from "path";
|
|
11831
11143
|
function planFiles(dir) {
|
|
11832
11144
|
let entries;
|
|
11833
11145
|
try {
|
|
@@ -11849,7 +11161,7 @@ function rafaHolds(roster, name) {
|
|
|
11849
11161
|
return item?.holders.some((holder) => holder.source === "rafa") === true;
|
|
11850
11162
|
}
|
|
11851
11163
|
function vendorableAgents(scan) {
|
|
11852
|
-
const dir = isAbsolute3(scan.planDir) ? scan.planDir :
|
|
11164
|
+
const dir = isAbsolute3(scan.planDir) ? scan.planDir : join23(scan.repoRoot, scan.planDir);
|
|
11853
11165
|
const roots = {
|
|
11854
11166
|
repoRoot: scan.repoRoot,
|
|
11855
11167
|
home: scan.home,
|
|
@@ -11858,7 +11170,7 @@ function vendorableAgents(scan) {
|
|
|
11858
11170
|
const roster = resolveAgentRoster(roots, scan.settings);
|
|
11859
11171
|
const found = [];
|
|
11860
11172
|
for (const file of planFiles(dir)) {
|
|
11861
|
-
const path =
|
|
11173
|
+
const path = join23(dir, file);
|
|
11862
11174
|
const markdown = readText2(path);
|
|
11863
11175
|
if (markdown === null)
|
|
11864
11176
|
continue;
|
|
@@ -11884,13 +11196,13 @@ function vendorableAgentWarnings(agents, root) {
|
|
|
11884
11196
|
|
|
11885
11197
|
// src/project/gitignore.ts
|
|
11886
11198
|
import { createHash } from "crypto";
|
|
11887
|
-
import { existsSync as
|
|
11888
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as
|
|
11199
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
|
|
11200
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join24 } from "path";
|
|
11889
11201
|
var GITIGNORE_FILE = ".gitignore";
|
|
11890
11202
|
var DIGEST_NAME = "tracking.digest";
|
|
11891
11203
|
var PRIVATE_TRIAGE = "triage/private";
|
|
11892
11204
|
var CACHE_DIR = "cache";
|
|
11893
|
-
var TRACKING_DIGEST_FILE =
|
|
11205
|
+
var TRACKING_DIGEST_FILE = join24(SCOPE_DIR, DIGEST_NAME);
|
|
11894
11206
|
var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
|
|
11895
11207
|
var BLOCK_END = "# <<< rafa tracking";
|
|
11896
11208
|
var SUB_PATHS = [
|
|
@@ -11924,8 +11236,8 @@ function lineBreakOf(text) {
|
|
|
11924
11236
|
return /\r?\n/.exec(text)?.[0] ?? `
|
|
11925
11237
|
`;
|
|
11926
11238
|
}
|
|
11927
|
-
function indicesOf(
|
|
11928
|
-
return
|
|
11239
|
+
function indicesOf(lines, marker) {
|
|
11240
|
+
return lines.flatMap((line, index) => line.replace(/\r$/, "") === marker ? [index] : []);
|
|
11929
11241
|
}
|
|
11930
11242
|
function whereLines(indices) {
|
|
11931
11243
|
if (indices.length === 0)
|
|
@@ -11933,9 +11245,9 @@ function whereLines(indices) {
|
|
|
11933
11245
|
const numbers = indices.map((index) => String(index + 1));
|
|
11934
11246
|
return indices.length === 1 ? `line ${numbers.join("")}` : `lines ${numbers.join(", ")}`;
|
|
11935
11247
|
}
|
|
11936
|
-
function blockSpan(
|
|
11937
|
-
const begins = indicesOf(
|
|
11938
|
-
const ends = indicesOf(
|
|
11248
|
+
function blockSpan(lines, file) {
|
|
11249
|
+
const begins = indicesOf(lines, BLOCK_BEGIN);
|
|
11250
|
+
const ends = indicesOf(lines, BLOCK_END);
|
|
11939
11251
|
if (begins.length === 0 && ends.length === 0)
|
|
11940
11252
|
return null;
|
|
11941
11253
|
const [begin] = begins;
|
|
@@ -11952,17 +11264,17 @@ function withTrackingBlock(text, flags, file = GITIGNORE_FILE) {
|
|
|
11952
11264
|
const block = [BLOCK_BEGIN, ...trackingEntry(flags), BLOCK_END].map((line) => `${line}${eol}`).join("");
|
|
11953
11265
|
if (source === "")
|
|
11954
11266
|
return block;
|
|
11955
|
-
const
|
|
11267
|
+
const lines = source.split(`
|
|
11956
11268
|
`);
|
|
11957
|
-
const span = blockSpan(
|
|
11269
|
+
const span = blockSpan(lines, file);
|
|
11958
11270
|
if (span === null) {
|
|
11959
11271
|
const separator = source.endsWith(`
|
|
11960
11272
|
`) ? eol : `${eol}${eol}`;
|
|
11961
11273
|
return `${source}${separator}${block}`;
|
|
11962
11274
|
}
|
|
11963
|
-
const before =
|
|
11275
|
+
const before = lines.slice(0, span.begin).map((line) => `${line}
|
|
11964
11276
|
`).join("");
|
|
11965
|
-
const after =
|
|
11277
|
+
const after = lines.slice(span.end + 1).join(`
|
|
11966
11278
|
`);
|
|
11967
11279
|
return `${before}${block}${after}`;
|
|
11968
11280
|
}
|
|
@@ -11971,21 +11283,21 @@ function requireAbsolute(root) {
|
|
|
11971
11283
|
throw new GitignoreError(`project root is ${describeValue(root)}, expected an absolute path`);
|
|
11972
11284
|
}
|
|
11973
11285
|
}
|
|
11974
|
-
function attempt(file,
|
|
11286
|
+
function attempt(file, failure, act) {
|
|
11975
11287
|
try {
|
|
11976
11288
|
return act();
|
|
11977
11289
|
} catch (error) {
|
|
11978
|
-
throw new GitignoreError(`${file} ${
|
|
11290
|
+
throw new GitignoreError(`${file} ${failure} (${messageOf(error)})`, { cause: error });
|
|
11979
11291
|
}
|
|
11980
11292
|
}
|
|
11981
11293
|
function readIfPresent2(file) {
|
|
11982
|
-
if (!
|
|
11294
|
+
if (!existsSync6(file))
|
|
11983
11295
|
return null;
|
|
11984
11296
|
return attempt(file, "cannot be read", () => readFileSync12(file, "utf8"));
|
|
11985
11297
|
}
|
|
11986
11298
|
function writeTrackingGitignore(root, flags) {
|
|
11987
11299
|
requireAbsolute(root);
|
|
11988
|
-
const file =
|
|
11300
|
+
const file = join24(root, GITIGNORE_FILE);
|
|
11989
11301
|
const text = readIfPresent2(file);
|
|
11990
11302
|
const next = withTrackingBlock(text, flags, file);
|
|
11991
11303
|
if (next === text)
|
|
@@ -12007,7 +11319,7 @@ function trackingDigest(flags) {
|
|
|
12007
11319
|
}
|
|
12008
11320
|
function exposureNotice(root) {
|
|
12009
11321
|
return [
|
|
12010
|
-
`tracking.all is true, so ${
|
|
11322
|
+
`tracking.all is true, so ${join24(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
|
|
12011
11323
|
" - unpatched issues described in specs and plans",
|
|
12012
11324
|
" - effort rows with model names and token counts",
|
|
12013
11325
|
" - instincts that may quote error output",
|
|
@@ -12020,7 +11332,7 @@ function printWarning(message) {
|
|
|
12020
11332
|
}
|
|
12021
11333
|
function noticeTrackingChange(root, flags, print2 = printWarning) {
|
|
12022
11334
|
requireAbsolute(root);
|
|
12023
|
-
const digestFile =
|
|
11335
|
+
const digestFile = join24(root, TRACKING_DIGEST_FILE);
|
|
12024
11336
|
const recorded = `${trackingDigest(flags)}
|
|
12025
11337
|
`;
|
|
12026
11338
|
if (readIfPresent2(digestFile) === recorded) {
|
|
@@ -12119,8 +11431,8 @@ async function promptForRoot(found, prompter, seams) {
|
|
|
12119
11431
|
}
|
|
12120
11432
|
|
|
12121
11433
|
// src/project/scaffold.ts
|
|
12122
|
-
import { existsSync as
|
|
12123
|
-
import { join as
|
|
11434
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
11435
|
+
import { join as join25 } from "path";
|
|
12124
11436
|
|
|
12125
11437
|
// src/commands/init-release.ts
|
|
12126
11438
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -12150,9 +11462,9 @@ var COMMENTED_RELEASE_KEY = /^#\s*release:\s*$/u;
|
|
|
12150
11462
|
var COMMENTED_ENABLED_KEY = /^#\s+enabled:/u;
|
|
12151
11463
|
var ENABLED_KEY = /^\s+enabled\s*:/u;
|
|
12152
11464
|
var SKIPPABLE = /^(\s*#|\s*$)/u;
|
|
12153
|
-
function enabledLineIn(
|
|
12154
|
-
for (let index = open + 1;index <
|
|
12155
|
-
const line =
|
|
11465
|
+
function enabledLineIn(lines, open) {
|
|
11466
|
+
for (let index = open + 1;index < lines.length; index += 1) {
|
|
11467
|
+
const line = lines[index] ?? "";
|
|
12156
11468
|
if (SKIPPABLE.test(line))
|
|
12157
11469
|
continue;
|
|
12158
11470
|
if (ENABLED_KEY.test(line))
|
|
@@ -12162,8 +11474,8 @@ function enabledLineIn(lines2, open) {
|
|
|
12162
11474
|
}
|
|
12163
11475
|
return null;
|
|
12164
11476
|
}
|
|
12165
|
-
function appended(
|
|
12166
|
-
const body =
|
|
11477
|
+
function appended(lines, block) {
|
|
11478
|
+
const body = lines.at(-1) === "" ? lines.slice(0, -1) : [...lines];
|
|
12167
11479
|
return [...body, ...block, ""];
|
|
12168
11480
|
}
|
|
12169
11481
|
function namesRelease(text) {
|
|
@@ -12177,28 +11489,28 @@ function namesRelease(text) {
|
|
|
12177
11489
|
}
|
|
12178
11490
|
function withReleaseEnabled(text, enabled) {
|
|
12179
11491
|
const setting = ` enabled: ${String(enabled)}`;
|
|
12180
|
-
const
|
|
11492
|
+
const lines = text.split(`
|
|
12181
11493
|
`);
|
|
12182
|
-
const open =
|
|
11494
|
+
const open = lines.findIndex((line) => RELEASE_KEY.test(line));
|
|
12183
11495
|
if (open >= 0) {
|
|
12184
|
-
const at = enabledLineIn(
|
|
11496
|
+
const at = enabledLineIn(lines, open);
|
|
12185
11497
|
if (at === null) {
|
|
12186
|
-
return [...
|
|
11498
|
+
return [...lines.slice(0, open + 1), setting, ...lines.slice(open + 1)].join(`
|
|
12187
11499
|
`);
|
|
12188
11500
|
}
|
|
12189
|
-
return [...
|
|
11501
|
+
return [...lines.slice(0, at), setting, ...lines.slice(at + 1)].join(`
|
|
12190
11502
|
`);
|
|
12191
11503
|
}
|
|
12192
|
-
const commented =
|
|
11504
|
+
const commented = lines.findIndex((line) => COMMENTED_RELEASE_KEY.test(line));
|
|
12193
11505
|
if (commented >= 0) {
|
|
12194
|
-
const next =
|
|
11506
|
+
const next = lines[commented + 1] ?? "";
|
|
12195
11507
|
const after = COMMENTED_ENABLED_KEY.test(next) ? commented + 2 : commented + 1;
|
|
12196
|
-
return [...
|
|
11508
|
+
return [...lines.slice(0, commented), "release:", setting, ...lines.slice(after)].join(`
|
|
12197
11509
|
`);
|
|
12198
11510
|
}
|
|
12199
11511
|
if (namesRelease(text))
|
|
12200
11512
|
return null;
|
|
12201
|
-
return appended(
|
|
11513
|
+
return appended(lines, ["release:", setting]).join(`
|
|
12202
11514
|
`);
|
|
12203
11515
|
}
|
|
12204
11516
|
|
|
@@ -12313,11 +11625,18 @@ function renderReleaseStep(result2) {
|
|
|
12313
11625
|
// src/project/scaffold.ts
|
|
12314
11626
|
var PROJECT_TREE = Object.freeze(["specs", "plans", "runs", "effort", "instincts"]);
|
|
12315
11627
|
var USER_TREE = Object.freeze(["instincts"]);
|
|
11628
|
+
function globsLine(patterns) {
|
|
11629
|
+
return `[${patterns.map((pattern) => JSON.stringify(pattern)).join(", ")}]`;
|
|
11630
|
+
}
|
|
12316
11631
|
var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
12317
11632
|
`# store: ${CONFIG_DEFAULTS.store} # sqlite | ndjson`,
|
|
12318
11633
|
"# effort:",
|
|
12319
11634
|
`# busyTimeoutMs: ${String(CONFIG_DEFAULTS.effortBusyTimeoutMs)} # 1..60000 ms; how long a store open waits for another process's lock`,
|
|
12320
11635
|
`# sync: ${CONFIG_DEFAULTS.effortSync} # local | file | git | service | p2p; how the store travels between devices`,
|
|
11636
|
+
"# hub:",
|
|
11637
|
+
"# url: # the hub effort.sync: service reaches, required with it",
|
|
11638
|
+
"# tokenSecret: # the secret store name the hub token is kept under, never the token",
|
|
11639
|
+
`# timeout: ${CONFIG_DEFAULTS.hubTimeout} # 1s..30s; how long one request to the hub may take`,
|
|
12321
11640
|
"# plan:",
|
|
12322
11641
|
`# inject: ${CONFIG_DEFAULTS.inject} # full | stage | task`,
|
|
12323
11642
|
`# dir: ${CONFIG_DEFAULTS.planDir}`,
|
|
@@ -12347,6 +11666,8 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
12347
11666
|
"# loop:",
|
|
12348
11667
|
`# settingSources: ${CONFIG_DEFAULTS.settingSources.join(",")} # a comma-separated subset of user, project, local`,
|
|
12349
11668
|
`# worktreeDir: ${CONFIG_DEFAULTS.loopWorktreeDir} # where a loop adds its worktrees, read from the project root`,
|
|
11669
|
+
"# wrapUp:",
|
|
11670
|
+
`# retries: ${String(CONFIG_DEFAULTS.loopWrapUpRetries)} # 1..3 | false, wrap-up sessions rerun before rafa opens a missing PR itself`,
|
|
12350
11671
|
"# pr:",
|
|
12351
11672
|
"# provider: # gh | none; unset reads it off the origin remote",
|
|
12352
11673
|
`# mergeMethod: ${CONFIG_DEFAULTS.prMergeMethod} # squash | merge | rebase`,
|
|
@@ -12386,7 +11707,10 @@ var CONFIG_SETTINGS_LINES = Object.freeze([
|
|
|
12386
11707
|
...DEFAULT_ROUTES.map(([shape, agent]) => `# ${shape}: ${agent}`),
|
|
12387
11708
|
"# task:",
|
|
12388
11709
|
`# 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
|
|
11710
|
+
`# lessons: ${CONFIG_DEFAULTS.taskLessons} # on | off, whether blessed lessons join a task's prompt`,
|
|
11711
|
+
"# tests:",
|
|
11712
|
+
`# fullSuiteTriggers: ${globsLine(CONFIG_DEFAULTS.testsFullSuiteTriggers)} # a task whose diff matches one runs the full suite`,
|
|
11713
|
+
`# integration: ${globsLine(CONFIG_DEFAULTS.testsIntegration)} # test files every stage step runs`
|
|
12390
11714
|
]);
|
|
12391
11715
|
var VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
|
|
12392
11716
|
var PROJECT_HEADER = Object.freeze([
|
|
@@ -12424,18 +11748,18 @@ class ScaffoldError extends Error {
|
|
|
12424
11748
|
}
|
|
12425
11749
|
function layoutOf2(base, tree) {
|
|
12426
11750
|
const scope = scopeAt(base);
|
|
12427
|
-
return { dirs: [scope.dir, ...tree.map((name) =>
|
|
11751
|
+
return { dirs: [scope.dir, ...tree.map((name) => join25(scope.dir, name))], configFile: configFilePath(base) };
|
|
12428
11752
|
}
|
|
12429
11753
|
function occupied(path) {
|
|
12430
11754
|
return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
12431
11755
|
}
|
|
12432
11756
|
function isDirectory2(path) {
|
|
12433
|
-
return
|
|
11757
|
+
return statSync6(path, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
12434
11758
|
}
|
|
12435
11759
|
function directoryConflict(dir) {
|
|
12436
11760
|
if (!occupied(dir) || isDirectory2(dir))
|
|
12437
11761
|
return null;
|
|
12438
|
-
return
|
|
11762
|
+
return existsSync7(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
|
|
12439
11763
|
}
|
|
12440
11764
|
function layoutConflicts(layout) {
|
|
12441
11765
|
const [scopeDir, ...tree] = layout.dirs;
|
|
@@ -12443,21 +11767,21 @@ function layoutConflicts(layout) {
|
|
|
12443
11767
|
if (opening !== null)
|
|
12444
11768
|
return [opening];
|
|
12445
11769
|
const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
|
|
12446
|
-
const file = occupied(layout.configFile) && !
|
|
11770
|
+
const file = occupied(layout.configFile) && !existsSync7(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
|
|
12447
11771
|
return [...dirs, ...file];
|
|
12448
11772
|
}
|
|
12449
11773
|
function scaffoldConflicts(root, home) {
|
|
12450
11774
|
return [...layoutConflicts(layoutOf2(root, PROJECT_TREE)), ...layoutConflicts(layoutOf2(home, USER_TREE))];
|
|
12451
11775
|
}
|
|
12452
|
-
function attempt2(path,
|
|
11776
|
+
function attempt2(path, failure, act) {
|
|
12453
11777
|
try {
|
|
12454
11778
|
act();
|
|
12455
11779
|
} catch (error) {
|
|
12456
|
-
throw new ScaffoldError(`${path} ${
|
|
11780
|
+
throw new ScaffoldError(`${path} ${failure} (${messageOf(error)})`, { cause: error });
|
|
12457
11781
|
}
|
|
12458
11782
|
}
|
|
12459
11783
|
function ensureDirectory(path) {
|
|
12460
|
-
if (
|
|
11784
|
+
if (existsSync7(path))
|
|
12461
11785
|
return { path, kind: "directory", change: "unchanged" };
|
|
12462
11786
|
attempt2(path, "cannot be created", () => {
|
|
12463
11787
|
mkdirSync4(path, { recursive: true });
|
|
@@ -12465,7 +11789,7 @@ function ensureDirectory(path) {
|
|
|
12465
11789
|
return { path, kind: "directory", change: "created" };
|
|
12466
11790
|
}
|
|
12467
11791
|
function ensureFile(path, text) {
|
|
12468
|
-
if (
|
|
11792
|
+
if (existsSync7(path))
|
|
12469
11793
|
return { path, kind: "file", change: "unchanged" };
|
|
12470
11794
|
attempt2(path, "cannot be written", () => {
|
|
12471
11795
|
writeFileSync5(path, text, { encoding: "utf8", flag: "wx" });
|
|
@@ -12504,8 +11828,8 @@ var SOURCE_PHRASES = {
|
|
|
12504
11828
|
"root-flag": "named by --root",
|
|
12505
11829
|
typed: "typed at the prompt"
|
|
12506
11830
|
};
|
|
12507
|
-
function refusal9(
|
|
12508
|
-
return new CommandExit(1, [...
|
|
11831
|
+
function refusal9(lines) {
|
|
11832
|
+
return new CommandExit(1, [...lines, NOTHING_WRITTEN2].join(`
|
|
12509
11833
|
`));
|
|
12510
11834
|
}
|
|
12511
11835
|
function expectNoArgument3(args) {
|
|
@@ -12601,9 +11925,9 @@ function resolvedConfig2(root, home, warn3) {
|
|
|
12601
11925
|
}
|
|
12602
11926
|
}
|
|
12603
11927
|
function checkGitignore(root, flags) {
|
|
12604
|
-
const file =
|
|
11928
|
+
const file = join26(root, GITIGNORE_FILE);
|
|
12605
11929
|
try {
|
|
12606
|
-
const text =
|
|
11930
|
+
const text = existsSync8(file) ? readFileSync14(file, "utf8") : null;
|
|
12607
11931
|
withTrackingBlock(text, flags, file);
|
|
12608
11932
|
} catch (error) {
|
|
12609
11933
|
const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
|
|
@@ -12635,8 +11959,8 @@ function initialise(root, start2, home, context, entry) {
|
|
|
12635
11959
|
const resolved = resolvedConfig2(root.path, home, warn3);
|
|
12636
11960
|
const config = resolved.config;
|
|
12637
11961
|
checkGitignore(root.path, config);
|
|
12638
|
-
const configExisted =
|
|
12639
|
-
const digestExisted =
|
|
11962
|
+
const configExisted = existsSync8(configFilePath(root.path));
|
|
11963
|
+
const digestExisted = existsSync8(join26(root.path, TRACKING_DIGEST_FILE));
|
|
12640
11964
|
const project = writeProjectScope(root.path);
|
|
12641
11965
|
const tracking = applyTracking(root.path, config, warn3);
|
|
12642
11966
|
const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
|
|
@@ -12689,7 +12013,7 @@ async function releaseStep(scopes, wanted, yes, seams) {
|
|
|
12689
12013
|
});
|
|
12690
12014
|
}
|
|
12691
12015
|
function shownPath2(write, root) {
|
|
12692
|
-
const shown = write.path.startsWith(`${root}${
|
|
12016
|
+
const shown = write.path.startsWith(`${root}${sep3}`) ? relative5(root, write.path) : write.path;
|
|
12693
12017
|
return write.kind === "directory" ? `${shown}/` : shown;
|
|
12694
12018
|
}
|
|
12695
12019
|
function renderInit(result2) {
|
|
@@ -12921,15 +12245,15 @@ async function runCheckCommand(context, spec, seams) {
|
|
|
12921
12245
|
fix
|
|
12922
12246
|
}, spec.usage);
|
|
12923
12247
|
const result2 = checkCommandResult(report2, projectRoot2);
|
|
12924
|
-
const
|
|
12248
|
+
const lines = renderCheck(result2);
|
|
12925
12249
|
if (report2.exitCode > 0)
|
|
12926
|
-
throw new CommandExit(report2.exitCode,
|
|
12250
|
+
throw new CommandExit(report2.exitCode, lines.join(`
|
|
12927
12251
|
`));
|
|
12928
12252
|
if (context.outputMode === "json") {
|
|
12929
12253
|
context.output.result(result2);
|
|
12930
12254
|
return;
|
|
12931
12255
|
}
|
|
12932
|
-
for (const line of
|
|
12256
|
+
for (const line of lines)
|
|
12933
12257
|
context.output.info(line);
|
|
12934
12258
|
}
|
|
12935
12259
|
function checkCommandRun(spec, seams) {
|
|
@@ -13290,12 +12614,12 @@ function everyRecord(context) {
|
|
|
13290
12614
|
clean: records.filter((entry) => entry.instinct !== null).length
|
|
13291
12615
|
};
|
|
13292
12616
|
}
|
|
13293
|
-
function answer(context, result2,
|
|
12617
|
+
function answer(context, result2, lines) {
|
|
13294
12618
|
if (context.outputMode === "json") {
|
|
13295
12619
|
context.output.result(result2);
|
|
13296
12620
|
return;
|
|
13297
12621
|
}
|
|
13298
|
-
for (const line of
|
|
12622
|
+
for (const line of lines())
|
|
13299
12623
|
context.output.info(line);
|
|
13300
12624
|
}
|
|
13301
12625
|
async function runList4(context, seams) {
|
|
@@ -13497,8 +12821,8 @@ function createInstinctShowCommand() {
|
|
|
13497
12821
|
var show_default3 = createInstinctShowCommand();
|
|
13498
12822
|
|
|
13499
12823
|
// src/commands/issue/check.ts
|
|
13500
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as
|
|
13501
|
-
import { join as
|
|
12824
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
|
|
12825
|
+
import { join as join27, resolve as resolve9 } from "path";
|
|
13502
12826
|
var CHECK_USAGE = "rafa issue check <n> [--stamp]";
|
|
13503
12827
|
var STAMP_FLAG = "stamp";
|
|
13504
12828
|
var ISSUE_NUMBER7 = /^[1-9]\d*$/u;
|
|
@@ -13530,7 +12854,7 @@ function copyNames(dir, issue) {
|
|
|
13530
12854
|
}
|
|
13531
12855
|
const prefix = `${boardId(issue)}-`;
|
|
13532
12856
|
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) =>
|
|
12857
|
+
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
12858
|
}
|
|
13535
12859
|
function findSavedCopy(root, specsDir, issue) {
|
|
13536
12860
|
const names = copyNames(resolve9(root, specsDir), issue);
|
|
@@ -13540,11 +12864,11 @@ function findSavedCopy(root, specsDir, issue) {
|
|
|
13540
12864
|
if (names.length > 1) {
|
|
13541
12865
|
throw new CommandExit(1, [
|
|
13542
12866
|
`\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
|
|
13543
|
-
...names.map((name) => ` ${
|
|
12867
|
+
...names.map((name) => ` ${join27(specsDir, name)}`)
|
|
13544
12868
|
].join(`
|
|
13545
12869
|
`));
|
|
13546
12870
|
}
|
|
13547
|
-
const path =
|
|
12871
|
+
const path = join27(specsDir, only);
|
|
13548
12872
|
return { path, absolute: resolve9(root, path) };
|
|
13549
12873
|
}
|
|
13550
12874
|
function liveText(live) {
|
|
@@ -13562,7 +12886,7 @@ function checkedRef(row2, stamp) {
|
|
|
13562
12886
|
unblock: row2.unblock
|
|
13563
12887
|
});
|
|
13564
12888
|
}
|
|
13565
|
-
function
|
|
12889
|
+
function rowSuffix(ref8) {
|
|
13566
12890
|
const noun = ref8.changedHeadings.length === 1 ? "heading" : "headings";
|
|
13567
12891
|
const headings = ref8.changedHeadings.length === 0 ? "" : `: ${noun} ${ref8.changedHeadings.map((heading) => `"${heading}"`).join(", ")} changed`;
|
|
13568
12892
|
const after = ref8.unblock === null ? "" : ` \u2014 ${ref8.unblock}`;
|
|
@@ -13570,7 +12894,7 @@ function rowSuffix2(ref8) {
|
|
|
13570
12894
|
return `${headings} (line ${String(ref8.line)})${after}${unknown}`;
|
|
13571
12895
|
}
|
|
13572
12896
|
function checkedRefLine(ref8) {
|
|
13573
|
-
return `${ref8.state} ${ref8.kind} ${ref8.text}${
|
|
12897
|
+
return `${ref8.state} ${ref8.kind} ${ref8.text}${rowSuffix(ref8)}`;
|
|
13574
12898
|
}
|
|
13575
12899
|
function countLine(references) {
|
|
13576
12900
|
const noun = references.length === 1 ? "reference" : "references";
|
|
@@ -13961,7 +13285,7 @@ function renderSessionList(root, list) {
|
|
|
13961
13285
|
return [
|
|
13962
13286
|
"Running sessions:",
|
|
13963
13287
|
...list.sessions.map(({ session, tasks }) => {
|
|
13964
|
-
const counts = tasks === null ?
|
|
13288
|
+
const counts = tasks === null ? `no plan or tracker to count ${phaseNote(session)}` : phasedCounts(tasks, session);
|
|
13965
13289
|
return ` ${sessionLine(session)}; ${counts}; ${checkoutColumn(root, session)}`;
|
|
13966
13290
|
})
|
|
13967
13291
|
];
|
|
@@ -13973,6 +13297,7 @@ async function runList5(context, seams) {
|
|
|
13973
13297
|
const checklist = readSessionChecklist(root, session);
|
|
13974
13298
|
return {
|
|
13975
13299
|
session,
|
|
13300
|
+
phase: sessionPhase(session),
|
|
13976
13301
|
tasks: checklist === null ? null : countTasks(checklist.tasks)
|
|
13977
13302
|
};
|
|
13978
13303
|
});
|
|
@@ -13991,7 +13316,7 @@ function createLoopListCommand(seams = {}) {
|
|
|
13991
13316
|
subject: "loop",
|
|
13992
13317
|
action: "list",
|
|
13993
13318
|
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.",
|
|
13319
|
+
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
13320
|
args: [],
|
|
13996
13321
|
flags: [],
|
|
13997
13322
|
examples: [
|
|
@@ -14205,10 +13530,10 @@ var wrapped = wrapPhaseZeroCommand({
|
|
|
14205
13530
|
}
|
|
14206
13531
|
],
|
|
14207
13532
|
aliases: ["start"],
|
|
14208
|
-
outputs: ["text", "json"],
|
|
13533
|
+
outputs: ["text", "json", "events"],
|
|
14209
13534
|
spends: {
|
|
14210
13535
|
when: "always",
|
|
14211
|
-
what: "one session per task, one for the wrap-up, and repair sessions while CI is red"
|
|
13536
|
+
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
13537
|
}
|
|
14213
13538
|
}, start);
|
|
14214
13539
|
var loopStart = Object.freeze({
|
|
@@ -14250,7 +13575,7 @@ function renderStatus(status) {
|
|
|
14250
13575
|
const pending = session.state === "paused" && task !== null ? " (the run holds once it ends)" : "";
|
|
14251
13576
|
return [
|
|
14252
13577
|
`Session ${sessionLine(session)}`,
|
|
14253
|
-
tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count` : ` Tasks: ${
|
|
13578
|
+
tasks === null ? ` Tasks: neither \`${session.plan}\` nor its tracker is there to count ${phaseNote(session)}` : ` Tasks: ${phasedCounts(tasks, session)}`,
|
|
14254
13579
|
...blocked.flatMap(blockedLines),
|
|
14255
13580
|
...task === null ? [] : [` Task: line ${task.line}, ${task.text}${pending}`],
|
|
14256
13581
|
...eta === null ? [] : [` ${etaLine(eta)}`]
|
|
@@ -14271,6 +13596,7 @@ async function runStatus(context, seams) {
|
|
|
14271
13596
|
}
|
|
14272
13597
|
const status = {
|
|
14273
13598
|
session: record,
|
|
13599
|
+
phase: sessionPhase(record),
|
|
14274
13600
|
checklist: checklist?.file ?? null,
|
|
14275
13601
|
tasks,
|
|
14276
13602
|
blocked: blockedTasks(checklist),
|
|
@@ -14290,13 +13616,13 @@ function createLoopStatusCommand(seams = {}) {
|
|
|
14290
13616
|
subject: "loop",
|
|
14291
13617
|
action: "status",
|
|
14292
13618
|
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,
|
|
13619
|
+
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
13620
|
args: [],
|
|
14295
13621
|
flags: [sessionIdFlag("to show")],
|
|
14296
13622
|
examples: [
|
|
14297
13623
|
{
|
|
14298
13624
|
cmd: "rafa loop status",
|
|
14299
|
-
note: "Shows the session on the branch checked out here: its state, tasks done over total and ETA."
|
|
13625
|
+
note: "Shows the session on the branch checked out here: its state, tasks done over total, phase and ETA."
|
|
14300
13626
|
},
|
|
14301
13627
|
{
|
|
14302
13628
|
cmd: "rafa loop status --session-id=9185b41c-65f7-4dd6-a0c1-6494c4028f0f --output=json",
|
|
@@ -14686,8 +14012,8 @@ function dryRunOf(flag, ceiling, hasTerminal) {
|
|
|
14686
14012
|
|
|
14687
14013
|
// src/next/sync.ts
|
|
14688
14014
|
var INDENT2 = " ";
|
|
14689
|
-
function refuse6(
|
|
14690
|
-
return Object.freeze({ kind: "refused", message:
|
|
14015
|
+
function refuse6(lines) {
|
|
14016
|
+
return Object.freeze({ kind: "refused", message: lines.join(`
|
|
14691
14017
|
`) });
|
|
14692
14018
|
}
|
|
14693
14019
|
function quotedLines(said) {
|
|
@@ -14868,6 +14194,7 @@ async function runNext(context, seams) {
|
|
|
14868
14194
|
...roadmap ? { roadmap } : {},
|
|
14869
14195
|
...relations === undefined ? {} : { relations }
|
|
14870
14196
|
});
|
|
14197
|
+
await (seams.pull ?? pullBeforeRead)({ roots: sources.roots, warn: (line) => context.output.warn(line) });
|
|
14871
14198
|
const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
|
|
14872
14199
|
const prompter = lazyPrompter(openPrompter);
|
|
14873
14200
|
const dry = watchDryEpic();
|
|
@@ -15187,7 +14514,7 @@ var risk_default = createPlanRiskCommand();
|
|
|
15187
14514
|
|
|
15188
14515
|
// src/commands/plan/show.ts
|
|
15189
14516
|
import { readFileSync as readFileSync17 } from "fs";
|
|
15190
|
-
import { join as
|
|
14517
|
+
import { join as join28 } from "path";
|
|
15191
14518
|
var USAGE33 = "rafa plan show <stub> [--tracker]";
|
|
15192
14519
|
var COMMAND7 = "rafa plan show";
|
|
15193
14520
|
function readTrackerFlag(value) {
|
|
@@ -15205,9 +14532,9 @@ function showPlan(plans, stub, tracker) {
|
|
|
15205
14532
|
throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
|
|
15206
14533
|
}
|
|
15207
14534
|
const name = planFileName(stub, tracker);
|
|
15208
|
-
const file =
|
|
14535
|
+
const file = join28(plans.path, name);
|
|
15209
14536
|
if (!isFile(file))
|
|
15210
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
14537
|
+
throw new CommandExit(1, missingText(stub, tracker, join28(plans.label, name)));
|
|
15211
14538
|
const model = parsePlan(readFileSync17(file, "utf8"));
|
|
15212
14539
|
const { header } = model;
|
|
15213
14540
|
return {
|
|
@@ -15231,24 +14558,24 @@ function taskLine(task) {
|
|
|
15231
14558
|
return ` ${checkbox(task.status)} ${task.text}`;
|
|
15232
14559
|
}
|
|
15233
14560
|
function renderShownPlan(shown, dirLabel) {
|
|
15234
|
-
const relative7 =
|
|
14561
|
+
const relative7 = join28(dirLabel, planFileName(shown.stub, shown.tracker));
|
|
15235
14562
|
const fileLabel = shown.tracker ? "Tracker" : "File";
|
|
15236
|
-
const
|
|
14563
|
+
const lines = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
|
|
15237
14564
|
if (shown.header.issue !== null)
|
|
15238
|
-
|
|
14565
|
+
lines.push(`Issue: ${shown.header.issue}`);
|
|
15239
14566
|
if (shown.header.spec !== null)
|
|
15240
|
-
|
|
15241
|
-
|
|
14567
|
+
lines.push(`Spec: ${shown.header.spec}`);
|
|
14568
|
+
lines.push(`Tasks: ${formatCounts(shown.counts)}`);
|
|
15242
14569
|
const unstaged = shown.tasks.filter((task) => task.stage === null);
|
|
15243
14570
|
if (unstaged.length > 0)
|
|
15244
|
-
|
|
14571
|
+
lines.push("", ...unstaged.map(taskLine));
|
|
15245
14572
|
for (const [index, stage] of shown.stages.entries()) {
|
|
15246
|
-
|
|
14573
|
+
lines.push("", `Stage: ${stage.name}`, ...shown.tasks.filter((task) => task.stage === index).map(taskLine));
|
|
15247
14574
|
}
|
|
15248
14575
|
if (shown.issues.length > 0) {
|
|
15249
|
-
|
|
14576
|
+
lines.push("", "Issues:", ...shown.issues.map((issue) => ` ${issueLine(relative7, issue)}`));
|
|
15250
14577
|
}
|
|
15251
|
-
return
|
|
14578
|
+
return lines;
|
|
15252
14579
|
}
|
|
15253
14580
|
var planShowCommand = {
|
|
15254
14581
|
name: "plan show",
|
|
@@ -15820,19 +15147,19 @@ var YES_ANSWERS2 = ["y", "yes"];
|
|
|
15820
15147
|
function answerOf(reading) {
|
|
15821
15148
|
return reading.ok ? reading.verdict.answer : "unread";
|
|
15822
15149
|
}
|
|
15823
|
-
function guardRefusal(head,
|
|
15824
|
-
return new CommandExit(1, [`\u274C ${head}`, ...
|
|
15150
|
+
function guardRefusal(head, lines) {
|
|
15151
|
+
return new CommandExit(1, [`\u274C ${head}`, ...lines.map((line) => ` ${line}`)].join(`
|
|
15825
15152
|
`));
|
|
15826
15153
|
}
|
|
15827
15154
|
function refusalHead(number, answer2) {
|
|
15828
15155
|
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
15156
|
}
|
|
15830
|
-
async function askGuard(step, answer2,
|
|
15157
|
+
async function askGuard(step, answer2, lines) {
|
|
15831
15158
|
const { number } = step.detail;
|
|
15832
15159
|
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.",
|
|
15160
|
+
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
15161
|
}
|
|
15835
|
-
for (const line of
|
|
15162
|
+
for (const line of lines)
|
|
15836
15163
|
step.warn(line);
|
|
15837
15164
|
const prompter = step.openPrompter();
|
|
15838
15165
|
try {
|
|
@@ -15842,11 +15169,11 @@ async function askGuard(step, answer2, lines2) {
|
|
|
15842
15169
|
prompter.close();
|
|
15843
15170
|
}
|
|
15844
15171
|
}
|
|
15845
|
-
function printLines(step, reaction,
|
|
15172
|
+
function printLines(step, reaction, lines) {
|
|
15846
15173
|
if (reaction === "silent")
|
|
15847
15174
|
return;
|
|
15848
15175
|
const write = reaction === "print" ? step.info : step.warn;
|
|
15849
|
-
for (const line of
|
|
15176
|
+
for (const line of lines)
|
|
15850
15177
|
write(line);
|
|
15851
15178
|
if (reaction === "accept") {
|
|
15852
15179
|
step.warn(`#${step.detail.number} merges anyway: dangerous.acceptVersionCollision is true.`);
|
|
@@ -15868,13 +15195,13 @@ async function guardBeforeMerge(step) {
|
|
|
15868
15195
|
});
|
|
15869
15196
|
const answer2 = answerOf(reading);
|
|
15870
15197
|
const reaction = guardReaction(reading, settings);
|
|
15871
|
-
const
|
|
15872
|
-
const report3 = { answer: answer2, reaction, lines
|
|
15198
|
+
const lines = guardLines(reading, settings.releaseVersionFile);
|
|
15199
|
+
const report3 = { answer: answer2, reaction, lines };
|
|
15873
15200
|
if (reaction === "refuse")
|
|
15874
|
-
throw guardRefusal(refusalHead(step.detail.number, answer2),
|
|
15201
|
+
throw guardRefusal(refusalHead(step.detail.number, answer2), lines);
|
|
15875
15202
|
if (reaction === "ask")
|
|
15876
|
-
return { report: report3, go: await askGuard(step, answer2,
|
|
15877
|
-
printLines(step, reaction,
|
|
15203
|
+
return { report: report3, go: await askGuard(step, answer2, lines) };
|
|
15204
|
+
printLines(step, reaction, lines);
|
|
15878
15205
|
return { report: report3, go: true };
|
|
15879
15206
|
}
|
|
15880
15207
|
|
|
@@ -15984,8 +15311,8 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
|
|
|
15984
15311
|
var USAGE36 = PR_USAGE.merge;
|
|
15985
15312
|
var YES_ANSWERS4 = ["y", "yes"];
|
|
15986
15313
|
var DEFAULT_MERGE_SEAMS = Object.freeze({});
|
|
15987
|
-
function refusal11(
|
|
15988
|
-
return new CommandExit(1,
|
|
15314
|
+
function refusal11(lines) {
|
|
15315
|
+
return new CommandExit(1, lines.join(`
|
|
15989
15316
|
`));
|
|
15990
15317
|
}
|
|
15991
15318
|
function readMethodFlag(flags, usage2) {
|
|
@@ -16359,9 +15686,9 @@ function parseFailedLog(text) {
|
|
|
16359
15686
|
end -= 1;
|
|
16360
15687
|
return raw.slice(0, end).map((line) => parseFailedLogLine(line));
|
|
16361
15688
|
}
|
|
16362
|
-
function readFailedStep(
|
|
15689
|
+
function readFailedStep(lines) {
|
|
16363
15690
|
let group;
|
|
16364
|
-
for (const line of
|
|
15691
|
+
for (const line of lines) {
|
|
16365
15692
|
const head = groupStep(line.text);
|
|
16366
15693
|
if (head !== undefined)
|
|
16367
15694
|
group = head;
|
|
@@ -16372,14 +15699,14 @@ function readFailedStep(lines2) {
|
|
|
16372
15699
|
if (group !== undefined)
|
|
16373
15700
|
return { name: group, source: "group-marker" };
|
|
16374
15701
|
}
|
|
16375
|
-
const column =
|
|
15702
|
+
const column = lines.find((line) => line.step !== undefined)?.step;
|
|
16376
15703
|
if (column !== undefined)
|
|
16377
15704
|
return { name: column, source: "step-column" };
|
|
16378
15705
|
return group === undefined ? undefined : { name: group, source: "group-marker" };
|
|
16379
15706
|
}
|
|
16380
|
-
function readJobs(
|
|
15707
|
+
function readJobs(lines) {
|
|
16381
15708
|
const jobs = [];
|
|
16382
|
-
for (const line of
|
|
15709
|
+
for (const line of lines) {
|
|
16383
15710
|
if (line.job !== undefined && !jobs.includes(line.job))
|
|
16384
15711
|
jobs.push(line.job);
|
|
16385
15712
|
}
|
|
@@ -16395,14 +15722,14 @@ function readCap(value) {
|
|
|
16395
15722
|
}
|
|
16396
15723
|
function readFailedLog(text, options = {}) {
|
|
16397
15724
|
const cap = readCap(options.maxLines);
|
|
16398
|
-
const
|
|
16399
|
-
const kept =
|
|
15725
|
+
const lines = parseFailedLog(text);
|
|
15726
|
+
const kept = lines.slice(Math.max(0, lines.length - cap));
|
|
16400
15727
|
return {
|
|
16401
|
-
jobs: readJobs(
|
|
16402
|
-
step: readFailedStep(
|
|
15728
|
+
jobs: readJobs(lines),
|
|
15729
|
+
step: readFailedStep(lines),
|
|
16403
15730
|
lines: kept.map((line) => line.text),
|
|
16404
|
-
omitted:
|
|
16405
|
-
total:
|
|
15731
|
+
omitted: lines.length - kept.length,
|
|
15732
|
+
total: lines.length
|
|
16406
15733
|
};
|
|
16407
15734
|
}
|
|
16408
15735
|
|
|
@@ -17277,7 +16604,7 @@ var TRIAGE_FRESH_WINDOW_MS = TRIAGE_FRESH_HOURS * HOUR_MS2;
|
|
|
17277
16604
|
var MAX_TRIAGE_CANDIDATES = 3;
|
|
17278
16605
|
var REFUSE_EXIT_CODE = 2;
|
|
17279
16606
|
var DAYS_FROM_HOURS = 48;
|
|
17280
|
-
var
|
|
16607
|
+
var REFUSAL_PREFIX = "rafa pr triage --resolve refuses";
|
|
17281
16608
|
var INDENT6 = " ";
|
|
17282
16609
|
function ageMs(updatedAt, now) {
|
|
17283
16610
|
const then = Date.parse(updatedAt);
|
|
@@ -17290,7 +16617,7 @@ function isFreshCandidate(updatedAt, now) {
|
|
|
17290
16617
|
const age = ageMs(updatedAt, now);
|
|
17291
16618
|
return age !== null && age <= TRIAGE_FRESH_WINDOW_MS;
|
|
17292
16619
|
}
|
|
17293
|
-
function
|
|
16620
|
+
function plural2(count, singular, many) {
|
|
17294
16621
|
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
17295
16622
|
}
|
|
17296
16623
|
function describeAge(updatedAt, now) {
|
|
@@ -17303,8 +16630,8 @@ function describeAge(updatedAt, now) {
|
|
|
17303
16630
|
if (hours < 1)
|
|
17304
16631
|
return "less than an hour ago";
|
|
17305
16632
|
if (hours < DAYS_FROM_HOURS)
|
|
17306
|
-
return `${
|
|
17307
|
-
return `${
|
|
16633
|
+
return `${plural2(hours, "hour", "hours")} ago`;
|
|
16634
|
+
return `${plural2(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
17308
16635
|
}
|
|
17309
16636
|
function triageCommand(number, resolve11 = false) {
|
|
17310
16637
|
return resolve11 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
@@ -17343,24 +16670,24 @@ function plain(decision, assess, headline) {
|
|
|
17343
16670
|
});
|
|
17344
16671
|
}
|
|
17345
16672
|
function listing(decision, assess, rest, headline, exitCode = 0) {
|
|
17346
|
-
const
|
|
16673
|
+
const lines = Object.freeze(rest.map((one) => skippedLine(one)));
|
|
17347
16674
|
return Object.freeze({
|
|
17348
16675
|
decision,
|
|
17349
16676
|
assess,
|
|
17350
16677
|
skipped: Object.freeze([...rest]),
|
|
17351
16678
|
exitCode,
|
|
17352
16679
|
headline,
|
|
17353
|
-
lines
|
|
17354
|
-
message: [headline, ...
|
|
16680
|
+
lines,
|
|
16681
|
+
message: [headline, ...lines].join(`
|
|
17355
16682
|
`)
|
|
17356
16683
|
});
|
|
17357
16684
|
}
|
|
17358
16685
|
function refusal12(rest, count) {
|
|
17359
|
-
const headline = `${
|
|
16686
|
+
const headline = `${REFUSAL_PREFIX}: ${plural2(count, "red pull request", "red pull requests")}, ` + "and --resolve acts on one. Run one of:";
|
|
17360
16687
|
return listing("refuse", Object.freeze([]), rest, headline, REFUSE_EXIT_CODE);
|
|
17361
16688
|
}
|
|
17362
16689
|
function someReading(fresh, stale, count) {
|
|
17363
|
-
const older = stale.length === 0 ? "" : `; ${
|
|
16690
|
+
const older = stale.length === 0 ? "" : `; ${plural2(stale.length, "older one is", "older ones are")} skipped`;
|
|
17364
16691
|
const headline = `${count} red: assessing ${joinNumbers(fresh)}${older}`;
|
|
17365
16692
|
return listing("some", fresh, stale, headline);
|
|
17366
16693
|
}
|
|
@@ -17376,9 +16703,9 @@ function selectTriagePullRequests(input) {
|
|
|
17376
16703
|
}
|
|
17377
16704
|
const candidates = byNumber2(input.candidates);
|
|
17378
16705
|
const count = candidates.length;
|
|
17379
|
-
const
|
|
16706
|
+
const lines = candidates.map((pull2) => skipped(pull2, now, resolve11));
|
|
17380
16707
|
if (resolve11 && count > 1)
|
|
17381
|
-
return refusal12(
|
|
16708
|
+
return refusal12(lines, count);
|
|
17382
16709
|
if (count === 0) {
|
|
17383
16710
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
17384
16711
|
}
|
|
@@ -17387,9 +16714,9 @@ function selectTriagePullRequests(input) {
|
|
|
17387
16714
|
return plain("single", Object.freeze([only]), `1 red: assessing #${only.number}, last moved ${describeAge(only.updatedAt, now)}`);
|
|
17388
16715
|
}
|
|
17389
16716
|
if (count > MAX_TRIAGE_CANDIDATES)
|
|
17390
|
-
return listReading(
|
|
16717
|
+
return listReading(lines, count);
|
|
17391
16718
|
const fresh = candidates.filter((pull2) => isFreshCandidate(pull2.updatedAt, now));
|
|
17392
|
-
const stale =
|
|
16719
|
+
const stale = lines.filter((one) => !one.fresh);
|
|
17393
16720
|
if (fresh.length === 0)
|
|
17394
16721
|
return listReading(stale, count);
|
|
17395
16722
|
return someReading(Object.freeze(fresh), stale, count);
|
|
@@ -17462,7 +16789,7 @@ function readConflict(git, base, head) {
|
|
|
17462
16789
|
// src/commands/pr/triage-read.ts
|
|
17463
16790
|
var RUN_URL = /\/actions\/runs\/(\d+)(?:[/?#]|$)/;
|
|
17464
16791
|
var CONFLICT_UNREAD = "unread";
|
|
17465
|
-
var
|
|
16792
|
+
var REMOTE2 = "origin";
|
|
17466
16793
|
function runIdOf(link) {
|
|
17467
16794
|
return RUN_URL.exec(link)?.[1] ?? null;
|
|
17468
16795
|
}
|
|
@@ -17510,10 +16837,10 @@ async function readWorkflowCount(pulls, rows) {
|
|
|
17510
16837
|
}
|
|
17511
16838
|
}
|
|
17512
16839
|
function baseCandidates(refs2) {
|
|
17513
|
-
return [`${
|
|
16840
|
+
return [`${REMOTE2}/${refs2.baseRefName}`, refs2.baseRefName];
|
|
17514
16841
|
}
|
|
17515
16842
|
function headCandidates(refs2) {
|
|
17516
|
-
return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${
|
|
16843
|
+
return refs2.isCrossRepository ? [refs2.headRefOid] : [refs2.headRefOid, `${REMOTE2}/${refs2.headRefName}`];
|
|
17517
16844
|
}
|
|
17518
16845
|
function resolvesRef(git, ref8) {
|
|
17519
16846
|
return git(["rev-parse", "--verify", "--quiet", `${ref8}^{commit}`]).ok;
|
|
@@ -17737,8 +17064,8 @@ function withTaskBudget(plan2, usd) {
|
|
|
17737
17064
|
}
|
|
17738
17065
|
|
|
17739
17066
|
// src/pr/plans/load.ts
|
|
17740
|
-
import { existsSync as
|
|
17741
|
-
import { dirname as dirname6, join as
|
|
17067
|
+
import { existsSync as existsSync9, readFileSync as readFileSync18 } from "fs";
|
|
17068
|
+
import { dirname as dirname6, join as join29 } from "path";
|
|
17742
17069
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17743
17070
|
var MODULE_DIR4 = dirname6(fileURLToPath4(import.meta.url));
|
|
17744
17071
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
@@ -17755,8 +17082,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
17755
17082
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
17756
17083
|
const name = pinnedPlanFileName(triageClass);
|
|
17757
17084
|
return [
|
|
17758
|
-
|
|
17759
|
-
|
|
17085
|
+
join29(moduleDir, PINNED_PLANS_DIRNAME, name),
|
|
17086
|
+
join29(moduleDir, name)
|
|
17760
17087
|
];
|
|
17761
17088
|
}
|
|
17762
17089
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -17764,7 +17091,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
17764
17091
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
17765
17092
|
}
|
|
17766
17093
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
17767
|
-
const found = candidates.find((candidate) =>
|
|
17094
|
+
const found = candidates.find((candidate) => existsSync9(candidate));
|
|
17768
17095
|
if (found === undefined) {
|
|
17769
17096
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
17770
17097
|
}
|
|
@@ -17909,19 +17236,19 @@ function readAttemptRepeat(input) {
|
|
|
17909
17236
|
|
|
17910
17237
|
// src/commands/pr/resolve-loop.ts
|
|
17911
17238
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
17912
|
-
import { join as
|
|
17913
|
-
var RESOLVE_SUBDIR =
|
|
17239
|
+
import { join as join30 } from "path";
|
|
17240
|
+
var RESOLVE_SUBDIR = join30(".rafa", "resolve");
|
|
17914
17241
|
var RUN_PREFIX = "pr-";
|
|
17915
17242
|
var ATTEMPT_PREFIX = "attempt-";
|
|
17916
17243
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
17917
17244
|
var CHILD_OUTPUT = "text";
|
|
17918
17245
|
function resolveRunDir(home, number, attempt3) {
|
|
17919
|
-
return
|
|
17246
|
+
return join30(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
17920
17247
|
}
|
|
17921
17248
|
function writeResolvePlan(write) {
|
|
17922
17249
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
17923
17250
|
mkdirSync5(dir, { recursive: true });
|
|
17924
|
-
const path =
|
|
17251
|
+
const path = join30(dir, pinnedPlanFileName(write.triageClass));
|
|
17925
17252
|
writeFileSync6(path, write.plan, "utf8");
|
|
17926
17253
|
return path;
|
|
17927
17254
|
}
|
|
@@ -17933,10 +17260,10 @@ async function forwardLines(stream, onLine) {
|
|
|
17933
17260
|
let pending = "";
|
|
17934
17261
|
for await (const chunk of stream) {
|
|
17935
17262
|
pending += decoder.decode(chunk, { stream: true });
|
|
17936
|
-
const
|
|
17263
|
+
const lines = pending.split(`
|
|
17937
17264
|
`);
|
|
17938
|
-
pending =
|
|
17939
|
-
for (const line of
|
|
17265
|
+
pending = lines.pop() ?? "";
|
|
17266
|
+
for (const line of lines)
|
|
17940
17267
|
onLine(line);
|
|
17941
17268
|
}
|
|
17942
17269
|
if (pending !== "")
|
|
@@ -17963,7 +17290,7 @@ async function runResolveLoop(run, seams = {}) {
|
|
|
17963
17290
|
|
|
17964
17291
|
// src/pr/triage/version-convert.ts
|
|
17965
17292
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
|
|
17966
|
-
import { dirname as dirname7, join as
|
|
17293
|
+
import { dirname as dirname7, join as join31, posix } from "path";
|
|
17967
17294
|
var FALLBACK_PLAN_PREFIX = "pr-";
|
|
17968
17295
|
var UNUSABLE_PLAN_CHARACTERS = /[^A-Za-z0-9._-]+/g;
|
|
17969
17296
|
var UNUSABLE_PLAN_START = /^[^A-Za-z0-9]+/;
|
|
@@ -18075,7 +17402,7 @@ function planConversion(input, problems) {
|
|
|
18075
17402
|
}
|
|
18076
17403
|
let branchVersionText;
|
|
18077
17404
|
try {
|
|
18078
|
-
branchVersionText = readFileSync19(
|
|
17405
|
+
branchVersionText = readFileSync19(join31(input.worktree, settings.releaseVersionFile), "utf8");
|
|
18079
17406
|
} catch (error) {
|
|
18080
17407
|
return refused(`${settings.releaseVersionFile} could not be read in the worktree: ${messageOf(error)}`, problems);
|
|
18081
17408
|
}
|
|
@@ -18101,7 +17428,7 @@ function writeConversion(input, plan2) {
|
|
|
18101
17428
|
[input.settings.releaseChangelog, plan2.changelogText]
|
|
18102
17429
|
];
|
|
18103
17430
|
for (const [path, text] of writes) {
|
|
18104
|
-
const target =
|
|
17431
|
+
const target = join31(input.worktree, path);
|
|
18105
17432
|
try {
|
|
18106
17433
|
mkdirSync6(dirname7(target), { recursive: true });
|
|
18107
17434
|
writeFileSync7(target, text);
|
|
@@ -18913,7 +18240,7 @@ var USAGE40 = PR_USAGE.wait;
|
|
|
18913
18240
|
var TIMEOUT_FLAG = "timeout";
|
|
18914
18241
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
18915
18242
|
var MS_PER_MIN = 60000;
|
|
18916
|
-
var
|
|
18243
|
+
var MS_PER_SECOND2 = 1000;
|
|
18917
18244
|
var WAIT_EXIT_CODES = Object.freeze({
|
|
18918
18245
|
green: 0,
|
|
18919
18246
|
red: 1,
|
|
@@ -18942,7 +18269,7 @@ function pollWord(polls) {
|
|
|
18942
18269
|
return polls === 1 ? "1 poll" : `${polls} polls`;
|
|
18943
18270
|
}
|
|
18944
18271
|
function secondsWord(elapsedMs) {
|
|
18945
|
-
return `${Math.round(elapsedMs /
|
|
18272
|
+
return `${Math.round(elapsedMs / MS_PER_SECOND2)}s`;
|
|
18946
18273
|
}
|
|
18947
18274
|
function verdictWord(verdict) {
|
|
18948
18275
|
return verdict === "timeout" || verdict === "pending" ? "still running" : verdict;
|
|
@@ -19333,9 +18660,9 @@ function tagSettle(worktree, mode, delivered) {
|
|
|
19333
18660
|
}
|
|
19334
18661
|
|
|
19335
18662
|
// src/release/settle-worktree.ts
|
|
19336
|
-
import { existsSync as
|
|
18663
|
+
import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, realpathSync as realpathSync3, rmSync as rmSync2 } from "fs";
|
|
19337
18664
|
import { tmpdir as tmpdir2 } from "os";
|
|
19338
|
-
import { join as
|
|
18665
|
+
import { join as join32 } from "path";
|
|
19339
18666
|
var SETTLE_SCRATCH_PREFIX = "rafa-settle-";
|
|
19340
18667
|
var WORKTREE_DIRECTORY = "base";
|
|
19341
18668
|
function removeScratch(git, scratch, path) {
|
|
@@ -19352,14 +18679,14 @@ function removeScratch(git, scratch, path) {
|
|
|
19352
18679
|
problems.push(`the worktree record of ${path} could not be pruned: ${gitSaid(pruned)}`);
|
|
19353
18680
|
}
|
|
19354
18681
|
}
|
|
19355
|
-
if (problems.length === 0 &&
|
|
18682
|
+
if (problems.length === 0 && existsSync10(scratch)) {
|
|
19356
18683
|
problems.push(`the scratch directory ${scratch} is still there`);
|
|
19357
18684
|
}
|
|
19358
18685
|
return problems.length === 0 ? null : problems.join("; ");
|
|
19359
18686
|
}
|
|
19360
18687
|
function makeScratch(root) {
|
|
19361
18688
|
try {
|
|
19362
|
-
return
|
|
18689
|
+
return realpathSync3(mkdtempSync2(join32(root, SETTLE_SCRATCH_PREFIX)));
|
|
19363
18690
|
} catch (error) {
|
|
19364
18691
|
return { ok: false, problem: `no scratch directory could be made under ${root}: ${messageOf(error)}`, leftover: null };
|
|
19365
18692
|
}
|
|
@@ -19381,7 +18708,7 @@ function addWorktree(options) {
|
|
|
19381
18708
|
const scratch = makeScratch(options.scratchRoot ?? tmpdir2());
|
|
19382
18709
|
if (typeof scratch !== "string")
|
|
19383
18710
|
return scratch;
|
|
19384
|
-
const path =
|
|
18711
|
+
const path = join32(scratch, WORKTREE_DIRECTORY);
|
|
19385
18712
|
const added = git(["worktree", "add", "--detach", path, commit]);
|
|
19386
18713
|
if (!added.ok) {
|
|
19387
18714
|
return {
|
|
@@ -19435,26 +18762,26 @@ function short(hash) {
|
|
|
19435
18762
|
return hash.slice(0, SHORT_HASH4);
|
|
19436
18763
|
}
|
|
19437
18764
|
function readingLines(reading, ref8) {
|
|
19438
|
-
const
|
|
18765
|
+
const lines = [`Strategy: ${reading.strategy}`];
|
|
19439
18766
|
if (reading.outcome === "unread")
|
|
19440
|
-
return
|
|
18767
|
+
return lines;
|
|
19441
18768
|
if (reading.outcome === "malformed") {
|
|
19442
|
-
return [...
|
|
18769
|
+
return [...lines, `Base: ${ref8} at ${short(reading.commit)}`];
|
|
19443
18770
|
}
|
|
19444
|
-
|
|
18771
|
+
lines.push(`Base: ${ref8} at ${short(reading.commit)}, version ${reading.baseVersion}`);
|
|
19445
18772
|
if (reading.fragments.length === 0) {
|
|
19446
|
-
|
|
18773
|
+
lines.push("Fragments: none waiting");
|
|
19447
18774
|
} else {
|
|
19448
|
-
|
|
18775
|
+
lines.push("Fragments, in the order the base received them:");
|
|
19449
18776
|
reading.fragments.forEach((each, index) => {
|
|
19450
18777
|
const { level, title } = each.fragment;
|
|
19451
|
-
|
|
18778
|
+
lines.push(`${INDENT8}${String(index + 1)}. ${each.path} \u2014 ${level}, "${title}", added ${each.addedOn} in ${short(each.commit)}`);
|
|
19452
18779
|
});
|
|
19453
18780
|
}
|
|
19454
18781
|
if (reading.outcome === "folded" || reading.outcome === "built" || reading.outcome === "unbuilt") {
|
|
19455
|
-
|
|
18782
|
+
lines.push(`Version: ${reading.baseVersion} \u2192 ${reading.version}`);
|
|
19456
18783
|
}
|
|
19457
|
-
return
|
|
18784
|
+
return lines;
|
|
19458
18785
|
}
|
|
19459
18786
|
function unwrittenEnding(reading) {
|
|
19460
18787
|
switch (reading.outcome) {
|
|
@@ -19468,19 +18795,19 @@ function unwrittenEnding(reading) {
|
|
|
19468
18795
|
return { line: "Nothing to settle: no fragment waits that ships a release.", refuses: false };
|
|
19469
18796
|
}
|
|
19470
18797
|
}
|
|
19471
|
-
function resultOf5(base,
|
|
18798
|
+
function resultOf5(base, lines, problem2) {
|
|
19472
18799
|
const exitCode = problem2 === null ? 0 : 1;
|
|
19473
|
-
return { ...base, lines
|
|
18800
|
+
return { ...base, lines, problem: problem2, exitCode };
|
|
19474
18801
|
}
|
|
19475
18802
|
function dryRunResult(reading, ref8, branch) {
|
|
19476
18803
|
const base = { dryRun: true, delivery: null, reading, delivered: null, tag: null };
|
|
19477
|
-
const
|
|
18804
|
+
const lines = [...readingLines(reading, ref8)];
|
|
19478
18805
|
if (reading.outcome === "folded") {
|
|
19479
|
-
|
|
19480
|
-
return resultOf5(base,
|
|
18806
|
+
lines.push(`Dry run: settle would commit "${releaseCommitSubject(reading.version)}" on ${branch}; nothing was written.`);
|
|
18807
|
+
return resultOf5(base, lines, null);
|
|
19481
18808
|
}
|
|
19482
18809
|
const ending = unwrittenEnding(reading);
|
|
19483
|
-
return ending.refuses ? resultOf5(base,
|
|
18810
|
+
return ending.refuses ? resultOf5(base, lines, ending.line) : resultOf5(base, [...lines, ending.line], null);
|
|
19484
18811
|
}
|
|
19485
18812
|
function deliveryEnding(delivered, branch) {
|
|
19486
18813
|
const { outcome } = delivered;
|
|
@@ -19509,18 +18836,18 @@ function deliveryEnding(delivered, branch) {
|
|
|
19509
18836
|
function settledResult(delivered, tag, ref8, branch) {
|
|
19510
18837
|
const reading = delivered.outcome.build;
|
|
19511
18838
|
const base = { dryRun: false, delivery: delivered.delivery, reading, delivered, tag };
|
|
19512
|
-
const
|
|
18839
|
+
const lines = [...readingLines(reading, ref8)];
|
|
19513
18840
|
const ending = deliveryEnding(delivered, branch);
|
|
19514
18841
|
if (ending.refuses)
|
|
19515
|
-
return resultOf5(base,
|
|
19516
|
-
|
|
18842
|
+
return resultOf5(base, lines, ending.line);
|
|
18843
|
+
lines.push(ending.line);
|
|
19517
18844
|
if (tag.outcome === "failed")
|
|
19518
|
-
return resultOf5(base,
|
|
18845
|
+
return resultOf5(base, lines, `\u274C ${tag.sentence}`);
|
|
19519
18846
|
if (tag.outcome === "tagged")
|
|
19520
|
-
|
|
18847
|
+
lines.push(`\u2705 ${tag.sentence}`);
|
|
19521
18848
|
if (tag.outcome === "skipped" && tag.sentence !== null)
|
|
19522
|
-
|
|
19523
|
-
return resultOf5(base,
|
|
18849
|
+
lines.push(`Tag: ${tag.sentence}`);
|
|
18850
|
+
return resultOf5(base, lines, null);
|
|
19524
18851
|
}
|
|
19525
18852
|
function providerFor(project, config, seams) {
|
|
19526
18853
|
requireGhProvider(resolvePrProvider({ configured: config.prProvider, dir: project.root, readRemote: seams.readRemote }));
|
|
@@ -19604,7 +18931,7 @@ var settle_default = createReleaseSettleCommand();
|
|
|
19604
18931
|
|
|
19605
18932
|
// src/commands/release/tag.ts
|
|
19606
18933
|
import { readFileSync as readFileSync20 } from "fs";
|
|
19607
|
-
import { join as
|
|
18934
|
+
import { join as join33 } from "path";
|
|
19608
18935
|
|
|
19609
18936
|
// src/commands/release/release-commit.ts
|
|
19610
18937
|
function unread(reason, message) {
|
|
@@ -19668,7 +18995,7 @@ function textOf(path) {
|
|
|
19668
18995
|
}
|
|
19669
18996
|
}
|
|
19670
18997
|
function readVersion(root, configured) {
|
|
19671
|
-
const resolved =
|
|
18998
|
+
const resolved = join33(root, configured);
|
|
19672
18999
|
const text = textOf(resolved);
|
|
19673
19000
|
if (text === null) {
|
|
19674
19001
|
return {
|
|
@@ -19682,7 +19009,7 @@ function readVersion(root, configured) {
|
|
|
19682
19009
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
19683
19010
|
}
|
|
19684
19011
|
function readNewestRelease(root, configured) {
|
|
19685
|
-
const resolved =
|
|
19012
|
+
const resolved = join33(root, configured);
|
|
19686
19013
|
const text = textOf(resolved);
|
|
19687
19014
|
if (text === null) {
|
|
19688
19015
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -19893,8 +19220,8 @@ function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
19893
19220
|
context.output.result(result2);
|
|
19894
19221
|
return;
|
|
19895
19222
|
}
|
|
19896
|
-
const
|
|
19897
|
-
for (const line of
|
|
19223
|
+
const lines = renderTagged(result2.written, result2.inputs.releaseBranch, result2.followUps);
|
|
19224
|
+
for (const line of lines)
|
|
19898
19225
|
context.output.info(line);
|
|
19899
19226
|
}
|
|
19900
19227
|
};
|
|
@@ -19952,17 +19279,17 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
19952
19279
|
var roadmap_default = createRoadmapCommand();
|
|
19953
19280
|
|
|
19954
19281
|
// src/commands/skill/backfill.ts
|
|
19955
|
-
import { statSync as
|
|
19956
|
-
import { join as
|
|
19282
|
+
import { statSync as statSync9 } from "fs";
|
|
19283
|
+
import { join as join38, resolve as resolve13 } from "path";
|
|
19957
19284
|
|
|
19958
19285
|
// src/backfill/backup.ts
|
|
19959
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
19960
|
-
import { dirname as dirname8, isAbsolute as isAbsolute6, join as
|
|
19286
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
|
|
19287
|
+
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join35, relative as relative7, resolve as resolve11, sep as sep4 } from "path";
|
|
19961
19288
|
|
|
19962
19289
|
// src/backfill/proposal-file.ts
|
|
19963
19290
|
import { readdirSync as readdirSync3, readFileSync as readFileSync21 } from "fs";
|
|
19964
|
-
import { join as
|
|
19965
|
-
var BACKFILL_PATH =
|
|
19291
|
+
import { join as join34 } from "path";
|
|
19292
|
+
var BACKFILL_PATH = join34(".rafa", "backfill");
|
|
19966
19293
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
19967
19294
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
19968
19295
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -19988,7 +19315,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
19988
19315
|
].join(`
|
|
19989
19316
|
`);
|
|
19990
19317
|
function proposalPath(dir, batch) {
|
|
19991
|
-
return
|
|
19318
|
+
return join34(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
19992
19319
|
}
|
|
19993
19320
|
function isProposalFileName(name) {
|
|
19994
19321
|
return name.startsWith(PROPOSAL_FILE_PREFIX) && name.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -20124,7 +19451,7 @@ function readProposalFiles(dir) {
|
|
|
20124
19451
|
return [];
|
|
20125
19452
|
}
|
|
20126
19453
|
return names.filter((name) => isProposalFileName(name)).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
20127
|
-
const path =
|
|
19454
|
+
const path = join34(dir, name);
|
|
20128
19455
|
try {
|
|
20129
19456
|
return { path, result: parseProposalFile(readFileSync21(path, "utf8")) };
|
|
20130
19457
|
} catch (error) {
|
|
@@ -20134,19 +19461,19 @@ function readProposalFiles(dir) {
|
|
|
20134
19461
|
}
|
|
20135
19462
|
|
|
20136
19463
|
// src/backfill/backup.ts
|
|
20137
|
-
var BACKUP_PATH =
|
|
19464
|
+
var BACKUP_PATH = join35(BACKFILL_PATH, "backup");
|
|
20138
19465
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
20139
19466
|
function backupDirectory(base) {
|
|
20140
|
-
return
|
|
19467
|
+
return join35(base, BACKUP_PATH);
|
|
20141
19468
|
}
|
|
20142
19469
|
function isUnder(root, path) {
|
|
20143
19470
|
const step = relative7(resolve11(root), resolve11(path));
|
|
20144
|
-
return step === "" || !step.startsWith(`..${
|
|
19471
|
+
return step === "" || !step.startsWith(`..${sep4}`) && step !== ".." && !isAbsolute6(step);
|
|
20145
19472
|
}
|
|
20146
19473
|
function backupPathFor(path, root, backupDir) {
|
|
20147
19474
|
if (!isUnder(root, path))
|
|
20148
19475
|
return null;
|
|
20149
|
-
return
|
|
19476
|
+
return join35(backupDir, relative7(resolve11(root), resolve11(path)));
|
|
20150
19477
|
}
|
|
20151
19478
|
function backupFile(path, options) {
|
|
20152
19479
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -20155,9 +19482,9 @@ function backupFile(path, options) {
|
|
|
20155
19482
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
20156
19483
|
if (backup === null)
|
|
20157
19484
|
return { path, kind: "outside", backup: null };
|
|
20158
|
-
if (!
|
|
19485
|
+
if (!existsSync11(path))
|
|
20159
19486
|
return { path, kind: "missing", backup: null };
|
|
20160
|
-
if (
|
|
19487
|
+
if (existsSync11(backup))
|
|
20161
19488
|
return { path, kind: "kept", backup };
|
|
20162
19489
|
mkdirSync7(dirname8(backup), { recursive: true });
|
|
20163
19490
|
copyFileSync3(path, backup);
|
|
@@ -20516,12 +19843,12 @@ function readVerdict(cell) {
|
|
|
20516
19843
|
function isEmptyCell(cell) {
|
|
20517
19844
|
return cell === "" || cell === EMPTY_CELL;
|
|
20518
19845
|
}
|
|
20519
|
-
function findTable(
|
|
20520
|
-
for (let index = 0;index + 1 <
|
|
20521
|
-
const header = splitTableRow(
|
|
19846
|
+
function findTable(lines) {
|
|
19847
|
+
for (let index = 0;index + 1 < lines.length; index += 1) {
|
|
19848
|
+
const header = splitTableRow(lines[index] ?? "");
|
|
20522
19849
|
if (header === null)
|
|
20523
19850
|
continue;
|
|
20524
|
-
const delimiter = splitTableRow(
|
|
19851
|
+
const delimiter = splitTableRow(lines[index + 1] ?? "");
|
|
20525
19852
|
if (delimiter !== null && isDelimiterRow(delimiter)) {
|
|
20526
19853
|
return { header, start: index };
|
|
20527
19854
|
}
|
|
@@ -20582,11 +19909,11 @@ function parseDemotionReport(text) {
|
|
|
20582
19909
|
const status = readStatus(document.data);
|
|
20583
19910
|
if (typeof status !== "string")
|
|
20584
19911
|
issues.push(status);
|
|
20585
|
-
const
|
|
19912
|
+
const lines = text.split(`
|
|
20586
19913
|
`).map((line) => line.replace(/\r$/, ""));
|
|
20587
|
-
const bodyStart =
|
|
19914
|
+
const bodyStart = lines.length - document.body.split(`
|
|
20588
19915
|
`).length;
|
|
20589
|
-
const table = findTable(
|
|
19916
|
+
const table = findTable(lines.slice(bodyStart));
|
|
20590
19917
|
if (table === null) {
|
|
20591
19918
|
issues.push(issue("missing-table", "file", "the report holds no row table", 1));
|
|
20592
19919
|
return { issues, report: null };
|
|
@@ -20599,8 +19926,8 @@ function parseDemotionReport(text) {
|
|
|
20599
19926
|
}
|
|
20600
19927
|
const rows = [];
|
|
20601
19928
|
const seen = new Set;
|
|
20602
|
-
for (let index = bodyStart + table.start + 2;index <
|
|
20603
|
-
const cells = splitTableRow(
|
|
19929
|
+
for (let index = bodyStart + table.start + 2;index < lines.length; index += 1) {
|
|
19930
|
+
const cells = splitTableRow(lines[index] ?? "");
|
|
20604
19931
|
if (cells === null)
|
|
20605
19932
|
break;
|
|
20606
19933
|
if (cells.length !== DEMOTION_REPORT_COLUMNS.length) {
|
|
@@ -20789,7 +20116,7 @@ function parseSessionAnswer(stdout) {
|
|
|
20789
20116
|
|
|
20790
20117
|
// src/backfill/propose.ts
|
|
20791
20118
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync9 } from "fs";
|
|
20792
|
-
import { join as
|
|
20119
|
+
import { join as join36, resolve as resolve12 } from "path";
|
|
20793
20120
|
var PROPOSAL_ACTION_KINDS = [
|
|
20794
20121
|
"applied",
|
|
20795
20122
|
"unchanged",
|
|
@@ -20938,7 +20265,7 @@ function writeRow(path, row2, original, options) {
|
|
|
20938
20265
|
};
|
|
20939
20266
|
}
|
|
20940
20267
|
function applyRow(root, row2, options) {
|
|
20941
|
-
const path =
|
|
20268
|
+
const path = join36(root, ...row2.path.split("/"));
|
|
20942
20269
|
if (row2.status === UNANSWERED_ROW) {
|
|
20943
20270
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
20944
20271
|
}
|
|
@@ -20975,7 +20302,7 @@ function applyProposals(files, root, options) {
|
|
|
20975
20302
|
for (const file of files) {
|
|
20976
20303
|
const refusal13 = fileRefusal(file, root);
|
|
20977
20304
|
for (const row2 of file.rows) {
|
|
20978
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
20305
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join36(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
20979
20306
|
actions.push(action);
|
|
20980
20307
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
20981
20308
|
triggers[row2.name] = row2.trigger;
|
|
@@ -20986,17 +20313,17 @@ function applyProposals(files, root, options) {
|
|
|
20986
20313
|
}
|
|
20987
20314
|
|
|
20988
20315
|
// src/demote/select.ts
|
|
20989
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as
|
|
20990
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
20316
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync25, realpathSync as realpathSync4, statSync as statSync8 } from "fs";
|
|
20317
|
+
import { basename as basename7, dirname as dirname9, join as join37 } from "path";
|
|
20991
20318
|
var SKILLS_DIRECTORY = "skills";
|
|
20992
20319
|
var CLAUDE_DIRECTORY = ".claude";
|
|
20993
20320
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
20994
|
-
var DEMOTED_PATH =
|
|
20995
|
-
var INSTINCTS_PATH =
|
|
20321
|
+
var DEMOTED_PATH = join37(".rafa", "demoted");
|
|
20322
|
+
var INSTINCTS_PATH = join37(".rafa", "instincts");
|
|
20996
20323
|
var REPORT_FILE = "report.md";
|
|
20997
20324
|
function realOrSelf(path) {
|
|
20998
20325
|
try {
|
|
20999
|
-
return
|
|
20326
|
+
return realpathSync4(path);
|
|
21000
20327
|
} catch {
|
|
21001
20328
|
return path;
|
|
21002
20329
|
}
|
|
@@ -21015,19 +20342,19 @@ function resolveDemotionScope(dir, home) {
|
|
|
21015
20342
|
if (basename7(claude) !== CLAUDE_DIRECTORY)
|
|
21016
20343
|
return null;
|
|
21017
20344
|
const base = dirname9(claude);
|
|
21018
|
-
const demotedDir =
|
|
20345
|
+
const demotedDir = join37(base, DEMOTED_PATH);
|
|
21019
20346
|
return {
|
|
21020
20347
|
scope: samePath(base, home) ? "user" : "project",
|
|
21021
20348
|
dir,
|
|
21022
20349
|
base,
|
|
21023
20350
|
demotedDir,
|
|
21024
|
-
instinctsDir:
|
|
21025
|
-
reportPath:
|
|
20351
|
+
instinctsDir: join37(base, INSTINCTS_PATH),
|
|
20352
|
+
reportPath: join37(demotedDir, REPORT_FILE)
|
|
21026
20353
|
};
|
|
21027
20354
|
}
|
|
21028
20355
|
function entryKind(path) {
|
|
21029
20356
|
try {
|
|
21030
|
-
const stats =
|
|
20357
|
+
const stats = statSync8(path);
|
|
21031
20358
|
if (stats.isFile())
|
|
21032
20359
|
return "file";
|
|
21033
20360
|
if (stats.isDirectory())
|
|
@@ -21049,7 +20376,7 @@ function readSelected(absolute, path, entries) {
|
|
|
21049
20376
|
let mtime;
|
|
21050
20377
|
try {
|
|
21051
20378
|
text = readFileSync25(absolute, "utf8");
|
|
21052
|
-
mtime =
|
|
20379
|
+
mtime = statSync8(absolute).mtime.toISOString();
|
|
21053
20380
|
} catch {
|
|
21054
20381
|
return null;
|
|
21055
20382
|
}
|
|
@@ -21064,14 +20391,14 @@ function selectsLearned(text, scope) {
|
|
|
21064
20391
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
21065
20392
|
}
|
|
21066
20393
|
function selectLearned(scope) {
|
|
21067
|
-
const dir =
|
|
20394
|
+
const dir = join37(scope.dir, LEARNED_DIRECTORY);
|
|
21068
20395
|
if (entryKind(dir) !== "directory")
|
|
21069
20396
|
return [];
|
|
21070
20397
|
const selected = [];
|
|
21071
20398
|
for (const name of visibleNames(dir)) {
|
|
21072
20399
|
if (!name.endsWith(MARKDOWN_EXTENSION) || name === MARKDOWN_EXTENSION)
|
|
21073
20400
|
continue;
|
|
21074
|
-
const absolute =
|
|
20401
|
+
const absolute = join37(dir, name);
|
|
21075
20402
|
if (entryKind(absolute) !== "file")
|
|
21076
20403
|
continue;
|
|
21077
20404
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name}`, undefined);
|
|
@@ -21085,10 +20412,10 @@ function selectSkillFiles(scope) {
|
|
|
21085
20412
|
for (const name of visibleNames(scope.dir)) {
|
|
21086
20413
|
if (name === LEARNED_DIRECTORY)
|
|
21087
20414
|
continue;
|
|
21088
|
-
const skillDir =
|
|
20415
|
+
const skillDir = join37(scope.dir, name);
|
|
21089
20416
|
if (entryKind(skillDir) !== "directory")
|
|
21090
20417
|
continue;
|
|
21091
|
-
const absolute =
|
|
20418
|
+
const absolute = join37(skillDir, SKILL_FILE);
|
|
21092
20419
|
if (entryKind(absolute) !== "file")
|
|
21093
20420
|
continue;
|
|
21094
20421
|
const file = readSelected(absolute, `${name}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -21149,7 +20476,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
21149
20476
|
}
|
|
21150
20477
|
function isDirectory3(path) {
|
|
21151
20478
|
try {
|
|
21152
|
-
return
|
|
20479
|
+
return statSync9(path).isDirectory();
|
|
21153
20480
|
} catch {
|
|
21154
20481
|
return false;
|
|
21155
20482
|
}
|
|
@@ -21198,12 +20525,12 @@ function actionLines(actions) {
|
|
|
21198
20525
|
function checkOptions(context, projectRoot2) {
|
|
21199
20526
|
return { projectRoot: projectRoot2, pathDirs: pathDirectories(context.env["PATH"]) };
|
|
21200
20527
|
}
|
|
21201
|
-
function report3(context, result2,
|
|
20528
|
+
function report3(context, result2, lines) {
|
|
21202
20529
|
if (context.outputMode === "json") {
|
|
21203
20530
|
context.output.result(result2);
|
|
21204
20531
|
return;
|
|
21205
20532
|
}
|
|
21206
|
-
for (const line of
|
|
20533
|
+
for (const line of lines)
|
|
21207
20534
|
context.output.info(line);
|
|
21208
20535
|
}
|
|
21209
20536
|
function runPlan(context, scope, backfillDir, projectRoot2) {
|
|
@@ -21273,7 +20600,7 @@ function reviewedProposals(dir) {
|
|
|
21273
20600
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
21274
20601
|
}
|
|
21275
20602
|
function rowPaths(files, root) {
|
|
21276
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
20603
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join38(root, ...row2.path.split("/"))));
|
|
21277
20604
|
}
|
|
21278
20605
|
function runApply(context, scope, backfillDir, projectRoot2) {
|
|
21279
20606
|
const options = checkOptions(context, projectRoot2);
|
|
@@ -21300,7 +20627,7 @@ function runApply(context, scope, backfillDir, projectRoot2) {
|
|
|
21300
20627
|
derivationCounts: countDerivations(derived.actions),
|
|
21301
20628
|
backups: copied
|
|
21302
20629
|
};
|
|
21303
|
-
const
|
|
20630
|
+
const lines = [
|
|
21304
20631
|
`${scope.scope} scope: ${scope.dir}`,
|
|
21305
20632
|
...actionLines(proposalActions),
|
|
21306
20633
|
proposalLine2(proposals.actions.length, proposals.counts),
|
|
@@ -21310,16 +20637,16 @@ function runApply(context, scope, backfillDir, projectRoot2) {
|
|
|
21310
20637
|
];
|
|
21311
20638
|
const refused2 = proposals.counts.refused + derived.counts.refused;
|
|
21312
20639
|
if (refused2 > 0)
|
|
21313
|
-
throw new CommandExit(Math.min(refused2, REFUSED_CAP),
|
|
20640
|
+
throw new CommandExit(Math.min(refused2, REFUSED_CAP), lines.join(`
|
|
21314
20641
|
`));
|
|
21315
|
-
report3(context, result2,
|
|
20642
|
+
report3(context, result2, lines);
|
|
21316
20643
|
}
|
|
21317
20644
|
async function runBackfill(context, seams) {
|
|
21318
20645
|
const mode = readMode(context.flags);
|
|
21319
20646
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
21320
20647
|
const projectRoot2 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
21321
20648
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
21322
|
-
const backfillDir =
|
|
20649
|
+
const backfillDir = join38(scope.base, BACKFILL_PATH);
|
|
21323
20650
|
if (mode === "propose") {
|
|
21324
20651
|
await runPropose(context, scope, backfillDir, seams);
|
|
21325
20652
|
return;
|
|
@@ -21433,13 +20760,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
21433
20760
|
var check_default3 = createSkillCheckCommand();
|
|
21434
20761
|
|
|
21435
20762
|
// src/commands/skill/demote.ts
|
|
21436
|
-
import { existsSync as
|
|
20763
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync27, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
21437
20764
|
import { dirname as dirname11, resolve as resolve14 } from "path";
|
|
21438
20765
|
|
|
21439
20766
|
// src/demote/apply.ts
|
|
21440
|
-
import { copyFileSync as copyFileSync4, existsSync as
|
|
20767
|
+
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
20768
|
import { tmpdir as tmpdir3 } from "os";
|
|
21442
|
-
import { dirname as dirname10, join as
|
|
20769
|
+
import { dirname as dirname10, join as join39 } from "path";
|
|
21443
20770
|
var DEMOTION_ACTION_KINDS = [
|
|
21444
20771
|
"demoted",
|
|
21445
20772
|
"kept",
|
|
@@ -21460,13 +20787,13 @@ function learnedSkillName(path) {
|
|
|
21460
20787
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
21461
20788
|
}
|
|
21462
20789
|
function sourcePath(scope, path) {
|
|
21463
|
-
return
|
|
20790
|
+
return join39(scope.dir, ...path.split("/"));
|
|
21464
20791
|
}
|
|
21465
20792
|
function demotedPath(scope, path) {
|
|
21466
|
-
return
|
|
20793
|
+
return join39(scope.demotedDir, ...path.split("/"));
|
|
21467
20794
|
}
|
|
21468
20795
|
function promotedPath(scope, path) {
|
|
21469
|
-
return
|
|
20796
|
+
return join39(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
21470
20797
|
}
|
|
21471
20798
|
function takenInstinctIds(dir) {
|
|
21472
20799
|
let names;
|
|
@@ -21479,7 +20806,7 @@ function takenInstinctIds(dir) {
|
|
|
21479
20806
|
}
|
|
21480
20807
|
function readText4(path) {
|
|
21481
20808
|
try {
|
|
21482
|
-
return
|
|
20809
|
+
return statSync10(path).isFile() ? readFileSync26(path, "utf8") : null;
|
|
21483
20810
|
} catch {
|
|
21484
20811
|
return null;
|
|
21485
20812
|
}
|
|
@@ -21507,7 +20834,7 @@ function planKept(scope, row2, file) {
|
|
|
21507
20834
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
21508
20835
|
}
|
|
21509
20836
|
const to = promotedPath(scope, row2.path);
|
|
21510
|
-
if (
|
|
20837
|
+
if (existsSync12(to) || existsSync12(dirname10(to))) {
|
|
21511
20838
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
21512
20839
|
}
|
|
21513
20840
|
return {
|
|
@@ -21521,7 +20848,7 @@ function planKept(scope, row2, file) {
|
|
|
21521
20848
|
};
|
|
21522
20849
|
}
|
|
21523
20850
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
21524
|
-
const path =
|
|
20851
|
+
const path = join39(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21525
20852
|
writeFileSync10(path, text, "utf8");
|
|
21526
20853
|
const report4 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
21527
20854
|
return failureLines2(report4.issues);
|
|
@@ -21542,11 +20869,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
21542
20869
|
if (failures.length > 0) {
|
|
21543
20870
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
21544
20871
|
}
|
|
21545
|
-
const destination =
|
|
20872
|
+
const destination = join39(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
21546
20873
|
const kept = demotedPath(scope, row2.path);
|
|
21547
|
-
if (
|
|
20874
|
+
if (existsSync12(destination))
|
|
21548
20875
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
21549
|
-
if (
|
|
20876
|
+
if (existsSync12(kept))
|
|
21550
20877
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
21551
20878
|
context.taken.push(id2);
|
|
21552
20879
|
return {
|
|
@@ -21578,7 +20905,7 @@ function planRow(scope, row2, context) {
|
|
|
21578
20905
|
entries: undefined,
|
|
21579
20906
|
absolute,
|
|
21580
20907
|
hash,
|
|
21581
|
-
mtime:
|
|
20908
|
+
mtime: statSync10(absolute).mtime.toISOString()
|
|
21582
20909
|
};
|
|
21583
20910
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
21584
20911
|
}
|
|
@@ -21589,7 +20916,7 @@ function countActions(actions) {
|
|
|
21589
20916
|
return counts;
|
|
21590
20917
|
}
|
|
21591
20918
|
function planDemotion(report4, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
21592
|
-
const temp = mkdtempSync3(
|
|
20919
|
+
const temp = mkdtempSync3(join39(tmpdir3(), "rafa-demote-"));
|
|
21593
20920
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
21594
20921
|
try {
|
|
21595
20922
|
const actions = report4.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
@@ -21680,7 +21007,7 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
21680
21007
|
}
|
|
21681
21008
|
function isDirectory4(path) {
|
|
21682
21009
|
try {
|
|
21683
|
-
return
|
|
21010
|
+
return statSync11(path).isDirectory();
|
|
21684
21011
|
} catch {
|
|
21685
21012
|
return false;
|
|
21686
21013
|
}
|
|
@@ -21708,7 +21035,7 @@ function verdictLine2(report4) {
|
|
|
21708
21035
|
return `${String(report4.rows.length)} file(s): ${parts.join(", ")}`;
|
|
21709
21036
|
}
|
|
21710
21037
|
function previousReport(path, context) {
|
|
21711
|
-
if (!
|
|
21038
|
+
if (!existsSync13(path))
|
|
21712
21039
|
return null;
|
|
21713
21040
|
const parsed = parseDemotionReport(readFileSync27(path, "utf8"));
|
|
21714
21041
|
if (parsed.report !== null)
|
|
@@ -21743,13 +21070,13 @@ function runWrite(context, scope) {
|
|
|
21743
21070
|
context.output.info(line);
|
|
21744
21071
|
}
|
|
21745
21072
|
function reviewedReport(scope) {
|
|
21746
|
-
if (!
|
|
21073
|
+
if (!existsSync13(scope.reportPath)) {
|
|
21747
21074
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
21748
21075
|
}
|
|
21749
21076
|
const parsed = parseDemotionReport(readFileSync27(scope.reportPath, "utf8"));
|
|
21750
21077
|
if (parsed.report === null) {
|
|
21751
|
-
const
|
|
21752
|
-
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...
|
|
21078
|
+
const lines = parsed.issues.map((issue2) => ` line ${String(issue2.line)} ${issue2.code}: ${issue2.message}`);
|
|
21079
|
+
throw new CommandExit(1, [`\u274C ${scope.reportPath} does not parse:`, ...lines].join(`
|
|
21753
21080
|
`));
|
|
21754
21081
|
}
|
|
21755
21082
|
if (parsed.report.status !== REVIEWED_STATUS) {
|
|
@@ -21787,16 +21114,16 @@ function runApply2(context, scope) {
|
|
|
21787
21114
|
actions: plan2.actions,
|
|
21788
21115
|
counts: plan2.counts
|
|
21789
21116
|
};
|
|
21790
|
-
const
|
|
21117
|
+
const lines = renderApply(result2);
|
|
21791
21118
|
if (plan2.counts.refused > 0) {
|
|
21792
|
-
throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP),
|
|
21119
|
+
throw new CommandExit(Math.min(plan2.counts.refused, REFUSED_ROW_CAP), lines.join(`
|
|
21793
21120
|
`));
|
|
21794
21121
|
}
|
|
21795
21122
|
if (context.outputMode === "json") {
|
|
21796
21123
|
context.output.result(result2);
|
|
21797
21124
|
return;
|
|
21798
21125
|
}
|
|
21799
|
-
for (const line of
|
|
21126
|
+
for (const line of lines)
|
|
21800
21127
|
context.output.info(line);
|
|
21801
21128
|
}
|
|
21802
21129
|
function runDemote(context, seams) {
|
|
@@ -21990,8 +21317,8 @@ var VERDICT_WORDS = Object.freeze({
|
|
|
21990
21317
|
pending: "checks pending",
|
|
21991
21318
|
none: "no checks"
|
|
21992
21319
|
});
|
|
21993
|
-
function counted3(count, noun,
|
|
21994
|
-
return `${String(count)} ${count === 1 ? noun :
|
|
21320
|
+
function counted3(count, noun, plural3 = `${noun}s`) {
|
|
21321
|
+
return `${String(count)} ${count === 1 ? noun : plural3}`;
|
|
21995
21322
|
}
|
|
21996
21323
|
function info2(text) {
|
|
21997
21324
|
return { level: "info", text };
|
|
@@ -22119,7 +21446,7 @@ function statusData(sections) {
|
|
|
22119
21446
|
}
|
|
22120
21447
|
|
|
22121
21448
|
// src/status/sections.ts
|
|
22122
|
-
import { existsSync as
|
|
21449
|
+
import { existsSync as existsSync14 } from "fs";
|
|
22123
21450
|
|
|
22124
21451
|
// src/status/blocked-count.ts
|
|
22125
21452
|
async function labelsCount(options) {
|
|
@@ -22379,7 +21706,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
22379
21706
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
22380
21707
|
}
|
|
22381
21708
|
function hasBoards(root, listing2) {
|
|
22382
|
-
if (
|
|
21709
|
+
if (existsSync14(positionFilePath(root)))
|
|
22383
21710
|
return true;
|
|
22384
21711
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
22385
21712
|
}
|
|
@@ -22510,6 +21837,10 @@ async function runStatus2(context, seams) {
|
|
|
22510
21837
|
const config = resolveProjectConfig(project, COMMAND_NAME14, (message) => {
|
|
22511
21838
|
context.output.warn(message);
|
|
22512
21839
|
});
|
|
21840
|
+
const warn3 = (message) => {
|
|
21841
|
+
context.output.warn(message);
|
|
21842
|
+
};
|
|
21843
|
+
await (seams.pull ?? pullBeforeRead)({ roots: { root: project.root, home: project.home }, warn: warn3 });
|
|
22513
21844
|
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
22514
21845
|
const sections = await read3({ root: project.root, home: project.home, config });
|
|
22515
21846
|
if (context.outputMode === "json") {
|