@open-tomato/rafa 0.16.0 → 0.17.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 +17 -1
- package/dist/SKILL.md +8 -5
- package/dist/cli.js +672 -29
- package/dist/effort/store/index.js +2 -2
- package/dist/{index-1vtd5fr7.js → index-4qd537z6.js} +53 -17
- package/dist/{index-krjzghpz.js → index-64wec0k7.js} +20 -7
- package/dist/{index-q491ekzm.js → index-7t0qz29r.js} +24 -3
- package/dist/{index-6cw1n0qy.js → index-ps5sac9p.js} +6 -3
- package/dist/{index-kph6p7rp.js → index-r7cnr44m.js} +362 -11
- package/dist/index.js +4 -4
- package/dist/plan/index.js +2 -2
- package/dist/plan-prompt.md +3 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -559,7 +559,8 @@ var init_config_schema = __esm(() => {
|
|
|
559
559
|
cleanupStaleDays: 30,
|
|
560
560
|
cleanupWorktreeIdleDays: 7,
|
|
561
561
|
cleanupKeep: Object.freeze([]),
|
|
562
|
-
dangerousAcceptStaleRefs: false
|
|
562
|
+
dangerousAcceptStaleRefs: false,
|
|
563
|
+
statusNotice: true
|
|
563
564
|
});
|
|
564
565
|
directory = text("a directory path");
|
|
565
566
|
trackerKind = text("a tracker kind name");
|
|
@@ -650,7 +651,8 @@ var init_config_schema = __esm(() => {
|
|
|
650
651
|
key: "dangerous.acceptStaleRefs",
|
|
651
652
|
read: flag,
|
|
652
653
|
cli: false
|
|
653
|
-
}
|
|
654
|
+
},
|
|
655
|
+
statusNotice: { key: "status.notice", read: flag, cli: false }
|
|
654
656
|
};
|
|
655
657
|
SETTING_NAMES = Object.keys(SETTINGS);
|
|
656
658
|
SETTING_BY_KEY = new Map(SETTING_NAMES.map((setting) => [
|
|
@@ -722,7 +724,8 @@ function readLayer(rawOf, atOf) {
|
|
|
722
724
|
cleanupStaleDays: read("cleanupStaleDays"),
|
|
723
725
|
cleanupWorktreeIdleDays: read("cleanupWorktreeIdleDays"),
|
|
724
726
|
cleanupKeep: read("cleanupKeep"),
|
|
725
|
-
dangerousAcceptStaleRefs: read("dangerousAcceptStaleRefs")
|
|
727
|
+
dangerousAcceptStaleRefs: read("dangerousAcceptStaleRefs"),
|
|
728
|
+
statusNotice: read("statusNotice")
|
|
726
729
|
};
|
|
727
730
|
return { layer, problems, extras };
|
|
728
731
|
}
|
|
@@ -1413,7 +1416,7 @@ function restoreRunningCommand(previous) {
|
|
|
1413
1416
|
var running = null;
|
|
1414
1417
|
|
|
1415
1418
|
// package.json
|
|
1416
|
-
var name = "@open-tomato/rafa", version = "0.
|
|
1419
|
+
var name = "@open-tomato/rafa", version = "0.17.0";
|
|
1417
1420
|
var init_package = () => {};
|
|
1418
1421
|
|
|
1419
1422
|
// src/cli/version.ts
|
|
@@ -5529,7 +5532,7 @@ var init_issue_values = __esm(() => {
|
|
|
5529
5532
|
|
|
5530
5533
|
// src/adapters/tracker/github.ts
|
|
5531
5534
|
function createGhRunner(options) {
|
|
5532
|
-
const { cwd, command = "gh", env } = options;
|
|
5535
|
+
const { cwd, command = "gh", env, timeoutMs } = options;
|
|
5533
5536
|
return async (args) => {
|
|
5534
5537
|
const executable = env === undefined ? command : Bun.which(command, { PATH: env.PATH ?? "", cwd });
|
|
5535
5538
|
if (executable === null) {
|
|
@@ -5543,17 +5546,37 @@ function createGhRunner(options) {
|
|
|
5543
5546
|
stdout: "pipe",
|
|
5544
5547
|
stderr: "pipe"
|
|
5545
5548
|
});
|
|
5546
|
-
const
|
|
5549
|
+
const finished = Promise.all([
|
|
5547
5550
|
new Response(proc.stdout).text(),
|
|
5548
5551
|
new Response(proc.stderr).text(),
|
|
5549
5552
|
proc.exited
|
|
5550
5553
|
]);
|
|
5554
|
+
const outcome = await withDeadline(finished, timeoutMs);
|
|
5555
|
+
if (outcome === TIMED_OUT) {
|
|
5556
|
+
proc.kill("SIGKILL");
|
|
5557
|
+
await proc.exited;
|
|
5558
|
+
return { ok: false, stdout: "", stderr: `${command} in ${cwd} timed out after ${timeoutMs}ms and was killed` };
|
|
5559
|
+
}
|
|
5560
|
+
const [stdout, stderr, exitCode] = outcome;
|
|
5551
5561
|
return { ok: exitCode === 0, stdout, stderr };
|
|
5552
5562
|
} catch (error) {
|
|
5553
5563
|
return { ok: false, stdout: "", stderr: `could not run ${command} in ${cwd}: ${messageOf(error)}` };
|
|
5554
5564
|
}
|
|
5555
5565
|
};
|
|
5556
5566
|
}
|
|
5567
|
+
async function withDeadline(work, timeoutMs) {
|
|
5568
|
+
if (timeoutMs === undefined)
|
|
5569
|
+
return work;
|
|
5570
|
+
let timer;
|
|
5571
|
+
const deadline = new Promise((resolve3) => {
|
|
5572
|
+
timer = setTimeout(() => resolve3(TIMED_OUT), timeoutMs);
|
|
5573
|
+
});
|
|
5574
|
+
try {
|
|
5575
|
+
return await Promise.race([work, deadline]);
|
|
5576
|
+
} finally {
|
|
5577
|
+
clearTimeout(timer);
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5557
5580
|
function notOnPath(command, cwd, path) {
|
|
5558
5581
|
const searched = path === undefined ? "an environment with no PATH" : `PATH ${JSON.stringify(path)}`;
|
|
5559
5582
|
return `could not run ${command} in ${cwd}: not found on ${searched} (ENOENT)`;
|
|
@@ -5830,7 +5853,7 @@ function createGithubTracker(options) {
|
|
|
5830
5853
|
};
|
|
5831
5854
|
return Object.freeze(tracker);
|
|
5832
5855
|
}
|
|
5833
|
-
var PREFIX2 = "github tracker", GITHUB_LABELS, VIEW_FIELDS = "number,title,body,state,stateReason,labels,url", LIST_FIELDS = "number,url,labels", DEFAULT_FIND_LIMIT = 30, NOT_PLANNED = "NOT_PLANNED", ISSUE_NUMBER, REPO_PART, CREATED_ISSUE_URL, REST_STATES, DRAFT_CHECKS;
|
|
5856
|
+
var PREFIX2 = "github tracker", GITHUB_LABELS, VIEW_FIELDS = "number,title,body,state,stateReason,labels,url", LIST_FIELDS = "number,url,labels", DEFAULT_FIND_LIMIT = 30, NOT_PLANNED = "NOT_PLANNED", ISSUE_NUMBER, REPO_PART, CREATED_ISSUE_URL, REST_STATES, TIMED_OUT, DRAFT_CHECKS;
|
|
5834
5857
|
var init_github = __esm(() => {
|
|
5835
5858
|
init_config_sections();
|
|
5836
5859
|
init_issue_values();
|
|
@@ -5853,6 +5876,7 @@ var init_github = __esm(() => {
|
|
|
5853
5876
|
released: "closed",
|
|
5854
5877
|
cancelled: "closed"
|
|
5855
5878
|
};
|
|
5879
|
+
TIMED_OUT = Symbol("timed out");
|
|
5856
5880
|
DRAFT_CHECKS = [
|
|
5857
5881
|
["opt", (value) => typeof value === "number" && Number.isFinite(value), "a number"],
|
|
5858
5882
|
["title", (value) => typeof value === "string", "a string"],
|
|
@@ -9639,7 +9663,7 @@ function isProbedTag(tag) {
|
|
|
9639
9663
|
return tag === "auto" || tag === "start";
|
|
9640
9664
|
}
|
|
9641
9665
|
function extractProbe(description) {
|
|
9642
|
-
const probe =
|
|
9666
|
+
const probe = PROBE_RE.exec(description)?.[1]?.trim();
|
|
9643
9667
|
return probe || null;
|
|
9644
9668
|
}
|
|
9645
9669
|
function readItem(lines, lineIndex, inherited) {
|
|
@@ -9686,24 +9710,36 @@ function requiredItemsTagged(items, tag) {
|
|
|
9686
9710
|
const probed = items.filter(isProbed).filter((item) => item.tag === tag);
|
|
9687
9711
|
return Object.freeze(probed.map(requiredItem));
|
|
9688
9712
|
}
|
|
9689
|
-
function
|
|
9713
|
+
function namedItemOf(item) {
|
|
9690
9714
|
return Object.freeze({ description: item.description, tag: item.tag, line: item.lineIndex + 1 });
|
|
9691
9715
|
}
|
|
9716
|
+
function isMalformed(item) {
|
|
9717
|
+
return isProbedTag(item.tag) && item.probe === null;
|
|
9718
|
+
}
|
|
9692
9719
|
function planPrerequisites(content) {
|
|
9693
9720
|
const items = parsePrerequisites(content);
|
|
9694
9721
|
return Object.freeze({
|
|
9695
9722
|
required: requiredItemsTagged(items, "auto"),
|
|
9696
9723
|
startRequired: requiredItemsTagged(items, "start"),
|
|
9697
|
-
reminders: Object.freeze(items.filter((item) => !
|
|
9724
|
+
reminders: Object.freeze(items.filter((item) => !isProbedTag(item.tag)).map(namedItemOf)),
|
|
9725
|
+
malformed: Object.freeze(items.filter(isMalformed).map(namedItemOf))
|
|
9698
9726
|
});
|
|
9699
9727
|
}
|
|
9728
|
+
function malformedPrerequisiteLines(file, malformed) {
|
|
9729
|
+
return [
|
|
9730
|
+
`${file} holds ${malformed.length} malformed [auto] or [start] item(s): no command ends the item after a final ": ".`,
|
|
9731
|
+
...malformed.map((item) => ` line ${item.line} [${item.tag}]: ${item.description}`),
|
|
9732
|
+
`Write each as ${PROBE_ITEM_EXAMPLE}, its one backticked span a complete command run as written;` + " prove a tool is there with `<tool> --version`, `<tool> --help` or `which <tool>`."
|
|
9733
|
+
];
|
|
9734
|
+
}
|
|
9700
9735
|
function mergePlanPrerequisites(settings, content) {
|
|
9701
9736
|
const plan = planPrerequisites(content ?? "");
|
|
9702
9737
|
return Object.freeze({
|
|
9703
9738
|
required: Object.freeze([...settings.prerequisitesRequired, ...plan.required]),
|
|
9704
9739
|
startRequired: plan.startRequired,
|
|
9705
9740
|
optional: Object.freeze([...settings.prerequisitesOptional]),
|
|
9706
|
-
reminders: plan.reminders
|
|
9741
|
+
reminders: plan.reminders,
|
|
9742
|
+
malformed: plan.malformed
|
|
9707
9743
|
});
|
|
9708
9744
|
}
|
|
9709
9745
|
function prerequisitesPathForPlan(planPath) {
|
|
@@ -9727,7 +9763,7 @@ async function loadPlanPrerequisites(planPath, settings) {
|
|
|
9727
9763
|
const content = path === null ? null : await readIfPresent2(path);
|
|
9728
9764
|
return mergePlanPrerequisites(settings, content);
|
|
9729
9765
|
}
|
|
9730
|
-
var SECTION_AUTO_RE, SECTION_START_RE, SECTION_HUMAN_RE, ITEM_RE, INLINE_AUTO_RE, INLINE_HUMAN_RE, INLINE_START_RE,
|
|
9766
|
+
var SECTION_AUTO_RE, SECTION_START_RE, SECTION_HUMAN_RE, ITEM_RE, INLINE_AUTO_RE, INLINE_HUMAN_RE, INLINE_START_RE, PROBE_RE, HEADING_MARKS_RE, FENCE_RE, NESTED_LIST_ITEM_RE, PLAN_FILE_RE, PROBE_ITEM_EXAMPLE = "- [ ] uv installed: `uvx --version`";
|
|
9731
9767
|
var init_prerequisites_md = __esm(() => {
|
|
9732
9768
|
init_config_sections();
|
|
9733
9769
|
SECTION_AUTO_RE = /\[auto\]/i;
|
|
@@ -9737,7 +9773,7 @@ var init_prerequisites_md = __esm(() => {
|
|
|
9737
9773
|
INLINE_AUTO_RE = /^-\s+\[\s*\]\s+\[auto\]\s+/i;
|
|
9738
9774
|
INLINE_HUMAN_RE = /^-\s+\[\s*\]\s+\[human\]\s+/i;
|
|
9739
9775
|
INLINE_START_RE = /^-\s+\[\s*\]\s+\[start\]\s+/i;
|
|
9740
|
-
|
|
9776
|
+
PROBE_RE = /:\s+`([^`]+)`\s*$/;
|
|
9741
9777
|
HEADING_MARKS_RE = /^#+/;
|
|
9742
9778
|
FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
|
|
9743
9779
|
NESTED_LIST_ITEM_RE = /^\s+(?:[-*+]|\d+[.)])\s/;
|
|
@@ -10972,6 +11008,18 @@ async function loadItems(options) {
|
|
|
10972
11008
|
`));
|
|
10973
11009
|
}
|
|
10974
11010
|
}
|
|
11011
|
+
function refuseMalformedItems(planPath, items) {
|
|
11012
|
+
if (items.malformed.length === 0)
|
|
11013
|
+
return;
|
|
11014
|
+
const file = basename4(prerequisitesPathForPlan(planPath) ?? planPath);
|
|
11015
|
+
const [head = "", ...rest] = malformedPrerequisiteLines(file, items.malformed);
|
|
11016
|
+
throw new CommandExit(1, [
|
|
11017
|
+
`\u274C Refusing to start: ${head}`,
|
|
11018
|
+
...rest.map((line) => ` ${line}`),
|
|
11019
|
+
" Nothing was checked and nothing was dispatched."
|
|
11020
|
+
].join(`
|
|
11021
|
+
`));
|
|
11022
|
+
}
|
|
10975
11023
|
function skippedStartLine(item, tracker) {
|
|
10976
11024
|
return `\u23ED start-only item ${item.kind} ${JSON.stringify(item.name)} was not checked:` + " it is probed on a first dispatch alone," + ` and ${tracker} already holds a ticked task`;
|
|
10977
11025
|
}
|
|
@@ -11029,6 +11077,7 @@ async function runStartPreflight(options) {
|
|
|
11029
11077
|
const runId = (options.newRunId ?? randomUUID6)();
|
|
11030
11078
|
refuseUnresolvableAgents(options);
|
|
11031
11079
|
const items = await loadItems(options);
|
|
11080
|
+
refuseMalformedItems(options.planPath, items);
|
|
11032
11081
|
announceReminders(options.planPath, items.reminders);
|
|
11033
11082
|
const tiers = {
|
|
11034
11083
|
required: [...automaticItems(options), ...startTier(options.planPath, items), ...items.required],
|
|
@@ -12296,6 +12345,15 @@ var init_blocked = __esm(() => {
|
|
|
12296
12345
|
});
|
|
12297
12346
|
|
|
12298
12347
|
// src/board/readiness.ts
|
|
12348
|
+
function codeSpanEnd(text2, limit) {
|
|
12349
|
+
const open = text2.indexOf(CODE_TICK);
|
|
12350
|
+
if (open === -1 || open > limit)
|
|
12351
|
+
return -1;
|
|
12352
|
+
const close = text2.indexOf(CODE_TICK, open + 1);
|
|
12353
|
+
if (close === -1)
|
|
12354
|
+
return -1;
|
|
12355
|
+
return close + 1;
|
|
12356
|
+
}
|
|
12299
12357
|
function stripComments(line, carried) {
|
|
12300
12358
|
const comments = [];
|
|
12301
12359
|
let visible = "";
|
|
@@ -12315,6 +12373,12 @@ function stripComments(line, carried) {
|
|
|
12315
12373
|
visible += rest;
|
|
12316
12374
|
break;
|
|
12317
12375
|
}
|
|
12376
|
+
const quoted = codeSpanEnd(rest, start);
|
|
12377
|
+
if (quoted !== -1) {
|
|
12378
|
+
visible += rest.slice(0, quoted);
|
|
12379
|
+
rest = rest.slice(quoted);
|
|
12380
|
+
continue;
|
|
12381
|
+
}
|
|
12318
12382
|
visible += rest.slice(0, start);
|
|
12319
12383
|
const body = rest.slice(start + COMMENT_OPEN.length);
|
|
12320
12384
|
const end = body.indexOf(COMMENT_CLOSE);
|
|
@@ -12335,6 +12399,9 @@ function headingOf2(visible) {
|
|
|
12335
12399
|
const [, hashes = "", title2 = ""] = match;
|
|
12336
12400
|
return { level: hashes.length, title: title2.replace(/\s+#+\s*$/u, "").trim() };
|
|
12337
12401
|
}
|
|
12402
|
+
function fencedLine(number, text2) {
|
|
12403
|
+
return { number, fenced: true, visible: text2, comments: [], heading: null };
|
|
12404
|
+
}
|
|
12338
12405
|
function readLines2(body) {
|
|
12339
12406
|
const raw = body.split(`
|
|
12340
12407
|
`).map((line) => line.replace(/\r$/u, ""));
|
|
@@ -12342,18 +12409,21 @@ function readLines2(body) {
|
|
|
12342
12409
|
let carried = false;
|
|
12343
12410
|
let fenced = false;
|
|
12344
12411
|
raw.forEach((text2, index) => {
|
|
12412
|
+
const number = index + 1;
|
|
12413
|
+
const isFence2 = FENCE_LINE2.test(text2);
|
|
12414
|
+
if (fenced || !carried && isFence2) {
|
|
12415
|
+
fenced = !fenced || !isFence2;
|
|
12416
|
+
lines.push(fencedLine(number, text2));
|
|
12417
|
+
return;
|
|
12418
|
+
}
|
|
12345
12419
|
const stripped = stripComments(text2, carried);
|
|
12346
12420
|
carried = stripped.open;
|
|
12347
|
-
const isFence2 = FENCE_LINE2.test(stripped.visible);
|
|
12348
|
-
const inFence = fenced || isFence2;
|
|
12349
|
-
if (isFence2)
|
|
12350
|
-
fenced = !fenced;
|
|
12351
12421
|
lines.push({
|
|
12352
|
-
number
|
|
12353
|
-
fenced:
|
|
12422
|
+
number,
|
|
12423
|
+
fenced: false,
|
|
12354
12424
|
visible: stripped.visible,
|
|
12355
12425
|
comments: stripped.comments,
|
|
12356
|
-
heading:
|
|
12426
|
+
heading: headingOf2(stripped.visible)
|
|
12357
12427
|
});
|
|
12358
12428
|
});
|
|
12359
12429
|
return lines;
|
|
@@ -12393,13 +12463,13 @@ function gapAt(kind, heading, what, line) {
|
|
|
12393
12463
|
return { kind, heading, what, line };
|
|
12394
12464
|
}
|
|
12395
12465
|
function placeholdersInLine(heading, line) {
|
|
12466
|
+
if (line.fenced)
|
|
12467
|
+
return [];
|
|
12396
12468
|
const found = [];
|
|
12397
12469
|
const unfilled = line.comments.some((comment) => !comment.trim().startsWith(MARKER_PREFIX));
|
|
12398
12470
|
if (unfilled) {
|
|
12399
12471
|
found.push(gapAt("placeholder", heading, `holds an unfilled template comment on line ${String(line.number)}`, line.number));
|
|
12400
12472
|
}
|
|
12401
|
-
if (line.fenced)
|
|
12402
|
-
return found;
|
|
12403
12473
|
const prose = line.visible.replace(CODE_SPAN2, " ");
|
|
12404
12474
|
for (const shape of PLACEHOLDER_SHAPES) {
|
|
12405
12475
|
if (!shape.pattern.test(prose))
|
|
@@ -12479,7 +12549,7 @@ function requireCompleteSpec(source, body) {
|
|
|
12479
12549
|
return;
|
|
12480
12550
|
throw new CommandExit(READINESS_REFUSAL_EXIT, readinessRefusalMessage(source, gaps));
|
|
12481
12551
|
}
|
|
12482
|
-
var SPEC_READY_LABEL = "spec:ready", READINESS_REFUSAL_EXIT = 2, TEMPLATE_HEADINGS, LIST_HEADINGS, PREAMBLE_HEADING = "the opening", MARKER_PREFIX = "rafa:", HEADING_LINE, FENCE_LINE2, LIST_ITEM, CODE_SPAN2, HEADING_DRESS, HEADING_TAIL, COMMENT_OPEN = "<!--", COMMENT_CLOSE = "-->", PLACEHOLDER_SHAPES, REMEDY2 = "fill each gap in the spec and rerun";
|
|
12552
|
+
var SPEC_READY_LABEL = "spec:ready", READINESS_REFUSAL_EXIT = 2, TEMPLATE_HEADINGS, LIST_HEADINGS, PREAMBLE_HEADING = "the opening", MARKER_PREFIX = "rafa:", HEADING_LINE, FENCE_LINE2, LIST_ITEM, CODE_SPAN2, CODE_TICK = "`", HEADING_DRESS, HEADING_TAIL, COMMENT_OPEN = "<!--", COMMENT_CLOSE = "-->", PLACEHOLDER_SHAPES, REMEDY2 = "fill each gap in the spec and rerun";
|
|
12483
12553
|
var init_readiness = __esm(() => {
|
|
12484
12554
|
init_command();
|
|
12485
12555
|
TEMPLATE_HEADINGS = [
|
|
@@ -22662,6 +22732,17 @@ async function loadItems2(plan, config) {
|
|
|
22662
22732
|
throw refusal4(["rafa doctor: the plan's prerequisites cannot be read:", ` ${messageOf(error)}`]);
|
|
22663
22733
|
}
|
|
22664
22734
|
}
|
|
22735
|
+
function refuseMalformedItems2(plan, items) {
|
|
22736
|
+
if (plan === null || items.malformed.length === 0)
|
|
22737
|
+
return;
|
|
22738
|
+
const file = basename12(prerequisitesPathForPlan(plan) ?? plan);
|
|
22739
|
+
const [head = "", ...rest] = malformedPrerequisiteLines(file, items.malformed);
|
|
22740
|
+
throw refusal4([
|
|
22741
|
+
`rafa doctor: ${head}`,
|
|
22742
|
+
...rest,
|
|
22743
|
+
"rafa loop start would refuse here, before any probe. No run was started and nothing was stored."
|
|
22744
|
+
]);
|
|
22745
|
+
}
|
|
22665
22746
|
function mergedFile(plan) {
|
|
22666
22747
|
const file = plan === null ? null : prerequisitesPathForPlan(plan);
|
|
22667
22748
|
return file !== null && isFile(file) ? file : null;
|
|
@@ -22693,6 +22774,7 @@ async function checkPreflight(context, project, seams) {
|
|
|
22693
22774
|
const { config } = resolved;
|
|
22694
22775
|
const { plan, lookedFor } = choosePlan(project.root, config, named);
|
|
22695
22776
|
const items = await loadItems2(plan, config);
|
|
22777
|
+
refuseMalformedItems2(plan, items);
|
|
22696
22778
|
const automatic = readProvider(project.root, config, seams);
|
|
22697
22779
|
const start = readStartTier(plan, items);
|
|
22698
22780
|
const tiers = {
|
|
@@ -24093,7 +24175,9 @@ var init_scaffold = __esm(() => {
|
|
|
24093
24175
|
`# worktreeIdleDays: ${String(CONFIG_DEFAULTS.cleanupWorktreeIdleDays)} # days before rafa cleanup lists a worktree as idle`,
|
|
24094
24176
|
"# keep: [] # glob patterns naming branches rafa cleanup never lists",
|
|
24095
24177
|
"# dangerous:",
|
|
24096
|
-
`# acceptStaleRefs: ${String(CONFIG_DEFAULTS.dangerousAcceptStaleRefs)} # true plans past dangling and suspect spec references on every run
|
|
24178
|
+
`# acceptStaleRefs: ${String(CONFIG_DEFAULTS.dangerousAcceptStaleRefs)} # true plans past dangling and suspect spec references on every run`,
|
|
24179
|
+
"# status:",
|
|
24180
|
+
`# notice: ${String(CONFIG_DEFAULTS.statusNotice)} # false drops the one-line notice of what is new since the last command`
|
|
24097
24181
|
]);
|
|
24098
24182
|
VERSION_LINE = `version: ${String(CONFIG_DEFAULTS.version)}`;
|
|
24099
24183
|
PROJECT_HEADER = Object.freeze([
|
|
@@ -27955,7 +28039,7 @@ function backtickRunLength(text2, start) {
|
|
|
27955
28039
|
end += 1;
|
|
27956
28040
|
return end - start;
|
|
27957
28041
|
}
|
|
27958
|
-
function
|
|
28042
|
+
function codeSpanEnd2(text2, from, length) {
|
|
27959
28043
|
let index = text2.indexOf("`", from);
|
|
27960
28044
|
while (index !== -1) {
|
|
27961
28045
|
const run = backtickRunLength(text2, index);
|
|
@@ -27971,7 +28055,7 @@ function firstClause(text2) {
|
|
|
27971
28055
|
const character = text2.charAt(index);
|
|
27972
28056
|
if (character === "`") {
|
|
27973
28057
|
const run = backtickRunLength(text2, index);
|
|
27974
|
-
const end =
|
|
28058
|
+
const end = codeSpanEnd2(text2, index + run, run);
|
|
27975
28059
|
index = end === -1 ? index + run : end;
|
|
27976
28060
|
continue;
|
|
27977
28061
|
}
|
|
@@ -38007,6 +38091,360 @@ var init_show7 = __esm(() => {
|
|
|
38007
38091
|
show_default6 = createSkillShowCommand();
|
|
38008
38092
|
});
|
|
38009
38093
|
|
|
38094
|
+
// src/status/render.ts
|
|
38095
|
+
function counted6(count, noun, plural7 = `${noun}s`) {
|
|
38096
|
+
return `${String(count)} ${count === 1 ? noun : plural7}`;
|
|
38097
|
+
}
|
|
38098
|
+
function info(text2) {
|
|
38099
|
+
return { level: "info", text: text2 };
|
|
38100
|
+
}
|
|
38101
|
+
function branchText(reading2) {
|
|
38102
|
+
const plan2 = reading2.plan === null ? "no plan" : `plan \`${reading2.plan.stub}\` (${formatCounts(reading2.plan.tasks)})`;
|
|
38103
|
+
return `\`${reading2.branch}\`, ${plan2}`;
|
|
38104
|
+
}
|
|
38105
|
+
function blockedTaskCount(blocked2) {
|
|
38106
|
+
return blocked2.reduce((sum, one) => sum + one.tasks.length, 0);
|
|
38107
|
+
}
|
|
38108
|
+
function loopsText(reading2) {
|
|
38109
|
+
const tasks = blockedTaskCount(reading2.blocked);
|
|
38110
|
+
return `${String(reading2.live.length)} running, ${counted6(tasks, "task")} blocked`;
|
|
38111
|
+
}
|
|
38112
|
+
function loopsBody(reading2) {
|
|
38113
|
+
const running2 = reading2.live.map((record) => info(`${INDENT15}${sessionLine(record)}`));
|
|
38114
|
+
const blocked2 = reading2.blocked.flatMap((one) => one.tasks.map((task) => {
|
|
38115
|
+
const blocker = task.blocker === null ? "" : ` (${task.blocker})`;
|
|
38116
|
+
return info(`${INDENT15}blocked: plan \`${planLabel(one.session)}\` line ${String(task.line)}: ${task.text}${blocker}`);
|
|
38117
|
+
}));
|
|
38118
|
+
return [...running2, ...blocked2];
|
|
38119
|
+
}
|
|
38120
|
+
function pullText(reading2) {
|
|
38121
|
+
if (reading2.pull === null)
|
|
38122
|
+
return "none open";
|
|
38123
|
+
const { summary, mergeable, verdict } = reading2.pull;
|
|
38124
|
+
return `#${String(summary.number)} ${summary.title}, ${MERGEABILITY_WORDS[mergeable]}, ${VERDICT_WORDS[verdict]}`;
|
|
38125
|
+
}
|
|
38126
|
+
function boardText(reading2) {
|
|
38127
|
+
const roadmap = `roadmap #${String(reading2.roadmap)}`;
|
|
38128
|
+
const next = reading2.next === null ? `${roadmap} has no line left` : `next is #${String(reading2.next.line.issue)} on ${roadmap}, ${nextStanding(reading2.next)}`;
|
|
38129
|
+
const blocked2 = reading2.blockedIssues === null ? `the ${SPEC_BLOCKED_LABEL} issues were not read` : `${counted6(reading2.blockedIssues, "issue")} labelled ${SPEC_BLOCKED_LABEL}`;
|
|
38130
|
+
return `${next}; ${blocked2}`;
|
|
38131
|
+
}
|
|
38132
|
+
function nextStanding(next) {
|
|
38133
|
+
if (next.blocked !== null)
|
|
38134
|
+
return blockedLineSentence(next.blocked);
|
|
38135
|
+
return next.ready ? "ready" : "not ready";
|
|
38136
|
+
}
|
|
38137
|
+
function housekeepingText(reading2) {
|
|
38138
|
+
const { merged: merged2, stale, notPushed, worktrees } = reading2.counts;
|
|
38139
|
+
return `${String(merged2)} merged, ${String(stale)} stale, ${String(notPushed)} not pushed,` + ` ${counted6(worktrees, "worktree")} (${String(reading2.idleWorktrees)} idle)`;
|
|
38140
|
+
}
|
|
38141
|
+
function sectionLines(key, section3, text2, body = () => []) {
|
|
38142
|
+
const title2 = STATUS_SECTION_TITLES[key];
|
|
38143
|
+
if (!section3.read)
|
|
38144
|
+
return [{ level: "warn", text: `${title2}: not read: ${section3.problem}` }];
|
|
38145
|
+
return [info(`${title2}: ${text2(section3)}`), ...body(section3)];
|
|
38146
|
+
}
|
|
38147
|
+
function renderStatus3(sections2) {
|
|
38148
|
+
return [
|
|
38149
|
+
...sectionLines("branch", sections2.branch, branchText),
|
|
38150
|
+
...sectionLines("loops", sections2.loops, loopsText, loopsBody),
|
|
38151
|
+
...sectionLines("pull", sections2.pull, pullText),
|
|
38152
|
+
...sectionLines("board", sections2.board, boardText),
|
|
38153
|
+
...sectionLines("housekeeping", sections2.housekeeping, housekeepingText)
|
|
38154
|
+
];
|
|
38155
|
+
}
|
|
38156
|
+
function sectionData(section3) {
|
|
38157
|
+
const copy = JSON.parse(JSON.stringify(section3));
|
|
38158
|
+
const reading2 = Object.fromEntries(Object.entries(copy).filter(([key]) => key !== "read"));
|
|
38159
|
+
return { read: section3.read, ...reading2 };
|
|
38160
|
+
}
|
|
38161
|
+
function statusData(sections2) {
|
|
38162
|
+
return {
|
|
38163
|
+
branch: sectionData(sections2.branch),
|
|
38164
|
+
loops: sectionData(sections2.loops),
|
|
38165
|
+
pull: sectionData(sections2.pull),
|
|
38166
|
+
board: sectionData(sections2.board),
|
|
38167
|
+
housekeeping: sectionData(sections2.housekeeping)
|
|
38168
|
+
};
|
|
38169
|
+
}
|
|
38170
|
+
var STATUS_SECTION_TITLES, INDENT15 = " ", MERGEABILITY_WORDS, VERDICT_WORDS;
|
|
38171
|
+
var init_render = __esm(() => {
|
|
38172
|
+
init_blocked_line();
|
|
38173
|
+
init_blocked();
|
|
38174
|
+
init_loop_sessions();
|
|
38175
|
+
init_plan_files();
|
|
38176
|
+
STATUS_SECTION_TITLES = Object.freeze({
|
|
38177
|
+
branch: "Branch",
|
|
38178
|
+
loops: "Loops",
|
|
38179
|
+
pull: "Pull request",
|
|
38180
|
+
board: "Board",
|
|
38181
|
+
housekeeping: "Housekeeping"
|
|
38182
|
+
});
|
|
38183
|
+
MERGEABILITY_WORDS = Object.freeze({
|
|
38184
|
+
mergeable: "mergeable",
|
|
38185
|
+
conflicting: "conflicting",
|
|
38186
|
+
unknown: "mergeability unknown"
|
|
38187
|
+
});
|
|
38188
|
+
VERDICT_WORDS = Object.freeze({
|
|
38189
|
+
green: "checks green",
|
|
38190
|
+
red: "checks red",
|
|
38191
|
+
pending: "checks pending",
|
|
38192
|
+
none: "no checks"
|
|
38193
|
+
});
|
|
38194
|
+
});
|
|
38195
|
+
|
|
38196
|
+
// src/status/sections.ts
|
|
38197
|
+
function isIdleWorktree(row2) {
|
|
38198
|
+
return !row2.blockers.some((blocker) => blocker.kind === "recent");
|
|
38199
|
+
}
|
|
38200
|
+
function unread2(problem2) {
|
|
38201
|
+
return { read: false, problem: problem2 };
|
|
38202
|
+
}
|
|
38203
|
+
function read2(reading2) {
|
|
38204
|
+
return { ...reading2, read: true };
|
|
38205
|
+
}
|
|
38206
|
+
function localSection(work) {
|
|
38207
|
+
try {
|
|
38208
|
+
const reading2 = work();
|
|
38209
|
+
return isUnread(reading2) ? reading2 : read2(reading2);
|
|
38210
|
+
} catch (error) {
|
|
38211
|
+
return unread2(messageOf(error));
|
|
38212
|
+
}
|
|
38213
|
+
}
|
|
38214
|
+
function isUnread(value) {
|
|
38215
|
+
return typeof value === "object" && value !== null && value.read === false;
|
|
38216
|
+
}
|
|
38217
|
+
function openDeadline(ms) {
|
|
38218
|
+
let endsAt = null;
|
|
38219
|
+
return {
|
|
38220
|
+
ms,
|
|
38221
|
+
start: () => {
|
|
38222
|
+
endsAt = Date.now() + ms;
|
|
38223
|
+
},
|
|
38224
|
+
left: () => endsAt === null ? ms : endsAt - Date.now()
|
|
38225
|
+
};
|
|
38226
|
+
}
|
|
38227
|
+
function boundedRunner(open, root, deadline) {
|
|
38228
|
+
return (args) => {
|
|
38229
|
+
const left = deadline.left();
|
|
38230
|
+
if (left <= 0) {
|
|
38231
|
+
return Promise.resolve({
|
|
38232
|
+
ok: false,
|
|
38233
|
+
stdout: "",
|
|
38234
|
+
stderr: `gh ${args.join(" ")} was not run: the ${String(deadline.ms)}ms network deadline had passed`
|
|
38235
|
+
});
|
|
38236
|
+
}
|
|
38237
|
+
return open(root, left)(args);
|
|
38238
|
+
};
|
|
38239
|
+
}
|
|
38240
|
+
async function networkSection(what, deadline, work) {
|
|
38241
|
+
let timer;
|
|
38242
|
+
const expired = new Promise((resolve29) => {
|
|
38243
|
+
timer = setTimeout(() => {
|
|
38244
|
+
resolve29(unread2(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
38245
|
+
}, Math.max(0, deadline.left()));
|
|
38246
|
+
});
|
|
38247
|
+
try {
|
|
38248
|
+
return await Promise.race([work().then((reading2) => read2(reading2)), expired]);
|
|
38249
|
+
} catch (error) {
|
|
38250
|
+
return unread2(messageOf(error));
|
|
38251
|
+
} finally {
|
|
38252
|
+
clearTimeout(timer);
|
|
38253
|
+
}
|
|
38254
|
+
}
|
|
38255
|
+
function notGhProblem(what, provider) {
|
|
38256
|
+
const from = provider.source === "config" ? "as the config names it" : "as origin is no GitHub remote";
|
|
38257
|
+
return `pr.provider is ${provider.provider}, ${from}, and rafa status reads the ${what} through gh alone`;
|
|
38258
|
+
}
|
|
38259
|
+
function readBranchSection(sources) {
|
|
38260
|
+
let notes = [];
|
|
38261
|
+
const note = (problem2) => {
|
|
38262
|
+
notes = [...notes, problem2];
|
|
38263
|
+
};
|
|
38264
|
+
const branch = readBranch(sources, note);
|
|
38265
|
+
if (branch === "") {
|
|
38266
|
+
return unread2(notes.join("; ") || "the branch at the project root could not be read");
|
|
38267
|
+
}
|
|
38268
|
+
const plans = readPlans(sources, note);
|
|
38269
|
+
return { branch, plan: readBranchPlan(branch, plans), notes };
|
|
38270
|
+
}
|
|
38271
|
+
function blockedSessions(root, records) {
|
|
38272
|
+
let seen = new Set;
|
|
38273
|
+
let found = [];
|
|
38274
|
+
for (const session of [...records].reverse()) {
|
|
38275
|
+
const checklist = readSessionChecklist(root, session);
|
|
38276
|
+
if (checklist === null || seen.has(checklist.file))
|
|
38277
|
+
continue;
|
|
38278
|
+
seen = new Set([...seen, checklist.file]);
|
|
38279
|
+
const tasks = blockedTasks(checklist);
|
|
38280
|
+
if (tasks.length > 0)
|
|
38281
|
+
found = [...found, { session, checklist: checklist.file, tasks }];
|
|
38282
|
+
}
|
|
38283
|
+
return found;
|
|
38284
|
+
}
|
|
38285
|
+
function readLoopsSection(root, sources) {
|
|
38286
|
+
return localSection(() => {
|
|
38287
|
+
const records = sources.runs();
|
|
38288
|
+
return { live: records.filter((record) => isLive(record)), blocked: blockedSessions(root, records) };
|
|
38289
|
+
});
|
|
38290
|
+
}
|
|
38291
|
+
function pullSection(sources, branch, provider, deadline) {
|
|
38292
|
+
if (provider.provider !== "gh")
|
|
38293
|
+
return Promise.resolve(unread2(notGhProblem("pull request", provider)));
|
|
38294
|
+
if (!branch.read) {
|
|
38295
|
+
return Promise.resolve(unread2(`no pull request was looked for, as the branch was not read: ${branch.problem}`));
|
|
38296
|
+
}
|
|
38297
|
+
return networkSection("pull request", deadline, () => readPull(sources, branch.branch));
|
|
38298
|
+
}
|
|
38299
|
+
async function readPull(sources, branch) {
|
|
38300
|
+
let notes = [];
|
|
38301
|
+
const pull = await readOpenPull(sources, branch, (problem2) => {
|
|
38302
|
+
notes = [...notes, problem2];
|
|
38303
|
+
});
|
|
38304
|
+
return { pull, notes };
|
|
38305
|
+
}
|
|
38306
|
+
async function readBoard(board, blocked2) {
|
|
38307
|
+
const [walk, report3] = await Promise.all([board.next(), blocked2]);
|
|
38308
|
+
const { line } = walk;
|
|
38309
|
+
const next = line === null ? null : { line, ready: await board.isReady(line.issue), blocked: await board.blocking(line.issue) };
|
|
38310
|
+
const listed3 = report3.problem === null ? [] : [`the issues labelled ${SPEC_BLOCKED_LABEL} could not be read: ${report3.problem}`];
|
|
38311
|
+
return {
|
|
38312
|
+
roadmap: walk.roadmap,
|
|
38313
|
+
next,
|
|
38314
|
+
passed: walk.passed,
|
|
38315
|
+
blockedIssues: report3.problem === null ? report3.readings.length : null,
|
|
38316
|
+
notes: [...walk.problems, ...listed3]
|
|
38317
|
+
};
|
|
38318
|
+
}
|
|
38319
|
+
async function readHousekeeping(input, cleanup, now) {
|
|
38320
|
+
try {
|
|
38321
|
+
const settings = doctorCleanupSettings({ root: input.root, home: input.home, config: input.config, gh: null }, now);
|
|
38322
|
+
const reading2 = await readCleanup(cleanup, settings);
|
|
38323
|
+
if (!reading2.ok)
|
|
38324
|
+
return unread2(reading2.detail);
|
|
38325
|
+
const idle = reading2.worktrees.filter((row2) => isIdleWorktree(row2));
|
|
38326
|
+
return read2({ counts: cleanupCounts(reading2), idleWorktrees: idle.length, notes: reading2.notes });
|
|
38327
|
+
} catch (error) {
|
|
38328
|
+
return unread2(messageOf(error));
|
|
38329
|
+
}
|
|
38330
|
+
}
|
|
38331
|
+
async function readStatusSections(input, seams = {}) {
|
|
38332
|
+
const { root, config } = input;
|
|
38333
|
+
const git = (seams.openGit ?? createGitRunner)(root);
|
|
38334
|
+
const openGh2 = seams.openGh ?? ((cwd, timeoutMs) => createGhRunner({ cwd, timeoutMs }));
|
|
38335
|
+
const deadline = openDeadline(seams.timeoutMs ?? STATUS_NETWORK_TIMEOUT_MS);
|
|
38336
|
+
const gh = boundedRunner(openGh2, root, deadline);
|
|
38337
|
+
const pulls = (seams.pullRequests ?? ((runner) => createGhPullRequests({ gh: runner })))(gh);
|
|
38338
|
+
const board = (seams.board ?? ghNextBoard)({ gh, git, configured: config.roadmapIssue });
|
|
38339
|
+
const sources = {
|
|
38340
|
+
base: config.prBase ?? DEFAULT_BASE_BRANCH,
|
|
38341
|
+
plans: plansDirAt(root, config.planDir),
|
|
38342
|
+
runs: () => readSessions(root, { isAlive: seams.isAlive }),
|
|
38343
|
+
git,
|
|
38344
|
+
pulls,
|
|
38345
|
+
board
|
|
38346
|
+
};
|
|
38347
|
+
const branch = localSection(() => readBranchSection(sources));
|
|
38348
|
+
const loops = readLoopsSection(root, sources);
|
|
38349
|
+
const provider = resolvePrProvider({ configured: config.prProvider, dir: root, readRemote: seams.readRemote });
|
|
38350
|
+
const isGh = provider.provider === "gh";
|
|
38351
|
+
const cleanup = (seams.cleanupSeams ?? defaultCleanupSeams)(root, isGh ? pulls : null);
|
|
38352
|
+
const now = (seams.now ?? (() => new Date))();
|
|
38353
|
+
const blockedIssues = seams.blockedIssues ?? ((runner) => readBlockedIssues({ gh: runner }));
|
|
38354
|
+
deadline.start();
|
|
38355
|
+
const pull = pullSection(sources, branch, provider, deadline);
|
|
38356
|
+
const boardSection = isGh ? networkSection("board", deadline, () => readBoard(board, blockedIssues(gh))) : Promise.resolve(unread2(notGhProblem("board", provider)));
|
|
38357
|
+
const housekeeping = readHousekeeping(input, cleanup, now);
|
|
38358
|
+
return {
|
|
38359
|
+
branch,
|
|
38360
|
+
loops,
|
|
38361
|
+
pull: await pull,
|
|
38362
|
+
board: await boardSection,
|
|
38363
|
+
housekeeping: await housekeeping
|
|
38364
|
+
};
|
|
38365
|
+
}
|
|
38366
|
+
var STATUS_NETWORK_TIMEOUT_MS = 5000;
|
|
38367
|
+
var init_sections = __esm(() => {
|
|
38368
|
+
init_github();
|
|
38369
|
+
init_blocked();
|
|
38370
|
+
init_cleanup();
|
|
38371
|
+
init_doctor_blocked();
|
|
38372
|
+
init_doctor_cleanup();
|
|
38373
|
+
init_loop_sessions();
|
|
38374
|
+
init_status2();
|
|
38375
|
+
init_plan_files();
|
|
38376
|
+
init_config_sections();
|
|
38377
|
+
init_sessions();
|
|
38378
|
+
init_readings();
|
|
38379
|
+
init_sources();
|
|
38380
|
+
init_pr();
|
|
38381
|
+
});
|
|
38382
|
+
|
|
38383
|
+
// src/commands/status.ts
|
|
38384
|
+
function projectOf15(context) {
|
|
38385
|
+
if (context.project === null)
|
|
38386
|
+
throw new Error("rafa status runs inside a project, and was handed none");
|
|
38387
|
+
return context.project;
|
|
38388
|
+
}
|
|
38389
|
+
function expectNoWord(args) {
|
|
38390
|
+
if (args.length === 0)
|
|
38391
|
+
return;
|
|
38392
|
+
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME6}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
|
|
38393
|
+
Usage: ${STATUS_USAGE}`);
|
|
38394
|
+
}
|
|
38395
|
+
async function runStatus2(context, seams) {
|
|
38396
|
+
expectNoWord(context.args);
|
|
38397
|
+
const project = projectOf15(context);
|
|
38398
|
+
const config = resolveProjectConfig(project, COMMAND_NAME6, (message) => {
|
|
38399
|
+
context.output.warn(message);
|
|
38400
|
+
});
|
|
38401
|
+
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
38402
|
+
const sections2 = await read3({ root: project.root, home: project.home, config });
|
|
38403
|
+
if (context.outputMode === "json") {
|
|
38404
|
+
context.output.result(statusData(sections2));
|
|
38405
|
+
return;
|
|
38406
|
+
}
|
|
38407
|
+
for (const line of renderStatus3(sections2)) {
|
|
38408
|
+
if (line.level === "warn")
|
|
38409
|
+
context.output.warn(line.text);
|
|
38410
|
+
else
|
|
38411
|
+
context.output.info(line.text);
|
|
38412
|
+
}
|
|
38413
|
+
}
|
|
38414
|
+
function createStatusCommand(seams = DEFAULT_STATUS_SEAMS) {
|
|
38415
|
+
const command = {
|
|
38416
|
+
name: "status",
|
|
38417
|
+
subject: "status",
|
|
38418
|
+
action: "status",
|
|
38419
|
+
summary: "where the project stands: branch and plan, loops, pull request, board and housekeeping",
|
|
38420
|
+
description: "Prints one line for each of five sections. Branch: the branch checked out at the project root" + " and the plan its `feat/<stub>` names, with its task counts. Loops: how many sessions are running and" + " how many tasks are blocked, with a line under it for each running loop and each blocked task. Pull" + " request: the branch's open pull request, whether it can be merged and its checks. Board: the" + " Roadmap's next issue and whether it is ready, and how many issues carry `spec:blocked`. Housekeeping:" + " what `rafa cleanup` would list, counted per group, and how many worktrees are idle. Git, the session" + " records and the plans are read first; the pull request and the board are read through `gh` under a" + " short deadline, and nothing is fetched. A section that cannot be read is one `warn` line saying why," + " and the others are printed all the same: it exits 1 only for a config that cannot be used. With" + " `--output=json` the five sections are the data of the terminal result event. Starts no session.",
|
|
38421
|
+
args: [],
|
|
38422
|
+
flags: [],
|
|
38423
|
+
examples: [
|
|
38424
|
+
{
|
|
38425
|
+
cmd: "rafa status",
|
|
38426
|
+
note: "Prints the five sections, one line each, with the running loops and blocked tasks under Loops."
|
|
38427
|
+
},
|
|
38428
|
+
{
|
|
38429
|
+
cmd: "rafa status --output=json",
|
|
38430
|
+
note: "Gives the five sections as data, each with `read` and its reading or the problem."
|
|
38431
|
+
}
|
|
38432
|
+
],
|
|
38433
|
+
outputs: ["text", "json"],
|
|
38434
|
+
run: async (context) => runStatus2(context, seams)
|
|
38435
|
+
};
|
|
38436
|
+
return Object.freeze(command);
|
|
38437
|
+
}
|
|
38438
|
+
var COMMAND_NAME6 = "rafa status", STATUS_USAGE = "rafa status", STATUS_ARGUMENT_EXIT = 2, DEFAULT_STATUS_SEAMS, status_default3;
|
|
38439
|
+
var init_status4 = __esm(() => {
|
|
38440
|
+
init_command();
|
|
38441
|
+
init_render();
|
|
38442
|
+
init_sections();
|
|
38443
|
+
init_plan_files();
|
|
38444
|
+
DEFAULT_STATUS_SEAMS = Object.freeze({});
|
|
38445
|
+
status_default3 = createStatusCommand();
|
|
38446
|
+
});
|
|
38447
|
+
|
|
38010
38448
|
// src/usage.ts
|
|
38011
38449
|
async function usage(_args) {
|
|
38012
38450
|
const pct = await getClaudeUsagePercent();
|
|
@@ -38113,6 +38551,7 @@ var init_commands2 = __esm(() => {
|
|
|
38113
38551
|
init_list();
|
|
38114
38552
|
init_search2();
|
|
38115
38553
|
init_show7();
|
|
38554
|
+
init_status4();
|
|
38116
38555
|
init_usage2();
|
|
38117
38556
|
CORE_SUBJECTS = Object.freeze([
|
|
38118
38557
|
{ name: "plan", summary: "create plans from specs; list, show and validate them; read their risk and needs" },
|
|
@@ -38173,6 +38612,7 @@ var init_commands2 = __esm(() => {
|
|
|
38173
38612
|
show_default2,
|
|
38174
38613
|
status_default2,
|
|
38175
38614
|
tag_default,
|
|
38615
|
+
status_default3,
|
|
38176
38616
|
next_default,
|
|
38177
38617
|
roadmap_default,
|
|
38178
38618
|
init_default,
|
|
@@ -38606,9 +39046,31 @@ function placeCommand(command, settings) {
|
|
|
38606
39046
|
return { kind: "refused", message: error.message };
|
|
38607
39047
|
}
|
|
38608
39048
|
}
|
|
39049
|
+
async function callHook(half, call, base) {
|
|
39050
|
+
try {
|
|
39051
|
+
return await call();
|
|
39052
|
+
} catch (error) {
|
|
39053
|
+
base.output.debug(`command hook ${half}: ${messageOf2(error)}`);
|
|
39054
|
+
return null;
|
|
39055
|
+
}
|
|
39056
|
+
}
|
|
38609
39057
|
async function settleCommand(route, base, registry, settings) {
|
|
38610
39058
|
const placement = placeCommand(route.command, settings);
|
|
38611
|
-
|
|
39059
|
+
if (placement.kind === "refused")
|
|
39060
|
+
return failure("no_project", placement.message);
|
|
39061
|
+
const { project } = placement;
|
|
39062
|
+
const hook = base.outputMode === "text" ? settings.commandHook : null;
|
|
39063
|
+
if (hook === null || project === null)
|
|
39064
|
+
return runCommand(route, base, registry, project, settings);
|
|
39065
|
+
const line = await callHook("before", async () => hook.before(route, project), base);
|
|
39066
|
+
if (line !== null)
|
|
39067
|
+
settings.stderr.write(`${line}
|
|
39068
|
+
`);
|
|
39069
|
+
try {
|
|
39070
|
+
return await runCommand(route, base, registry, project, settings);
|
|
39071
|
+
} finally {
|
|
39072
|
+
await callHook("after", async () => hook.after(route, project), base);
|
|
39073
|
+
}
|
|
38612
39074
|
}
|
|
38613
39075
|
async function settle(route, base, problem, registry, settings) {
|
|
38614
39076
|
switch (route.kind) {
|
|
@@ -38665,7 +39127,8 @@ async function dispatch(argv, options) {
|
|
|
38665
39127
|
now: options.now ?? (() => new Date),
|
|
38666
39128
|
renderHelp: options.renderHelp ?? renderUsage,
|
|
38667
39129
|
cwd: () => options.cwd ?? process.cwd(),
|
|
38668
|
-
home: () => options.home ?? homedir()
|
|
39130
|
+
home: () => options.home ?? homedir(),
|
|
39131
|
+
commandHook: options.commandHook ?? null
|
|
38669
39132
|
};
|
|
38670
39133
|
const loaded = await loadModuleCommands(options.registry, options.modules ?? [], options.importModule);
|
|
38671
39134
|
const route = routeLine(loaded.registry, argv);
|
|
@@ -38688,11 +39151,191 @@ async function dispatch(argv, options) {
|
|
|
38688
39151
|
init_help();
|
|
38689
39152
|
init_commands2();
|
|
38690
39153
|
init_load();
|
|
39154
|
+
|
|
39155
|
+
// src/status/hook.ts
|
|
39156
|
+
init_config_load();
|
|
39157
|
+
|
|
39158
|
+
// src/status/notice.ts
|
|
39159
|
+
var CLEANUP_COMMAND2 = "rafa cleanup";
|
|
39160
|
+
var STATUS_COMMAND = "rafa status";
|
|
39161
|
+
var NOTICE_PREFIX = "rafa: since your last command: ";
|
|
39162
|
+
var LIVE_STATES2 = new Set(["running", "paused"]);
|
|
39163
|
+
function added(previous, current) {
|
|
39164
|
+
const seen = new Set(previous);
|
|
39165
|
+
return current.filter((item) => !seen.has(item)).sort();
|
|
39166
|
+
}
|
|
39167
|
+
function hasStopped(before, now) {
|
|
39168
|
+
return before !== undefined && LIVE_STATES2.has(before.state) && !LIVE_STATES2.has(now.state);
|
|
39169
|
+
}
|
|
39170
|
+
function hasNewBlocked(before, now) {
|
|
39171
|
+
const seen = new Set(before?.blocked ?? []);
|
|
39172
|
+
return now.blocked.some((line) => !seen.has(line));
|
|
39173
|
+
}
|
|
39174
|
+
function compareSeen(previous, current) {
|
|
39175
|
+
const ids = Object.keys(current.sessions).sort();
|
|
39176
|
+
const before = (id) => Object.hasOwn(previous.sessions, id) ? previous.sessions[id] : undefined;
|
|
39177
|
+
return {
|
|
39178
|
+
idleWorktrees: added(previous.idleWorktrees, current.idleWorktrees),
|
|
39179
|
+
mergedBranches: added(previous.mergedBranches, current.mergedBranches),
|
|
39180
|
+
stoppedSessions: ids.filter((id) => hasStopped(before(id), current.sessions[id])),
|
|
39181
|
+
blockedSessions: ids.filter((id) => hasNewBlocked(before(id), current.sessions[id]))
|
|
39182
|
+
};
|
|
39183
|
+
}
|
|
39184
|
+
function counted7(count, singular, many) {
|
|
39185
|
+
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
39186
|
+
}
|
|
39187
|
+
function newsLine(news) {
|
|
39188
|
+
const parts = [
|
|
39189
|
+
[news.idleWorktrees.length, "worktree went idle", "worktrees went idle"],
|
|
39190
|
+
[news.mergedBranches.length, "merged branch left behind", "merged branches left behind"],
|
|
39191
|
+
[news.stoppedSessions.length, "loop stopped", "loops stopped"],
|
|
39192
|
+
[news.blockedSessions.length, "loop has a new blocked task", "loops have new blocked tasks"]
|
|
39193
|
+
];
|
|
39194
|
+
const said = parts.filter(([count]) => count > 0).map(([count, one, many]) => counted7(count, one, many));
|
|
39195
|
+
if (said.length === 0)
|
|
39196
|
+
return null;
|
|
39197
|
+
const loops = news.stoppedSessions.length + news.blockedSessions.length;
|
|
39198
|
+
const command = loops > 0 ? STATUS_COMMAND : CLEANUP_COMMAND2;
|
|
39199
|
+
return `${NOTICE_PREFIX}${said.join(", ")}; run ${command}`;
|
|
39200
|
+
}
|
|
39201
|
+
function noticeLine(previous, current) {
|
|
39202
|
+
if (previous === null)
|
|
39203
|
+
return null;
|
|
39204
|
+
return newsLine(compareSeen(previous, current));
|
|
39205
|
+
}
|
|
39206
|
+
|
|
39207
|
+
// src/status/seen.ts
|
|
39208
|
+
init_cleanup();
|
|
39209
|
+
init_doctor_cleanup();
|
|
39210
|
+
init_loop_sessions();
|
|
39211
|
+
init_status2();
|
|
39212
|
+
init_config_sections();
|
|
39213
|
+
init_sessions();
|
|
39214
|
+
init_scope();
|
|
39215
|
+
init_sections();
|
|
39216
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync54, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "fs";
|
|
39217
|
+
import { dirname as dirname20, join as join61 } from "path";
|
|
39218
|
+
var SEEN_FILE = "status-seen.json";
|
|
39219
|
+
var SEEN_VERSION = 1;
|
|
39220
|
+
function seenFilePath(root) {
|
|
39221
|
+
return join61(scopeAt(root).dir, SEEN_FILE);
|
|
39222
|
+
}
|
|
39223
|
+
function readSeenSessions(root, isAlive) {
|
|
39224
|
+
const sessions = {};
|
|
39225
|
+
for (const record of readSessions(root, { isAlive })) {
|
|
39226
|
+
const blocked2 = blockedTasks(readSessionChecklist(root, record)).map((task) => task.line);
|
|
39227
|
+
sessions[record.sessionId] = { state: record.state, blocked: [...blocked2].sort((a, b) => a - b) };
|
|
39228
|
+
}
|
|
39229
|
+
return sessions;
|
|
39230
|
+
}
|
|
39231
|
+
async function takeSeenSnapshot(input, seams = {}) {
|
|
39232
|
+
try {
|
|
39233
|
+
const now = (seams.now ?? (() => new Date))();
|
|
39234
|
+
const worktreeSeams = (seams.worktreeSeams ?? defaultWorktreeSeams)(input.root);
|
|
39235
|
+
const settings = doctorCleanupSettings({ ...input, gh: null }, now);
|
|
39236
|
+
const reading2 = await readCleanup({ ...worktreeSeams, pulls: null }, { ...settings, fetch: false });
|
|
39237
|
+
if (!reading2.ok)
|
|
39238
|
+
return { ok: false, detail: reading2.detail };
|
|
39239
|
+
const idle = reading2.worktrees.filter((row2) => isIdleWorktree(row2)).map((row2) => row2.path);
|
|
39240
|
+
const merged2 = reading2.merged.map((row2) => row2.branch.name);
|
|
39241
|
+
return {
|
|
39242
|
+
ok: true,
|
|
39243
|
+
snapshot: {
|
|
39244
|
+
version: SEEN_VERSION,
|
|
39245
|
+
idleWorktrees: [...idle].sort(),
|
|
39246
|
+
mergedBranches: [...merged2].sort(),
|
|
39247
|
+
sessions: readSeenSessions(input.root, seams.isAlive)
|
|
39248
|
+
}
|
|
39249
|
+
};
|
|
39250
|
+
} catch (error) {
|
|
39251
|
+
return { ok: false, detail: messageOf(error) };
|
|
39252
|
+
}
|
|
39253
|
+
}
|
|
39254
|
+
function isRecord2(value) {
|
|
39255
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39256
|
+
}
|
|
39257
|
+
function isStrings(value) {
|
|
39258
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
39259
|
+
}
|
|
39260
|
+
function isSeenSession(value) {
|
|
39261
|
+
return isRecord2(value) && SESSION_STATES.includes(value.state) && Array.isArray(value.blocked) && value.blocked.every((line) => Number.isInteger(line) && line > 0);
|
|
39262
|
+
}
|
|
39263
|
+
function asSnapshot(value) {
|
|
39264
|
+
if (!isRecord2(value) || value.version !== SEEN_VERSION)
|
|
39265
|
+
return null;
|
|
39266
|
+
const { idleWorktrees, mergedBranches, sessions } = value;
|
|
39267
|
+
if (!isStrings(idleWorktrees) || !isStrings(mergedBranches) || !isRecord2(sessions))
|
|
39268
|
+
return null;
|
|
39269
|
+
if (!Object.values(sessions).every((session) => isSeenSession(session)))
|
|
39270
|
+
return null;
|
|
39271
|
+
return { version: SEEN_VERSION, idleWorktrees, mergedBranches, sessions };
|
|
39272
|
+
}
|
|
39273
|
+
function readSeenFile(root) {
|
|
39274
|
+
let text2;
|
|
39275
|
+
try {
|
|
39276
|
+
text2 = readFileSync54(seenFilePath(root), "utf8");
|
|
39277
|
+
} catch {
|
|
39278
|
+
return null;
|
|
39279
|
+
}
|
|
39280
|
+
try {
|
|
39281
|
+
return asSnapshot(JSON.parse(text2));
|
|
39282
|
+
} catch {
|
|
39283
|
+
return null;
|
|
39284
|
+
}
|
|
39285
|
+
}
|
|
39286
|
+
function writeSeenFile(root, snapshot) {
|
|
39287
|
+
const file = seenFilePath(root);
|
|
39288
|
+
mkdirSync19(dirname20(file), { recursive: true });
|
|
39289
|
+
const temporary = `${file}.${String(process.pid)}.tmp`;
|
|
39290
|
+
writeFileSync21(temporary, `${JSON.stringify(snapshot, null, 2)}
|
|
39291
|
+
`);
|
|
39292
|
+
renameSync5(temporary, file);
|
|
39293
|
+
}
|
|
39294
|
+
|
|
39295
|
+
// src/status/hook.ts
|
|
39296
|
+
var QUIET_COMMANDS = Object.freeze(["status", "cleanup"]);
|
|
39297
|
+
function projectConfig(project) {
|
|
39298
|
+
return loadConfig({ root: project.root, home: project.home }, {}, () => {
|
|
39299
|
+
return;
|
|
39300
|
+
}).config;
|
|
39301
|
+
}
|
|
39302
|
+
function isQuietCommand(route) {
|
|
39303
|
+
return route.module === null && QUIET_COMMANDS.includes(route.command.name);
|
|
39304
|
+
}
|
|
39305
|
+
function createStatusHook(seams = {}) {
|
|
39306
|
+
const configOf = seams.config ?? projectConfig;
|
|
39307
|
+
const take = seams.take ?? (async (input) => takeSeenSnapshot(input));
|
|
39308
|
+
const snapshot = async (project) => {
|
|
39309
|
+
const config = configOf(project);
|
|
39310
|
+
if (!config.statusNotice)
|
|
39311
|
+
return null;
|
|
39312
|
+
const reading2 = await take({ root: project.root, home: project.home, config });
|
|
39313
|
+
if (!reading2.ok)
|
|
39314
|
+
throw new Error(`the status reading failed: ${reading2.detail}`);
|
|
39315
|
+
return reading2.snapshot;
|
|
39316
|
+
};
|
|
39317
|
+
return Object.freeze({
|
|
39318
|
+
before: async (route, project) => {
|
|
39319
|
+
if (isQuietCommand(route))
|
|
39320
|
+
return null;
|
|
39321
|
+
const current = await snapshot(project);
|
|
39322
|
+
return current === null ? null : noticeLine(readSeenFile(project.root), current);
|
|
39323
|
+
},
|
|
39324
|
+
after: async (_route, project) => {
|
|
39325
|
+
const current = await snapshot(project);
|
|
39326
|
+
if (current !== null)
|
|
39327
|
+
writeSeenFile(project.root, current);
|
|
39328
|
+
}
|
|
39329
|
+
});
|
|
39330
|
+
}
|
|
39331
|
+
|
|
39332
|
+
// src/rafa.ts
|
|
38691
39333
|
var modules = await loadInvocationModules({ cwd: process.cwd(), home: homedir10() });
|
|
38692
39334
|
var { exitCode } = await dispatch(process.argv.slice(2), {
|
|
38693
39335
|
registry: CORE_REGISTRY,
|
|
38694
39336
|
renderHelp,
|
|
38695
39337
|
modules: modules.commands,
|
|
38696
|
-
warnings: modules.warnings
|
|
39338
|
+
warnings: modules.warnings,
|
|
39339
|
+
commandHook: createStatusHook()
|
|
38697
39340
|
});
|
|
38698
39341
|
process.exitCode = exitCode;
|