@mutmutco/cli 4.5.16 → 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 +204 -31
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -7401,6 +7401,18 @@ function surfaceInferenceSuffix(inferred) {
7401
7401
  const alts = inferred.alternatives?.length ? ` Other candidates: ${inferred.alternatives.join(", ")}.` : "";
7402
7402
  return ` This pick is low-confidence \u2014 pass --surface <value> to choose explicitly.${alts}`;
7403
7403
  }
7404
+ function uncertainSurfaceRefusal(inferred, command = "issue create") {
7405
+ if (!inferred.uncertain) return void 0;
7406
+ const alternatives = inferred.alternatives?.length ? ` Other candidates: ${inferred.alternatives.join(", ")}.` : "";
7407
+ return {
7408
+ message: `${command}: surface inference is uncertain (${inferred.reason}) \u2014 pass --surface <value> to choose explicitly or --no-surface only when the filing is genuinely exempt.${alternatives}`,
7409
+ payload: {
7410
+ code: ERROR_CODES.ERR_MISSING_FLAG,
7411
+ offending_flag: "--surface",
7412
+ expected: ["--surface <value>", "--no-surface for a genuinely exempt filing"]
7413
+ }
7414
+ };
7415
+ }
7404
7416
  function surfaceInferenceNote(inferred) {
7405
7417
  const base = `mmi-cli: no --surface given \u2014 filed under ${inferred.label} (${inferred.reason}).`;
7406
7418
  return inferred.uncertain ? `${base}${surfaceInferenceSuffix(inferred)}
@@ -7438,7 +7450,9 @@ async function checkSurfaceRequirement(input, deps = {}) {
7438
7450
  }
7439
7451
  if (known.length === 0) return { enforcing: false, taxonomyAbsent: true };
7440
7452
  if (labelsCarrySurface(input.labels)) return { enforcing: true };
7441
- return { enforcing: true, inferred: inferSurface(known, { title: input.title, type: input.type, body: input.body }) };
7453
+ const inferred = inferSurface(known, { title: input.title, type: input.type, body: input.body });
7454
+ const refusal = uncertainSurfaceRefusal(inferred, input.command ?? "issue create");
7455
+ return refusal ? { enforcing: true, refusal } : { enforcing: true, inferred };
7442
7456
  }
7443
7457
  function conflictingSurfaceInputs(surfaceFlag, labels) {
7444
7458
  if (!surfaceFlag) return void 0;
@@ -9530,11 +9544,16 @@ function resolveUseEnvNames(keys, name) {
9530
9544
  function referencesDollarEnv(arg, name) {
9531
9545
  return arg.includes(`\${${name}}`) || new RegExp(`\\$${name}(?![A-Za-z0-9_])`).test(arg);
9532
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
+ }
9533
9551
  function secretsUseDollarHint(command, envNames) {
9552
+ if (isShellScriptCommand(command)) return null;
9534
9553
  for (const arg of command) {
9535
9554
  for (const name of envNames) {
9536
9555
  if (!referencesDollarEnv(arg, name)) continue;
9537
- 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}"'`;
9538
9557
  }
9539
9558
  }
9540
9559
  return null;
@@ -10851,6 +10870,9 @@ function readSessionId(env) {
10851
10870
  }
10852
10871
  return void 0;
10853
10872
  }
10873
+ function readSessionIdSource(env) {
10874
+ return SESSION_ID_ENV_VARS.find((key) => Boolean(env[key]?.trim()));
10875
+ }
10854
10876
  var synthesizedSessionId;
