@mutmutco/cli 4.1.12 → 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 +276 -28
  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.12",
13156
- tag: "v4.1.12",
13157
- commit: "306701c103b7",
13158
- npm: "@mutmutco/cli@4.1.12"
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.12"
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.12 and redeploy the Hub Lambda from tag v4.1.12 (306701c103b7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
- v3Target: "v4.1.12 (@mutmutco/cli@4.1.12, tag commit 306701c103b7 \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
  {
@@ -15938,9 +15967,13 @@ async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
15938
15967
  return { ...result, checkout, ...localSync ? { localSync } : {} };
15939
15968
  }
15940
15969
  var HUB_REPO3 = "mutmutco/MMI-Hub";
15970
+ var JERV_HUB_REPO = "mutmutco/Jerv-Hub";
15941
15971
  function isHubControlRepo(repo) {
15942
15972
  return repo.toLowerCase() === HUB_REPO3.toLowerCase();
15943
15973
  }
15974
+ function isJervHubRepo(repo) {
15975
+ return repo.toLowerCase() === JERV_HUB_REPO.toLowerCase();
15976
+ }
15944
15977
  function supportsReleaseAbort(_repo, meta, _branchHints) {
15945
15978
  return DEPLOY_MODELS.includes(meta.deployModel);
15946
15979
  }
@@ -16653,6 +16686,42 @@ function appendPublishDispatch(deploy, publish) {
16653
16686
  deployStatus: deploy.deployStatus === "failure" || publish.deployStatus === "failure" ? "failure" : deploy.deployStatus === "pending" || publish.deployStatus === "pending" ? "pending" : "success"
16654
16687
  };
16655
16688
  }
