@mutmutco/cli 3.92.0 → 3.94.0

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 +98 -49
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -4304,6 +4304,24 @@ async function surfaceLabelApplies(repo, deps = {}) {
4304
4304
  return (known?.length ?? 0) > 0;
4305
4305
  }
4306
4306
 
4307
+ // src/parse-error-hints.ts
4308
+ var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project", "--issue"]);
4309
+ function targetFlagValueFromArgv(flag, argv) {
4310
+ const i = argv.indexOf(flag);
4311
+ if (i < 0) return void 0;
4312
+ const next = argv[i + 1];
4313
+ if (!next || next.startsWith("-")) return void 0;
4314
+ return next;
4315
+ }
4316
+ function formatPositionalTarget(commandPath3, argName, opts = {}) {
4317
+ const concrete = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
4318
+ if (concrete) return `mmi-cli ${commandPath3} ${concrete}`;
4319
+ return `mmi-cli ${commandPath3} <${argName}>`;
4320
+ }
4321
+ function unknownTargetFlagMessage(flag, positional) {
4322
+ return `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}`;
4323
+ }
4324
+
4307
4325
  // src/session-start.ts
4308
4326
  var import_node_fs8 = require("node:fs");
4309
4327
  var import_node_path6 = require("node:path");
@@ -11688,6 +11706,29 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
11688
11706
  function sameContexts(left, right) {
11689
11707
  return left.length === right.length && left.every((context, index) => context === right[index]);
11690
11708
  }