10855
10877
  function fallbackSessionId(surface) {
10856
10878
  if (!synthesizedSessionId) {
@@ -11076,6 +11098,7 @@ var ITEM_FIELD_VALUES_PAGE = 25;
11076
11098
  var ITEM_LABELS_PAGE = 25;
11077
11099
  var ITEM_ASSIGNEES_PAGE = 10;
11078
11100
  var ITEM_PROJECT_ITEMS_PAGE = 50;
11101
+ var LINKED_REPOS_PAGE = 20;
11079
11102
  var CONTENT_LABELS_AND_ASSIGNEES = `
11080
11103
  labels(first: ${ITEM_LABELS_PAGE}) { pageInfo { hasNextPage } nodes { name } }
11081
11104
  assignees(first: ${ITEM_ASSIGNEES_PAGE}) { pageInfo { hasNextPage } nodes { login } }`;
@@ -11086,6 +11109,7 @@ query($owner: String!, $number: Int!, $after: String, $query: String) {
11086
11109
  projectV2(number: $number) {
11087
11110
  id
11088
11111
  title
11112
+ repositories(first: ${LINKED_REPOS_PAGE}) { pageInfo { hasNextPage } nodes { nameWithOwner } }
11089
11113
  items(first: 100, after: $after, query: $query) {
11090
11114
  pageInfo { hasNextPage endCursor }
11091
11115
  nodes {
@@ -11126,6 +11150,44 @@ query($owner: String!, $number: Int!, $after: String, $query: String) {
11126
11150
  }
11127
11151
  }
11128
11152
  }`;
11153
+ var INDEX_LAG_RECOVERY_PAGE = 50;
11154
+ var INDEX_LAG_RECOVERY_ITEMS_PAGE = 10;
11155
+ var INDEX_LAG_RECOVERY_MAX_PAGES = 10;
11156
+ var indexLagRecoveryQuery = (connection) => `
11157
+ query($repoOwner: String!, $repoName: String!, $after: String) {
11158
+ repository(owner: $repoOwner, name: $repoName) {
11159
+ ${connection}(first: ${INDEX_LAG_RECOVERY_PAGE}, after: $after, states: OPEN) {
11160
+ pageInfo { hasNextPage endCursor }
11161
+ nodes {
11162
+ __typename
11163
+ id
11164
+ number
11165
+ title
11166
+ url
11167
+ state
11168
+ updatedAt
11169
+ repository { nameWithOwner }${CONTENT_LABELS_AND_ASSIGNEES}
11170
+ projectItems(first: ${INDEX_LAG_RECOVERY_ITEMS_PAGE}) {
11171
+ pageInfo { hasNextPage }
11172
+ nodes {
11173
+ id
11174
+ project { id }
11175
+ fieldValues(first: ${ITEM_FIELD_VALUES_PAGE}) {
11176
+ pageInfo { hasNextPage }
11177
+ nodes {
11178
+ ... on ProjectV2ItemFieldSingleSelectValue {
11179
+ name
11180
+ optionId
11181
+ field { ... on ProjectV2SingleSelectField { id name } }
11182
+ }
11183
+ }
11184
+ }
11185
+ }
11186
+ }
11187
+ }
11188
+ }
11189
+ }
11190
+ }`;
11129
11191
  var ACTIVE_BOARD_ITEMS_FILTER = "-status:Done -is:closed";
11130
11192
  var DOCTOR_BOARD_ITEMS_FILTER = "-status:Done";
11131
11193
  var ARCHIVE_BOARD_ITEMS_FILTER = "status:Done is:closed";
@@ -11349,6 +11411,52 @@ function shouldSkipInactiveBoardNode(node, cfg) {
11349
11411
  const status = parseSingleSelectFields(node.fieldValues?.nodes, cfg).status?.name;
11350
11412
  return status === "Done";
11351
11413
  }
11414
+ async function recoverIndexLaggedItems(graphql, projectId, linkedRepos, listed, isInactive, warnings) {
11415
+ const known = new Set(listed.map((node) => node.id));
11416
+ const nodes = [];
11417
+ let partial = false;
11418
+ if (linkedRepos?.pageInfo?.hasNextPage) {
11419
+ warnings.push(`partial board read: index-lag recovery covers only the first ${LINKED_REPOS_PAGE} linked repos`);
11420
+ partial = true;
11421
+ }
11422
+ for (const repo of (linkedRepos?.nodes ?? []).map((node) => node.nameWithOwner).filter((name) => Boolean(name))) {
11423
+ const [repoOwner, repoName2] = repo.split("/");
11424
+ let recoveredHere = 0;
11425
+ for (const connection of ["issues", "pullRequests"]) {
11426
+ let after;
11427
+ let pages = 0;
11428
+ try {
11429
+ do {
11430
+ const page = (await graphql(indexLagRecoveryQuery(connection), { repoOwner, repoName: repoName2, ...after ? { after } : {} })).repository?.[connection];
11431
+ for (const content of page?.nodes ?? []) {
11432
+ const { projectItems, ...contentFields } = content;
11433
+ const item = (projectItems?.nodes ?? []).find((entry) => entry.project?.id === projectId);
11434
+ if (!item?.id || known.has(item.id)) continue;
11435
+ const node = { id: item.id, fieldValues: item.fieldValues, content: contentFields };
11436
+ if (isInactive(node)) continue;
11437
+ known.add(item.id);
11438
+ nodes.push(node);
11439
+ recoveredHere += 1;
11440
+ }
11441
+ after = page?.pageInfo?.hasNextPage ? page.pageInfo.endCursor ?? void 0 : void 0;
11442
+ pages += 1;
11443
+ if (after && pages >= INDEX_LAG_RECOVERY_MAX_PAGES) {
11444
+ warnings.push(`partial board read: index-lag recovery for ${repo} ${connection} capped at ${pages * INDEX_LAG_RECOVERY_PAGE} open items`);
11445
+ partial = true;
11446
+ after = void 0;
11447
+ }
11448
+ } while (after);
11449
+ } catch (e) {
11450
+ warnings.push(`partial board read: index-lag recovery for ${repo} ${connection} failed (${e.message})`);
11451
+ partial = true;
11452
+ }
11453
+ }
11454
+ if (recoveredHere) {
11455
+ warnings.push(`board read: ${recoveredHere} active item(s) from ${repo} were missing from GitHub's project items listing (Projects index lag) \u2014 recovered from their own project membership`);
11456
+ }
11457
+ }
11458
+ return { nodes, partial };
11459
+ }
11352
11460
  async function collectBoardItems(cfg, options, deps) {
11353
11461
  const client = deps.client ?? defaultGitHubClient();
11354
11462
  const currentRepo = await resolveCurrentRepo(options, deps);
@@ -11361,6 +11469,7 @@ async function collectBoardItems(cfg, options, deps) {
11361
11469
  let projectId = cfg.projectId;
11362
11470
  let partial = false;
11363
11471
  let pageCount = 0;
11472
+ let linkedRepos;
11364
11473
  do {
11365
11474
  try {
11366
11475
  const page = await fetchProjectPage(client, cfg, after, itemsQuery);
@@ -11369,6 +11478,7 @@ async function collectBoardItems(cfg, options, deps) {
11369
11478
  if (!project2) throw new Error(`project ${cfg.projectOwner}#${cfg.projectNumber} not found`);
11370
11479
  projectId = project2.id;
11371
11480
  projectTitle ||= project2.title;
11481
+ linkedRepos ??= project2.repositories;
11372
11482
  pageCount += 1;
11373
11483
  const pageNodes = project2.items.nodes ?? [];
11374
11484
  if (options.activeOnly) {
@@ -11410,6 +11520,18 @@ async function collectBoardItems(cfg, options, deps) {
11410
11520
  partial = true;
11411
11521
  }
11412
11522
  }
11523
+ if (options.activeOnly) {
11524
+ const recovered = await recoverIndexLaggedItems(
11525
+ (query, variables) => client.graphql(query, variables),
11526
+ projectId,
11527
+ linkedRepos,
11528
+ nodes,
11529
+ (node) => shouldSkipInactiveBoardNode(node, cfg),
11530
+ warnings
11531
+ );
11532
+ nodes.push(...recovered.nodes);
11533
+ partial ||= recovered.partial;
11534
+ }
11413
11535
  const read = nodesToItems(nodes, warnings, cfg);
11414
11536
  if (read.failures.length) {
11415
11537
  const message2 = `partial board read: ${read.failures.length} item(s) came back truncated and were not read \u2014 ${read.failures.join(" | ")}`;
@@ -12467,15 +12589,36 @@ async function moveBoardItem(options, deps = {}) {
12467
12589
  ({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId));
12468
12590
  } catch (e) {
12469
12591
  if (options.status === "Done" && isArchivedItemRefusal(ghError(e))) {
12470
- return {
12471
- item,
12472
- viewer: lookup.viewer,
12473
- repo: currentRepo,
12474
- status: item.status,
12475
- partial: false,
12476
- archived: true,
12477
- warning: `${item.ref} is archived (already terminal \u2014 its close archived the card); no ${options.status} move was performed`
12478
- };
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
+ }
12479
12622
  }
12480
12623
  const warning = `partial move: ${item.ref} status was not changed to ${options.status} (${ghError(e)})`;
12481
12624
  if (!options.allowPartial) throw new Error(warning);
@@ -12549,9 +12692,26 @@ async function prepareClaimContext(options, selectors, deps, collected, snapshot
12549
12692
  items: collected.items,
12550
12693
  writable: writableOrUnknown(writable),
12551
12694
  report,
12552
- 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
+ } : {}
12553
12703
  };
12554
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
+ }
12555
12715
  async function claimOneBoardItem(ctx, selector, options) {
12556
12716
  const { cfg, client, report } = ctx;
12557
12717
  const flatItem = findBoardItem(ctx.items, selector, { owner: cfg.projectOwner, number: cfg.projectNumber });
@@ -12607,7 +12767,7 @@ async function claimOneBoardItem(ctx, selector, options) {
12607
12767
  const pre = evaluateClaim(flatItem, assignedLogin);
12608
12768
  if (!pre.ok) throw new NotClaimableError(pre.reason, { ref: flatItem.ref, status: flatItem.status });
12609
12769
  let item = flatItem;
12610
- 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 });
12611
12771
  if (!fresh) throw new Error(`${item.ref} is not on this project board`);
12612
12772
  const verdict = evaluateClaim(fresh, assignedLogin);
12613
12773
  if (!verdict.ok) throw new NotClaimableError(verdict.reason, { ref: fresh.ref, status: fresh.status });
@@ -14724,9 +14884,10 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
14724
14884
  deployModel,
14725
14885
  // Persist the canonical stage hosts at the same write-time boundary as deployModel. Read paths never
14726
14886
  // infer from an absent edgeDomains field.
14727
- edgeDomains: inferEdgeDomainsForWrite(slug),
14887
+ edgeDomains: options.existing?.edgeDomains ?? inferEdgeDomainsForWrite(slug),
14728
14888
  // #1359: always persist an explicit track so release tooling never guesses from absence alone.
14729
- 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),
14730
14891
  // Board coords (from GraphQL at bootstrap, passed as --var by the skill).
14731
14892
  projectOwner: vars.PROJECT_OWNER || void 0,
14732
14893
  projectNumber: num(vars.PROJECT_NUMBER),
@@ -16001,10 +16162,10 @@ var rollout_plan_default = {
16001
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)."
16002
16163
  },
16003
16164
  baseline: {
16004
- version: "4.5.16",
16005
- tag: "v4.5.16",
16006
- commit: "4aa24e1877a7",
16007
- npm: "@mutmutco/cli@4.5.16"
16165
+ version: "4.5.18",
16166
+ tag: "v4.5.18",
16167
+ commit: "afc70753c56b",
16168
+ npm: "@mutmutco/cli@4.5.18"
16008
16169
  },
16009
16170
  exitCriterion: "fleet-n-of-n",
16010
16171
  hubOnlyShortcut: "forbidden",
@@ -16021,14 +16182,14 @@ var rollout_plan_default = {
16021
16182
  repo: "mutmutco/mmi-hub",
16022
16183
  role: "canary",
16023
16184
  schedule: "train",
16024
- v3Target: "v4.5.16"
16185
+ v3Target: "v4.5.18"
16025
16186
  }
16026
16187
  ],
16027
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.",
16028
16189
  rollback: {
16029
16190
  independent: true,
16030
- mechanism: "npm dist-tag latest -> 4.5.16 and redeploy the Hub Lambda from tag v4.5.16 (4aa24e1877a7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16031
- v3Target: "v4.5.16 (@mutmutco/cli@4.5.16, tag commit 4aa24e1877a7 \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)"
16032
16193
  }
16033
16194
  },
16034
16195
  {
@@ -30111,7 +30272,7 @@ function registerBootstrapCommands(program3) {
30111
30272
  for (const [k, v] of Object.entries(gateConfigToVars(meta?.gate))) if (rawVars[k] == null) rawVars[k] = v;
30112
30273
  } catch {
30113
30274
  }
30114
- const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack, registryRequiredCheckBranches, registryMeta?.requiredChecks);
30275
+ const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, registryMeta?.requiredChecks);
30115
30276
  if (!vars.PROJECT_ID) {
30116
30277
  try {
30117
30278
  const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
@@ -30477,7 +30638,9 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
30477
30638
  projectType: o.projectType || registryMeta?.projectType || void 0,
30478
30639
  // #6090: META is authoritative on a rerun
30479
30640
  deployModel: o.deployModel || registryMeta?.deployModel || void 0,
30480
- 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
30481
30644
  });
