@mutmutco/cli 4.5.17 → 4.5.18

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 +78 -26
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -9544,11 +9544,16 @@ function resolveUseEnvNames(keys, name) {
9544
9544
  function referencesDollarEnv(arg, name) {
9545
9545
  return arg.includes(`\${${name}}`) || new RegExp(`\\$${name}(?![A-Za-z0-9_])`).test(arg);
9546
9546
  }
9547
+ function isShellScriptCommand(command) {
9548
+ const exe = (command[0] ?? "").split(/[/\\]/).pop()?.toLowerCase().replace(/\.exe$/, "") ?? "";
9549
+ return /^(?:ba|z|da|k)?sh$/.test(exe) && command.slice(1).some((a) => /^-[a-z]*c[a-z]*$/i.test(a));
9550
+ }
9547
9551
  function secretsUseDollarHint(command, envNames) {
9552
+ if (isShellScriptCommand(command)) return null;
9548
9553
  for (const arg of command) {
9549
9554
  for (const name of envNames) {
9550
9555
  if (!referencesDollarEnv(arg, name)) continue;
9551
- return `secrets use: $${name} is injected into the CHILD's env, not your shell \u2014 the caller expanded it (empty) before this ran. Wrap the consumer so expansion happens inside: bash -c 'your-command "$${name}"'`;
9556
+ return `secrets use: ${command[0]} receives the literal text $${name} \u2014 the value is injected into the CHILD's env, and ${command[0]} does not expand variables in its arguments. Wrap the consumer in a shell so expansion happens inside: bash -c 'your-command "$${name}"'`;
9552
9557
  }
9553
9558
  }
9554
9559
  return null;
@@ -10865,6 +10870,9 @@ function readSessionId(env) {
10865
10870
  }
10866
10871
  return void 0;
10867
10872
  }
10873
+ function readSessionIdSource(env) {
10874
+ return SESSION_ID_ENV_VARS.find((key) => Boolean(env[key]?.trim()));
10875
+ }
10868
10876
  var synthesizedSessionId;
10869
10877
  function fallbackSessionId(surface) {
10870
10878
  if (!synthesizedSessionId) {
@@ -12581,15 +12589,36 @@ async function moveBoardItem(options, deps = {}) {
12581
12589
  ({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId));
12582
12590
  } catch (e) {
12583
12591
  if (options.status === "Done" && isArchivedItemRefusal(ghError(e))) {
12584
- return {
12585
- item,
12586
- viewer: lookup.viewer,
12587
- repo: currentRepo,
12588
- status: item.status,
12589
- partial: false,
12590
- archived: true,
12591
- warning: `${item.ref} is archived (already terminal \u2014 its close archived the card); no ${options.status} move was performed`
12592
- };
12592
+ if (item.status === "Done") {
12593
+ return {
12594
+ item,
12595
+ viewer: lookup.viewer,
12596
+ repo: currentRepo,
12597
+ status: "Done",
12598
+ partial: false,
12599
+ archived: true,
12600
+ warning: `${item.ref} is archived and already Done; no status write was needed`
12601
+ };
12602
+ }
12603
+ try {
12604
+ await client.graphql(
12605
+ `mutation($projectId: ID!, $itemId: ID!) { unarchiveProjectV2Item(input: { projectId: $projectId, itemId: $itemId }) { item { id } } }`,
12606
+ { projectId: cfg.projectId, itemId: item.itemId }
12607
+ );
12608
+ ({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId));
12609
+ return {
12610
+ item: { ...item, status: "Done", statusOptionId: optionId },
12611
+ viewer: lookup.viewer,
12612
+ repo: currentRepo,
12613
+ status: "Done",
12614
+ partial: false,
12615
+ credential
12616
+ };
12617
+ } catch (recoveryError) {
12618
+ const warning2 = `partial move: ${item.ref} was archived at '${item.status}' and could not be unarchived and moved to Done (${ghError(recoveryError)})`;
12619
+ if (!options.allowPartial) throw new Error(warning2);
12620
+ return { item, viewer: lookup.viewer, repo: currentRepo, status: item.status, partial: true, warning: warning2 };
12621
+ }
12593
12622
  }
12594
12623
  const warning = `partial move: ${item.ref} status was not changed to ${options.status} (${ghError(e)})`;
12595
12624
  if (!options.allowPartial) throw new Error(warning);
@@ -12663,9 +12692,26 @@ async function prepareClaimContext(options, selectors, deps, collected, snapshot
12663
12692
  items: collected.items,
12664
12693
  writable: writableOrUnknown(writable),
12665
12694
  report,
12666
- session: deps.session ?? describeSessionIdentity()
12695
+ session: deps.session ?? describeSessionIdentity(),
12696
+ ...deps.snapshot ? {
12697
+ freshAppItem: async (selector) => {
12698
+ const fresh = await collectBoardPreferringSnapshot(cfg, { repo: options.repo }, deps);
12699
+ if (!fresh.snapshot || fresh.collected.partial) return void 0;
12700
+ return fresh.collected.items.find((candidate) => candidate.repository.toLowerCase() === selector.repo.toLowerCase() && candidate.number === selector.number);
12701
+ }
12702
+ } : {}
12667
12703
  };
12668
12704
  }
12705
+ async function freshClaimItem(ctx, selector) {
12706
+ try {
12707
+ return (await fetchIssueProjectItem(ctx.client, ctx.cfg, selector)).item;
12708
+ } catch (e) {
12709
+ if (!isRateLimitRefusal(e) || !ctx.freshAppItem) throw e;
12710
+ const item = await ctx.freshAppItem(selector).catch(() => void 0);
12711
+ if (!item) throw e;
12712
+ return item;
12713
+ }
12714
+ }
12669
12715
  async function claimOneBoardItem(ctx, selector, options) {
12670
12716
  const { cfg, client, report } = ctx;
12671
12717
  const flatItem = findBoardItem(ctx.items, selector, { owner: cfg.projectOwner, number: cfg.projectNumber });
@@ -12721,7 +12767,7 @@ async function claimOneBoardItem(ctx, selector, options) {
12721
12767
  const pre = evaluateClaim(flatItem, assignedLogin);
12722
12768
  if (!pre.ok) throw new NotClaimableError(pre.reason, { ref: flatItem.ref, status: flatItem.status });
12723
12769
  let item = flatItem;
12724
- const fresh = (await fetchIssueProjectItem(client, cfg, { repo: item.repository, number: item.number })).item;
12770
+ const fresh = await freshClaimItem(ctx, { repo: item.repository, number: item.number });
12725
12771
  if (!fresh) throw new Error(`${item.ref} is not on this project board`);
12726
12772
  const verdict = evaluateClaim(fresh, assignedLogin);
12727
12773
  if (!verdict.ok) throw new NotClaimableError(verdict.reason, { ref: fresh.ref, status: fresh.status });
@@ -14838,9 +14884,10 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
14838
14884
  deployModel,
14839
14885
  // Persist the canonical stage hosts at the same write-time boundary as deployModel. Read paths never
14840
14886
  // infer from an absent edgeDomains field.
14841
- edgeDomains: inferEdgeDomainsForWrite(slug),
14887
+ edgeDomains: options.existing?.edgeDomains ?? inferEdgeDomainsForWrite(slug),
14842
14888
  // #1359: always persist an explicit track so release tooling never guesses from absence alone.
14843
- releaseTrack: resolveBootstrapReleaseTrack(cls, options.releaseTrack),
14889
+ // #7134: an explicit flag wins, then the registered track, then the class default (first bootstrap).
14890
+ releaseTrack: options.releaseTrack ?? (isReleaseTrack(options.existing?.releaseTrack) ? options.existing.releaseTrack : void 0) ?? resolveBootstrapReleaseTrack(cls, void 0),
14844
14891
  // Board coords (from GraphQL at bootstrap, passed as --var by the skill).
14845
14892
  projectOwner: vars.PROJECT_OWNER || void 0,
14846
14893
  projectNumber: num(vars.PROJECT_NUMBER),
@@ -16115,10 +16162,10 @@ var rollout_plan_default = {
16115
16162
  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)."
16116
16163
  },
16117
16164
  baseline: {
16118
- version: "4.5.17",
16119
- tag: "v4.5.17",
16120
- commit: "be54d6077479",
16121
- npm: "@mutmutco/cli@4.5.17"
16165
+ version: "4.5.18",
16166
+ tag: "v4.5.18",
16167
+ commit: "afc70753c56b",
16168
+ npm: "@mutmutco/cli@4.5.18"
16122
16169
  },
16123
16170
  exitCriterion: "fleet-n-of-n",
16124
16171
  hubOnlyShortcut: "forbidden",
@@ -16135,14 +16182,14 @@ var rollout_plan_default = {
16135
16182
  repo: "mutmutco/mmi-hub",
16136
16183
  role: "canary",
16137
16184
  schedule: "train",
16138
- v3Target: "v4.5.17"
16185
+ v3Target: "v4.5.18"
16139
16186
  }
16140
16187
  ],
16141
16188
  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.",
16142
16189
  rollback: {
16143
16190
  independent: true,
16144
- mechanism: "npm dist-tag latest -> 4.5.17 and redeploy the Hub Lambda from tag v4.5.17 (be54d6077479); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16145
- v3Target: "v4.5.17 (@mutmutco/cli@4.5.17, tag commit be54d6077479 \u2014 last known-good release carrying the repo-index v4-only contract)"
16191
+ mechanism: "npm dist-tag latest -> 4.5.18 and redeploy the Hub Lambda from tag v4.5.18 (afc70753c56b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16192
+ v3Target: "v4.5.18 (@mutmutco/cli@4.5.18, tag commit afc70753c56b \u2014 last known-good release carrying the repo-index v4-only contract)"
16146
16193
  }
16147
16194
  },
16148
16195
  {
@@ -30225,7 +30272,7 @@ function registerBootstrapCommands(program3) {
30225
30272
  for (const [k, v] of Object.entries(gateConfigToVars(meta?.gate))) if (rawVars[k] == null) rawVars[k] = v;
30226
30273
  } catch {
30227
30274
  }
30228
- const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack, registryRequiredCheckBranches, registryMeta?.requiredChecks);
30275
+ const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, registryMeta?.requiredChecks);
30229
30276
  if (!vars.PROJECT_ID) {
30230
30277
  try {
30231
30278
  const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
@@ -30591,7 +30638,9 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
30591
30638
  projectType: o.projectType || registryMeta?.projectType || void 0,
30592
30639
  // #6090: META is authoritative on a rerun
30593
30640
  deployModel: o.deployModel || registryMeta?.deployModel || void 0,
30594
- releaseTrack: bootstrapReleaseTrack
30641
+ // #7134: explicit flag only; the registered track and edge domains win on a re-run.
30642
+ releaseTrack: o.releaseTrack || void 0,
30643
+ existing: registryMeta
30595
30644
  });
30596
30645
  } catch (e) {
30597
30646
  return fail(`bootstrap apply: ${e.message}`);
@@ -33115,7 +33164,8 @@ async function collectOnboardStatus(opts = {}) {
33115
33164
  let board = { ok: false, detail: "no config" };
33116
33165
  try {
33117
33166
  if (cfg.sagaApiUrl) {
33118
- const report = await readBoard({ config: cfg }, { snapshot: registryClientDeps(cfg) });
33167
+ const boardCfg = await loadConfigOrDiscover();
33168
+ const report = await readBoard({ config: boardCfg }, { snapshot: registryClientDeps(boardCfg) });
33119
33169
  const total = report.primary.claimable.length + report.primary.userOwned.length + report.primary.taken.length;
33120
33170
  board = { ok: true, detail: `board has ${total} active items (${report.primary.claimable.length} claimable, ${report.primary.userOwned.length} yours)` };
33121
33171
  } else {
@@ -41149,6 +41199,8 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
41149
41199
  const repo = await requireRepo(repoOption);
41150
41200
  const { client, throttledPaths } = withRateLimitRetry(deps.client ?? defaultGitHubClient(), deps);
41151
41201
  const actor = deps.actor ?? describeSessionIdentity();
41202
+ const source = deps.actorSource ?? (deps.actor ? "the caller-supplied identity" : readSessionIdSource(process.env) ?? "a synthesized fallback");
41203
+ const requester = `requesting lane: ${actor.surface}/${actor.session}@${actor.host} (session id from ${source})`;
41152
41204
  const checkContest = deps.checkContest ?? checkLaneContest;
41153
41205
  for (const number of issues) {
41154
41206
  const state = await client.rest("GET", `repos/${repo}/issues/${number}`).then((issue) => issue?.state, () => void 0);
@@ -41166,10 +41218,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
41166
41218
  contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : ""
41167
41219
  ].filter(Boolean).join("; ");
41168
41220
  if (!contest.marker && !contest.unverifiable.includes("comments")) {
41169
- return `pr create: REFUSED \u2014 ${ref} carries no claim marker (its claim-marker comments were read from ${commentsPath} and name no holder) and shows ${kinds} work evidence (${why}); if this work is yours, \`mmi-cli oracle board claim ${ref} --force\` re-stamps your lane's marker and clears the refusal (move it to In Progress first if it sits In Review), or drop the closing line before creating this PR`;
41221
+ return `pr create: REFUSED \u2014 ${ref} carries no claim marker (its claim-marker comments were read from ${commentsPath} and name no holder) and shows ${kinds} work evidence (${why}); if this work is yours, \`mmi-cli oracle board claim ${ref} --force\` re-stamps your lane's marker and clears the refusal (move it to In Progress first if it sits In Review), or drop the closing line before creating this PR; ${requester}`;
41170
41222
  }
41171
41223
  const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)} (claim marker read from ${commentsPath}${posted})` : `an unreadable lane (its claim-marker comments could not be read${throttledPaths.has(commentsPath) ? " \u2014 GitHub rate-limited the read (HTTP 403) and the bounded reset-aware retry could not clear it" : ""}; the holder may be this very lane)`;
41172
- return `pr create: REFUSED \u2014 ${ref} is held by ${holder} with ${kinds} work evidence (${why}); run \`mmi-cli oracle board claim ${ref} --force\` (move it to In Progress first if it sits In Review) or drop the closing line before creating this PR`;
41224
+ return `pr create: REFUSED \u2014 ${ref} is held by ${holder} with ${kinds} work evidence (${why}); run \`mmi-cli oracle board claim ${ref} --force\` (move it to In Progress first if it sits In Review) or drop the closing line before creating this PR; ${requester}`;
41173
41225
  }
41174
41226
  return void 0;
41175
41227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.17",
3
+ "version": "4.5.18",
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",