@mutmutco/cli 4.0.1 → 4.0.3

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 +165 -58
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -8237,6 +8237,25 @@ function awsScheduleRefs(payload) {
8237
8237
  }
8238
8238
  return refs;
8239
8239
  }
8240
+ function decorateAwsModel(entries, registry2, verdicts) {
8241
+ if (!registry2 || registry2.length === 0) return [...entries];
8242
+ const rowById = new Map(registry2.map((r) => [r.id, r]));
8243
+ const literalById = /* @__PURE__ */ new Map();
8244
+ for (const v of verdicts ?? []) {
8245
+ if (typeof v.modelId === "string" && v.modelId.trim() && !literalById.has(v.id)) {
8246
+ literalById.set(v.id, v.modelId);
8247
+ }
8248
+ }
8249
+ return entries.map((e) => {
8250
+ if (!e.scheduleId) return e;
8251
+ const row = rowById.get(e.scheduleId);
8252
+ if (!row || row.executor !== "cursor-agent") return e;
8253
+ const literal = literalById.get(e.scheduleId);
8254
+ if (literal) return { ...e, model: literal };
8255
+ if (row.model) return { ...e, model: `role: ${row.model}` };
8256
+ return { ...e, model: "org-default" };
8257
+ });
8258
+ }
8240
8259
  var DECLARED_ENTRIES = [
8241
8260
  { name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli devops runtime box get <box> --ssh)" },
8242
8261
  // #3370: the floor under runner-hygiene.yml. That workflow runs ON the runner and needs checkout +
@@ -8247,8 +8266,16 @@ var DECLARED_ENTRIES = [
8247
8266
  // #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
8248
8267
  // it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
8249
8268
  { name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli devops runtime box get mmi-runner --ssh)" },
8250
- { name: "zuber-bake", cadence: "*:05/30 (every 30 min)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake.timer \u2192 rolling overlay bake, metro tier, next ~6h window (verify over ssh)" },
8251
- { name: "zuber-bake-full", cadence: "00:30 UTC (03:30 TRT, daily)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake-full.timer \u2192 full 24h overlay rebuild, all 81 il (verify over ssh)" }
8269
+ // #3852: the runner /tmp sweep daily inode hygiene (03:41 UTC), deliberately the one clutter path the
8270
+ // out-of-band disk-relief script must never touch. Verified installed 2026-08-18: the cron.d file and
8271
+ // flock line match scripts/runner-tmp-sweep.cron exactly.
8272
+ { name: "mmi-runner-tmp-sweep (mmi-runner)", cadence: "41 3 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-tmp-sweep \u2192 /opt/mmi-control/runner-tmp-sweep.sh; daily inode hygiene, flock -n (verify: mmi-cli devops runtime box get mmi-runner --ssh)" }
8273
+ // RETIRED 2026-08-18 (MMI-Hub#5234): `zuber-bake` (every 30 min) and `zuber-bake-full` (daily 00:30 UTC)
8274
+ // were declared as zuber systemd timers. The ZuberShade full park (#1774, 2026-08-06) stopped them: on
8275
+ // the box (46.225.58.31) zuber-bake.timer / zuber-bake-full.timer answer not-found and
8276
+ // zuber-bake-demand.timer is disabled — no bake/oven timer remains armed, and the notebook must not
8277
+ // claim a job the box does not run. Re-add when the hold lifts and the demand bake re-arms
8278
+ // (MMC-ZuberShade docs/aws-teardown-hold-2026-08-03.md).
8252
8279
  ];
8253
8280
  function sortEntries(entries) {
8254
8281
  return [...entries].sort(
@@ -8263,10 +8290,12 @@ function formatSchedulesTable(entries, now = /* @__PURE__ */ new Date()) {
8263
8290
  const cadW = w((e) => e.cadence, "CADENCE");
8264
8291
  const exeW = w((e) => e.executor, "EXECUTOR");
8265
8292
  const llmW = w((e) => e.llm, "LLM");
8293
+ const modelOf = (e) => e.model ?? "-";
8294
+ const modW = w((e) => modelOf(e), "MODEL");
8266
8295
  const resW = w((e) => resolvedOf.get(e) ?? e.resolved, "RESOLVED");
8267
- const row = (n, c, x, l, r, s) => `${n.padEnd(nameW)} ${c.padEnd(cadW)} ${x.padEnd(exeW)} ${l.padEnd(llmW)} ${r.padEnd(resW)} ${s}`;
8268
- const lines = [row("JOB", "CADENCE", "EXECUTOR", "LLM", "RESOLVED", "SOURCE")];
8269
- for (const e of entries) lines.push(row(e.name, e.cadence, e.executor, e.llm, resolvedOf.get(e) ?? e.resolved, e.source));
8296
+ const row = (n, c, x, l, m, r, s) => `${n.padEnd(nameW)} ${c.padEnd(cadW)} ${x.padEnd(exeW)} ${l.padEnd(llmW)} ${m.padEnd(modW)} ${r.padEnd(resW)} ${s}`;
8297
+ const lines = [row("JOB", "CADENCE", "EXECUTOR", "LLM", "MODEL", "RESOLVED", "SOURCE")];
8298
+ for (const e of entries) lines.push(row(e.name, e.cadence, e.executor, e.llm, modelOf(e), resolvedOf.get(e) ?? e.resolved, e.source));
8270
8299
  return lines.join("\n");
8271
8300
  }
8272
8301
  function withTickState(entry, now = /* @__PURE__ */ new Date()) {
@@ -8274,11 +8303,11 @@ function withTickState(entry, now = /* @__PURE__ */ new Date()) {
8274
8303
  }
8275
8304
  function renderDocSection(entries, generatedAtIso) {
8276
8305
  const lines = [
8277
- "| Job | Cadence | Executor | LLM | Resolved | Source |",
8278
- "|---|---|---|---|---|---|"
8306
+ "| Job | Cadence | Executor | LLM | Model | Resolved | Source |",
8307
+ "|---|---|---|---|---|---|---|"
8279
8308
  ];
8280
8309
  for (const e of entries) {
8281
- lines.push(`| ${e.name} | \`${e.cadence}\` | ${e.executor} | ${e.llm} | ${e.resolved} | ${e.source} |`);
8310
+ lines.push(`| ${e.name} | \`${e.cadence}\` | ${e.executor} | ${e.llm} | ${e.model ?? "-"} | ${e.resolved} | ${e.source} |`);
8282
8311
  }
8283
8312
  return [
8284
8313
  DOC_START_MARKER,
@@ -8694,6 +8723,22 @@ async function fetchSchedulesList(deps) {
8694
8723
  return null;
8695
8724
  }
8696
8725
  }
8726
+ async function fetchRunVerdicts(deps) {
8727
+ if (!deps.baseUrl) return null;
8728
+ const token = await deps.token();
8729
+ if (!token) return null;
8730
+ try {
8731
+ const res = await retriedFetch(deps, `${deps.baseUrl.replace(/\/$/, "")}/schedules/verdicts`, {
8732
+ method: "GET",
8733
+ headers: { Authorization: `Bearer ${token}` }
8734
+ });
8735
+ if (!res.ok) return null;
8736
+ const body = await res.json();
8737
+ return Array.isArray(body?.runVerdicts) ? body.runVerdicts : null;
8738
+ } catch {
8739
+ return null;
8740
+ }
8741
+ }
8697
8742
  async function fetchOrgConfig(deps) {
8698
8743
  if (!deps.baseUrl) return null;
8699
8744
  const token = await deps.token();
@@ -9110,6 +9155,7 @@ var OWNER2 = "mutmutco";
9110
9155
  var SSM_ROOT = "/mmi-future";
9111
9156
  var PROJECT_TIER_SEGMENT = "dev";
9112
9157
  var ORG_INFRA_SLUG = "_org";
9158
+ var JERV_VAULT_POINTER = "Not in the MMI vault? Jerv service, personal API and model-provider credentials live in the separate Jerv AWS Vault \u2014 enumerate: `jerv-cli vault list`; consume keyless: `jerv-cli vault use <KEY> -- <cmd>` (values are never printed).";
9113
9159
  var KEY_MAX_PATH_SEGMENTS = 4;
9114
9160
  var KEY_RE = new RegExp(`^(?:[a-z0-9][a-z0-9-]*/){0,${KEY_MAX_PATH_SEGMENTS}}[A-Za-z][A-Za-z0-9_]*$`);
9115
9161
  function isValidSecretKey(key) {
@@ -9126,7 +9172,7 @@ function secretParamName(slug, key) {
9126
9172
  return `${SSM_ROOT}/${slug}/${key}`;
9127
9173
  }
9128
9174
  function formatSecretList(items) {
9129
- if (!items.length) return "no secrets";
9175
+ if (!items.length) return `no secrets \u2014 ${JERV_VAULT_POINTER}`;
9130
9176
  const width = Math.max(...items.map((i) => i.key.length));
9131
9177
  return items.map((i) => {
9132
9178
  const head = `${i.canManage ? "*" : " "} ${i.key.padEnd(width)} ${i.tier}`;
@@ -9317,7 +9363,7 @@ function formatCapabilities(r) {
9317
9363
  const items = [...r.capabilities ?? []].sort((a, b) => a.scope.localeCompare(b.scope));
9318
9364
  if (!items.length) return `${head}
9319
9365
 
9320
- no vault credentials visible`;
9366
+ no vault credentials visible \u2014 ${JERV_VAULT_POINTER}`;
9321
9367
  const width = Math.max(...items.map((i) => i.scope.length));
9322
9368
  const lines = items.map(
9323
9369
  (i) => `${i.accessible ? "+" : " "} ${i.scope.padEnd(width)} ${i.tier.padEnd(7)} ${i.grantScope ? `${i.reason} (${i.grantScope})` : i.reason}`
@@ -9440,7 +9486,8 @@ function resolveNotFoundGuidance(input) {
9440
9486
  const lines = [
9441
9487
  `Secret ${key} was not found at ${tried}, and no key named ${leaf} is visible to you.`,
9442
9488
  `Locate it by intent first: \`mmi-cli vault secrets find "<what it is for>"\` \u2014 it returns the exact keyless-use command.`,
9443
- mnemonic
9489
+ mnemonic,
9490
+ JERV_VAULT_POINTER
9444
9491
  ];
9445
9492
  if (isMaster) {
9446
9493
  lines.push("You are master: if it truly does not exist yet, create it with `mmi-cli vault secrets set` rather than requesting a grant from yourself.");
@@ -9722,6 +9769,7 @@ async function secretsVerify(deps, key, opts) {
9722
9769
  if (!value) {
9723
9770
  deps.err(`${key}: ABSENT or unreachable at this coordinate \u2014 no value could be read`);
9724
9771
  deps.err(`Locate a key across every vault you can reach: mmi-cli oracle org access capabilities${opts.repo ? ` --repo ${opts.repo}` : ""}`);
9772
+ deps.err(JERV_VAULT_POINTER);
9725
9773
  return false;
9726
9774
  }
9727
9775
  deps.log(`${key}: PRESENT (${value.length} chars) \u2014 reachability only; no provider verifier is configured for ${secretLeafName(key)}, so the credential itself was not validated`);
@@ -10377,6 +10425,7 @@ async function fetchSecretForUse(deps, { repo, key, slug }) {
10377
10425
  // src/report.ts
10378
10426
  var HUB_REPO = "mutmutco/MMI-Hub";
10379
10427
  var REPORT_LABEL = "report";
10428
+ var LEARNING_LABEL = "learning";
10380
10429
  var REPORT_LOOP_KIND = "friction";
10381
10430
  var REPORT_SURFACE_WAIVER_REASON = "Hub-only friction filing; attribution repo is footer-only; no honest single surface";
10382
10431
  function preflightReportSurface() {
@@ -10774,10 +10823,10 @@ var rollout_plan_default = {
10774
10823
  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)."
10775
10824
  },
10776
10825
  baseline: {
10777
- version: "4.0.1",
10778
- tag: "v4.0.1",
10779
- commit: "2807e6205fab",
10780
- npm: "@mutmutco/cli@4.0.1"
10826
+ version: "4.0.3",
10827
+ tag: "v4.0.3",
10828
+ commit: "3a588e3fa3a3",
10829
+ npm: "@mutmutco/cli@4.0.3"
10781
10830
  },
10782
10831
  exitCriterion: "fleet-n-of-n",
10783
10832
  hubOnlyShortcut: "forbidden",
@@ -10794,14 +10843,14 @@ var rollout_plan_default = {
10794
10843
  repo: "mutmutco/mmi-hub",
10795
10844
  role: "canary",
10796
10845
  schedule: "train",
10797
- v3Target: "v4.0.1"
10846
+ v3Target: "v4.0.3"
10798
10847
  }
10799
10848
  ],
10800
10849
  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.",
10801
10850
  rollback: {
10802
10851
  independent: true,
10803
- mechanism: "npm dist-tag latest -> 4.0.1 and redeploy the Hub Lambda from tag v4.0.1 (2807e6205fab); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10804
- v3Target: "v4.0.1 (@mutmutco/cli@4.0.1, tag commit 2807e6205fab \u2014 last known-good release carrying the repo-index v4-only contract)"
10852
+ mechanism: "npm dist-tag latest -> 4.0.3 and redeploy the Hub Lambda from tag v4.0.3 (3a588e3fa3a3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10853
+ v3Target: "v4.0.3 (@mutmutco/cli@4.0.3, tag commit 3a588e3fa3a3 \u2014 last known-good release carrying the repo-index v4-only contract)"
10805
10854
  }
10806
10855
  },
10807
10856
  {
@@ -17561,6 +17610,7 @@ async function postIssueComment(client, input) {
17561
17610
 
17562
17611
  // src/skill-lesson.ts
17563
17612
  var SKILL_LESSON_LABEL = "skill-lesson";
17613
+ var SKILL_LESSON_FILE_LABELS = [SKILL_LESSON_LABEL, LEARNING_LABEL];
17564
17614
  var SKILL_LESSON_LOOP_KIND = "lesson";
17565
17615
  var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage"];
17566
17616
  function assertSkillName(name) {
@@ -17593,6 +17643,7 @@ function findDuplicateLesson(source, openLessons) {
17593
17643
  }
17594
17644
 
17595
17645
  // src/session-identity.ts
17646
+ var import_node_crypto4 = require("node:crypto");
17596
17647
  var import_node_os7 = require("node:os");
17597
17648
 
17598
17649
  // src/plugin-guard-io.ts
@@ -18580,12 +18631,19 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
18580
18631
  // src/session-identity.ts
18581
18632
  var SESSION_ID_ENV_VARS = [
18582
18633
  "MMI_SESSION_ID",
18634
+ "MMI_GATE_SESSION_ID",
18583
18635
  "CLAUDE_SESSION_ID",
18584
18636
  "CLAUDE_CODE_SESSION_ID",
18585
18637
  "CODEX_SESSION_ID",
18638
+ "CODEX_THREAD_ID",
18586
18639
  "CURSOR_SESSION_ID",
18640
+ "HERMES_SESSION_ID",
18587
18641
  "PI_SESSION_ID"
18588
18642
  ];
18643
+ var SYNTH_SESSION_PREFIX = "synth-";
18644
+ function isHostSessionId(session) {
18645
+ return Boolean(session) && !session.startsWith(SYNTH_SESSION_PREFIX);
18646
+ }
18589
18647
  function readSessionId(env) {
18590
18648
  for (const key of SESSION_ID_ENV_VARS) {
18591
18649
  const value = env[key]?.trim();
@@ -18593,11 +18651,18 @@ function readSessionId(env) {
18593
18651
  }
18594
18652
  return void 0;
18595
18653
  }
18654
+ var synthesizedSessionId;
18655
+ function fallbackSessionId(surface) {
18656
+ if (!synthesizedSessionId) {
18657
+ synthesizedSessionId = `${SYNTH_SESSION_PREFIX}${surface}-${process.pid}-${(0, import_node_crypto4.randomBytes)(3).toString("hex")}`;
18658
+ }
18659
+ return synthesizedSessionId;
18660
+ }
18596
18661
  function describeSessionIdentity(env = process.env) {
18597
- const session = readSessionId(env);
18662
+ const surface = detectSurface(env);
18598
18663
  return {
18599
- ...session ? { session } : {},
18600
- surface: detectSurface(env),
18664
+ session: readSessionId(env) ?? fallbackSessionId(surface),
18665
+ surface,
18601
18666
  host: (0, import_node_os7.hostname)()
18602
18667
  };
18603
18668
  }
@@ -19865,7 +19930,7 @@ async function postClaimMarkerComment(client, item) {
19865
19930
  const actor = describeSessionIdentity();
19866
19931
  const marker = {
19867
19932
  v: 1,
19868
- ...actor.session ? { session: actor.session } : {},
19933
+ session: actor.session,
19869
19934
  surface: actor.surface,
19870
19935
  host: actor.host,
19871
19936
  ts: (/* @__PURE__ */ new Date()).toISOString()
@@ -20006,7 +20071,7 @@ function sameClaimHost(a, b) {
20006
20071
  }
20007
20072
  function laneOwnership(marker, current) {
20008
20073
  if (!marker) return "unknown";
20009
- if (marker.session && current.session) {
20074
+ if (isHostSessionId(marker.session) && isHostSessionId(current.session)) {
20010
20075
  return marker.session === current.session ? "mine" : "other";
20011
20076
  }
20012
20077
  if (marker.surface && current.surface && marker.surface !== current.surface) return "other";
@@ -21682,14 +21747,14 @@ function renderVerifyBroker(input) {
21682
21747
  }
21683
21748
 
21684
21749
  // src/tenant-artifact.ts
21685
- var import_node_crypto4 = require("node:crypto");
21750
+ var import_node_crypto5 = require("node:crypto");
21686
21751
  var import_node_fs22 = require("node:fs");
21687
21752
  var import_promises3 = require("node:fs/promises");
21688
21753
  var import_node_path20 = require("node:path");
21689
21754
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
21690
21755
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
21691
21756
  async function sha256File(path2) {
21692
- const hash = (0, import_node_crypto4.createHash)("sha256");
21757
+ const hash = (0, import_node_crypto5.createHash)("sha256");
21693
21758
  for await (const chunk of (0, import_node_fs22.createReadStream)(path2)) hash.update(chunk);
21694
21759
  return hash.digest("hex");
21695
21760
  }
@@ -22829,7 +22894,7 @@ async function announceRelease(deps, args) {
22829
22894
  }
22830
22895
 
22831
22896
  // src/repo-index.ts
22832
- var import_node_crypto5 = require("node:crypto");
22897
+ var import_node_crypto6 = require("node:crypto");
22833
22898
  var import_node_child_process12 = require("node:child_process");
22834
22899
  var import_node_fs23 = require("node:fs");
22835
22900
  var import_node_path21 = require("node:path");
@@ -23019,7 +23084,7 @@ function rebuildRepoIndex(cwd, repoSlug3) {
23019
23084
  continue;
23020
23085
  }
23021
23086
  if (text.length > 15e5) continue;
23022
- const hash = (0, import_node_crypto5.createHash)("sha256").update(text).digest("hex").slice(0, 16);
23087
+ const hash = (0, import_node_crypto6.createHash)("sha256").update(text).digest("hex").slice(0, 16);
23023
23088
  const symbols = extractSymbols(text);
23024
23089
  const docBlurb = extractModuleBlurb(text);
23025
23090
  const top = rel.includes("/") ? rel.split("/")[0] : "";
@@ -23124,16 +23189,16 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
23124
23189
  }
23125
23190
 
23126
23191
  // src/repo-index-cloud-client.ts
23127
- var import_node_crypto8 = require("node:crypto");
23192
+ var import_node_crypto9 = require("node:crypto");
23128
23193
 
23129
23194
  // src/repo-index-v4/builder.ts
23130
- var import_node_crypto7 = require("node:crypto");
23195
+ var import_node_crypto8 = require("node:crypto");
23131
23196
  var import_node_child_process13 = require("node:child_process");
23132
23197
  var import_node_fs25 = require("node:fs");
23133
23198
  var import_node_path23 = require("node:path");
23134
23199
 
23135
23200
  // src/repo-index-v4/chunks.ts
23136
- var import_node_crypto6 = require("node:crypto");
23201
+ var import_node_crypto7 = require("node:crypto");
23137
23202
  var import_node_fs24 = require("node:fs");
23138
23203
  var import_node_path22 = require("node:path");
23139
23204
 
@@ -23200,7 +23265,7 @@ async function parserFor(language) {
23200
23265
  return parser;
23201
23266
  }
23202
23267
  function sha256(value) {
23203
- return (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
23268
+ return (0, import_node_crypto7.createHash)("sha256").update(value).digest("hex");
23204
23269
  }
23205
23270
  function pointerId(path2, kind, contentHash, start, end, symbol) {
23206
23271
  return sha256(["repo-index-v4", path2, kind, contentHash, String(start), String(end), symbol ?? ""].join("\0"));
@@ -23320,7 +23385,7 @@ function canonicalJson(value) {
23320
23385
  return `{${Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`).join(",")}}`;
23321
23386
  }
23322
23387
  function sha2562(value) {
23323
- return (0, import_node_crypto7.createHash)("sha256").update(canonicalJson(value)).digest("hex");
23388
+ return (0, import_node_crypto8.createHash)("sha256").update(canonicalJson(value)).digest("hex");
23324
23389
  }
23325
23390
  function quantizeV4Vector(vector) {
23326
23391
  return vector.map((value) => Number(value.toFixed(V4_VECTOR_DECIMAL_PLACES)));
@@ -23516,16 +23581,23 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
23516
23581
  if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
23517
23582
  const baseUrl = deps.baseUrl.replace(/\/$/, "");
23518
23583
  const headers = { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" };
23519
- try {
23584
+ const shadowPass = async () => {
23520
23585
  for (const query of queries) {
23521
- const res2 = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
23586
+ const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
23522
23587
  method: "POST",
23523
23588
  headers,
23524
23589
  body: JSON.stringify({ q: query.query, mode: query.mode, readinessProbe: true })
23525
23590
  }, { attempts: RETRY_ATTEMPTS2, timeoutMs: 12e4, sleep: deps.retrySleep });
23526
- const body2 = await res2.json().catch(() => ({}));
23527
- if (!res2.ok) return { ok: false, error: `${query.id}: ${body2.error ?? `v4 readiness probe HTTP ${res2.status}`}${body2.errorClass ? ` (${body2.errorClass})` : ""}`, status: res2.status };
23591
+ const body = await res.json().catch(() => ({}));
23592
+ if (!res.ok) return { ok: false, error: `${query.id}: ${body.error ?? `v4 readiness probe HTTP ${res.status}`}${body.errorClass ? ` (${body.errorClass})` : ""}`, status: res.status };
23528
23593
  }
23594
+ return { ok: true };
23595
+ };
23596
+ try {
23597
+ const warmup = await shadowPass();
23598
+ if (!warmup.ok) return warmup;
23599
+ const verdict = await shadowPass();
23600
+ if (!verdict.ok) return verdict;
23529
23601
  const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/status`, {
23530
23602
  method: "GET",
23531
23603
  headers: { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" ") }
@@ -23534,8 +23606,8 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
23534
23606
  if (!res.ok) return { ok: false, error: body.error ?? `v4 readiness status HTTP ${res.status}`, status: res.status };
23535
23607
  const readiness = body.v4Readiness;
23536
23608
  const complete = !!readiness && ["ready", "not-ready"].includes(readiness.verdict) && Array.isArray(readiness.reasons) && Number.isFinite(readiness.goldenCount) && Number.isFinite(readiness.evidenceCount) && !!readiness.modes && ["lexical", "semantic", "hybrid"].every((mode) => {
23537
- const verdict = readiness.modes[mode]?.verdict;
23538
- return verdict === "ready" || verdict === "not-ready";
23609
+ const verdict2 = readiness.modes[mode]?.verdict;
23610
+ return verdict2 === "ready" || verdict2 === "not-ready";
23539
23611
  });
23540
23612
  if (!complete) return { ok: false, error: "Hub status omitted the explicit v4 readiness verdict/reasons/modes contract", status: res.status };
23541
23613
  return { ok: true, readiness };
@@ -23640,7 +23712,7 @@ async function searchRepoIndexCloud(query, opts, deps) {
23640
23712
  const body = await res.json().catch(() => ({}));
23641
23713
  if (!res.ok) return { ok: false, error: body.error ?? `search HTTP ${res.status}`, status: res.status };
23642
23714
  const shadowRate = Math.max(0, Math.min(1, Number(process.env.MMI_REPO_INDEX_V4_SHADOW_RATE ?? 0.1) || 0));
23643
- if ((0, import_node_crypto8.createHash)("sha256").update(query).digest()[0] / 256 < shadowRate) {
23715
+ if ((0, import_node_crypto9.createHash)("sha256").update(query).digest()[0] / 256 < shadowRate) {
23644
23716
  void fetchWithRetry(
23645
23717
  deps.fetch ?? fetch,
23646
23718
  `${deps.baseUrl.replace(/\/$/, "")}/repo-index/v4/shadow`,
@@ -25979,7 +26051,7 @@ function registerSecretsCommands(program3) {
25979
26051
  }
25980
26052
 
25981
26053
  // src/app-actor.ts
25982
- var import_node_crypto9 = require("node:crypto");
26054
+ var import_node_crypto10 = require("node:crypto");
25983
26055
  var APP_ACTOR_ENV = "MMI_ACTOR";
25984
26056
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
25985
26057
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -26023,7 +26095,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
26023
26095
  exp: now + APP_JWT_TTL_S,
26024
26096
  iss: appId
26025
26097
  }));
26026
- const signer = (0, import_node_crypto9.createSign)("RSA-SHA256");
26098
+ const signer = (0, import_node_crypto10.createSign)("RSA-SHA256");
26027
26099
  signer.update(`${header}.${payload}`);
26028
26100
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
26029
26101
  }
@@ -26508,12 +26580,20 @@ async function readOrFail(read) {
26508
26580
  async function defaultRegistryRead() {
26509
26581
  return fetchSchedulesList(registryClientDeps(await loadConfig()));
26510
26582
  }
26511
- async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defaultRegistryRead, readProjects = defaultProjectsRead) {
26512
- const [gh, aws, registryRead, projectsRead] = await Promise.all([
26583
+ async function defaultRunVerdictRead() {
26584
+ return fetchRunVerdicts(registryClientDeps(await loadConfig()));
26585
+ }
26586
+ function modelRoleKeyOf(row) {
26587
+ const model = row.model;
26588
+ return typeof model === "string" && model.trim() ? model : void 0;
26589
+ }
26590
+ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defaultRegistryRead, readProjects = defaultProjectsRead, readRunVerdicts = defaultRunVerdictRead) {
26591
+ const [gh, aws, registryRead, projectsRead, runVerdictRead] = await Promise.all([
26513
26592
  githubEntries(client),
26514
26593
  awsEntries(),
26515
26594
  readOrFail(readRegistry),
26516
- readOrFail(readProjects)
26595
+ readOrFail(readProjects),
26596
+ readOrFail(readRunVerdicts)
26517
26597
  ]);
26518
26598
  const readFailures = [];
26519
26599
  if (registryRead.state === "failed") {
@@ -26523,10 +26603,16 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
26523
26603
  readFailures.push(`registry: could not read the projects list \u2014 schedulesMode opt-outs unresolved (nothing suppressed) \u2014 ${projectsRead.error}`);
26524
26604
  }
26525
26605
  const registry2 = registryRead.state === "ok" ? registryRead.value : null;
26606
+ const runVerdicts = runVerdictRead.state === "ok" ? runVerdictRead.value : null;
26526
26607
  const projects = projectsRead.state === "ok" ? projectsRead.value : null;
26608
+ const decoratedAws = decorateAwsModel(
26609
+ aws.entries,
26610
+ registry2?.map((r) => ({ id: r.id, executor: r.executor, model: modelRoleKeyOf(r) })) ?? null,
26611
+ runVerdicts
26612
+ );
26527
26613
  const recon = assembleReconciliation(gh.entries, new Set(gh.readRepos), registry2, new Set(gh.workflowNames), {
26528
26614
  // #3286: the harbour side joins registry rows against the live aws-scheduler clocks by scheduleId.
26529
- awsEntries: aws.entries,
26615
+ awsEntries: decoratedAws,
26530
26616
  schedulerRead: Boolean(aws.schedulerRead)
26531
26617
  }, new Set(gh.disabledWorkflowNames));
26532
26618
  const selfManaged = /* @__PURE__ */ new Set();
@@ -26540,7 +26626,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
26540
26626
  );
26541
26627
  const driftLines = reconciliation.map(renderDrift);
26542
26628
  return {
26543
- entries: sortEntries([...gh.entries, ...aws.entries, ...DECLARED_ENTRIES]),
26629
+ entries: sortEntries([...gh.entries, ...decoratedAws, ...DECLARED_ENTRIES]),
26544
26630
  incomplete: [...gh.incomplete, ...aws.incomplete, ...recon.incomplete, ...readFailures],
26545
26631
  drift: driftLines,
26546
26632
  reconciliation,
@@ -26937,7 +27023,7 @@ var import_node_os14 = require("node:os");
26937
27023
  var import_node_path32 = require("node:path");
26938
27024
 
26939
27025
  // src/bootstrap-drift.ts
26940
- var import_node_crypto10 = require("node:crypto");
27026
+ var import_node_crypto11 = require("node:crypto");
26941
27027
  function byteComparableSeeds(manifest, cls) {
26942
27028
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
26943
27029
  }
@@ -26957,7 +27043,7 @@ function compareSeedBytes(hubContent, repoContent) {
26957
27043
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
26958
27044
  }
26959
27045
  function seedContentHash(content) {
26960
- return (0, import_node_crypto10.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
27046
+ return (0, import_node_crypto11.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
26961
27047
  }
26962
27048
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
26963
27049
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -27120,7 +27206,7 @@ function renderPropagationReport(plan) {
27120
27206
  }
27121
27207
 
27122
27208
  // src/bootstrap-propagation-identity.ts
27123
- var import_node_crypto11 = require("node:crypto");
27209
+ var import_node_crypto12 = require("node:crypto");
27124
27210
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
27125
27211
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
27126
27212
  function safeBranchPart(value, maxLength, fallback) {
@@ -27133,7 +27219,7 @@ function repoSlug2(repo) {
27133
27219
  function propagationBranch(repo, target) {
27134
27220
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
27135
27221
  const targetPart = safeBranchPart(target, 48, "target");
27136
- const hash = (0, import_node_crypto11.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
27222
+ const hash = (0, import_node_crypto12.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
27137
27223
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
27138
27224
  }
27139
27225
  function legacyPropagationBranch(repo) {
@@ -29673,7 +29759,7 @@ function registerBoardCommands(program3) {
29673
29759
  const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
29674
29760
  board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--allow-partial applies to the paginated path and detail reads.\n").action((o) => runBoardRead(o));
29675
29761
  withExamples(mutating(
29676
- board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
29762
+ board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
29677
29763
  (_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
29678
29764
  ).action(async (issueRefs, o) => {
29679
29765
  if (issueRefs.length === 1) {
@@ -30294,7 +30380,7 @@ async function checkDocsIndexAtHead(opts, deps) {
30294
30380
 
30295
30381
  // src/issue-commands.ts
30296
30382
  var import_node_fs38 = require("node:fs");
30297
- var import_node_crypto12 = require("node:crypto");
30383
+ var import_node_crypto13 = require("node:crypto");
30298
30384
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
30299
30385
  var ReparentConflictError = class extends Error {
30300
30386
  constructor(message, payload) {
@@ -30585,7 +30671,7 @@ function rowIdempotencyKey(batchKey, spec) {
30585
30671
  const identity = `${spec.type}
30586
30672
  ${spec.title.trim()}
30587
30673
  ${spec.body ?? ""}`;
30588
- const hash = (0, import_node_crypto12.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
30674
+ const hash = (0, import_node_crypto13.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
30589
30675
  return `${batchKey}:${hash}`;
30590
30676
  }
30591
30677
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -31217,7 +31303,23 @@ async function collectStatus() {
31217
31303
  } catch {
31218
31304
  stage = { running: false };
31219
31305
  }
31220
- return { repo, branch, worktrees, myOpenPrs, claimedItems, stage };
31306
+ let releaseTrack;
31307
+ let branches;
31308
+ try {
31309
+ const cfg = await loadConfigOrDiscover();
31310
+ if (cfg.sagaApiUrl) {
31311
+ const slug = await repoSlug();
31312
+ const deps = registryClientDeps(cfg);
31313
+ const read = await fetchProjectBySlugChecked(slug, deps);
31314
+ if (read.ok && read.project) {
31315
+ const trackRepo = repo ?? `mutmutco/${slug}`;
31316
+ releaseTrack = resolveReleaseTrack(read.project, void 0, trackRepo);
31317
+ branches = branchesForTrack(releaseTrack);
31318
+ }
31319
+ }
31320
+ } catch {
31321
+ }
31322
+ return { repo, branch, releaseTrack, branches, worktrees, myOpenPrs, claimedItems, stage };
31221
31323
  }
31222
31324
  var PRIORITY_RANK = {
31223
31325
  Urgent: 0,
@@ -31362,6 +31464,9 @@ function registerDiscoveryCommands(program3) {
31362
31464
  } else {
31363
31465
  console.log(`repo: ${report.repo ?? "unknown"}`);
31364
31466
  console.log(`branch: ${report.branch}`);
31467
+ if (report.releaseTrack) {
31468
+ console.log(`release track: ${report.releaseTrack} (${(report.branches ?? []).join(" -> ")})`);
31469
+ }
31365
31470
  console.log(`worktrees: ${report.worktrees.length} linked`);
31366
31471
  console.log(`my open PRs: ${report.myOpenPrs.length}`);
31367
31472
  console.log(`claimed board items: ${report.claimedItems.length}`);
@@ -37502,7 +37607,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
37502
37607
  title = buildSkillLessonTitle(skill, rawTitle);
37503
37608
  priority = resolveCreatePriority(o.priority, "skill-lesson");
37504
37609
  body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
37505
- args = buildIssueArgs({ type: "task", title, body, priority, repo: targetRepo3, labels: [SKILL_LESSON_LABEL] });
37610
+ args = buildIssueArgs({ type: "task", title, body, priority, repo: targetRepo3, labels: [...SKILL_LESSON_FILE_LABELS] });
37506
37611
  } catch (e) {
37507
37612
  return fail(`skill-lesson: ${e.message}`);
37508
37613
  }
@@ -37544,9 +37649,11 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
37544
37649
  });
37545
37650
  if (surfaceWarn) process.stderr.write(`${surfaceWarn}
37546
37651
  `);
37547
- try {
37548
- await execFileP2("gh", ["label", "create", SKILL_LESSON_LABEL, "--color", "c2e0c6", "--repo", targetRepo3], { timeout: GH_MUTATION_TIMEOUT_MS });
37549
- } catch {
37652
+ for (const [label, color] of [[SKILL_LESSON_LABEL, "c2e0c6"], [LEARNING_LABEL, "ededed"]]) {
37653
+ try {
37654
+ await execFileP2("gh", ["label", "create", label, "--color", color, "--repo", targetRepo3], { timeout: GH_MUTATION_TIMEOUT_MS });
37655
+ } catch {
37656
+ }
37550
37657
  }
37551
37658
  const created = await ghCreate(args);
37552
37659
  const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
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",