@mutmutco/codex-plugin 4.5.210 → 4.5.211

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mmi",
3
- "version": "4.5.210",
3
+ "version": "4.5.211",
4
4
  "mmiCompat": "4.x",
5
5
  "description": "MMI workflow skills and org gates delivery.",
6
6
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -10608,6 +10608,10 @@ async function fetchTrainAuthority(repo, deps) {
10608
10608
  }
10609
10609
  const body = await res.json();
10610
10610
  if (typeof body?.train !== "boolean" || !body.role) return { ok: false, error: "malformed train-authority response" };
10611
+ if (body.authenticatedDelegation !== void 0 && (body.authenticatedDelegation !== true || !body.train || body.repo !== repo || body.delegatedActor !== body.login || !/^[A-Za-z0-9-]{1,39}$/.test(body.delegatedActor ?? ""))) {
10612
+ return { ok: false, error: "malformed delegated train-authority response" };
10613
+ }
10614
+ if (body.delegatedActor !== void 0 && body.authenticatedDelegation !== true) return { ok: false, error: "unverified delegated actor" };
10611
10615
  return { ok: true, authority: body };
10612
10616
  } catch (e) {
10613
10617
  return { ok: false, error: e.message };
@@ -15805,12 +15809,16 @@ function isUnknownHold(contest) {
15805
15809
  return !contest.marker && contest.live.length === 0 && contest.unverifiable.length > 0;
15806
15810
  }
15807
15811
  var CONTESTED_CLAIM_RECOVERY = "leave the holder's work untouched and choose another unclaimed item or coordinate a handoff (use --force only for an intentional takeover)";
15812
+ function contestedClaimRecovery(ref) {
15813
+ const repo = /^([^/\s#]+\/[^/\s#]+)#\d+$/.exec(ref)?.[1];
15814
+ return `${CONTESTED_CLAIM_RECOVERY}; list free items with: mmi-cli oracle board read --json${repo ? ` --repo ${repo}` : ""} and pick from its claimable list`;
15815
+ }
15808
15816
  function laneContestMessage(ref, contest, verb) {
15809
15817
  const { holder, why } = laneContestParts(contest);
15810
15818
  if (isUnknownHold(contest)) {
15811
- return verb === "claim" ? `${ref} cannot be verified as free: ${holder} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 ${CONTESTED_CLAIM_RECOVERY}` : `refusing to unclaim ${ref}: ${holder} (${why}); the liveness reads failed so the unclaim is refused fail-closed \u2014 re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
15819
+ return verb === "claim" ? `${ref} cannot be verified as free: ${holder} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 ${contestedClaimRecovery(ref)}` : `refusing to unclaim ${ref}: ${holder} (${why}); the liveness reads failed so the unclaim is refused fail-closed \u2014 re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
15812
15820
  }
15813
- return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); ${CONTESTED_CLAIM_RECOVERY}` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
15821
+ return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); ${contestedClaimRecovery(ref)}` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
15814
15822
  }
15815
15823
  function ghError(e) {
15816
15824
  const err = e;
@@ -19653,10 +19661,10 @@ var rollout_plan_default = {
19653
19661
  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)."
19654
19662
  },
19655
19663
  baseline: {
19656
- version: "4.5.210",
19657
- tag: "v4.5.210",
19658
- commit: "a9e5d838bc19",
19659
- npm: "@mutmutco/cli@4.5.210"
19664
+ version: "4.5.211",
19665
+ tag: "v4.5.211",
19666
+ commit: "cbbcc24fc015",
19667
+ npm: "@mutmutco/cli@4.5.211"
19660
19668
  },
19661
19669
  exitCriterion: "fleet-n-of-n",
19662
19670
  hubOnlyShortcut: "forbidden",
@@ -19673,14 +19681,14 @@ var rollout_plan_default = {
19673
19681
  repo: "mutmutco/mmi-hub",
19674
19682
  role: "canary",
19675
19683
  schedule: "train",
19676
- v3Target: "v4.5.210"
19684
+ v3Target: "v4.5.211"
19677
19685
  }
19678
19686
  ],
19679
19687
  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.",
19680
19688
  rollback: {
19681
19689
  independent: true,
19682
- mechanism: "npm dist-tag latest -> 4.5.210 and redeploy the Hub Lambda from tag v4.5.210 (a9e5d838bc19); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
19683
- v3Target: "v4.5.210 (@mutmutco/cli@4.5.210, tag commit a9e5d838bc19 \u2014 last known-good release carrying the repo-index v4-only contract)"
19690
+ mechanism: "npm dist-tag latest -> 4.5.211 and redeploy the Hub Lambda from tag v4.5.211 (cbbcc24fc015); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
19691
+ v3Target: "v4.5.211 (@mutmutco/cli@4.5.211, tag commit cbbcc24fc015 \u2014 last known-good release carrying the repo-index v4-only contract)"
19684
19692
  }
19685
19693
  },
