@open-tomato/rafa 0.38.0 → 0.39.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 +10 -6
- package/dist/PROMPT.md +3 -1
- package/dist/bundled/agents/loop-implementer.md +5 -0
- package/dist/bundled/agents/qa-bug-reporter.md +2 -2
- package/dist/bundled/operators/.claude-plugin/plugin.json +1 -1
- package/dist/bundled/operators/agents/rafa-stretch-analyst.md +7 -0
- package/dist/bundled/operators/agents/rafa-stretch-engineer.md +21 -45
- package/dist/bundled/operators/agents/rafa-stretch-watchtower.md +7 -0
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +1 -1
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +1 -1
- package/dist/bundled/skills/dev-planner/SKILL.md +2 -2
- package/dist/bundled/stretch/engineer-prompt-default.md +5 -0
- package/dist/cli.js +12841 -9199
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-74btmn1e.js → index-1r2b1052.js} +6 -5
- package/dist/{index-zdz0mp63.js → index-g4116t9m.js} +1 -1
- package/dist/{index-w9naz372.js → index-m0wt4nr1.js} +1 -1
- package/dist/{index-e3vhhfka.js → index-nwgwxfkt.js} +1598 -787
- package/dist/{index-mzcz139z.js → index-psv1sxkv.js} +4522 -1823
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/dist/plan-prompt.md +27 -0
- package/dist/plans/resolve-conflict-lockfile.md +2 -2
- package/dist/plans/resolve-conflict-manifest.md +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
trackerPathFor,
|
|
15
15
|
updateTrackerLine,
|
|
16
16
|
writeTrackerBlocker
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-g4116t9m.js";
|
|
18
18
|
import {
|
|
19
19
|
ACROSS_STORES_ORDER,
|
|
20
20
|
CORE_ADAPTER_REGISTRY,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
NOT_A_REPOSITORY,
|
|
36
36
|
PORT_VERSIONS,
|
|
37
37
|
RAFA_VERSION,
|
|
38
|
+
RECORD_EXTENSION,
|
|
38
39
|
SCOPE_DIR,
|
|
39
40
|
SKILL_FILE,
|
|
40
41
|
SQLITE_MIGRATIONS,
|
|
@@ -77,6 +78,7 @@ import {
|
|
|
77
78
|
oneLine,
|
|
78
79
|
padKind,
|
|
79
80
|
parseInstinct,
|
|
81
|
+
parseSessionRecord,
|
|
80
82
|
planFileName,
|
|
81
83
|
planFilePath,
|
|
82
84
|
planSchema,
|
|
@@ -93,6 +95,7 @@ import {
|
|
|
93
95
|
readScopes,
|
|
94
96
|
readSession,
|
|
95
97
|
readSessions,
|
|
98
|
+
readState,
|
|
96
99
|
readStoreMeta,
|
|
97
100
|
readStoreSchema,
|
|
98
101
|
readSwitch,
|
|
@@ -126,7 +129,7 @@ import {
|
|
|
126
129
|
writeNames,
|
|
127
130
|
writePositionFile,
|
|
128
131
|
writeSqliteStore
|
|
129
|
-
} from "./index-
|
|
132
|
+
} from "./index-1r2b1052.js";
|
|
130
133
|
import {
|
|
131
134
|
CHANGE_LEVELS,
|
|
132
135
|
CONFIG_DEFAULTS,
|
|
@@ -135,6 +138,7 @@ import {
|
|
|
135
138
|
FINDING_KINDS,
|
|
136
139
|
FINDING_SIGNALS,
|
|
137
140
|
PREREQUISITE_KINDS,
|
|
141
|
+
RELEASE_DEFAULTS,
|
|
138
142
|
REPORT_STATUSES,
|
|
139
143
|
SETTINGS,
|
|
140
144
|
activeOutput,
|
|
@@ -154,7 +158,7 @@ import {
|
|
|
154
158
|
requiredPrerequisite,
|
|
155
159
|
resolveTiers,
|
|
156
160
|
stampPrompt
|
|
157
|
-
} from "./index-
|
|
161
|
+
} from "./index-m0wt4nr1.js";
|
|
158
162
|
import {
|
|
159
163
|
promotable
|
|
160
164
|
} from "./index-d6d0942q.js";
|
|
@@ -5965,8 +5969,8 @@ async function report(args, repoRoot) {
|
|
|
5965
5969
|
}
|
|
5966
5970
|
|
|
5967
5971
|
// src/commands/plan/validate.ts
|
|
5968
|
-
import { readFileSync as
|
|
5969
|
-
import {
|
|
5972
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5973
|
+
import { resolve as resolve8 } from "path";
|
|
5970
5974
|
|
|
5971
5975
|
// src/board/readiness.ts
|
|
5972
5976
|
var SPEC_READY_LABEL = "spec:ready";
|
|
@@ -6311,23 +6315,425 @@ function storeRuleLine(file, problem) {
|
|
|
6311
6315
|
return `${file}:${String(problem.line)}: ${problem.kind}: ${problem.text}`;
|
|
6312
6316
|
}
|
|
6313
6317
|
|
|
6314
|
-
// src/commands/plan/
|
|
6315
|
-
|
|
6318
|
+
// src/commands/plan/store-check.ts
|
|
6319
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync9, renameSync as renameSync2 } from "fs";
|
|
6320
|
+
import { basename as basename5, dirname as dirname6, resolve as resolve7 } from "path";
|
|
6321
|
+
|
|
6322
|
+
// src/board/gate.ts
|
|
6323
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync8, renameSync, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
6324
|
+
import { basename as basename4, dirname as dirname5, join as join8, resolve as resolve6 } from "path";
|
|
6325
|
+
|
|
6326
|
+
// src/board/flags.ts
|
|
6327
|
+
var SPEC_FLAG = "--spec";
|
|
6328
|
+
var ISSUE_FLAG = "--issue";
|
|
6329
|
+
var NEXT_FLAG = "--next";
|
|
6330
|
+
var REFRESH_FLAG = "--refresh";
|
|
6331
|
+
var ROADMAP_FLAG = "--roadmap";
|
|
6332
|
+
var CLAIM_AHEAD_FLAG = "--claim-ahead";
|
|
6333
|
+
var DRY_RUN_FLAG = "--dry-run";
|
|
6334
|
+
var SKIP_REVIEW_FLAG = "--skip-review";
|
|
6335
|
+
var NO_COMMENT_FLAG = "--no-comment";
|
|
6336
|
+
var ACCEPT_REFS_FLAG = "--accept-refs";
|
|
6337
|
+
|
|
6338
|
+
// src/board/review-comment.ts
|
|
6339
|
+
var SPEC_REVIEW_MARKER = "<!-- rafa:spec-review v1 -->";
|
|
6340
|
+
var SPEC_REVIEW_COMMENT_NAME = "rafa:spec-review";
|
|
6341
|
+
var ONE_LINE = /\s+/gu;
|
|
6342
|
+
var COMMENT_REMEDY = "Close the gaps, label the issue `spec:ready` again, and plan from it again.";
|
|
6343
|
+
var ASSUMED_COMMENT_REMEDY = "Answer these in the spec where an assumption is wrong, and plan from it" + " again. No label changed: none of these gaps blocked planning.";
|
|
6344
|
+
var COMMENT_HISTORY = "This comment is edited in place each time the spec is reviewed.";
|
|
6345
|
+
function counted2(count, noun) {
|
|
6346
|
+
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
6347
|
+
}
|
|
6348
|
+
function gapItem(gap) {
|
|
6349
|
+
return `- **${gap.heading.replace(ONE_LINE, " ").trim()}** \u2014 ${gap.what.replace(ONE_LINE, " ").trim()}`;
|
|
6350
|
+
}
|
|
6351
|
+
function assumedGapItem(gap) {
|
|
6352
|
+
return gap.assumption === null ? gapItem(gap) : `${gapItem(gap)}
|
|
6353
|
+
- Planned under: ${gap.assumption.replace(ONE_LINE, " ").trim()}`;
|
|
6354
|
+
}
|
|
6355
|
+
function requireGaps(gaps) {
|
|
6356
|
+
if (gaps.length === 0) {
|
|
6357
|
+
throw new TypeError("board review comment: no gap was named, and there is no comment to write");
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
function commentBody(found, items, remedy) {
|
|
6361
|
+
return [SPEC_REVIEW_MARKER, ...found, "", ...items, "", remedy, "", COMMENT_HISTORY, ""].join(`
|
|
6362
|
+
`);
|
|
6363
|
+
}
|
|
6364
|
+
function specReviewCommentBody(gaps) {
|
|
6365
|
+
requireGaps(gaps);
|
|
6366
|
+
return commentBody([`**rafa reviewed this spec before planning and found ${counted2(gaps.length, "gap")}.**`, "No plan was written."], gaps.map(gapItem), COMMENT_REMEDY);
|
|
6367
|
+
}
|
|
6368
|
+
function assumedReviewCommentBody(gaps) {
|
|
6369
|
+
requireGaps(gaps);
|
|
6370
|
+
return commentBody([
|
|
6371
|
+
`**rafa reviewed this spec before planning and found ${counted2(gaps.length, "gap")}, none of them blocking.**`,
|
|
6372
|
+
"The plan was written under the assumptions below."
|
|
6373
|
+
], gaps.map(assumedGapItem), ASSUMED_COMMENT_REMEDY);
|
|
6374
|
+
}
|
|
6375
|
+
function specReviewComments(comments) {
|
|
6376
|
+
return [...comments].reverse().filter((comment) => comment.body.includes(SPEC_REVIEW_MARKER));
|
|
6377
|
+
}
|
|
6378
|
+
function ignoredReviewCommentMessage(comment, reading, repo) {
|
|
6379
|
+
const who = `was written by ${reading.login}, ${trustRefusalClause(repo, reading)}`;
|
|
6380
|
+
return `the ${SPEC_REVIEW_COMMENT_NAME} comment ${comment.id} ${who};` + " it was left alone and the gaps went in a comment of their own";
|
|
6381
|
+
}
|
|
6382
|
+
async function readTrustedSpecReviewComment(comments, trust) {
|
|
6383
|
+
const trustOf = memoisedTrust(trust);
|
|
6384
|
+
const ignored = [];
|
|
6385
|
+
for (const comment of specReviewComments(comments)) {
|
|
6386
|
+
const reading = await trustOf(comment.author);
|
|
6387
|
+
if (reading.trusted)
|
|
6388
|
+
return { comment, ignored };
|
|
6389
|
+
ignored.push({
|
|
6390
|
+
id: comment.id,
|
|
6391
|
+
author: comment.author,
|
|
6392
|
+
reason: ignoredReviewCommentMessage(comment, reading, trust.repo)
|
|
6393
|
+
});
|
|
6394
|
+
}
|
|
6395
|
+
return { comment: null, ignored };
|
|
6396
|
+
}
|
|
6397
|
+
async function writeSpecReviewComment(options) {
|
|
6398
|
+
const { body, issue: issue3, board, trust } = options;
|
|
6399
|
+
const found = await readTrustedSpecReviewComment(await board.comments(issue3), trust);
|
|
6400
|
+
const { comment: existing, ignored } = found;
|
|
6401
|
+
if (existing === null) {
|
|
6402
|
+
return { action: "posted", comment: await board.comment(issue3, body), ignored };
|
|
6403
|
+
}
|
|
6404
|
+
return { action: "edited", comment: await board.editComment(existing.id, body), ignored };
|
|
6405
|
+
}
|
|
6406
|
+
function memoisedTrust(trust) {
|
|
6407
|
+
const read = new Map;
|
|
6408
|
+
return (login) => {
|
|
6409
|
+
const taken = read.get(login) ?? readBoardTrust(trust, login);
|
|
6410
|
+
read.set(login, taken);
|
|
6411
|
+
return taken;
|
|
6412
|
+
};
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
// src/board/plan-field.ts
|
|
6416
|
+
var PLAN_BLOCK_KIND = "plan";
|
|
6417
|
+
var FIELD_NAME = /^[a-z][a-z-]*$/u;
|
|
6418
|
+
var ISSUE_FIELD = "issue";
|
|
6419
|
+
function unrecorded(answer, text, note) {
|
|
6420
|
+
return { answer, text, recorded: false, note };
|
|
6421
|
+
}
|
|
6422
|
+
function indentOf(line) {
|
|
6423
|
+
return /^\s*/u.exec(line)?.[0] ?? "";
|
|
6424
|
+
}
|
|
6425
|
+
function carriageOf(line) {
|
|
6426
|
+
return line.endsWith("\r") ? "\r" : "";
|
|
6427
|
+
}
|
|
6428
|
+
function fieldLine(field2) {
|
|
6429
|
+
if (!FIELD_NAME.test(field2)) {
|
|
6430
|
+
throw new TypeError(`board plan field: ${JSON.stringify(field2)} is no rafa:plan key this records`);
|
|
6431
|
+
}
|
|
6432
|
+
return new RegExp(`^(\\s*)${field2}\\s*:(.*)$`, "u");
|
|
6433
|
+
}
|
|
6434
|
+
function stampPlanField(plan, field2, value) {
|
|
6435
|
+
const pattern = fieldLine(field2);
|
|
6436
|
+
const line = `${field2}: ${value}`;
|
|
6437
|
+
const block = readRafaBlocks(plan).find((found) => found.kind === PLAN_BLOCK_KIND);
|
|
6438
|
+
if (block === undefined) {
|
|
6439
|
+
return unrecorded("no-block", plan, "the plan holds no rafa:plan block to record it in");
|
|
6440
|
+
}
|
|
6441
|
+
if (!block.closed) {
|
|
6442
|
+
return unrecorded("unclosed-block", plan, `the rafa:plan block at line ${String(block.span.first)} is never closed, so nothing was recorded in it`);
|
|
6443
|
+
}
|
|
6444
|
+
const lines = plan.split(`
|
|
6445
|
+
`);
|
|
6446
|
+
const opening = lines[block.span.first - 1] ?? "";
|
|
6447
|
+
const indent = indentOf(opening);
|
|
6448
|
+
const written = `${indent}${line}${carriageOf(opening)}`;
|
|
6449
|
+
for (let index = block.span.first;index < block.span.last - 1; index += 1) {
|
|
6450
|
+
const found = pattern.exec(lines[index] ?? "");
|
|
6451
|
+
if (found === null || found[1] !== indent)
|
|
6452
|
+
continue;
|
|
6453
|
+
if ((found[2] ?? "").trim() === value) {
|
|
6454
|
+
return { answer: "unchanged", text: plan, recorded: true, note: `the plan already records ${line}` };
|
|
6455
|
+
}
|
|
6456
|
+
const replaced = [...lines];
|
|
6457
|
+
replaced[index] = written;
|
|
6458
|
+
return {
|
|
6459
|
+
answer: "replaced",
|
|
6460
|
+
text: replaced.join(`
|
|
6461
|
+
`),
|
|
6462
|
+
recorded: true,
|
|
6463
|
+
note: `line ${String(index + 1)} of the plan now records ${line}`
|
|
6464
|
+
};
|
|
6465
|
+
}
|
|
6466
|
+
const at = block.span.last - 1;
|
|
6467
|
+
const stamped = [...lines.slice(0, at), written, ...lines.slice(at)];
|
|
6468
|
+
return {
|
|
6469
|
+
answer: "inserted",
|
|
6470
|
+
text: stamped.join(`
|
|
6471
|
+
`),
|
|
6472
|
+
recorded: true,
|
|
6473
|
+
note: `line ${String(at + 1)} of the plan now records ${line}`
|
|
6474
|
+
};
|
|
6475
|
+
}
|
|
6476
|
+
function issueFieldLine(issue3) {
|
|
6477
|
+
if (!Number.isSafeInteger(issue3) || issue3 < 1) {
|
|
6478
|
+
throw new TypeError(`board plan field: issue ${String(issue3)} is no issue number, expected a positive whole one`);
|
|
6479
|
+
}
|
|
6480
|
+
return `${ISSUE_FIELD}: ${JSON.stringify(String(issue3))}`;
|
|
6481
|
+
}
|
|
6482
|
+
function stampPlanIssue(plan, issue3) {
|
|
6483
|
+
return stampPlanField(plan, ISSUE_FIELD, JSON.stringify(String(issue3)));
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
// src/board/review-stamp.ts
|
|
6487
|
+
var REVIEW_FIELD = "review";
|
|
6488
|
+
var REVIEW_SKIPPED = "skipped";
|
|
6489
|
+
var REVIEW_MISSING = "missing";
|
|
6490
|
+
var REVIEW_ASSUMED = "assumed";
|
|
6491
|
+
var REVIEW_SKIPPED_LINE = `${REVIEW_FIELD}: ${REVIEW_SKIPPED}`;
|
|
6492
|
+
var REVIEW_MISSING_LINE = `${REVIEW_FIELD}: ${REVIEW_MISSING}`;
|
|
6493
|
+
var REVIEW_ASSUMED_LINE = `${REVIEW_FIELD}: ${REVIEW_ASSUMED}`;
|
|
6494
|
+
function stampReviewSkipped(plan) {
|
|
6495
|
+
return stampPlanField(plan, REVIEW_FIELD, REVIEW_SKIPPED);
|
|
6496
|
+
}
|
|
6497
|
+
function stampReviewMissing(plan) {
|
|
6498
|
+
return stampPlanField(plan, REVIEW_FIELD, REVIEW_MISSING);
|
|
6499
|
+
}
|
|
6500
|
+
function stampReviewAssumed(plan) {
|
|
6501
|
+
return stampPlanField(plan, REVIEW_FIELD, REVIEW_ASSUMED);
|
|
6502
|
+
}
|
|
6503
|
+
var ASSUMPTIONS_HEADING = "# Planned under assumptions";
|
|
6504
|
+
var ASSUMPTIONS_PREAMBLE = [
|
|
6505
|
+
"The spec review found gaps, none of them blocking, so this plan was",
|
|
6506
|
+
"written under the assumptions below. Each names the gap it was",
|
|
6507
|
+
"written for; closing that gap in the spec is what replaces the guess."
|
|
6508
|
+
];
|
|
6509
|
+
function assumptionsHeading(gaps) {
|
|
6510
|
+
const items = gaps.filter((gap) => gap.assumption !== null).map(assumedGapItem);
|
|
6511
|
+
if (items.length === 0) {
|
|
6512
|
+
throw new TypeError("board review stamp: no gap named an assumption, and there is no heading to open a plan with");
|
|
6513
|
+
}
|
|
6514
|
+
return [ASSUMPTIONS_HEADING, "", ...ASSUMPTIONS_PREAMBLE, "", ...items, "", ""].join(`
|
|
6515
|
+
`);
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
// src/board/gate.ts
|
|
6519
|
+
var SPEC_NOT_READY_EXIT = 3;
|
|
6520
|
+
var SPEC_NEEDS_WORK_LABEL = "spec:needs-work";
|
|
6521
|
+
function readGateFlags(args) {
|
|
6522
|
+
return {
|
|
6523
|
+
skipReview: args.includes(SKIP_REVIEW_FLAG),
|
|
6524
|
+
comment: !args.includes(NO_COMMENT_FLAG)
|
|
6525
|
+
};
|
|
6526
|
+
}
|
|
6527
|
+
function describeGap2(gap) {
|
|
6528
|
+
return ` \u2022 "${gap.heading}": ${gap.what}`;
|
|
6529
|
+
}
|
|
6530
|
+
function remedyFor(issue3) {
|
|
6531
|
+
return issue3 === null ? " Close the gaps in the spec, then plan from it again." : ` Close the gaps, label the issue ${SPEC_READY_LABEL} again, and plan from it again.`;
|
|
6532
|
+
}
|
|
6533
|
+
function specNotReadyMessage(source, reading, gaps, issue3 = null) {
|
|
6534
|
+
return [
|
|
6535
|
+
`\u274C ${source} is not ready to plan from: ${reading}`,
|
|
6536
|
+
...gaps.map(describeGap2),
|
|
6537
|
+
remedyFor(issue3)
|
|
6538
|
+
].join(`
|
|
6539
|
+
`);
|
|
6540
|
+
}
|
|
6541
|
+
function unreadReviewWarning(reading, planPath) {
|
|
6542
|
+
return `${reading}; ${planPath} reads as written, so the plan stands unreviewed`;
|
|
6543
|
+
}
|
|
6544
|
+
function assumedReviewWarning(reading, planPath) {
|
|
6545
|
+
return `${reading}, none of them blocking; ${planPath} is written under the assumptions the review named`;
|
|
6546
|
+
}
|
|
6547
|
+
function unreadReviewMessage(source, reading, planIssues) {
|
|
6548
|
+
return [
|
|
6549
|
+
`\u274C ${source}: no plan stands \u2014 ${reading}, and the plan the session wrote does not read as written:`,
|
|
6550
|
+
...planIssues.map((line) => ` ${line}`),
|
|
6551
|
+
" Plan from the spec again."
|
|
6552
|
+
].join(`
|
|
6553
|
+
`);
|
|
6554
|
+
}
|
|
6555
|
+
function isWrittenFile(repoRoot, path) {
|
|
6556
|
+
const full = resolve6(repoRoot, path);
|
|
6557
|
+
return existsSync11(full) && statSync4(full).isFile();
|
|
6558
|
+
}
|
|
6559
|
+
var NOT_READY_REASON = "the planner judged the spec not ready, so no plan stands.";
|
|
6560
|
+
var UNREAD_REASON = "no review came back and the plan does not read as written, so no plan stands.";
|
|
6561
|
+
var REJECTED_DIR = "rejected";
|
|
6562
|
+
function rejectedPath(path) {
|
|
6563
|
+
const parent = dirname5(path);
|
|
6564
|
+
return parent === "." ? join8(REJECTED_DIR, basename4(path)) : join8(parent, REJECTED_DIR, basename4(path));
|
|
6565
|
+
}
|
|
6566
|
+
function moveWritten(options, output, reason) {
|
|
6567
|
+
for (const path of [options.planPath, options.prerequisitesPath]) {
|
|
6568
|
+
if (!isWrittenFile(options.repoRoot, path))
|
|
6569
|
+
continue;
|
|
6570
|
+
const destination = rejectedPath(path);
|
|
6571
|
+
try {
|
|
6572
|
+
mkdirSync2(resolve6(options.repoRoot, dirname5(destination)), { recursive: true });
|
|
6573
|
+
renameSync(resolve6(options.repoRoot, path), resolve6(options.repoRoot, destination));
|
|
6574
|
+
output.info(`\uD83D\uDDC3 Moved ${path} to ${destination}: ${reason}`);
|
|
6575
|
+
} catch (error) {
|
|
6576
|
+
output.warn(`${path} was written against a refused review and could not be moved: ${messageOf(error)}`);
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
function readWrittenPlan(options) {
|
|
6581
|
+
const { repoRoot, planPath } = options;
|
|
6582
|
+
if (!isWrittenFile(repoRoot, planPath)) {
|
|
6583
|
+
return { reads: false, issues: [`${planPath}: the session wrote no plan there`] };
|
|
6584
|
+
}
|
|
6585
|
+
try {
|
|
6586
|
+
const { issues } = validatePlan(resolve6(repoRoot, planPath));
|
|
6587
|
+
return { reads: issues.length === 0, issues: issues.map((issue3) => issueLine(planPath, issue3)) };
|
|
6588
|
+
} catch (error) {
|
|
6589
|
+
return { reads: false, issues: [`${planPath}: ${messageOf(error)}`] };
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
function standOrRefuse(options, review, output) {
|
|
6593
|
+
const plan = readWrittenPlan(options);
|
|
6594
|
+
if (plan.reads) {
|
|
6595
|
+
output.warn(unreadReviewWarning(review.text, options.planPath));
|
|
6596
|
+
return "unread";
|
|
6597
|
+
}
|
|
6598
|
+
moveWritten(options, output, UNREAD_REASON);
|
|
6599
|
+
throw new CommandExit(SPEC_NOT_READY_EXIT, unreadReviewMessage(options.source, review.text, plan.issues));
|
|
6600
|
+
}
|
|
6601
|
+
function recordAssumptions(options, gaps, output) {
|
|
6602
|
+
const { repoRoot, planPath } = options;
|
|
6603
|
+
const unopened = (why) => {
|
|
6604
|
+
output.warn(`${planPath} was not opened under the review's assumptions: ${why}`);
|
|
6605
|
+
};
|
|
6606
|
+
if (!isWrittenFile(repoRoot, planPath)) {
|
|
6607
|
+
unopened("the session wrote no plan there");
|
|
6608
|
+
return;
|
|
6609
|
+
}
|
|
6610
|
+
const file = resolve6(repoRoot, planPath);
|
|
6611
|
+
let stamped;
|
|
6612
|
+
try {
|
|
6613
|
+
stamped = stampReviewAssumed(`${assumptionsHeading(gaps)}${readFileSync8(file, "utf8")}`);
|
|
6614
|
+
writeFileSync3(file, stamped.text, "utf8");
|
|
6615
|
+
} catch (error) {
|
|
6616
|
+
unopened(messageOf(error));
|
|
6617
|
+
return;
|
|
6618
|
+
}
|
|
6619
|
+
if (!stamped.recorded) {
|
|
6620
|
+
output.warn(`${planPath} opens under the review's assumptions and records no ${REVIEW_ASSUMED_LINE}: ${stamped.note}`);
|
|
6621
|
+
return;
|
|
6622
|
+
}
|
|
6623
|
+
output.info(`\uD83E\uDDED ${planPath} opens under the review's assumptions and records ${REVIEW_ASSUMED_LINE}.`);
|
|
6624
|
+
}
|
|
6625
|
+
async function publishGaps(issue3, gaps, body, output) {
|
|
6626
|
+
try {
|
|
6627
|
+
const write = await writeSpecReviewComment({
|
|
6628
|
+
issue: issue3.number,
|
|
6629
|
+
body: body(gaps),
|
|
6630
|
+
board: issue3.board,
|
|
6631
|
+
trust: issue3.trust
|
|
6632
|
+
});
|
|
6633
|
+
for (const passed2 of write.ignored)
|
|
6634
|
+
output.warn(passed2.reason);
|
|
6635
|
+
output.info(`\uD83D\uDCAC ${write.action === "posted" ? "Posted" : "Edited"} the review comment on issue #${String(issue3.number)}.`);
|
|
6636
|
+
} catch (error) {
|
|
6637
|
+
output.warn(`the review comment on issue #${String(issue3.number)} was not written: ${messageOf(error)}`);
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
async function swapLabels(issue3, output) {
|
|
6641
|
+
try {
|
|
6642
|
+
await issue3.board.swapLabels(issue3.number, SPEC_READY_LABEL, SPEC_NEEDS_WORK_LABEL);
|
|
6643
|
+
output.info(`\uD83C\uDFF7 Swapped ${SPEC_READY_LABEL} for ${SPEC_NEEDS_WORK_LABEL} on issue #${String(issue3.number)}.`);
|
|
6644
|
+
} catch (error) {
|
|
6645
|
+
output.warn(`${SPEC_READY_LABEL} was not swapped for ${SPEC_NEEDS_WORK_LABEL} on issue` + ` #${String(issue3.number)}: ${messageOf(error)}`);
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
function nothingBlocks(review) {
|
|
6649
|
+
return !review.gaps.some((gap) => gap.blocking);
|
|
6650
|
+
}
|
|
6651
|
+
async function planUnderAssumptions(options, review, output) {
|
|
6652
|
+
output.warn(assumedReviewWarning(review.text, options.planPath));
|
|
6653
|
+
recordAssumptions(options, review.gaps, output);
|
|
6654
|
+
const { issue: issue3 } = options;
|
|
6655
|
+
if (issue3 !== null) {
|
|
6656
|
+
if (options.comment) {
|
|
6657
|
+
await publishGaps(issue3, review.gaps, assumedReviewCommentBody, output);
|
|
6658
|
+
} else {
|
|
6659
|
+
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
return "assumed";
|
|
6663
|
+
}
|
|
6664
|
+
async function enforceSpecReview(options) {
|
|
6665
|
+
const { review, issue: issue3 } = options;
|
|
6666
|
+
if (review === undefined)
|
|
6667
|
+
return "unjudged";
|
|
6668
|
+
if (review.ready)
|
|
6669
|
+
return "ready";
|
|
6670
|
+
const output = options.output ?? activeOutput();
|
|
6671
|
+
if (review.answer !== "not-ready")
|
|
6672
|
+
return standOrRefuse(options, review, output);
|
|
6673
|
+
if (nothingBlocks(review))
|
|
6674
|
+
return planUnderAssumptions(options, review, output);
|
|
6675
|
+
moveWritten(options, output, NOT_READY_REASON);
|
|
6676
|
+
if (issue3 !== null) {
|
|
6677
|
+
if (options.comment) {
|
|
6678
|
+
await publishGaps(issue3, review.gaps, specReviewCommentBody, output);
|
|
6679
|
+
} else {
|
|
6680
|
+
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
6681
|
+
}
|
|
6682
|
+
await swapLabels(issue3, output);
|
|
6683
|
+
}
|
|
6684
|
+
throw new CommandExit(SPEC_NOT_READY_EXIT, specNotReadyMessage(options.source, review.text, review.gaps, issue3));
|
|
6685
|
+
}
|
|
6686
|
+
|
|
6687
|
+
// src/commands/plan/store-check.ts
|
|
6316
6688
|
function readFileOrNull(path) {
|
|
6317
|
-
return path !== null && isFile(path) ?
|
|
6689
|
+
return path !== null && isFile(path) ? readFileSync9(path, "utf8") : null;
|
|
6318
6690
|
}
|
|
6319
6691
|
function checkStoreRules(file, markdown) {
|
|
6320
6692
|
const prerequisitesPath = prerequisitesPathForPlan(file);
|
|
6321
6693
|
return findStoreRuleProblems({
|
|
6322
6694
|
plan: markdown,
|
|
6323
6695
|
prerequisites: readFileOrNull(prerequisitesPath),
|
|
6324
|
-
prerequisitesName:
|
|
6696
|
+
prerequisitesName: basename5(prerequisitesPath ?? "PREREQUISITES-<stub>.md")
|
|
6325
6697
|
});
|
|
6326
6698
|
}
|
|
6699
|
+
function moveRejected(repoRoot, path, output) {
|
|
6700
|
+
const full = resolve7(repoRoot, path);
|
|
6701
|
+
if (!isFile(full))
|
|
6702
|
+
return;
|
|
6703
|
+
const destination = rejectedPath(path);
|
|
6704
|
+
try {
|
|
6705
|
+
mkdirSync3(resolve7(repoRoot, dirname6(destination)), { recursive: true });
|
|
6706
|
+
renameSync2(full, resolve7(repoRoot, destination));
|
|
6707
|
+
output.info(`\uD83D\uDDC3 Moved ${path} to ${destination}: the plan breaks the effort-store rules.`);
|
|
6708
|
+
} catch (error) {
|
|
6709
|
+
output.warn(`${path} breaks the effort-store rules and could not be moved: ${messageOf(error)}`);
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
function enforceStoreRules(repoRoot, generated, output = activeOutput()) {
|
|
6713
|
+
const planFile = resolve7(repoRoot, generated.planPath);
|
|
6714
|
+
if (!isFile(planFile))
|
|
6715
|
+
return;
|
|
6716
|
+
const problems = checkStoreRules(planFile, readFileSync9(planFile, "utf8"));
|
|
6717
|
+
if (problems.length === 0)
|
|
6718
|
+
return;
|
|
6719
|
+
const prerequisites = generated.prerequisitesPath ?? prerequisitesPathForPlan(generated.planPath);
|
|
6720
|
+
for (const path of [generated.planPath, prerequisites]) {
|
|
6721
|
+
if (path !== null)
|
|
6722
|
+
moveRejected(repoRoot, path, output);
|
|
6723
|
+
}
|
|
6724
|
+
throw new CommandExit(1, [
|
|
6725
|
+
`\u274C ${generated.planPath}: ${plural(problems.length, "broken effort-store rule")};` + " rafa loop start would refuse this plan, so no plan stands.",
|
|
6726
|
+
...problems.map((problem) => ` ${storeRuleLine(generated.planPath, problem)}`)
|
|
6727
|
+
].join(`
|
|
6728
|
+
`));
|
|
6729
|
+
}
|
|
6730
|
+
|
|
6731
|
+
// src/commands/plan/validate.ts
|
|
6732
|
+
var USAGE = "rafa plan validate <file>";
|
|
6327
6733
|
function validatePlan(file) {
|
|
6328
6734
|
if (!isFile(file))
|
|
6329
6735
|
throw new CommandExit(1, `\u274C Plan file not found: ${file}`);
|
|
6330
|
-
const model = parsePlan(
|
|
6736
|
+
const model = parsePlan(readFileSync10(file, "utf8"));
|
|
6331
6737
|
return { file, stages: model.stages.length, tasks: countTasks(model.tasks), issues: model.issues };
|
|
6332
6738
|
}
|
|
6333
6739
|
function resolvedConfig(project, warn) {
|
|
@@ -6400,10 +6806,10 @@ function createPlanValidateCommand(workingDirectory = () => process.cwd()) {
|
|
|
6400
6806
|
outputs: ["text", "json"],
|
|
6401
6807
|
run: async (context) => {
|
|
6402
6808
|
const typed = expectOneArgument(context.args, USAGE);
|
|
6403
|
-
const file =
|
|
6809
|
+
const file = resolve8(workingDirectory(), typed);
|
|
6404
6810
|
const validation = validatePlan(file);
|
|
6405
6811
|
const { issues, stages, tasks } = validation;
|
|
6406
|
-
const markdown =
|
|
6812
|
+
const markdown = readFileSync10(file, "utf8");
|
|
6407
6813
|
const findings = checkRoster(context, markdown);
|
|
6408
6814
|
const { missingAgents, skillCollisions, unresolvedSkills } = findings;
|
|
6409
6815
|
const storeProblems = checkStoreRules(file, markdown);
|
|
@@ -6440,8 +6846,8 @@ function createPlanValidateCommand(workingDirectory = () => process.cwd()) {
|
|
|
6440
6846
|
var validate_default = createPlanValidateCommand();
|
|
6441
6847
|
|
|
6442
6848
|
// src/board/board-cache.ts
|
|
6443
|
-
import { mkdirSync as
|
|
6444
|
-
import { dirname as
|
|
6849
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
6850
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
6445
6851
|
|
|
6446
6852
|
// src/board/board-cache-native.ts
|
|
6447
6853
|
var NATIVE_ISSUE_SELECTION = [
|
|
@@ -6712,7 +7118,7 @@ function createGhBoardListing(options) {
|
|
|
6712
7118
|
}
|
|
6713
7119
|
|
|
6714
7120
|
// src/board/board-cache.ts
|
|
6715
|
-
var BOARD_CACHE_FILE =
|
|
7121
|
+
var BOARD_CACHE_FILE = join9(SCOPE_DIR, "cache", "board.json");
|
|
6716
7122
|
var BOARD_CACHE_VERSION = 1;
|
|
6717
7123
|
var NATIVE_MODE_KEY = Object.freeze({ mode: "native" });
|
|
6718
7124
|
var WATERMARK_ARGS = Object.freeze([
|
|
@@ -6739,7 +7145,7 @@ function modeOfFile(value) {
|
|
|
6739
7145
|
function readCache(path, mode) {
|
|
6740
7146
|
let payload;
|
|
6741
7147
|
try {
|
|
6742
|
-
payload = JSON.parse(
|
|
7148
|
+
payload = JSON.parse(readFileSync11(path, "utf8"));
|
|
6743
7149
|
} catch {
|
|
6744
7150
|
return null;
|
|
6745
7151
|
}
|
|
@@ -6774,10 +7180,10 @@ function fileOf(cache) {
|
|
|
6774
7180
|
function writeCache(path, cache) {
|
|
6775
7181
|
const temporary = `${path}.${String(process.pid)}.tmp`;
|
|
6776
7182
|
try {
|
|
6777
|
-
|
|
6778
|
-
|
|
7183
|
+
mkdirSync4(dirname7(path), { recursive: true });
|
|
7184
|
+
writeFileSync4(temporary, `${JSON.stringify(fileOf(cache))}
|
|
6779
7185
|
`);
|
|
6780
|
-
|
|
7186
|
+
renameSync3(temporary, path);
|
|
6781
7187
|
return true;
|
|
6782
7188
|
} catch {
|
|
6783
7189
|
return false;
|
|
@@ -6859,7 +7265,7 @@ function changesNothing(rows, changed) {
|
|
|
6859
7265
|
}
|
|
6860
7266
|
function createCachedBoardListing(options) {
|
|
6861
7267
|
const { gh, root, refresh = false, limit = BOARD_LISTING_LIMIT, mode = "labels" } = options;
|
|
6862
|
-
const path =
|
|
7268
|
+
const path = join9(root, BOARD_CACHE_FILE);
|
|
6863
7269
|
const full = createGhBoardListing({ gh, limit, mode });
|
|
6864
7270
|
return async () => {
|
|
6865
7271
|
const cache = refresh ? null : readCache(path, mode);
|
|
@@ -6881,7 +7287,7 @@ function invalidateRows(root, numbers) {
|
|
|
6881
7287
|
const bad = numbers.find((number) => !isIssueNumber(number));
|
|
6882
7288
|
if (bad !== undefined)
|
|
6883
7289
|
throw new RangeError(`not an issue number: ${String(bad)}`);
|
|
6884
|
-
const path =
|
|
7290
|
+
const path = join9(root, BOARD_CACHE_FILE);
|
|
6885
7291
|
const cache = readCache(path, "native");
|
|
6886
7292
|
if (cache === null)
|
|
6887
7293
|
return false;
|
|
@@ -7031,10 +7437,75 @@ var MAX_LISTED_CHANGES = 10;
|
|
|
7031
7437
|
var DEFAULT_REMOTE = "origin";
|
|
7032
7438
|
var BRANCH_REF_PREFIX = "refs/heads/";
|
|
7033
7439
|
var INDENT = " ";
|
|
7440
|
+
var UNTRACKED_PREFIX = "?? ";
|
|
7034
7441
|
function parseWorkingTree(stdout) {
|
|
7035
7442
|
const entries = stdout.split(`
|
|
7036
7443
|
`).map((line) => line.replace(/\r$/, "")).filter((line) => line.trim() !== "");
|
|
7037
|
-
|
|
7444
|
+
const tracked = entries.filter((line) => !line.startsWith(UNTRACKED_PREFIX));
|
|
7445
|
+
const untracked = entries.filter((line) => line.startsWith(UNTRACKED_PREFIX)).map((line) => unquoteGitPath(line.slice(UNTRACKED_PREFIX.length)));
|
|
7446
|
+
return {
|
|
7447
|
+
clean: entries.length === 0,
|
|
7448
|
+
entries: Object.freeze(entries),
|
|
7449
|
+
tracked: Object.freeze(tracked),
|
|
7450
|
+
untracked: Object.freeze(untracked)
|
|
7451
|
+
};
|
|
7452
|
+
}
|
|
7453
|
+
var C_ESCAPES = {
|
|
7454
|
+
a: 7,
|
|
7455
|
+
b: 8,
|
|
7456
|
+
f: 12,
|
|
7457
|
+
n: 10,
|
|
7458
|
+
r: 13,
|
|
7459
|
+
t: 9,
|
|
7460
|
+
v: 11,
|
|
7461
|
+
"\\": 92,
|
|
7462
|
+
'"': 34
|
|
7463
|
+
};
|
|
7464
|
+
var OCTAL_ESCAPE = /^[0-7]{3}$/;
|
|
7465
|
+
function unquoteGitPath(raw) {
|
|
7466
|
+
if (raw.length < 2 || !raw.startsWith('"') || !raw.endsWith('"'))
|
|
7467
|
+
return raw;
|
|
7468
|
+
const body = raw.slice(1, -1);
|
|
7469
|
+
const bytes = [];
|
|
7470
|
+
const encoder = new TextEncoder;
|
|
7471
|
+
for (let at = 0;at < body.length; at += 1) {
|
|
7472
|
+
const char = body.charAt(at);
|
|
7473
|
+
if (char !== "\\") {
|
|
7474
|
+
bytes.push(...encoder.encode(char));
|
|
7475
|
+
continue;
|
|
7476
|
+
}
|
|
7477
|
+
const octal = body.slice(at + 1, at + 4);
|
|
7478
|
+
if (OCTAL_ESCAPE.test(octal)) {
|
|
7479
|
+
bytes.push(Number.parseInt(octal, 8));
|
|
7480
|
+
at += 3;
|
|
7481
|
+
continue;
|
|
7482
|
+
}
|
|
7483
|
+
const escaped = C_ESCAPES[body.charAt(at + 1)];
|
|
7484
|
+
if (escaped === undefined) {
|
|
7485
|
+
bytes.push(...encoder.encode(char));
|
|
7486
|
+
continue;
|
|
7487
|
+
}
|
|
7488
|
+
bytes.push(escaped);
|
|
7489
|
+
at += 1;
|
|
7490
|
+
}
|
|
7491
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
7492
|
+
}
|
|
7493
|
+
function parseTreePaths(stdout) {
|
|
7494
|
+
return Object.freeze(stdout.split(`
|
|
7495
|
+
`).map((line) => line.replace(/\r$/, "")).filter((line) => line.trim() !== "").map(unquoteGitPath));
|
|
7496
|
+
}
|
|
7497
|
+
function untrackedIncoming(untracked, incoming) {
|
|
7498
|
+
const held = new Set(incoming);
|
|
7499
|
+
return Object.freeze(untracked.filter((path2) => path2.endsWith("/") ? incoming.some((file) => file.startsWith(path2)) : held.has(path2)));
|
|
7500
|
+
}
|
|
7501
|
+
function untrackedLeftLine(paths) {
|
|
7502
|
+
if (paths.length === 0)
|
|
7503
|
+
return null;
|
|
7504
|
+
const named = paths.slice(0, MAX_LISTED_CHANGES).map((path2) => shellQuote(path2));
|
|
7505
|
+
const hidden = paths.length - named.length;
|
|
7506
|
+
const more = hidden > 0 ? ` and ${hidden} more` : "";
|
|
7507
|
+
const which = paths.length === 1 ? "path" : "paths";
|
|
7508
|
+
return `Leaving the untracked ${which} the merge does not touch in place: ${named.join(", ")}${more}.`;
|
|
7038
7509
|
}
|
|
7039
7510
|
function parseWorktrees(stdout) {
|
|
7040
7511
|
const entries = [];
|
|
@@ -7097,14 +7568,23 @@ function noChecksRefusal(number) {
|
|
|
7097
7568
|
`)
|
|
7098
7569
|
};
|
|
7099
7570
|
}
|
|
7100
|
-
function
|
|
7101
|
-
|
|
7571
|
+
function heldUntracked(reading) {
|
|
7572
|
+
return reading.incoming === undefined ? reading.tree.untracked : untrackedIncoming(reading.tree.untracked, reading.incoming);
|
|
7573
|
+
}
|
|
7574
|
+
function dirtyTreeRefusal(tree, held) {
|
|
7575
|
+
const heldSet = new Set(held);
|
|
7576
|
+
const listed = tree.entries.filter((line) => !line.startsWith(UNTRACKED_PREFIX) || heldSet.has(unquoteGitPath(line.slice(UNTRACKED_PREFIX.length))));
|
|
7577
|
+
if (listed.length === 0)
|
|
7578
|
+
return null;
|
|
7579
|
+
const count = plural2(listed.length, "change", "changes");
|
|
7580
|
+
const untrackedNote = held.length === 0 ? [] : ["An untracked path listed here is one the merge brings a file onto."];
|
|
7102
7581
|
return {
|
|
7103
7582
|
reason: "dirty-tree",
|
|
7104
7583
|
message: [
|
|
7105
7584
|
`${REFUSAL_PREFIX}: the working tree has ${count}.`,
|
|
7106
7585
|
"Commit or stash them, then run rafa pr merge again.",
|
|
7107
|
-
...
|
|
7586
|
+
...untrackedNote,
|
|
7587
|
+
...listChanges(listed)
|
|
7108
7588
|
].join(`
|
|
7109
7589
|
`)
|
|
7110
7590
|
};
|
|
@@ -7168,8 +7648,9 @@ function worktreeRefusal(branch, held) {
|
|
|
7168
7648
|
};
|
|
7169
7649
|
}
|
|
7170
7650
|
function readMergeRefusal(reading) {
|
|
7171
|
-
|
|
7172
|
-
|
|
7651
|
+
const dirty = dirtyTreeRefusal(reading.tree, heldUntracked(reading));
|
|
7652
|
+
if (dirty !== null)
|
|
7653
|
+
return dirty;
|
|
7173
7654
|
if (reading.merge.mergeable !== "mergeable")
|
|
7174
7655
|
return mergeStateRefusal(reading);
|
|
7175
7656
|
const checks = checksRefusalOf(reading);
|
|
@@ -7406,7 +7887,7 @@ function readWholeNumber2(value, command, where) {
|
|
|
7406
7887
|
}
|
|
7407
7888
|
return value;
|
|
7408
7889
|
}
|
|
7409
|
-
function
|
|
7890
|
+
function readState2(value, command, where) {
|
|
7410
7891
|
const state = STATES.get(readString3(value, command, where));
|
|
7411
7892
|
if (state === undefined) {
|
|
7412
7893
|
refuse5(command, `${where} is ${describeValue(value)}, expected one of: ${[...STATES.keys()].join(", ")}`);
|
|
@@ -7435,7 +7916,7 @@ function readSummary(value, command, where) {
|
|
|
7435
7916
|
number: readWholeNumber2(pull["number"], command, `${where}.number`),
|
|
7436
7917
|
title: readString3(pull["title"], command, `${where}.title`),
|
|
7437
7918
|
url: readString3(pull["url"], command, `${where}.url`),
|
|
7438
|
-
state:
|
|
7919
|
+
state: readState2(pull["state"], command, `${where}.state`),
|
|
7439
7920
|
headRefName: readString3(pull["headRefName"], command, `${where}.headRefName`),
|
|
7440
7921
|
baseRefName: readString3(pull["baseRefName"], command, `${where}.baseRefName`),
|
|
7441
7922
|
author: readAuthor(pull["author"], command, `${where}.author`),
|
|
@@ -7715,7 +8196,7 @@ function ghAuthOkIn(cwd) {
|
|
|
7715
8196
|
var REMOTE = "origin";
|
|
7716
8197
|
var BRANCH_PREFIX2 = "feat/";
|
|
7717
8198
|
var INDENT2 = " ";
|
|
7718
|
-
var
|
|
8199
|
+
var UNTRACKED_PREFIX2 = "??";
|
|
7719
8200
|
var MAX_LISTED_CHANGES2 = 10;
|
|
7720
8201
|
var COUNT = /^\d+$/;
|
|
7721
8202
|
var DETACHED_HEAD = "HEAD";
|
|
@@ -7791,7 +8272,7 @@ function listChanges2(entries) {
|
|
|
7791
8272
|
return hidden > 0 ? [...shown, `${INDENT2}... and ${hidden} more`] : shown;
|
|
7792
8273
|
}
|
|
7793
8274
|
function trackedChanges(tree) {
|
|
7794
|
-
return Object.freeze(tree.entries.filter((entry) => !entry.startsWith(
|
|
8275
|
+
return Object.freeze(tree.entries.filter((entry) => !entry.startsWith(UNTRACKED_PREFIX2)));
|
|
7795
8276
|
}
|
|
7796
8277
|
function treeRefusal(tree, plan) {
|
|
7797
8278
|
const changes = trackedChanges(tree);
|
|
@@ -8483,8 +8964,8 @@ function exhaustedMessage(roadmap, skipped2) {
|
|
|
8483
8964
|
}
|
|
8484
8965
|
|
|
8485
8966
|
// src/board/setup.ts
|
|
8486
|
-
import { existsSync as existsSync13, lstatSync, mkdirSync as
|
|
8487
|
-
import { dirname as
|
|
8967
|
+
import { existsSync as existsSync13, lstatSync, mkdirSync as mkdirSync7, readFileSync as readFileSync14, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
8968
|
+
import { dirname as dirname9, join as join11 } from "path";
|
|
8488
8969
|
import { fileURLToPath } from "url";
|
|
8489
8970
|
|
|
8490
8971
|
// src/board/blocked.ts
|
|
@@ -8588,374 +9069,9 @@ function blockedFaultMessage(read) {
|
|
|
8588
9069
|
return `#${String(read.issue)} ${faultClause(read)}; ${REMEDY2}`;
|
|
8589
9070
|
}
|
|
8590
9071
|
|
|
8591
|
-
// src/board/gate.ts
|
|
8592
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync as renameSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
8593
|
-
import { basename as basename5, dirname as dirname6, join as join9, resolve as resolve7 } from "path";
|
|
8594
|
-
|
|
8595
|
-
// src/board/flags.ts
|
|
8596
|
-
var SPEC_FLAG = "--spec";
|
|
8597
|
-
var ISSUE_FLAG = "--issue";
|
|
8598
|
-
var NEXT_FLAG = "--next";
|
|
8599
|
-
var REFRESH_FLAG = "--refresh";
|
|
8600
|
-
var ROADMAP_FLAG = "--roadmap";
|
|
8601
|
-
var CLAIM_AHEAD_FLAG = "--claim-ahead";
|
|
8602
|
-
var DRY_RUN_FLAG = "--dry-run";
|
|
8603
|
-
var SKIP_REVIEW_FLAG = "--skip-review";
|
|
8604
|
-
var NO_COMMENT_FLAG = "--no-comment";
|
|
8605
|
-
var ACCEPT_REFS_FLAG = "--accept-refs";
|
|
8606
|
-
|
|
8607
|
-
// src/board/review-comment.ts
|
|
8608
|
-
var SPEC_REVIEW_MARKER = "<!-- rafa:spec-review v1 -->";
|
|
8609
|
-
var SPEC_REVIEW_COMMENT_NAME = "rafa:spec-review";
|
|
8610
|
-
var ONE_LINE = /\s+/gu;
|
|
8611
|
-
var COMMENT_REMEDY = "Close the gaps, label the issue `spec:ready` again, and plan from it again.";
|
|
8612
|
-
var ASSUMED_COMMENT_REMEDY = "Answer these in the spec where an assumption is wrong, and plan from it" + " again. No label changed: none of these gaps blocked planning.";
|
|
8613
|
-
var COMMENT_HISTORY = "This comment is edited in place each time the spec is reviewed.";
|
|
8614
|
-
function counted2(count, noun) {
|
|
8615
|
-
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
8616
|
-
}
|
|
8617
|
-
function gapItem(gap) {
|
|
8618
|
-
return `- **${gap.heading.replace(ONE_LINE, " ").trim()}** \u2014 ${gap.what.replace(ONE_LINE, " ").trim()}`;
|
|
8619
|
-
}
|
|
8620
|
-
function assumedGapItem(gap) {
|
|
8621
|
-
return gap.assumption === null ? gapItem(gap) : `${gapItem(gap)}
|
|
8622
|
-
- Planned under: ${gap.assumption.replace(ONE_LINE, " ").trim()}`;
|
|
8623
|
-
}
|
|
8624
|
-
function requireGaps(gaps) {
|
|
8625
|
-
if (gaps.length === 0) {
|
|
8626
|
-
throw new TypeError("board review comment: no gap was named, and there is no comment to write");
|
|
8627
|
-
}
|
|
8628
|
-
}
|
|
8629
|
-
function commentBody(found, items, remedy) {
|
|
8630
|
-
return [SPEC_REVIEW_MARKER, ...found, "", ...items, "", remedy, "", COMMENT_HISTORY, ""].join(`
|
|
8631
|
-
`);
|
|
8632
|
-
}
|
|
8633
|
-
function specReviewCommentBody(gaps) {
|
|
8634
|
-
requireGaps(gaps);
|
|
8635
|
-
return commentBody([`**rafa reviewed this spec before planning and found ${counted2(gaps.length, "gap")}.**`, "No plan was written."], gaps.map(gapItem), COMMENT_REMEDY);
|
|
8636
|
-
}
|
|
8637
|
-
function assumedReviewCommentBody(gaps) {
|
|
8638
|
-
requireGaps(gaps);
|
|
8639
|
-
return commentBody([
|
|
8640
|
-
`**rafa reviewed this spec before planning and found ${counted2(gaps.length, "gap")}, none of them blocking.**`,
|
|
8641
|
-
"The plan was written under the assumptions below."
|
|
8642
|
-
], gaps.map(assumedGapItem), ASSUMED_COMMENT_REMEDY);
|
|
8643
|
-
}
|
|
8644
|
-
function specReviewComments(comments) {
|
|
8645
|
-
return [...comments].reverse().filter((comment) => comment.body.includes(SPEC_REVIEW_MARKER));
|
|
8646
|
-
}
|
|
8647
|
-
function ignoredReviewCommentMessage(comment, reading2, repo) {
|
|
8648
|
-
const who = `was written by ${reading2.login}, ${trustRefusalClause(repo, reading2)}`;
|
|
8649
|
-
return `the ${SPEC_REVIEW_COMMENT_NAME} comment ${comment.id} ${who};` + " it was left alone and the gaps went in a comment of their own";
|
|
8650
|
-
}
|
|
8651
|
-
async function readTrustedSpecReviewComment(comments, trust) {
|
|
8652
|
-
const trustOf = memoisedTrust(trust);
|
|
8653
|
-
const ignored = [];
|
|
8654
|
-
for (const comment of specReviewComments(comments)) {
|
|
8655
|
-
const reading2 = await trustOf(comment.author);
|
|
8656
|
-
if (reading2.trusted)
|
|
8657
|
-
return { comment, ignored };
|
|
8658
|
-
ignored.push({
|
|
8659
|
-
id: comment.id,
|
|
8660
|
-
author: comment.author,
|
|
8661
|
-
reason: ignoredReviewCommentMessage(comment, reading2, trust.repo)
|
|
8662
|
-
});
|
|
8663
|
-
}
|
|
8664
|
-
return { comment: null, ignored };
|
|
8665
|
-
}
|
|
8666
|
-
async function writeSpecReviewComment(options) {
|
|
8667
|
-
const { body, issue: issue3, board, trust } = options;
|
|
8668
|
-
const found = await readTrustedSpecReviewComment(await board.comments(issue3), trust);
|
|
8669
|
-
const { comment: existing, ignored } = found;
|
|
8670
|
-
if (existing === null) {
|
|
8671
|
-
return { action: "posted", comment: await board.comment(issue3, body), ignored };
|
|
8672
|
-
}
|
|
8673
|
-
return { action: "edited", comment: await board.editComment(existing.id, body), ignored };
|
|
8674
|
-
}
|
|
8675
|
-
function memoisedTrust(trust) {
|
|
8676
|
-
const read = new Map;
|
|
8677
|
-
return (login) => {
|
|
8678
|
-
const taken = read.get(login) ?? readBoardTrust(trust, login);
|
|
8679
|
-
read.set(login, taken);
|
|
8680
|
-
return taken;
|
|
8681
|
-
};
|
|
8682
|
-
}
|
|
8683
|
-
|
|
8684
|
-
// src/board/plan-field.ts
|
|
8685
|
-
var PLAN_BLOCK_KIND = "plan";
|
|
8686
|
-
var FIELD_NAME = /^[a-z][a-z-]*$/u;
|
|
8687
|
-
var ISSUE_FIELD = "issue";
|
|
8688
|
-
function unrecorded(answer, text, note) {
|
|
8689
|
-
return { answer, text, recorded: false, note };
|
|
8690
|
-
}
|
|
8691
|
-
function indentOf(line) {
|
|
8692
|
-
return /^\s*/u.exec(line)?.[0] ?? "";
|
|
8693
|
-
}
|
|
8694
|
-
function carriageOf(line) {
|
|
8695
|
-
return line.endsWith("\r") ? "\r" : "";
|
|
8696
|
-
}
|
|
8697
|
-
function fieldLine(field2) {
|
|
8698
|
-
if (!FIELD_NAME.test(field2)) {
|
|
8699
|
-
throw new TypeError(`board plan field: ${JSON.stringify(field2)} is no rafa:plan key this records`);
|
|
8700
|
-
}
|
|
8701
|
-
return new RegExp(`^(\\s*)${field2}\\s*:(.*)$`, "u");
|
|
8702
|
-
}
|
|
8703
|
-
function stampPlanField(plan, field2, value) {
|
|
8704
|
-
const pattern = fieldLine(field2);
|
|
8705
|
-
const line = `${field2}: ${value}`;
|
|
8706
|
-
const block = readRafaBlocks(plan).find((found) => found.kind === PLAN_BLOCK_KIND);
|
|
8707
|
-
if (block === undefined) {
|
|
8708
|
-
return unrecorded("no-block", plan, "the plan holds no rafa:plan block to record it in");
|
|
8709
|
-
}
|
|
8710
|
-
if (!block.closed) {
|
|
8711
|
-
return unrecorded("unclosed-block", plan, `the rafa:plan block at line ${String(block.span.first)} is never closed, so nothing was recorded in it`);
|
|
8712
|
-
}
|
|
8713
|
-
const lines = plan.split(`
|
|
8714
|
-
`);
|
|
8715
|
-
const opening = lines[block.span.first - 1] ?? "";
|
|
8716
|
-
const indent = indentOf(opening);
|
|
8717
|
-
const written = `${indent}${line}${carriageOf(opening)}`;
|
|
8718
|
-
for (let index = block.span.first;index < block.span.last - 1; index += 1) {
|
|
8719
|
-
const found = pattern.exec(lines[index] ?? "");
|
|
8720
|
-
if (found === null || found[1] !== indent)
|
|
8721
|
-
continue;
|
|
8722
|
-
if ((found[2] ?? "").trim() === value) {
|
|
8723
|
-
return { answer: "unchanged", text: plan, recorded: true, note: `the plan already records ${line}` };
|
|
8724
|
-
}
|
|
8725
|
-
const replaced = [...lines];
|
|
8726
|
-
replaced[index] = written;
|
|
8727
|
-
return {
|
|
8728
|
-
answer: "replaced",
|
|
8729
|
-
text: replaced.join(`
|
|
8730
|
-
`),
|
|
8731
|
-
recorded: true,
|
|
8732
|
-
note: `line ${String(index + 1)} of the plan now records ${line}`
|
|
8733
|
-
};
|
|
8734
|
-
}
|
|
8735
|
-
const at = block.span.last - 1;
|
|
8736
|
-
const stamped = [...lines.slice(0, at), written, ...lines.slice(at)];
|
|
8737
|
-
return {
|
|
8738
|
-
answer: "inserted",
|
|
8739
|
-
text: stamped.join(`
|
|
8740
|
-
`),
|
|
8741
|
-
recorded: true,
|
|
8742
|
-
note: `line ${String(at + 1)} of the plan now records ${line}`
|
|
8743
|
-
};
|
|
8744
|
-
}
|
|
8745
|
-
function issueFieldLine(issue3) {
|
|
8746
|
-
if (!Number.isSafeInteger(issue3) || issue3 < 1) {
|
|
8747
|
-
throw new TypeError(`board plan field: issue ${String(issue3)} is no issue number, expected a positive whole one`);
|
|
8748
|
-
}
|
|
8749
|
-
return `${ISSUE_FIELD}: ${JSON.stringify(String(issue3))}`;
|
|
8750
|
-
}
|
|
8751
|
-
function stampPlanIssue(plan, issue3) {
|
|
8752
|
-
return stampPlanField(plan, ISSUE_FIELD, JSON.stringify(String(issue3)));
|
|
8753
|
-
}
|
|
8754
|
-
|
|
8755
|
-
// src/board/review-stamp.ts
|
|
8756
|
-
var REVIEW_FIELD = "review";
|
|
8757
|
-
var REVIEW_SKIPPED = "skipped";
|
|
8758
|
-
var REVIEW_MISSING = "missing";
|
|
8759
|
-
var REVIEW_ASSUMED = "assumed";
|
|
8760
|
-
var REVIEW_SKIPPED_LINE = `${REVIEW_FIELD}: ${REVIEW_SKIPPED}`;
|
|
8761
|
-
var REVIEW_MISSING_LINE = `${REVIEW_FIELD}: ${REVIEW_MISSING}`;
|
|
8762
|
-
var REVIEW_ASSUMED_LINE = `${REVIEW_FIELD}: ${REVIEW_ASSUMED}`;
|
|
8763
|
-
function stampReviewSkipped(plan) {
|
|
8764
|
-
return stampPlanField(plan, REVIEW_FIELD, REVIEW_SKIPPED);
|
|
8765
|
-
}
|
|
8766
|
-
function stampReviewMissing(plan) {
|
|
8767
|
-
return stampPlanField(plan, REVIEW_FIELD, REVIEW_MISSING);
|
|
8768
|
-
}
|
|
8769
|
-
function stampReviewAssumed(plan) {
|
|
8770
|
-
return stampPlanField(plan, REVIEW_FIELD, REVIEW_ASSUMED);
|
|
8771
|
-
}
|
|
8772
|
-
var ASSUMPTIONS_HEADING = "# Planned under assumptions";
|
|
8773
|
-
var ASSUMPTIONS_PREAMBLE = [
|
|
8774
|
-
"The spec review found gaps, none of them blocking, so this plan was",
|
|
8775
|
-
"written under the assumptions below. Each names the gap it was",
|
|
8776
|
-
"written for; closing that gap in the spec is what replaces the guess."
|
|
8777
|
-
];
|
|
8778
|
-
function assumptionsHeading(gaps) {
|
|
8779
|
-
const items = gaps.filter((gap) => gap.assumption !== null).map(assumedGapItem);
|
|
8780
|
-
if (items.length === 0) {
|
|
8781
|
-
throw new TypeError("board review stamp: no gap named an assumption, and there is no heading to open a plan with");
|
|
8782
|
-
}
|
|
8783
|
-
return [ASSUMPTIONS_HEADING, "", ...ASSUMPTIONS_PREAMBLE, "", ...items, "", ""].join(`
|
|
8784
|
-
`);
|
|
8785
|
-
}
|
|
8786
|
-
|
|
8787
|
-
// src/board/gate.ts
|
|
8788
|
-
var SPEC_NOT_READY_EXIT = 3;
|
|
8789
|
-
var SPEC_NEEDS_WORK_LABEL = "spec:needs-work";
|
|
8790
|
-
function readGateFlags(args) {
|
|
8791
|
-
return {
|
|
8792
|
-
skipReview: args.includes(SKIP_REVIEW_FLAG),
|
|
8793
|
-
comment: !args.includes(NO_COMMENT_FLAG)
|
|
8794
|
-
};
|
|
8795
|
-
}
|
|
8796
|
-
function describeGap2(gap) {
|
|
8797
|
-
return ` \u2022 "${gap.heading}": ${gap.what}`;
|
|
8798
|
-
}
|
|
8799
|
-
function remedyFor(issue3) {
|
|
8800
|
-
return issue3 === null ? " Close the gaps in the spec, then plan from it again." : ` Close the gaps, label the issue ${SPEC_READY_LABEL} again, and plan from it again.`;
|
|
8801
|
-
}
|
|
8802
|
-
function specNotReadyMessage(source, reading2, gaps, issue3 = null) {
|
|
8803
|
-
return [
|
|
8804
|
-
`\u274C ${source} is not ready to plan from: ${reading2}`,
|
|
8805
|
-
...gaps.map(describeGap2),
|
|
8806
|
-
remedyFor(issue3)
|
|
8807
|
-
].join(`
|
|
8808
|
-
`);
|
|
8809
|
-
}
|
|
8810
|
-
function unreadReviewWarning(reading2, planPath) {
|
|
8811
|
-
return `${reading2}; ${planPath} reads as written, so the plan stands unreviewed`;
|
|
8812
|
-
}
|
|
8813
|
-
function assumedReviewWarning(reading2, planPath) {
|
|
8814
|
-
return `${reading2}, none of them blocking; ${planPath} is written under the assumptions the review named`;
|
|
8815
|
-
}
|
|
8816
|
-
function unreadReviewMessage(source, reading2, planIssues) {
|
|
8817
|
-
return [
|
|
8818
|
-
`\u274C ${source}: no plan stands \u2014 ${reading2}, and the plan the session wrote does not read as written:`,
|
|
8819
|
-
...planIssues.map((line) => ` ${line}`),
|
|
8820
|
-
" Plan from the spec again."
|
|
8821
|
-
].join(`
|
|
8822
|
-
`);
|
|
8823
|
-
}
|
|
8824
|
-
function isWrittenFile(repoRoot, path2) {
|
|
8825
|
-
const full = resolve7(repoRoot, path2);
|
|
8826
|
-
return existsSync11(full) && statSync4(full).isFile();
|
|
8827
|
-
}
|
|
8828
|
-
var NOT_READY_REASON = "the planner judged the spec not ready, so no plan stands.";
|
|
8829
|
-
var UNREAD_REASON = "no review came back and the plan does not read as written, so no plan stands.";
|
|
8830
|
-
var REJECTED_DIR = "rejected";
|
|
8831
|
-
function rejectedPath(path2) {
|
|
8832
|
-
const parent = dirname6(path2);
|
|
8833
|
-
return parent === "." ? join9(REJECTED_DIR, basename5(path2)) : join9(parent, REJECTED_DIR, basename5(path2));
|
|
8834
|
-
}
|
|
8835
|
-
function moveWritten(options, output, reason) {
|
|
8836
|
-
for (const path2 of [options.planPath, options.prerequisitesPath]) {
|
|
8837
|
-
if (!isWrittenFile(options.repoRoot, path2))
|
|
8838
|
-
continue;
|
|
8839
|
-
const destination = rejectedPath(path2);
|
|
8840
|
-
try {
|
|
8841
|
-
mkdirSync3(resolve7(options.repoRoot, dirname6(destination)), { recursive: true });
|
|
8842
|
-
renameSync2(resolve7(options.repoRoot, path2), resolve7(options.repoRoot, destination));
|
|
8843
|
-
output.info(`\uD83D\uDDC3 Moved ${path2} to ${destination}: ${reason}`);
|
|
8844
|
-
} catch (error) {
|
|
8845
|
-
output.warn(`${path2} was written against a refused review and could not be moved: ${messageOf(error)}`);
|
|
8846
|
-
}
|
|
8847
|
-
}
|
|
8848
|
-
}
|
|
8849
|
-
function readWrittenPlan(options) {
|
|
8850
|
-
const { repoRoot, planPath } = options;
|
|
8851
|
-
if (!isWrittenFile(repoRoot, planPath)) {
|
|
8852
|
-
return { reads: false, issues: [`${planPath}: the session wrote no plan there`] };
|
|
8853
|
-
}
|
|
8854
|
-
try {
|
|
8855
|
-
const { issues } = validatePlan(resolve7(repoRoot, planPath));
|
|
8856
|
-
return { reads: issues.length === 0, issues: issues.map((issue3) => issueLine(planPath, issue3)) };
|
|
8857
|
-
} catch (error) {
|
|
8858
|
-
return { reads: false, issues: [`${planPath}: ${messageOf(error)}`] };
|
|
8859
|
-
}
|
|
8860
|
-
}
|
|
8861
|
-
function standOrRefuse(options, review, output) {
|
|
8862
|
-
const plan = readWrittenPlan(options);
|
|
8863
|
-
if (plan.reads) {
|
|
8864
|
-
output.warn(unreadReviewWarning(review.text, options.planPath));
|
|
8865
|
-
return "unread";
|
|
8866
|
-
}
|
|
8867
|
-
moveWritten(options, output, UNREAD_REASON);
|
|
8868
|
-
throw new CommandExit(SPEC_NOT_READY_EXIT, unreadReviewMessage(options.source, review.text, plan.issues));
|
|
8869
|
-
}
|
|
8870
|
-
function recordAssumptions(options, gaps, output) {
|
|
8871
|
-
const { repoRoot, planPath } = options;
|
|
8872
|
-
const unopened = (why) => {
|
|
8873
|
-
output.warn(`${planPath} was not opened under the review's assumptions: ${why}`);
|
|
8874
|
-
};
|
|
8875
|
-
if (!isWrittenFile(repoRoot, planPath)) {
|
|
8876
|
-
unopened("the session wrote no plan there");
|
|
8877
|
-
return;
|
|
8878
|
-
}
|
|
8879
|
-
const file = resolve7(repoRoot, planPath);
|
|
8880
|
-
let stamped;
|
|
8881
|
-
try {
|
|
8882
|
-
stamped = stampReviewAssumed(`${assumptionsHeading(gaps)}${readFileSync10(file, "utf8")}`);
|
|
8883
|
-
writeFileSync4(file, stamped.text, "utf8");
|
|
8884
|
-
} catch (error) {
|
|
8885
|
-
unopened(messageOf(error));
|
|
8886
|
-
return;
|
|
8887
|
-
}
|
|
8888
|
-
if (!stamped.recorded) {
|
|
8889
|
-
output.warn(`${planPath} opens under the review's assumptions and records no ${REVIEW_ASSUMED_LINE}: ${stamped.note}`);
|
|
8890
|
-
return;
|
|
8891
|
-
}
|
|
8892
|
-
output.info(`\uD83E\uDDED ${planPath} opens under the review's assumptions and records ${REVIEW_ASSUMED_LINE}.`);
|
|
8893
|
-
}
|
|
8894
|
-
async function publishGaps(issue3, gaps, body, output) {
|
|
8895
|
-
try {
|
|
8896
|
-
const write = await writeSpecReviewComment({
|
|
8897
|
-
issue: issue3.number,
|
|
8898
|
-
body: body(gaps),
|
|
8899
|
-
board: issue3.board,
|
|
8900
|
-
trust: issue3.trust
|
|
8901
|
-
});
|
|
8902
|
-
for (const passed2 of write.ignored)
|
|
8903
|
-
output.warn(passed2.reason);
|
|
8904
|
-
output.info(`\uD83D\uDCAC ${write.action === "posted" ? "Posted" : "Edited"} the review comment on issue #${String(issue3.number)}.`);
|
|
8905
|
-
} catch (error) {
|
|
8906
|
-
output.warn(`the review comment on issue #${String(issue3.number)} was not written: ${messageOf(error)}`);
|
|
8907
|
-
}
|
|
8908
|
-
}
|
|
8909
|
-
async function swapLabels(issue3, output) {
|
|
8910
|
-
try {
|
|
8911
|
-
await issue3.board.swapLabels(issue3.number, SPEC_READY_LABEL, SPEC_NEEDS_WORK_LABEL);
|
|
8912
|
-
output.info(`\uD83C\uDFF7 Swapped ${SPEC_READY_LABEL} for ${SPEC_NEEDS_WORK_LABEL} on issue #${String(issue3.number)}.`);
|
|
8913
|
-
} catch (error) {
|
|
8914
|
-
output.warn(`${SPEC_READY_LABEL} was not swapped for ${SPEC_NEEDS_WORK_LABEL} on issue` + ` #${String(issue3.number)}: ${messageOf(error)}`);
|
|
8915
|
-
}
|
|
8916
|
-
}
|
|
8917
|
-
function nothingBlocks(review) {
|
|
8918
|
-
return !review.gaps.some((gap) => gap.blocking);
|
|
8919
|
-
}
|
|
8920
|
-
async function planUnderAssumptions(options, review, output) {
|
|
8921
|
-
output.warn(assumedReviewWarning(review.text, options.planPath));
|
|
8922
|
-
recordAssumptions(options, review.gaps, output);
|
|
8923
|
-
const { issue: issue3 } = options;
|
|
8924
|
-
if (issue3 !== null) {
|
|
8925
|
-
if (options.comment) {
|
|
8926
|
-
await publishGaps(issue3, review.gaps, assumedReviewCommentBody, output);
|
|
8927
|
-
} else {
|
|
8928
|
-
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8931
|
-
return "assumed";
|
|
8932
|
-
}
|
|
8933
|
-
async function enforceSpecReview(options) {
|
|
8934
|
-
const { review, issue: issue3 } = options;
|
|
8935
|
-
if (review === undefined)
|
|
8936
|
-
return "unjudged";
|
|
8937
|
-
if (review.ready)
|
|
8938
|
-
return "ready";
|
|
8939
|
-
const output = options.output ?? activeOutput();
|
|
8940
|
-
if (review.answer !== "not-ready")
|
|
8941
|
-
return standOrRefuse(options, review, output);
|
|
8942
|
-
if (nothingBlocks(review))
|
|
8943
|
-
return planUnderAssumptions(options, review, output);
|
|
8944
|
-
moveWritten(options, output, NOT_READY_REASON);
|
|
8945
|
-
if (issue3 !== null) {
|
|
8946
|
-
if (options.comment) {
|
|
8947
|
-
await publishGaps(issue3, review.gaps, specReviewCommentBody, output);
|
|
8948
|
-
} else {
|
|
8949
|
-
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
8950
|
-
}
|
|
8951
|
-
await swapLabels(issue3, output);
|
|
8952
|
-
}
|
|
8953
|
-
throw new CommandExit(SPEC_NOT_READY_EXIT, specNotReadyMessage(options.source, review.text, review.gaps, issue3));
|
|
8954
|
-
}
|
|
8955
|
-
|
|
8956
9072
|
// src/board/issue.ts
|
|
8957
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
8958
|
-
import { dirname as
|
|
9073
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync12, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
9074
|
+
import { dirname as dirname8, resolve as resolve10 } from "path";
|
|
8959
9075
|
|
|
8960
9076
|
// src/refs/stamp.ts
|
|
8961
9077
|
import { createHash } from "crypto";
|
|
@@ -9179,7 +9295,8 @@ function readStamp(entry, index) {
|
|
|
9179
9295
|
const text = stringField(field2(entry, "text"), `${what}: text`);
|
|
9180
9296
|
const fingerprint = readFingerprint(entry, what);
|
|
9181
9297
|
if (!STAMPS_BY_KIND[refKind].includes(fingerprint.kind)) {
|
|
9182
|
-
|
|
9298
|
+
const article = /^[aeiou]/.test(refKind) ? "an" : "a";
|
|
9299
|
+
throw new RefsBlockError(`${what}: ${article} ${refKind} is not stamped ${fingerprintText(fingerprint)}`);
|
|
9183
9300
|
}
|
|
9184
9301
|
return Object.freeze({ kind: refKind, text, fingerprint });
|
|
9185
9302
|
}
|
|
@@ -9224,8 +9341,8 @@ function carryRefsBlock(copy, text) {
|
|
|
9224
9341
|
}
|
|
9225
9342
|
|
|
9226
9343
|
// src/board/previous-copy.ts
|
|
9227
|
-
import { linkSync, mkdirSync as
|
|
9228
|
-
import { basename as basename6, extname, join as join10, resolve as
|
|
9344
|
+
import { linkSync, mkdirSync as mkdirSync5, readdirSync as readdirSync6, statSync as statSync5, unlinkSync } from "fs";
|
|
9345
|
+
import { basename as basename6, extname, join as join10, resolve as resolve9 } from "path";
|
|
9229
9346
|
var PREFIX5 = "board previous copy";
|
|
9230
9347
|
var PREVIOUS_DIR_NAME = "previous";
|
|
9231
9348
|
var PREVIOUS_COPY_WARN_ABOVE = 50;
|
|
@@ -9264,14 +9381,14 @@ function linkUnderFreeName(source, directory, savedName, timestamp) {
|
|
|
9264
9381
|
}
|
|
9265
9382
|
function movePreviousCopy(options) {
|
|
9266
9383
|
const { repoRoot, specsDir, path: path2 } = options;
|
|
9267
|
-
const source =
|
|
9384
|
+
const source = resolve9(repoRoot, path2);
|
|
9268
9385
|
const directory = previousDir(specsDir);
|
|
9269
|
-
const absoluteDirectory =
|
|
9386
|
+
const absoluteDirectory = resolve9(repoRoot, directory);
|
|
9270
9387
|
const savedName = basename6(path2);
|
|
9271
9388
|
let name2;
|
|
9272
9389
|
try {
|
|
9273
9390
|
const timestamp = previousTimestamp(statSync5(source).mtime);
|
|
9274
|
-
|
|
9391
|
+
mkdirSync5(absoluteDirectory, { recursive: true });
|
|
9275
9392
|
name2 = linkUnderFreeName(source, absoluteDirectory, savedName, timestamp);
|
|
9276
9393
|
} catch (error) {
|
|
9277
9394
|
throw new Error(`${PREFIX5}: ${path2} could not be moved to ${directory}: ${messageOf(error)}`, { cause: error });
|
|
@@ -9293,7 +9410,7 @@ function isMissing(error) {
|
|
|
9293
9410
|
function countPreviousCopies(repoRoot, specsDir) {
|
|
9294
9411
|
const directory = previousDir(specsDir);
|
|
9295
9412
|
try {
|
|
9296
|
-
return readdirSync6(
|
|
9413
|
+
return readdirSync6(resolve9(repoRoot, directory), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(COPY_EXTENSION)).length;
|
|
9297
9414
|
} catch (error) {
|
|
9298
9415
|
if (isMissing(error))
|
|
9299
9416
|
return 0;
|
|
@@ -9497,13 +9614,13 @@ function readIfFile(file, what) {
|
|
|
9497
9614
|
throw new CommandExit(ISSUE_REFUSAL_EXIT, `${what} ${file} is not a file, and a spec is one`);
|
|
9498
9615
|
}
|
|
9499
9616
|
try {
|
|
9500
|
-
return
|
|
9617
|
+
return readFileSync12(file, "utf8");
|
|
9501
9618
|
} catch (error) {
|
|
9502
9619
|
throw new CommandExit(ISSUE_REFUSAL_EXIT, `${what} ${file} could not be read: ${messageOf(error)}`);
|
|
9503
9620
|
}
|
|
9504
9621
|
}
|
|
9505
9622
|
function readLocalNotes(repoRoot, specsDir, issue3) {
|
|
9506
|
-
return readIfFile(
|
|
9623
|
+
return readIfFile(resolve10(repoRoot, notesPath(specsDir, issue3)), "the local notes");
|
|
9507
9624
|
}
|
|
9508
9625
|
function snapshotDiffersMessage(path2, issue3) {
|
|
9509
9626
|
return `${path2} is a snapshot of issue #${String(issue3)} that no longer matches it:` + " the issue body, or the local notes appended to it, changed since the snapshot was written;" + ` pass ${REFRESH_FLAG} to plan from the issue as it reads now, or --spec=${path2} to plan from` + " the snapshot as it stands";
|
|
@@ -9519,7 +9636,7 @@ function writeSpecSnapshot(options) {
|
|
|
9519
9636
|
throw new CommandExit(ISSUE_REFUSAL_EXIT, notesCollisionMessage(path2, issue3.number));
|
|
9520
9637
|
}
|
|
9521
9638
|
const local = readLocalNotes(repoRoot, specsDir, issue3.number);
|
|
9522
|
-
const absolute =
|
|
9639
|
+
const absolute = resolve10(repoRoot, path2);
|
|
9523
9640
|
const existing = readIfFile(absolute, "the spec snapshot");
|
|
9524
9641
|
const text = carryRefsBlock(existing, snapshotText(issue3.body, local));
|
|
9525
9642
|
const written = {
|
|
@@ -9538,7 +9655,7 @@ function writeSpecSnapshot(options) {
|
|
|
9538
9655
|
}
|
|
9539
9656
|
const previous = action === "created" ? null : movePreviousCopy({ repoRoot, specsDir, path: path2 }).path;
|
|
9540
9657
|
try {
|
|
9541
|
-
|
|
9658
|
+
mkdirSync6(dirname8(absolute), { recursive: true });
|
|
9542
9659
|
writeFileSync5(absolute, text);
|
|
9543
9660
|
} catch (error) {
|
|
9544
9661
|
throw new Error(`${PREFIX6}: the snapshot ${path2} could not be written: ${messageOf(error)}`, { cause: error });
|
|
@@ -9557,7 +9674,7 @@ function rewriteAction(existing, body, notes, notesFile, refresh) {
|
|
|
9557
9674
|
}
|
|
9558
9675
|
|
|
9559
9676
|
// src/board/setup-config.ts
|
|
9560
|
-
import { readFileSync as
|
|
9677
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
9561
9678
|
function roadmapIssueIn(text, path2) {
|
|
9562
9679
|
return parseConfigText(text, path2).values.roadmapIssue ?? null;
|
|
9563
9680
|
}
|
|
@@ -9565,7 +9682,7 @@ function readRoadmapSetting(root) {
|
|
|
9565
9682
|
const path2 = configFilePath(root);
|
|
9566
9683
|
let text;
|
|
9567
9684
|
try {
|
|
9568
|
-
text =
|
|
9685
|
+
text = readFileSync13(path2, "utf8");
|
|
9569
9686
|
} catch (error) {
|
|
9570
9687
|
return { issue: null, text: "", problem: `${path2} could not be read: ${messageOf(error)}` };
|
|
9571
9688
|
}
|
|
@@ -9615,7 +9732,7 @@ function withRoadmapIssue(text, issue3) {
|
|
|
9615
9732
|
|
|
9616
9733
|
// src/board/setup.ts
|
|
9617
9734
|
var PREFIX7 = "board setup";
|
|
9618
|
-
var MODULE_DIR =
|
|
9735
|
+
var MODULE_DIR = dirname9(fileURLToPath(import.meta.url));
|
|
9619
9736
|
var ROADMAP_LABEL = `${GITHUB_LABELS.typePrefix}roadmap`;
|
|
9620
9737
|
var BOARD_LABELS = Object.freeze([
|
|
9621
9738
|
{
|
|
@@ -9779,7 +9896,7 @@ function readSpecTemplate(moduleDir = MODULE_DIR) {
|
|
|
9779
9896
|
if (!existsSync13(source)) {
|
|
9780
9897
|
throw new Error(`${PREFIX7}: the spec issue template is missing: no file at ${source}`);
|
|
9781
9898
|
}
|
|
9782
|
-
return
|
|
9899
|
+
return readFileSync14(source, "utf8");
|
|
9783
9900
|
}
|
|
9784
9901
|
function writeShippedFile(root, file) {
|
|
9785
9902
|
const path2 = join11(root, file.path);
|
|
@@ -9793,7 +9910,7 @@ function writeShippedFile(root, file) {
|
|
|
9793
9910
|
}
|
|
9794
9911
|
try {
|
|
9795
9912
|
const text = file.read();
|
|
9796
|
-
|
|
9913
|
+
mkdirSync7(dirname9(path2), { recursive: true });
|
|
9797
9914
|
writeFileSync6(path2, text, { encoding: "utf8", flag: "wx" });
|
|
9798
9915
|
return partOf("template", name2, "created", `written from ${file.source}`);
|
|
9799
9916
|
} catch (error) {
|
|
@@ -11935,14 +12052,14 @@ function withSubIssuesSummary(epic, row, claims, today) {
|
|
|
11935
12052
|
import { existsSync as existsSync15, readdirSync as readdirSync8 } from "fs";
|
|
11936
12053
|
|
|
11937
12054
|
// src/commands/doctor-refs.ts
|
|
11938
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
11939
|
-
import { join as join13, resolve as
|
|
12055
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync20, statSync as statSync9 } from "fs";
|
|
12056
|
+
import { join as join13, resolve as resolve13 } from "path";
|
|
11940
12057
|
|
|
11941
12058
|
// src/board/refs-gate.ts
|
|
11942
|
-
import { readFileSync as
|
|
12059
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
11943
12060
|
|
|
11944
12061
|
// src/refs/reading.ts
|
|
11945
|
-
import { readFileSync as
|
|
12062
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
11946
12063
|
|
|
11947
12064
|
// src/refs/extract.ts
|
|
11948
12065
|
var FILE_EXTENSIONS = new Set([
|
|
@@ -12218,7 +12335,7 @@ async function restampRefsText(options) {
|
|
|
12218
12335
|
return answer2(rows, options.copy, body, Object.freeze(stamps), true);
|
|
12219
12336
|
}
|
|
12220
12337
|
async function overFile(options, reading2) {
|
|
12221
|
-
const copy =
|
|
12338
|
+
const copy = readFileSync15(options.path, "utf8");
|
|
12222
12339
|
const { issue: issue3, verify, blockers } = options;
|
|
12223
12340
|
const read = await reading2(blockers === undefined ? { copy, issue: issue3, verify } : { copy, issue: issue3, verify, blockers });
|
|
12224
12341
|
if (read.changed)
|
|
@@ -12669,8 +12786,8 @@ function aheadNotClaimedWarning(ahead) {
|
|
|
12669
12786
|
}
|
|
12670
12787
|
|
|
12671
12788
|
// src/board/snapshot-settle.ts
|
|
12672
|
-
import { existsSync as existsSync14, readFileSync as
|
|
12673
|
-
import { resolve as
|
|
12789
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16, statSync as statSync8 } from "fs";
|
|
12790
|
+
import { resolve as resolve11 } from "path";
|
|
12674
12791
|
var PREFIX15 = "board snapshot settle";
|
|
12675
12792
|
var DATE_LENGTH = 10;
|
|
12676
12793
|
function savedCopyDate(savedAt) {
|
|
@@ -12689,7 +12806,7 @@ function readSavedCopy(absolute, path2) {
|
|
|
12689
12806
|
const stat = statSync8(absolute);
|
|
12690
12807
|
if (!stat.isFile())
|
|
12691
12808
|
return null;
|
|
12692
|
-
return { text:
|
|
12809
|
+
return { text: readFileSync16(absolute, "utf8"), savedAt: stat.mtime };
|
|
12693
12810
|
} catch (error) {
|
|
12694
12811
|
throw new Error(`${PREFIX15}: the saved copy ${path2} could not be read: ${messageOf(error)}`, { cause: error });
|
|
12695
12812
|
}
|
|
@@ -12707,7 +12824,7 @@ async function settleSpecSnapshot(options) {
|
|
|
12707
12824
|
const notes = notesPath(specsDir, issue3.number);
|
|
12708
12825
|
if (path2 === notes)
|
|
12709
12826
|
return write(refresh);
|
|
12710
|
-
const saved = readSavedCopy(
|
|
12827
|
+
const saved = readSavedCopy(resolve11(repoRoot, path2), path2);
|
|
12711
12828
|
if (saved === null)
|
|
12712
12829
|
return write(refresh);
|
|
12713
12830
|
const change = readSnapshotChange({
|
|
@@ -13459,7 +13576,7 @@ function memoiseVerifier(verify) {
|
|
|
13459
13576
|
}
|
|
13460
13577
|
async function acceptAll(options, acceptance, output) {
|
|
13461
13578
|
const verify = memoiseVerifier(options.verify);
|
|
13462
|
-
const before = await readRefsText({ copy:
|
|
13579
|
+
const before = await readRefsText({ copy: readFileSync17(options.path, "utf8"), issue: options.issue, verify });
|
|
13463
13580
|
const after = await restampCopyRefs({ path: options.path, issue: options.issue, verify });
|
|
13464
13581
|
const accepted = before.rows.filter(isRefusedRow);
|
|
13465
13582
|
listPassingRows(before.rows, output);
|
|
@@ -13489,12 +13606,12 @@ async function enforceRefsGate(options) {
|
|
|
13489
13606
|
|
|
13490
13607
|
// src/refs/outline-cache.ts
|
|
13491
13608
|
import { createHash as createHash2 } from "crypto";
|
|
13492
|
-
import { mkdirSync as
|
|
13493
|
-
import { join as join12, resolve as
|
|
13609
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync18, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
13610
|
+
import { join as join12, resolve as resolve12 } from "path";
|
|
13494
13611
|
var OUTLINE_CACHE_DIR = join12(SCOPE_DIR, "cache", "outline", "v2");
|
|
13495
13612
|
function readKept(path2) {
|
|
13496
13613
|
try {
|
|
13497
|
-
const payload = JSON.parse(
|
|
13614
|
+
const payload = JSON.parse(readFileSync18(path2, "utf8"));
|
|
13498
13615
|
return Array.isArray(payload) && payload.every((name2) => typeof name2 === "string") ? payload : null;
|
|
13499
13616
|
} catch {
|
|
13500
13617
|
return null;
|
|
@@ -13503,14 +13620,14 @@ function readKept(path2) {
|
|
|
13503
13620
|
function keep(path2, dir, names) {
|
|
13504
13621
|
const temporary = `${path2}.${String(process.pid)}.tmp`;
|
|
13505
13622
|
try {
|
|
13506
|
-
|
|
13623
|
+
mkdirSync8(dir, { recursive: true });
|
|
13507
13624
|
writeFileSync8(temporary, JSON.stringify(names));
|
|
13508
|
-
|
|
13625
|
+
renameSync4(temporary, path2);
|
|
13509
13626
|
} catch {}
|
|
13510
13627
|
}
|
|
13511
13628
|
function contentKey(root, file) {
|
|
13512
13629
|
try {
|
|
13513
|
-
return createHash2("sha256").update(
|
|
13630
|
+
return createHash2("sha256").update(readFileSync18(resolve12(root, file))).digest("hex");
|
|
13514
13631
|
} catch {
|
|
13515
13632
|
return null;
|
|
13516
13633
|
}
|
|
@@ -13535,7 +13652,7 @@ function withOutlineCache(outline, root) {
|
|
|
13535
13652
|
}
|
|
13536
13653
|
|
|
13537
13654
|
// src/commands/plan/refs-check.ts
|
|
13538
|
-
import { readFileSync as
|
|
13655
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
13539
13656
|
import path2 from "path";
|
|
13540
13657
|
var RAFA_PACKAGE_NAME = "@open-tomato/rafa";
|
|
13541
13658
|
var RAFA_ENTRY = "src/rafa.ts";
|
|
@@ -13543,7 +13660,7 @@ var CHECKOUT_ROSTER_TIMEOUT_MS = 30000;
|
|
|
13543
13660
|
var DESCRIBE_LINE = `bun ${RAFA_ENTRY} describe --output=json`;
|
|
13544
13661
|
async function coreRoster() {
|
|
13545
13662
|
const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
|
|
13546
|
-
import("./index-
|
|
13663
|
+
import("./index-psv1sxkv.js"),
|
|
13547
13664
|
import("./describe-pwhh0vws.js")
|
|
13548
13665
|
]);
|
|
13549
13666
|
return describeRegistry(CORE_REGISTRY, version);
|
|
@@ -13592,7 +13709,7 @@ function resultData(stdout) {
|
|
|
13592
13709
|
}
|
|
13593
13710
|
function isRafaPackage(repoRoot) {
|
|
13594
13711
|
try {
|
|
13595
|
-
const manifest = recordOf(JSON.parse(
|
|
13712
|
+
const manifest = recordOf(JSON.parse(readFileSync19(path2.join(repoRoot, "package.json"), "utf8")));
|
|
13596
13713
|
return manifest?.["name"] === RAFA_PACKAGE_NAME;
|
|
13597
13714
|
} catch {
|
|
13598
13715
|
return false;
|
|
@@ -13761,7 +13878,7 @@ async function readCopy(file, input, verify) {
|
|
|
13761
13878
|
const path3 = join13(input.specsDir, file.name);
|
|
13762
13879
|
const empty = { issue: file.issue, path: path3, suspect: 0, dangling: 0, unknown: 0 };
|
|
13763
13880
|
try {
|
|
13764
|
-
const copy =
|
|
13881
|
+
const copy = readFileSync20(resolve13(input.root, path3), "utf8");
|
|
13765
13882
|
const { rows } = await readRefsText({ copy, issue: file.issue, verify: await verify() });
|
|
13766
13883
|
const states = rows.map((row) => row.state);
|
|
13767
13884
|
return Object.freeze({
|
|
@@ -13781,7 +13898,7 @@ function total(copies, field4) {
|
|
|
13781
13898
|
async function readDoctorRefs(input, seams = {}) {
|
|
13782
13899
|
let files;
|
|
13783
13900
|
try {
|
|
13784
|
-
const listed = listCopies(
|
|
13901
|
+
const listed = listCopies(resolve13(input.root, input.specsDir));
|
|
13785
13902
|
const only = input.issues === undefined ? null : new Set(input.issues);
|
|
13786
13903
|
files = listed.filter((file) => only === null || only.has(file.issue));
|
|
13787
13904
|
} catch (error) {
|
|
@@ -17507,7 +17624,7 @@ function createIssueListCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
17507
17624
|
var list_default = createIssueListCommand();
|
|
17508
17625
|
|
|
17509
17626
|
// src/commands/release/status.ts
|
|
17510
|
-
import { existsSync as existsSync17, readdirSync as readdirSync9, readFileSync as
|
|
17627
|
+
import { existsSync as existsSync17, readdirSync as readdirSync9, readFileSync as readFileSync25 } from "fs";
|
|
17511
17628
|
import { join as join17 } from "path";
|
|
17512
17629
|
|
|
17513
17630
|
// src/effort/store/changes.ts
|
|
@@ -17606,15 +17723,15 @@ function readPlanChanges(repoRoot, planStub2) {
|
|
|
17606
17723
|
}
|
|
17607
17724
|
|
|
17608
17725
|
// src/release/audit.ts
|
|
17609
|
-
import { readFileSync as
|
|
17726
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
17610
17727
|
import { join as join15 } from "path";
|
|
17611
17728
|
|
|
17612
17729
|
// src/release/receipt.ts
|
|
17613
|
-
import { readFileSync as
|
|
17730
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
17614
17731
|
import { join as join14 } from "path";
|
|
17615
17732
|
|
|
17616
17733
|
// src/release/version.ts
|
|
17617
|
-
import { readFileSync as
|
|
17734
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync9 } from "fs";
|
|
17618
17735
|
var RELEASE_REMOTE = "origin";
|
|
17619
17736
|
var RELEASE_BASE_BRANCH = "main";
|
|
17620
17737
|
var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
@@ -17706,7 +17823,7 @@ function writeManifestVersion(path3, next) {
|
|
|
17706
17823
|
const unwritten = { path: path3, written: false, previous: null, version: next };
|
|
17707
17824
|
let text;
|
|
17708
17825
|
try {
|
|
17709
|
-
text =
|
|
17826
|
+
text = readFileSync21(path3, "utf8");
|
|
17710
17827
|
} catch (error) {
|
|
17711
17828
|
return { ...unwritten, problem: `${path3} could not be read: ${messageOf(error)}` };
|
|
17712
17829
|
}
|
|
@@ -17814,7 +17931,7 @@ function receiptProblem(verdict, version2, path3) {
|
|
|
17814
17931
|
function readReceiptVerdict(root, configured, version2) {
|
|
17815
17932
|
let text;
|
|
17816
17933
|
try {
|
|
17817
|
-
text =
|
|
17934
|
+
text = readFileSync22(join14(root, configured), "utf8");
|
|
17818
17935
|
} catch {
|
|
17819
17936
|
return null;
|
|
17820
17937
|
}
|
|
@@ -17915,7 +18032,7 @@ function auditSentence(finding) {
|
|
|
17915
18032
|
function readAudit(root, configured, tagged) {
|
|
17916
18033
|
let text;
|
|
17917
18034
|
try {
|
|
17918
|
-
text =
|
|
18035
|
+
text = readFileSync23(join15(root, configured), "utf8");
|
|
17919
18036
|
} catch {
|
|
17920
18037
|
return { path: configured, read: false, sections: 0, findings: [] };
|
|
17921
18038
|
}
|
|
@@ -18286,8 +18403,9 @@ function forecastSettle(input) {
|
|
|
18286
18403
|
}
|
|
18287
18404
|
|
|
18288
18405
|
// src/release/settle.ts
|
|
18289
|
-
import { readFileSync as
|
|
18406
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "fs";
|
|
18290
18407
|
import { join as join16 } from "path";
|
|
18408
|
+
var CHANGELOG_TITLE = "# Changelog";
|
|
18291
18409
|
function releaseCommitSubject(version2) {
|
|
18292
18410
|
return `chore: release ${version2}`;
|
|
18293
18411
|
}
|
|
@@ -18325,7 +18443,17 @@ function readSettle(git, tree, settings) {
|
|
|
18325
18443
|
return { ...basis, outcome: "failed", line: folded.line };
|
|
18326
18444
|
if (folded.result === null)
|
|
18327
18445
|
return { ...basis, outcome: "nothing" };
|
|
18328
|
-
|
|
18446
|
+
const changelog = changelogMissingAt(git, commit, settings.releaseChangelog);
|
|
18447
|
+
if (typeof changelog === "object")
|
|
18448
|
+
return { outcome: "unread", strategy: strategy.name, problem: changelog.problem };
|
|
18449
|
+
return { ...basis, outcome: "folded", version: folded.result.version, section: folded.result.section, changelogMissing: changelog };
|
|
18450
|
+
}
|
|
18451
|
+
function changelogMissingAt(git, commit, changelog) {
|
|
18452
|
+
const path3 = gitPathOf(changelog);
|
|
18453
|
+
const listed = git(["ls-tree", "--name-only", commit, "--", path3]);
|
|
18454
|
+
if (!listed.ok)
|
|
18455
|
+
return { problem: `${commit}:${path3} could not be listed: ${gitSaid(listed)}` };
|
|
18456
|
+
return listed.stdout.trim() === "";
|
|
18329
18457
|
}
|
|
18330
18458
|
function unbuilt(reading2, problem) {
|
|
18331
18459
|
return { ...reading2, outcome: "unbuilt", problem };
|
|
@@ -18342,18 +18470,24 @@ function writeVersion(root, reading2, versionFile) {
|
|
|
18342
18470
|
function writeChangelog(root, reading2, changelog) {
|
|
18343
18471
|
const path3 = join16(root, gitPathOf(changelog));
|
|
18344
18472
|
let text;
|
|
18473
|
+
let created = false;
|
|
18345
18474
|
try {
|
|
18346
|
-
text =
|
|
18475
|
+
text = readFileSync24(path3, "utf8");
|
|
18347
18476
|
} catch (error) {
|
|
18348
|
-
|
|
18477
|
+
if (errorCode(error) !== "ENOENT")
|
|
18478
|
+
return { problem: `${changelog} could not be read: ${messageOf(error)}` };
|
|
18479
|
+
text = `${CHANGELOG_TITLE}
|
|
18480
|
+
`;
|
|
18481
|
+
created = true;
|
|
18349
18482
|
}
|
|
18350
18483
|
const inserted = insertChangelogEntry(text, reading2.section);
|
|
18484
|
+
const verb = created ? "created" : "written";
|
|
18351
18485
|
try {
|
|
18352
18486
|
writeFileSync10(path3, inserted.text);
|
|
18353
18487
|
} catch (error) {
|
|
18354
|
-
return { problem: `${changelog} could not be
|
|
18488
|
+
return { problem: `${changelog} could not be ${verb}: ${messageOf(error)}` };
|
|
18355
18489
|
}
|
|
18356
|
-
return { point: inserted.point };
|
|
18490
|
+
return { point: inserted.point, created };
|
|
18357
18491
|
}
|
|
18358
18492
|
function commitRelease(git, reading2, settings) {
|
|
18359
18493
|
const files = [gitPathOf(settings.releaseVersionFile), gitPathOf(settings.releaseChangelog)];
|
|
@@ -18392,7 +18526,8 @@ function buildSettle(worktree, settings) {
|
|
|
18392
18526
|
outcome: "built",
|
|
18393
18527
|
release: committed.release,
|
|
18394
18528
|
deleted: reading2.fragments.map((each) => each.path),
|
|
18395
|
-
insertPoint: changelog.point
|
|
18529
|
+
insertPoint: changelog.point,
|
|
18530
|
+
createdChangelog: changelog.created ? settings.releaseChangelog : null
|
|
18396
18531
|
};
|
|
18397
18532
|
}
|
|
18398
18533
|
|
|
@@ -18485,6 +18620,7 @@ var CODE_FENCE4 = /^ {0,3}(?:```|~~~)/;
|
|
|
18485
18620
|
var RELEASE_TAG = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/;
|
|
18486
18621
|
var NUMERIC_IDENTIFIER = /^(?:0|[1-9]\d*)$/;
|
|
18487
18622
|
var DEFAULT_RELEASE_SEAMS = Object.freeze({});
|
|
18623
|
+
var MISSING_CHANGELOG_CELL = "missing; rafa release settle creates it";
|
|
18488
18624
|
function compareIdentifiers(left, right) {
|
|
18489
18625
|
const leftNumeric = NUMERIC_IDENTIFIER.test(left);
|
|
18490
18626
|
const rightNumeric = NUMERIC_IDENTIFIER.test(right);
|
|
@@ -18574,7 +18710,7 @@ function untaggedVersions(released, tags) {
|
|
|
18574
18710
|
}
|
|
18575
18711
|
function textOf(path3) {
|
|
18576
18712
|
try {
|
|
18577
|
-
return
|
|
18713
|
+
return readFileSync25(path3, "utf8");
|
|
18578
18714
|
} catch {
|
|
18579
18715
|
return null;
|
|
18580
18716
|
}
|
|
@@ -18604,11 +18740,12 @@ function readUntagged(root, configured, tags) {
|
|
|
18604
18740
|
path: configured,
|
|
18605
18741
|
released: [],
|
|
18606
18742
|
versions: [],
|
|
18743
|
+
missing: !existsSync17(resolved),
|
|
18607
18744
|
problem: `the changelog could not be read at ${resolved}`
|
|
18608
18745
|
};
|
|
18609
18746
|
}
|
|
18610
18747
|
const released = changelogVersions(text);
|
|
18611
|
-
return { path: configured, released, versions: untaggedVersions(released, tags), problem: null };
|
|
18748
|
+
return { path: configured, released, versions: untaggedVersions(released, tags), missing: false, problem: null };
|
|
18612
18749
|
}
|
|
18613
18750
|
function readBranch(git) {
|
|
18614
18751
|
const result = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -18676,6 +18813,8 @@ function tagCell(reading2) {
|
|
|
18676
18813
|
return reading2.latest === null ? `${NOTHING}, ${held}` : `${reading2.latest.tag}, ${held}`;
|
|
18677
18814
|
}
|
|
18678
18815
|
function untaggedCell(reading2) {
|
|
18816
|
+
if (reading2.missing)
|
|
18817
|
+
return MISSING_CHANGELOG_CELL;
|
|
18679
18818
|
if (reading2.problem !== null)
|
|
18680
18819
|
return UNREADABLE2;
|
|
18681
18820
|
if (reading2.released.length === 0)
|
|
@@ -18700,7 +18839,7 @@ function problemsOf(reading2) {
|
|
|
18700
18839
|
return [
|
|
18701
18840
|
reading2.versionFile.problem,
|
|
18702
18841
|
reading2.tags.problem,
|
|
18703
|
-
reading2.untagged.problem,
|
|
18842
|
+
reading2.untagged.missing ? null : reading2.untagged.problem,
|
|
18704
18843
|
reading2.plan.problem,
|
|
18705
18844
|
...reading2.waiting.problems
|
|
18706
18845
|
].filter((problem) => problem !== null);
|
|
@@ -18714,7 +18853,7 @@ function renderStatus(reading2) {
|
|
|
18714
18853
|
untaggedCell(reading2.untagged),
|
|
18715
18854
|
pendingCell(reading2.plan),
|
|
18716
18855
|
waitingCell(reading2.waiting) ?? UNREADABLE2,
|
|
18717
|
-
auditCell(reading2.audit) ?? UNREADABLE2
|
|
18856
|
+
auditCell(reading2.audit) ?? (reading2.untagged.missing ? MISSING_CHANGELOG_CELL : UNREADABLE2)
|
|
18718
18857
|
];
|
|
18719
18858
|
const [versionLine, tagLine, untaggedLine, pendingLine, waitingLine, auditLine] = LABELS.map((label, index) => `${INDENT3}${label.padEnd(width)}${GAP}${cells[index] ?? ""}`);
|
|
18720
18859
|
const under = (line) => `${INDENT3}${" ".repeat(width)}${GAP}${line}`;
|
|
@@ -18834,7 +18973,7 @@ function createReleaseStatusCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
18834
18973
|
var status_default = createReleaseStatusCommand();
|
|
18835
18974
|
|
|
18836
18975
|
// src/project/bin-path.ts
|
|
18837
|
-
import { delimiter as delimiter2, isAbsolute as isAbsolute3, join as join18, resolve as
|
|
18976
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute3, join as join18, resolve as resolve14 } from "path";
|
|
18838
18977
|
var RAFA_BIN_DIR = join18(SCOPE_DIR, "bin");
|
|
18839
18978
|
var BUN_BIN_DIR = join18(".bun", "bin");
|
|
18840
18979
|
function exportLine(rafaBin) {
|
|
@@ -18853,7 +18992,7 @@ function canonical(path3, fs) {
|
|
|
18853
18992
|
try {
|
|
18854
18993
|
return fs.realpath(path3);
|
|
18855
18994
|
} catch {
|
|
18856
|
-
return
|
|
18995
|
+
return resolve14(path3);
|
|
18857
18996
|
}
|
|
18858
18997
|
}
|
|
18859
18998
|
function indexOf(entries, dir, fs) {
|
|
@@ -18883,21 +19022,21 @@ import {
|
|
|
18883
19022
|
chmodSync,
|
|
18884
19023
|
copyFileSync,
|
|
18885
19024
|
existsSync as existsSync19,
|
|
18886
|
-
mkdirSync as
|
|
19025
|
+
mkdirSync as mkdirSync9,
|
|
18887
19026
|
readdirSync as readdirSync11,
|
|
18888
|
-
readFileSync as
|
|
19027
|
+
readFileSync as readFileSync26,
|
|
18889
19028
|
realpathSync as realpathSync2,
|
|
18890
|
-
renameSync as
|
|
19029
|
+
renameSync as renameSync5,
|
|
18891
19030
|
rmSync as rmSync2,
|
|
18892
19031
|
statSync as statSync11,
|
|
18893
19032
|
symlinkSync
|
|
18894
19033
|
} from "fs";
|
|
18895
|
-
import { basename as basename7, delimiter as delimiter3, dirname as
|
|
19034
|
+
import { basename as basename7, delimiter as delimiter3, dirname as dirname10, join as join20, relative as relative6, resolve as resolve16 } from "path";
|
|
18896
19035
|
|
|
18897
19036
|
// src/start/runtime.ts
|
|
18898
19037
|
import { existsSync as existsSync18, readdirSync as readdirSync10, realpathSync, statSync as statSync10 } from "fs";
|
|
18899
19038
|
import { homedir as homedir5 } from "os";
|
|
18900
|
-
import { isAbsolute as isAbsolute4, join as join19, relative as relative5, resolve as
|
|
19039
|
+
import { isAbsolute as isAbsolute4, join as join19, relative as relative5, resolve as resolve15, sep } from "path";
|
|
18901
19040
|
|
|
18902
19041
|
// src/start/session.ts
|
|
18903
19042
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -19119,7 +19258,7 @@ function resolveRuntime(value, places) {
|
|
|
19119
19258
|
throw refusal2([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
|
|
19120
19259
|
}
|
|
19121
19260
|
const isVersion = VERSION_SHAPE2.test(value);
|
|
19122
|
-
const named3 = isVersion ? join19(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) :
|
|
19261
|
+
const named3 = isVersion ? join19(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve15(places.cwd, value);
|
|
19123
19262
|
refuseInsideSource(value, named3, places);
|
|
19124
19263
|
const entry = entryOf2(value, named3, isVersion, places.home);
|
|
19125
19264
|
const real = realpathSync(entry);
|
|
@@ -19212,7 +19351,7 @@ function realOrResolved(path3) {
|
|
|
19212
19351
|
try {
|
|
19213
19352
|
return realpathSync(path3);
|
|
19214
19353
|
} catch {
|
|
19215
|
-
return
|
|
19354
|
+
return resolve15(path3);
|
|
19216
19355
|
}
|
|
19217
19356
|
}
|
|
19218
19357
|
async function runRuntime(entry, words, run) {
|
|
@@ -19360,17 +19499,17 @@ function runtimeRefusalLines(runtimeDir, version2) {
|
|
|
19360
19499
|
];
|
|
19361
19500
|
}
|
|
19362
19501
|
function listTrackers(repoRoot, planDir) {
|
|
19363
|
-
const dir =
|
|
19502
|
+
const dir = resolve16(repoRoot, planDir);
|
|
19364
19503
|
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) => join20(dir, entry)).filter((path3) => statSync11(path3).isFile()).map((path3) => relative6(repoRoot, path3));
|
|
19365
19504
|
}
|
|
19366
19505
|
function openTrackers(repoRoot, planDir) {
|
|
19367
19506
|
return listTrackers(repoRoot, planDir).flatMap((path3) => {
|
|
19368
|
-
const task = findNextTask(
|
|
19507
|
+
const task = findNextTask(readFileSync26(join20(repoRoot, path3), "utf8"));
|
|
19369
19508
|
return task ? [{ path: path3, task }] : [];
|
|
19370
19509
|
});
|
|
19371
19510
|
}
|
|
19372
19511
|
function readManifestVersion2(repoRoot) {
|
|
19373
|
-
const manifest = JSON.parse(
|
|
19512
|
+
const manifest = JSON.parse(readFileSync26(join20(repoRoot, "package.json"), "utf8"));
|
|
19374
19513
|
const record = typeof manifest === "object" && manifest !== null ? manifest : {};
|
|
19375
19514
|
if (record["name"] !== name) {
|
|
19376
19515
|
throw new Error(`${join20(repoRoot, "package.json")} names ${JSON.stringify(record["name"])}, not ${name}: this is no rafa checkout`);
|
|
@@ -19382,7 +19521,7 @@ function readManifestVersion2(repoRoot) {
|
|
|
19382
19521
|
return version2;
|
|
19383
19522
|
}
|
|
19384
19523
|
function copyTree(from, to) {
|
|
19385
|
-
|
|
19524
|
+
mkdirSync9(to, { recursive: true });
|
|
19386
19525
|
let copied = 0;
|
|
19387
19526
|
const entries = readdirSync11(from, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
19388
19527
|
for (const entry of entries) {
|
|
@@ -19413,27 +19552,27 @@ function replaceTree(from, to) {
|
|
|
19413
19552
|
}
|
|
19414
19553
|
function swapIn(staging, to) {
|
|
19415
19554
|
if (!existsSync19(to)) {
|
|
19416
|
-
|
|
19555
|
+
renameSync5(staging, to);
|
|
19417
19556
|
return;
|
|
19418
19557
|
}
|
|
19419
19558
|
const outgoing = `${temporaryName(to)}.outgoing`;
|
|
19420
19559
|
rmSync2(outgoing, { recursive: true, force: true });
|
|
19421
|
-
|
|
19560
|
+
renameSync5(to, outgoing);
|
|
19422
19561
|
try {
|
|
19423
|
-
|
|
19562
|
+
renameSync5(staging, to);
|
|
19424
19563
|
} catch (err) {
|
|
19425
|
-
|
|
19564
|
+
renameSync5(outgoing, to);
|
|
19426
19565
|
throw err;
|
|
19427
19566
|
}
|
|
19428
19567
|
rmSync2(outgoing, { recursive: true, force: true });
|
|
19429
19568
|
}
|
|
19430
19569
|
function relink(linkPath, target) {
|
|
19431
|
-
|
|
19570
|
+
mkdirSync9(dirname10(linkPath), { recursive: true });
|
|
19432
19571
|
const temporary = temporaryName(linkPath);
|
|
19433
19572
|
rmSync2(temporary, { force: true });
|
|
19434
19573
|
try {
|
|
19435
19574
|
symlinkSync(target, temporary);
|
|
19436
|
-
|
|
19575
|
+
renameSync5(temporary, linkPath);
|
|
19437
19576
|
} catch (err) {
|
|
19438
19577
|
rmSync2(temporary, { force: true });
|
|
19439
19578
|
throw err;
|
|
@@ -19451,7 +19590,7 @@ function runBuild(repoRoot, lines) {
|
|
|
19451
19590
|
cwd: repoRoot,
|
|
19452
19591
|
env: {
|
|
19453
19592
|
...process.env,
|
|
19454
|
-
PATH: [
|
|
19593
|
+
PATH: [dirname10(process.execPath), process.env["PATH"] ?? ""].join(delimiter3)
|
|
19455
19594
|
},
|
|
19456
19595
|
stdin: "ignore",
|
|
19457
19596
|
stdout: stdio,
|
|
@@ -19488,7 +19627,7 @@ function runInstall(seams, options) {
|
|
|
19488
19627
|
log(`runtime root: ${runtimeRoot}`);
|
|
19489
19628
|
log(`link: ${linkPath}`);
|
|
19490
19629
|
const version2 = inStage("manifest", () => readManifestVersion2(repoRoot));
|
|
19491
|
-
const planDir = inStage("config", () =>
|
|
19630
|
+
const planDir = inStage("config", () => resolve16(repoRoot, loadConfig({ root: repoRoot, home }, {}, seams.warn).config.planDir));
|
|
19492
19631
|
log(`plan dir: ${planDir}`);
|
|
19493
19632
|
const open = inStage("trackers", () => openTrackers(repoRoot, planDir));
|
|
19494
19633
|
if (open.length > 0)
|
|
@@ -19536,14 +19675,14 @@ function replaceFile(source, target) {
|
|
|
19536
19675
|
try {
|
|
19537
19676
|
copyFileSync(source, temporary);
|
|
19538
19677
|
chmodSync(temporary, statSync11(source).mode & 4095);
|
|
19539
|
-
|
|
19678
|
+
renameSync5(temporary, target);
|
|
19540
19679
|
} catch (err) {
|
|
19541
19680
|
rmSync2(temporary, { force: true });
|
|
19542
19681
|
throw err;
|
|
19543
19682
|
}
|
|
19544
19683
|
}
|
|
19545
19684
|
function temporaryName(path3) {
|
|
19546
|
-
return join20(
|
|
19685
|
+
return join20(dirname10(path3), `.${basename7(path3)}.snapshot-${process.pid}`);
|
|
19547
19686
|
}
|
|
19548
19687
|
|
|
19549
19688
|
// src/commands/self-update.ts
|
|
@@ -19651,7 +19790,7 @@ function createSelfUpdateCommand(seams = DEFAULT_SELF_UPDATE_SEAMS) {
|
|
|
19651
19790
|
var self_update_default = createSelfUpdateCommand();
|
|
19652
19791
|
|
|
19653
19792
|
// src/commands/plan/list.ts
|
|
19654
|
-
import { existsSync as existsSync20, readdirSync as readdirSync12, readFileSync as
|
|
19793
|
+
import { existsSync as existsSync20, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
19655
19794
|
import { join as join21 } from "path";
|
|
19656
19795
|
var USAGE5 = "rafa plan list";
|
|
19657
19796
|
var OPEN_HINT = "Type it bare: rafa plan list --open";
|
|
@@ -19659,8 +19798,8 @@ function listing(dir, stub) {
|
|
|
19659
19798
|
const plan = join21(dir, planFileName(stub, false));
|
|
19660
19799
|
const trackerPath = join21(dir, planFileName(stub, true));
|
|
19661
19800
|
const tracker = isFile(trackerPath) ? trackerPath : null;
|
|
19662
|
-
const planModel = parsePlan(
|
|
19663
|
-
const counted4 = tracker === null ? planModel : parsePlan(
|
|
19801
|
+
const planModel = parsePlan(readFileSync27(plan, "utf8"));
|
|
19802
|
+
const counted4 = tracker === null ? planModel : parsePlan(readFileSync27(tracker, "utf8"));
|
|
19664
19803
|
return { stub, plan, tracker, tasks: countTasks(counted4.tasks), issues: planModel.issues.length };
|
|
19665
19804
|
}
|
|
19666
19805
|
function listPlans(plans) {
|
|
@@ -19849,7 +19988,7 @@ async function openNextRelations(context, seams) {
|
|
|
19849
19988
|
|
|
19850
19989
|
// src/commands/loop/loop-sessions.ts
|
|
19851
19990
|
import { execFileSync } from "child_process";
|
|
19852
|
-
import { readFileSync as
|
|
19991
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
19853
19992
|
import { join as join22, relative as relative7 } from "path";
|
|
19854
19993
|
|
|
19855
19994
|
// src/effort/store/task-finishes.ts
|
|
@@ -20006,15 +20145,20 @@ function readSessionChecklist(root, record) {
|
|
|
20006
20145
|
const file = [trackerPathFor(plan), plan].find((path3) => isFile(path3));
|
|
20007
20146
|
if (file === undefined)
|
|
20008
20147
|
return null;
|
|
20009
|
-
const content =
|
|
20148
|
+
const content = readFileSync28(file, "utf8");
|
|
20010
20149
|
return { file, tasks: parsePlan(content).tasks, lines: content.split(`
|
|
20011
20150
|
`) };
|
|
20012
20151
|
}
|
|
20013
|
-
function phaseNote(record) {
|
|
20014
|
-
return `(phase ${
|
|
20152
|
+
function phaseNote(record, counts = null) {
|
|
20153
|
+
return `(phase ${notedPhase(record, counts)})`;
|
|
20154
|
+
}
|
|
20155
|
+
function notedPhase(record, counts) {
|
|
20156
|
+
const phase = sessionPhase(record);
|
|
20157
|
+
const everyTaskTicked = counts !== null && counts.total > 0 && counts.done === counts.total;
|
|
20158
|
+
return phase === "task" && record.state === "running" && record.task === null && everyTaskTicked ? "wrap-up" : phase;
|
|
20015
20159
|
}
|
|
20016
20160
|
function phasedCounts(counts, record) {
|
|
20017
|
-
return `${counts.done}/${counts.total} done ${phaseNote(record)}, ${counts.blocked} blocked, ${counts.open} open`;
|
|
20161
|
+
return `${counts.done}/${counts.total} done ${phaseNote(record, counts)}, ${counts.blocked} blocked, ${counts.open} open`;
|
|
20018
20162
|
}
|
|
20019
20163
|
function checkboxAt(checklist, line) {
|
|
20020
20164
|
return checklist?.tasks.find((task) => task.lineNum === line - 1)?.status ?? null;
|
|
@@ -20155,7 +20299,7 @@ function readBoardBody(body) {
|
|
|
20155
20299
|
}
|
|
20156
20300
|
|
|
20157
20301
|
// src/board/codeowners.ts
|
|
20158
|
-
import { readFileSync as
|
|
20302
|
+
import { readFileSync as readFileSync29, statSync as statSync12 } from "fs";
|
|
20159
20303
|
import { join as join23 } from "path";
|
|
20160
20304
|
var CODEOWNERS_LOCATIONS = Object.freeze([
|
|
20161
20305
|
".github/CODEOWNERS",
|
|
@@ -20216,7 +20360,7 @@ function readCodeowners(root) {
|
|
|
20216
20360
|
return null;
|
|
20217
20361
|
let text;
|
|
20218
20362
|
try {
|
|
20219
|
-
text =
|
|
20363
|
+
text = readFileSync29(join23(root, path3), "utf8");
|
|
20220
20364
|
} catch (error) {
|
|
20221
20365
|
const why = error instanceof Error ? error.message : String(error);
|
|
20222
20366
|
throw new Error(`Cannot read ${path3}: ${why}`, { cause: error });
|
|
@@ -20990,12 +21134,12 @@ function pickerFor(followed, seams) {
|
|
|
20990
21134
|
}
|
|
20991
21135
|
|
|
20992
21136
|
// src/commands/pr/merge-cleanup.ts
|
|
20993
|
-
import { existsSync as existsSync22, readFileSync as
|
|
21137
|
+
import { existsSync as existsSync22, readFileSync as readFileSync30 } from "fs";
|
|
20994
21138
|
import { join as join24 } from "path";
|
|
20995
21139
|
|
|
20996
21140
|
// src/release/enabled.ts
|
|
20997
21141
|
import { statSync as statSync13 } from "fs";
|
|
20998
|
-
import { resolve as
|
|
21142
|
+
import { resolve as resolve17 } from "path";
|
|
20999
21143
|
function isRegularFile(full) {
|
|
21000
21144
|
try {
|
|
21001
21145
|
return statSync13(full).isFile();
|
|
@@ -21004,7 +21148,7 @@ function isRegularFile(full) {
|
|
|
21004
21148
|
}
|
|
21005
21149
|
}
|
|
21006
21150
|
function readFile(repoRoot, path3) {
|
|
21007
|
-
const resolved =
|
|
21151
|
+
const resolved = resolve17(repoRoot, path3);
|
|
21008
21152
|
return { path: path3, resolved, present: isRegularFile(resolved) };
|
|
21009
21153
|
}
|
|
21010
21154
|
function resolveReleaseEnabled(settings, repoRoot) {
|
|
@@ -21155,7 +21299,7 @@ function cleanUpAfterMerge(git, merged, report2) {
|
|
|
21155
21299
|
}
|
|
21156
21300
|
function textOf2(path3) {
|
|
21157
21301
|
try {
|
|
21158
|
-
return
|
|
21302
|
+
return readFileSync30(path3, "utf8");
|
|
21159
21303
|
} catch {
|
|
21160
21304
|
return "";
|
|
21161
21305
|
}
|
|
@@ -22588,8 +22732,8 @@ function wantsHint(flags) {
|
|
|
22588
22732
|
}
|
|
22589
22733
|
var DEFAULT_HINT_SEAMS = Object.freeze({});
|
|
22590
22734
|
function afterDelay(ms) {
|
|
22591
|
-
return new Promise((
|
|
22592
|
-
setTimeout(
|
|
22735
|
+
return new Promise((resolve18) => {
|
|
22736
|
+
setTimeout(resolve18, ms).unref();
|
|
22593
22737
|
});
|
|
22594
22738
|
}
|
|
22595
22739
|
function silenced() {
|
|
@@ -23131,8 +23275,8 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
23131
23275
|
import { homedir as homedir6 } from "os";
|
|
23132
23276
|
|
|
23133
23277
|
// src/notices/notices.ts
|
|
23134
|
-
import { mkdirSync as
|
|
23135
|
-
import { dirname as
|
|
23278
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync31, writeFileSync as writeFileSync11 } from "fs";
|
|
23279
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
23136
23280
|
var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
|
|
23137
23281
|
var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
|
|
23138
23282
|
var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
|
|
@@ -23145,7 +23289,7 @@ function isNoticeId(value) {
|
|
|
23145
23289
|
function readDismissed(home) {
|
|
23146
23290
|
let raw;
|
|
23147
23291
|
try {
|
|
23148
|
-
raw =
|
|
23292
|
+
raw = readFileSync31(noticesPath(home), "utf8");
|
|
23149
23293
|
} catch {
|
|
23150
23294
|
return [];
|
|
23151
23295
|
}
|
|
@@ -23164,7 +23308,7 @@ function readDismissed(home) {
|
|
|
23164
23308
|
function writeDismissed(home, ids) {
|
|
23165
23309
|
const all = NOTICE_IDS.filter((id2) => ids.includes(id2) || readDismissed(home).includes(id2));
|
|
23166
23310
|
const path3 = noticesPath(home);
|
|
23167
|
-
|
|
23311
|
+
mkdirSync10(dirname11(path3), { recursive: true });
|
|
23168
23312
|
writeFileSync11(path3, `${JSON.stringify({ dismissed: all }, null, 2)}
|
|
23169
23313
|
`);
|
|
23170
23314
|
}
|
|
@@ -23450,8 +23594,8 @@ function haltIfWrapUpMoved(options, seams = {}) {
|
|
|
23450
23594
|
|
|
23451
23595
|
// src/project/roots.ts
|
|
23452
23596
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
23453
|
-
import { accessSync, constants, readFileSync as
|
|
23454
|
-
import { isAbsolute as isAbsolute5, join as join26, resolve as
|
|
23597
|
+
import { accessSync, constants, readFileSync as readFileSync32, statSync as statSync14 } from "fs";
|
|
23598
|
+
import { isAbsolute as isAbsolute5, join as join26, resolve as resolve18 } from "path";
|
|
23455
23599
|
var REFUSED_ROOTS = Object.freeze([
|
|
23456
23600
|
{ path: "/", why: "is the filesystem root" },
|
|
23457
23601
|
{ path: "/var", why: "holds system state" },
|
|
@@ -23470,7 +23614,7 @@ var MONOREPO_MARKER_FILES = Object.freeze([
|
|
|
23470
23614
|
var PACKAGE_JSON = "package.json";
|
|
23471
23615
|
var DISK_ROOTS_FILE_SYSTEM = Object.freeze({
|
|
23472
23616
|
...DISK_FILE_SYSTEM,
|
|
23473
|
-
readFile: (path3) =>
|
|
23617
|
+
readFile: (path3) => readFileSync32(path3, "utf8"),
|
|
23474
23618
|
isDirectory: (path3) => statSync14(path3).isDirectory(),
|
|
23475
23619
|
assertWritable: (path3) => accessSync(path3, constants.W_OK)
|
|
23476
23620
|
});
|
|
@@ -23513,7 +23657,7 @@ function realOrSpelled(path3, fs) {
|
|
|
23513
23657
|
try {
|
|
23514
23658
|
return fs.realpath(path3);
|
|
23515
23659
|
} catch {
|
|
23516
|
-
return
|
|
23660
|
+
return resolve18(path3);
|
|
23517
23661
|
}
|
|
23518
23662
|
}
|
|
23519
23663
|
function namedRoots(home, fs) {
|
|
@@ -23555,7 +23699,7 @@ function writeRefusal(given, real, fs) {
|
|
|
23555
23699
|
}
|
|
23556
23700
|
function refusalOf(given, named3, fs) {
|
|
23557
23701
|
const resolved = resolveReal(given, fs);
|
|
23558
|
-
const byName2 = namedRefusal(given, resolved.real ??
|
|
23702
|
+
const byName2 = namedRefusal(given, resolved.real ?? resolve18(given), named3);
|
|
23559
23703
|
if (byName2 !== null)
|
|
23560
23704
|
return byName2;
|
|
23561
23705
|
if (resolved.real === null) {
|
|
@@ -23907,9 +24051,46 @@ function commitTaskWork(options) {
|
|
|
23907
24051
|
};
|
|
23908
24052
|
}
|
|
23909
24053
|
|
|
24054
|
+
// src/start/background-wait.ts
|
|
24055
|
+
var WAIT_LINE_CAP = 200;
|
|
24056
|
+
var FENCE_LINE5 = /^\s*(?:```|~~~)/;
|
|
24057
|
+
var WAIT_PHRASES = [
|
|
24058
|
+
/\b(?:running|runs|run)\s+in\s+(?:the\s+)?background\b/i,
|
|
24059
|
+
/\bwait(?:ing)?\s+(?:for|on)\s+(?:the|a|its)\s+(?:completion\s+)?notification\b/i,
|
|
24060
|
+
/\brun_in_background\b/
|
|
24061
|
+
];
|
|
24062
|
+
function unfencedLines(output) {
|
|
24063
|
+
const lines = [];
|
|
24064
|
+
let fenced = false;
|
|
24065
|
+
for (const line of output.split(`
|
|
24066
|
+
`)) {
|
|
24067
|
+
if (FENCE_LINE5.test(line)) {
|
|
24068
|
+
fenced = !fenced;
|
|
24069
|
+
continue;
|
|
24070
|
+
}
|
|
24071
|
+
if (!fenced)
|
|
24072
|
+
lines.push(line);
|
|
24073
|
+
}
|
|
24074
|
+
return lines;
|
|
24075
|
+
}
|
|
24076
|
+
function capped(line) {
|
|
24077
|
+
const trimmed = line.trim();
|
|
24078
|
+
return trimmed.length > WAIT_LINE_CAP ? `${trimmed.slice(0, WAIT_LINE_CAP - 1)}\u2026` : trimmed;
|
|
24079
|
+
}
|
|
24080
|
+
function readBackgroundWait(output) {
|
|
24081
|
+
if (output.trim().length === 0)
|
|
24082
|
+
return null;
|
|
24083
|
+
const reading2 = parseReport(output);
|
|
24084
|
+
if (reading2.present || reading2.reason !== "no-block")
|
|
24085
|
+
return null;
|
|
24086
|
+
const waiting = unfencedLines(output).find((line) => WAIT_PHRASES.some((phrase) => phrase.test(line)));
|
|
24087
|
+
return waiting === undefined ? null : capped(waiting);
|
|
24088
|
+
}
|
|
24089
|
+
|
|
23910
24090
|
// src/start/commit.ts
|
|
23911
24091
|
var DROPPED_BLOCKER_FIELD = /^blockers\[\d+\]$/;
|
|
23912
24092
|
var NO_REPORT_HOLD = "no report: the session wrote no rafa:report block the loop could read";
|
|
24093
|
+
var BACKGROUND_WAIT_HOLD = "no report: the session ended its turn waiting on a command left running in the background:";
|
|
23913
24094
|
var NO_COMMIT_HOLD = "no commit: the task changed no tracked file";
|
|
23914
24095
|
var NOTHING_REPORTED_OR_COMMITTED = "the session wrote no report block and changed no tracked file";
|
|
23915
24096
|
function indentBlock(text) {
|
|
@@ -23935,8 +24116,12 @@ function holdsOf(reading2) {
|
|
|
23935
24116
|
function leftNothingBehind(attempt2, reported) {
|
|
23936
24117
|
return !reported && attempt2.outcome === "nothing-to-commit";
|
|
23937
24118
|
}
|
|
23938
|
-
function
|
|
23939
|
-
|
|
24119
|
+
function noReportHold(output) {
|
|
24120
|
+
const waiting = output === undefined ? null : readBackgroundWait(output);
|
|
24121
|
+
return waiting === null ? NO_REPORT_HOLD : `${BACKGROUND_WAIT_HOLD} ${waiting}`;
|
|
24122
|
+
}
|
|
24123
|
+
function holdsAfterCommit(holds4, silent, output) {
|
|
24124
|
+
return silent ? [...holds4, noReportHold(output), NO_COMMIT_HOLD] : holds4;
|
|
23940
24125
|
}
|
|
23941
24126
|
function commitFinishedTask(options) {
|
|
23942
24127
|
const { taskInfo, trackerPath } = options;
|
|
@@ -23957,7 +24142,7 @@ function commitFinishedTask(options) {
|
|
|
23957
24142
|
return attempt2;
|
|
23958
24143
|
}
|
|
23959
24144
|
const silent = leftNothingBehind(attempt2, reported);
|
|
23960
|
-
const holds4 = holdsAfterCommit(reportHolds, silent);
|
|
24145
|
+
const holds4 = holdsAfterCommit(reportHolds, silent, options.output);
|
|
23961
24146
|
const held = holds4.length > 0;
|
|
23962
24147
|
const marked = silent && writeTrackerBlocker(trackerPath, taskInfo.lineNum, NOTHING_REPORTED_OR_COMMITTED);
|
|
23963
24148
|
if (!marked) {
|
|
@@ -23988,8 +24173,8 @@ function finishCleanExit(options) {
|
|
|
23988
24173
|
const reading2 = parseReport(output);
|
|
23989
24174
|
const reported = reading2.present;
|
|
23990
24175
|
const reportHolds = reported ? holdsOf(reading2) : [];
|
|
23991
|
-
const attempt2 = commitFinishedTask({ ...task, holds: reportHolds, reported });
|
|
23992
|
-
const holds4 = holdsAfterCommit(reportHolds, leftNothingBehind(attempt2, reported));
|
|
24176
|
+
const attempt2 = commitFinishedTask({ ...task, holds: reportHolds, reported, output });
|
|
24177
|
+
const holds4 = holdsAfterCommit(reportHolds, leftNothingBehind(attempt2, reported), output);
|
|
23993
24178
|
const blocked3 = attempt2.outcome === "failed" || holds4.length > 0;
|
|
23994
24179
|
return {
|
|
23995
24180
|
attempt: attempt2,
|
|
@@ -24038,7 +24223,7 @@ var SCRIPT_EXTENSIONS = [
|
|
|
24038
24223
|
".cjs",
|
|
24039
24224
|
".ts"
|
|
24040
24225
|
];
|
|
24041
|
-
var
|
|
24226
|
+
var FENCE_LINE6 = /^ {0,3}(?:```|~~~)/;
|
|
24042
24227
|
var HEADING_LINE2 = /^ {0,3}(#{1,6})\s+(.*)$/;
|
|
24043
24228
|
var NUMBERED_ITEM = /^ {0,3}\d+[.)]\s+\S/;
|
|
24044
24229
|
var CONTINUATION = /^ {2,}\S/;
|
|
@@ -24072,7 +24257,7 @@ function bodySections(body) {
|
|
|
24072
24257
|
const headings = [];
|
|
24073
24258
|
let fenced = false;
|
|
24074
24259
|
lines.forEach((line, index) => {
|
|
24075
|
-
if (
|
|
24260
|
+
if (FENCE_LINE6.test(line)) {
|
|
24076
24261
|
fenced = !fenced;
|
|
24077
24262
|
return;
|
|
24078
24263
|
}
|
|
@@ -24111,7 +24296,7 @@ function numberedRuns(body) {
|
|
|
24111
24296
|
steps = 0;
|
|
24112
24297
|
};
|
|
24113
24298
|
bodyLines2(body).forEach((text, index) => {
|
|
24114
|
-
if (
|
|
24299
|
+
if (FENCE_LINE6.test(text)) {
|
|
24115
24300
|
fenced = !fenced;
|
|
24116
24301
|
return;
|
|
24117
24302
|
}
|
|
@@ -24199,7 +24384,7 @@ function withoutFences(text) {
|
|
|
24199
24384
|
const kept = [];
|
|
24200
24385
|
let fenced = false;
|
|
24201
24386
|
for (const line of bodyLines2(text)) {
|
|
24202
|
-
if (
|
|
24387
|
+
if (FENCE_LINE6.test(line)) {
|
|
24203
24388
|
fenced = !fenced;
|
|
24204
24389
|
continue;
|
|
24205
24390
|
}
|
|
@@ -24258,7 +24443,7 @@ function extractedDate(body) {
|
|
|
24258
24443
|
function errorString(body) {
|
|
24259
24444
|
let fenced = false;
|
|
24260
24445
|
for (const line of bodyLines2(body)) {
|
|
24261
|
-
if (
|
|
24446
|
+
if (FENCE_LINE6.test(line)) {
|
|
24262
24447
|
fenced = !fenced;
|
|
24263
24448
|
continue;
|
|
24264
24449
|
}
|
|
@@ -24861,7 +25046,7 @@ function writeProgress(repoRoot, planStub2, dir = repoRoot) {
|
|
|
24861
25046
|
}
|
|
24862
25047
|
|
|
24863
25048
|
// src/inventory/search/rank.ts
|
|
24864
|
-
import { readFileSync as
|
|
25049
|
+
import { readFileSync as readFileSync33 } from "fs";
|
|
24865
25050
|
var SEARCH_LIMIT = 12;
|
|
24866
25051
|
var MIN_WORD_LENGTH = 2;
|
|
24867
25052
|
var MIN_PREFIX_LENGTH = 4;
|
|
@@ -24983,7 +25168,7 @@ function rankCandidates(question, candidates, limit = SEARCH_LIMIT) {
|
|
|
24983
25168
|
return [];
|
|
24984
25169
|
return candidates.map((candidate) => scoreCandidate(candidate, words)).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
24985
25170
|
}
|
|
24986
|
-
function readRankCandidate(record, read = (path3) =>
|
|
25171
|
+
function readRankCandidate(record, read = (path3) => readFileSync33(path3, "utf8")) {
|
|
24987
25172
|
let text;
|
|
24988
25173
|
try {
|
|
24989
25174
|
text = read(record.path);
|
|
@@ -25232,7 +25417,7 @@ function renderInheritedSection(failures) {
|
|
|
25232
25417
|
|
|
25233
25418
|
// src/start/preflight.ts
|
|
25234
25419
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
25235
|
-
import { existsSync as existsSync28, readFileSync as
|
|
25420
|
+
import { existsSync as existsSync28, readFileSync as readFileSync35 } from "fs";
|
|
25236
25421
|
import { homedir as homedir7 } from "os";
|
|
25237
25422
|
import { basename as basename8, join as join28 } from "path";
|
|
25238
25423
|
|
|
@@ -25350,11 +25535,11 @@ function developmentApplied(report2) {
|
|
|
25350
25535
|
}
|
|
25351
25536
|
|
|
25352
25537
|
// src/preflight/first-dispatch.ts
|
|
25353
|
-
import { readFileSync as
|
|
25538
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
25354
25539
|
var TICKED_TASK = /^- \[[xX]\] (.+)/;
|
|
25355
25540
|
function readIfReadable(path3) {
|
|
25356
25541
|
try {
|
|
25357
|
-
return
|
|
25542
|
+
return readFileSync34(path3, "utf8");
|
|
25358
25543
|
} catch {
|
|
25359
25544
|
return null;
|
|
25360
25545
|
}
|
|
@@ -25696,7 +25881,7 @@ function driftLines(report2) {
|
|
|
25696
25881
|
|
|
25697
25882
|
// src/start/preflight-drift.ts
|
|
25698
25883
|
var DRIFT_EVERY = 2;
|
|
25699
|
-
var
|
|
25884
|
+
var RECORD_EXTENSION2 = ".json";
|
|
25700
25885
|
function countRunRecords(root) {
|
|
25701
25886
|
let names2;
|
|
25702
25887
|
try {
|
|
@@ -25706,7 +25891,7 @@ function countRunRecords(root) {
|
|
|
25706
25891
|
return 0;
|
|
25707
25892
|
throw error;
|
|
25708
25893
|
}
|
|
25709
|
-
return names2.filter((name2) => name2.endsWith(
|
|
25894
|
+
return names2.filter((name2) => name2.endsWith(RECORD_EXTENSION2) && isSessionId(name2.slice(0, -RECORD_EXTENSION2.length))).length;
|
|
25710
25895
|
}
|
|
25711
25896
|
function isDriftRun(count) {
|
|
25712
25897
|
return count > 0 && count % DRIFT_EVERY === 0;
|
|
@@ -25819,7 +26004,7 @@ function agentSourcePath(planPath) {
|
|
|
25819
26004
|
}
|
|
25820
26005
|
function readIfReadable2(path3) {
|
|
25821
26006
|
try {
|
|
25822
|
-
return
|
|
26007
|
+
return readFileSync35(path3, "utf8");
|
|
25823
26008
|
} catch {
|
|
25824
26009
|
return null;
|
|
25825
26010
|
}
|
|
@@ -26075,6 +26260,7 @@ function runTaskSession(prompt, flags, sessionId, settingSources, served = [], c
|
|
|
26075
26260
|
const options = cwd === undefined ? {} : { cwd };
|
|
26076
26261
|
return runClaudeCaptured(prompt, settingSources, [SESSION_ID_FLAG2, sessionId, ...flags], spawn, served, options);
|
|
26077
26262
|
}
|
|
26263
|
+
var HALTED_EXIT_CODE = 1;
|
|
26078
26264
|
var BLOCKER_PROMPT_PREFIX = "An earlier run of this task left it blocked on: ";
|
|
26079
26265
|
function blockerLines(blocker) {
|
|
26080
26266
|
return blocker === null || blocker.trim().length === 0 ? [] : [`${BLOCKER_PROMPT_PREFIX}${escapeBlockerText(blocker)}`];
|
|
@@ -26135,22 +26321,28 @@ async function dispatchTask(options) {
|
|
|
26135
26321
|
const handed = await handOut(options.handout, task, resolution, options.repoRoot);
|
|
26136
26322
|
const prompt = withStamp(buildTaskPrompt(taskText, options.promptContent, injection.text, options.knownMissing, taskInfo.blocker ?? null, { skills: renderSkillsSection(handed.skills), lessons: renderLessonsSection(handed.lessons) }, options.base, options.inherited, options.alwaysRun));
|
|
26137
26323
|
const served = serveForSession(options.serving, resolution);
|
|
26138
|
-
const
|
|
26139
|
-
const session = await run(prompt, flags, sessionId, options.settingSources, served, options.checkout);
|
|
26140
|
-
return {
|
|
26324
|
+
const record = {
|
|
26141
26325
|
taskText,
|
|
26142
26326
|
prompt,
|
|
26143
26327
|
flags,
|
|
26144
26328
|
served,
|
|
26145
26329
|
declaration,
|
|
26146
26330
|
injection,
|
|
26147
|
-
sessionId,
|
|
26148
|
-
exitCode: session.exitCode,
|
|
26149
|
-
output: session.stdout,
|
|
26150
26331
|
resolver: handed.resolver,
|
|
26151
26332
|
skillsOffered: handed.skills,
|
|
26152
26333
|
lessonsOffered: handed.lessons
|
|
26153
26334
|
};
|
|
26335
|
+
if (haltBeforeSpawn(options.guard ?? null, taskInfo)) {
|
|
26336
|
+
return { ...record, sessionId: "", exitCode: HALTED_EXIT_CODE, output: "", halted: true };
|
|
26337
|
+
}
|
|
26338
|
+
const sessionId = (options.newSessionId ?? randomUUID8)();
|
|
26339
|
+
const session = await run(prompt, flags, sessionId, options.settingSources, served, options.checkout);
|
|
26340
|
+
return { ...record, sessionId, exitCode: session.exitCode, output: session.stdout, halted: false };
|
|
26341
|
+
}
|
|
26342
|
+
function haltBeforeSpawn(guard, taskInfo) {
|
|
26343
|
+
if (guard === null)
|
|
26344
|
+
return false;
|
|
26345
|
+
return haltIfCheckoutMoved({ expected: guard.expected, trackerPath: guard.trackerPath, taskInfo }, guard.seams);
|
|
26154
26346
|
}
|
|
26155
26347
|
function serveForSession(serving, resolution) {
|
|
26156
26348
|
if (serving === null)
|
|
@@ -26240,7 +26432,7 @@ async function storeTaskReport(options) {
|
|
|
26240
26432
|
}
|
|
26241
26433
|
|
|
26242
26434
|
// src/start/loop-events.ts
|
|
26243
|
-
import { appendFileSync, mkdirSync as
|
|
26435
|
+
import { appendFileSync, mkdirSync as mkdirSync11 } from "fs";
|
|
26244
26436
|
import { join as join29 } from "path";
|
|
26245
26437
|
var MINUTE_MS = 60000;
|
|
26246
26438
|
var TOKENS_PER_K = 1000;
|
|
@@ -26292,7 +26484,7 @@ function bindEventsFile(root, sessionId, warn2 = (line) => {
|
|
|
26292
26484
|
}) {
|
|
26293
26485
|
const file = eventsFilePath(root, sessionId);
|
|
26294
26486
|
try {
|
|
26295
|
-
|
|
26487
|
+
mkdirSync11(runsDir(root), { recursive: true });
|
|
26296
26488
|
} catch {}
|
|
26297
26489
|
bound = { file, warn: warn2, warned: false };
|
|
26298
26490
|
return file;
|
|
@@ -26386,20 +26578,20 @@ async function holdWhilePaused(options) {
|
|
|
26386
26578
|
|
|
26387
26579
|
// src/start/plan-path.ts
|
|
26388
26580
|
import { existsSync as existsSync29 } from "fs";
|
|
26389
|
-
import { resolve as
|
|
26581
|
+
import { resolve as resolve19 } from "path";
|
|
26390
26582
|
var DEFAULT_PLAN_FILE = "PLAN.md";
|
|
26391
26583
|
function resolvePlanPath(repoRoot, planDir, planArg) {
|
|
26392
26584
|
if (planArg)
|
|
26393
|
-
return
|
|
26394
|
-
const inPlanDir =
|
|
26395
|
-
return existsSync29(inPlanDir) ? inPlanDir :
|
|
26585
|
+
return resolve19(repoRoot, planArg);
|
|
26586
|
+
const inPlanDir = resolve19(repoRoot, planDir, DEFAULT_PLAN_FILE);
|
|
26587
|
+
return existsSync29(inPlanDir) ? inPlanDir : resolve19(repoRoot, DEFAULT_PLAN_FILE);
|
|
26396
26588
|
}
|
|
26397
26589
|
|
|
26398
26590
|
// src/start/risk-total.ts
|
|
26399
|
-
import { readFileSync as
|
|
26591
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
26400
26592
|
|
|
26401
26593
|
// src/plan/risk.ts
|
|
26402
|
-
import { readFileSync as
|
|
26594
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
26403
26595
|
import { isAbsolute as isAbsolute6, join as join30, relative as relative8 } from "path";
|
|
26404
26596
|
|
|
26405
26597
|
// src/plan/risk/accounts.ts
|
|
@@ -26959,7 +27151,7 @@ function located(finding2, file, task) {
|
|
|
26959
27151
|
}
|
|
26960
27152
|
function readText5(path3) {
|
|
26961
27153
|
try {
|
|
26962
|
-
return
|
|
27154
|
+
return readFileSync36(path3, "utf8");
|
|
26963
27155
|
} catch {
|
|
26964
27156
|
return null;
|
|
26965
27157
|
}
|
|
@@ -27061,7 +27253,7 @@ function spawningRunners(root) {
|
|
|
27061
27253
|
}
|
|
27062
27254
|
async function readRiskTotalLine(input, seams = {}) {
|
|
27063
27255
|
const makeRunners = seams.runners ?? spawningRunners;
|
|
27064
|
-
const report2 = await assessPlanRisk({ path: input.planPath, text:
|
|
27256
|
+
const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync37(input.planPath, "utf8") }, {
|
|
27065
27257
|
repoRoot: input.repoRoot,
|
|
27066
27258
|
home: input.home,
|
|
27067
27259
|
settingSources: input.config.settingSources,
|
|
@@ -27621,7 +27813,7 @@ function readRunArgs(args) {
|
|
|
27621
27813
|
|
|
27622
27814
|
// src/start/worktree.ts
|
|
27623
27815
|
import { realpathSync as realpathSync4 } from "fs";
|
|
27624
|
-
import { join as join31, resolve as
|
|
27816
|
+
import { join as join31, resolve as resolve21 } from "path";
|
|
27625
27817
|
|
|
27626
27818
|
// src/start/claim-catch-up.ts
|
|
27627
27819
|
var OWN_COMMIT_FORMAT = "--format=%H%x09%P%x09%s";
|
|
@@ -27686,9 +27878,9 @@ function readClaimCatchUp(git, branch, base) {
|
|
|
27686
27878
|
}
|
|
27687
27879
|
|
|
27688
27880
|
// src/start/worktree-dir.ts
|
|
27689
|
-
import { resolve as
|
|
27881
|
+
import { resolve as resolve20 } from "path";
|
|
27690
27882
|
function worktreeDirAt(projectRoot3, worktreeDir) {
|
|
27691
|
-
return
|
|
27883
|
+
return resolve20(projectRoot3, worktreeDir);
|
|
27692
27884
|
}
|
|
27693
27885
|
|
|
27694
27886
|
// src/start/worktree.ts
|
|
@@ -27752,7 +27944,7 @@ function canonicalPath(path3) {
|
|
|
27752
27944
|
try {
|
|
27753
27945
|
return realpathSync4(path3);
|
|
27754
27946
|
} catch {
|
|
27755
|
-
return
|
|
27947
|
+
return resolve21(path3);
|
|
27756
27948
|
}
|
|
27757
27949
|
}
|
|
27758
27950
|
function readExisting(listing2, branch, path3) {
|
|
@@ -27925,13 +28117,13 @@ async function settleRunCheckout(request, seams = {}) {
|
|
|
27925
28117
|
}
|
|
27926
28118
|
|
|
27927
28119
|
// src/start/suite-step.ts
|
|
27928
|
-
import { existsSync as
|
|
28120
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync14, readFileSync as readFileSync44, renameSync as renameSync7, writeFileSync as writeFileSync17 } from "fs";
|
|
27929
28121
|
import { constants as constants2 } from "os";
|
|
27930
|
-
import { basename as basename11, dirname as
|
|
28122
|
+
import { basename as basename11, dirname as dirname15, join as join37 } from "path";
|
|
27931
28123
|
|
|
27932
28124
|
// src/suite/baseline.ts
|
|
27933
|
-
import { existsSync as existsSync30, mkdirSync as
|
|
27934
|
-
import { basename as basename9, dirname as
|
|
28125
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync12, readFileSync as readFileSync38, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "fs";
|
|
28126
|
+
import { basename as basename9, dirname as dirname12, join as join32 } from "path";
|
|
27935
28127
|
var BASELINE_VERSION = 1;
|
|
27936
28128
|
var BASELINE_FILE_PREFIX = "SUITE_BASELINE";
|
|
27937
28129
|
var PLAN_FILE_NAME2 = /^PLAN(?:_TRACKER)?(-.+)?\.md$/;
|
|
@@ -27941,7 +28133,7 @@ function baselinePathFor(trackerPath) {
|
|
|
27941
28133
|
if (match === null) {
|
|
27942
28134
|
throw new RangeError(`baselinePathFor: ${trackerPath} is not a PLAN or PLAN_TRACKER file`);
|
|
27943
28135
|
}
|
|
27944
|
-
return join32(
|
|
28136
|
+
return join32(dirname12(trackerPath), `${BASELINE_FILE_PREFIX}${match[1] ?? ""}.json`);
|
|
27945
28137
|
}
|
|
27946
28138
|
function copyFailure(failure3) {
|
|
27947
28139
|
return failure3.message === undefined ? { file: failure3.file, name: failure3.name } : { file: failure3.file, name: failure3.name, message: failure3.message };
|
|
@@ -27960,11 +28152,11 @@ function baselineOf(result, recordedAt, commit) {
|
|
|
27960
28152
|
};
|
|
27961
28153
|
}
|
|
27962
28154
|
function writeBaseline(path3, baseline) {
|
|
27963
|
-
|
|
28155
|
+
mkdirSync12(dirname12(path3), { recursive: true });
|
|
27964
28156
|
const temporary = `${path3}.${process.pid}.tmp`;
|
|
27965
28157
|
writeFileSync13(temporary, `${JSON.stringify(baseline, null, 2)}
|
|
27966
28158
|
`, "utf8");
|
|
27967
|
-
|
|
28159
|
+
renameSync6(temporary, path3);
|
|
27968
28160
|
}
|
|
27969
28161
|
function isStringOrNull(value) {
|
|
27970
28162
|
return value === null || typeof value === "string";
|
|
@@ -28008,7 +28200,7 @@ function readBaseline(path3) {
|
|
|
28008
28200
|
return { state: "missing" };
|
|
28009
28201
|
let text;
|
|
28010
28202
|
try {
|
|
28011
|
-
text =
|
|
28203
|
+
text = readFileSync38(path3, "utf8");
|
|
28012
28204
|
} catch (error) {
|
|
28013
28205
|
const reason = error instanceof Error ? error.message : String(error);
|
|
28014
28206
|
return { state: "unreadable", reason };
|
|
@@ -28103,8 +28295,8 @@ async function readPlanOwns(options) {
|
|
|
28103
28295
|
}
|
|
28104
28296
|
|
|
28105
28297
|
// src/suite/run.ts
|
|
28106
|
-
import { existsSync as existsSync31, mkdirSync as
|
|
28107
|
-
import { basename as basename10, dirname as
|
|
28298
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync13, readFileSync as readFileSync39, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
|
|
28299
|
+
import { basename as basename10, dirname as dirname13, extname as extname2, isAbsolute as isAbsolute7, join as join33 } from "path";
|
|
28108
28300
|
|
|
28109
28301
|
// src/suite/unhandled.ts
|
|
28110
28302
|
var UNHANDLED_HEADING = "# Unhandled error between tests";
|
|
@@ -28214,7 +28406,7 @@ var OUTPUT_SUFFIX = ".output.txt";
|
|
|
28214
28406
|
function outputFileFor(junitFile) {
|
|
28215
28407
|
const name2 = basename10(junitFile);
|
|
28216
28408
|
const stem = name2.endsWith(JUNIT_SUFFIX) ? name2.slice(0, -JUNIT_SUFFIX.length) : name2.slice(0, name2.length - extname2(name2).length);
|
|
28217
|
-
return join33(
|
|
28409
|
+
return join33(dirname13(junitFile), `${stem}${OUTPUT_SUFFIX}`);
|
|
28218
28410
|
}
|
|
28219
28411
|
function suiteEnv(base) {
|
|
28220
28412
|
return Object.fromEntries(Object.entries(base).filter(([key]) => key !== CLAUDE_CODE_ENV));
|
|
@@ -28322,7 +28514,7 @@ function readJunit(path3) {
|
|
|
28322
28514
|
return { junit: "missing", failures: [] };
|
|
28323
28515
|
let text;
|
|
28324
28516
|
try {
|
|
28325
|
-
text =
|
|
28517
|
+
text = readFileSync39(path3, "utf8");
|
|
28326
28518
|
} catch {
|
|
28327
28519
|
return { junit: "unreadable", failures: [] };
|
|
28328
28520
|
}
|
|
@@ -28346,7 +28538,7 @@ async function runSuite(options) {
|
|
|
28346
28538
|
}
|
|
28347
28539
|
const command = suiteCommand(options.junitFile, options.paths, options.changedSince);
|
|
28348
28540
|
const outputFile = outputFileFor(options.junitFile);
|
|
28349
|
-
|
|
28541
|
+
mkdirSync13(dirname13(options.junitFile), { recursive: true });
|
|
28350
28542
|
rmSync4(options.junitFile, { force: true });
|
|
28351
28543
|
rmSync4(outputFile, { force: true });
|
|
28352
28544
|
const spawn = options.spawn ?? spawnSuite;
|
|
@@ -28361,7 +28553,7 @@ async function runSuite(options) {
|
|
|
28361
28553
|
}
|
|
28362
28554
|
|
|
28363
28555
|
// src/suite/scope.ts
|
|
28364
|
-
import { existsSync as existsSync32, readdirSync as readdirSync14, readFileSync as
|
|
28556
|
+
import { existsSync as existsSync32, readdirSync as readdirSync14, readFileSync as readFileSync40 } from "fs";
|
|
28365
28557
|
import { isAbsolute as isAbsolute8, join as join34, relative as relative9, sep as sep2 } from "path";
|
|
28366
28558
|
var BUNFIG_FILE = "bunfig.toml";
|
|
28367
28559
|
var TEST_FILE_NAME = /[._](?:test|spec)\.(?:tsx?|jsx?|[cm][jt]s)$/;
|
|
@@ -28449,7 +28641,7 @@ function readPreloadFiles(root) {
|
|
|
28449
28641
|
if (!existsSync32(path3))
|
|
28450
28642
|
return { state: "missing", files: [] };
|
|
28451
28643
|
try {
|
|
28452
|
-
return { state: "read", files: preloadFilesOf(
|
|
28644
|
+
return { state: "read", files: preloadFilesOf(readFileSync40(path3, "utf8"), root) };
|
|
28453
28645
|
} catch (error) {
|
|
28454
28646
|
const reason = error instanceof Error ? error.message : String(error);
|
|
28455
28647
|
return { state: "unreadable", files: [], reason };
|
|
@@ -28605,7 +28797,7 @@ async function runLintStep(input) {
|
|
|
28605
28797
|
}
|
|
28606
28798
|
|
|
28607
28799
|
// src/start/suite-blocker.ts
|
|
28608
|
-
import { readFileSync as
|
|
28800
|
+
import { readFileSync as readFileSync41, writeFileSync as writeFileSync15 } from "fs";
|
|
28609
28801
|
function failingFiles(failures) {
|
|
28610
28802
|
const counts = new Map;
|
|
28611
28803
|
for (const failure3 of failures)
|
|
@@ -28689,7 +28881,7 @@ function blockRepairAgain(trackerPath, content, line, blocker) {
|
|
|
28689
28881
|
return writeTrackerBlocker(trackerPath, line, blocker) ? { line, inserted: false } : null;
|
|
28690
28882
|
}
|
|
28691
28883
|
function writeRepairTask(trackerPath, blocker, site) {
|
|
28692
|
-
const content =
|
|
28884
|
+
const content = readFileSync41(trackerPath, "utf8");
|
|
28693
28885
|
const repaired = existingRepairOf(content, site);
|
|
28694
28886
|
if (repaired !== null)
|
|
28695
28887
|
return blockRepairAgain(trackerPath, content, repaired, blocker);
|
|
@@ -28699,7 +28891,7 @@ function writeRepairTask(trackerPath, blocker, site) {
|
|
|
28699
28891
|
}
|
|
28700
28892
|
|
|
28701
28893
|
// src/start/sweep-timing.ts
|
|
28702
|
-
import { existsSync as existsSync34, readFileSync as
|
|
28894
|
+
import { existsSync as existsSync34, readFileSync as readFileSync42 } from "fs";
|
|
28703
28895
|
var SLOW_SWEEP_SECONDS = 10;
|
|
28704
28896
|
var SUITE_TAG = /<(\/?)testsuite\b([^>]*?)(\/?)>/g;
|
|
28705
28897
|
function fileTimeOf(attributes) {
|
|
@@ -28742,7 +28934,7 @@ function readFileTimes(path3) {
|
|
|
28742
28934
|
if (!existsSync34(path3))
|
|
28743
28935
|
return [];
|
|
28744
28936
|
try {
|
|
28745
|
-
return parseJunitFileTimes(
|
|
28937
|
+
return parseJunitFileTimes(readFileSync42(path3, "utf8")) ?? [];
|
|
28746
28938
|
} catch {
|
|
28747
28939
|
return [];
|
|
28748
28940
|
}
|
|
@@ -28816,8 +29008,300 @@ function foldResults(first, second, stopCode) {
|
|
|
28816
29008
|
};
|
|
28817
29009
|
return first.noTestFiles === true && second.noTestFiles === true ? { ...folded2, noTestFiles: true } : folded2;
|
|
28818
29010
|
}
|
|
29011
|
+
|
|
29012
|
+
// src/start/type-step.ts
|
|
29013
|
+
import { existsSync as existsSync35, mkdtempSync, realpathSync as realpathSync5, rmSync as rmSync5, symlinkSync as symlinkSync2, writeFileSync as writeFileSync16 } from "fs";
|
|
29014
|
+
import { tmpdir } from "os";
|
|
29015
|
+
import { dirname as dirname14, join as join36, relative as relative11, resolve as resolve22, sep as sep3 } from "path";
|
|
29016
|
+
var TEST_FILE_SUFFIX = ".test.ts";
|
|
29017
|
+
var TSC_FLAGS = ["--noEmit", "--pretty", "false"];
|
|
29018
|
+
var ERRORS_LISTED2 = 10;
|
|
29019
|
+
var NODE_MODULES = "node_modules";
|
|
29020
|
+
var TSC_BIN = [".bin", "tsc"];
|
|
29021
|
+
var SCRATCH_NAME = "tsconfig.json";
|
|
29022
|
+
var FILE_ERROR = /^(.+)\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
|
29023
|
+
var GLOBAL_ERROR = /^error (TS\d+): (.*)$/;
|
|
29024
|
+
var NOTHING_RAN2 = { ran: false, red: false, interrupted: false, blocker: null };
|
|
29025
|
+
var GREEN = { ran: true, red: false, interrupted: false, blocker: null };
|
|
29026
|
+
var INTERRUPTED = { ran: true, red: false, interrupted: true, blocker: null };
|
|
29027
|
+
async function runTsc(options) {
|
|
29028
|
+
const proc = Bun.spawn([...options.argv], {
|
|
29029
|
+
cwd: options.cwd,
|
|
29030
|
+
stdin: "ignore",
|
|
29031
|
+
stdout: "pipe",
|
|
29032
|
+
stderr: "pipe"
|
|
29033
|
+
});
|
|
29034
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
29035
|
+
new Response(proc.stdout).text(),
|
|
29036
|
+
new Response(proc.stderr).text(),
|
|
29037
|
+
proc.exited
|
|
29038
|
+
]);
|
|
29039
|
+
return { exitCode, stdout, stderr };
|
|
29040
|
+
}
|
|
29041
|
+
function typeLabel(task) {
|
|
29042
|
+
return `type step after "${task}"`;
|
|
29043
|
+
}
|
|
29044
|
+
function pathsOf(status) {
|
|
29045
|
+
return /^[RC]/.test(status) ? 2 : 1;
|
|
29046
|
+
}
|
|
29047
|
+
function readTypeFiles(git, base) {
|
|
29048
|
+
const result = git(["diff", "--name-status", "-z", "--find-renames", "--diff-filter=d", base, "HEAD"]);
|
|
29049
|
+
if (!result.ok) {
|
|
29050
|
+
activeOutput().warn(`\u26A0\uFE0F git diff from ${base} did not answer (${gitSaid(result) || "nothing said"}); the type step runs nothing.`);
|
|
29051
|
+
return null;
|
|
29052
|
+
}
|
|
29053
|
+
const fields = result.stdout.split("\x00").filter((field4) => field4 !== "");
|
|
29054
|
+
const files = [];
|
|
29055
|
+
for (let index = 0;index < fields.length; ) {
|
|
29056
|
+
const status = fields[index] ?? "";
|
|
29057
|
+
const count = pathsOf(status);
|
|
29058
|
+
const paths = fields.slice(index + 1, index + 1 + count);
|
|
29059
|
+
index += 1 + count;
|
|
29060
|
+
const head = paths[paths.length - 1];
|
|
29061
|
+
if (head === undefined || !head.endsWith(TEST_FILE_SUFFIX))
|
|
29062
|
+
continue;
|
|
29063
|
+
files.push({ head, base: status.startsWith("A") ? null : paths[0] ?? null });
|
|
29064
|
+
}
|
|
29065
|
+
return files;
|
|
29066
|
+
}
|
|
29067
|
+
function realPath(path3) {
|
|
29068
|
+
try {
|
|
29069
|
+
return realpathSync5(path3);
|
|
29070
|
+
} catch {
|
|
29071
|
+
return resolve22(path3);
|
|
29072
|
+
}
|
|
29073
|
+
}
|
|
29074
|
+
function walkStop(git) {
|
|
29075
|
+
const result = git(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
29076
|
+
const common = result.stdout.trim();
|
|
29077
|
+
return result.ok && common !== "" ? dirname14(realPath(common)) : null;
|
|
29078
|
+
}
|
|
29079
|
+
function isUnder(dir, root) {
|
|
29080
|
+
return dir === root || dir.startsWith(root.endsWith(sep3) ? root : `${root}${sep3}`);
|
|
29081
|
+
}
|
|
29082
|
+
function findNodeModules(checkout, git) {
|
|
29083
|
+
const start = realPath(checkout);
|
|
29084
|
+
const stop = walkStop(git);
|
|
29085
|
+
const top = stop !== null && isUnder(start, stop) ? stop : start;
|
|
29086
|
+
for (let dir = start;; dir = dirname14(dir)) {
|
|
29087
|
+
const modules = join36(dir, NODE_MODULES);
|
|
29088
|
+
if (existsSync35(join36(modules, ...TSC_BIN)))
|
|
29089
|
+
return modules;
|
|
29090
|
+
if (dir === top || dirname14(dir) === dir)
|
|
29091
|
+
return null;
|
|
29092
|
+
}
|
|
29093
|
+
}
|
|
29094
|
+
function scratchTsconfig(tree, files, modules) {
|
|
29095
|
+
const root = resolve22(tree);
|
|
29096
|
+
return `${JSON.stringify({
|
|
29097
|
+
extends: join36(root, "tsconfig.json"),
|
|
29098
|
+
compilerOptions: { typeRoots: [join36(resolve22(modules), "@types")] },
|
|
29099
|
+
files: files.map((file) => join36(root, file)),
|
|
29100
|
+
include: []
|
|
29101
|
+
}, null, 2)}
|
|
29102
|
+
`;
|
|
29103
|
+
}
|
|
29104
|
+
function parseTscOutput(stdout, cwd) {
|
|
29105
|
+
const errors = [];
|
|
29106
|
+
for (const line of stdout.split(`
|
|
29107
|
+
`)) {
|
|
29108
|
+
const atFile = FILE_ERROR.exec(line);
|
|
29109
|
+
const global = atFile === null ? GLOBAL_ERROR.exec(line) : null;
|
|
29110
|
+
const last = errors[errors.length - 1];
|
|
29111
|
+
if (atFile !== null) {
|
|
29112
|
+
const [, file = "", row = "0", column = "0", code = "", message = ""] = atFile;
|
|
29113
|
+
errors.push({ file: relative11(cwd, resolve22(cwd, file)), line: Number(row), column: Number(column), code, message });
|
|
29114
|
+
} else if (global !== null) {
|
|
29115
|
+
errors.push({ file: null, line: 0, column: 0, code: global[1] ?? "", message: global[2] ?? "" });
|
|
29116
|
+
} else if (line.startsWith(" ") && last !== undefined) {
|
|
29117
|
+
errors[errors.length - 1] = { ...last, message: `${last.message}
|
|
29118
|
+
${line.trimEnd()}` };
|
|
29119
|
+
}
|
|
29120
|
+
}
|
|
29121
|
+
return errors;
|
|
29122
|
+
}
|
|
29123
|
+
function identity(error) {
|
|
29124
|
+
return `${error.code} ${error.message}`;
|
|
29125
|
+
}
|
|
29126
|
+
function newErrors(head, base) {
|
|
29127
|
+
const left = new Map;
|
|
29128
|
+
for (const error of base) {
|
|
29129
|
+
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
29130
|
+
left.set(key, (left.get(key) ?? 0) + 1);
|
|
29131
|
+
}
|
|
29132
|
+
return head.filter((error) => {
|
|
29133
|
+
const key = `${error.file ?? ""}\x00${identity(error)}`;
|
|
29134
|
+
const count = left.get(key) ?? 0;
|
|
29135
|
+
if (count === 0)
|
|
29136
|
+
return true;
|
|
29137
|
+
left.set(key, count - 1);
|
|
29138
|
+
return false;
|
|
29139
|
+
});
|
|
29140
|
+
}
|
|
29141
|
+
function errorLine2(error) {
|
|
29142
|
+
const first = error.message.split(`
|
|
29143
|
+
`)[0] ?? "";
|
|
29144
|
+
return `${error.file ?? "(no file)"}:${error.line}:${error.column} ${error.code} ${first}`;
|
|
29145
|
+
}
|
|
29146
|
+
function listed(errors) {
|
|
29147
|
+
const shown2 = errors.slice(0, ERRORS_LISTED2).map(errorLine2);
|
|
29148
|
+
return errors.length > ERRORS_LISTED2 ? [...shown2, `...and ${errors.length - ERRORS_LISTED2} more`] : shown2;
|
|
29149
|
+
}
|
|
29150
|
+
function typeBlockerText(label, base, errors) {
|
|
29151
|
+
const files = [...new Set(errors.map((error) => error.file ?? ""))];
|
|
29152
|
+
const shown2 = listed(errors).join("; ").replace(/\.$/u, "");
|
|
29153
|
+
return `The runner's ${label} found type errors in the task's test files that ${base} did not hold. ` + `New errors: ${shown2}. ` + `Fix them, then check ${files.join(" ")} with tsc ${TSC_FLAGS.join(" ")} -p over a tsconfig outside the checkout ` + "that extends its tsconfig.json by absolute path and lists them under files.";
|
|
29154
|
+
}
|
|
29155
|
+
function brokenBlockerText(label, run, said) {
|
|
29156
|
+
return `The runner's ${label} could not type-check the task's test files: tsc exited ${run.exitCode}${said === undefined ? " and printed no error." : `: ${said}`}`;
|
|
29157
|
+
}
|
|
29158
|
+
function configError(errors, scratch, cwd) {
|
|
29159
|
+
const config = relative11(cwd, scratch);
|
|
29160
|
+
return errors.find((error) => error.file === null || error.file === config);
|
|
29161
|
+
}
|
|
29162
|
+
function firstLine7(run) {
|
|
29163
|
+
return `${run.stdout}
|
|
29164
|
+
${run.stderr}`.split(`
|
|
29165
|
+
`).map((line) => line.trim()).find((line) => line !== "");
|
|
29166
|
+
}
|
|
29167
|
+
async function checkTree(input, modules, tree, files) {
|
|
29168
|
+
const scratchDir = mkdtempSync(join36(tmpdir(), "rafa-type-step-"));
|
|
29169
|
+
const scratch = join36(scratchDir, SCRATCH_NAME);
|
|
29170
|
+
try {
|
|
29171
|
+
writeFileSync16(scratch, scratchTsconfig(tree, files, modules), "utf8");
|
|
29172
|
+
const tsc = join36(modules, ...TSC_BIN);
|
|
29173
|
+
let run;
|
|
29174
|
+
try {
|
|
29175
|
+
run = await (input.runTypes ?? runTsc)({ cwd: tree, argv: [tsc, "-p", scratch, ...TSC_FLAGS] });
|
|
29176
|
+
} catch (error) {
|
|
29177
|
+
return { kind: "threw", why: messageOf(error) };
|
|
29178
|
+
}
|
|
29179
|
+
if (run.exitCode === input.stopCode || input.isInterrupted?.() === true)
|
|
29180
|
+
return { kind: "interrupted" };
|
|
29181
|
+
const errors = parseTscOutput(run.stdout, tree);
|
|
29182
|
+
const config = configError(errors, scratch, tree);
|
|
29183
|
+
if (config !== undefined)
|
|
29184
|
+
return { kind: "broken", run, said: `error ${config.code}: ${config.message.split(`
|
|
29185
|
+
`)[0] ?? ""}` };
|
|
29186
|
+
if (run.exitCode !== 0 && errors.length === 0)
|
|
29187
|
+
return { kind: "broken", run, said: firstLine7(run) };
|
|
29188
|
+
const wanted = new Set(files);
|
|
29189
|
+
return { kind: "read", errors: errors.filter((error) => error.file !== null && wanted.has(error.file)) };
|
|
29190
|
+
} finally {
|
|
29191
|
+
rmSync5(scratchDir, { recursive: true, force: true });
|
|
29192
|
+
}
|
|
29193
|
+
}
|
|
29194
|
+
async function inBaseTree(input, label, modules, check) {
|
|
29195
|
+
const parent = mkdtempSync(join36(tmpdir(), "rafa-type-base-"));
|
|
29196
|
+
const tree = join36(parent, "tree");
|
|
29197
|
+
try {
|
|
29198
|
+
const added = input.git(["worktree", "add", "--detach", tree, input.base]);
|
|
29199
|
+
if (!added.ok) {
|
|
29200
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check out ${input.base} (${gitSaid(added) || "nothing said"}); it reads no error as new.`);
|
|
29201
|
+
return null;
|
|
29202
|
+
}
|
|
29203
|
+
if (existsSync35(modules) && !existsSync35(join36(tree, NODE_MODULES)))
|
|
29204
|
+
symlinkSync2(modules, join36(tree, NODE_MODULES));
|
|
29205
|
+
if (!existsSync35(join36(tree, "tsconfig.json")))
|
|
29206
|
+
return { kind: "read", errors: [] };
|
|
29207
|
+
return await check(tree);
|
|
29208
|
+
} finally {
|
|
29209
|
+
if (existsSync35(tree)) {
|
|
29210
|
+
const removed = input.git(["worktree", "remove", "--force", tree]);
|
|
29211
|
+
if (!removed.ok)
|
|
29212
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not remove its worktree ${tree} (${gitSaid(removed) || "nothing said"}).`);
|
|
29213
|
+
}
|
|
29214
|
+
rmSync5(parent, { recursive: true, force: true });
|
|
29215
|
+
}
|
|
29216
|
+
}
|
|
29217
|
+
async function baseErrors(input, label, modules, files) {
|
|
29218
|
+
const pairs = files.filter((file) => file.base !== null);
|
|
29219
|
+
if (pairs.length === 0)
|
|
29220
|
+
return { errors: [] };
|
|
29221
|
+
const run = await inBaseTree(input, label, modules, (tree) => checkTree(input, modules, tree, pairs.map((file) => file.base)));
|
|
29222
|
+
if (run === null)
|
|
29223
|
+
return { outcome: GREEN };
|
|
29224
|
+
if (run.kind === "interrupted")
|
|
29225
|
+
return { outcome: interrupted(label) };
|
|
29226
|
+
if (run.kind !== "read") {
|
|
29227
|
+
const why = run.kind === "threw" ? run.why : brokenBlockerText(label, run.run, run.said);
|
|
29228
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not check the files at ${input.base} (${why}); it reads no error as new.`);
|
|
29229
|
+
return { outcome: GREEN };
|
|
29230
|
+
}
|
|
29231
|
+
const headOf = new Map(pairs.map((file) => [file.base, file.head]));
|
|
29232
|
+
return { errors: run.errors.map((error) => ({ ...error, file: headOf.get(error.file ?? "") ?? error.file })) };
|
|
29233
|
+
}
|
|
29234
|
+
function interrupted(label) {
|
|
29235
|
+
activeOutput().info(`\u23F9 The ${label} was interrupted by SIGINT: read as a stop, not as errors.`);
|
|
29236
|
+
return INTERRUPTED;
|
|
29237
|
+
}
|
|
29238
|
+
function filesToCheck(input, label) {
|
|
29239
|
+
if (!existsSync35(join36(input.checkout, "tsconfig.json"))) {
|
|
29240
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: no tsconfig.json at the checkout root; nothing to type-check.`);
|
|
29241
|
+
return null;
|
|
29242
|
+
}
|
|
29243
|
+
const files = readTypeFiles(input.git, input.base);
|
|
29244
|
+
if (files === null)
|
|
29245
|
+
return null;
|
|
29246
|
+
if (files.length > 0)
|
|
29247
|
+
return files;
|
|
29248
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: the task changed no ${TEST_FILE_SUFFIX} file; nothing to type-check.`);
|
|
29249
|
+
return null;
|
|
29250
|
+
}
|
|
29251
|
+
async function runTypeStep(input) {
|
|
29252
|
+
const label = typeLabel(input.task);
|
|
29253
|
+
const files = filesToCheck(input, label);
|
|
29254
|
+
if (files === null)
|
|
29255
|
+
return NOTHING_RAN2;
|
|
29256
|
+
const modules = findNodeModules(input.checkout, input.git) ?? join36(resolve22(input.checkout), NODE_MODULES);
|
|
29257
|
+
const head = await checkTree(input, modules, input.checkout, files.map((file) => file.head));
|
|
29258
|
+
if (head.kind === "threw") {
|
|
29259
|
+
activeOutput().warn(`\u26A0\uFE0F The ${label} could not run tsc (${head.why}); the run goes on without it.`);
|
|
29260
|
+
return NOTHING_RAN2;
|
|
29261
|
+
}
|
|
29262
|
+
if (head.kind === "interrupted")
|
|
29263
|
+
return interrupted(label);
|
|
29264
|
+
if (head.kind === "broken") {
|
|
29265
|
+
const blocker = brokenBlockerText(label, head.run, head.said);
|
|
29266
|
+
activeOutput().info(`\uD83D\uDD0E ${label}: ${blocker}`);
|
|
29267
|
+
return { ran: true, red: true, interrupted: false, blocker };
|
|
29268
|
+
}
|
|
29269
|
+
const ran = `\uD83D\uDD0E ${label}: tsc over ${files.length} test file(s)`;
|
|
29270
|
+
if (head.errors.length === 0) {
|
|
29271
|
+
activeOutput().info(`${ran}: no type error.`);
|
|
29272
|
+
return GREEN;
|
|
29273
|
+
}
|
|
29274
|
+
const base = await baseErrors(input, label, modules, files.filter((file) => head.errors.some((error) => error.file === file.head)));
|
|
29275
|
+
if ("outcome" in base)
|
|
29276
|
+
return base.outcome;
|
|
29277
|
+
const added = newErrors(head.errors, base.errors);
|
|
29278
|
+
activeOutput().info(`${ran}: ${head.errors.length} error(s), ${head.errors.length - added.length} already held at ${input.base}, ${added.length} not held.`);
|
|
29279
|
+
for (const line of listed(added))
|
|
29280
|
+
activeOutput().info(` ${line}`);
|
|
29281
|
+
if (added.length === 0)
|
|
29282
|
+
return GREEN;
|
|
29283
|
+
return { ran: true, red: true, interrupted: false, blocker: typeBlockerText(label, input.base, added) };
|
|
29284
|
+
}
|
|
29285
|
+
|
|
29286
|
+
// src/start/task-step-checks.ts
|
|
29287
|
+
function lintTask(context, seams, input) {
|
|
29288
|
+
const isInterrupted = context.isInterrupted ?? (() => false);
|
|
29289
|
+
return runLintStep({ checkout: context.checkout, base: input.base, task: input.task, git: seams.git, runLint: seams.runLint, stopCode: SIGINT_EXIT_CODE, isInterrupted });
|
|
29290
|
+
}
|
|
29291
|
+
function typeCheckTask(context, seams, input) {
|
|
29292
|
+
const isInterrupted = context.isInterrupted ?? (() => false);
|
|
29293
|
+
return runTypeStep({ checkout: context.checkout, base: input.base, task: input.task, git: seams.git, runTypes: seams.runTypes, stopCode: SIGINT_EXIT_CODE, isInterrupted });
|
|
29294
|
+
}
|
|
29295
|
+
async function taskDiffChecks(context, seams, input, result) {
|
|
29296
|
+
if (isStepInterrupted(context, result))
|
|
29297
|
+
return {};
|
|
29298
|
+
const lint = await lintTask(context, seams, input);
|
|
29299
|
+
if (lint.interrupted)
|
|
29300
|
+
return { lint };
|
|
29301
|
+
return { lint, types: await typeCheckTask(context, seams, input) };
|
|
29302
|
+
}
|
|
28819
29303
|
// src/start/suite-stage-step.ts
|
|
28820
|
-
import { readFileSync as
|
|
29304
|
+
import { readFileSync as readFileSync43 } from "fs";
|
|
28821
29305
|
function stageSince(trackerPath, baseline) {
|
|
28822
29306
|
const taken = readStageLedger(stageLedgerPathFor(trackerPath)).filter((entry) => entry.commit !== null);
|
|
28823
29307
|
return taken.at(-1)?.commit ?? baseline?.commit ?? null;
|
|
@@ -28873,7 +29357,7 @@ async function runStageStep(context, stage, baseline) {
|
|
|
28873
29357
|
}
|
|
28874
29358
|
async function runDueStageSteps(context, baseline) {
|
|
28875
29359
|
const ledger = readStageLedger(stageLedgerPathFor(context.trackerPath));
|
|
28876
|
-
const due = dueStages(
|
|
29360
|
+
const due = dueStages(readFileSync43(context.trackerPath, "utf8"), ledger);
|
|
28877
29361
|
const outcomes = [];
|
|
28878
29362
|
for (const stage of due) {
|
|
28879
29363
|
const outcome2 = await runStageStep(context, stage, baseline);
|
|
@@ -28900,19 +29384,20 @@ function seamsOf(context) {
|
|
|
28900
29384
|
listTestFiles: seams.listTestFiles ?? listTestFiles,
|
|
28901
29385
|
readPreloadFiles: seams.readPreloadFiles ?? readPreloadFiles,
|
|
28902
29386
|
now: seams.now ?? (() => new Date),
|
|
28903
|
-
runLint: seams.runLint ?? runEslint
|
|
29387
|
+
runLint: seams.runLint ?? runEslint,
|
|
29388
|
+
runTypes: seams.runTypes ?? runTsc
|
|
28904
29389
|
};
|
|
28905
29390
|
}
|
|
28906
29391
|
function junitFileFor(repoRoot, sessionId, kind) {
|
|
28907
|
-
return
|
|
29392
|
+
return join37(runsDir(repoRoot), sessionId, "suite", `${kind}.junit.xml`);
|
|
28908
29393
|
}
|
|
28909
29394
|
function alwaysRunJunitFileFor(repoRoot, sessionId, kind = "task") {
|
|
28910
|
-
return
|
|
29395
|
+
return join37(runsDir(repoRoot), sessionId, "suite", `${kind}-always-run.junit.xml`);
|
|
28911
29396
|
}
|
|
28912
29397
|
function stageLedgerPathFor(trackerPath) {
|
|
28913
29398
|
const baselinePath = baselinePathFor(trackerPath);
|
|
28914
29399
|
const name2 = basename11(baselinePath).replace(BASELINE_FILE_PREFIX, STAGE_LEDGER_PREFIX);
|
|
28915
|
-
return
|
|
29400
|
+
return join37(dirname15(baselinePath), name2);
|
|
28916
29401
|
}
|
|
28917
29402
|
function isLedgerEntry(value) {
|
|
28918
29403
|
if (typeof value !== "object" || value === null)
|
|
@@ -28936,11 +29421,11 @@ function parseLedger(text) {
|
|
|
28936
29421
|
return stages;
|
|
28937
29422
|
}
|
|
28938
29423
|
function readStageLedger(path3) {
|
|
28939
|
-
if (!
|
|
29424
|
+
if (!existsSync36(path3))
|
|
28940
29425
|
return [];
|
|
28941
29426
|
let parsed2;
|
|
28942
29427
|
try {
|
|
28943
|
-
parsed2 = parseLedger(
|
|
29428
|
+
parsed2 = parseLedger(readFileSync44(path3, "utf8"));
|
|
28944
29429
|
} catch (error) {
|
|
28945
29430
|
parsed2 = messageOf(error);
|
|
28946
29431
|
}
|
|
@@ -28950,11 +29435,11 @@ function readStageLedger(path3) {
|
|
|
28950
29435
|
return [];
|
|
28951
29436
|
}
|
|
28952
29437
|
function writeStageLedger(path3, entries) {
|
|
28953
|
-
|
|
29438
|
+
mkdirSync14(dirname15(path3), { recursive: true });
|
|
28954
29439
|
const temporary = `${path3}.${process.pid}.tmp`;
|
|
28955
|
-
|
|
29440
|
+
writeFileSync17(temporary, `${JSON.stringify({ version: STAGE_LEDGER_VERSION, stages: entries }, null, 2)}
|
|
28956
29441
|
`, "utf8");
|
|
28957
|
-
|
|
29442
|
+
renameSync7(temporary, path3);
|
|
28958
29443
|
}
|
|
28959
29444
|
function addToLedger(trackerPath, added) {
|
|
28960
29445
|
if (added.length === 0)
|
|
@@ -28998,9 +29483,9 @@ function readDiff(git, from) {
|
|
|
28998
29483
|
}
|
|
28999
29484
|
function verdictOf3(result, baseline) {
|
|
29000
29485
|
const { fresh, known } = splitFailures(result.failures, baseline);
|
|
29001
|
-
const
|
|
29486
|
+
const newErrors2 = Math.max(0, (result.errors ?? 0) - (baseline?.errors ?? 0));
|
|
29002
29487
|
const unreported2 = result.exitCode !== 0 && result.summary === null && result.noTestFiles !== true;
|
|
29003
|
-
return { fresh, known, newErrors, unreported: unreported2 };
|
|
29488
|
+
return { fresh, known, newErrors: newErrors2, unreported: unreported2 };
|
|
29004
29489
|
}
|
|
29005
29490
|
function isStepInterrupted(context, result) {
|
|
29006
29491
|
return result.exitCode === SIGINT_EXIT_CODE || context.isInterrupted?.() === true;
|
|
@@ -29038,6 +29523,15 @@ function announce2(label, result, known) {
|
|
|
29038
29523
|
if (known.length > KNOWN_LISTED)
|
|
29039
29524
|
activeOutput().info(` ...and ${known.length - KNOWN_LISTED} more.`);
|
|
29040
29525
|
}
|
|
29526
|
+
function checksOf(settling) {
|
|
29527
|
+
return [settling.lint, settling.types].filter((check) => check !== undefined);
|
|
29528
|
+
}
|
|
29529
|
+
function isCheckInterrupted(settling) {
|
|
29530
|
+
return checksOf(settling).some((check) => check.interrupted);
|
|
29531
|
+
}
|
|
29532
|
+
function checkBlockers(settling) {
|
|
29533
|
+
return checksOf(settling).flatMap((check) => check.blocker === null ? [] : [check.blocker]);
|
|
29534
|
+
}
|
|
29041
29535
|
function settleInterrupted(seams, settling) {
|
|
29042
29536
|
const { kind, label, result } = settling;
|
|
29043
29537
|
const step = { ...stepOf(settling, result, []), interrupted: true };
|
|
@@ -29048,32 +29542,32 @@ function settleInterrupted(seams, settling) {
|
|
|
29048
29542
|
}
|
|
29049
29543
|
function settleStep(context, seams, settling) {
|
|
29050
29544
|
const { kind, label, result, baseline } = settling;
|
|
29051
|
-
if (isStepInterrupted(context, result) || settling
|
|
29545
|
+
if (isStepInterrupted(context, result) || isCheckInterrupted(settling))
|
|
29052
29546
|
return settleInterrupted(seams, settling);
|
|
29053
29547
|
const verdict = verdictOf3(result, baseline);
|
|
29054
29548
|
const step = stepOf(settling, result, verdict.fresh);
|
|
29055
29549
|
recordStep(seams, step);
|
|
29056
29550
|
announce2(label, result, verdict.known);
|
|
29057
|
-
const
|
|
29058
|
-
if (!isRed(verdict) &&
|
|
29551
|
+
const checked = checkBlockers(settling);
|
|
29552
|
+
if (!isRed(verdict) && checked.length === 0)
|
|
29059
29553
|
return { kind, step, red: false, interrupted: false, blocker: null, blockedLine: null, repairInserted: false };
|
|
29060
29554
|
const tested = isRed(verdict) ? [blockerText(label, result, verdict)] : [];
|
|
29061
|
-
const blocker = [...tested, ...
|
|
29555
|
+
const blocker = [...tested, ...checked].join(" ");
|
|
29062
29556
|
activeOutput().error(`\u274C ${blocker}`);
|
|
29063
29557
|
const written4 = settling.repair === null ? null : writeRepairTask(context.trackerPath, blocker, { ...settling.repair, commit: readHead(seams.git) });
|
|
29064
29558
|
if (written4 !== null)
|
|
29065
29559
|
activeOutput().error(written4.inserted ? ` A repair task (line ${written4.line + 1}) is inserted, blocked on it.` : ` The repair task (line ${written4.line + 1}) is marked blocked on it again.`);
|
|
29066
29560
|
return { kind, step, red: true, interrupted: false, blocker, blockedLine: written4?.line ?? null, repairInserted: written4?.inserted ?? false };
|
|
29067
29561
|
}
|
|
29068
|
-
function isErrorsOnly(verdict,
|
|
29069
|
-
return verdict.newErrors > 0 && verdict.fresh.length === 0 && !verdict.unreported && (
|
|
29562
|
+
function isErrorsOnly(verdict, settling) {
|
|
29563
|
+
return verdict.newErrors > 0 && verdict.fresh.length === 0 && !verdict.unreported && checkBlockers(settling).length === 0;
|
|
29070
29564
|
}
|
|
29071
29565
|
async function retakeOnErrors(context, seams, settling, rerun) {
|
|
29072
29566
|
const { label, result, baseline } = settling;
|
|
29073
|
-
if (isStepInterrupted(context, result) || settling
|
|
29567
|
+
if (isStepInterrupted(context, result) || isCheckInterrupted(settling))
|
|
29074
29568
|
return settling;
|
|
29075
29569
|
const verdict = verdictOf3(result, baseline);
|
|
29076
|
-
if (!isErrorsOnly(verdict, settling
|
|
29570
|
+
if (!isErrorsOnly(verdict, settling))
|
|
29077
29571
|
return settling;
|
|
29078
29572
|
recordStep(seams, stepOf(settling, result, verdict.fresh));
|
|
29079
29573
|
announce2(label, result, verdict.known);
|
|
@@ -29116,7 +29610,7 @@ async function ensureBaseline(context) {
|
|
|
29116
29610
|
activeOutput().warn(`\u26A0\uFE0F Could not write ${basename11(path3)} (${messageOf(error)}); the next run records the baseline again.`);
|
|
29117
29611
|
}
|
|
29118
29612
|
const outcome2 = settleStep(context, seams, settling);
|
|
29119
|
-
const covered = completeStages(
|
|
29613
|
+
const covered = completeStages(readFileSync44(context.trackerPath, "utf8"));
|
|
29120
29614
|
addToLedger(context.trackerPath, covered.map((stage) => ({ ...stage, commit, via: "baseline" })));
|
|
29121
29615
|
return { baseline, step: outcome2.step, interrupted: false };
|
|
29122
29616
|
}
|
|
@@ -29159,10 +29653,6 @@ async function runWithAlwaysRun(context, seams, kind, runs) {
|
|
|
29159
29653
|
const second = await runOne(context, seams, kind, { paths: runs.alwaysRun }, junitFile);
|
|
29160
29654
|
return foldResults(first, second, SIGINT_EXIT_CODE);
|
|
29161
29655
|
}
|
|
29162
|
-
function lintTask(context, seams, input) {
|
|
29163
|
-
const isInterrupted = context.isInterrupted ?? (() => false);
|
|
29164
|
-
return runLintStep({ checkout: context.checkout, base: input.base, task: input.task, git: seams.git, runLint: seams.runLint, stopCode: SIGINT_EXIT_CODE, isInterrupted });
|
|
29165
|
-
}
|
|
29166
29656
|
async function runTaskStep(context, input) {
|
|
29167
29657
|
const seams = seamsOf(context);
|
|
29168
29658
|
const scope = await taskScopeOf(context, seams, input);
|
|
@@ -29170,8 +29660,8 @@ async function runTaskStep(context, input) {
|
|
|
29170
29660
|
const recorded = scope?.scope ?? "full";
|
|
29171
29661
|
const result = await runWithAlwaysRun(context, seams, "task", runs);
|
|
29172
29662
|
const label = `task step after "${input.task}"`;
|
|
29173
|
-
const
|
|
29174
|
-
const settling = { kind: "task", scope: recorded, reason: taskStepReason(scope), label, result, baseline: input.baseline, repair: { kind: "task", task: input.task }, ...
|
|
29663
|
+
const checks = await taskDiffChecks(context, seams, input, result);
|
|
29664
|
+
const settling = { kind: "task", scope: recorded, reason: taskStepReason(scope), label, result, baseline: input.baseline, repair: { kind: "task", task: input.task }, ...checks };
|
|
29175
29665
|
const outcome2 = settleStep(context, seams, await retakeOnErrors(context, seams, settling, () => runWithAlwaysRun(context, seams, "task", runs)));
|
|
29176
29666
|
const timedIn = runs.alwaysRun.length > 0 ? alwaysRunJunitFileFor(context.repoRoot, context.sessionId) : junitFileFor(context.repoRoot, context.sessionId, "task");
|
|
29177
29667
|
if (!outcome2.interrupted)
|
|
@@ -29485,13 +29975,13 @@ function similarCommentOpening(score, threshold) {
|
|
|
29485
29975
|
}
|
|
29486
29976
|
function possibleDuplicatesSection(duplicates2, redact) {
|
|
29487
29977
|
const intro = `No open bug scored at or above the similarity threshold of ${String(duplicates2.threshold)}.`;
|
|
29488
|
-
const
|
|
29978
|
+
const listed2 = duplicates2.nearest.length === 0 ? "No open bug shares a word with this one." : duplicates2.nearest.map(({ issue: issue3, score }) => `- ${issueNameOf(issue3.ref)} (score ${scoreText(score)})`).join(`
|
|
29489
29979
|
`);
|
|
29490
29980
|
return `## Possible duplicates
|
|
29491
29981
|
|
|
29492
29982
|
${redact(`${intro}
|
|
29493
29983
|
|
|
29494
|
-
${
|
|
29984
|
+
${listed2}`)}`;
|
|
29495
29985
|
}
|
|
29496
29986
|
function issueNameOf(ref8) {
|
|
29497
29987
|
return ref8.url ?? `${ref8.kind} issue ${ref8.externalId}`;
|
|
@@ -29527,8 +30017,8 @@ function issueText(opening, values, redact) {
|
|
|
29527
30017
|
}
|
|
29528
30018
|
|
|
29529
30019
|
// src/triage/local-paths.ts
|
|
29530
|
-
import { realpathSync as
|
|
29531
|
-
import { resolve as
|
|
30020
|
+
import { realpathSync as realpathSync6 } from "fs";
|
|
30021
|
+
import { resolve as resolve23 } from "path";
|
|
29532
30022
|
var HOME_MARKER = "[redacted: HOME]";
|
|
29533
30023
|
var SEGMENT = "[^/\\s'\"`()<>:,;]+";
|
|
29534
30024
|
var HOME_PATTERN = `(?:/${SEGMENT})*?/(?:Users|home)/${SEGMENT}`;
|
|
@@ -29539,9 +30029,9 @@ function literalPattern(text) {
|
|
|
29539
30029
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29540
30030
|
}
|
|
29541
30031
|
function forms(path3) {
|
|
29542
|
-
const given =
|
|
30032
|
+
const given = resolve23(path3);
|
|
29543
30033
|
try {
|
|
29544
|
-
const real =
|
|
30034
|
+
const real = realpathSync6(given);
|
|
29545
30035
|
return real === given ? [given] : [given, real];
|
|
29546
30036
|
} catch {
|
|
29547
30037
|
return [given];
|
|
@@ -29572,11 +30062,11 @@ function localPathRedactor(repoRoot, home) {
|
|
|
29572
30062
|
|
|
29573
30063
|
// src/triage/triage.ts
|
|
29574
30064
|
import { homedir as homedir8 } from "os";
|
|
29575
|
-
import { join as
|
|
30065
|
+
import { join as join38 } from "path";
|
|
29576
30066
|
|
|
29577
30067
|
// src/effort/store/tracker-refs.ts
|
|
29578
30068
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
29579
|
-
import { existsSync as
|
|
30069
|
+
import { existsSync as existsSync37 } from "fs";
|
|
29580
30070
|
var SELECT_HELD2 = `
|
|
29581
30071
|
SELECT seq, tracker_ref FROM findings
|
|
29582
30072
|
WHERE session_id = ? AND artifact = ?
|
|
@@ -29725,7 +30215,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
29725
30215
|
wanted.json,
|
|
29726
30216
|
collectedAt
|
|
29727
30217
|
];
|
|
29728
|
-
const [action, stored] = withSqliteStore(path3, "write", !
|
|
30218
|
+
const [action, stored] = withSqliteStore(path3, "write", !existsSync37(path3), (db) => {
|
|
29729
30219
|
const place = db.transaction(() => placeRef(db, path3, write, wanted, superseded, insertValues));
|
|
29730
30220
|
return place.immediate();
|
|
29731
30221
|
});
|
|
@@ -29734,7 +30224,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
29734
30224
|
function readTrackerRef(repoRoot, artifact) {
|
|
29735
30225
|
checkArtifact("read", artifact);
|
|
29736
30226
|
const path3 = sqliteStorePath(repoRoot);
|
|
29737
|
-
if (!
|
|
30227
|
+
if (!existsSync37(path3))
|
|
29738
30228
|
return null;
|
|
29739
30229
|
const newest = withSqliteStore(path3, "read", false, (db) => db.query(SELECT_NEWEST).get(artifact));
|
|
29740
30230
|
return newest === null ? null : heldRefOf(path3, newest.seq, newest.tracker_ref).ref;
|
|
@@ -29804,7 +30294,7 @@ function createArtifactRefsVerifier(repoRoot, env) {
|
|
|
29804
30294
|
// src/triage/similarity.ts
|
|
29805
30295
|
import { basename as basename14 } from "path";
|
|
29806
30296
|
var NON_WORD2 = /\W+/;
|
|
29807
|
-
var
|
|
30297
|
+
var FENCE_LINE7 = /^`{3,}$/;
|
|
29808
30298
|
var HEADING_LINE3 = /^## (.+)$/;
|
|
29809
30299
|
function wordSetOf(text) {
|
|
29810
30300
|
const words = strippedText(text).toLowerCase().split(NON_WORD2).filter((word) => word.length > 0);
|
|
@@ -29824,7 +30314,7 @@ function fencedValueAt(lines, start) {
|
|
|
29824
30314
|
if (open === -1)
|
|
29825
30315
|
return null;
|
|
29826
30316
|
const fence = lines[start + open];
|
|
29827
|
-
if (!
|
|
30317
|
+
if (!FENCE_LINE7.test(fence))
|
|
29828
30318
|
return null;
|
|
29829
30319
|
const body = lines.slice(start + open + 1);
|
|
29830
30320
|
const close = body.indexOf(fence);
|
|
@@ -29863,12 +30353,12 @@ function nearestOpenBugs(report2, openBugs) {
|
|
|
29863
30353
|
}
|
|
29864
30354
|
|
|
29865
30355
|
// src/triage/triage.ts
|
|
29866
|
-
var PRIVATE_TRIAGE_DIR =
|
|
30356
|
+
var PRIVATE_TRIAGE_DIR = join38(".rafa", "triage", "private");
|
|
29867
30357
|
var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
|
|
29868
30358
|
var TRIAGE_MODULE = "unassigned";
|
|
29869
30359
|
var BLOCKER_SEPARATOR2 = "; ";
|
|
29870
30360
|
function privateTriageDir(repoRoot) {
|
|
29871
|
-
return
|
|
30361
|
+
return join38(repoRoot, PRIVATE_TRIAGE_DIR);
|
|
29872
30362
|
}
|
|
29873
30363
|
function createPrivateTriageTracker(repoRoot, options = {}) {
|
|
29874
30364
|
return createLocalTracker({
|
|
@@ -30047,10 +30537,10 @@ async function answerMiss(run) {
|
|
|
30047
30537
|
const list = tracker.openIssues;
|
|
30048
30538
|
if (similarity === null || list === undefined)
|
|
30049
30539
|
return fileNew(run);
|
|
30050
|
-
const
|
|
30051
|
-
if (!
|
|
30052
|
-
return resultOf(run, "failed", { problem:
|
|
30053
|
-
const nearest = nearestOpenBugs(run.filing.compared,
|
|
30540
|
+
const listed2 = await step(run, `the ${run.route.channel} tracker openIssues`, () => list.call(tracker, "bug"));
|
|
30541
|
+
if (!listed2.ok)
|
|
30542
|
+
return resultOf(run, "failed", { problem: listed2.problem });
|
|
30543
|
+
const nearest = nearestOpenBugs(run.filing.compared, listed2.value);
|
|
30054
30544
|
const [first] = nearest;
|
|
30055
30545
|
return first !== undefined && first.score >= similarity.threshold ? commentOnNearest(run, first, similarity.threshold) : fileNew(run, { duplicates: { threshold: similarity.threshold, nearest: nearest.slice(0, similarity.candidates) } });
|
|
30056
30546
|
}
|
|
@@ -30230,9 +30720,9 @@ function describeStartTriage(result, inherited = new Map) {
|
|
|
30230
30720
|
};
|
|
30231
30721
|
}
|
|
30232
30722
|
async function resolvePublicTracker(options, redact) {
|
|
30233
|
-
const
|
|
30723
|
+
const resolve24 = options.resolve ?? resolveTracker;
|
|
30234
30724
|
try {
|
|
30235
|
-
const { tracker, attempts } = await
|
|
30725
|
+
const { tracker, attempts } = await resolve24({
|
|
30236
30726
|
config: options.config,
|
|
30237
30727
|
context: { repoRoot: options.repoRoot }
|
|
30238
30728
|
});
|
|
@@ -30419,8 +30909,128 @@ function aheadOf(track) {
|
|
|
30419
30909
|
return Number(count);
|
|
30420
30910
|
}
|
|
30421
30911
|
|
|
30912
|
+
// src/cleanup/groups.ts
|
|
30913
|
+
import { basename as basename15 } from "path";
|
|
30914
|
+
|
|
30915
|
+
// src/cleanup/past-head.ts
|
|
30916
|
+
import { posix as posix4 } from "path";
|
|
30917
|
+
var PATH_TERMINATOR = "\x00";
|
|
30918
|
+
var FIELD_SEPARATOR = "\t";
|
|
30919
|
+
function unread3(command, result) {
|
|
30920
|
+
const said = gitSaid(result);
|
|
30921
|
+
return {
|
|
30922
|
+
kind: "unread",
|
|
30923
|
+
detail: said === "" ? `git ${command} failed` : `git ${command} failed: ${said}`
|
|
30924
|
+
};
|
|
30925
|
+
}
|
|
30926
|
+
function terminated(output) {
|
|
30927
|
+
return output.split(PATH_TERMINATOR).filter((entry) => entry !== "");
|
|
30928
|
+
}
|
|
30929
|
+
function isAncestor(git, head, tip) {
|
|
30930
|
+
if (!git(["rev-parse", "--verify", "--quiet", `${head}^{commit}`]).ok) {
|
|
30931
|
+
return false;
|
|
30932
|
+
}
|
|
30933
|
+
const result = git(["merge-base", "--is-ancestor", head, tip]);
|
|
30934
|
+
if (result.ok) {
|
|
30935
|
+
return true;
|
|
30936
|
+
}
|
|
30937
|
+
return result.stderr.trim() === "" ? false : unread3("merge-base --is-ancestor", result);
|
|
30938
|
+
}
|
|
30939
|
+
function receiptIds(git, baseRef, changelog) {
|
|
30940
|
+
const path3 = posix4.normalize(changelog.replace(/\\/g, "/"));
|
|
30941
|
+
const listed2 = git(["ls-tree", "-z", "--name-only", baseRef, "--", path3]);
|
|
30942
|
+
if (!listed2.ok) {
|
|
30943
|
+
return unread3("ls-tree", listed2);
|
|
30944
|
+
}
|
|
30945
|
+
if (!terminated(listed2.stdout).includes(path3)) {
|
|
30946
|
+
return new Set;
|
|
30947
|
+
}
|
|
30948
|
+
const shown2 = git(["cat-file", "blob", `${baseRef}:${path3}`]);
|
|
30949
|
+
if (!shown2.ok) {
|
|
30950
|
+
return unread3("cat-file", shown2);
|
|
30951
|
+
}
|
|
30952
|
+
return new Set(changelogSections(shown2.stdout).flatMap((section2) => section2.receipt ?? []));
|
|
30953
|
+
}
|
|
30954
|
+
function readBaseHolding(git, baseRef, settings) {
|
|
30955
|
+
const prefix = directoryPrefix(settings.fragments);
|
|
30956
|
+
const listed2 = git(["ls-tree", "-r", "-z", "--name-only", baseRef, "--", prefix === "" ? "." : prefix]);
|
|
30957
|
+
if (!listed2.ok) {
|
|
30958
|
+
return unread3("ls-tree", listed2);
|
|
30959
|
+
}
|
|
30960
|
+
const ids = receiptIds(git, baseRef, settings.changelog);
|
|
30961
|
+
if ("kind" in ids) {
|
|
30962
|
+
return ids;
|
|
30963
|
+
}
|
|
30964
|
+
return { paths: new Set(terminated(listed2.stdout)), receiptIds: ids };
|
|
30965
|
+
}
|
|
30966
|
+
function isHeldFragment(path3, prefix, holding) {
|
|
30967
|
+
if (!path3.startsWith(prefix)) {
|
|
30968
|
+
return false;
|
|
30969
|
+
}
|
|
30970
|
+
const name2 = path3.slice(prefix.length);
|
|
30971
|
+
const id2 = name2.includes("/") ? null : fragmentIdOf(name2);
|
|
30972
|
+
if (id2 === null) {
|
|
30973
|
+
return false;
|
|
30974
|
+
}
|
|
30975
|
+
return holding.paths.has(path3) || holding.receiptIds.has(id2);
|
|
30976
|
+
}
|
|
30977
|
+
function commitsIn(git, range) {
|
|
30978
|
+
const logged = git(["log", "--reverse", `--format=%H${FIELD_SEPARATOR}%s`, range, "--"]);
|
|
30979
|
+
if (!logged.ok) {
|
|
30980
|
+
return unread3("log", logged);
|
|
30981
|
+
}
|
|
30982
|
+
return logged.stdout.split(`
|
|
30983
|
+
`).filter((line) => line !== "").map((line) => {
|
|
30984
|
+
const at = line.indexOf(FIELD_SEPARATOR);
|
|
30985
|
+
return at === -1 ? { hash: line, subject: "" } : { hash: line.slice(0, at), subject: line.slice(at + 1) };
|
|
30986
|
+
});
|
|
30987
|
+
}
|
|
30988
|
+
function readPastHead(git, branch, pullRequest, settings) {
|
|
30989
|
+
const tip = `refs/heads/${branch}`;
|
|
30990
|
+
const descends = isAncestor(git, pullRequest.headRefOid, tip);
|
|
30991
|
+
if (descends === false) {
|
|
30992
|
+
return { kind: "not-descended" };
|
|
30993
|
+
}
|
|
30994
|
+
if (descends !== true) {
|
|
30995
|
+
return descends;
|
|
30996
|
+
}
|
|
30997
|
+
const listed2 = commitsIn(git, `${pullRequest.headRefOid}..${tip}`);
|
|
30998
|
+
if ("kind" in listed2) {
|
|
30999
|
+
return listed2;
|
|
31000
|
+
}
|
|
31001
|
+
if (listed2.length === 0) {
|
|
31002
|
+
return { kind: "past-head", pullRequest, count: 0, commits: [], held: true };
|
|
31003
|
+
}
|
|
31004
|
+
const holding = readBaseHolding(git, `refs/heads/${settings.base}`, settings);
|
|
31005
|
+
if ("kind" in holding) {
|
|
31006
|
+
return holding;
|
|
31007
|
+
}
|
|
31008
|
+
const prefix = directoryPrefix(settings.fragments);
|
|
31009
|
+
const commits = [];
|
|
31010
|
+
for (const { hash, subject } of listed2) {
|
|
31011
|
+
const touched = git(["diff-tree", "-r", "-m", "--no-commit-id", "--name-only", "-z", hash]);
|
|
31012
|
+
if (!touched.ok) {
|
|
31013
|
+
return unread3("diff-tree", touched);
|
|
31014
|
+
}
|
|
31015
|
+
const paths = [...new Set(terminated(touched.stdout))];
|
|
31016
|
+
const held = paths.every((path3) => isHeldFragment(path3, prefix, holding));
|
|
31017
|
+
commits.push({ hash, subject, paths, held });
|
|
31018
|
+
}
|
|
31019
|
+
return {
|
|
31020
|
+
kind: "past-head",
|
|
31021
|
+
pullRequest,
|
|
31022
|
+
count: commits.length,
|
|
31023
|
+
commits,
|
|
31024
|
+
held: commits.every((each) => each.held)
|
|
31025
|
+
};
|
|
31026
|
+
}
|
|
31027
|
+
|
|
30422
31028
|
// src/cleanup/groups.ts
|
|
30423
31029
|
var MERGED_STATE_UNKNOWN = "merged state unknown";
|
|
31030
|
+
var DEFAULT_RELEASE_PATHS = Object.freeze({
|
|
31031
|
+
fragments: RELEASE_DEFAULTS.releaseFragments,
|
|
31032
|
+
changelog: RELEASE_DEFAULTS.releaseChangelog
|
|
31033
|
+
});
|
|
30424
31034
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1000;
|
|
30425
31035
|
function unreachableNote(detail) {
|
|
30426
31036
|
return `pull request provider unreachable (${detail}); merged pull requests not read, grouped from git alone`;
|
|
@@ -30428,6 +31038,13 @@ function unreachableNote(detail) {
|
|
|
30428
31038
|
function baseUnreadNote(base, detail) {
|
|
30429
31039
|
return `could not read which branches are merged into ${base} (${detail}); none is listed as merged into it`;
|
|
30430
31040
|
}
|
|
31041
|
+
function pastHeadUnreadNote(branch, pullRequest, detail) {
|
|
31042
|
+
return `could not read the commits ${branch} holds past #${String(pullRequest)}'s head (${detail}); its delete is not forced`;
|
|
31043
|
+
}
|
|
31044
|
+
function pastHeadReason(reading2) {
|
|
31045
|
+
const subjects = reading2.commits.map((commit) => commit.subject).join(", ");
|
|
31046
|
+
return `${plural4(reading2.count, "commit")} past #${String(reading2.pullRequest.number)}'s head: ${subjects}`;
|
|
31047
|
+
}
|
|
30431
31048
|
async function readProviderMerges(pulls) {
|
|
30432
31049
|
if (pulls === null) {
|
|
30433
31050
|
return { state: "none" };
|
|
@@ -30435,8 +31052,25 @@ async function readProviderMerges(pulls) {
|
|
|
30435
31052
|
try {
|
|
30436
31053
|
return { state: "read", pullRequests: await pulls.listMerged() };
|
|
30437
31054
|
} catch (error) {
|
|
30438
|
-
return { state: "unreachable", detail:
|
|
31055
|
+
return { state: "unreachable", detail: firstLine8(messageOf(error)) };
|
|
31056
|
+
}
|
|
31057
|
+
}
|
|
31058
|
+
function heldReason(holder) {
|
|
31059
|
+
return `checked out in ${holder.name} (${holder.blockers.join(", ")})`;
|
|
31060
|
+
}
|
|
31061
|
+
function holdBranchRows(rows, worktrees) {
|
|
31062
|
+
const holders = new Map;
|
|
31063
|
+
for (const row of worktrees) {
|
|
31064
|
+
if (row.tickable || row.branch === null) {
|
|
31065
|
+
continue;
|
|
31066
|
+
}
|
|
31067
|
+
const blockers = [...new Set(row.blockers.map((blocker) => blocker.kind))];
|
|
31068
|
+
holders.set(row.branch, { path: row.path, name: basename15(row.path), blockers });
|
|
30439
31069
|
}
|
|
31070
|
+
return rows.map((row) => {
|
|
31071
|
+
const holder = holders.get(row.branch.name);
|
|
31072
|
+
return holder === undefined ? row : { ...row, ticked: false, heldBy: holder, reason: `${row.reason}; ${heldReason(holder)}` };
|
|
31073
|
+
});
|
|
30440
31074
|
}
|
|
30441
31075
|
function classifyBranches(git, read, provider, settings) {
|
|
30442
31076
|
const notes = provider.state === "unreachable" ? [unreachableNote(provider.detail)] : [];
|
|
@@ -30458,12 +31092,20 @@ function classifyBranches(git, read, provider, settings) {
|
|
|
30458
31092
|
unknown: provider.state === "none",
|
|
30459
31093
|
exclude: mergedRead.ok ? [baseRef] : []
|
|
30460
31094
|
};
|
|
31095
|
+
const unreached = {
|
|
31096
|
+
git,
|
|
31097
|
+
base: read.base,
|
|
31098
|
+
baseRead: mergedRead.ok,
|
|
31099
|
+
release: settings.release ?? DEFAULT_RELEASE_PATHS,
|
|
31100
|
+
notes
|
|
31101
|
+
};
|
|
30461
31102
|
for (const branch of read.branches) {
|
|
30462
31103
|
const pulls = named3.get(branch.name) ?? [];
|
|
30463
31104
|
const atTip = pulls.find((pull) => pull.headRefOid === tips.get(branch.name)) ?? null;
|
|
30464
31105
|
const mergedBy = mergedReadings(branch, reachable.has(branch.name), atTip);
|
|
30465
31106
|
if (mergedBy.length > 0) {
|
|
30466
|
-
|
|
31107
|
+
const row = mergedRow(branch, mergedBy, atTip, read.base);
|
|
31108
|
+
merged.push(mergedBy.includes("base") || atTip !== null ? row : unreachedRow(unreached, row, pulls));
|
|
30467
31109
|
continue;
|
|
30468
31110
|
}
|
|
30469
31111
|
const suffix = rowSuffix(context, pulls[0] ?? null);
|
|
@@ -30481,13 +31123,36 @@ function classifyBranches(git, read, provider, settings) {
|
|
|
30481
31123
|
stale.push({ group: "stale", branch, ticked: false, idleDays, reason: `no commit in ${plural4(idleDays, "day")}${suffix}` });
|
|
30482
31124
|
}
|
|
30483
31125
|
}
|
|
30484
|
-
|
|
31126
|
+
const worktrees = settings.worktrees ?? [];
|
|
31127
|
+
return {
|
|
31128
|
+
ok: true,
|
|
31129
|
+
base: read.base,
|
|
31130
|
+
merged: holdBranchRows(merged, worktrees),
|
|
31131
|
+
stale: holdBranchRows(stale, worktrees),
|
|
31132
|
+
notPushed: holdBranchRows(notPushed, worktrees),
|
|
31133
|
+
notes
|
|
31134
|
+
};
|
|
31135
|
+
}
|
|
31136
|
+
function unreachedRow(context, row, pulls) {
|
|
31137
|
+
const settings = { base: context.base, ...context.release };
|
|
31138
|
+
for (const pull of pulls) {
|
|
31139
|
+
const reading2 = readPastHead(context.git, row.branch.name, pull, settings);
|
|
31140
|
+
if (reading2.kind === "past-head") {
|
|
31141
|
+
return { ...row, ticked: false, pastHead: reading2, reason: `${row.reason}; ${pastHeadReason(reading2)}` };
|
|
31142
|
+
}
|
|
31143
|
+
if (reading2.kind === "unread") {
|
|
31144
|
+
context.notes.push(pastHeadUnreadNote(row.branch.name, pull.number, firstLine8(reading2.detail)));
|
|
31145
|
+
break;
|
|
31146
|
+
}
|
|
31147
|
+
}
|
|
31148
|
+
const why = context.baseRead ? `${context.base} does not reach its tip` : `whether ${context.base} reaches its tip could not be read`;
|
|
31149
|
+
return { ...row, ticked: false, reason: `${row.reason}; ${why}` };
|
|
30485
31150
|
}
|
|
30486
31151
|
function namedPullRequests(pullRequests, branches) {
|
|
30487
|
-
const
|
|
31152
|
+
const listed2 = new Set(branches.map((branch) => branch.name));
|
|
30488
31153
|
const named3 = new Map;
|
|
30489
31154
|
for (const pull of pullRequests) {
|
|
30490
|
-
if (
|
|
31155
|
+
if (listed2.has(pull.headRefName)) {
|
|
30491
31156
|
named3.set(pull.headRefName, [...named3.get(pull.headRefName) ?? [], pull]);
|
|
30492
31157
|
}
|
|
30493
31158
|
}
|
|
@@ -30556,7 +31221,7 @@ function countLost(git, name2, exclude) {
|
|
|
30556
31221
|
return count;
|
|
30557
31222
|
}
|
|
30558
31223
|
function failure3(what, result) {
|
|
30559
|
-
const said =
|
|
31224
|
+
const said = firstLine8(gitSaid(result));
|
|
30560
31225
|
return said === "" ? `${what} failed` : `${what} failed: ${said}`;
|
|
30561
31226
|
}
|
|
30562
31227
|
function plural4(count, noun) {
|
|
@@ -30566,26 +31231,87 @@ function lines(text) {
|
|
|
30566
31231
|
return text.split(`
|
|
30567
31232
|
`).filter((line) => line !== "");
|
|
30568
31233
|
}
|
|
30569
|
-
function
|
|
31234
|
+
function firstLine8(text) {
|
|
30570
31235
|
return text.split(`
|
|
30571
31236
|
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
30572
31237
|
}
|
|
30573
31238
|
|
|
31239
|
+
// src/cleanup/runs.ts
|
|
31240
|
+
import { existsSync as existsSync38, readdirSync as readdirSync15, readFileSync as readFileSync45 } from "fs";
|
|
31241
|
+
import { join as join39 } from "path";
|
|
31242
|
+
var LIVE_STATES = new Set(["running", "paused"]);
|
|
31243
|
+
function readRunRecords(root, seams = {}) {
|
|
31244
|
+
const dir = runsDir(root);
|
|
31245
|
+
const isAlive = seams.isAlive ?? isPidAlive;
|
|
31246
|
+
let names2;
|
|
31247
|
+
try {
|
|
31248
|
+
names2 = readdirSync15(dir);
|
|
31249
|
+
} catch (error) {
|
|
31250
|
+
if (errorCode(error) === "ENOENT")
|
|
31251
|
+
return { runs: [], notes: [] };
|
|
31252
|
+
return { runs: [], notes: [`run records: cannot list ${dir}: ${messageOf(error)}`] };
|
|
31253
|
+
}
|
|
31254
|
+
const notes = [];
|
|
31255
|
+
const finished = [];
|
|
31256
|
+
for (const name2 of names2.filter((entry) => entry.endsWith(RECORD_EXTENSION)).sort()) {
|
|
31257
|
+
const file = join39(dir, name2);
|
|
31258
|
+
const read = readRecord(file);
|
|
31259
|
+
if (typeof read === "string") {
|
|
31260
|
+
notes.push(read);
|
|
31261
|
+
continue;
|
|
31262
|
+
}
|
|
31263
|
+
if (!LIVE_STATES.has(readState(read, isAlive)))
|
|
31264
|
+
finished.push({ file, record: read });
|
|
31265
|
+
}
|
|
31266
|
+
const sorted = [...finished].sort((a, b) => byStart(a.record, b.record));
|
|
31267
|
+
const newest = new Map;
|
|
31268
|
+
for (const entry of sorted)
|
|
31269
|
+
newest.set(planKey(entry.record), entry);
|
|
31270
|
+
const kept = new Set(newest.values());
|
|
31271
|
+
const runs = sorted.filter((entry) => !kept.has(entry)).map((entry) => runRow(dir, entry));
|
|
31272
|
+
return { runs, notes };
|
|
31273
|
+
}
|
|
31274
|
+
function readRecord(file) {
|
|
31275
|
+
try {
|
|
31276
|
+
return parseSessionRecord(readFileSync45(file, "utf8"), file);
|
|
31277
|
+
} catch (error) {
|
|
31278
|
+
return `run records: skipped ${file}: ${messageOf(error)}`;
|
|
31279
|
+
}
|
|
31280
|
+
}
|
|
31281
|
+
function planKey(record) {
|
|
31282
|
+
return record.planStub === null ? `plan:${record.plan}` : `stub:${record.planStub}`;
|
|
31283
|
+
}
|
|
31284
|
+
function byStart(a, b) {
|
|
31285
|
+
return Date.parse(a.startedAt) - Date.parse(b.startedAt) || a.sessionId.localeCompare(b.sessionId);
|
|
31286
|
+
}
|
|
31287
|
+
function runRow(dir, entry) {
|
|
31288
|
+
const { record } = entry;
|
|
31289
|
+
const events = join39(dir, `${record.sessionId}${EVENTS_EXTENSION}`);
|
|
31290
|
+
return {
|
|
31291
|
+
sessionId: record.sessionId,
|
|
31292
|
+
path: entry.file,
|
|
31293
|
+
eventsPath: existsSync38(events) ? events : null,
|
|
31294
|
+
plan: record.planStub ?? record.plan,
|
|
31295
|
+
startedAt: record.startedAt,
|
|
31296
|
+
ticked: true
|
|
31297
|
+
};
|
|
31298
|
+
}
|
|
31299
|
+
|
|
30574
31300
|
// src/cleanup/worktrees.ts
|
|
30575
|
-
import { realpathSync as
|
|
30576
|
-
import { join as
|
|
31301
|
+
import { realpathSync as realpathSync8, statSync as statSync15 } from "fs";
|
|
31302
|
+
import { join as join41, sep as sep4 } from "path";
|
|
30577
31303
|
|
|
30578
31304
|
// src/pr/worktree.ts
|
|
30579
|
-
import { existsSync as
|
|
30580
|
-
import { join as
|
|
30581
|
-
var WORKTREES_SUBDIR =
|
|
31305
|
+
import { existsSync as existsSync39, realpathSync as realpathSync7 } from "fs";
|
|
31306
|
+
import { join as join40 } from "path";
|
|
31307
|
+
var WORKTREES_SUBDIR = join40(".rafa", "worktrees");
|
|
30582
31308
|
var WORKTREE_PREFIX = "pr-";
|
|
30583
31309
|
var DEFAULT_REMOTE3 = "origin";
|
|
30584
31310
|
var REFUSAL_PREFIX2 = "rafa pr triage --resolve refuses";
|
|
30585
31311
|
var FORCING_FLAG = /^(?:-f|--force(?:-with-lease|-if-includes)?(?:=.*)?|--mirror)$/;
|
|
30586
31312
|
var FORCING_REFSPEC_MARK = "+";
|
|
30587
31313
|
function resolveWorktreePath(home, number) {
|
|
30588
|
-
return
|
|
31314
|
+
return join40(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX}${number}`);
|
|
30589
31315
|
}
|
|
30590
31316
|
function crossRepositoryRefusal(pr) {
|
|
30591
31317
|
if (!pr.isCrossRepository)
|
|
@@ -30652,20 +31378,20 @@ function pushResolved(git, branch, remote = DEFAULT_REMOTE3) {
|
|
|
30652
31378
|
return { id: step2.id, ok: false, command: worktreeCommandLine(step2), said: refusal4 };
|
|
30653
31379
|
}
|
|
30654
31380
|
function pathForms(path3) {
|
|
30655
|
-
if (!
|
|
31381
|
+
if (!existsSync39(path3))
|
|
30656
31382
|
return [path3];
|
|
30657
|
-
return [path3,
|
|
31383
|
+
return [path3, realpathSync7(path3)];
|
|
30658
31384
|
}
|
|
30659
31385
|
function worktreeAt(git, path3) {
|
|
30660
|
-
const
|
|
30661
|
-
if (!
|
|
31386
|
+
const listed2 = git(["worktree", "list", "--porcelain"]);
|
|
31387
|
+
if (!listed2.ok)
|
|
30662
31388
|
return null;
|
|
30663
31389
|
const forms2 = pathForms(path3);
|
|
30664
|
-
return parseWorktrees(
|
|
31390
|
+
return parseWorktrees(listed2.stdout).find((entry) => forms2.includes(entry.path)) ?? null;
|
|
30665
31391
|
}
|
|
30666
31392
|
|
|
30667
31393
|
// src/cleanup/worktrees.ts
|
|
30668
|
-
var CLAUDE_WORKTREES_SUBDIR =
|
|
31394
|
+
var CLAUDE_WORKTREES_SUBDIR = join41(".claude", "worktrees");
|
|
30669
31395
|
var WORKTREE_LIST = Object.freeze(["worktree", "list", "--porcelain"]);
|
|
30670
31396
|
var GIT_DIR = Object.freeze(["rev-parse", "--absolute-git-dir"]);
|
|
30671
31397
|
var WORKTREE_STATUS = Object.freeze([
|
|
@@ -30674,11 +31400,11 @@ var WORKTREE_STATUS = Object.freeze([
|
|
|
30674
31400
|
"--porcelain=v1",
|
|
30675
31401
|
"--untracked-files=all"
|
|
30676
31402
|
]);
|
|
30677
|
-
var ADMIN_FILES = Object.freeze(["HEAD",
|
|
31403
|
+
var ADMIN_FILES = Object.freeze(["HEAD", join41("logs", "HEAD"), "index"]);
|
|
30678
31404
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1000;
|
|
30679
31405
|
var UNTRACKED_MARK = "?? ";
|
|
30680
31406
|
var LOCKED_LINE = "locked";
|
|
30681
|
-
var
|
|
31407
|
+
var LIVE_STATES2 = new Set(["running", "paused"]);
|
|
30682
31408
|
function defaultWorktreeSeams(cwd) {
|
|
30683
31409
|
return {
|
|
30684
31410
|
git: createGitRunner(cwd),
|
|
@@ -30693,7 +31419,7 @@ function defaultWorktreeSeams(cwd) {
|
|
|
30693
31419
|
},
|
|
30694
31420
|
realPath: (path3) => {
|
|
30695
31421
|
try {
|
|
30696
|
-
return
|
|
31422
|
+
return realpathSync8(path3);
|
|
30697
31423
|
} catch {
|
|
30698
31424
|
return path3;
|
|
30699
31425
|
}
|
|
@@ -30701,21 +31427,21 @@ function defaultWorktreeSeams(cwd) {
|
|
|
30701
31427
|
};
|
|
30702
31428
|
}
|
|
30703
31429
|
function readWorktrees(seams, read) {
|
|
30704
|
-
const
|
|
30705
|
-
if (!
|
|
30706
|
-
return { ok: false, detail: failure4("git worktree list",
|
|
31430
|
+
const listed2 = seams.git(WORKTREE_LIST);
|
|
31431
|
+
if (!listed2.ok) {
|
|
31432
|
+
return { ok: false, detail: failure4("git worktree list", listed2) };
|
|
30707
31433
|
}
|
|
30708
|
-
const entries = parseWorktrees(
|
|
31434
|
+
const entries = parseWorktrees(listed2.stdout);
|
|
30709
31435
|
const main = entries[0]?.path;
|
|
30710
31436
|
if (main === undefined) {
|
|
30711
31437
|
return { ok: true, worktrees: [] };
|
|
30712
31438
|
}
|
|
30713
31439
|
const roots = [
|
|
30714
|
-
...pathForms2(seams,
|
|
30715
|
-
...pathForms2(seams,
|
|
31440
|
+
...pathForms2(seams, join41(main, CLAUDE_WORKTREES_SUBDIR)),
|
|
31441
|
+
...pathForms2(seams, join41(read.home, WORKTREES_SUBDIR)),
|
|
30716
31442
|
...pathForms2(seams, worktreeDirAt(read.projectRoot, read.worktreeDir))
|
|
30717
31443
|
];
|
|
30718
|
-
const locks = parseLocks(
|
|
31444
|
+
const locks = parseLocks(listed2.stdout);
|
|
30719
31445
|
const context = {
|
|
30720
31446
|
seams,
|
|
30721
31447
|
read,
|
|
@@ -30783,7 +31509,7 @@ function lastModified(seams, git, path3) {
|
|
|
30783
31509
|
if (!gitDir.ok || adminDir === "") {
|
|
30784
31510
|
return failure4("git rev-parse --absolute-git-dir", gitDir);
|
|
30785
31511
|
}
|
|
30786
|
-
const times = [path3, ...ADMIN_FILES.map((file) =>
|
|
31512
|
+
const times = [path3, ...ADMIN_FILES.map((file) => join41(adminDir, file))].map((file) => seams.modifiedAt(file)).filter((time) => time !== null).map((time) => time.getTime());
|
|
30787
31513
|
if (times.length === 0) {
|
|
30788
31514
|
return `no modification time could be read under ${path3} or ${adminDir}`;
|
|
30789
31515
|
}
|
|
@@ -30835,7 +31561,7 @@ function recentBlockers(modified, read) {
|
|
|
30835
31561
|
}
|
|
30836
31562
|
function readLive(seams, root) {
|
|
30837
31563
|
try {
|
|
30838
|
-
return seams.sessions(root).filter((record) =>
|
|
31564
|
+
return seams.sessions(root).filter((record) => LIVE_STATES2.has(record.state));
|
|
30839
31565
|
} catch (error) {
|
|
30840
31566
|
return `the session records under ${runsDir(root)} could not be read: ${messageOf(error)}`;
|
|
30841
31567
|
}
|
|
@@ -30858,7 +31584,7 @@ function pathForms2(seams, path3) {
|
|
|
30858
31584
|
return resolved === path3 ? [path3] : [path3, resolved];
|
|
30859
31585
|
}
|
|
30860
31586
|
function isInside3(path3, dir) {
|
|
30861
|
-
const prefix = dir.endsWith(
|
|
31587
|
+
const prefix = dir.endsWith(sep4) ? dir : `${dir}${sep4}`;
|
|
30862
31588
|
return path3.startsWith(prefix);
|
|
30863
31589
|
}
|
|
30864
31590
|
function failure4(what, result) {
|
|
@@ -30870,7 +31596,14 @@ function plural5(count, noun) {
|
|
|
30870
31596
|
return count === 1 ? `1 ${noun}` : `${String(count)} ${noun}s`;
|
|
30871
31597
|
}
|
|
30872
31598
|
// src/cleanup/steps.ts
|
|
31599
|
+
import { rmSync as rmSync6 } from "fs";
|
|
31600
|
+
var SHORT_HASH_LENGTH2 = 7;
|
|
30873
31601
|
var FORCING_FLAG2 = /^(?:--force(?:=.*)?|-[^-]*f[^-]*)$/;
|
|
31602
|
+
var DEFAULT_CLEANUP_FILES = Object.freeze({
|
|
31603
|
+
remove: (path3) => {
|
|
31604
|
+
rmSync6(path3);
|
|
31605
|
+
}
|
|
31606
|
+
});
|
|
30874
31607
|
function frozenStep2(step2) {
|
|
30875
31608
|
return Object.freeze({ ...step2, argv: Object.freeze([...step2.argv]) });
|
|
30876
31609
|
}
|
|
@@ -30891,30 +31624,72 @@ function deleteBranchStep(branch, forced, after = null) {
|
|
|
30891
31624
|
after
|
|
30892
31625
|
});
|
|
30893
31626
|
}
|
|
31627
|
+
function removeRunStep(row) {
|
|
31628
|
+
const events = row.eventsPath === null ? [] : [row.eventsPath];
|
|
31629
|
+
return frozenStep2({
|
|
31630
|
+
kind: "remove-run",
|
|
31631
|
+
subject: row.path,
|
|
31632
|
+
argv: ["rm", row.path, ...events],
|
|
31633
|
+
after: null
|
|
31634
|
+
});
|
|
31635
|
+
}
|
|
30894
31636
|
function needsForcedDelete(row) {
|
|
30895
|
-
|
|
31637
|
+
if (row.mergedBy.includes("base")) {
|
|
31638
|
+
return false;
|
|
31639
|
+
}
|
|
31640
|
+
if (row.mergedBy.includes("pull-request") && row.pullRequest !== null) {
|
|
31641
|
+
return true;
|
|
31642
|
+
}
|
|
31643
|
+
return row.pastHead?.held === true;
|
|
31644
|
+
}
|
|
31645
|
+
function unheldPastHeadReason(reading2) {
|
|
31646
|
+
const unheld = reading2.commits.filter((commit) => !commit.held);
|
|
31647
|
+
const named3 = unheld.map((commit) => `${commit.hash.slice(0, SHORT_HASH_LENGTH2)} ${commit.subject}`).join(", ");
|
|
31648
|
+
const head = `#${String(reading2.pullRequest.number)}'s head`;
|
|
31649
|
+
const counted5 = unheld.length === 1 ? `1 commit past ${head} is not a release fragment` : `${String(unheld.length)} commits past ${head} are not release fragments`;
|
|
31650
|
+
return `${counted5} the base holds: ${named3}; deleting it would lose them`;
|
|
30896
31651
|
}
|
|
30897
31652
|
function branchPicks(selection) {
|
|
30898
31653
|
return [
|
|
30899
|
-
...selection.merged.map((row) => ({
|
|
30900
|
-
|
|
30901
|
-
|
|
31654
|
+
...selection.merged.map((row) => ({
|
|
31655
|
+
name: row.branch.name,
|
|
31656
|
+
merged: true,
|
|
31657
|
+
forced: needsForcedDelete(row),
|
|
31658
|
+
holder: row.heldBy ?? null,
|
|
31659
|
+
unheld: row.pastHead === undefined || row.pastHead.held ? null : row.pastHead
|
|
31660
|
+
})),
|
|
31661
|
+
...[...selection.stale, ...selection.notPushed].map((row) => ({
|
|
31662
|
+
name: row.branch.name,
|
|
31663
|
+
merged: false,
|
|
31664
|
+
forced: true,
|
|
31665
|
+
holder: row.heldBy ?? null,
|
|
31666
|
+
unheld: null
|
|
31667
|
+
}))
|
|
30902
31668
|
];
|
|
30903
31669
|
}
|
|
30904
|
-
function
|
|
31670
|
+
function heldReason2(path3) {
|
|
30905
31671
|
return `held by the worktree at ${path3}; only a Merged branch is deleted with its worktree`;
|
|
30906
31672
|
}
|
|
30907
31673
|
function blockedHolderReason(path3) {
|
|
30908
31674
|
return `held by the worktree at ${path3}, which is not removed`;
|
|
30909
31675
|
}
|
|
31676
|
+
function untickableHolderReason(holder) {
|
|
31677
|
+
return `${heldReason(holder)}, which is not removed`;
|
|
31678
|
+
}
|
|
30910
31679
|
function branchStep(pick, removed, blocked3) {
|
|
31680
|
+
if (pick.holder !== null) {
|
|
31681
|
+
return { kind: "delete-branch", subject: pick.name, reason: untickableHolderReason(pick.holder) };
|
|
31682
|
+
}
|
|
31683
|
+
if (pick.unheld !== null) {
|
|
31684
|
+
return { kind: "delete-branch", subject: pick.name, reason: unheldPastHeadReason(pick.unheld) };
|
|
31685
|
+
}
|
|
30911
31686
|
const blockedPath = blocked3.get(pick.name);
|
|
30912
31687
|
if (blockedPath !== undefined) {
|
|
30913
31688
|
return { kind: "delete-branch", subject: pick.name, reason: blockedHolderReason(blockedPath) };
|
|
30914
31689
|
}
|
|
30915
31690
|
const path3 = removed.get(pick.name) ?? null;
|
|
30916
31691
|
if (path3 !== null && !pick.merged) {
|
|
30917
|
-
return { kind: "delete-branch", subject: pick.name, reason:
|
|
31692
|
+
return { kind: "delete-branch", subject: pick.name, reason: heldReason2(path3) };
|
|
30918
31693
|
}
|
|
30919
31694
|
return deleteBranchStep(pick.name, pick.forced, path3);
|
|
30920
31695
|
}
|
|
@@ -30931,7 +31706,8 @@ function cleanupSteps(selection) {
|
|
|
30931
31706
|
return Object.freeze({
|
|
30932
31707
|
steps: Object.freeze([
|
|
30933
31708
|
...removable.map((row) => removeWorktreeStep2(row.path)),
|
|
30934
|
-
...branchEntries.filter(isStep)
|
|
31709
|
+
...branchEntries.filter(isStep),
|
|
31710
|
+
...(selection.runs ?? []).filter((row) => row.ticked).map((row) => removeRunStep(row))
|
|
30935
31711
|
]),
|
|
30936
31712
|
withheld: Object.freeze([
|
|
30937
31713
|
...untickable.map((row) => ({ kind: "remove-worktree", subject: row.path, reason: row.reason })),
|
|
@@ -30961,19 +31737,32 @@ function afterFailedReason(path3) {
|
|
|
30961
31737
|
function notRun(step2, said) {
|
|
30962
31738
|
return { step: step2, ran: false, ok: false, command: cleanupCommandLine(step2), said };
|
|
30963
31739
|
}
|
|
30964
|
-
function
|
|
31740
|
+
function removeRun(files, step2) {
|
|
31741
|
+
const command = cleanupCommandLine(step2);
|
|
31742
|
+
for (const path3 of step2.argv.slice(1)) {
|
|
31743
|
+
try {
|
|
31744
|
+
files.remove(path3);
|
|
31745
|
+
} catch (error) {
|
|
31746
|
+
return { step: step2, ran: true, ok: false, command, said: `cannot remove ${path3}: ${messageOf(error)}` };
|
|
31747
|
+
}
|
|
31748
|
+
}
|
|
31749
|
+
return { step: step2, ran: true, ok: true, command, said: "" };
|
|
31750
|
+
}
|
|
31751
|
+
function runStep2(git, files, step2, removed) {
|
|
30965
31752
|
const refusal4 = forcedFlagRefusal(step2.argv);
|
|
30966
31753
|
if (refusal4 !== null)
|
|
30967
31754
|
return notRun(step2, refusal4);
|
|
30968
31755
|
if (step2.after !== null && !removed.has(step2.after))
|
|
30969
31756
|
return notRun(step2, afterFailedReason(step2.after));
|
|
31757
|
+
if (step2.kind === "remove-run")
|
|
31758
|
+
return removeRun(files, step2);
|
|
30970
31759
|
const result = git(step2.argv.slice(1));
|
|
30971
31760
|
return { step: step2, ran: true, ok: result.ok, command: cleanupCommandLine(step2), said: gitSaid(result) };
|
|
30972
31761
|
}
|
|
30973
|
-
function runCleanupSteps(git, plan) {
|
|
31762
|
+
function runCleanupSteps(git, plan, files = DEFAULT_CLEANUP_FILES) {
|
|
30974
31763
|
const removed = new Set;
|
|
30975
31764
|
return plan.steps.map((step2) => {
|
|
30976
|
-
const outcome2 = runStep2(git, step2, removed);
|
|
31765
|
+
const outcome2 = runStep2(git, files, step2, removed);
|
|
30977
31766
|
if (outcome2.ok && step2.kind === "remove-worktree")
|
|
30978
31767
|
removed.add(step2.subject);
|
|
30979
31768
|
return outcome2;
|
|
@@ -30995,7 +31784,7 @@ async function readCleanup(seams, settings) {
|
|
|
30995
31784
|
const result = seams.git(FETCH_PRUNE);
|
|
30996
31785
|
fetched = result.ok;
|
|
30997
31786
|
if (!result.ok) {
|
|
30998
|
-
notes.push(fetchFailedNote(
|
|
31787
|
+
notes.push(fetchFailedNote(firstLine9(gitSaid(result)) || "git said nothing"));
|
|
30999
31788
|
}
|
|
31000
31789
|
}
|
|
31001
31790
|
const branches = readBranches(seams.git, { base: settings.base, keep: settings.keep });
|
|
@@ -31005,7 +31794,8 @@ async function readCleanup(seams, settings) {
|
|
|
31005
31794
|
const provider = await readProviderMerges(seams.pulls);
|
|
31006
31795
|
const groups = classifyBranches(seams.git, branches, provider, {
|
|
31007
31796
|
staleDays: settings.staleDays,
|
|
31008
|
-
now: settings.now
|
|
31797
|
+
now: settings.now,
|
|
31798
|
+
...settings.release === undefined ? {} : { release: settings.release }
|
|
31009
31799
|
});
|
|
31010
31800
|
if (!groups.ok) {
|
|
31011
31801
|
return groups;
|
|
@@ -31022,15 +31812,17 @@ async function readCleanup(seams, settings) {
|
|
|
31022
31812
|
if (!worktrees.ok) {
|
|
31023
31813
|
return worktrees;
|
|
31024
31814
|
}
|
|
31815
|
+
const runs = readRunRecords(settings.projectRoot, seams);
|
|
31025
31816
|
return {
|
|
31026
31817
|
ok: true,
|
|
31027
31818
|
base: groups.base,
|
|
31028
31819
|
fetched,
|
|
31029
|
-
merged: groups.merged,
|
|
31030
|
-
stale: groups.stale,
|
|
31031
|
-
notPushed: groups.notPushed,
|
|
31820
|
+
merged: holdBranchRows(groups.merged, worktrees.worktrees),
|
|
31821
|
+
stale: holdBranchRows(groups.stale, worktrees.worktrees),
|
|
31822
|
+
notPushed: holdBranchRows(groups.notPushed, worktrees.worktrees),
|
|
31032
31823
|
worktrees: worktrees.worktrees,
|
|
31033
|
-
|
|
31824
|
+
runs: runs.runs,
|
|
31825
|
+
notes: [...notes, ...groups.notes, ...runs.notes]
|
|
31034
31826
|
};
|
|
31035
31827
|
}
|
|
31036
31828
|
function cleanupCounts(read) {
|
|
@@ -31041,7 +31833,7 @@ function cleanupCounts(read) {
|
|
|
31041
31833
|
worktrees: read.worktrees.length
|
|
31042
31834
|
};
|
|
31043
31835
|
}
|
|
31044
|
-
function
|
|
31836
|
+
function firstLine9(text) {
|
|
31045
31837
|
return text.split(`
|
|
31046
31838
|
`).map((line) => line.trim()).find((line) => line !== "") ?? "";
|
|
31047
31839
|
}
|
|
@@ -31068,9 +31860,76 @@ async function retargetPullRequest(pull, base, seams) {
|
|
|
31068
31860
|
return { kind: "retargeted", from, to: base };
|
|
31069
31861
|
}
|
|
31070
31862
|
|
|
31863
|
+
// src/start/release-body.ts
|
|
31864
|
+
var RELEASE_BODY_SEAMS = {
|
|
31865
|
+
pulls: ghPullRequestsIn,
|
|
31866
|
+
readProvider: (repoRoot) => resolvePrProvider({ configured: null, dir: repoRoot })
|
|
31867
|
+
};
|
|
31868
|
+
function unwritten(number, problem) {
|
|
31869
|
+
return { number, carried: false, already: false, problem };
|
|
31870
|
+
}
|
|
31871
|
+
async function carryIntoBody(pulls, branch, write) {
|
|
31872
|
+
try {
|
|
31873
|
+
const found = await pulls.findOpen(branch);
|
|
31874
|
+
if (found === null) {
|
|
31875
|
+
return unwritten(null, `no open pull request was found for ${branch} to write it to`);
|
|
31876
|
+
}
|
|
31877
|
+
const detail = await pulls.get(found.number);
|
|
31878
|
+
if (detail === null) {
|
|
31879
|
+
return unwritten(found.number, `pull request #${found.number} could not be read back`);
|
|
31880
|
+
}
|
|
31881
|
+
const next = bodyWithRelease(detail.body, write.sentence, write.block);
|
|
31882
|
+
if (next === detail.body) {
|
|
31883
|
+
return { number: found.number, carried: true, already: true, problem: null };
|
|
31884
|
+
}
|
|
31885
|
+
await pulls.editBody(found.number, next);
|
|
31886
|
+
return { number: found.number, carried: true, already: false, problem: null };
|
|
31887
|
+
} catch (error) {
|
|
31888
|
+
return unwritten(null, `the pull request body could not be written: ${messageOf(error)}`);
|
|
31889
|
+
}
|
|
31890
|
+
}
|
|
31891
|
+
function noProviderProblem(reading2) {
|
|
31892
|
+
const origin = reading2.remote === null ? "origin is not set" : `origin is ${reading2.remote}`;
|
|
31893
|
+
const because = reading2.source === "config" ? "pr.provider says so" : origin;
|
|
31894
|
+
return `this repository resolves to pr.provider: ${reading2.provider}, because ${because},` + " so there is no pull request to write it to";
|
|
31895
|
+
}
|
|
31896
|
+
function linesSubject(count) {
|
|
31897
|
+
return count === 1 ? { noun: "That line", verb: "is" } : { noun: "Those lines", verb: "are" };
|
|
31898
|
+
}
|
|
31899
|
+
function announceBody(write, count) {
|
|
31900
|
+
const out = activeOutput();
|
|
31901
|
+
const { noun, verb } = linesSubject(count);
|
|
31902
|
+
if (write.problem !== null) {
|
|
31903
|
+
out.error(` ${noun} ${verb} not in the pull request body: ${write.problem}`);
|
|
31904
|
+
return;
|
|
31905
|
+
}
|
|
31906
|
+
if (write.already) {
|
|
31907
|
+
out.info(` Pull request #${write.number} already carries ${noun.toLowerCase()}.`);
|
|
31908
|
+
return;
|
|
31909
|
+
}
|
|
31910
|
+
out.info(` ${noun} ${verb} now in the body of pull request #${write.number}.`);
|
|
31911
|
+
}
|
|
31912
|
+
async function writeBody(seams, target, write) {
|
|
31913
|
+
const reading2 = seams.readProvider(target.repoRoot);
|
|
31914
|
+
if (reading2.provider !== "gh") {
|
|
31915
|
+
const problem = noProviderProblem(reading2);
|
|
31916
|
+
activeOutput().info(` No pull request body carries ${JSON.stringify(write.lines.join(" "))}: ${problem}.`);
|
|
31917
|
+
return unwritten(null, problem);
|
|
31918
|
+
}
|
|
31919
|
+
const body = await carryIntoBody(seams.pulls(target.repoRoot), target.branch, write);
|
|
31920
|
+
announceBody(body, write.lines.length);
|
|
31921
|
+
return body;
|
|
31922
|
+
}
|
|
31923
|
+
async function carryReleaseIntoPullRequest(finish, target, seams = {}) {
|
|
31924
|
+
const { written: written4, body } = finish;
|
|
31925
|
+
if (written4 === null || body === null || body.number !== null)
|
|
31926
|
+
return null;
|
|
31927
|
+
return writeBody({ ...RELEASE_BODY_SEAMS, ...seams }, target, written4);
|
|
31928
|
+
}
|
|
31929
|
+
|
|
31071
31930
|
// src/release/prepare.ts
|
|
31072
|
-
import { mkdirSync as
|
|
31073
|
-
import { dirname as
|
|
31931
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync46, writeFileSync as writeFileSync18 } from "fs";
|
|
31932
|
+
import { dirname as dirname16, join as join42, posix as posix5 } from "path";
|
|
31074
31933
|
var NO_FRAGMENT = "no release fragment";
|
|
31075
31934
|
var WHITESPACE3 = /\s+/g;
|
|
31076
31935
|
function levelFields(reading2) {
|
|
@@ -31128,18 +31987,18 @@ function readBase(input) {
|
|
|
31128
31987
|
}
|
|
31129
31988
|
function textOf3(path3) {
|
|
31130
31989
|
try {
|
|
31131
|
-
return
|
|
31990
|
+
return readFileSync46(path3, "utf8");
|
|
31132
31991
|
} catch {
|
|
31133
31992
|
return null;
|
|
31134
31993
|
}
|
|
31135
31994
|
}
|
|
31136
31995
|
function nameOf2(path3) {
|
|
31137
|
-
return
|
|
31996
|
+
return posix5.basename(path3);
|
|
31138
31997
|
}
|
|
31139
31998
|
function writeFragment(path3, resolved, text) {
|
|
31140
31999
|
try {
|
|
31141
|
-
|
|
31142
|
-
|
|
32000
|
+
mkdirSync15(dirname16(resolved), { recursive: true });
|
|
32001
|
+
writeFileSync18(resolved, text);
|
|
31143
32002
|
return null;
|
|
31144
32003
|
} catch (error) {
|
|
31145
32004
|
return `${path3} could not be written: ${messageOf(error)}`;
|
|
@@ -31162,8 +32021,8 @@ function prepareRelease(input) {
|
|
|
31162
32021
|
const lines2 = fragmentNotes(input, level, title);
|
|
31163
32022
|
const fragment = { plan: input.plan, title, level, notes: lines2.notes };
|
|
31164
32023
|
const name2 = allocateFragmentName(input.plan, read.base.waiting.map((waiting) => nameOf2(waiting.path)));
|
|
31165
|
-
const path3 =
|
|
31166
|
-
const resolved =
|
|
32024
|
+
const path3 = posix5.join(input.settings.releaseFragments.replace(/\\/g, "/"), name2);
|
|
32025
|
+
const resolved = join42(input.repoRoot, path3);
|
|
31167
32026
|
const before = textOf3(resolved);
|
|
31168
32027
|
const after = serializeFragment(fragment);
|
|
31169
32028
|
const problems = lines2.problem === null ? read.problems : [...read.problems, lines2.problem];
|
|
@@ -31183,18 +32042,18 @@ function prepareRelease(input) {
|
|
|
31183
32042
|
}
|
|
31184
32043
|
|
|
31185
32044
|
// src/release/verify.ts
|
|
31186
|
-
import { readFileSync as
|
|
31187
|
-
import { posix as
|
|
32045
|
+
import { readFileSync as readFileSync47, writeFileSync as writeFileSync19 } from "fs";
|
|
32046
|
+
import { posix as posix6 } from "path";
|
|
31188
32047
|
var STATUS_PREFIX_LENGTH = 3;
|
|
31189
32048
|
function textOf4(path3) {
|
|
31190
32049
|
try {
|
|
31191
|
-
return
|
|
32050
|
+
return readFileSync47(path3, "utf8");
|
|
31192
32051
|
} catch {
|
|
31193
32052
|
return null;
|
|
31194
32053
|
}
|
|
31195
32054
|
}
|
|
31196
32055
|
function repoPath2(path3) {
|
|
31197
|
-
const normal =
|
|
32056
|
+
const normal = posix6.normalize(path3.replace(/\\/g, "/"));
|
|
31198
32057
|
return normal.endsWith("/") && normal.length > 1 ? normal.slice(0, -1) : normal;
|
|
31199
32058
|
}
|
|
31200
32059
|
function listOf3(names2) {
|
|
@@ -31204,7 +32063,7 @@ function listOf3(names2) {
|
|
|
31204
32063
|
}
|
|
31205
32064
|
function restoreFragment(file) {
|
|
31206
32065
|
try {
|
|
31207
|
-
|
|
32066
|
+
writeFileSync19(file.resolved, file.after);
|
|
31208
32067
|
return { path: file.path, restored: true, problem: null };
|
|
31209
32068
|
} catch (error) {
|
|
31210
32069
|
return {
|
|
@@ -31307,8 +32166,7 @@ var RELEASE_STAGE_SEAMS = {
|
|
|
31307
32166
|
readNotes: readPlanChanges,
|
|
31308
32167
|
git: createGitRunner,
|
|
31309
32168
|
push: pushBranch,
|
|
31310
|
-
|
|
31311
|
-
readProvider: (repoRoot) => resolvePrProvider({ configured: null, dir: repoRoot }),
|
|
32169
|
+
...RELEASE_BODY_SEAMS,
|
|
31312
32170
|
now: () => new Date
|
|
31313
32171
|
};
|
|
31314
32172
|
var PLAN_HEADING = /^# +(.+)$/;
|
|
@@ -31317,14 +32175,10 @@ function releaseTitle(input) {
|
|
|
31317
32175
|
return planTitleIn(input.planContent, input.planStub);
|
|
31318
32176
|
}
|
|
31319
32177
|
function planTitleIn(planContent, planStub2) {
|
|
31320
|
-
|
|
31321
|
-
`))
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
continue;
|
|
31325
|
-
return (heading[1] ?? "").replace(PLAN_LABEL, "").trim();
|
|
31326
|
-
}
|
|
31327
|
-
return planStub2 ?? "";
|
|
32178
|
+
const headings = planContent.split(`
|
|
32179
|
+
`).filter((line) => line.trim() !== ASSUMPTIONS_HEADING).map((line) => (PLAN_HEADING.exec(line)?.[1] ?? "").trim()).filter((heading) => heading !== "");
|
|
32180
|
+
const chosen = headings.find((heading) => PLAN_LABEL.test(heading)) ?? headings[0];
|
|
32181
|
+
return chosen === undefined ? planStub2 ?? "" : chosen.replace(PLAN_LABEL, "").trim();
|
|
31328
32182
|
}
|
|
31329
32183
|
function announcePreparation(preparation) {
|
|
31330
32184
|
const out = activeOutput();
|
|
@@ -31407,61 +32261,6 @@ function bodyWithSentence(body, sentence) {
|
|
|
31407
32261
|
|
|
31408
32262
|
${sentence}`;
|
|
31409
32263
|
}
|
|
31410
|
-
function unwritten(number, problem) {
|
|
31411
|
-
return { number, carried: false, already: false, problem };
|
|
31412
|
-
}
|
|
31413
|
-
async function carryIntoBody(pulls, branch, write) {
|
|
31414
|
-
try {
|
|
31415
|
-
const found = await pulls.findOpen(branch);
|
|
31416
|
-
if (found === null) {
|
|
31417
|
-
return unwritten(null, `no open pull request was found for ${branch} to write it to`);
|
|
31418
|
-
}
|
|
31419
|
-
const detail = await pulls.get(found.number);
|
|
31420
|
-
if (detail === null) {
|
|
31421
|
-
return unwritten(found.number, `pull request #${found.number} could not be read back`);
|
|
31422
|
-
}
|
|
31423
|
-
const next = bodyWithRelease(detail.body, write.sentence, write.block);
|
|
31424
|
-
if (next === detail.body) {
|
|
31425
|
-
return { number: found.number, carried: true, already: true, problem: null };
|
|
31426
|
-
}
|
|
31427
|
-
await pulls.editBody(found.number, next);
|
|
31428
|
-
return { number: found.number, carried: true, already: false, problem: null };
|
|
31429
|
-
} catch (error) {
|
|
31430
|
-
return unwritten(null, `the pull request body could not be written: ${messageOf(error)}`);
|
|
31431
|
-
}
|
|
31432
|
-
}
|
|
31433
|
-
function noProviderProblem(reading2) {
|
|
31434
|
-
const origin = reading2.remote === null ? "origin is not set" : `origin is ${reading2.remote}`;
|
|
31435
|
-
const because = reading2.source === "config" ? "pr.provider says so" : origin;
|
|
31436
|
-
return `this repository resolves to pr.provider: ${reading2.provider}, because ${because},` + " so there is no pull request to write it to";
|
|
31437
|
-
}
|
|
31438
|
-
function linesSubject(count) {
|
|
31439
|
-
return count === 1 ? { noun: "That line", verb: "is" } : { noun: "Those lines", verb: "are" };
|
|
31440
|
-
}
|
|
31441
|
-
function announceBody(write, count) {
|
|
31442
|
-
const out = activeOutput();
|
|
31443
|
-
const { noun, verb } = linesSubject(count);
|
|
31444
|
-
if (write.problem !== null) {
|
|
31445
|
-
out.error(` ${noun} ${verb} not in the pull request body: ${write.problem}`);
|
|
31446
|
-
return;
|
|
31447
|
-
}
|
|
31448
|
-
if (write.already) {
|
|
31449
|
-
out.info(` Pull request #${write.number} already carries ${noun.toLowerCase()}.`);
|
|
31450
|
-
return;
|
|
31451
|
-
}
|
|
31452
|
-
out.info(` ${noun} ${verb} now in the body of pull request #${write.number}.`);
|
|
31453
|
-
}
|
|
31454
|
-
async function writeBody(io, input, write) {
|
|
31455
|
-
const reading2 = io.readProvider(input.repoRoot);
|
|
31456
|
-
if (reading2.provider !== "gh") {
|
|
31457
|
-
const problem = noProviderProblem(reading2);
|
|
31458
|
-
activeOutput().info(` No pull request body carries ${JSON.stringify(write.lines.join(" "))}: ${problem}.`);
|
|
31459
|
-
return unwritten(null, problem);
|
|
31460
|
-
}
|
|
31461
|
-
const body = await carryIntoBody(io.pulls(input.repoRoot), input.branch, write);
|
|
31462
|
-
announceBody(body, write.lines.length);
|
|
31463
|
-
return body;
|
|
31464
|
-
}
|
|
31465
32264
|
async function reportFailure(io, input, outcome2, sentence, facts) {
|
|
31466
32265
|
const out = activeOutput();
|
|
31467
32266
|
if (outcome2 === "skipped") {
|
|
@@ -31473,8 +32272,9 @@ async function reportFailure(io, input, outcome2, sentence, facts) {
|
|
|
31473
32272
|
}
|
|
31474
32273
|
const parts = { forecast: null, levelReport: facts.levelReport };
|
|
31475
32274
|
const block = releaseBodyBlock(parts);
|
|
31476
|
-
const
|
|
31477
|
-
|
|
32275
|
+
const written4 = { sentence, block, lines: [sentence, ...releaseBodyLines(parts)] };
|
|
32276
|
+
const body = await writeBody(io, input, written4);
|
|
32277
|
+
return { outcome: outcome2, sentence, forecast: null, written: written4, body, ...facts };
|
|
31478
32278
|
}
|
|
31479
32279
|
var NO_RELEASE = {
|
|
31480
32280
|
outcome: "none",
|
|
@@ -31484,6 +32284,7 @@ var NO_RELEASE = {
|
|
|
31484
32284
|
sentence: null,
|
|
31485
32285
|
forecast: null,
|
|
31486
32286
|
levelReport: null,
|
|
32287
|
+
written: null,
|
|
31487
32288
|
body: null
|
|
31488
32289
|
};
|
|
31489
32290
|
async function reportForecast(io, input, forecast, facts) {
|
|
@@ -31493,8 +32294,9 @@ async function reportForecast(io, input, forecast, facts) {
|
|
|
31493
32294
|
out.warn(` \u26A0\uFE0F ${problem}`);
|
|
31494
32295
|
const parts = { forecast, levelReport: facts.levelReport };
|
|
31495
32296
|
const block = releaseBodyBlock(parts);
|
|
31496
|
-
const
|
|
31497
|
-
|
|
32297
|
+
const written4 = { sentence: null, block, lines: releaseBodyLines(parts) };
|
|
32298
|
+
const body = await writeBody(io, input, written4);
|
|
32299
|
+
return { outcome: "released", sentence: null, forecast, written: written4, body, ...facts };
|
|
31498
32300
|
}
|
|
31499
32301
|
async function finishRelease(input, seams = {}) {
|
|
31500
32302
|
const io = { ...RELEASE_STAGE_SEAMS, ...seams };
|
|
@@ -31539,7 +32341,7 @@ async function finishRelease(input, seams = {}) {
|
|
|
31539
32341
|
}
|
|
31540
32342
|
|
|
31541
32343
|
// src/start/runner-pr.ts
|
|
31542
|
-
import { readFileSync as
|
|
32344
|
+
import { readFileSync as readFileSync48 } from "fs";
|
|
31543
32345
|
import path3 from "path";
|
|
31544
32346
|
var RELEASE_NOTES_HEADING = "## Release notes";
|
|
31545
32347
|
var UNFINISHED_WRAP_UP_LINE = "> \u26A0\uFE0F The wrap-up did not finish: no wrap-up session opened this pull request, so the loop opened it. Check that the base is merged in, the release notes were rewritten and the lessons were promoted before merging.";
|
|
@@ -31572,7 +32374,7 @@ function fragmentNotesIn(checkout, fragment) {
|
|
|
31572
32374
|
return [];
|
|
31573
32375
|
let text;
|
|
31574
32376
|
try {
|
|
31575
|
-
text =
|
|
32377
|
+
text = readFileSync48(path3.resolve(checkout, fragment), "utf8");
|
|
31576
32378
|
} catch {
|
|
31577
32379
|
return [];
|
|
31578
32380
|
}
|
|
@@ -31634,7 +32436,7 @@ async function openRunnerPullRequest(input, seams) {
|
|
|
31634
32436
|
}
|
|
31635
32437
|
|
|
31636
32438
|
// src/start/wrap-up.ts
|
|
31637
|
-
import { posix as
|
|
32439
|
+
import { posix as posix8 } from "path";
|
|
31638
32440
|
|
|
31639
32441
|
// src/pr/conflict-sentence.ts
|
|
31640
32442
|
var MECHANICAL_CONFLICT_SENTENCE = "Resolve MECHANICAL conflicts yourself and do not stop for them: dependency version bumps (take the base's version unless this branch deliberately pinned it, and say which in the commit), lockfiles, generated artifacts, and complementary additions where both sides appended different material to the same file (keep BOTH). Stop only for a genuine semantic conflict \u2014 two sides changing the same behaviour incompatibly. In that case commit nothing, leave the branch as it is, and report the conflicting paths and both sides' intent, so a human decides.";
|
|
@@ -31643,7 +32445,7 @@ function mechanicalConflictBullet() {
|
|
|
31643
32445
|
}
|
|
31644
32446
|
|
|
31645
32447
|
// src/start/promoted-check.ts
|
|
31646
|
-
import { isAbsolute as isAbsolute9, posix as
|
|
32448
|
+
import { isAbsolute as isAbsolute9, posix as posix7, relative as relative12 } from "path";
|
|
31647
32449
|
|
|
31648
32450
|
// src/start/promoted.ts
|
|
31649
32451
|
var PROMOTED_BLOCK_KIND = "promoted";
|
|
@@ -31688,7 +32490,7 @@ function parsePromoted(output) {
|
|
|
31688
32490
|
return { status: "unclosed", answers: [], unreadable: [] };
|
|
31689
32491
|
const answers = [];
|
|
31690
32492
|
const unreadable = [];
|
|
31691
|
-
const
|
|
32493
|
+
const firstLine10 = new Map;
|
|
31692
32494
|
block.body.split(`
|
|
31693
32495
|
`).forEach((text, index) => {
|
|
31694
32496
|
if (text.trim() === "")
|
|
@@ -31700,13 +32502,13 @@ function parsePromoted(output) {
|
|
|
31700
32502
|
return;
|
|
31701
32503
|
}
|
|
31702
32504
|
const { id: id2 } = reading2.answer;
|
|
31703
|
-
const earlier =
|
|
32505
|
+
const earlier = firstLine10.get(id2);
|
|
31704
32506
|
if (earlier !== undefined) {
|
|
31705
32507
|
const reason = `\`${id2}\` was already answered at line ${String(earlier)}`;
|
|
31706
32508
|
unreadable.push({ line, text, reason });
|
|
31707
32509
|
return;
|
|
31708
32510
|
}
|
|
31709
|
-
|
|
32511
|
+
firstLine10.set(id2, line);
|
|
31710
32512
|
answers.push(reading2.answer);
|
|
31711
32513
|
});
|
|
31712
32514
|
return { status: "read", answers, unreadable };
|
|
@@ -31734,8 +32536,8 @@ function changedSince(git, head) {
|
|
|
31734
32536
|
return { ok: true, head, paths: new Set(paths) };
|
|
31735
32537
|
}
|
|
31736
32538
|
function repoPath3(path4, repoRoot) {
|
|
31737
|
-
const inside = isAbsolute9(path4) ?
|
|
31738
|
-
const normal =
|
|
32539
|
+
const inside = isAbsolute9(path4) ? relative12(repoRoot, path4) : path4;
|
|
32540
|
+
const normal = posix7.normalize(inside.replaceAll("\\", "/"));
|
|
31739
32541
|
if (normal === "." || normal === ".." || normal.startsWith("../") || isAbsolute9(normal))
|
|
31740
32542
|
return null;
|
|
31741
32543
|
return normal.replace(/^(\.\/)+/, "");
|
|
@@ -31748,7 +32550,7 @@ function promotedOutcome(lesson, named3, changed, repoRoot) {
|
|
|
31748
32550
|
}
|
|
31749
32551
|
function checkPromoted(lessons, reading2, changed, repoRoot) {
|
|
31750
32552
|
const answers = new Map(reading2.answers.map((answer4) => [answer4.id, answer4]));
|
|
31751
|
-
const
|
|
32553
|
+
const listed2 = new Set(lessons.map((lesson) => lesson.id));
|
|
31752
32554
|
const promoted = [];
|
|
31753
32555
|
const skipped2 = [];
|
|
31754
32556
|
const unpromoted = [];
|
|
@@ -31766,7 +32568,7 @@ function checkPromoted(lessons, reading2, changed, repoRoot) {
|
|
|
31766
32568
|
unpromoted.push(outcome2);
|
|
31767
32569
|
}
|
|
31768
32570
|
}
|
|
31769
|
-
const unlisted = reading2.answers.map((answer4) => answer4.id).filter((id2) => !
|
|
32571
|
+
const unlisted = reading2.answers.map((answer4) => answer4.id).filter((id2) => !listed2.has(id2));
|
|
31770
32572
|
return { promoted, skipped: skipped2, unpromoted, unlisted };
|
|
31771
32573
|
}
|
|
31772
32574
|
function unpromotedPhrase(lesson, head) {
|
|
@@ -31934,7 +32736,7 @@ function releaseBullets(release) {
|
|
|
31934
32736
|
];
|
|
31935
32737
|
}
|
|
31936
32738
|
function fragmentDirectory(prepared) {
|
|
31937
|
-
return
|
|
32739
|
+
return posix8.dirname(prepared.file.path);
|
|
31938
32740
|
}
|
|
31939
32741
|
function fragmentRewriteBullet(prepared) {
|
|
31940
32742
|
const { plan, level } = prepared.fragment;
|
|
@@ -31982,9 +32784,10 @@ async function lessonsToPromote(learning) {
|
|
|
31982
32784
|
return [];
|
|
31983
32785
|
}
|
|
31984
32786
|
}
|
|
31985
|
-
async function preserveProgress(planContent, settingSources, release, serving, learning, base, checkout) {
|
|
32787
|
+
async function preserveProgress(planContent, settingSources, release, serving, learning, base, checkout, seams = {}) {
|
|
32788
|
+
const lookup = seams.lookup ?? openPullRequestNumber;
|
|
31986
32789
|
const branch = getCurrentBranch(checkout);
|
|
31987
|
-
const openPullRequest = await
|
|
32790
|
+
const openPullRequest = await lookup(checkout, branch);
|
|
31988
32791
|
return runWrapUpSession({
|
|
31989
32792
|
buildPrompt: (lessons) => buildWrapUpPrompt(branch, base, planContent, openPullRequest, release, lessons),
|
|
31990
32793
|
settingSources,
|
|
@@ -31992,10 +32795,15 @@ async function preserveProgress(planContent, settingSources, release, serving, l
|
|
|
31992
32795
|
learning,
|
|
31993
32796
|
checkout,
|
|
31994
32797
|
branch,
|
|
31995
|
-
succeeded:
|
|
31996
|
-
|
|
32798
|
+
succeeded: async () => progressPreservedLine(await lookup(checkout, branch)),
|
|
32799
|
+
...seams.spawn === undefined ? {} : { spawn: seams.spawn }
|
|
31997
32800
|
});
|
|
31998
32801
|
}
|
|
32802
|
+
function progressPreservedLine(openPullRequest) {
|
|
32803
|
+
return openPullRequest === null ? `
|
|
32804
|
+
\u2705 Progress preserved; no pull request is open on this branch yet \u2014 the loop retries the wrap-up or opens one itself.` : `
|
|
32805
|
+
\u2705 Progress preserved; pull request #${openPullRequest} is open on this branch.`;
|
|
32806
|
+
}
|
|
31999
32807
|
async function runWrapUpSession(run) {
|
|
32000
32808
|
const { learning, checkout } = run;
|
|
32001
32809
|
const lessons = await lessonsToPromote(learning);
|
|
@@ -32011,7 +32819,7 @@ async function runWrapUpSession(run) {
|
|
|
32011
32819
|
\u274C Failed to preserve progress (exit ${session.exitCode}). Please try again.`);
|
|
32012
32820
|
return session.stdout;
|
|
32013
32821
|
}
|
|
32014
|
-
activeOutput().info(run.succeeded);
|
|
32822
|
+
activeOutput().info(typeof run.succeeded === "string" ? run.succeeded : await run.succeeded());
|
|
32015
32823
|
if (learning === null || git === null)
|
|
32016
32824
|
return session.stdout;
|
|
32017
32825
|
await checkWrapUpAnswer({
|
|
@@ -32085,7 +32893,7 @@ async function runWrapUp(input) {
|
|
|
32085
32893
|
session.wrapUpStarted();
|
|
32086
32894
|
activeOutput().info(`
|
|
32087
32895
|
\u2705 All tasks completed!`);
|
|
32088
|
-
activeOutput().info("\uD83E\uDDF9 Wrap-up session starting: promote
|
|
32896
|
+
activeOutput().info("\uD83E\uDDF9 Wrap-up session starting: promote the listed lessons, sync with main, then commit, push and open the PR.");
|
|
32089
32897
|
activeOutput().info(" This is one full Claude session with no intermediate output \u2014 expect several quiet minutes. Interrupting it skips the push and PR; if that happens, run again to retry just this stage.");
|
|
32090
32898
|
emitLoopEvent({ kind: "wrap-up", phase: "fragment" });
|
|
32091
32899
|
const release = prepareReleaseStage({
|
|
@@ -32103,9 +32911,8 @@ async function runWrapUp(input) {
|
|
|
32103
32911
|
dir: checkout
|
|
32104
32912
|
});
|
|
32105
32913
|
const lookup = () => readProvider().provider === "none" ? Promise.resolve(NO_PROVIDER_REASON) : openPullRequestNumber(checkout, expected2.branch);
|
|
32106
|
-
await emitPullRequestEvent("session", expected2.branch, null, lookup);
|
|
32107
32914
|
if (haltIfWrapUpMoved({ expected: expectWrapUpCommits(expected2), before: "release" })) {
|
|
32108
|
-
await emitPullRequestEvent(
|
|
32915
|
+
await emitPullRequestEvent(expected2.branch, null, lookup);
|
|
32109
32916
|
emitLoopEvent({ kind: "halt", reason: "checkout moved" });
|
|
32110
32917
|
return;
|
|
32111
32918
|
}
|
|
@@ -32113,14 +32920,15 @@ async function runWrapUp(input) {
|
|
|
32113
32920
|
const finish = await finishRelease({ repoRoot: checkout, branch: expected2.branch, settings, preparation: release }, { readProvider });
|
|
32114
32921
|
if (readProvider().provider !== "none") {
|
|
32115
32922
|
const delivery = await deliverPullRequest({ branch: expected2.branch, retries: settings.loopWrapUpRetries, previousMessage: finalMessage }, deliverySeamsIn({ ...input, base, fragment: finish.fragment }));
|
|
32116
|
-
await emitPullRequestEvent(
|
|
32923
|
+
await emitPullRequestEvent(expected2.branch, delivery.kind === "delivered" ? delivery.pull.number : null, lookup);
|
|
32117
32924
|
if (delivery.kind === "interrupted")
|
|
32118
32925
|
return;
|
|
32119
32926
|
if (delivery.kind === "blocked")
|
|
32120
32927
|
throw new CommandExit(1, delivery.message);
|
|
32928
|
+
await carryReleaseIntoPullRequest(finish, { repoRoot: checkout, branch: expected2.branch }, { readProvider });
|
|
32121
32929
|
await retargetPullRequest(delivery.pull, base, { pulls: ghPullRequestsIn(checkout), output: activeOutput() });
|
|
32122
32930
|
} else {
|
|
32123
|
-
await emitPullRequestEvent(
|
|
32931
|
+
await emitPullRequestEvent(expected2.branch, null, lookup);
|
|
32124
32932
|
}
|
|
32125
32933
|
if (ciWait) {
|
|
32126
32934
|
emitLoopEvent({ kind: "wrap-up", phase: "ci" });
|
|
@@ -32134,11 +32942,7 @@ async function runWrapUp(input) {
|
|
|
32134
32942
|
session.finished();
|
|
32135
32943
|
}
|
|
32136
32944
|
var NO_PROVIDER_REASON = "no pull request provider is configured";
|
|
32137
|
-
async function emitPullRequestEvent(
|
|
32138
|
-
const textPlace = "delivery";
|
|
32139
|
-
const ownPlace = activeOutputMode() === "text" ? textPlace : "session";
|
|
32140
|
-
if (place !== ownPlace)
|
|
32141
|
-
return;
|
|
32945
|
+
async function emitPullRequestEvent(branch, known, lookup) {
|
|
32142
32946
|
const number = known ?? await lookup();
|
|
32143
32947
|
if (typeof number === "string") {
|
|
32144
32948
|
emitLoopEvent({ kind: "no-pr", reason: number });
|
|
@@ -32275,8 +33079,8 @@ function msUntil(startAt, now = new Date) {
|
|
|
32275
33079
|
return target.getTime() - now.getTime();
|
|
32276
33080
|
}
|
|
32277
33081
|
function sleepFor(ms) {
|
|
32278
|
-
return new Promise((
|
|
32279
|
-
setTimeout(
|
|
33082
|
+
return new Promise((resolve24) => {
|
|
33083
|
+
setTimeout(resolve24, ms);
|
|
32280
33084
|
});
|
|
32281
33085
|
}
|
|
32282
33086
|
async function deferUntil(startAt, now = new Date, sleep = sleepFor) {
|
|
@@ -32287,7 +33091,7 @@ async function deferUntil(startAt, now = new Date, sleep = sleepFor) {
|
|
|
32287
33091
|
|
|
32288
33092
|
// src/start.ts
|
|
32289
33093
|
var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
|
|
32290
|
-
var
|
|
33094
|
+
var interrupted2 = false;
|
|
32291
33095
|
async function start(args, repoRoot) {
|
|
32292
33096
|
refuseDetachedRun(args);
|
|
32293
33097
|
refuseWorktreeBesideCreateBranch(args);
|
|
@@ -32391,7 +33195,7 @@ async function start(args, repoRoot) {
|
|
|
32391
33195
|
sessionId: session.id,
|
|
32392
33196
|
settings: runConfig.config,
|
|
32393
33197
|
planContent,
|
|
32394
|
-
isInterrupted: () =>
|
|
33198
|
+
isInterrupted: () => interrupted2
|
|
32395
33199
|
});
|
|
32396
33200
|
if (!fs.existsSync(trackerPath)) {
|
|
32397
33201
|
activeOutput().info(`\uD83D\uDCCB Creating new plan tracker at ${path4.basename(trackerPath)}...`);
|
|
@@ -32400,14 +33204,14 @@ async function start(args, repoRoot) {
|
|
|
32400
33204
|
activeOutput().info(`\uD83D\uDCCB Resuming from existing ${path4.basename(trackerPath)}...`);
|
|
32401
33205
|
}
|
|
32402
33206
|
process.on("SIGINT", () => {
|
|
32403
|
-
|
|
33207
|
+
interrupted2 = true;
|
|
32404
33208
|
interruptClaudeSessions();
|
|
32405
33209
|
});
|
|
32406
33210
|
while (true) {
|
|
32407
|
-
if (
|
|
33211
|
+
if (interrupted2)
|
|
32408
33212
|
break;
|
|
32409
|
-
await holdWhilePaused({ repoRoot, sessionId: session.id, isInterrupted: () =>
|
|
32410
|
-
if (
|
|
33213
|
+
await holdWhilePaused({ repoRoot, sessionId: session.id, isInterrupted: () => interrupted2 });
|
|
33214
|
+
if (interrupted2)
|
|
32411
33215
|
break;
|
|
32412
33216
|
const trackerContent = fs.readFileSync(trackerPath, "utf8");
|
|
32413
33217
|
const taskInfo = findNextTask(trackerContent);
|
|
@@ -32423,7 +33227,7 @@ async function start(args, repoRoot) {
|
|
|
32423
33227
|
emitLoopEvent({ kind: "halt", reason: "suite step red" });
|
|
32424
33228
|
return;
|
|
32425
33229
|
}
|
|
32426
|
-
if (
|
|
33230
|
+
if (interrupted2)
|
|
32427
33231
|
break;
|
|
32428
33232
|
if (suiteGate === "repair")
|
|
32429
33233
|
continue;
|
|
@@ -32444,7 +33248,7 @@ async function start(args, repoRoot) {
|
|
|
32444
33248
|
ciWait,
|
|
32445
33249
|
ciTimeoutMin,
|
|
32446
33250
|
ciAttempts,
|
|
32447
|
-
isInterrupted: () =>
|
|
33251
|
+
isInterrupted: () => interrupted2
|
|
32448
33252
|
});
|
|
32449
33253
|
break;
|
|
32450
33254
|
}
|
|
@@ -32467,16 +33271,22 @@ async function start(args, repoRoot) {
|
|
|
32467
33271
|
alwaysRun: readAlwaysRunFiles(createGitRunner(checkout), runConfig.config.testsAlwaysRun),
|
|
32468
33272
|
serving,
|
|
32469
33273
|
handout,
|
|
32470
|
-
base
|
|
33274
|
+
base,
|
|
33275
|
+
guard: { expected: expected2, trackerPath }
|
|
32471
33276
|
});
|
|
32472
33277
|
const { exitCode } = dispatch;
|
|
33278
|
+
if (dispatch.halted) {
|
|
33279
|
+
emitLoopEvent({ kind: "task-blocked", position, reason: "checkout moved" });
|
|
33280
|
+
emitLoopEvent({ kind: "halt", reason: "checkout moved" });
|
|
33281
|
+
return;
|
|
33282
|
+
}
|
|
32473
33283
|
const storeReport = async (outcome2) => {
|
|
32474
33284
|
const stored2 = await storeTaskReport({ repoRoot, planStub: planStub2, dispatch, outcome: outcome2, learning });
|
|
32475
33285
|
await triageTask({ trackerPath, lineNum: taskInfo.lineNum, planStub: planStub2, dispatch, outcome: outcome2 });
|
|
32476
33286
|
await hubContact.pushThenPull();
|
|
32477
33287
|
return stored2;
|
|
32478
33288
|
};
|
|
32479
|
-
if (
|
|
33289
|
+
if (interrupted2) {
|
|
32480
33290
|
updateTrackerLine(trackerPath, taskInfo.lineNum, "blocked");
|
|
32481
33291
|
activeOutput().info(`
|
|
32482
33292
|
\u26A0\uFE0F Interrupted. Task marked as blocked. Run again to resume.`);
|
|
@@ -32609,11 +33419,11 @@ function claimOnRemote(attempt3, ahead, context) {
|
|
|
32609
33419
|
const answer4 = keepLocally(attempt3, git, "offline", fetched.reason);
|
|
32610
33420
|
return ahead === null ? answer4 : withAhead(answer4, notTried(ahead, `${REMOTE} could not be reached`));
|
|
32611
33421
|
}
|
|
32612
|
-
const
|
|
32613
|
-
if (!
|
|
32614
|
-
return refused2(attempt3, attempt3.branch, null, `the ${REMOTE} branches of #${String(attempt3.issue)} could not be listed: ${gitSaid(
|
|
33422
|
+
const listed2 = git(["for-each-ref", "--format=%(refname:lstrip=3)", `refs/remotes/${REMOTE}/${BRANCH_PREFIX2}`]);
|
|
33423
|
+
if (!listed2.ok) {
|
|
33424
|
+
return refused2(attempt3, attempt3.branch, null, `the ${REMOTE} branches of #${String(attempt3.issue)} could not be listed: ${gitSaid(listed2)}`);
|
|
32615
33425
|
}
|
|
32616
|
-
const names2 =
|
|
33426
|
+
const names2 = listed2.stdout.split(`
|
|
32617
33427
|
`).map((line) => line.trim());
|
|
32618
33428
|
const move = readMove(attempt3, names2, context);
|
|
32619
33429
|
if (ahead === null || move.kind === "answer")
|
|
@@ -33151,11 +33961,11 @@ async function resolveCreateSpec(options, seams = DEFAULT_ROUTE_SEAMS) {
|
|
|
33151
33961
|
const source = readSpecSourceFlags(options.args);
|
|
33152
33962
|
if (source.request === null)
|
|
33153
33963
|
throw new CommandExit(SOURCE_REFUSAL_EXIT, usageRefusal(specsDir));
|
|
33154
|
-
const
|
|
33964
|
+
const resolve24 = seams.resolve ?? resolvePlanSpec;
|
|
33155
33965
|
const makeReadyOffer = seams.makeReadyOffer ?? createPlanReadyOffer;
|
|
33156
33966
|
const makeAlternativeOffer = seams.makeAlternativeOffer ?? createPlanBlockedOffer;
|
|
33157
33967
|
const makeRefreshOffer = seams.makeRefreshOffer ?? createPlanRefreshOffer;
|
|
33158
|
-
return await
|
|
33968
|
+
return await resolve24({
|
|
33159
33969
|
request: source.request,
|
|
33160
33970
|
refresh: source.refresh,
|
|
33161
33971
|
dryRun: source.dryRun,
|
|
@@ -33184,7 +33994,7 @@ function formatProgressSection(progressContent) {
|
|
|
33184
33994
|
const trimmed = progressContent?.trim();
|
|
33185
33995
|
if (!trimmed)
|
|
33186
33996
|
return "";
|
|
33187
|
-
const
|
|
33997
|
+
const capped2 = trimmed.length > PROGRESS_CAP_CHARS ? `\u2026(older findings truncated)\u2026
|
|
33188
33998
|
${trimmed.slice(-PROGRESS_CAP_CHARS)}` : trimmed;
|
|
33189
33999
|
return [
|
|
33190
34000
|
"## Findings from previous runs (progress.txt)",
|
|
@@ -33195,7 +34005,7 @@ ${trimmed.slice(-PROGRESS_CAP_CHARS)}` : trimmed;
|
|
|
33195
34005
|
"not requirements, they may be stale, and they must NOT be copied into",
|
|
33196
34006
|
"the plan.",
|
|
33197
34007
|
"",
|
|
33198
|
-
|
|
34008
|
+
capped2,
|
|
33199
34009
|
""
|
|
33200
34010
|
].join(`
|
|
33201
34011
|
`);
|
|
@@ -33421,6 +34231,7 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
33421
34231
|
await requireNoticesAnswered();
|
|
33422
34232
|
activeOutput().info(`\uD83D\uDCDD Generating ${planFile} from ${path8.basename(specPath2)}...`);
|
|
33423
34233
|
const generated = await generateOrExit(planner, { specPath: specRequest, stub }, gate, flags.skipReview);
|
|
34234
|
+
enforceStoreRules(repoRoot, generated);
|
|
33424
34235
|
await settleReview(gate, generated, flags.skipReview);
|
|
33425
34236
|
if (resolved.spec.issue !== null) {
|
|
33426
34237
|
recordPlanIssue(repoRoot, generated.planPath, resolved.spec.issue);
|
|
@@ -33437,4 +34248,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
33437
34248
|
}
|
|
33438
34249
|
}
|
|
33439
34250
|
|
|
33440
|
-
export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, PROJECT_WRITE_PAUSE_MS, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, WORKTREES_SUBDIR, resolveWorktreePath, crossRepositoryRefusal, addResolveWorktree, removeResolveWorktree, pushResolved, worktreeAt, worktreeDirAt, WORKTREE_STATUS, countChanges, cleanupSteps, dryRunLines, runCleanupSteps, defaultCleanupSeams, readCleanup, cleanupCounts, createLinePrompter, openNextRelations, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, LABELS_ONLY_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter2 as lazyPrompter, ready_default, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, readBlockedIssues, renderBlockedIssues, blockerToken, addBlockerToBody, resolveCreateSpec, TRIAGE_MODULE, namedSecrets, triageReport,
|
|
34251
|
+
export { SERVE_CLI_VERSION, PROVENANCE_FIELD, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, SKILL_USE_CLI_VERSION, sessionLogDir, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, loadModules, moduleSettings, pullBeforeRead, collectSessionRow, collect, AGENT_DEFINITION_DIR, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, parseReportArgs, formatCount, alignRows, verdictOf, failingRows, formatRows, waitForChecks, recordPlanCi, formatTokens, formatTrendSegment, formatLoopsSegment, readTrendInputs, trendReportOf, buildReport, readSkillsReport, report, SPEC_EXTENSION, boardId, notesFileName, branchName, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, parseWorkingTree, parseTreePaths, untrackedLeftLine, parseWorktrees, worktreesHolding, readMergeRefusal, resolvePrProvider, requireGhProvider, createGhPullRequests, ghPullRequestsIn, REMOTE, BRANCH_PREFIX2 as BRANCH_PREFIX, parseBaseStanding, hasDiverged, parseClaimMessage, claimBranchIssue, makeOwnershipCommit, fetchClaimBranches, readClaimBranch, pushOwnershipCommit, CLAIMED_LABEL, IN_DEVELOPMENT_LABEL, readClaimState, idleText, ROADMAP_TITLE, ROADMAP_SETTING, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, readEpicBody, SPEC_BLOCKED_LABEL, BLOCKED_BY_FIELD, hasSpecBlockedLabel, readBlockedBy, EPIC_LABEL_PREFIX, epicSlugsOf, isNotPlanned, groupByEpicLabel, localDay, readEpics, BOARD_LISTING_LIMIT, createGhBoardListing, SPEC_READY_LABEL, findReadinessGaps, requireCompleteSpec, validate_default, RefsBlockError, fingerprintText, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, createGhSpecIssueReader, hasSpecLabel, readRoadmapSetting, ROADMAP_LABEL, BOARD_LABELS, LABEL_LIST_LIMIT, SPEC_TEMPLATE_PATH, TEMPLATES_DIRNAME, boardChanged, listBoardLabels, missingBoardLabels, setUpLabels, anythingAt, writeShippedFile, setUpBoard, resolveDefaultBoard, unlabelledRoadmapMessage, createGhRoadmapBody, tickSentence, appendLine, tickLine, editChecklist, noBoardListsLine, epicTickSentence, tickRoadmapAfterMerge, createGhIssueBoard, PROJECT_WRITE_PAUSE_MS, matchProjectFields, PROJECT_SCOPE_FIX, BOARD_SYNC_FIX, isMissingProjectScope, scopeWarning, rateLimitWarning, notFoundWarning, isWaiting, blockerArg, createNativeRelations, selectBoardRelations, resolveTracker, DEFAULT_ISSUE_SEAMS, lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, NATIVE_MODE, NATIVE_RETRY_HINT, nativeEpicLeft, nativeAlreadyMessage, nativeParentLine, readBoardRepository, configuredMoveRelations, FRAGMENT_PLAN_ID_PATTERN, serializeFragment, allocateFragmentName, directoryPrefix, HORIZON_LABEL_PREFIX, readEpicProblems, epicProblemMessage, readEpicDependents, REASON_FLAG, normaliseReason, renderHorizonComment, renderParkedPullRequestComment, renderMoveComment, renderCloseComment, renderCancelComment, cancelMoveReason, renderDependentComment, renderUnblockNote, readReason, unaskedReasonMessage, blankReasonMessage, readRefsText, readCopyRefs, restampCopyRefs, RefVerifyError, readConfiguredRelations, requireNoLeak, blockedLineSentence, boardRepoLabel, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, rankCandidates, readRankCandidate, NO_NEXT_STEP, readSchemaReport, gateMeaning, unknownAdditive, unknownAdditiveWarning, developmentApplied, isFirstDispatch, STAGE_LABELS, unlabelReleased, createStartPreflightClaim, invalidateRows, checkDrift, driftLines, readDoctorEffortSync, renderDoctorEffortSync, effortSyncRefusal, SESSION_ID_FLAG2 as SESSION_ID_FLAG, readBoardBody, openBoards, boardOfEpic, createOwnerResolver, runIssueList, ISSUE_LIST_FLAGS, list_default, firstNowEpic, show_default, samePlace, resolvePlace, createGhProjectPort, resolveLoopSeams, sessionIdFlag, lineRefusal2 as lineRefusal1, refusal3 as refusal, NoSessionRefusal, isLive, planLabel, sessionLine, projectRoot2 as projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, phaseNote, phasedCounts, checkboxAt, estimateEta, readSessionFinishes, formatDuration, etaLine, RELEASE_REMOTE, RELEASE_BASE_BRANCH, parseSemanticVersion, readManifestVersion, replaceManifestVersion, gitPathOf, receiptProblem, readReceiptVerdict, CHANGELOG_TITLE, releaseCommitSubject, readSettle, buildSettle, waitingSettingsOf, readWaiting, forecastPhrase, DEFAULT_RELEASE_SEAMS, compareVersions, changelogVersions, readTags, status_default, epicEndLines, watchDryEpic, nextOwnerGate, resolveReleaseEnabled, refuseMisplacedRuntime, readBinPath, self_update_default, versionTag, INDENT4 as INDENT, cleanUpAfterMerge, reportFollowUps, RELEASE_BLOCK_OPEN, RELEASE_BLOCK_CLOSE, localPlanNotes, readGuard, guardLines, mergeGuardSettings, readMergeGuard, guardReaction, followsMerge, readSettleAfterMerge, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, readRemoteHead, eventsFilePath, WORKTREES_SUBDIR, resolveWorktreePath, crossRepositoryRefusal, addResolveWorktree, removeResolveWorktree, pushResolved, worktreeAt, worktreeDirAt, WORKTREE_STATUS, countChanges, cleanupSteps, dryRunLines, runCleanupSteps, defaultCleanupSeams, readCleanup, cleanupCounts, createLinePrompter, openNextRelations, list_default2 as list_default1, readBranch2 as readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, readHomeAfterLoop, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, LABELS_ONLY_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, lazyPrompter2 as lazyPrompter, ready_default, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, readBlockedIssues, renderBlockedIssues, blockerToken, addBlockerToBody, resolveCreateSpec, TRIAGE_MODULE, namedSecrets, triageReport, unresolvedTracker, DISK_ROOTS_FILE_SYSTEM, gitToplevel, rootRefusal, rootCandidates, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, retargetedLine, MECHANICAL_CONFLICT_SENTENCE, start, createPlanRefsVerifier, readDoctorRefs, renderDoctorRefs, createPlanDirNames, hasPlanFor, HORIZONS, horizonOf, readListedEpics, stageOf, rankOf, readIssueFacts, readRefreshBoard, refreshProjectItems, refreshIssueItems, refreshFailedWarning, unblock_default, unblockAfterMerge, labelsLineBlockersOf, LABELS_READS, createLabelsRelations, EPIC_TYPE_LABEL, plan };
|