@gobing-ai/spur 0.4.10 → 0.4.11

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/spur.js CHANGED
@@ -144167,10 +144167,333 @@ function runSpine(argv, rt) {
144167
144167
  return 2;
144168
144168
  }
144169
144169
  }
144170
+ function obj(value) {
144171
+ return typeof value === "object" && value !== null ? value : {};
144172
+ }
144173
+ function text4(value) {
144174
+ return typeof value === "string" ? value : "";
144175
+ }
144176
+ function list(value) {
144177
+ return Array.isArray(value) ? value : [];
144178
+ }
144179
+ function word(payload, fallback = "FAIL") {
144180
+ const verdict = payload?.verdict;
144181
+ return typeof verdict === "string" && verdict !== "" ? verdict : fallback;
144182
+ }
144183
+ function findingsArg(value) {
144184
+ return list(value);
144185
+ }
144186
+ function runVerbOp(argv, rt, sink) {
144187
+ const out = [];
144188
+ const previous = io;
144189
+ io = { out: (line) => out.push(line), err: (line) => sink.err(line) };
144190
+ try {
144191
+ const code = runSpine(argv, rt);
144192
+ let payload = null;
144193
+ for (let i = out.length - 1;i >= 0; i--) {
144194
+ const line = (out[i] ?? "").trim();
144195
+ if (!line.startsWith("{"))
144196
+ continue;
144197
+ try {
144198
+ payload = JSON.parse(line);
144199
+ } catch {
144200
+ payload = null;
144201
+ }
144202
+ break;
144203
+ }
144204
+ return { code, payload };
144205
+ } finally {
144206
+ io = previous;
144207
+ }
144208
+ }
144209
+ function validPollBudget(opts, sink) {
144210
+ if (opts.timeout !== undefined && !(Number.isFinite(Number(opts.timeout)) && Number(opts.timeout) >= 0)) {
144211
+ sink.err("error: --timeout must be a non-negative number");
144212
+ return false;
144213
+ }
144214
+ if (opts.interval !== undefined && !(Number.isFinite(Number(opts.interval)) && Number(opts.interval) > 0)) {
144215
+ sink.err("error: --interval must be a positive number");
144216
+ return false;
144217
+ }
144218
+ return true;
144219
+ }
144220
+ function writeVerbStatus(rt, opts, verdict) {
144221
+ if (opts.statusFile !== undefined)
144222
+ rt.writeStatus(opts.statusFile, `${verdict}
144223
+ `);
144224
+ }
144225
+ function currentHead(rt) {
144226
+ const res = run3(rt, ["git", "rev-parse", "HEAD"]);
144227
+ const head = res.stdout.trim();
144228
+ return res.code === 0 && head !== "" ? head : undefined;
144229
+ }
144230
+ function composeStatus(rt, opts, sink) {
144231
+ const flags = ["--json"];
144232
+ if (opts.since !== undefined)
144233
+ flags.push("--since", opts.since);
144234
+ const head = opts.head ?? currentHead(rt);
144235
+ if (head !== undefined)
144236
+ flags.push("--head", head);
144237
+ const status = runVerbOp(["status", ...flags], rt, sink);
144238
+ if (status.code !== 0)
144239
+ return { code: status.code, payload: { ...obj(status.payload), review: null } };
144240
+ if (status.payload?.pr === null || status.payload?.pr === undefined) {
144241
+ return { code: 0, payload: { ...obj(status.payload), review: null } };
144242
+ }
144243
+ const collect = runVerbOp(["collect", ...flags], rt, sink);
144244
+ return { code: collect.code, payload: { ...obj(status.payload), review: collect.payload } };
144245
+ }
144246
+ function composeCheck(rt, opts, sink) {
144247
+ const flags = ["--json"];
144248
+ if (opts.statusFile !== undefined)
144249
+ flags.push("--status-file", opts.statusFile);
144250
+ if (opts.base !== undefined)
144251
+ flags.push("--base", opts.base);
144252
+ const preflight = opts.scanOnly === true ? null : runVerbOp(["preflight", ...flags], rt, sink);
144253
+ if (preflight !== null && preflight.code !== 0) {
144254
+ return {
144255
+ code: preflight.code,
144256
+ payload: { ok: false, verdict: "FAIL", preflight: preflight.payload, hygiene: null }
144257
+ };
144258
+ }
144259
+ const hygiene = runVerbOp(["hygiene", ...flags], rt, sink);
144260
+ const opWord = word(hygiene.payload, "PASS");
144261
+ const verdict = hygiene.code === 0 ? opWord : opWord === "BLOCK" ? "BLOCK" : "FAIL";
144262
+ return {
144263
+ code: hygiene.code,
144264
+ payload: { ok: hygiene.code === 0, verdict, preflight: preflight?.payload ?? null, hygiene: hygiene.payload }
144265
+ };
144266
+ }
144267
+ function composeOpen(rt, opts, sink) {
144268
+ const flags = ["--json"];
144269
+ if (opts.statusFile !== undefined)
144270
+ flags.push("--status-file", opts.statusFile);
144271
+ const push = runVerbOp(["push", ...flags], rt, sink);
144272
+ if (push.code !== 0) {
144273
+ return { code: push.code, payload: { ok: false, verdict: "FAIL", push: push.payload, pr: null } };
144274
+ }
144275
+ const ensureFlags = [...flags];
144276
+ if (opts.base !== undefined)
144277
+ ensureFlags.push("--base", opts.base);
144278
+ const ensure = runVerbOp(["ensure", ...ensureFlags], rt, sink);
144279
+ const verdict = ensure.code === 0 ? ensure.payload?.created === true ? "CREATED" : "FOUND" : "FAIL";
144280
+ return {
144281
+ code: ensure.code,
144282
+ payload: { ok: ensure.code === 0, verdict, push: push.payload, pr: ensure.payload?.pr ?? null }
144283
+ };
144284
+ }
144285
+ function composeReview(rt, opts, sink) {
144286
+ const statusFlags = opts.statusFile !== undefined ? ["--status-file", opts.statusFile] : [];
144287
+ if (!validPollBudget(opts, sink)) {
144288
+ return { code: 1, payload: { ok: false, verdict: "FAIL", request: null, findings: [] } };
144289
+ }
144290
+ const requestFlags = ["--json", ...statusFlags];
144291
+ if (opts.force === true)
144292
+ requestFlags.push("--force");
144293
+ if (opts.focus !== undefined)
144294
+ requestFlags.push("--focus", opts.focus);
144295
+ const request = runVerbOp(["request", ...requestFlags], rt, sink);
144296
+ if (request.code !== 0) {
144297
+ return { code: request.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
144298
+ }
144299
+ const record = obj(request.payload);
144300
+ const correlation = [];
144301
+ if (text4(record.requestedAt) !== "")
144302
+ correlation.push("--since", text4(record.requestedAt));
144303
+ if (text4(record.head) !== "")
144304
+ correlation.push("--head", text4(record.head));
144305
+ const collected = () => {
144306
+ const collect = runVerbOp(["collect", ...correlation, "--json", ...statusFlags], rt, sink);
144307
+ return {
144308
+ code: collect.code,
144309
+ payload: {
144310
+ ok: collect.code === 0,
144311
+ verdict: collect.code === 0 ? word(collect.payload) : "FAIL",
144312
+ request: request.payload,
144313
+ findings: collect.code === 0 ? list(collect.payload?.findings) : []
144314
+ }
144315
+ };
144316
+ };
144317
+ const pending = (code) => {
144318
+ writeVerbStatus(rt, opts, "PENDING");
144319
+ return { code, payload: { ok: true, verdict: "PENDING", request: request.payload, findings: [] } };
144320
+ };
144321
+ if (record.alreadyReviewed === true)
144322
+ return collected();
144323
+ if (record.pending === true || record.requested === true && opts.noWait === true)
144324
+ return pending(0);
144325
+ const waitFlags = [...correlation, "--json", ...statusFlags];
144326
+ if (opts.timeout !== undefined)
144327
+ waitFlags.push("--timeout", opts.timeout);
144328
+ if (opts.interval !== undefined)
144329
+ waitFlags.push("--interval", opts.interval);
144330
+ const waited = runVerbOp(["wait", ...waitFlags], rt, sink);
144331
+ if (waited.code === 3)
144332
+ return pending(3);
144333
+ if (waited.code !== 0) {
144334
+ return { code: waited.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
144335
+ }
144336
+ return collected();
144337
+ }
144338
+ function renderPrVerb(verb, payload) {
144339
+ if (verb === "status") {
144340
+ const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
144341
+ const review = payload.review === null || payload.review === undefined ? null : obj(payload.review);
144342
+ const lines = [
144343
+ `Repository: ${text4(payload.repo) || "unknown"}`,
144344
+ `PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)}`}`,
144345
+ `Branch: ${text4(payload.branch)}`,
144346
+ `HEAD: ${text4(payload.head)}`,
144347
+ `Base: ${text4(payload.base)}`,
144348
+ `Local: ${text4(payload.local)}`,
144349
+ `CI: ${text4(payload.ci)}`,
144350
+ `Codex: ${text4(payload.codex)}`
144351
+ ];
144352
+ if (review === null)
144353
+ return [...lines, "Review: no PR \u2014 nothing collected"];
144354
+ lines.push(`Review: ${text4(review.verdict) || "unknown"}`);
144355
+ const findings = findingsArg(review.findings);
144356
+ if (findings.length > 0)
144357
+ lines.push("", renderFindings(findings));
144358
+ return lines;
144359
+ }
144360
+ if (verb === "check") {
144361
+ const preflight = payload.preflight === null || payload.preflight === undefined ? null : obj(payload.preflight);
144362
+ const hygiene = payload.hygiene === null || payload.hygiene === undefined ? null : obj(payload.hygiene);
144363
+ const lines = [
144364
+ preflight === null ? "Preflight: skipped (--scan-only)" : preflight.ok === true ? `Preflight: ${text4(preflight.nameWithOwner)} ${text4(preflight.branch)} @ ${text4(preflight.shortHead)}` : `Preflight: FAIL \u2014 ${text4(preflight.error) || "probe failed"}`
144365
+ ];
144366
+ if (hygiene === null)
144367
+ return lines;
144368
+ lines.push(`Hygiene: ${text4(hygiene.verdict) || "FAIL"}`);
144369
+ for (const blocker of list(hygiene.blockers))
144370
+ lines.push(` BLOCK ${String(blocker)}`);
144371
+ for (const warning of list(hygiene.warnings))
144372
+ lines.push(` WARN ${String(warning)}`);
144373
+ return lines;
144374
+ }
144375
+ if (verb === "open") {
144376
+ const push = obj(payload.push);
144377
+ const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
144378
+ return [
144379
+ `Push: ${push.pushed === true ? "PUSHED" : "UP_TO_DATE"} ${text4(push.head).slice(0, 7)}`,
144380
+ `PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)} (${text4(payload.verdict)})`}`
144381
+ ];
144382
+ }
144383
+ const request = obj(payload.request);
144384
+ const outcome = request.requested === true ? "REQUESTED" : request.alreadyReviewed === true ? "ALREADY_REVIEWED" : request.pending === true ? "ALREADY_REQUESTED" : "FAIL";
144385
+ const lines = [
144386
+ `Request: ${outcome} PR #${String(request.pr ?? "?")} ${text4(request.url)}`,
144387
+ `HEAD: ${text4(request.head).slice(0, 7)}`,
144388
+ `Review: ${text4(payload.verdict) || "unknown"}`
144389
+ ];
144390
+ const findings = findingsArg(payload.findings);
144391
+ if (findings.length > 0)
144392
+ lines.push("", renderFindings(findings));
144393
+ return lines;
144394
+ }
144395
+ var PR_VERB_COMPOSERS = { status: composeStatus, check: composeCheck, open: composeOpen, review: composeReview };
144396
+ function runPrVerb(verb, opts, sink, context = {}) {
144397
+ const rt = createRuntime(context);
144398
+ const result = PR_VERB_COMPOSERS[verb](rt, opts, sink);
144399
+ if (opts.json === true)
144400
+ sink.out(JSON.stringify(result.payload));
144401
+ else
144402
+ for (const line of renderPrVerb(verb, result.payload))
144403
+ sink.out(line);
144404
+ return result.code;
144405
+ }
144170
144406
 