19686
19694
  {
@@ -21824,9 +21832,12 @@ async function buildTrainApplyContext(deps) {
21824
21832
  const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
21825
21833
  const [owner, name] = repo.split("/");
21826
21834
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
21827
- const login = await readTrainLogin(deps);
21828
21835
  const verdict = await deps.trainAuthority(repo);
21829
21836
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
21837
+ if (verdict.authenticatedDelegation === true && (!verdict.train || !/^[A-Za-z0-9-]{1,39}$/.test(verdict.delegatedActor ?? ""))) {
21838
+ throw new Error("invalid delegated train authority");
21839
+ }
21840
+ const login = verdict.authenticatedDelegation === true ? verdict.delegatedActor : await readTrainLogin(deps);
21830
21841
  if (!verdict.train) {
21831
21842
  throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
21832
21843
  }
@@ -24202,6 +24213,12 @@ function classifyRunnerCost(selector) {
24202
24213
  const labels = /^\[[^\[\]]+\]$/.test(raw) ? raw.slice(1, -1).split(",").map((s) => s.trim().replace(/^(['"])(.*)\1$/, "$2")) : /^(?:-\s*[^\n]+\s*)+$/.test(raw) ? raw.split("\n").map((s) => s.trim().replace(/^-\s*/, "").replace(/^(['"])(.*)\1$/, "$2")) : [raw.replace(/^(['"])(.*)\1$/, "$2")];
24203
24214
  return labels.every((l) => /^[\w-]+$/.test(l)) && labels.some((l) => l.toLowerCase() === "self-hosted") ? "self-hosted" : "unknown";
24204
24215
  }
24216
+ var STANDARD_HOSTED_ALLOWLIST = [
24217
+ ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"]
24218
+ ];
24219
+ function standardHostedLabelFor(repo, path2, jobId) {
24220
+ return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
24221
+ }
24205
24222
  function classifyJobRunnerCost(selector, jobBody, standardHostedLabel) {
24206
24223
  const direct = classifyRunnerCost(selector);
24207
24224
  const matrix = /^\$\{\{\s*matrix\.([\w-]+)\s*\}\}$/.exec(selector.trim());
@@ -24230,14 +24247,15 @@ function parseJson(raw, label) {
24230
24247
  throw new Error(`${label} was not valid JSON`);
24231
24248
  }
24232
24249
  }
24233
- function workflowNeedsHostedRunner(yaml, localWorkflows = /* @__PURE__ */ new Set()) {
24250
+ function workflowNeedsHostedRunner(yaml, localWorkflows = /* @__PURE__ */ new Set(), where) {
24234
24251
  const jobs = parseWorkflowJobs(yaml.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join("\n"));
24235
24252
  return !jobs.length || jobs.some((job) => {
24236
24253
  const callee = /^ uses:\s*\.\/(.+)$/m.exec(job.body)?.[1];
24237
24254
  if (callee && localWorkflows.has(callee)) return false;
24238
24255
  const section = /^ runs-on:([^\n]*(?:\n {6,}[^\n]*)*)/m.exec(job.body)?.[1] ?? "";
24239
24256
  const selector = section.split("\n").map((line) => line.trim()).join("\n");
24240
- return classifyJobRunnerCost(selector, job.body) !== "self-hosted";
24257
+ const cost = classifyJobRunnerCost(selector, job.body, where && standardHostedLabelFor(where.repo, where.path, job.id));
24258
+ return cost !== "self-hosted" && cost !== "standard-hosted";
24241
24259
  });
24242
24260
  }
24243
24261
  async function hostedWorkflowsOf(deps, repo) {
@@ -24253,7 +24271,7 @@ async function hostedWorkflowsOf(deps, repo) {
24253
24271
  workflows.push({ path: f.path, body: Buffer.from(raw.content ?? "", "base64").toString("utf8") });
24254
24272
  }
24255
24273
  const local = new Set(workflows.map((file) => file.path));
24256
- return workflows.filter((file) => workflowNeedsHostedRunner(file.body, local)).map((file) => file.path);
24274
+ return workflows.filter((file) => workflowNeedsHostedRunner(file.body, local, { repo, path: file.path })).map((file) => file.path);
24257
24275
  }
24258
24276
  async function assertActionsJobsCanStart(deps, targetRepo2) {
24259
24277
  let hosted;
@@ -25803,6 +25821,9 @@ function failedRunIdentity(run, repo) {
25803
25821
  function manualRunAction(run, repo) {
25804
25822
  return `safe next action: diagnose with gh run view ${run.runId} --repo ${repo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${run.runId} --repo ${repo} --failed (never re-tag)`;
25805
25823
  }
25824
+ function runsOnShaAction(repo, sha) {
25825
+ return `safe next action: mmi-cli devops read run list --repo ${repo} --commit ${sha} --json databaseId,workflowName,event,status,conclusion to identify the run`;
25826
+ }
25806
25827
  async function readFailedRunsOnSha(deps, repo, sha) {
25807
25828
  try {
25808
25829
  const rows = JSON.parse(await deps.run("gh", [
@@ -25878,7 +25899,7 @@ async function correlateCandidateRunsToRequiredContexts(deps, repo, sha, candida
25878
25899
  if (contexts.length === 0) {
25879
25900
  return {
25880
25901
  unique: false,
25881
- note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required; safe next action: gh run list --repo ${repo} --commit ${sha.slice(0, 12)} to identify the run`
25902
+ note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required; ${runsOnShaAction(repo, sha)}`
25882
25903
  };
25883
25904
  }
25884
25905
  const failedChecks = await readFailedCheckRunsOnSha(deps, repo, sha);
@@ -25931,7 +25952,7 @@ async function transientRecoveryForFailedPhase(deps, input) {
25931
25952
  };
25932
25953
  }
25933
25954
  if (candidates.length === 0) {
25934
- return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun); safe next action: gh run list --repo ${input.repo} --commit ${sha.slice(0, 12)} to identify the run` };
25955
+ return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun); ${runsOnShaAction(input.repo, sha)}` };
25935
25956
  }
25936
25957
  if (enumerated && phase === "ordinaryCi") {
25937
25958
  const correlation = await correlateCandidateRunsToRequiredContexts(deps, input.repo, sha, candidates, extractFailedRequiredContexts(failure));
@@ -30429,9 +30450,6 @@ var NPM_PUBLISH_SAFE_FLOOR_SHA = "b5632c8b940d8025fdb5630e576502e1397c4552";
30429
30450
  var SHARED_ACTION_PINS = [
30430
30451
  { path: "npm-publish", floor: NPM_PUBLISH_SAFE_FLOOR_SHA }
30431
30452
  ];
30432
- var STANDARD_HOSTED_ALLOWLIST = [
30433
- ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"]
30434
- ];
30435
30453
  function auditRunnerFallbacks(repo, files) {
30436
30454
  const checks = [];
30437
30455
  for (const { path: path2, body } of files) {
@@ -30441,9 +30459,7 @@ function auditRunnerFallbacks(repo, files) {
30441
30459
  const callee = /^ uses:\s*(.+)$/m.exec(job.body)?.[1];
30442
30460
  if (callee?.startsWith("./") && files.some((file) => file.path === callee.slice(2))) continue;
30443
30461
  if (callee && /^mutmutco\/MMI-Hub\/\.github\/workflows\/[\w.-]+\.ya?ml@[\w./-]+$/.test(callee)) continue;
30444
- const standardHostedLabel = STANDARD_HOSTED_ALLOWLIST.find(
30445
- ([allowedRepo, allowedPath, allowedJob]) => repo === allowedRepo && path2 === allowedPath && job.id === allowedJob
30446
- )?.[3];
30462
+ const standardHostedLabel = standardHostedLabelFor(repo, path2, job.id);
30447
30463
  const cost = classifyJobRunnerCost(runnerSection.split("\n").map((line) => line.trim()).join("\n"), job.body, standardHostedLabel);
30448
30464
  const ok = !callee && (cost === "self-hosted" || cost === "standard-hosted");
30449
30465
  checks.push({
@@ -49845,7 +49861,12 @@ function trainApplyDeps() {
49845
49861
  runSelf: async (args) => (await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
49846
49862
  trainAuthority: async (repo) => {
49847
49863
  const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
49848
- return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
49864
+ return verdict.ok ? {
49865
+ ok: true,
49866
+ role: verdict.authority.role,
49867
+ train: verdict.authority.train,
49868
+ ...verdict.authority.authenticatedDelegation === true ? { authenticatedDelegation: true, delegatedActor: verdict.authority.delegatedActor } : {}
49869
+ } : verdict;
49849
49870
  },
49850
49871
  // Hub-App-authority dispatch of the central tenant deploy (#953) — the Hub fires the
49851
49872
  // workflow_dispatch with its App token, so the caller needs no MMI-Hub Actions write.
@@ -54899,12 +54920,19 @@ function parseBrokerCreated(v) {
54899
54920
  if (!CREATED_RE.test(v)) throw new Error(`--created must be a GitHub created filter like ">=2026-10-11T00:30:00Z" (got ${JSON.stringify(v)})`);
54900
54921
  return v;
54901
54922
  }
54923
+ function parseBrokerCommit(v) {
54924
+ if (v === void 0) return void 0;
54925
+ if (!/^[0-9a-f]{40}$/i.test(v)) throw new Error(`read: --commit must be a full 40-character commit SHA (received '${v}')`);
54926
+ return v;
54927
+ }
54902
54928
  function buildRunListSpec(o) {
54903
54929
  const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
54904
54930
  const workflow = parseBrokerWorkflow(o.workflow);
54905
54931
  if (workflow) args.push("--workflow", workflow);
54906
54932
  const branch = parseBrokerBranch(o.branch);
54907
54933
  if (branch) args.push("--branch", branch);
54934
+ const commit = parseBrokerCommit(o.commit);
54935
+ if (commit) args.push("--commit", commit);
54908
54936
  const status = parseBrokerRunStatus(o.status);
54909
54937
  if (status) args.push("--status", status);
54910
54938
  const created = parseBrokerCreated(o.created);
@@ -55108,7 +55136,7 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
55108
55136
  }
55109
55137
  await dispatchGh(spec, "read run view");
55110
55138
  });
55111
- run.command("list").description("list workflow runs (workflow/branch/status/created/limit/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--workflow <name>", "filter by workflow name or file", workflowOpt).option("--branch <name>", "filter by branch", branchOpt).option("--status <status>", "filter by run status", runStatusOpt).option("--created <filter>", 'only runs created in this window, e.g. ">=2026-10-11T00:30:00Z" (#8096)', (v) => parseBrokerCreated(v)).option("--limit <n>", "maximum runs to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
55139
+ run.command("list").description("list workflow runs (workflow/branch/commit/status/created/limit/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--workflow <name>", "filter by workflow name or file", workflowOpt).option("--branch <name>", "filter by branch", branchOpt).option("--commit <sha>", "filter by the full 40-character head commit SHA (#8937)", (v) => parseBrokerCommit(v)).option("--status <status>", "filter by run status", runStatusOpt).option("--created <filter>", 'only runs created in this window, e.g. ">=2026-10-11T00:30:00Z" (#8096)', (v) => parseBrokerCreated(v)).option("--limit <n>", "maximum runs to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
55112
55140
  let spec;
55113
55141
  try {
55114
55142
  spec = buildRunListSpec(o);
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.210",
3
+ "version": "4.5.211",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/codex-plugin",
3
- "version": "4.5.210",
3
+ "version": "4.5.211",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",
@@ -21,6 +21,8 @@ accepts an incomplete board. The CLI partitions rows into **Yours**, **Free to c
21
21
  stamps (4 hours without work) hold nothing. Partition rules, stamp details and `--out` rationale:
22
22
  [reference.md](reference.md).
23
23
 
24
+ Read single issues/PRs with `MMI_ACTOR=app mmi-cli devops read issue view <n> --repo <owner/repo> --json <fields>` (or `issue list`, `pr view`, `run view`) and filter the JSON locally: under the App pool, gh `--jq`/`-q`/`--template`/`--web` are refused.
25
+
24
26
  ## Step 2 — render it
25
27
 
26
28
  Plain markdown, never a fenced block. Flat `-` lists under bold heads, never `##` headings, never tables.
@@ -62,6 +64,8 @@ Follow printed owner rulings; record new ones with `mmi-cli oracle issue decisio
62
64
 
63
65
  With several task lanes in one session, pass `worktree: <the mmi-worktrees/<Repo>/<issue>-<slug> path jerv_worktree_create reported>` to `jerv_worktree_exec` / `jerv_worktree_land` every time; they refuse to guess (#5629).
64
66
 
67
+ A fresh mmi worktree has no `.jerv/`: run `mkdir -p .jerv/tmp` before writing scratch files there (PR bodies, batches) or listing it.
68
+
65
69
  ## Step 5 — land one PR per batch
66
70
 
67
71
  No `--issues` or `--closes` flag; put `Closes #6395, #6396, #6397` in the **body**. After merge, board
@@ -29,7 +29,7 @@ Inspect existing children first:
29
29
  mmi-cli oracle issue children <owner/repo#N>
30
30
  ```
31
31
 
32
- Write a JSON array, one object per child, to `.jerv/tmp/epic-<parentNumber>-children.json` (unique per epic; a leftover batch from another epic collides with the new rows):
32
+ Write a JSON array, one object per child, to `.jerv/tmp/epic-<parentNumber>-children.json` (unique per epic; a leftover batch from another epic collides with the new rows). A fresh mmi worktree has no `.jerv/`; run `mkdir -p .jerv/tmp` first:
33
33
 
34
34
  ```json
35
35
  [
@@ -29,6 +29,8 @@ mmi-cli devops train doctor --lane hotfix --heal --json
29
29
 
30
30
  Parse stdout only; never `2>&1`. `ready` false → stop and quote every blocker's `code` and `remedy`; an unread origin is never green. Findings carry `code`, `severity`, `source`, `remedy`, `anchor`.
31
31
 
32
+ Piping `mmi-cli devops api get` into a JSON parser: a refusal exits 1 with empty stdout, so guard both: `out=$(MMI_ACTOR=app mmi-cli devops api get <path>) && [ -n "$out" ] && printf '%s' "$out" | node -e '…'`. One job's metadata: read the allowlisted `repos/{owner}/{repo}/actions/runs/{id}/jobs` and filter `jobs[]` by `id`; `actions/jobs/{id}` is refused (only its `/logs`, with `--out`).
33
+
32
34
  ## Apply
33
35
 
34
36
  From the primary checkout.
@@ -19,6 +19,10 @@ Under the App pool,
19
19
  `--jq`/`-q`/`--template`/`--web` are refused: use
20
20
  `MMI_ACTOR=app mmi-cli devops read issue view <id> --repo <owner/repo> --json comments`
21
21
  (or `pr`/`run view` with their fields); filter returned JSON locally.
22
+ For run logs, never native `gh run view --log`/`--log-failed`: use
23
+ `MMI_ACTOR=app mmi-cli devops read run log <runId> --repo <owner/repo> [--failed | --job <name>] [--tail N]`.
24
+ A refused command writes no output, so a `> file` redirect leaves it empty or missing: read the
25
+ refusal, then rerun the typed form.
22
26
 
23
27
  Read the issue, implementation, callers, and repository policy before editing.
24
28
  Run `mmi-cli tests policy --base <comparison-base>` to determine required checks against your target