@mutmutco/cli 4.4.6 → 4.4.8

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 +116 -30
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -1112,6 +1112,7 @@ var init_cli_shared = __esm({
1112
1112
  // src/board-write.ts
1113
1113
  var board_write_exports = {};
1114
1114
  __export(board_write_exports, {
1115
+ addIssueToProjectViaHub: () => addIssueToProjectViaHub,
1115
1116
  writeHubBoardField: () => writeHubBoardField
1116
1117
  });
1117
1118
  async function writeHubBoardField(request) {
@@ -1125,6 +1126,18 @@ async function writeHubBoardField(request) {
1125
1126
  const body = await res.json().catch(() => null);
1126
1127
  if (!res.ok || body?.updated !== true) throw new Error(`board field write HTTP ${res.status}: ${body?.error ?? "unconfirmed result"}`);
1127
1128
  }
1129
+ async function addIssueToProjectViaHub(cfg, contentNodeId) {
1130
+ const config = await loadConfig();
1131
+ const res = await fetch(`${config.sagaApiUrl?.replace(/\/$/, "")}/board/attach`, {
1132
+ method: "POST",
1133
+ headers: await hubHeaders({ "content-type": "application/json" }),
1134
+ body: JSON.stringify({ projectId: cfg.projectId, contentNodeId }),
1135
+ signal: AbortSignal.timeout(25e3)
1136
+ });
1137
+ const body = await res.json().catch(() => null);
1138
+ if (!res.ok || !body?.itemId) throw new Error(`board attach HTTP ${res.status}: ${body?.error ?? "unconfirmed result"}`);
1139
+ return body.itemId;
1140
+ }
1128
1141
  var init_board_write = __esm({
1129
1142
  "src/board-write.ts"() {
1130
1143
  "use strict";
@@ -11148,11 +11161,24 @@ mutation($projectId: ID!, $itemId: ID!) {
11148
11161
  async function updateItemSingleSelect(client, projectId, itemId, fieldId, optionId) {
11149
11162
  try {
11150
11163
  await client.graphql(UPDATE_ITEM_FIELD_MUTATION, { projectId, itemId, fieldId, optionId });
11164
+ return { credential: "user" };
11151
11165
  } catch (error) {
11152
11166
  const errors = error.graphqlErrors;
11153
- if (!errors?.length || !errors.every((entry) => entry.type === "INSUFFICIENT_SCOPES")) throw error;
11154
- const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
11155
- await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
11167
+ if (errors?.length && errors.every((entry) => entry.type === "INSUFFICIENT_SCOPES")) {
11168
+ const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
11169
+ await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
11170
+ return { credential: "app_installation" };
11171
+ }
11172
+ if (isGitHubRateLimitError(error)) {
11173
+ const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
11174
+ try {
11175
+ await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
11176
+ return { credential: "app_installation" };
11177
+ } catch {
11178
+ throw error;
11179
+ }
11180
+ }
11181
+ throw error;
11156
11182
  }
11157
11183
  }
11158
11184
  function parseIssueSelector(selector, defaultRepo, expectedRepo) {
@@ -12395,8 +12421,9 @@ async function moveBoardItem(options, deps = {}) {
12395
12421
  throw boardNotFoundError(`${selector.repo}#${selector.number}`, { owner: cfg.projectOwner, number: cfg.projectNumber });
12396
12422
  }
12397
12423
  const optionId = cfg.statusOptions[options.status];
12424
+ let credential = "user";
12398
12425
  try {
12399
- await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId);
12426
+ ({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId));
12400
12427
  } catch (e) {
12401
12428
  if (options.status === "Done" && isArchivedItemRefusal(ghError(e))) {
12402
12429
  return {
@@ -12418,7 +12445,9 @@ async function moveBoardItem(options, deps = {}) {
12418
12445
  viewer: lookup.viewer,
12419
12446
  repo: currentRepo,
12420
12447
  status: options.status,
12421
- partial: false
12448
+ partial: false,
12449
+ credential
12450
+ // #6834: which credential served the write — the user token or the Hub App leg.
12422
12451
  };
12423
12452
  }
12424
12453
  async function resolveClaimWritable(collected, client, snapshot, unscanned) {
@@ -12584,8 +12613,9 @@ async function claimOneBoardItem(ctx, selector, options) {
12584
12613
  throw new Error(`claim failed before board status changed: ${ghError(e)}`);
12585
12614
  }
12586
12615
  await postClaimMarkerComment(client, item, ctx.session);
12616
+ let credential = "user";
12587
12617
  try {
12588
- await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]);
12618
+ ({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]));
12589
12619
  } catch (e) {
12590
12620
  const warning = `partial claim: ${item.ref} was assigned to @${assignedLogin}, but Status was not moved to In Progress (${ghError(e)})`;
12591
12621
  if (!options.allowPartial) throw new Error(warning);
@@ -12602,6 +12632,7 @@ async function claimOneBoardItem(ctx, selector, options) {
12602
12632
  repo: report.repo,
12603
12633
  status: "In Progress",
12604
12634
  partial: false,
12635
+ credential,
12605
12636
  ...claimedReceipt()
12606
12637
  };
12607
12638
  }
@@ -12635,7 +12666,7 @@ async function claimBoardIssues(options, deps = {}) {
12635
12666
  const ref = `${selector.repo}#${selector.number}`;
12636
12667
  try {
12637
12668
  const result = await claimOneBoardItem(ctx, selector, { ...options, bulk: true });
12638
- results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, reclaimedFrom: result.reclaimedFrom, alreadyClaimed: result.alreadyClaimed, renewed: result.renewed, checked: result.checked };
12669
+ results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, reclaimedFrom: result.reclaimedFrom, alreadyClaimed: result.alreadyClaimed, renewed: result.renewed, checked: result.checked, credential: result.credential };
12639
12670
  } catch (e) {
12640
12671
  results[index] = { ref, claimed: false, reason: e.message };
12641
12672
  }
@@ -12695,13 +12726,14 @@ async function moveBoardIssues(options, deps = {}) {
12695
12726
  const idx = next++;
12696
12727
  const { item, ref, index: resultIdx } = resolvedList[idx];
12697
12728
  try {
12698
- await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, statusOptionId);
12729
+ const { credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, statusOptionId);
12699
12730
  results[resultIdx] = {
12700
12731
  ref,
12701
12732
  moved: true,
12702
12733
  item: { ...item, status: options.status, statusOptionId },
12703
12734
  status: options.status,
12704
- partial: false
12735
+ partial: false,
12736
+ credential
12705
12737
  };
12706
12738
  } catch (e) {
12707
12739
  const warning = `partial move: ${ref} status was not changed to ${options.status} (${ghError(e)})`;
@@ -12768,8 +12800,9 @@ async function unclaimBoardIssue(options, deps = {}) {
12768
12800
  return { item, viewer, repo: currentRepo, status: item.status, partial: true, warning };
12769
12801
  }
12770
12802
  const optionId = cfg.statusOptions[toStatus];
12803
+ let unclaimCredential = "user";
12771
12804
  try {
12772
- await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId);
12805
+ ({ credential: unclaimCredential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId));
12773
12806
  } catch (e) {
12774
12807
  const warning = `partial unclaim: ${item.ref} status was not changed to ${toStatus} (${ghError(e)})`;
12775
12808
  if (!options.allowPartial) throw new Error(warning);
@@ -12780,7 +12813,8 @@ async function unclaimBoardIssue(options, deps = {}) {
12780
12813
  viewer,
12781
12814
  repo: currentRepo,
12782
12815
  status: toStatus,
12783
- partial: false
12816
+ partial: false,
12817
+ credential: unclaimCredential
12784
12818
  };
12785
12819
  }
12786
12820
  async function setBoardItemPriority(client, cfg, itemId, priority) {
@@ -15904,10 +15938,10 @@ var rollout_plan_default = {
15904
15938
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
15905
15939
  },
15906
15940
  baseline: {
15907
- version: "4.4.6",
15908
- tag: "v4.4.6",
15909
- commit: "640e312fac88",
15910
- npm: "@mutmutco/cli@4.4.6"
15941
+ version: "4.4.8",
15942
+ tag: "v4.4.8",
15943
+ commit: "beed56b5ec82",
15944
+ npm: "@mutmutco/cli@4.4.8"
15911
15945
  },
15912
15946
  exitCriterion: "fleet-n-of-n",
15913
15947
  hubOnlyShortcut: "forbidden",
@@ -15924,14 +15958,14 @@ var rollout_plan_default = {
15924
15958
  repo: "mutmutco/mmi-hub",
15925
15959
  role: "canary",
15926
15960
  schedule: "train",
15927
- v3Target: "v4.4.6"
15961
+ v3Target: "v4.4.8"
15928
15962
  }
15929
15963
  ],
15930
15964
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
15931
15965
  rollback: {
15932
15966
  independent: true,
15933
- mechanism: "npm dist-tag latest -> 4.4.6 and redeploy the Hub Lambda from tag v4.4.6 (640e312fac88); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15934
- v3Target: "v4.4.6 (@mutmutco/cli@4.4.6, tag commit 640e312fac88 \u2014 last known-good release carrying the repo-index v4-only contract)"
15967
+ mechanism: "npm dist-tag latest -> 4.4.8 and redeploy the Hub Lambda from tag v4.4.8 (beed56b5ec82); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15968
+ v3Target: "v4.4.8 (@mutmutco/cli@4.4.8, tag commit beed56b5ec82 \u2014 last known-good release carrying the repo-index v4-only contract)"
15935
15969
  }
15936
15970
  },
15937
15971
  {
@@ -37729,6 +37763,10 @@ function spawnDetachedSelf(args, deps, opts = {}) {
37729
37763
  var import_node_path39 = require("node:path");
37730
37764
 
37731
37765
  // src/attach-to-project.ts
37766
+ async function attachViaHubApp(cfg, contentNodeId) {
37767
+ const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
37768
+ return addIssueToProjectViaHub2(cfg, contentNodeId);
37769
+ }
37732
37770
  function boardAttachRateLimitedReceipt(resetEpochSeconds) {
37733
37771
  return {
37734
37772
  onBoard: false,
@@ -40771,7 +40809,7 @@ async function attachToProject(issueNumber, repo, priority) {
40771
40809
  }
40772
40810
  }
40773
40811
  }
40774
- return { projectItemId, onBoard: true };
40812
+ return { projectItemId, onBoard: true, credential: "user" };
40775
40813
  } catch (e) {
40776
40814
  const err = e;
40777
40815
  const detail = (err.stderr || err.message || String(e)).trim();
@@ -40783,11 +40821,33 @@ async function attachToProject(issueNumber, repo, priority) {
40783
40821
  { repo: targetRepo2, number: issueNumber },
40784
40822
  priority
40785
40823
  );
40786
- return { projectItemId, onBoard: true };
40824
+ return { projectItemId, onBoard: true, credential: "user" };
40787
40825
  }
40788
40826
  return { onBoard: true };
40789
40827
  }
40790
40828
  if (isRateLimitText(detail)) {
40829
+ if (targetRepo2) {
40830
+ try {
40831
+ const boardCfg = await loadConfigForRepo(targetRepo2);
40832
+ const viewArgs2 = ["issue", "view", String(issueNumber), "--json", "id", "--jq", ".id"];
40833
+ if (targetRepo2) viewArgs2.push("--repo", targetRepo2);
40834
+ const contentId2 = (await execFileP("gh", viewArgs2, { timeout: 1e4 })).stdout.trim();
40835
+ if (contentId2 && boardCfg.projectId) {
40836
+ const projectItemId = await attachViaHubApp(boardCfg, contentId2);
40837
+ if (priority) {
40838
+ try {
40839
+ await setBoardItemPriority(defaultGitHubClient(), boardCfg, projectItemId, priority);
40840
+ } catch (e2) {
40841
+ const err2 = e2;
40842
+ process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${(err2.stderr || err2.message || String(e2)).trim()}
40843
+ `);
40844
+ }
40845
+ }
40846
+ return { projectItemId, onBoard: true, credential: "app_installation" };
40847
+ }
40848
+ } catch {
40849
+ }
40850
+ }
40791
40851
  process.stderr.write(`warning: issue #${issueNumber} created but board attach rate-limited: ${detail}
40792
40852
  `);
40793
40853
  let resetEpochSeconds;
@@ -41002,7 +41062,7 @@ function registerCollaborationCommands(program3) {
41002
41062
  return;
41003
41063
  }
41004
41064
  const attached = await attachToProject(created.number, targetRepo2, priority);
41005
- const { projectItemId, onBoard } = attached;
41065
+ const { projectItemId, onBoard, credential: attachCredential } = attached;
41006
41066
  let parent;
41007
41067
  let parentLinkError;
41008
41068
  if (o.parent !== void 0) {
@@ -41022,6 +41082,7 @@ function registerCollaborationCommands(program3) {
41022
41082
  priority,
41023
41083
  projectItemId,
41024
41084
  onBoard,
41085
+ ...attachCredential ? { credential: attachCredential } : {},
41025
41086
  // #5489: partial receipt when the GitHub issue landed but Project v2 attach hit GraphQL quota.
41026
41087
  ...attached.boardAttach ? {
41027
41088
  boardAttach: attached.boardAttach,
@@ -41657,10 +41718,21 @@ ${list}`);
41657
41718
  return cwdRepo?.toLowerCase() === repo.split("/").slice(-2).join("/").toLowerCase();
41658
41719
  }
41659
41720
  async function prLandUpdateBranch(prNumber, repo, explicitRepo) {
41660
- const viewed = JSON.parse((await execFileP("gh", ["pr", "view", prNumber, "--repo", repo, "--json", "headRefName,baseRefName"], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
41661
- const localCheckedOut = await prHeadCheckedOutHere(viewed.headRefName, repo, explicitRepo);
41662
- console.warn(`pr land: PR #${prNumber} is BEHIND ${viewed.baseRefName} \u2014 updating ${viewed.headRefName} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and re-waiting the checks once (#6263).`);
41663
- return updatePrHeadForMerge({ prNumber, repo, head: viewed.headRefName, base: viewed.baseRefName, localCheckedOut });
41721
+ let head = "";
41722
+ let base = "";
41723
+ const rested = await fetchRestPrSnapshot(prNumber, repo).catch(() => void 0);
41724
+ if (rested && rested.headRef && rested.baseRef) {
41725
+ console.warn("pr land: PR head/base read via REST (App-capable poll identity) instead of gh GraphQL (#6834).");
41726
+ head = rested.headRef;
41727
+ base = rested.baseRef;
41728
+ } else {
41729
+ const viewed = JSON.parse((await execFileP("gh", ["pr", "view", prNumber, "--repo", repo, "--json", "headRefName,baseRefName"], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
41730
+ head = viewed.headRefName;
41731
+ base = viewed.baseRefName;
41732
+ }
41733
+ const localCheckedOut = await prHeadCheckedOutHere(head, repo, explicitRepo);
41734
+ console.warn(`pr land: PR #${prNumber} is BEHIND ${base} \u2014 updating ${head} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and re-waiting the checks once (#6263).`);
41735
+ return updatePrHeadForMerge({ prNumber, repo, head, base, localCheckedOut });
41664
41736
  }
41665
41737
  class PrHeadBehindBaseError extends Error {
41666
41738
  }
@@ -41701,9 +41773,22 @@ ${list}`);
41701
41773
  if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
41702
41774
  const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
41703
41775
  resolveRepo: async (prNumber, repoOpt) => {
41704
- const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
41705
- const viewed = (await execFileP("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS4 })).stdout.trim();
41706
- const [repoFromGh, base] = viewed.split(/\s+/);
41776
+ const knownRepo = repoOpt ?? await resolveRepo(o.repo).catch(() => void 0) ?? o.repo;
41777
+ let repoFromGh = "";
41778
+ let base = "";
41779
+ if (knownRepo) {
41780
+ const rested = await fetchRestPrSnapshot(prNumber, knownRepo).catch(() => void 0);
41781
+ if (rested) {
41782
+ console.warn("pr land: PR repo/base read via REST (App-capable poll identity) instead of gh GraphQL (#6834).");
41783
+ repoFromGh = knownRepo;
41784
+ base = rested.baseRef;
41785
+ }
41786
+ }
41787
+ if (!repoFromGh || !base) {
41788
+ const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
41789
+ const viewed = (await execFileP("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS4 })).stdout.trim();
41790
+ [repoFromGh, base] = viewed.split(/\s+/);
41791
+ }
41707
41792
  const repo = repoOpt ?? repoFromGh;
41708
41793
  if (!repo) throw new Error("pr land: could not resolve PR repo");
41709
41794
  let track;
@@ -41829,8 +41914,9 @@ ${list}`);
41829
41914
  console.error(line);
41830
41915
  }
41831
41916
  }
41832
- if (o.json) printLine(JSON.stringify(result));
41833
- else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
41917
+ const credential = await pollToken().catch(() => void 0) ? "app_installation" : "user";
41918
+ if (o.json) printLine(JSON.stringify({ ...result, credential }));
41919
+ else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""} (credential: ${credential})`);
41834
41920
  if (result.status === "failed") process.exitCode = 1;
41835
41921
  });
41836
41922
  jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict")).action(async (number, o) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.4.6",
3
+ "version": "4.4.8",
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",