144171
144407
  // src/commands/builder/pr.ts
144172
- function registerBuilderPrCommand(builder, context) {
144173
- builder.command("pr").summary("GitHub Codex PR-review loop (preflight | push | ensure | hygiene | request | wait | collect | status)").description(PR_REVIEW_HELP).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "subcommand and flags (see description)").action((argv) => {
144408
+ var PR_VERB_FOR_OP = {
144409
+ preflight: "pr-check",
144410
+ hygiene: "pr-check",
144411
+ push: "pr-open",
144412
+ ensure: "pr-open",
144413
+ "ensure-pr": "pr-open",
144414
+ request: "pr-review",
144415
+ wait: "pr-review",
144416
+ collect: "pr-status",
144417
+ status: "pr-status"
144418
+ };
144419
+ function prDeprecationLine(op) {
144420
+ const replacement = op === undefined ? undefined : PR_VERB_FOR_OP[op];
144421
+ return replacement !== undefined ? `spur builder pr ${op} is deprecated; use spur builder ${replacement}` : "spur builder pr is deprecated; use spur builder pr-status | pr-check | pr-open | pr-review";
144422
+ }
144423
+ function registerBuilderPrCommands(builder, context) {
144424
+ const io = () => ({
144425
+ out: (line) => context.output.write(line),
144426
+ err: (line) => context.output.error(line)
144427
+ });
144428
+ const ctx = () => ({ cwd: context.cwd, env: context.env });
144429
+ const run = (verb, opts) => {
144430
+ context.setExitCode(runPrVerb(verb, opts, io(), ctx()));
144431
+ };
144432
+ builder.command("pr-status").summary("read-only PR/CI/Codex status with the current-HEAD review as `review`").description([
144433
+ "Read-only composition of the status and collect operations:",
144434
+ "repository, branch, PR/base, local changes, CI and Codex state, plus",
144435
+ "`review` \u2014 the current-HEAD Codex record when the branch has a PR,",
144436
+ "else null. Writes no verdict: a status probe is not a gate.",
144437
+ "Exit codes: 0 ok \xB7 2 probe failure."
144438
+ ].join(`
144439
+ `)).option("--since <iso>", "only count review output submitted at or after this time").option("--head <sha>", "pin the reviewed commit (default: the current local HEAD)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "accepted for symmetry; a read-only status writes no verdict").action((options) => {
144440
+ run("status", {
144441
+ ...options.since !== undefined ? { since: options.since } : {},
144442
+ ...options.head !== undefined ? { head: options.head } : {},
144443
+ ...options.json === true ? { json: true } : {},
144444
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144445
+ });
144446
+ });
144447
+ builder.command("pr-check").summary("PR submission gate: preflight then hygiene (PASS | WARN | BLOCK | FAIL)").description([
144448
+ "Preflight (git/gh/repo checks, clean tree, not the base branch) then",
144449
+ "the hygiene scan of base...HEAD for secrets/.env/conflict markers",
144450
+ "(BLOCK) and debug residue (WARN). A preflight FAIL stops before",
144451
+ "hygiene. `--scan-only` runs hygiene alone, for a local review on a",
144452
+ "dirty tree. Exit codes: 0 ok \xB7 2 BLOCK/preflight FAIL/probe failure."
144453
+ ].join(`
144454
+ `)).option("--base <branch>", "base branch for the base...HEAD diff (default: the PR base, else the repo default)").option("--scan-only", "hygiene only; skip preflight (a dirty tree is expected)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (PASS | WARN | BLOCK | FAIL)").action((options) => {
144455
+ run("check", {
144456
+ ...options.base !== undefined ? { base: options.base } : {},
144457
+ ...options.scanOnly === true ? { scanOnly: true } : {},
144458
+ ...options.json === true ? { json: true } : {},
144459
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144460
+ });
144461
+ });
144462
+ builder.command("pr-open").summary("publish the branch and reuse or create its pull request (FOUND | CREATED | FAIL)").description([
144463
+ "Push the branch (normal push only \u2014 never force; sets upstream when",
144464
+ "missing), then reuse the branch PR or create one with",
144465
+ "`gh pr create --fill`. A rejected push stops before any PR work.",
144466
+ "Exit codes: 0 ok \xB7 2 push rejected or ensure failed."
144467
+ ].join(`
144468
+ `)).option("--base <branch>", "base for a newly created PR (default: the repo default branch)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (FOUND | CREATED | FAIL)").action((options) => {
144469
+ run("open", {
144470
+ ...options.base !== undefined ? { base: options.base } : {},
144471
+ ...options.json === true ? { json: true } : {},
144472
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144473
+ });
144474
+ });
144475
+ builder.command("pr-review").summary("request the GitHub Codex review and collect its result (CLEAN | FINDINGS | PENDING | FAIL)").description([
144476
+ "Post the `@codex review` request (per-HEAD dedupe unless --force),",
144477
+ "poll for the result, then collect it. An already-reviewed HEAD is",
144478
+ "collected without a new request; an in-flight request or --no-wait",
144479
+ "returns PENDING. Exit codes: 0 ok \xB7 2 hard failure \xB7 3 wait timeout",
144480
+ "(verdict PENDING \u2014 pending, never failure)."
144481
+ ].join(`
144482
+ `)).option("--force", "request a fresh review even when the current HEAD is already reviewed").option("--focus <text>", "extra review focus appended to the request").option("--no-wait", "post the request and return PENDING without polling").option("--timeout <s>", "wait budget in seconds (service default 600)").option("--interval <s>", "poll interval in seconds (service default 30)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (CLEAN | FINDINGS | PENDING | FAIL)").action((options) => {
144483
+ run("review", {
144484
+ ...options.force === true ? { force: true } : {},
144485
+ ...options.focus !== undefined ? { focus: options.focus } : {},
144486
+ ...options.wait === false ? { noWait: true } : {},
144487
+ ...options.timeout !== undefined ? { timeout: options.timeout } : {},
144488
+ ...options.interval !== undefined ? { interval: options.interval } : {},
144489
+ ...options.json === true ? { json: true } : {},
144490
+ ...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
144491
+ });
144492
+ });
144493
+ }
144494
+ function registerBuilderPrAliasCommand(builder, context) {
144495
+ builder.command("pr", { hidden: true }).summary("deprecated alias for the flat pr-<verb> commands").description(PR_REVIEW_HELP).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "subcommand and flags (see description)").action((argv) => {
144496
+ context.output.error(prDeprecationLine(argv[0]));
144174
144497
  context.setExitCode(main(argv, {
144175
144498
  out: (line) => context.output.write(line),
144176
144499
  err: (line) => context.output.error(line)
@@ -144178,9 +144501,574 @@ function registerBuilderPrCommand(builder, context) {
144178
144501
  });
144179
144502
  }
144180
144503
 
144504
+ // ../../packages/app/src/services/branch-topology/render.ts
144505
+ var INDENT = " ";
144506
+ var MERMAID_DEFAULT_BRANCH = "main";
144507
+ var TABLE_HEADERS = ["branch", "ahead", "behind", "contained", "last commit", "worktree", "PR"];
144508
+ function renderMarkdown2(topology, graph) {
144509
+ const lines = [
144510
+ `## Branch topology vs \`${markdownCell(topology.base)}\``,
144511
+ "",
144512
+ `Generated ${topology.generatedAt}.`
144513
+ ];
144514
+ for (const warning of topology.warnings)
144515
+ lines.push("", `> warning: ${warning}`);
144516
+ lines.push("", ...markdownTable(topology.branches), ...omittedFooter(topology, "_", "_"));
144517
+ lines.push("", "```mermaid", renderGitGraph(topology, graph), "```");
144518
+ return `${lines.join(`
144519
+ `)}
144520
+ `;
144521
+ }
144522
+ function renderText(topology) {
144523
+ const rows = [TABLE_HEADERS.map((header) => header), ...topology.branches.map(rowCells)];
144524
+ const widths = TABLE_HEADERS.map((_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
144525
+ const lines = [`base: ${topology.base}`, `generated: ${topology.generatedAt}`];
144526
+ for (const warning of topology.warnings)
144527
+ lines.push(`warning: ${warning}`);
144528
+ lines.push("", ...rows.map((row) => alignRow(row, widths)), ...omittedFooter(topology, "", ""));
144529
+ return `${lines.join(`
144530
+ `)}
144531
+ `;
144532
+ }
144533
+ function markdownTable(branches) {
144534
+ const lines = [`| ${TABLE_HEADERS.join(" | ")} |`, `| ${TABLE_HEADERS.map(() => "---").join(" | ")} |`];
144535
+ for (const row of branches) {
144536
+ lines.push(`| ${rowCells(row).map(markdownCell).join(" | ")} |`);
144537
+ }
144538
+ return lines;
144539
+ }
144540
+ function rowCells(row) {
144541
+ return [
144542
+ row.name,
144543
+ String(row.ahead),
144544
+ String(row.behind),
144545
+ row.contained ? "yes" : "no",
144546
+ row.lastCommitAt.slice(0, 10),
144547
+ row.worktree ?? "-",
144548
+ row.pr === null ? "-" : `#${row.pr.number} ${row.pr.state}${row.pr.reviewDecision === null ? "" : ` ${row.pr.reviewDecision}`}`
144549
+ ];
144550
+ }
144551
+ function alignRow(cells, widths) {
144552
+ return cells.map((cell, column) => {
144553
+ const width = widths[column] ?? cell.length;
144554
+ return column === 0 ? cell.padEnd(width) : cell.padStart(width);
144555
+ }).join(" ").trimEnd();
144556
+ }
144557
+ function omittedFooter(topology, open, close) {
144558
+ if (topology.omitted === 0)
144559
+ return [];
144560
+ const noun = topology.omitted === 1 ? "branch" : "branches";
144561
+ return ["", `${open}Omitted: ${topology.omitted} ${noun} beyond --limit.${close}`];
144562
+ }
144563
+ function markdownCell(value) {
144564
+ return value.replaceAll("|", "\\|").replaceAll("`", "'");
144565
+ }
144566
+ function renderGitGraph(topology, graph) {
144567
+ const lines = [];
144568
+ if (graph.theme !== undefined && graph.theme.length > 0) {
144569
+ lines.push(`%%{init: { 'theme': '${sanitizeId(graph.theme)}' } }%%`);
144570
+ }
144571
+ lines.push("gitGraph");
144572
+ const baseId = sanitizeId(topology.base);
144573
+ const usedRefs = new Set;
144574
+ const base = refToken(topology.base, usedRefs);
144575
+ const baseRow = baseRowOf(topology);
144576
+ if (baseId !== MERMAID_DEFAULT_BRANCH)
144577
+ lines.push(`${INDENT}branch ${base}`);
144578
+ const tagsBySha = collectTags(topology.branches);
144579
+ const usedIds = new Set;
144580
+ const usedTags = new Set;
144581
+ const window2 = (baseRow?.commits ?? []).slice(0, Math.max(graph.maxCommits, 0));
144582
+ for (const { sha } of deepForkPoints(topology, baseRow, window2.length, graph.hideContained)) {
144583
+ lines.push(`${INDENT}${commitStatement({ sha, subject: "", tags: tagsBySha.get(sha) ?? [] }, usedIds, usedTags, undefined)}`);
144584
+ }
144585
+ for (const [index, commit] of window2.slice().reverse().entries()) {
144586
+ const isTip = index === window2.length - 1;
144587
+ lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
144588
+ }
144589
+ for (const row of topology.branches) {
144590
+ if (row === baseRow)
144591
+ continue;
144592
+ if (row.contained && graph.hideContained)
144593
+ continue;
144594
+ lines.push(`${INDENT}checkout ${base}`);
144595
+ const branch = refToken(row.name, usedRefs);
144596
+ lines.push(branch === MERMAID_DEFAULT_BRANCH ? `${INDENT}checkout ${branch}` : `${INDENT}branch ${branch}`);
144597
+ if (row.contained) {
144598
+ lines.push(`${INDENT}${commitStatement({ sha: row.tip, subject: "", tags: tagsBySha.get(row.tip) ?? [] }, usedIds, usedTags, undefined)}`);
144599
+ lines.push(`${INDENT}checkout ${base}`);
144600
+ lines.push(`${INDENT}merge ${branch}`);
144601
+ continue;
144602
+ }
144603
+ const shown = row.commits.slice(0, Math.max(Math.min(graph.maxCommits, row.ahead), 0)).reverse();
144604
+ const hidden = Math.max(row.ahead - shown.length, 0);
144605
+ if (hidden > 0) {
144606
+ lines.push(`${INDENT}commit id:"${uniqueId(`+${hidden} earlier`, usedIds)}"`);
144607
+ }
144608
+ for (const [index, commit] of shown.entries()) {
144609
+ const isTip = index === shown.length - 1;
144610
+ lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
144611
+ }
144612
+ }
144613
+ return lines.join(`
144614
+ `);
144615
+ }
144616
+ function commitStatement(commit, usedIds, usedTags, type) {
144617
+ const sha = short(commit.sha);
144618
+ const id = uniqueId(sanitizeId(sha), usedIds);
144619
+ const tag = claimTag(commit.tags[0], usedTags);
144620
+ let line = `commit id:"${id}"`;
144621
+ if (tag !== undefined)
144622
+ line += ` tag:"${tag}"`;
144623
+ if (type !== undefined)
144624
+ line += ` type: ${type}`;
144625
+ return line;
144626
+ }
144627
+ function claimTag(name, usedTags) {
144628
+ if (name === undefined)
144629
+ return;
144630
+ const sanitized = sanitizeId(name);
144631
+ if (usedTags.has(sanitized))
144632
+ return;
144633
+ usedTags.add(sanitized);
144634
+ return sanitized;
144635
+ }
144636
+ function deepForkPoints(topology, baseRow, windowLength, hideContained) {
144637
+ const behindBySha = new Map;
144638
+ for (const row of topology.branches) {
144639
+ if (row === baseRow)
144640
+ continue;
144641
+ if (row.contained && hideContained)
144642
+ continue;
144643
+ if (row.forkPoint.length === 0 || row.forkPoint === row.tip)
144644
+ continue;
144645
+ if (row.behind < windowLength)
144646
+ continue;
144647
+ if (!behindBySha.has(row.forkPoint))
144648
+ behindBySha.set(row.forkPoint, row.behind);
144649
+ }
144650
+ return [...behindBySha].map(([sha, behind]) => ({ sha, behind })).sort((a, b) => b.behind - a.behind || (a.sha < b.sha ? -1 : 1));
144651
+ }
144652
+ function commitType(commit) {
144653
+ return commit.subject.startsWith('Revert "') ? "REVERSE" : undefined;
144654
+ }
144655
+ function collectTags(branches) {
144656
+ const tags = new Map;
144657
+ for (const row of branches) {
144658
+ for (const commit of row.commits)
144659
+ tags.set(commit.sha, commit.tags);
144660
+ }
144661
+ return tags;
144662
+ }
144663
+ function isBaseRow(base, row) {
144664
+ if (row.name === base)
144665
+ return true;
144666
+ if (row.kind !== "local")
144667
+ return false;
144668
+ return row.name === base.replace(/^[^/]+\//, "");
144669
+ }
144670
+ function short(sha) {
144671
+ return sha.slice(0, 7);
144672
+ }
144673
+ function refToken(value, usedRefs) {
144674
+ const id = uniqueId(sanitizeRef(value), usedRefs);
144675
+ return /^[A-Za-z_]/.test(id) ? id : `"${id}"`;
144676
+ }
144677
+ function sanitizeId(value) {
144678
+ return sanitizeRef(value).slice(0, 40);
144679
+ }
144680
+ function sanitizeRef(value) {
144681
+ return value.replace(/[^A-Za-z0-9._/-]/g, "-");
144682
+ }
144683
+ function baseRowOf(topology) {
144684
+ return topology.branches.find((row) => row.name === topology.base) ?? topology.branches.find((row) => isBaseRow(topology.base, row)) ?? null;
144685
+ }
144686
+ function uniqueId(id, usedIds) {
144687
+ let candidate = id;
144688
+ let suffix = 2;
144689
+ while (usedIds.has(candidate)) {
144690
+ candidate = `${id}-${suffix}`;
144691
+ suffix += 1;
144692
+ }
144693
+ usedIds.add(candidate);
144694
+ return candidate;
144695
+ }
144696
+
144697
+ // ../../packages/app/src/services/branch-topology/index.ts
144698
+ var GRAPH_COMMIT_WINDOW = 10;
144699
+ var FIELD_SEP = "\x00";
144700
+
144701
+ class BranchTopologyUsageError extends Error {
144702
+ }
144703
+
144704
+ class BranchTopologyGitError extends Error {
144705
+ }
144706
+ function collectBranchTopology(runner, opts) {
144707
+ const warnings = [];
144708
+ const base = resolveBase(runner, opts.base);
144709
+ const refs = listRefs(runner, opts.scope);
144710
+ const worktrees = readWorktrees(runner, warnings);
144711
+ const tagIndex = readTagIndex(runner, warnings);
144712
+ const rows = refs.map((ref) => buildRow(runner, base.sha, ref));
144713
+ const detached = detachedHead(runner, base.sha);
144714
+ if (detached !== null && opts.scope !== "remote")
144715
+ rows.push(detached);
144716
+ for (const row of rows) {
144717
+ row.worktree = row.kind === "detached" ? worktrees.bySha.get(row.tip) ?? null : worktrees.byBranch.get(row.name) ?? null;
144718
+ }
144719
+ if (opts.pr)
144720
+ attachPullRequests(rows, readOpenPullRequests(runner, warnings));
144721
+ const sorted = sortRows(rows);
144722
+ const branches = sorted.slice(0, Math.max(opts.limit, 0));
144723
+ const omitted = sorted.length - branches.length;
144724
+ for (const row of branches) {
144725
+ if (row.contained && !isBaseRow(base.name, row))
144726
+ continue;
144727
+ row.commits = readCommits(runner, row.tip, tagIndex, warnings);
144728
+ }
144729
+ return { base: base.name, generatedAt: new Date().toISOString(), branches, omitted, warnings };
144730
+ }
144731
+ function resolveBase(runner, explicit) {
144732
+ const trimmed = explicit?.trim();
144733
+ const requested = trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined;
144734
+ let candidate = requested ?? "";
144735
+ if (candidate.length === 0) {
144736
+ const originHead = runner(["git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
144737
+ const target = originHead.code === 0 ? originHead.stdout.trim() : "";
144738
+ candidate = target.length > 0 ? target : "main";
144739
+ }
144740
+ const resolved = runner(["git", "rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]);
144741
+ if (resolved.code !== 0) {
144742
+ if ((resolved.error ?? "").length > 0) {
144743
+ throw new BranchTopologyGitError(`git is not available: ${resolved.error}`);
144744
+ }
144745
+ if (/not a git repository/i.test(resolved.stderr)) {
144746
+ throw new BranchTopologyGitError(`not a git repository: ${failureDetail(resolved)}`);
144747
+ }
144748
+ throw new BranchTopologyUsageError(requested === undefined ? `unknown base ref '${candidate}' (no --base given; tried origin/HEAD, then main)` : `unknown base ref '${candidate}'`);
144749
+ }
144750
+ return { name: candidate, sha: resolved.stdout.trim() };
144751
+ }
144752
+ function listRefs(runner, scope) {
144753
+ const namespaces = scope === "local" ? ["refs/heads"] : scope === "remote" ? ["refs/remotes"] : ["refs/heads", "refs/remotes"];
144754
+ const res = runner(["git", "for-each-ref", "--format=%(refname)%00%(objectname)", ...namespaces]);
144755
+ if (res.code !== 0)
144756
+ throw gitFailure(res, "git for-each-ref");
144757
+ const refs = [];
144758
+ for (const line of res.stdout.split(`
144759
+ `)) {
144760
+ if (line.length === 0)
144761
+ continue;
144762
+ const [refname, sha] = line.split(FIELD_SEP);
144763
+ if (refname === undefined || sha === undefined || sha.length === 0)
144764
+ continue;
144765
+ if (refname.endsWith("/HEAD"))
144766
+ continue;
144767
+ if (refname.startsWith("refs/heads/")) {
144768
+ refs.push({ name: refname.slice("refs/heads/".length), kind: "local", sha });
144769
+ } else if (refname.startsWith("refs/remotes/")) {
144770
+ refs.push({ name: refname.slice("refs/remotes/".length), kind: "remote", sha });
144771
+ }
144772
+ }
144773
+ if (scope !== "all")
144774
+ return refs;
144775
+ const localTips = new Map(refs.filter((ref) => ref.kind === "local").map((ref) => [ref.name, ref.sha]));
144776
+ return refs.filter((ref) => {
144777
+ if (ref.kind !== "remote")
144778
+ return true;
144779
+ const twin = ref.name.slice(ref.name.indexOf("/") + 1);
144780
+ return localTips.get(twin) !== ref.sha;
144781
+ });
144782
+ }
144783
+ function buildRow(runner, baseSha, ref) {
144784
+ const mergeBase = runner(["git", "merge-base", baseSha, ref.sha]);
144785
+ const forkPoint = mergeBase.code === 0 ? mergeBase.stdout.trim() : "";
144786
+ const counts = runner(["git", "rev-list", "--left-right", "--count", `${baseSha}...${ref.sha}`]);
144787
+ if (counts.code !== 0)
144788
+ throw gitFailure(counts, `git rev-list for ${ref.name}`);
144789
+ const [behindRaw, aheadRaw] = counts.stdout.trim().split(/\s+/);
144790
+ const behind = parseCount2(behindRaw, ref.name);
144791
+ const ahead = parseCount2(aheadRaw, ref.name);
144792
+ const log = runner(["git", "log", "-1", "--format=%H%x00%cI%x00%s", ref.sha]);
144793
+ if (log.code !== 0)
144794
+ throw gitFailure(log, `git log for ${ref.name}`);
144795
+ const [tip, lastCommitAt, ...subjectParts] = log.stdout.replace(/\n$/, "").split(FIELD_SEP);
144796
+ return {
144797
+ name: ref.name,
144798
+ kind: ref.kind,
144799
+ tip: tip ?? ref.sha,
144800
+ forkPoint,
144801
+ ahead,
144802
+ behind,
144803
+ contained: ahead === 0,
144804
+ lastCommitAt: lastCommitAt ?? "",
144805
+ subject: subjectParts.join(FIELD_SEP),
144806
+ worktree: null,
144807
+ pr: null,
144808
+ commits: []
144809
+ };
144810
+ }
144811
+ function parseCount2(raw, ref) {
144812
+ const parsed = Number.parseInt(raw ?? "", 10);
144813
+ if (!Number.isInteger(parsed)) {
144814
+ throw new BranchTopologyGitError(`git rev-list for ${ref} returned '${raw ?? ""}'`);
144815
+ }
144816
+ return parsed;
144817
+ }
144818
+ function detachedHead(runner, baseSha) {
144819
+ const symbolic = runner(["git", "symbolic-ref", "--short", "-q", "HEAD"]);
144820
+ if (symbolic.code === 0)
144821
+ return null;
144822
+ const head = runner(["git", "rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
144823
+ if (head.code !== 0)
144824
+ return null;
144825
+ const tip = head.stdout.trim();
144826
+ return buildRow(runner, baseSha, { name: shortSha(tip), kind: "detached", sha: tip });
144827
+ }
144828
+ function sortRows(rows) {
144829
+ const time = (row) => {
144830
+ const parsed = Date.parse(row.lastCommitAt);
144831
+ return Number.isFinite(parsed) ? parsed : 0;
144832
+ };
144833
+ return [...rows].sort((a, b) => {
144834
+ const byTime = time(b) - time(a);
144835
+ if (byTime !== 0)
144836
+ return byTime;
144837
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
144838
+ });
144839
+ }
144840
+ function readWorktrees(runner, warnings) {
144841
+ const index = { byBranch: new Map, bySha: new Map };
144842
+ const res = runner(["git", "worktree", "list", "--porcelain"]);
144843
+ if (res.code !== 0) {
144844
+ warnings.push(`git worktree list failed: ${failureDetail(res)}`);
144845
+ return index;
144846
+ }
144847
+ let path = "";
144848
+ let head = "";
144849
+ let detached = false;
144850
+ const flush = () => {
144851
+ if (detached && head.length > 0 && path.length > 0)
144852
+ index.bySha.set(head, path);
144853
+ path = "";
144854
+ head = "";
144855
+ detached = false;
144856
+ };
144857
+ for (const line of res.stdout.split(`
144858
+ `)) {
144859
+ if (line.length === 0) {
144860
+ flush();
144861
+ } else if (line.startsWith("worktree ")) {
144862
+ flush();
144863
+ path = line.slice("worktree ".length).trim();
144864
+ } else if (line.startsWith("HEAD ")) {
144865
+ head = line.slice("HEAD ".length).trim();
144866
+ } else if (line === "detached") {
144867
+ detached = true;
144868
+ } else if (line.startsWith("branch refs/heads/")) {
144869
+ index.byBranch.set(line.slice("branch refs/heads/".length).trim(), path);
144870
+ }
144871
+ }
144872
+ flush();
144873
+ return index;
144874
+ }
144875
+ function readTagIndex(runner, warnings) {
144876
+ const index = new Map;
144877
+ const res = runner(["git", "for-each-ref", "--format=%(objectname) %(*objectname) %(refname:short)", "refs/tags"]);
144878
+ if (res.code !== 0) {
144879
+ warnings.push(`git for-each-ref refs/tags failed: ${failureDetail(res)}`);
144880
+ return index;
144881
+ }
144882
+ for (const line of res.stdout.split(`
144883
+ `)) {
144884
+ if (line.length === 0)
144885
+ continue;
144886
+ const [object, peeled, ...nameParts] = line.split(" ");
144887
+ const name = nameParts.join(" ");
144888
+ if (object === undefined || name.length === 0)
144889
+ continue;
144890
+ const target = (peeled ?? "").length > 0 ? peeled : object;
144891
+ index.set(target, [...index.get(target) ?? [], name]);
144892
+ }
144893
+ return index;
144894
+ }
144895
+ function readCommits(runner, sha, tagIndex, warnings) {
144896
+ const res = runner(["git", "log", "-n", String(GRAPH_COMMIT_WINDOW), "--format=%H%x00%s", sha]);
144897
+ if (res.code !== 0) {
144898
+ warnings.push(`git log for ${shortSha(sha)} failed: ${failureDetail(res)}`);
144899
+ return [];
144900
+ }
144901
+ const commits = [];
144902
+ for (const line of res.stdout.split(`
144903
+ `)) {
144904
+ if (line.length === 0)
144905
+ continue;
144906
+ const [commitSha, ...subjectParts] = line.split(FIELD_SEP);
144907
+ if (commitSha === undefined || commitSha.length === 0)
144908
+ continue;
144909
+ commits.push({
144910
+ sha: commitSha,
144911
+ subject: subjectParts.join(FIELD_SEP),
144912
+ tags: tagIndex.get(commitSha) ?? []
144913
+ });
144914
+ }
144915
+ return commits;
144916
+ }
144917
+ function readOpenPullRequests(runner, warnings) {
144918
+ const index = new Map;
144919
+ const res = runner([
144920
+ "gh",
144921
+ "pr",
144922
+ "list",
144923
+ "--state",
144924
+ "open",
144925
+ "--json",
144926
+ "number,headRefName,state,reviewDecision",
144927
+ "--limit",
144928
+ "200"
144929
+ ]);
144930
+ if (res.code !== 0 || res.stdout.trim().length === 0) {
144931
+ warnings.push(`--pr: gh pr list failed: ${failureDetail(res)}`);
144932
+ return index;
144933
+ }
144934
+ let parsed;
144935
+ try {
144936
+ parsed = JSON.parse(res.stdout);
144937
+ } catch {
144938
+ warnings.push("--pr: gh pr list returned unparseable JSON");
144939
+ return index;
144940
+ }
144941
+ if (!Array.isArray(parsed)) {
144942
+ warnings.push("--pr: gh pr list returned unparseable JSON");
144943
+ return index;
144944
+ }
144945
+ for (const entry of parsed) {
144946
+ const { number, headRefName, state, reviewDecision } = entry;
144947
+ if (typeof number !== "number" || typeof headRefName !== "string")
144948
+ continue;
144949
+ const pr = {
144950
+ number,
144951
+ state: typeof state === "string" ? state : "",
144952
+ reviewDecision: typeof reviewDecision === "string" && reviewDecision.length > 0 ? reviewDecision : null
144953
+ };
144954
+ const existing = index.get(headRefName);
144955
+ if (existing === undefined || pr.number < existing.number)
144956
+ index.set(headRefName, pr);
144957
+ }
144958
+ return index;
144959
+ }
144960
+ function attachPullRequests(rows, index) {
144961
+ for (const row of rows) {
144962
+ if (row.kind === "detached")
144963
+ continue;
144964
+ const keys = row.kind === "remote" ? [row.name, row.name.replace(/^[^/]+\//, "")] : [row.name];
144965
+ row.pr = keys.map((key) => index.get(key)).find((pr) => pr !== undefined) ?? null;
144966
+ }
144967
+ }
144968
+ function shortSha(sha) {
144969
+ return sha.slice(0, 7);
144970
+ }
144971
+ function gitFailure(res, what) {
144972
+ return new BranchTopologyGitError(`${what} failed: ${failureDetail(res)}`);
144973
+ }
144974
+ function failureDetail(res) {
144975
+ const first = (res.stderr.trim() || res.stdout.trim()).split(`
144976
+ `)[0] ?? "";
144977
+ if (first.length > 0)
144978
+ return first;
144979
+ return (res.error ?? "").length > 0 ? res.error : `exit ${res.code}`;
144980
+ }
144981
+
144982
+ // src/commands/builder/show-branches.ts
144983
+ var FORMATS = ["markdown", "json", "text"];
144984
+ function registerBuilderShowBranchesCommand(builder, context) {
144985
+ const command = builder.command("show-branches").summary("read-only branch topology report against a base (markdown | json | text)").description([
144986
+ "Report every branch against a base ref, read-only: fork point, commits ahead/behind,",
144987
+ "contained flag, last commit, worktree binding, and (with --pr) open pull-request state.",
144988
+ "Base resolution: --base, else the origin/HEAD target, else main.",
144989
+ "markdown (default) prints the summary table and one condensed mermaid gitGraph;",
144990
+ "json prints the raw BranchTopology object; text prints the aligned table only.",
144991
+ "Exit codes: 0 ok \xB7 1 usage (bad --format/--limit/--max-commits, unknown base ref,",
144992
+ "--local with --remote) \xB7 2 git failure (not a repository, git missing)."
144993
+ ].join(`
144994
+ `)).option("--format <format>", "output format: markdown | json | text (default markdown)", "markdown").option("--json", "alias of --format json (prints the raw BranchTopology object)").option("--base <ref>", "base ref to measure every branch against (default: origin/HEAD, else main)").option("--local", "only local branches").option("--remote", "only remote branches").option("--pr", "enrich rows with open pull-request state (one gh call; degrades with a warning)").option("--limit <n>", "maximum branch rows kept, most recent last-commit first (default 30)", "30").option("--max-commits <n>", "maximum commit nodes per branch line in the graph (default 3)", "3").option("--hide-contained", "drop contained branches from the graph instead of drawing their join").option("--theme <name>", "mermaid theme name for the graph init directive").action((options) => {
144995
+ const format = resolveFormat(options.format, command.getOptionValueSource("format") === "cli", options.json === true, context);
144996
+ if (format === null)
144997
+ return;
144998
+ const limit = positiveInteger(options.limit, "--limit", context);
144999
+ if (limit === null)
145000
+ return;
145001
+ const maxCommits = positiveInteger(options.maxCommits, "--max-commits", context);
145002
+ if (maxCommits === null)
145003
+ return;
145004
+ if (options.local === true && options.remote === true) {
145005
+ usageError("--local and --remote are mutually exclusive", context);
145006
+ return;
145007
+ }
145008
+ try {
145009
+ const topology = collectBranchTopology(createSpawnRunner({ cwd: context.cwd, env: context.env }), {
145010
+ ...options.base !== undefined ? { base: options.base } : {},
145011
+ scope: options.local === true ? "local" : options.remote === true ? "remote" : "all",
145012
+ pr: options.pr === true,
145013
+ limit
145014
+ });
145015
+ if (format === "json") {
145016
+ context.output.write(toJson2(topology));
145017
+ } else if (format === "text") {
145018
+ context.output.write(renderText(topology));
145019
+ } else {
145020
+ context.output.write(renderMarkdown2(topology, {
145021
+ maxCommits,
145022
+ hideContained: options.hideContained === true,
145023
+ ...options.theme !== undefined ? { theme: options.theme } : {}
145024
+ }));
145025
+ }
145026
+ context.setExitCode(0);
145027
+ } catch (err) {
145028
+ if (err instanceof BranchTopologyUsageError) {
145029
+ usageError(err.message, context);
145030
+ return;
145031
+ }
145032
+ if (err instanceof BranchTopologyGitError) {
145033
+ context.output.error(err.message);
145034
+ context.setExitCode(2);
145035
+ return;
145036
+ }
145037
+ context.output.error(err instanceof Error ? err.message : String(err));
145038
+ context.setExitCode(2);
145039
+ }
145040
+ });
145041
+ }
145042
+ function resolveFormat(requested, explicitFormat, json, context) {
145043
+ if (!FORMATS.includes(requested)) {
145044
+ usageError(`unknown --format '${requested}': expected ${FORMATS.join(" | ")}`, context);
145045
+ return null;
145046
+ }
145047
+ if (!json)
145048
+ return requested;
145049
+ if (explicitFormat && requested !== "json") {
145050
+ usageError(`--json conflicts with --format ${requested}`, context);
145051
+ return null;
145052
+ }
145053
+ return "json";
145054
+ }
145055
+ function positiveInteger(value, flag, context) {
145056
+ const trimmed = value.trim();
145057
+ const parsed = Number.parseInt(trimmed, 10);
145058
+ if (!/^\d+$/.test(trimmed) || parsed < 1) {
145059
+ usageError(`${flag} expects a positive integer, got '${value}'`, context);
145060
+ return null;
145061
+ }
145062
+ return parsed;
145063
+ }
145064
+ function usageError(message, context) {
145065
+ context.output.error(message);
145066
+ context.setExitCode(1);
145067
+ }
145068
+
144181
145069
  // src/commands/builder.ts
144182
145070
  function registerBuilderCommand(program, context) {
144183
- const noun = program.command("builder").summary("release plumbing: version bumps, tags, and PR-review ops");
145071
+ const noun = program.command("builder").summary("release plumbing: version bumps, tags, branch topology, and PR-review ops");
144184
145072
  noun.command("bump-ver").summary("bump a package version, commit, tag, optionally push").description([
144185
145073
  "Bump one workspace package (or every released package with --all),",
144186
145074
  "rewrite workspace pins, commit, and tag. A bare version bumps all:",
@@ -144282,7 +145170,9 @@ function registerBuilderCommand(program, context) {
144282
145170
  context.setExitCode(1);
144283
145171
  }
144284
145172
  });
144285
- registerBuilderPrCommand(noun, context);
145173
+ registerBuilderShowBranchesCommand(noun, context);
145174
+ registerBuilderPrCommands(noun, context);
145175
+ registerBuilderPrAliasCommand(noun, context);
144286
145176
  }
144287
145177
 
144288
145178
  // src/commands/decision.ts
@@ -145127,7 +146017,7 @@ import { createRequire as createRequire3 } from "module";
145127
146017
  var CLI_CONFIG = {
145128
146018
  binaryName: "spur",
145129
146019
  binaryLabel: "spur",
145130
- binaryVersion: "0.4.10",
146020
+ binaryVersion: "0.4.11",
145131
146021
  configDir: ".spur",
145132
146022
  configFile: ".spur/config.yaml",
145133
146023
  databaseFile: ".spur/spur.db"
@@ -147264,7 +148154,7 @@ ${l}
147264
148154
  }).prompt();
147265
148155
  };
147266
148156
  var i2 = `${styleText2("gray", S_BAR)} `;
147267
- var text4 = (t) => new n3({
148157
+ var text5 = (t) => new n3({
147268
148158
  validate: t.validate,
147269
148159
  placeholder: t.placeholder,
147270
148160
  defaultValue: t.defaultValue,
@@ -147334,7 +148224,7 @@ class ClackHitlResponder {
147334
148224
  return { value: result };
147335
148225
  }
147336
148226
  case "input": {
147337
- const result = await text4({
148227
+ const result = await text5({
147338
148228
  message: request.prompt
147339
148229
  });
147340
148230
  if (isCancel(result)) {
@@ -154915,7 +155805,7 @@ init_dist5();
154915
155805
  import { homedir as homedir16 } from "os";
154916
155806
  import { basename as basename20, join as join90, relative as relative19, resolve as resolve35 } from "path";
154917
155807
  // ../server/package.json
154918
- var version3 = "0.4.10";
155808
+ var version3 = "0.4.11";
154919
155809
 
154920
155810
  // ../server/src/modules/health/index.ts
154921
155811
  var startedAt = Date.now();
@@ -162295,8 +163185,14 @@ function shouldRenderBanner(argv) {
162295
163185
  return false;
162296
163186
  if (argv[0] === "history" && argv[1] === "timeline")
162297
163187
  return false;
163188
+ if (argv[0] === "builder" && argv[1] === "show-branches" && requestsJsonFormat(argv))
163189
+ return false;
162298
163190
  return !argv.some((arg) => arg === "--no-logo" || arg === "--json" || arg === "--quiet" || arg === "--silent");
162299
163191
  }
163192
+ function requestsJsonFormat(argv) {
163193
+ const index = argv.indexOf("--format");
163194
+ return argv.includes("--format=json") || index !== -1 && argv[index + 1] === "json";
163195
+ }
162300
163196
  async function runCli() {
162301
163197
  const argv = process.argv.slice(2);
162302
163198
  if (shouldRenderBanner(argv)) {