@mutmutco/cli 3.105.0 → 3.105.2

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.
Files changed (2) hide show
  1. package/dist/main.cjs +155 -1
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -20462,7 +20462,8 @@ async function runHotfixStart(deps, options) {
20462
20462
  }
20463
20463
  notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
20464
20464
  if (deployModel === "hub-serverless") {
20465
- await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
20465
+ const durableSource = clean3(await deps.run("git", ["merge-base", "HEAD", "origin/main"]));
20466
+ await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", durableSource]);
20466
20467
  const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
20467
20468
  await deps.run("git", ["add", "-f", "--", ...changedFiles]);
20468
20469
  const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
@@ -25439,6 +25440,148 @@ async function runIssueChildren(deps, epic, opts) {
25439
25440
  }
25440
25441
  return out;
25441
25442
  }
25443
+ var FRONTIER_BUCKET_ORDER = {
25444
+ in_flight: 0,
25445
+ partial: 1,
25446
+ ready: 2,
25447
+ landed: 3
25448
+ };
25449
+ function prStateIs(pr2, want) {
25450
+ return String(pr2.state ?? "").toUpperCase() === want;
25451
+ }
25452
+ function isBoardDone(boardStatus) {
25453
+ return /^done$/i.test(String(boardStatus ?? "").trim());
25454
+ }
25455
+ function isBoardInFlight(boardStatus) {
25456
+ return /^(in\s*progress|in\s*review|review|doing)$/i.test(String(boardStatus ?? "").trim());
25457
+ }
25458
+ function classifyFrontierChild(child2) {
25459
+ const mergedPrs = child2.linkedPrs.filter((p) => prStateIs(p, "MERGED"));
25460
+ const openPrs = child2.linkedPrs.filter((p) => prStateIs(p, "OPEN"));
25461
+ const otherPrs = child2.linkedPrs.filter((p) => !prStateIs(p, "MERGED") && !prStateIs(p, "OPEN"));
25462
+ const closed = String(child2.state ?? "").toUpperCase() === "CLOSED";
25463
+ let bucket;
25464
+ if (closed || isBoardDone(child2.boardStatus)) {
25465
+ bucket = "landed";
25466
+ } else if (openPrs.length > 0 || isBoardInFlight(child2.boardStatus)) {
25467
+ bucket = "in_flight";
25468
+ } else if (mergedPrs.length > 0) {
25469
+ bucket = "partial";
25470
+ } else {
25471
+ bucket = "ready";
25472
+ }
25473
+ return {
25474
+ number: child2.number,
25475
+ title: child2.title,
25476
+ state: child2.state,
25477
+ url: child2.url,
25478
+ repo: child2.repo,
25479
+ assignee: child2.assignee,
25480
+ boardStatus: child2.boardStatus,
25481
+ depth: child2.depth,
25482
+ bucket,
25483
+ mergedPrs,
25484
+ openPrs,
25485
+ otherPrs
25486
+ };
25487
+ }
25488
+ function sortFrontierChildren(rows) {
25489
+ return [...rows].sort((a, b) => {
25490
+ const byBucket = FRONTIER_BUCKET_ORDER[a.bucket] - FRONTIER_BUCKET_ORDER[b.bucket];
25491
+ if (byBucket !== 0) return byBucket;
25492
+ return a.number - b.number;
25493
+ });
25494
+ }
25495
+ function buildFrontierReport(epic, children) {
25496
+ const classified = children.map(classifyFrontierChild);
25497
+ const landed = classified.filter((c) => c.bucket === "landed").sort((a, b) => a.number - b.number);
25498
+ const frontier = sortFrontierChildren(classified.filter((c) => c.bucket !== "landed"));
25499
+ const inFlight = frontier.filter((c) => c.bucket === "in_flight").length;
25500
+ const partial = frontier.filter((c) => c.bucket === "partial").length;
25501
+ const ready = frontier.filter((c) => c.bucket === "ready").length;
25502
+ const report = {
25503
+ epic,
25504
+ counts: {
25505
+ total: classified.length,
25506
+ landed: landed.length,
25507
+ frontier: frontier.length,
25508
+ inFlight,
25509
+ partial,
25510
+ ready
25511
+ },
25512
+ landed,
25513
+ frontier,
25514
+ children: classified,
25515
+ summary: ""
25516
+ };
25517
+ report.summary = formatFrontierSummary(report);
25518
+ return report;
25519
+ }
25520
+ function fmtChildLine(c) {
25521
+ const board = c.boardStatus ? ` board=${c.boardStatus}` : "";
25522
+ const merged = c.mergedPrs.length ? ` merged=[${c.mergedPrs.map((p) => `#${p.number}`).join(",")}]` : "";
25523
+ const open2 = c.openPrs.length ? ` open=[${c.openPrs.map((p) => `#${p.number}`).join(",")}]` : "";
25524
+ return ` #${c.number} [${c.bucket}] ${c.title} (${c.state}${board}${merged}${open2})`;
25525
+ }
25526
+ function formatFrontierSummary(report) {
25527
+ const { epic, counts } = report;
25528
+ const lines = [
25529
+ `issue frontier \u2014 ${epic.repo}#${epic.number} ${epic.title} (${epic.state})`,
25530
+ ` ${counts.total} children \xB7 ${counts.landed} landed \xB7 ${counts.frontier} frontier (${counts.inFlight} in-flight, ${counts.partial} partial, ${counts.ready} ready)`
25531
+ ];
25532
+ if (report.frontier.length) {
25533
+ lines.push("frontier:");
25534
+ for (const c of report.frontier) lines.push(fmtChildLine(c));
25535
+ } else {
25536
+ lines.push("frontier: (empty \u2014 every walked child is landed)");
25537
+ }
25538
+ if (report.landed.length) {
25539
+ lines.push("landed:");
25540
+ for (const c of report.landed) lines.push(fmtChildLine(c));
25541
+ }
25542
+ return lines.join("\n");
25543
+ }
25544
+ function frontierEpicGraphqlArgs(owner, name, number) {
25545
+ const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number title state url}}}";
25546
+ return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
25547
+ }
25548
+ function extractFrontierEpicResponse(resp, repo) {
25549
+ const r = resp ?? {};
25550
+ const issue2 = r.data?.repository?.issue;
25551
+ if (!issue2 || (r.errors ?? []).some((e) => e?.type === "NOT_FOUND")) {
25552
+ throw new QueryReadError("NOT_FOUND", "issue not found");
25553
+ }
25554
+ return {
25555
+ number: Number(issue2.number),
25556
+ title: String(issue2.title ?? ""),
25557
+ state: String(issue2.state ?? ""),
25558
+ url: String(issue2.url ?? ""),
25559
+ repo
25560
+ };
25561
+ }
25562
+ async function runIssueFrontier(deps, epic, opts) {
25563
+ const ref = parseIssueRef(epic);
25564
+ const repo = ref.repo ?? await deps.resolveRepo(void 0);
25565
+ if (!repo) {
25566
+ throw new QueryReadError("BAD_INPUT", `could not resolve repo for #${ref.number} (pass --repo <owner/repo>)`);
25567
+ }
25568
+ const { owner, name } = splitRepo(repo);
25569
+ let headerResp;
25570
+ try {
25571
+ headerResp = await deps.ghJson(frontierEpicGraphqlArgs(owner, name, ref.number), GH_LIST_TIMEOUT_MS);
25572
+ } catch (e) {
25573
+ if (isGhNotFound(e)) {
25574
+ throw new QueryReadError("NOT_FOUND", `#${ref.number} not found in ${repo}`);
25575
+ }
25576
+ throw e;
25577
+ }
25578
+ const epicHeader = extractFrontierEpicResponse(headerResp, repo);
25579
+ const children = await runIssueChildren(deps, `${repo}#${ref.number}`, {
25580
+ recursive: opts.recursive,
25581
+ boardProjectId: opts.boardProjectId
25582
+ });
25583
+ return buildFrontierReport(epicHeader, children);
25584
+ }
25442
25585
  function buildPrListArgs(opts, repo, author) {
25443
25586
  const limit = clampLimit(opts.limit, DEFAULT_LIMIT);
25444
25587
  const state = opts.state ?? "open";
@@ -25615,6 +25758,17 @@ function registerQueryCommands(program3) {
25615
25758
  queryFail("issue children", e);
25616
25759
  }
25617
25760
  });
