@mutmutco/claude-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.
- package/.claude-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +51 -23
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +4 -0
- package/skills/epic/SKILL.md +1 -1
- package/skills/hotfix/SKILL.md +2 -0
- package/skills/org-guide/references/work-and-releases.md +4 -0
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 ${
|
|
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}); ${
|
|
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.
|
|
19657
|
-
tag: "v4.5.
|
|
19658
|
-
commit: "
|
|
19659
|
-
npm: "@mutmutco/cli@4.5.
|
|
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.
|
|
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.
|
|
19683
|
-
v3Target: "v4.5.
|
|
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
|
-
|
|
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;
|
|
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);
|
|
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 =
|
|
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 ? {
|
|
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
package/package.json
CHANGED
package/skills/board/SKILL.md
CHANGED
|
@@ -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
|
package/skills/epic/SKILL.md
CHANGED
|
@@ -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
|
[
|
package/skills/hotfix/SKILL.md
CHANGED
|
@@ -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
|