@mutmutco/cli 4.1.13 → 4.1.14

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 +224 -25
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -803,6 +803,8 @@ var init_house_map = __esm({
803
803
  "skill-lesson": "learning",
804
804
  "closure-rate": "learning",
805
805
  // closure rate per loop kind, computed at read (#4440)
806
+ pickup: "learning",
807
+ // cloud-agent adoption gate (#5708)
806
808
  // --- core — the front door itself ---------------------------------------------------------------
807
809
  commands: "core",
808
810
  whoami: "core",
@@ -5978,7 +5980,7 @@ init_clean_exit();
5978
5980
  init_error_codes();
5979
5981
 
5980
5982
  // src/parse-error-hints.ts
5981
- var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project", "--issue"]);
5983
+ var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project", "--issue", "--number"]);
5982
5984
  function targetFlagValueFromArgv(flag, argv) {
5983
5985
  const i = argv.indexOf(flag);
5984
5986
  if (i < 0) return void 0;
@@ -6000,6 +6002,33 @@ function unknownTargetFlagMessage(flag, positional) {
6000
6002
  return `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}`;
6001
6003
  }
6002
6004
 
6005
+ // src/unknown-command-domain-hints.ts
6006
+ var GUIDES = /* @__PURE__ */ new Map([
6007
+ [
6008
+ "oracle issue:search",
6009
+ {
6010
+ hint: "no `oracle issue search` \u2014 friction reports dedup server-side via `mmi-cli learning report`; new issues accept `--idempotency-key` on `oracle issue create`; bounded label/state filters use `oracle issue list`",
6011
+ expected: ["learning report", "oracle issue create", "oracle issue list"],
6012
+ did_you_mean: "learning report"
6013
+ }
6014
+ ]
6015
+ ]);
6016
+ var BLOCKED_SUGGESTIONS = /* @__PURE__ */ new Map([
6017
+ ["oracle issue:search", ["oracle repo-index search"]]
6018
+ ]);
6019
+ function unknownCommandDomainGuide(parentPath, token) {
6020
+ if (!parentPath || !token) return void 0;
6021
+ return GUIDES.get(`${parentPath}:${token}`);
6022
+ }
6023
+ function blockedUnknownCommandSuggestions(parentPath, token) {
6024
+ if (!parentPath || !token) return [];
6025
+ return BLOCKED_SUGGESTIONS.get(`${parentPath}:${token}`) ?? [];
6026
+ }
6027
+ function filterUnknownCommandCandidates(parentPath, token, candidates) {
6028
+ const blocked = new Set(blockedUnknownCommandSuggestions(parentPath, token));
6029
+ return blocked.size ? candidates.filter((p) => !blocked.has(p)) : [...candidates];
6030
+ }
6031
+
6003
6032
  // src/session-start.ts
6004
6033
  var import_node_fs8 = require("node:fs");
6005
6034
  var import_node_path6 = require("node:path");
@@ -6874,7 +6903,7 @@ var COMMAND_LADDER_WRITES = [
6874
6903
  ];
6875
6904
  var COMMAND_LADDER_READS = [
6876
6905
  // #5483: `show` aliases `view` for board-verb callers; advertise the canonical read plus the alias.
6877
- { gh: "gh issue view <N>", replacement: "mmi-cli oracle issue view <N> (alias: show)" },
6906
+ { gh: "gh issue view <N>", replacement: "mmi-cli oracle issue view <N> (aliases: show, get)" },
6878
6907
  { gh: "gh pr view <N>", replacement: "mmi-cli devops pr view <N>" },
6879
6908
  { gh: "gh pr checks <N>", replacement: "mmi-cli devops pr checks-wait <N>" }
6880
6909
  ];
@@ -13152,10 +13181,10 @@ var rollout_plan_default = {
13152
13181
  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)."
13153
13182
  },
13154
13183
  baseline: {
13155
- version: "4.1.13",
13156
- tag: "v4.1.13",
13157
- commit: "91a8a172eda6",
13158
- npm: "@mutmutco/cli@4.1.13"
13184
+ version: "4.1.14",
13185
+ tag: "v4.1.14",
13186
+ commit: "e91bb87d9ce0",
13187
+ npm: "@mutmutco/cli@4.1.14"
13159
13188
  },
13160
13189
  exitCriterion: "fleet-n-of-n",
13161
13190
  hubOnlyShortcut: "forbidden",
@@ -13172,14 +13201,14 @@ var rollout_plan_default = {
13172
13201
  repo: "mutmutco/mmi-hub",
13173
13202
  role: "canary",
13174
13203
  schedule: "train",
13175
- v3Target: "v4.1.13"
13204
+ v3Target: "v4.1.14"
13176
13205
  }
13177
13206
  ],
13178
13207
  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.",
13179
13208
  rollback: {
13180
13209
  independent: true,
13181
- mechanism: "npm dist-tag latest -> 4.1.13 and redeploy the Hub Lambda from tag v4.1.13 (91a8a172eda6); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
- v3Target: "v4.1.13 (@mutmutco/cli@4.1.13, tag commit 91a8a172eda6 \u2014 last known-good release carrying the repo-index v4-only contract)"
13210
+ mechanism: "npm dist-tag latest -> 4.1.14 and redeploy the Hub Lambda from tag v4.1.14 (e91bb87d9ce0); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13211
+ v3Target: "v4.1.14 (@mutmutco/cli@4.1.14, tag commit e91bb87d9ce0 \u2014 last known-good release carrying the repo-index v4-only contract)"
13183
13212
  }
13184
13213
  },
