@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/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.16.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 [stdout, stderr, exitCode] = await Promise.all([
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 = BACKTICK_RE.exec(description)?.[1]?.trim();
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 reminderOf(item) {
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) => !isProbed(item)).map(reminderOf))
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, BACKTICK_RE, HEADING_MARKS_RE, FENCE_RE, NESTED_LIST_ITEM_RE, PLAN_FILE_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
- BACKTICK_RE = /`([^`]+)`/;
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: index + 1,
12353
- fenced: inFence,
12422
+ number,
12423
+ fenced: false,
12354
12424
  visible: stripped.visible,
12355
12425
  comments: stripped.comments,
12356
- heading: inFence ? null : headingOf2(stripped.visible)
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 codeSpanEnd(text2, from, length) {
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 = codeSpanEnd(text2, index + run, run);
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
- return placement.kind === "refused" ? failure("no_project", placement.message) : runCommand(route, base, registry, placement.project, settings);
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;