@mutmutco/cli 4.5.15 → 4.5.17

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 +134 -13
  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;
@@ -11076,6 +11090,7 @@ var ITEM_FIELD_VALUES_PAGE = 25;
11076
11090
  var ITEM_LABELS_PAGE = 25;
11077
11091
  var ITEM_ASSIGNEES_PAGE = 10;
11078
11092
  var ITEM_PROJECT_ITEMS_PAGE = 50;
11093
+ var LINKED_REPOS_PAGE = 20;
11079
11094
  var CONTENT_LABELS_AND_ASSIGNEES = `
11080
11095
  labels(first: ${ITEM_LABELS_PAGE}) { pageInfo { hasNextPage } nodes { name } }
11081
11096
  assignees(first: ${ITEM_ASSIGNEES_PAGE}) { pageInfo { hasNextPage } nodes { login } }`;
@@ -11086,6 +11101,7 @@ query($owner: String!, $number: Int!, $after: String, $query: String) {
11086
11101
  projectV2(number: $number) {
11087
11102
  id
11088
11103
  title
11104
+ repositories(first: ${LINKED_REPOS_PAGE}) { pageInfo { hasNextPage } nodes { nameWithOwner } }
11089
11105
  items(first: 100, after: $after, query: $query) {
11090
11106
  pageInfo { hasNextPage endCursor }
11091
11107
  nodes {
@@ -11126,6 +11142,44 @@ query($owner: String!, $number: Int!, $after: String, $query: String) {
11126
11142
  }
11127
11143
  }
11128
11144
  }`;
11145
+ var INDEX_LAG_RECOVERY_PAGE = 50;
11146
+ var INDEX_LAG_RECOVERY_ITEMS_PAGE = 10;
11147
+ var INDEX_LAG_RECOVERY_MAX_PAGES = 10;
11148
+ var indexLagRecoveryQuery = (connection) => `
11149
+ query($repoOwner: String!, $repoName: String!, $after: String) {
11150
+ repository(owner: $repoOwner, name: $repoName) {
11151
+ ${connection}(first: ${INDEX_LAG_RECOVERY_PAGE}, after: $after, states: OPEN) {
11152
+ pageInfo { hasNextPage endCursor }
11153
+ nodes {
11154
+ __typename
11155
+ id
11156
+ number
11157
+ title
11158
+ url
11159
+ state
11160
+ updatedAt
11161
+ repository { nameWithOwner }${CONTENT_LABELS_AND_ASSIGNEES}
11162
+ projectItems(first: ${INDEX_LAG_RECOVERY_ITEMS_PAGE}) {
11163
+ pageInfo { hasNextPage }
11164
+ nodes {
11165
+ id
11166
+ project { id }
11167
+ fieldValues(first: ${ITEM_FIELD_VALUES_PAGE}) {
11168
+ pageInfo { hasNextPage }
11169
+ nodes {
11170
+ ... on ProjectV2ItemFieldSingleSelectValue {
11171
+ name
11172
+ optionId
11173
+ field { ... on ProjectV2SingleSelectField { id name } }
11174
+ }
11175
+ }
11176
+ }
11177
+ }
11178
+ }
11179
+ }
11180
+ }
11181
+ }
11182
+ }`;
11129
11183
  var ACTIVE_BOARD_ITEMS_FILTER = "-status:Done -is:closed";
11130
11184
  var DOCTOR_BOARD_ITEMS_FILTER = "-status:Done";
11131
11185
  var ARCHIVE_BOARD_ITEMS_FILTER = "status:Done is:closed";
@@ -11349,6 +11403,52 @@ function shouldSkipInactiveBoardNode(node, cfg) {
11349
11403
  const status = parseSingleSelectFields(node.fieldValues?.nodes, cfg).status?.name;
11350
11404
  return status === "Done";
11351
11405
  }
11406
+ async function recoverIndexLaggedItems(graphql, projectId, linkedRepos, listed, isInactive, warnings) {
11407
+ const known = new Set(listed.map((node) => node.id));
11408
+ const nodes = [];
11409
+ let partial = false;
11410
+ if (linkedRepos?.pageInfo?.hasNextPage) {
11411
+ warnings.push(`partial board read: index-lag recovery covers only the first ${LINKED_REPOS_PAGE} linked repos`);
11412
+ partial = true;
11413
+ }
11414
+ for (const repo of (linkedRepos?.nodes ?? []).map((node) => node.nameWithOwner).filter((name) => Boolean(name))) {
11415
+ const [repoOwner, repoName2] = repo.split("/");
11416
+ let recoveredHere = 0;
11417
+ for (const connection of ["issues", "pullRequests"]) {
11418
+ let after;
11419
+ let pages = 0;
11420
+ try {
11421
+ do {
11422
+ const page = (await graphql(indexLagRecoveryQuery(connection), { repoOwner, repoName: repoName2, ...after ? { after } : {} })).repository?.[connection];
11423
+ for (const content of page?.nodes ?? []) {
11424
+ const { projectItems, ...contentFields } = content;
11425
+ const item = (projectItems?.nodes ?? []).find((entry) => entry.project?.id === projectId);
11426
+ if (!item?.id || known.has(item.id)) continue;
11427
+ const node = { id: item.id, fieldValues: item.fieldValues, content: contentFields };
11428
+ if (isInactive(node)) continue;
11429
+ known.add(item.id);
11430
+ nodes.push(node);
11431
+ recoveredHere += 1;
11432
+ }
11433
+ after = page?.pageInfo?.hasNextPage ? page.pageInfo.endCursor ?? void 0 : void 0;
11434
+ pages += 1;
11435
+ if (after && pages >= INDEX_LAG_RECOVERY_MAX_PAGES) {
11436
+ warnings.push(`partial board read: index-lag recovery for ${repo} ${connection} capped at ${pages * INDEX_LAG_RECOVERY_PAGE} open items`);
11437
+ partial = true;
11438
+ after = void 0;
11439
+ }
11440
+ } while (after);
11441
+ } catch (e) {
11442
+ warnings.push(`partial board read: index-lag recovery for ${repo} ${connection} failed (${e.message})`);
11443
+ partial = true;
11444
+ }
11445
+ }
11446
+ if (recoveredHere) {
11447
+ 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`);
11448
+ }
11449
+ }
11450
+ return { nodes, partial };
11451
+ }
11352
11452
  async function collectBoardItems(cfg, options, deps) {
11353
11453
  const client = deps.client ?? defaultGitHubClient();
11354
11454
  const currentRepo = await resolveCurrentRepo(options, deps);
@@ -11361,6 +11461,7 @@ async function collectBoardItems(cfg, options, deps) {
11361
11461
  let projectId = cfg.projectId;
11362
11462
  let partial = false;
11363
11463
  let pageCount = 0;
11464
+ let linkedRepos;
11364
11465
  do {
11365
11466
  try {
11366
11467
  const page = await fetchProjectPage(client, cfg, after, itemsQuery);
@@ -11369,6 +11470,7 @@ async function collectBoardItems(cfg, options, deps) {
11369
11470
  if (!project2) throw new Error(`project ${cfg.projectOwner}#${cfg.projectNumber} not found`);
11370
11471
  projectId = project2.id;
11371
11472
  projectTitle ||= project2.title;
11473
+ linkedRepos ??= project2.repositories;
11372
11474
  pageCount += 1;
11373
11475
  const pageNodes = project2.items.nodes ?? [];
11374
11476
  if (options.activeOnly) {
@@ -11410,6 +11512,18 @@ async function collectBoardItems(cfg, options, deps) {
11410
11512
  partial = true;
11411
11513
  }
11412
11514
  }
11515
+ if (options.activeOnly) {
11516
+ const recovered = await recoverIndexLaggedItems(
11517
+ (query, variables) => client.graphql(query, variables),
11518
+ projectId,
11519
+ linkedRepos,
11520
+ nodes,
11521
+ (node) => shouldSkipInactiveBoardNode(node, cfg),
11522
+ warnings
11523
+ );
11524
+ nodes.push(...recovered.nodes);
11525
+ partial ||= recovered.partial;
11526
+ }
11413
11527
  const read = nodesToItems(nodes, warnings, cfg);
11414
11528
  if (read.failures.length) {
11415
11529
  const message2 = `partial board read: ${read.failures.length} item(s) came back truncated and were not read \u2014 ${read.failures.join(" | ")}`;
@@ -16001,10 +16115,10 @@ var rollout_plan_default = {
16001
16115
  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
16116
  },
16003
16117
  baseline: {
16004
- version: "4.5.15",
16005
- tag: "v4.5.15",
16006
- commit: "fa2cf924d900",
16007
- npm: "@mutmutco/cli@4.5.15"
16118
+ version: "4.5.17",
16119
+ tag: "v4.5.17",
16120
+ commit: "be54d6077479",
16121
+ npm: "@mutmutco/cli@4.5.17"
16008
16122
  },
16009
16123
  exitCriterion: "fleet-n-of-n",
16010
16124
  hubOnlyShortcut: "forbidden",
@@ -16021,14 +16135,14 @@ var rollout_plan_default = {
16021
16135
  repo: "mutmutco/mmi-hub",
16022
16136
  role: "canary",
16023
16137
  schedule: "train",
16024
- v3Target: "v4.5.15"
16138
+ v3Target: "v4.5.17"
16025
16139
  }
16026
16140
  ],
16027
16141
  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
16142
  rollback: {
16029
16143
  independent: true,
16030
- mechanism: "npm dist-tag latest -> 4.5.15 and redeploy the Hub Lambda from tag v4.5.15 (fa2cf924d900); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16031
- v3Target: "v4.5.15 (@mutmutco/cli@4.5.15, tag commit fa2cf924d900 \u2014 last known-good release carrying the repo-index v4-only contract)"
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)"
16032
16146
  }
16033
16147
  },
16034
16148
  {
@@ -35691,10 +35805,12 @@ function validateBatchSpecs(specs) {
35691
35805
  }
35692
35806
  return { ok: errors.length === 0, errors, validated };
35693
35807
  }
35694
- async function preflightBatchSurfaces(validated, rowRepo, options) {
35808
+ async function preflightBatchSurfaces(validated, rowRepo, options, deps = {}) {
35809
+ const surfaceApplies = deps.surfaceLabelApplies ?? surfaceLabelApplies;
35810
+ const readLabels = deps.readRepoSurfaceLabels ?? readRepoSurfaceLabels;
35695
35811
  const applies = /* @__PURE__ */ new Map();
35696
35812
  const appliesTo = async (repo) => {
35697
- if (!applies.has(repo)) applies.set(repo, await surfaceLabelApplies(repo));
35813
+ if (!applies.has(repo)) applies.set(repo, await surfaceApplies(repo));
35698
35814
  return applies.get(repo);
35699
35815
  };
35700
35816
  if (options.surface) {
@@ -35709,7 +35825,7 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
35709
35825
  const censusFor = (repo) => {
35710
35826
  let pending = censusCache.get(repo);
35711
35827
  if (!pending) {
35712
- pending = readRepoSurfaceLabels(repo);
35828
+ pending = readLabels(repo);
35713
35829
  censusCache.set(repo, pending);
35714
35830
  }
35715
35831
  return pending;
@@ -35727,9 +35843,14 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
35727
35843
  }
35728
35844
  if (known.length === 0) continue;
35729
35845
  const inferred = inferSurface(known, { title: spec.title, type: spec.type, body: spec.body });
35846
+ const refusal = uncertainSurfaceRefusal(inferred, `issue create --batch row ${row}`);
35847
+ if (refusal) {
35848
+ errors.push({ row, error: refusal.message });
35849
+ continue;
35850
+ }
35730
35851
  spec.labels = [...spec.labels ?? [], inferred.label];
35731
35852
  process.stderr.write(
35732
- `mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.${surfaceInferenceSuffix(inferred)}
35853
+ `mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
35733
35854
  `
35734
35855
  );
35735
35856
  }
@@ -42229,7 +42350,7 @@ function registerCollaborationCommands(program3) {
42229
42350
  }
42230
42351
  const issue = program3.command("issue").description("issues \u2014 create and view with structured JSON (view; show, get and read alias view for board-verb/gh-shaped callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim` or the flat `board claim`");
42231
42352
  withExamples(mutating(
42232
- issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Any value satisfies the one-surface-label board rule, so this is not a closed enum. Omit it on an enforcing repo and one is inferred from the title/body (#1164) \u2014 the pick is printed and included in --json as surface_inferred/surface/surface_reason").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface). Suppresses inference too").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
42353
+ issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 create tmp/issue.md with a file-write tool (create tmp/ first if needed), then pass --body-file tmp/issue.md; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Any value satisfies the one-surface-label board rule, so this is not a closed enum. Omit it on an enforcing repo and one is inferred from the title/body (#1164) \u2014 the pick is printed and included in --json as surface_inferred/surface/surface_reason").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface). Suppresses inference too").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
42233
42354
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
42234
42355
  // and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
42235
42356
  // is not a plan of the mutation that will run (#3914).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.15",
3
+ "version": "4.5.17",
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",