13185
13214
  {
@@ -20665,13 +20694,13 @@ var CONTENT_LABELS_AND_ASSIGNEES = `
20665
20694
  labels(first: ${ITEM_LABELS_PAGE}) { pageInfo { hasNextPage } nodes { name } }
20666
20695
  assignees(first: ${ITEM_ASSIGNEES_PAGE}) { pageInfo { hasNextPage } nodes { login } }`;
20667
20696
  var PROJECT_ITEMS_QUERY = `
20668
- query($owner: String!, $number: Int!, $after: String) {
20697
+ query($owner: String!, $number: Int!, $after: String, $query: String) {
20669
20698
  viewer { login }
20670
20699
  organization(login: $owner) {
20671
20700
  projectV2(number: $number) {
20672
20701
  id
20673
20702
  title
20674
- items(first: 100, after: $after) {
20703
+ items(first: 100, after: $after, query: $query) {
20675
20704
  pageInfo { hasNextPage endCursor }
20676
20705
  nodes {
20677
20706
  id
@@ -20711,6 +20740,23 @@ query($owner: String!, $number: Int!, $after: String) {
20711
20740
  }
20712
20741
  }
20713
20742
  }`;
20743
+ var ACTIVE_BOARD_ITEMS_FILTER = "-status:Done -is:closed";
20744
+ var DOCTOR_BOARD_ITEMS_FILTER = "-status:Done";
20745
+ var ARCHIVE_BOARD_ITEMS_FILTER = "status:Done is:closed";
20746
+ var retireBoardItemsFilter = (repo) => `repo:"${repo}"`;
20747
+ function boardItemsSearchFilter(options) {
20748
+ if (options.activeOnly) return ACTIVE_BOARD_ITEMS_FILTER;
20749
+ switch (options.purpose?.kind) {
20750
+ case "doctor":
20751
+ return DOCTOR_BOARD_ITEMS_FILTER;
20752
+ case "archive":
20753
+ return ARCHIVE_BOARD_ITEMS_FILTER;
20754
+ case "retire":
20755
+ return retireBoardItemsFilter(options.purpose.repo);
20756
+ default:
20757
+ return void 0;
20758
+ }
20759
+ }
20714
20760
  var SINGLE_ITEM_CONTENT_FRAGMENT = `
20715
20761
  id
20716
20762
  number
@@ -20914,13 +20960,14 @@ function renderBoardReport(report) {
20914
20960
  }
20915
20961
  function shouldSkipInactiveBoardNode(node, cfg) {
20916
20962
  const content = node.content;
20917
- if (content && "state" in content && content.state === "CLOSED") return true;
20963
+ if (content && "state" in content && (content.state === "CLOSED" || content.state === "MERGED")) return true;
20918
20964
  const status = parseSingleSelectFields(node.fieldValues?.nodes, cfg).status?.name;
20919
20965
  return status === "Done";
20920
20966
  }
20921
20967
  async function collectBoardItems(cfg, options, deps) {
20922
20968
  const client = deps.client ?? defaultGitHubClient();
20923
20969
  const currentRepo = await resolveCurrentRepo(options, deps);
20970
+ const itemsQuery = boardItemsSearchFilter(options);
20924
20971
  const warnings = [];
20925
20972
  const nodes = [];
20926
20973
  let viewer = "";
@@ -20931,7 +20978,7 @@ async function collectBoardItems(cfg, options, deps) {
20931
20978
  let pageCount = 0;
20932
20979
  do {
20933
20980
  try {
20934
- const page = await fetchProjectPage(client, cfg, after);
20981
+ const page = await fetchProjectPage(client, cfg, after, itemsQuery);
20935
20982
  viewer ||= page.viewer.login;
20936
20983
  const project2 = page.organization?.projectV2;
20937
20984
  if (!project2) throw new Error(`project ${cfg.projectOwner}#${cfg.projectNumber} not found`);
@@ -21159,7 +21206,7 @@ async function removeRepoBoardItems(options, deps = {}) {
21159
21206
  const cfg = resolveBoardConfig(options.config);
21160
21207
  const client = deps.client ?? defaultGitHubClient();
21161
21208
  const repo = options.repo;
21162
- const collected = await collectBoardItems(cfg, { repo, activeOnly: false }, deps);
21209
+ const collected = await collectBoardItems(cfg, { repo, purpose: { kind: "retire", repo } }, deps);
21163
21210
  const board = { owner: cfg.projectOwner, number: cfg.projectNumber, id: collected.projectId };
21164
21211
  const matches = collected.items.filter((it) => it.repository.toLowerCase() === repo.toLowerCase());
21165
21212
  const removed = [];
@@ -21181,7 +21228,7 @@ async function removeRepoBoardItems(options, deps = {}) {
21181
21228
  async function archiveClosedDoneBoardItems(options, deps = {}) {
21182
21229
  const cfg = resolveBoardConfig(options.config);
21183
21230
  const client = deps.client ?? defaultGitHubClient();
21184
- const collected = await collectBoardItems(cfg, { repo: options.repo, activeOnly: false }, deps);
21231
+ const collected = await collectBoardItems(cfg, { repo: options.repo, purpose: { kind: "archive" } }, deps);
21185
21232
  const candidates = collected.items.filter((item) => item.status === "Done" && item.state === "CLOSED");
21186
21233
  const result = {
21187
21234
  board: { owner: cfg.projectOwner, number: cfg.projectNumber, id: collected.projectId },
@@ -21552,11 +21599,12 @@ async function setBoardPriorityForSelector(options, deps = {}) {
21552
21599
  }
21553
21600
  return { ref: `${options.selector.repo}#${options.selector.number}`, itemId, priority };
21554
21601
  }
21555
- async function fetchProjectPage(client, cfg, after) {
21602
+ async function fetchProjectPage(client, cfg, after, itemsQuery) {
21556
21603
  return client.graphql(PROJECT_ITEMS_QUERY, {
21557
21604
  owner: cfg.projectOwner,
21558
21605
  number: cfg.projectNumber,
21559
- ...after ? { after } : {}
21606
+ ...after ? { after } : {},
21607
+ ...itemsQuery ? { query: itemsQuery } : {}
21560
21608
  });
21561
21609
  }
21562
21610
  function isGraphQLNotFound(e) {
@@ -22039,6 +22087,9 @@ function openPullsFetcher(client) {
22039
22087
  return pulls;
22040
22088
  };
22041
22089
  }
22090
+ async function readClaimLiveness(client, repo, number) {
22091
+ return gatherClaimLiveness(client, repo, number, openPullsFetcher(client));
22092
+ }
22042
22093
  async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
22043
22094
  const out = { failed: [] };
22044
22095
  try {
@@ -22134,7 +22185,7 @@ var DOCTOR_CONCURRENCY = 4;
22134
22185
  async function boardDoctor(options, deps = {}) {
22135
22186
  const cfg = resolveBoardConfig(options.config);
22136
22187
  const client = deps.client ?? defaultGitHubClient();
22137
- const collected = await collectBoardItems(cfg, { repo: options.repo, activeOnly: false }, deps);
22188
+ const collected = await collectBoardItems(cfg, { repo: options.repo, purpose: { kind: "doctor" } }, deps);
22138
22189
  const concurrency = Math.max(1, options.concurrency ?? DOCTOR_CONCURRENCY);
22139
22190
  const findings = [];
22140
22191
  const result = { scanned: collected.items.length, findings: 0, fixed: 0, failed: 0, items: [], liveClaims: [] };
@@ -22429,6 +22480,7 @@ var PRIMARY_GROUPS = [
22429
22480
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
22430
22481
  ];
22431
22482
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
22483
+ var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup"]);
22432
22484
  var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
22433
22485
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
22434
22486
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
@@ -22439,6 +22491,7 @@ for (const [helpGroup, names] of PRIMARY_GROUPS) {
22439
22491
  }
22440
22492
  HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
22441
22493
  for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
22494
+ for (const name of CAMPAIGN_ORCHESTRATOR_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
22442
22495
  var PATH_OVERRIDES = {
22443
22496
  // #4184: the estate-search door reads as Orient, while rebuild/publish/gc/sync-estate stay
22444
22497
  // Setup and support — that split is the point.
@@ -22487,6 +22540,7 @@ var COMMAND_OWNERSHIP = {
22487
22540
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
22488
22541
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
22489
22542
  "closure-rate": { module_owner: "cli/src/learning-closure-rate.ts", consumer: "campaign-orchestrator" },
22543
+ pickup: { module_owner: "cli/src/learning-pickup.ts", consumer: "campaign-orchestrator" },
22490
22544
  org: { module_owner: "cli/src/command-consolidation.ts", consumer: "org-operator" },
22491
22545
  runtime: { module_owner: "cli/src/command-consolidation.ts", consumer: "runtime-operator" },
22492
22546
  plugin: { module_owner: "cli/src/plugin-guard-io.ts", consumer: "host-runtime" }
@@ -22519,6 +22573,14 @@ function primaryMetadata(name) {
22519
22573
  function topLevelMetadata(name) {
22520
22574
  const primary = primaryMetadata(name);
22521
22575
  if (primary) return primary;
22576
+ if (CAMPAIGN_ORCHESTRATOR_TOP_LEVEL.has(name)) {
22577
+ return {
22578
+ category: "support",
22579
+ discovery: "all-only",
22580
+ help_group: "Coordinate and improve",
22581
+ ...commandOwnership(name)
22582
+ };
22583
+ }
22522
22584
  if (!OPERATIONAL_TOP_LEVEL.has(name)) {
22523
22585
  throw new Error(`command taxonomy: unclassified top-level command "${name}"`);
22524
22586
  }
@@ -36158,9 +36220,12 @@ function catalogCommandPaths(manifest) {
36158
36220
  return acc;
36159
36221
  }
36160
36222
  function nearestExplainTargets(query, paths, limit = 5) {
36161
- const all = [...paths];
36223
+ const parentPath = query.includes(" ") ? query.slice(0, query.lastIndexOf(" ")) : void 0;
36224
+ const leaf = query.includes(" ") ? query.slice(query.lastIndexOf(" ") + 1) : query;
36225
+ const guide = unknownCommandDomainGuide(parentPath, leaf);
36226
+ if (guide) return guide.expected.slice(0, limit);
36227
+ const all = filterUnknownCommandCandidates(parentPath, leaf, [...paths]);
36162
36228
  const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
36163
- const leaf = leafOf(query);
36164
36229
  const exactLeaf = all.filter((p) => p !== query && leafOf(p) === leaf).sort();
36165
36230
  if (exactLeaf.length) return exactLeaf.slice(0, limit);
36166
36231
  const out = [];
@@ -36286,13 +36351,15 @@ function registerExplainCommand(program3) {
36286
36351
  const commandPath3 = commandArgs.join(" ");
36287
36352
  const command = findCommandInManifest(manifest, commandPath3);
36288
36353
  if (!command) {
36354
+ const parentPath = commandArgs.length > 1 ? commandArgs.slice(0, -1).join(" ") : void 0;
36355
+ const guide = unknownCommandDomainGuide(parentPath, commandArgs.at(-1));
36289
36356
  const nearest = nearestExplainTargets(commandPath3, catalogCommandPaths(manifest));
36290
36357
  fail(`explain: unknown command "${commandPath3}"`, {
36291
36358
  code: ERROR_CODES.ERR_NOT_FOUND,
36292
36359
  ...nearest.length ? {
36293
36360
  expected: nearest,
36294
- did_you_mean: nearest[0],
36295
- corrected_command: `mmi-cli explain ${nearest[0]} --json`
36361
+ did_you_mean: guide?.did_you_mean ?? nearest[0],
36362
+ corrected_command: `mmi-cli explain ${guide?.did_you_mean ?? nearest[0]} --json`
36296
36363
  } : {}
36297
36364
  });
36298
36365
  return;
@@ -37033,6 +37100,130 @@ function registerSessionReport(program3) {
37033
37100
  });
37034
37101
  }
37035
37102
 
37103
+ // src/learning-pickup.ts
37104
+ init_cli_shared();
37105
+ init_github_client();
37106
+ var LEARNING_PICKUP_CMD = ["mmi-cli", "learning", "pickup"].join(" ");
37107
+ var LEARNING_CLAIM_MARKER_LIVE_MS = 24 * 60 * 6e4;
37108
+ function isLearningChannelItem(labels) {
37109
+ return labels.includes(LEARNING_LABEL) || loopKindOf(labels) !== void 0;
37110
+ }
37111
+ function claimEvidenceBlocksLearningAdoption(evidence) {
37112
+ if (evidence.failed.includes("comments")) return true;
37113
+ if (!evidence.marker) return false;
37114
+ if (evidence.sessionState === "live") return true;
37115
+ if (evidence.openPr || evidence.branch) return true;
37116
+ if (evidence.failed.length > 0) return true;
37117
+ if (evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < LEARNING_CLAIM_MARKER_LIVE_MS) {
37118
+ return true;
37119
+ }
37120
+ return false;
37121
+ }
37122
+ function evaluateLearningAdoption(input) {
37123
+ if (!isLearningChannelItem(input.labels)) {
37124
+ return { adoptable: false, learning: false, reason: "issue is not in the learning channel" };
37125
+ }
37126
+ if (String(input.state).toUpperCase() === "CLOSED") {
37127
+ return { adoptable: false, learning: true, reason: "issue is closed" };
37128
+ }
37129
+ if (input.status === "In Progress" || input.status === "In Review") {
37130
+ const owners = input.assignees.length ? [...input.assignees] : void 0;
37131
+ const ownerPhrase = owners?.length ? `@${owners.join(", @")}` : "an active board owner";
37132
+ return {
37133
+ adoptable: false,
37134
+ learning: true,
37135
+ reason: `${input.status} \u2014 held by ${ownerPhrase}`,
37136
+ ...owners ? { owners } : {}
37137
+ };
37138
+ }
37139
+ if (input.assignees.length > 0) {
37140
+ const owners = [...input.assignees];
37141
+ return {
37142
+ adoptable: false,
37143
+ learning: true,
37144
+ reason: `assigned to @${owners.join(", @")}`,
37145
+ owners
37146
+ };
37147
+ }
37148
+ if (input.claim && claimEvidenceBlocksLearningAdoption(input.claim)) {
37149
+ const claimHolder = input.claim.marker ? describeClaimMarker(input.claim.marker) : void 0;
37150
+ return {
37151
+ adoptable: false,
37152
+ learning: true,
37153
+ reason: claimHolder ? `live claim marker from ${claimHolder}` : "claim marker evidence is unreadable \u2014 deferring adoption",
37154
+ ...claimHolder ? { claimHolder } : {},
37155
+ ...input.claim.marker ? { claimMarker: input.claim.marker } : {}
37156
+ };
37157
+ }
37158
+ return { adoptable: true, learning: true };
37159
+ }
37160
+ async function probeLearningAdoption(deps, selector) {
37161
+ const cfg = await deps.loadBoardConfig();
37162
+ if (!cfg?.sagaApiUrl) {
37163
+ throw new Error("learning pickup: Hub API URL not configured \u2014 board read required for adoption probe");
37164
+ }
37165
+ const item = await showBoardItem(
37166
+ { config: cfg, selector: String(selector.number), repo: selector.repo },
37167
+ { client: deps.client }
37168
+ );
37169
+ const claim = await readClaimLiveness(deps.client, item.repository, item.number);
37170
+ const verdict = evaluateLearningAdoption({
37171
+ labels: item.labels,
37172
+ state: item.state,
37173
+ status: item.status,
37174
+ assignees: item.assignees,
37175
+ claim
37176
+ });
37177
+ return { ...verdict, repo: item.repository, number: item.number, ref: item.ref };
37178
+ }
37179
+ function registerLearningPickupCommand(program3) {
37180
+ withExamples(
37181
+ program3.command("pickup").description(
37182
+ "probe whether a learning-channel issue is free for cloud-agent adoption \u2014 refuses when a live claim marker or assigned In Progress owner already holds it (#5708)"
37183
+ ).option("--repo <owner/repo>", "repo holding the issue").option("--number <n>", "issue number").argument("[ref]", "issue ref (#N or owner/repo#N)").option("--json", "print the structured adoption verdict JSON").action(async (ref, o) => {
37184
+ const defaultRepo = await resolveRepo(o.repo);
37185
+ if (!defaultRepo) return fail("learning pickup: could not resolve repo \u2014 pass --repo owner/repo");
37186
+ let selector;
37187
+ try {
37188
+ if (ref) {
37189
+ selector = parseIssueSelector(ref, defaultRepo);
37190
+ } else if (o.number) {
37191
+ selector = { repo: defaultRepo, number: Number.parseInt(o.number, 10) };
37192
+ if (!Number.isFinite(selector.number)) throw new Error("invalid --number");
37193
+ } else {
37194
+ throw new Error("pass an issue ref or --number");
37195
+ }
37196
+ } catch (e) {
37197
+ return fail(`learning pickup: ${e.message}`);
37198
+ }
37199
+ try {
37200
+ const verdict = await probeLearningAdoption(
37201
+ {
37202
+ client: defaultGitHubClient(),
37203
+ loadBoardConfig: async () => loadConfigOrDiscover()
37204
+ },
37205
+ selector
37206
+ );
37207
+ if (o.json) console.log(JSON.stringify(verdict, null, 2));
37208
+ else if (verdict.adoptable) {
37209
+ console.log(`${verdict.ref} is adoptable for learning automation`);
37210
+ } else {
37211
+ const owner = "owners" in verdict && verdict.owners?.length ? ` (owner: @${verdict.owners.join(", @")})` : "claimHolder" in verdict && verdict.claimHolder ? ` (claim: ${verdict.claimHolder})` : "";
37212
+ console.log(`${verdict.ref} is not adoptable \u2014 ${verdict.reason}${owner}`);
37213
+ }
37214
+ if (!verdict.adoptable) process.exitCode = 2;
37215
+ } catch (e) {
37216
+ return fail(`learning pickup: ${e.message}`);
37217
+ }
37218
+ }),
37219
+ [
37220
+ LEARNING_PICKUP_CMD + " mutmutco/Jerv-JervCode#2395 --json",
37221
+ LEARNING_PICKUP_CMD + " --repo mutmutco/MMI-Hub --number 42"
37222
+ ],
37223
+ "exit code 2 means defer or refuse \u2014 the learning lane must not implement the issue"
37224
+ );
37225
+ }
37226
+
37036
37227
  // ../infra/src/fleet-health.ts
37037
37228
  function uniqueRepos(repos) {
37038
37229
  const unique = /* @__PURE__ */ new Map();
@@ -40781,7 +40972,14 @@ function unknownCommandCandidates(parent, allPaths) {
40781
40972
  function resolveParseHint() {
40782
40973
  if (lastParseErrorKind === "unknown-command") {
40783
40974
  const parent = resolveCommandFromArgv(program2, process.argv.slice(2));
40784
- const candidates = unknownCommandCandidates(parent, allCommandPaths());
40975
+ const parentPath = parent ? canonicalPathFor(commandPath2(parent)) ?? commandPath2(parent) : void 0;
40976
+ const guide = unknownCommandDomainGuide(parentPath, lastUnknownCommand);
40977
+ if (guide) return `(${guide.hint}. ${DISCOVERY_HINT})`;
40978
+ const candidates = filterUnknownCommandCandidates(
40979
+ parentPath,
40980
+ lastUnknownCommand,
40981
+ unknownCommandCandidates(parent, allCommandPaths())
40982
+ );
40785
40983
  const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, candidates) : void 0;
40786
40984
  const canonical = path3 ? canonicalPathFor(path3) ?? path3 : void 0;
40787
40985
  if (canonical) return `(did you mean \`mmi-cli ${canonical}\`? ${DISCOVERY_HINT})`;
@@ -42123,7 +42321,7 @@ function resolveCreateSurface(opts) {
42123
42321
  function surfaceWaived() {
42124
42322
  return rawFlag("--no-surface");
42125
42323
  }
42126
- var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim`");
42324
+ var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show and get alias view for board-verb/gh-shaped callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim`");
42127
42325
  withExamples(mutating(
42128
42326
  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). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").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)").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"),
42129
42327
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
@@ -42284,7 +42482,7 @@ async function readParentField(number, repo) {
42284
42482
  }
42285
42483
  return resolveParentField(payload);
42286
42484
  }
42287
- issue.command("view <number>").alias("show").description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show` is an alias (#5483)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
42485
+ issue.command("view <number>").aliases(["show", "get"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show` and `get` alias view (#5483/#5706)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
42288
42486
  const n = Number(number);
42289
42487
  if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
42290
42488
  const repo = await resolveRepo(o.repo);
@@ -43203,6 +43401,7 @@ registerExplainCommand(program2);
43203
43401
  registerPrLifecycleCommands(program2);
43204
43402
  registerSessionReport(program2);
43205
43403
  registerLearningClosureRateCommand(program2);
43404
+ registerLearningPickupCommand(program2);
43206
43405
  registerBoardCommands(program2);
43207
43406
  registerStageCommands(program2);
43208
43407
  var GH_TRAIN_TIMEOUT_MS = 3e4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.1.13",
3
+ "version": "4.1.14",
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",