25761
+ issue2.command("frontier <epic>").description("compute an epic's landed-vs-open frontier from merged PRs + board Status (read-only; never mutates the epic body) \u2014 human summary by default, --json for the structured report").option("--recursive", "walk the full sub-issue tree (bounded depth + total cap)").option("--repo <owner/repo>", "repo for a bare epic ref (defaults to the current repo)").option("--json", "print the structured FrontierReport JSON (includes summary)").action(async (epic, o) => {
25762
+ try {
25763
+ const frontierDeps = { ...deps, resolveRepo: async (r) => deps.resolveRepo(r ?? o.repo) };
25764
+ const boardProjectId = await resolveBoardProjectId(o.repo);
25765
+ const report = await runIssueFrontier(frontierDeps, epic, { recursive: o.recursive, boardProjectId });
25766
+ if (o.json) console.log(JSON.stringify(report, null, 2));
25767
+ else console.log(report.summary);
25768
+ } catch (e) {
25769
+ queryFail("issue frontier", e);
25770
+ }
25771
+ });
25618
25772
  pr2.command("list").description("bounded PR read \u2014 --mine scopes to the viewer's own PRs (resolves the viewer via gh); always prints JSON").option("--mine", "only the caller's own PRs (resolves the viewer via `gh api user`)").addOption(new Option("--state <state>", "open | all").default("open").choices(PR_STATES)).option("--limit <n>", "max PRs to return (capped at 100)", (v) => Number(v), DEFAULT_LIMIT).option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (o) => {
25619
25773
  try {
25620
25774
  const state = validateEnum("--state", PR_STATES, o.state, "pr list");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.105.0",
3
+ "version": "3.105.2",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",