30482
30645
  } catch (e) {
30483
30646
  return fail(`bootstrap apply: ${e.message}`);
@@ -33001,7 +33164,8 @@ async function collectOnboardStatus(opts = {}) {
33001
33164
  let board = { ok: false, detail: "no config" };
33002
33165
  try {
33003
33166
  if (cfg.sagaApiUrl) {
33004
- const report = await readBoard({ config: cfg }, { snapshot: registryClientDeps(cfg) });
33167
+ const boardCfg = await loadConfigOrDiscover();
33168
+ const report = await readBoard({ config: boardCfg }, { snapshot: registryClientDeps(boardCfg) });
33005
33169
  const total = report.primary.claimable.length + report.primary.userOwned.length + report.primary.taken.length;
33006
33170
  board = { ok: true, detail: `board has ${total} active items (${report.primary.claimable.length} claimable, ${report.primary.userOwned.length} yours)` };
33007
33171
  } else {
@@ -35691,10 +35855,12 @@ function validateBatchSpecs(specs) {
35691
35855
  }
35692
35856
  return { ok: errors.length === 0, errors, validated };
35693
35857
  }
35694
- async function preflightBatchSurfaces(validated, rowRepo, options) {
35858
+ async function preflightBatchSurfaces(validated, rowRepo, options, deps = {}) {
35859
+ const surfaceApplies = deps.surfaceLabelApplies ?? surfaceLabelApplies;
35860
+ const readLabels = deps.readRepoSurfaceLabels ?? readRepoSurfaceLabels;
35695
35861
  const applies = /* @__PURE__ */ new Map();
35696
35862
  const appliesTo = async (repo) => {
35697
- if (!applies.has(repo)) applies.set(repo, await surfaceLabelApplies(repo));
35863
+ if (!applies.has(repo)) applies.set(repo, await surfaceApplies(repo));
35698
35864
  return applies.get(repo);
35699
35865
  };
35700
35866
  if (options.surface) {
@@ -35709,7 +35875,7 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
35709
35875
  const censusFor = (repo) => {
35710
35876
  let pending = censusCache.get(repo);
35711
35877
  if (!pending) {
35712
- pending = readRepoSurfaceLabels(repo);
35878
+ pending = readLabels(repo);
35713
35879
  censusCache.set(repo, pending);
35714
35880
  }
35715
35881
  return pending;
@@ -35727,9 +35893,14 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
35727
35893
  }
35728
35894
  if (known.length === 0) continue;
35729
35895
  const inferred = inferSurface(known, { title: spec.title, type: spec.type, body: spec.body });
35896
+ const refusal = uncertainSurfaceRefusal(inferred, `issue create --batch row ${row}`);
35897
+ if (refusal) {
35898
+ errors.push({ row, error: refusal.message });
35899
+ continue;
35900
+ }
35730
35901
  spec.labels = [...spec.labels ?? [], inferred.label];
35731
35902
  process.stderr.write(
35732
- `mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.${surfaceInferenceSuffix(inferred)}
35903
+ `mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
35733
35904
  `
35734
35905
  );
35735
35906
  }
@@ -41028,6 +41199,8 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
41028
41199
  const repo = await requireRepo(repoOption);
41029
41200
  const { client, throttledPaths } = withRateLimitRetry(deps.client ?? defaultGitHubClient(), deps);
41030
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})`;
41031
41204
  const checkContest = deps.checkContest ?? checkLaneContest;
41032
41205
  for (const number of issues) {
41033
41206
  const state = await client.rest("GET", `repos/${repo}/issues/${number}`).then((issue) => issue?.state, () => void 0);
@@ -41045,10 +41218,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
41045
41218
  contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : ""
41046
41219
  ].filter(Boolean).join("; ");
41047
41220
  if (!contest.marker && !contest.unverifiable.includes("comments")) {
41048
- 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}`;
41049
41222
  }
41050
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)`;
41051
- 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}`;
41052
41225
  }
41053
41226
  return void 0;
41054
41227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.16",
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",