16689
+ async function appendJervGatewayReleaseDeploy(deps, repo, tag, tagSha, dispatch) {
16690
+ if (!isJervHubRepo(repo)) return dispatch;
16691
+ if (dispatch.deployStatus !== "success") {
16692
+ return {
16693
+ ...dispatch,
16694
+ note: `${dispatch.note}; Jerv Gateway deploy deferred until package publication is proven green`
16695
+ };
16696
+ }
16697
+ try {
16698
+ const output = await deps.run("bash", ["scripts/jerv-gateway-release-deploy.sh", "--tag", tag]);
16699
+ const receipt = output.trim().split(/\r?\n/).reverse().map((line) => {
16700
+ try {
16701
+ return JSON.parse(line);
16702
+ } catch {
16703
+ return null;
16704
+ }
16705
+ }).find((value) => value?.schema === "jerv-gateway/release-deploy-v1");
16706
+ if (!receipt || receipt.tag !== tag || receipt.commit !== tagSha || receipt.status !== "healthy") {
16707
+ throw new Error("helper receipt did not match the released tag and commit");
16708
+ }
16709
+ return {
16710
+ ...dispatch,
16711
+ note: `${dispatch.note}; Jerv Gateway deployed ${tagSha.slice(0, 12)} and passed health checks`,
16712
+ workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "jerv-gateway", conclusion: "success" }],
16713
+ deployStatus: "success"
16714
+ };
16715
+ } catch (error) {
16716
+ const detail = (error instanceof Error ? error.message : String(error)).split(/\r?\n/, 1)[0].slice(0, 240);
16717
+ return {
16718
+ ...dispatch,
16719
+ note: `${dispatch.note}; Jerv Gateway deploy FAILED (${detail})`,
16720
+ workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "jerv-gateway", conclusion: "failure" }],
16721
+ deployStatus: "failure"
16722
+ };
16723
+ }
16724
+ }
16656
16725
  async function watchOwnWorkflowRuns(deps, repo, targets, since, headSha, enumerateSha = false) {
16657
16726
  const workflowRuns = [];
16658
16727
  for (const target of targets) {
@@ -17366,6 +17435,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
17366
17435
  const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
17367
17436
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
17368
17437
  let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17438
+ dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
17369
17439
  if (!publishDispatch && deployDispatch.deployStatus !== "success" && meta.publishRequired && (deployModel === "tenant-container" || deployModel === "solo-container")) {
17370
17440
  const reason = deployDispatch.deployStatus === "failure" ? "box deploy failed \u2014 redeploy the box before publishing" : "box deploy not confirmed (run with --watch) \u2014 publish after the box deploy lands";
17371
17441
  dispatch = {
@@ -17504,9 +17574,14 @@ async function runReleaseResume(deps, options = {}) {
17504
17574
  const rcAlignment2 = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
17505
17575
  if (rcAlignment2) steps2.push(`rc alignment: ${rcAlignment2.status}`);
17506
17576
  const historicalDeploy = await deriveHistoricalDeployStatus(deps, ctx, deployModel, tagSha);
17577
+ const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
17578
+ note: `historical ${deployModel} deploy/publish status: ${historicalDeploy}`,
17579
+ deployStatus: historicalDeploy
17580
+ });
17581
+ if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
17507
17582
  const state2 = resumeStateOf(deriveTrainFollowUpStatus({
17508
17583
  projectInfo: "ok",
17509
- deploy: deployFollowUpOutcome(historicalDeploy),
17584
+ deploy: deployFollowUpOutcome(dispatch2.deployStatus),
17510
17585
  rcRetirement: "ok",
17511
17586
  alignment: alignmentFollowUpOutcome(devRollForward2, rcAlignment2),
17512
17587
  foldPort: "ok"
@@ -17519,6 +17594,7 @@ async function runReleaseResume(deps, options = {}) {
17519
17594
  steps: steps2,
17520
17595
  devRollForward: devRollForward2,
17521
17596
  rcAlignment: rcAlignment2,
17597
+ dispatch: dispatch2,
17522
17598
  note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
17523
17599
  };
17524
17600
  }
@@ -17551,7 +17627,8 @@ async function runReleaseResume(deps, options = {}) {
17551
17627
  const autoRunSince = (deps.now ?? Date.now)();
17552
17628
  const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
17553
17629
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
17554
- const dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17630
+ let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17631
+ dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, dispatch);
17555
17632
  steps.push(`dispatched the ${deployModel} deploy path`);
17556
17633
  const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
17557
17634
  steps.push(`development roll-forward: ${devRollForward.status}`);
@@ -20617,13 +20694,13 @@ var CONTENT_LABELS_AND_ASSIGNEES = `
20617
20694
  labels(first: ${ITEM_LABELS_PAGE}) { pageInfo { hasNextPage } nodes { name } }
20618
20695
  assignees(first: ${ITEM_ASSIGNEES_PAGE}) { pageInfo { hasNextPage } nodes { login } }`;
20619
20696
  var PROJECT_ITEMS_QUERY = `
20620
- query($owner: String!, $number: Int!, $after: String) {
20697
+ query($owner: String!, $number: Int!, $after: String, $query: String) {
20621
20698
  viewer { login }
20622
20699
  organization(login: $owner) {
20623
20700
  projectV2(number: $number) {
20624
20701
  id
20625
20702
  title
20626
- items(first: 100, after: $after) {
20703
+ items(first: 100, after: $after, query: $query) {
20627
20704
  pageInfo { hasNextPage endCursor }
20628
20705
  nodes {
20629
20706
  id
@@ -20663,6 +20740,23 @@ query($owner: String!, $number: Int!, $after: String) {
20663
20740
  }
20664
20741
  }
20665
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
+ }
20666
20760
  var SINGLE_ITEM_CONTENT_FRAGMENT = `
20667
20761
  id
20668
20762
  number
@@ -20866,13 +20960,14 @@ function renderBoardReport(report) {
20866
20960
  }
20867
20961
  function shouldSkipInactiveBoardNode(node, cfg) {
20868
20962
  const content = node.content;
20869
- if (content && "state" in content && content.state === "CLOSED") return true;
20963
+ if (content && "state" in content && (content.state === "CLOSED" || content.state === "MERGED")) return true;
20870
20964
  const status = parseSingleSelectFields(node.fieldValues?.nodes, cfg).status?.name;
20871
20965
  return status === "Done";
20872
20966
  }
20873
20967
  async function collectBoardItems(cfg, options, deps) {
20874
20968
  const client = deps.client ?? defaultGitHubClient();
20875
20969
  const currentRepo = await resolveCurrentRepo(options, deps);
20970
+ const itemsQuery = boardItemsSearchFilter(options);
20876
20971
  const warnings = [];
20877
20972
  const nodes = [];
20878
20973
  let viewer = "";
@@ -20883,7 +20978,7 @@ async function collectBoardItems(cfg, options, deps) {
20883
20978
  let pageCount = 0;
20884
20979
  do {
20885
20980
  try {
20886
- const page = await fetchProjectPage(client, cfg, after);
20981
+ const page = await fetchProjectPage(client, cfg, after, itemsQuery);
20887
20982
  viewer ||= page.viewer.login;
20888
20983
  const project2 = page.organization?.projectV2;
20889
20984
  if (!project2) throw new Error(`project ${cfg.projectOwner}#${cfg.projectNumber} not found`);
@@ -21111,7 +21206,7 @@ async function removeRepoBoardItems(options, deps = {}) {
21111
21206
  const cfg = resolveBoardConfig(options.config);
21112
21207
  const client = deps.client ?? defaultGitHubClient();
21113
21208
  const repo = options.repo;
21114
- const collected = await collectBoardItems(cfg, { repo, activeOnly: false }, deps);
21209
+ const collected = await collectBoardItems(cfg, { repo, purpose: { kind: "retire", repo } }, deps);
21115
21210
  const board = { owner: cfg.projectOwner, number: cfg.projectNumber, id: collected.projectId };
21116
21211
  const matches = collected.items.filter((it) => it.repository.toLowerCase() === repo.toLowerCase());
21117
21212
  const removed = [];
@@ -21133,7 +21228,7 @@ async function removeRepoBoardItems(options, deps = {}) {
21133
21228
  async function archiveClosedDoneBoardItems(options, deps = {}) {
21134
21229
  const cfg = resolveBoardConfig(options.config);
21135
21230
  const client = deps.client ?? defaultGitHubClient();
21136
- const collected = await collectBoardItems(cfg, { repo: options.repo, activeOnly: false }, deps);
21231
+ const collected = await collectBoardItems(cfg, { repo: options.repo, purpose: { kind: "archive" } }, deps);
21137
21232
  const candidates = collected.items.filter((item) => item.status === "Done" && item.state === "CLOSED");
21138
21233
  const result = {
21139
21234
  board: { owner: cfg.projectOwner, number: cfg.projectNumber, id: collected.projectId },
@@ -21504,11 +21599,12 @@ async function setBoardPriorityForSelector(options, deps = {}) {
21504
21599
  }
21505
21600
  return { ref: `${options.selector.repo}#${options.selector.number}`, itemId, priority };
21506
21601
  }
21507
- async function fetchProjectPage(client, cfg, after) {
21602
+ async function fetchProjectPage(client, cfg, after, itemsQuery) {
21508
21603
  return client.graphql(PROJECT_ITEMS_QUERY, {
21509
21604
  owner: cfg.projectOwner,
21510
21605
  number: cfg.projectNumber,
21511
- ...after ? { after } : {}
21606
+ ...after ? { after } : {},
21607
+ ...itemsQuery ? { query: itemsQuery } : {}
21512
21608
  });
21513
21609
  }
21514
21610
  function isGraphQLNotFound(e) {
@@ -21991,6 +22087,9 @@ function openPullsFetcher(client) {
21991
22087
  return pulls;
21992
22088
  };
21993
22089
  }
22090
+ async function readClaimLiveness(client, repo, number) {
22091
+ return gatherClaimLiveness(client, repo, number, openPullsFetcher(client));
22092
+ }
21994
22093
  async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
21995
22094
  const out = { failed: [] };
21996
22095
  try {
@@ -22086,7 +22185,7 @@ var DOCTOR_CONCURRENCY = 4;
22086
22185
  async function boardDoctor(options, deps = {}) {
22087
22186
  const cfg = resolveBoardConfig(options.config);
22088
22187
  const client = deps.client ?? defaultGitHubClient();
22089
- const collected = await collectBoardItems(cfg, { repo: options.repo, activeOnly: false }, deps);
22188
+ const collected = await collectBoardItems(cfg, { repo: options.repo, purpose: { kind: "doctor" } }, deps);
22090
22189
  const concurrency = Math.max(1, options.concurrency ?? DOCTOR_CONCURRENCY);
22091
22190
  const findings = [];
22092
22191
  const result = { scanned: collected.items.length, findings: 0, fixed: 0, failed: 0, items: [], liveClaims: [] };
@@ -22381,6 +22480,7 @@ var PRIMARY_GROUPS = [
22381
22480
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
22382
22481
  ];
22383
22482
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
22483
+ var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup"]);
22384
22484
  var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
22385
22485
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
22386
22486
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
@@ -22391,6 +22491,7 @@ for (const [helpGroup, names] of PRIMARY_GROUPS) {
22391
22491
  }
22392
22492
  HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
22393
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++);
22394
22495
  var PATH_OVERRIDES = {
22395
22496
  // #4184: the estate-search door reads as Orient, while rebuild/publish/gc/sync-estate stay
22396
22497
  // Setup and support — that split is the point.
@@ -22439,6 +22540,7 @@ var COMMAND_OWNERSHIP = {
22439
22540
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
22440
22541
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
22441
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" },
22442
22544
  org: { module_owner: "cli/src/command-consolidation.ts", consumer: "org-operator" },
22443
22545
  runtime: { module_owner: "cli/src/command-consolidation.ts", consumer: "runtime-operator" },
22444
22546
  plugin: { module_owner: "cli/src/plugin-guard-io.ts", consumer: "host-runtime" }
@@ -22471,6 +22573,14 @@ function primaryMetadata(name) {
22471
22573
  function topLevelMetadata(name) {
22472
22574
  const primary = primaryMetadata(name);
22473
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
+ }
22474
22584
  if (!OPERATIONAL_TOP_LEVEL.has(name)) {
22475
22585
  throw new Error(`command taxonomy: unclassified top-level command "${name}"`);
22476
22586
  }
@@ -36110,9 +36220,12 @@ function catalogCommandPaths(manifest) {
36110
36220
  return acc;
36111
36221
  }
36112
36222
  function nearestExplainTargets(query, paths, limit = 5) {
36113
- 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]);
36114
36228
  const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
36115
- const leaf = leafOf(query);
36116
36229
  const exactLeaf = all.filter((p) => p !== query && leafOf(p) === leaf).sort();
36117
36230
  if (exactLeaf.length) return exactLeaf.slice(0, limit);
36118
36231
  const out = [];
@@ -36238,13 +36351,15 @@ function registerExplainCommand(program3) {
36238
36351
  const commandPath3 = commandArgs.join(" ");
36239
36352
  const command = findCommandInManifest(manifest, commandPath3);
36240
36353
  if (!command) {
36354
+ const parentPath = commandArgs.length > 1 ? commandArgs.slice(0, -1).join(" ") : void 0;
36355
+ const guide = unknownCommandDomainGuide(parentPath, commandArgs.at(-1));
36241
36356
  const nearest = nearestExplainTargets(commandPath3, catalogCommandPaths(manifest));
36242
36357
  fail(`explain: unknown command "${commandPath3}"`, {
36243
36358
  code: ERROR_CODES.ERR_NOT_FOUND,
36244
36359
  ...nearest.length ? {
36245
36360
  expected: nearest,
36246
- did_you_mean: nearest[0],
36247
- 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`
36248
36363
  } : {}
36249
36364
  });
36250
36365
  return;
@@ -36985,6 +37100,130 @@ function registerSessionReport(program3) {
36985
37100
  });
36986
37101
  }
36987
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
+
36988
37227
  // ../infra/src/fleet-health.ts
36989
37228
  function uniqueRepos(repos) {
36990
37229
  const unique = /* @__PURE__ */ new Map();
@@ -40733,7 +40972,14 @@ function unknownCommandCandidates(parent, allPaths) {
40733
40972
  function resolveParseHint() {
40734
40973
  if (lastParseErrorKind === "unknown-command") {
40735
40974
  const parent = resolveCommandFromArgv(program2, process.argv.slice(2));
40736
- 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
+ );
40737
40983
  const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, candidates) : void 0;
40738
40984
  const canonical = path3 ? canonicalPathFor(path3) ?? path3 : void 0;
40739
40985
  if (canonical) return `(did you mean \`mmi-cli ${canonical}\`? ${DISCOVERY_HINT})`;
@@ -42075,7 +42321,7 @@ function resolveCreateSurface(opts) {
42075
42321
  function surfaceWaived() {
42076
42322
  return rawFlag("--no-surface");
42077
42323
  }
42078
- 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`");
42079
42325
  withExamples(mutating(
42080
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"),
42081
42327
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
@@ -42236,7 +42482,7 @@ async function readParentField(number, repo) {
42236
42482
  }
42237
42483
  return resolveParentField(payload);
42238
42484
  }
42239
- 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) => {
42240
42486
  const n = Number(number);
42241
42487
  if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
42242
42488
  const repo = await resolveRepo(o.repo);
@@ -43155,6 +43401,7 @@ registerExplainCommand(program2);
43155
43401
  registerPrLifecycleCommands(program2);
43156
43402
  registerSessionReport(program2);
43157
43403
  registerLearningClosureRateCommand(program2);
43404
+ registerLearningPickupCommand(program2);
43158
43405
  registerBoardCommands(program2);
43159
43406
  registerStageCommands(program2);
43160
43407
  var GH_TRAIN_TIMEOUT_MS = 3e4;
@@ -43162,10 +43409,11 @@ var GH_RUN_WATCH_TIMEOUT_MS = 20 * 6e4;
43162
43409
  var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
43163
43410
  var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
43164
43411
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
43412
+ var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
43165
43413
  function trainApplyDeps() {
43166
43414
  return {
43167
43415
  run: async (file, args) => {
43168
- const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43416
+ const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && args[0] === "scripts/jerv-gateway-release-deploy.sh" ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43169
43417
  try {
43170
43418
  return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
43171
43419
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.1.12",
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",