11709
+ function resolveProductRulesetReconcilePlan(input) {
11710
+ const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
11711
+ const liveIsActive = input.liveEnforcement === "active";
11712
+ if (liveIsActive && !liveNeedsContextConvergence) {
11713
+ return { shouldActivate: false, targetEnforcement: "active" };
11714
+ }
11715
+ if (input.unsafeContexts.length > 0) {
11716
+ const unsafe = [...input.unsafeContexts];
11717
+ return {
11718
+ shouldActivate: false,
11719
+ targetEnforcement: "disabled",
11720
+ holdReason: `product ruleset left non-enforcing \u2014 [${unsafe.join(", ")}] ${unsafe.length === 1 ? "is" : "are"} emitted ONLY by path-filtered workflow(s), so the context is not reported on a PR outside those paths and requiring it would block that PR forever (#3836). Give the gate a companion job that runs unconditionally, then re-run this command`
11721
+ };
11722
+ }
11723
+ if (!input.gateProvenGreen) {
11724
+ return {
11725
+ shouldActivate: false,
11726
+ targetEnforcement: "disabled",
11727
+ holdReason: "product ruleset left non-enforcing \u2014 the gate is not green on development; activating it would block every PR (#3694)"
11728
+ };
11729
+ }
11730
+ return { shouldActivate: true, targetEnforcement: "active" };
11731
+ }
11691
11732
  async function contentExists(deps, repo, branch, path2) {
11692
11733
  try {
11693
11734
  const encodedPath = path2.split("/").map(encodeURIComponent).join("/");
@@ -12391,46 +12432,39 @@ async function applyCiReconcileRepo(repo, deps) {
12391
12432
  return finalizeCiReconcile(repo, deps, result, report);
12392
12433
  }
12393
12434
  const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
12394
- const liveNeedsConvergence = live == null || !sameContexts(liveContexts, authority.contexts);
12395
- const enforcement = live?.enforcement === "disabled" || live == null && authority.apiPayload.enforcement === "disabled" ? "disabled" : "active";
12396
- if (liveNeedsConvergence && enforcement === "active") {
12397
- const prWorkflows = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, "deployable") ?? [];
12398
- const gateFiles = gateWorkflowFiles(prWorkflows);
12399
- const allPrWorkflows = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
12400
- const bodies = [];
12401
- for (const path2 of allPrWorkflows) {
12402
- const body = await fetchFileContent(deps, repo, baseBranch, path2);
12403
- if (body) bodies.push({ path: path2, body });
12404
- }
12405
- const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
12406
- const safeContexts = new Set(collectPullRequestWorkflowContexts(bodies.filter((b) => !filteredPaths.has(b.path))));
12407
- const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => authority.contexts.includes(c) && !safeContexts.has(c));
12408
- if (unsafe.length) {
12409
- const reason = `product ruleset left non-enforcing \u2014 [${unsafe.join(", ")}] ${unsafe.length === 1 ? "is" : "are"} emitted ONLY by path-filtered workflow(s) (${[...filteredPaths].join(", ")}), so the context is not reported on a PR outside those paths and requiring it would block that PR forever (#3836). Give the gate a companion job that runs unconditionally, then re-run this command`;
12410
- result.skipped.push(reason);
12411
- return finalizeCiReconcile(repo, deps, result, report, reason);
12412
- }
12413
- if (!await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles)) {
12414
- const reason = "product ruleset left non-enforcing \u2014 the gate is not green on development; activating it would block every PR (#3694)";
12415
- result.skipped.push(reason);
12416
- return finalizeCiReconcile(repo, deps, result, report, reason);
12417
- }
12418
- }
12419
- if (liveNeedsConvergence) {
12420
- try {
12421
- const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, enforcement);
12422
- if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
12423
- else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
12424
- } catch (e) {
12425
- result.errors.push(e.message);
12426
- return finalizeCiReconcile(repo, deps, result, report);
12435
+ const prWorkflows = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, "deployable") ?? [];
12436
+ const gateFiles = gateWorkflowFiles(prWorkflows);
12437
+ const allPrWorkflows = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
12438
+ const bodies = [];
12439
+ for (const path2 of allPrWorkflows) {
12440
+ const body = await fetchFileContent(deps, repo, baseBranch, path2);
12441
+ if (body) bodies.push({ path: path2, body });
12442
+ }
12443
+ const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
12444
+ const safeContexts = new Set(collectPullRequestWorkflowContexts(bodies.filter((b) => !filteredPaths.has(b.path))));
12445
+ const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => authority.contexts.includes(c) && !safeContexts.has(c));
12446
+ const plan = resolveProductRulesetReconcilePlan({
12447
+ liveEnforcement: live?.enforcement,
12448
+ liveContexts,
12449
+ authorityContexts: authority.contexts,
12450
+ gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
12451
+ unsafeContexts: unsafe
12452
+ });
12453
+ if (!plan.shouldActivate) {
12454
+ if (plan.holdReason) {
12455
+ result.skipped.push(plan.holdReason);
12456
+ return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
12427
12457
  }
12428
- } else {
12429
12458
  result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts already match ${authority.source}`);
12459
+ return finalizeCiReconcile(repo, deps, result, report);
12430
12460
  }
12431
- if (enforcement === "disabled") {
12432
- const reason = `${PRODUCT_RULESET_NAME} remains parked (disabled); authoritative contexts were preserved without changing enforcement`;
12433
- return finalizeCiReconcile(repo, deps, result, report, reason);
12461
+ try {
12462
+ const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
12463
+ if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
12464
+ else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
12465
+ } catch (e) {
12466
+ result.errors.push(e.message);
12467
+ return finalizeCiReconcile(repo, deps, result, report);
12434
12468
  }
12435
12469
  return finalizeCiReconcile(repo, deps, result, report);
12436
12470
  }
@@ -23427,6 +23461,12 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
23427
23461
  detail: optionDetail(missing)
23428
23462
  });
23429
23463
  } else if (repoClass === "deployable") {
23464
+ const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
23465
+ checks.push({
23466
+ ok: productRuleset?.enforcement === "active",
23467
+ label: "product required-check ruleset enforcement active",
23468
+ detail: productRuleset?.enforcement !== "active" ? `${PRODUCT_RULESET_NAME} is ${productRuleset?.enforcement ?? "missing"} \u2014 run mmi-cli ci reconcile --apply --repo ${repo} once the gate is green` : void 0
23469
+ });
23430
23470
  const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
23431
23471
  const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
23432
23472
  checks.push({
@@ -28840,7 +28880,8 @@ function diagnoseSurface(evidence) {
28840
28880
  return { ...base, state: "repair-failed", repairDetail: evidence.repair.detail };
28841
28881
  }
28842
28882
  if (evidence.repair?.attempted && evidence.repair.ok) {
28843
- return { ...base, state: "clean", repairDetail: evidence.repair.detail };
28883
+ const behindReleased = evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0;
28884
+ return { ...base, state: behindReleased ? "pending-reload" : "clean", repairDetail: evidence.repair.detail };
28844
28885
  }
28845
28886
  if (!evidence.installRecordPresent) return { ...base, state: "missing" };
28846
28887
  if (!evidence.deliveryPresent || !evidence.payloadPresent || evidence.manifest === "missing") {
@@ -28858,7 +28899,7 @@ function reloadInstruction(descriptor) {
28858
28899
  return `${verb} ${descriptor.displayName}`;
28859
28900
  }
28860
28901
  function planSurfaceRepair(diagnosis) {
28861
- if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
28902
+ if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
28862
28903
  const descriptor = diagnosis.descriptor;
28863
28904
  if (descriptor.repairOwner !== "mmi-cli") {
28864
28905
  return {
@@ -28883,6 +28924,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
28883
28924
  const detailByState = {
28884
28925
  skipped: "skipped \u2014 host not active",
28885
28926
  clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
28927
+ "pending-reload": `pending ${descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
28886
28928
  "freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
28887
28929
  stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
28888
28930
  missing: "missing \u2014 no install record",
@@ -28894,11 +28936,14 @@ function buildSurfaceDoctorCheck(diagnosis) {
28894
28936
  id: `${descriptor.token}-plugin`,
28895
28937
  surface: descriptor.token,
28896
28938
  state,
28897
- ok: state === "clean" || state === "skipped",
28939
+ ok: state === "clean" || state === "skipped" || state === "pending-reload",
28898
28940
  ...state === "freshness-unknown" ? { reportOnly: true } : {},
28941
+ // Nothing is broken, so this is not a ✗ — but it is the one OK row an operator must act on, and the
28942
+ // short default lane and the SessionStart banner both filter to failures unless a row says otherwise.
28943
+ ...state === "pending-reload" ? { warn: true } : {},
28899
28944
  label: `${descriptor.displayName} plugin`,
28900
28945
  detail: detailByState[state],
28901
- ...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
28946
+ ...plan ? { fix: plan.instruction } : state === "pending-reload" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
28902
28947
  verbose: [
28903
28948
  // The three numbers the legacy builder used to print. They belong here now that this is the only
28904
28949
  // plugin row, and a report that names a state without naming the versions behind it is not evidence.
@@ -30000,13 +30045,12 @@ function commandOwnLongFlags(cmd) {
30000
30045
  }
30001
30046
  return [...flags];
30002
30047
  }
30003
- var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project"]);
30004
- function positionalTargetForm(cmd) {
30048
+ function positionalTargetForm(cmd, opts = {}) {
30005
30049
  if (!cmd) return void 0;
30006
30050
  const args = cmd.registeredArguments ?? [];
30007
30051
  const first = args[0];
30008
30052
  if (!first) return void 0;
30009
- return `mmi-cli ${commandPath2(cmd)} <${first.name()}>`;
30053
+ return formatPositionalTarget(commandPath2(cmd), first.name(), opts);
30010
30054
  }
30011
30055
  function resolveParseHint() {
30012
30056
  if (lastParseErrorKind === "unknown-command") {
@@ -30051,12 +30095,13 @@ function envelopeAwareWriteErr(str) {
30051
30095
  const match = /unknown option '([^']+)'/.exec(plain);
30052
30096
  if (match) {
30053
30097
  const flag = match[1];
30098
+ const argv = process.argv.slice(2);
30099
+ const invoked = resolveCommandFromArgv(program2, argv);
30100
+ const positional = TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked, { flag, argv }) : void 0;
30054
30101
  if (argvWantsJson2()) {
30055
- const invoked = resolveCommandFromArgv(program2, process.argv.slice(2));
30056
- const suggestion = didYouMean(flag, commandOwnLongFlags(invoked));
30057
- const corrected = suggestion ? `mmi-cli ${process.argv.slice(2).map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
30058
- const positional = !suggestion && TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked) : void 0;
30059
- const message = positional ? `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}` : `unknown option '${flag}'`;
30102
+ const suggestion = positional ? void 0 : didYouMean(flag, commandOwnLongFlags(invoked));
30103
+ const corrected = suggestion ? `mmi-cli ${argv.map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
30104
+ const message = positional ? unknownTargetFlagMessage(flag, positional) : `unknown option '${flag}'`;
30060
30105
  process.stderr.write(
30061
30106
  formatErrorEnvelope(message, {
30062
30107
  code: ERROR_CODES.ERR_UNKNOWN_FLAG,
@@ -30068,6 +30113,10 @@ function envelopeAwareWriteErr(str) {
30068
30113
  unknownFlagJsonHandled = true;
30069
30114
  return;
30070
30115
  }
30116
+ if (positional) {
30117
+ process.stderr.write(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
30118
+ return;
30119
+ }
30071
30120
  process.stderr.write(str);
30072
30121
  return;
30073
30122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.92.0",
3
+ "version": "3.94.0",
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",