@mutmutco/claude-plugin 4.5.151 → 4.5.153
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/cli/dist/main.cjs
CHANGED
|
@@ -7081,6 +7081,12 @@ var APP_ELIGIBLE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
7081
7081
|
"read run watch",
|
|
7082
7082
|
"read run log",
|
|
7083
7083
|
// #8136: bounded jobs/logs REST read, same polling credential.
|
|
7084
|
+
"read pr list",
|
|
7085
|
+
// #8347: stored-field PR list; author/viewer-dependent filters refused.
|
|
7086
|
+
"read pr diff",
|
|
7087
|
+
// #8347: fixed PR diff read; no viewer fields.
|
|
7088
|
+
"read pr queue",
|
|
7089
|
+
// #8347: fixed mergeQueueEntry query; no generic GraphQL or viewer resolution.
|
|
7084
7090
|
"read pr view",
|
|
7085
7091
|
"read pr checks",
|
|
7086
7092
|
"read issue view",
|
|
@@ -7152,7 +7158,7 @@ async function readAppVaultSecret(read) {
|
|
|
7152
7158
|
if (/^(Hub GitHub App allowance used up|rate limited)/.test(reason)) {
|
|
7153
7159
|
throw new AppActorError(`app actor: the vault could not read the App credentials because GitHub is rate-limited, not a denial: ${reason}`, "mint-failed");
|
|
7154
7160
|
}
|
|
7155
|
-
if (/^authority refused|\(HTTP
|
|
7161
|
+
if (/^authority refused|\(HTTP 40[14]\b/.test(reason)) return null;
|
|
7156
7162
|
throw new AppActorError(`app actor: the vault read of the App credentials failed, not a denial: ${reason}`, "mint-failed");
|
|
7157
7163
|
}
|
|
7158
7164
|
}
|
|
@@ -7203,7 +7209,8 @@ async function mintInstallationToken(deps) {
|
|
|
7203
7209
|
}
|
|
7204
7210
|
return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
|
|
7205
7211
|
}
|
|
7206
|
-
async function pollTokenOrUndefined(deps) {
|
|
7212
|
+
async function pollTokenOrUndefined(deps, env = process.env) {
|
|
7213
|
+
if (env[APP_ACTOR_ENV]?.trim() === "personal") return void 0;
|
|
7207
7214
|
try {
|
|
7208
7215
|
return (await mintInstallationToken(deps)).token;
|
|
7209
7216
|
} catch (e) {
|
|
@@ -7212,25 +7219,42 @@ async function pollTokenOrUndefined(deps) {
|
|
|
7212
7219
|
}
|
|
7213
7220
|
}
|
|
7214
7221
|
var notedIneligibleCommand = false;
|
|
7222
|
+
var notedVaultDenied = false;
|
|
7215
7223
|
async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
|
|
7216
7224
|
`)) {
|
|
7217
7225
|
activeQuotaScope = void 0;
|
|
7218
7226
|
activeToken = void 0;
|
|
7219
7227
|
const requested = (env[APP_ACTOR_ENV] ?? "").trim();
|
|
7220
|
-
if (
|
|
7221
|
-
if (requested !== "app") {
|
|
7222
|
-
throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app'
|
|
7228
|
+
if (requested === "personal") return "personal";
|
|
7229
|
+
if (requested && requested !== "app") {
|
|
7230
|
+
throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app' and 'personal' are supported; refusing to guess an identity`);
|
|
7223
7231
|
}
|
|
7224
7232
|
if (!APP_ELIGIBLE_COMMANDS.has(commandPath3)) {
|
|
7225
|
-
if (!notedIneligibleCommand) {
|
|
7233
|
+
if (requested === "app" && !notedIneligibleCommand) {
|
|
7226
7234
|
notedIneligibleCommand = true;
|
|
7227
7235
|
note(`app actor: '${commandPath3}' is personal-only (viewer/@me/authorship) \u2014 running on the personal token despite ${APP_ACTOR_ENV}=app`);
|
|
7228
7236
|
}
|
|
7229
7237
|
return "personal";
|
|
7230
7238
|
}
|
|
7239
|
+
const personalGh = env.GH_TOKEN;
|
|
7240
|
+
const personalGithub = env.GITHUB_TOKEN;
|
|
7231
7241
|
delete env.GH_TOKEN;
|
|
7232
7242
|
delete env.GITHUB_TOKEN;
|
|
7233
|
-
|
|
7243
|
+
let minted;
|
|
7244
|
+
try {
|
|
7245
|
+
minted = await mint();
|
|
7246
|
+
} catch (e) {
|
|
7247
|
+
if (!requested && e instanceof AppActorError && e.code === "vault-denied") {
|
|
7248
|
+
if (personalGh !== void 0) env.GH_TOKEN = personalGh;
|
|
7249
|
+
if (personalGithub !== void 0) env.GITHUB_TOKEN = personalGithub;
|
|
7250
|
+
if (!notedVaultDenied) {
|
|
7251
|
+
notedVaultDenied = true;
|
|
7252
|
+
note("app actor: vault denied App credentials \u2014 running on the personal token on this host");
|
|
7253
|
+
}
|
|
7254
|
+
return "personal";
|
|
7255
|
+
}
|
|
7256
|
+
throw e;
|
|
7257
|
+
}
|
|
7234
7258
|
env.GH_TOKEN = minted.token;
|
|
7235
7259
|
activeQuotaScope = minted.quotaScope;
|
|
7236
7260
|
activeToken = minted.token;
|
|
@@ -7741,7 +7765,7 @@ function setPollTokenProvider(provider) {
|
|
|
7741
7765
|
observedPool.clear();
|
|
7742
7766
|
}
|
|
7743
7767
|
async function pollToken() {
|
|
7744
|
-
if (!pollTokenProvider) return void 0;
|
|
7768
|
+
if (process.env.MMI_ACTOR?.trim() === "personal" || !pollTokenProvider) return void 0;
|
|
7745
7769
|
pollTokenOnce ??= pollTokenProvider().catch((e) => {
|
|
7746
7770
|
if (e instanceof AppActorError && e.code === "mint-failed") throw e;
|
|
7747
7771
|
return void 0;
|
|
@@ -16397,6 +16421,33 @@ function defaultTrackedFirstSegments(root, firstSegments, exec3 = import_node_ch
|
|
|
16397
16421
|
throw error;
|
|
16398
16422
|
}
|
|
16399
16423
|
}
|
|
16424
|
+
function mergeGroupRunbookMessages(root, deps, readFile10, warnings) {
|
|
16425
|
+
const env = deps.env ?? process.env;
|
|
16426
|
+
if (env.GITHUB_EVENT_NAME !== "merge_group") return "";
|
|
16427
|
+
let source = "merge_group event payload";
|
|
16428
|
+
try {
|
|
16429
|
+
const event = JSON.parse(readFile10(env.GITHUB_EVENT_PATH ?? "") ?? "");
|
|
16430
|
+
const match = /^refs\/heads\/gh-readonly-queue\/.+\/pr-([1-9]\d*)-[a-f0-9]+$/.exec(event.merge_group?.head_ref ?? "");
|
|
16431
|
+
const repo = env.GITHUB_REPOSITORY;
|
|
16432
|
+
if (!match || !repo || !/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error("invalid queue source");
|
|
16433
|
+
source = `repos/${repo}/pulls/${match[1]}/commits`;
|
|
16434
|
+
const gh2 = deps.gh ?? ((args) => (0, import_node_child_process6.execFileSync)("gh", args, {
|
|
16435
|
+
cwd: root,
|
|
16436
|
+
encoding: "utf8",
|
|
16437
|
+
windowsHide: true,
|
|
16438
|
+
timeout: 3e4,
|
|
16439
|
+
maxBuffer: CHECK_IGNORE_MAX_BUFFER,
|
|
16440
|
+
env: { ...env, GH_TOKEN: env.GH_TOKEN || env.GITHUB_TOKEN },
|
|
16441
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
16442
|
+
}));
|
|
16443
|
+
const pages = JSON.parse(gh2(["api", source, "--paginate", "--slurp"]));
|
|
16444
|
+
if (!Array.isArray(pages) || !pages.every((page) => Array.isArray(page) && page.every((commit) => typeof commit?.commit?.message === "string"))) throw new Error("invalid commit response");
|
|
16445
|
+
return pages.flat().map((commit) => commit.commit.message).join("\n");
|
|
16446
|
+
} catch {
|
|
16447
|
+
warnings.push({ kind: "runbook-source-unverified", doc: source, line: 0, detail: `failed to read ${source}; using git-log Runbook-Unaffected declarations only` });
|
|
16448
|
+
return "";
|
|
16449
|
+
}
|
|
16450
|
+
}
|
|
16400
16451
|
function runDocRefs(root, deps = {}) {
|
|
16401
16452
|
const readFile10 = deps.readFile ?? readFileOrNull;
|
|
16402
16453
|
const exists = deps.exists ?? import_node_fs17.existsSync;
|
|
@@ -16433,7 +16484,10 @@ function runDocRefs(root, deps = {}) {
|
|
|
16433
16484
|
if (deps.base) {
|
|
16434
16485
|
const git3 = deps.git ?? ((args) => (0, import_node_child_process6.execFileSync)("git", args, { cwd: root, encoding: "utf8", maxBuffer: CHECK_IGNORE_MAX_BUFFER, windowsHide: true }));
|
|
16435
16486
|
const stale = checkRunbookCoverage(runbooks, deps.base, git3);
|
|
16436
|
-
const
|
|
16487
|
+
const messages = git3(["log", "--format=%B", `${deps.base}..HEAD`]);
|
|
16488
|
+
const sourceMessages = stale.length ? mergeGroupRunbookMessages(root, deps, readFile10, warnings) : "";
|
|
16489
|
+
const override = /^Runbook-Unaffected:[ \t]*\S.*$/m.test(`${messages}
|
|
16490
|
+
${sourceMessages}`);
|
|
16437
16491
|
(override ? warnings : findings).push(...stale);
|
|
16438
16492
|
}
|
|
16439
16493
|
return {
|
|
@@ -18666,10 +18720,10 @@ var rollout_plan_default = {
|
|
|
18666
18720
|
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)."
|
|
18667
18721
|
},
|
|
18668
18722
|
baseline: {
|
|
18669
|
-
version: "4.5.
|
|
18670
|
-
tag: "v4.5.
|
|
18671
|
-
commit: "
|
|
18672
|
-
npm: "@mutmutco/cli@4.5.
|
|
18723
|
+
version: "4.5.153",
|
|
18724
|
+
tag: "v4.5.153",
|
|
18725
|
+
commit: "57b23d8d7d5f",
|
|
18726
|
+
npm: "@mutmutco/cli@4.5.153"
|
|
18673
18727
|
},
|
|
18674
18728
|
exitCriterion: "fleet-n-of-n",
|
|
18675
18729
|
hubOnlyShortcut: "forbidden",
|
|
@@ -18686,14 +18740,14 @@ var rollout_plan_default = {
|
|
|
18686
18740
|
repo: "mutmutco/mmi-hub",
|
|
18687
18741
|
role: "canary",
|
|
18688
18742
|
schedule: "train",
|
|
18689
|
-
v3Target: "v4.5.
|
|
18743
|
+
v3Target: "v4.5.153"
|
|
18690
18744
|
}
|
|
18691
18745
|
],
|
|
18692
18746
|
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.",
|
|
18693
18747
|
rollback: {
|
|
18694
18748
|
independent: true,
|
|
18695
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
18696
|
-
v3Target: "v4.5.
|
|
18749
|
+
mechanism: "npm dist-tag latest -> 4.5.153 and redeploy the Hub Lambda from tag v4.5.153 (57b23d8d7d5f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
18750
|
+
v3Target: "v4.5.153 (@mutmutco/cli@4.5.153, tag commit 57b23d8d7d5f \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
18697
18751
|
}
|
|
18698
18752
|
},
|
|
18699
18753
|
{
|
|
@@ -22984,6 +23038,14 @@ async function runTenantControl(deps, options) {
|
|
|
22984
23038
|
if (output) result.taskOutput = clampTaskOutput(output);
|
|
22985
23039
|
}
|
|
22986
23040
|
}
|
|
23041
|
+
if (watch && runId != null && action === "run-task" && conclusion === "success") {
|
|
23042
|
+
try {
|
|
23043
|
+
const pages = JSON.parse(await deps.run("gh", ["api", `repos/${HUB_REPO2}/actions/runs/${runId}/artifacts`, "--paginate", "--slurp"]));
|
|
23044
|
+
result.outputArtifacts = pages.flatMap((page) => page.artifacts ?? []).filter((a) => a.name.startsWith("task-output-") && Number.isSafeInteger(a.id) && a.id > 0).map((a) => ({ id: a.id, name: a.name, runId, downloadCommand: `MMI_ACTOR=app mmi-cli devops api get repos/${HUB_REPO2}/actions/artifacts/${a.id}/zip --out tmp/task-output-${runId}-${a.id}.zip` }));
|
|
23045
|
+
} catch {
|
|
23046
|
+
result.outputArtifactReadError = `Could not read output artifacts; retry with --resume ${runId} --watch (no re-dispatch)`;
|
|
23047
|
+
}
|
|
23048
|
+
}
|
|
22987
23049
|
result.note = conclusion === "success" ? `tenant-control ${action} run succeeded` : conclusion === "failure" ? `tenant-control ${action} run failed \u2014 inspect the run` : runId == null ? `dispatched tenant-control.yml (${action}) \u2014 run not correlated; check the Actions tab` : `dispatched tenant-control.yml (${action}) \u2014 not watched`;
|
|
22988
23050
|
if (conclusion === "pending" && runId != null && action === "run-task") {
|
|
22989
23051
|
result.note += `; run ${runId} \u2014 resume without re-dispatching: mmi-cli devops runtime tenant control ${repo} ${stage} run-task --resume ${runId} --watch`;
|
|
@@ -22998,6 +23060,8 @@ function renderTenantControl(r) {
|
|
|
22998
23060
|
if (r.secrets?.length) {
|
|
22999
23061
|
for (const s of r.secrets) lines2.push(` ${s.key}: ${s.status}`);
|
|
23000
23062
|
}
|
|
23063
|
+
for (const a of r.outputArtifacts ?? []) lines2.push(` output artifact: ${a.name} (id ${a.id}, run ${a.runId})`, ` ${a.downloadCommand}`);
|
|
23064
|
+
if (r.outputArtifactReadError) lines2.push(` ${r.outputArtifactReadError}`);
|
|
23001
23065
|
if (r.taskOutput) {
|
|
23002
23066
|
lines2.push(" task output:");
|
|
23003
23067
|
for (const l of r.taskOutput.split("\n")) lines2.push(` ${l}`);
|
|
@@ -32898,7 +32962,7 @@ function parseTenantTasksVar(raw) {
|
|
|
32898
32962
|
if (!nameRe.test(name)) throw new Error(`org project set: tenantTasks name ${JSON.stringify(name)} must be lowercase kebab-case`);
|
|
32899
32963
|
if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) throw new Error(`org project set: tenantTasks.${name} must be an object`);
|
|
32900
32964
|
const task = rawTask;
|
|
32901
|
-
if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
|
|
32965
|
+
if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "output", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
|
|
32902
32966
|
if (typeof task.service !== "string" || !serviceRe.test(task.service)) throw new Error(`org project set: tenantTasks.${name}.service has an unsafe shape`);
|
|
32903
32967
|
if (!Array.isArray(task.command) || task.command.length < 1 || task.command.length > 32 || task.command.some((arg) => typeof arg !== "string" || arg.length < 1 || arg.length > 256 || /[\u0000\r\n]/.test(arg))) throw new Error(`org project set: tenantTasks.${name}.command must be 1-32 bounded argv strings`);
|
|
32904
32968
|
if (!Array.isArray(task.stages) || task.stages.length < 1 || task.stages.length > 3 || task.stages.some((stage) => typeof stage !== "string" || !validStages.has(stage)) || new Set(task.stages).size !== task.stages.length) throw new Error(`org project set: tenantTasks.${name}.stages must be unique dev/rc/main values`);
|
|
@@ -32908,12 +32972,16 @@ function parseTenantTasksVar(raw) {
|
|
|
32908
32972
|
if (artifact !== "none" && artifact !== "required") throw new Error(`org project set: tenantTasks.${name}.artifact must be none or required`);
|
|
32909
32973
|
const carriesPlaceholder = task.command.some((arg) => arg.includes("{artifact}"));
|
|
32910
32974
|
if (artifact === "required" !== carriesPlaceholder) throw new Error(`org project set: tenantTasks.${name} must use {artifact} exactly when artifact is required`);
|
|
32975
|
+
if (task.output !== void 0 && task.output !== "file") throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
|
|
32976
|
+
const outputPlaceholder = task.command.some((arg) => arg.includes("{output}"));
|
|
32977
|
+
if (task.output === "file" !== outputPlaceholder || task.output !== void 0 && task.secretOutput !== void 0) throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
|
|
32911
32978
|
out[name] = {
|
|
32912
32979
|
service: task.service,
|
|
32913
32980
|
command: task.command,
|
|
32914
32981
|
stages: task.stages,
|
|
32915
32982
|
...task.timeoutSeconds !== void 0 ? { timeoutSeconds: task.timeoutSeconds } : {},
|
|
32916
32983
|
...artifact !== "none" ? { artifact } : {},
|
|
32984
|
+
...task.output === "file" ? { output: "file" } : {},
|
|
32917
32985
|
...task.secretOutput !== void 0 ? { secretOutput: parseTenantTaskSecretOutput(task.secretOutput, `tenantTasks.${name}.secretOutput`) } : {}
|
|
32918
32986
|
};
|
|
32919
32987
|
}
|
|
@@ -32976,7 +33044,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
32976
33044
|
requiredGcpApis: "comma-string",
|
|
32977
33045
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
32978
33046
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
32979
|
-
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
33047
|
+
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",output?:"file",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
32980
33048
|
internalPeers: "JSON {owner/repo:[dev|rc|main]} \u2014 same-box suite peers via the box Caddy; clear with --unset internalPeers (#7886)",
|
|
32981
33049
|
secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[]} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
|
|
32982
33050
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
@@ -53108,6 +53176,46 @@ function parseBrokerAssignee(raw) {
|
|
|
53108
53176
|
return v;
|
|
53109
53177
|
}
|
|
53110
53178
|
var SINGLE_SHOT_TIMEOUT_MS = 6e4;
|
|
53179
|
+
var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
|
|
53180
|
+
var PR_QUEUE_QUERY = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){mergeQueueEntry{state position enqueuedAt estimatedTimeToMerge headCommit{oid}}}}}";
|
|
53181
|
+
function buildPrQueueSpec(number, o) {
|
|
53182
|
+
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
53183
|
+
if (!repo) throw new Error("read pr queue: --repo is required");
|
|
53184
|
+
const [owner, name] = repo.split("/");
|
|
53185
|
+
return { program: "gh", args: ["api", "graphql", "-f", `query=${PR_QUEUE_QUERY}`, "-f", `owner=${owner}`, "-f", `name=${name}`, "-F", `number=${parseBrokerId(number, "PR number")}`], timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
53186
|
+
}
|
|
53187
|
+
function buildPrDiffSpec(number, o) {
|
|
53188
|
+
const args = withRepo(["pr", "diff", parseBrokerId(number, "PR number")], parseBrokerRepo(o.repo, "--repo"));
|
|
53189
|
+
if (o.nameOnly) args.push("--name-only");
|
|
53190
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
53191
|
+
}
|
|
53192
|
+
function parseBrokerPrSearch(raw) {
|
|
53193
|
+
const search = parseBrokerSearch(raw);
|
|
53194
|
+
if (search && /@me/i.test(search)) throw new Error("read: --search resolves the viewer \u2014 viewer-scoped reads stay personal");
|
|
53195
|
+
return search;
|
|
53196
|
+
}
|
|
53197
|
+
function parseBrokerPrState(raw) {
|
|
53198
|
+
if (raw === void 0) return void 0;
|
|
53199
|
+
if (!["open", "closed", "merged", "all"].includes(raw)) throw new Error("read: --state must be one of: open, closed, merged, all");
|
|
53200
|
+
return raw;
|
|
53201
|
+
}
|
|
53202
|
+
function buildPrListSpec(o) {
|
|
53203
|
+
const args = withJson(withRepo(["pr", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
53204
|
+
const state = parseBrokerPrState(o.state);
|
|
53205
|
+
if (state) args.push("--state", state);
|
|
53206
|
+
for (const flag of ["base", "head"]) {
|
|
53207
|
+
const branch = parseBrokerBranch(o[flag]);
|
|
53208
|
+
if (branch) args.push(`--${flag}`, branch);
|
|
53209
|
+
}
|
|
53210
|
+
const label = parseBrokerLabel(o.label);
|
|
53211
|
+
if (label !== void 0) args.push("--label", label);
|
|
53212
|
+
const search = parseBrokerPrSearch(o.search);
|
|
53213
|
+
if (search !== void 0) args.push("--search", search);
|
|
53214
|
+
const assignee = parseBrokerAssignee(o.assignee);
|
|
53215
|
+
if (assignee) args.push("--assignee", assignee);
|
|
53216
|
+
if (o.limit !== void 0) args.push("--limit", String(parseBrokerInt(o.limit, "--limit", 1, 100)));
|
|
53217
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
53218
|
+
}
|
|
53111
53219
|
function withRepo(args, repo) {
|
|
53112
53220
|
return repo ? [...args, "--repo", repo] : args;
|
|
53113
53221
|
}
|
|
@@ -53256,8 +53364,9 @@ async function runBrokerRestChecks(deps, spec, resolveRepo2) {
|
|
|
53256
53364
|
return { rows: read.rows, state: read.state, exitCode: checkRowsExitCode(read.state) };
|
|
53257
53365
|
}
|
|
53258
53366
|
function requireBrokerApp(label) {
|
|
53259
|
-
|
|
53260
|
-
|
|
53367
|
+
const actor = (process.env.MMI_ACTOR ?? "").trim();
|
|
53368
|
+
if (actor && actor !== "app") {
|
|
53369
|
+
fail(`${label}: App-only read \u2014 re-run with spawn-scoped MMI_ACTOR=app (explicit personal opt-out uses gh and the human commands instead)`);
|
|
53261
53370
|
}
|
|
53262
53371
|
}
|
|
53263
53372
|
function registerPrChecksRequiredCommand(prRoot, resolveRepo2, overrides) {
|
|
@@ -53299,7 +53408,7 @@ async function dispatchRestChecksRead(deps, resolveRepo2, label, spec, json) {
|
|
|
53299
53408
|
process.exitCode = result.exitCode;
|
|
53300
53409
|
}
|
|
53301
53410
|
function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
53302
|
-
const ghRun = overrides?.ghRunner ?? (async (args, opts) => (await execFileP("gh", args, { timeout: opts.timeoutMs, env: opts.env })).stdout);
|
|
53411
|
+
const ghRun = overrides?.ghRunner ?? (async (args, opts) => (await execFileP("gh", args, { timeout: opts.timeoutMs, env: opts.env, maxBuffer: BROKER_MAX_BUFFER })).stdout);
|
|
53303
53412
|
const restGhApi = overrides?.restGhApi ?? ((env) => ghApiWithEnv(env));
|
|
53304
53413
|
const deps = { readRequired: readRequiredCheckRows, fetchSnapshot: fetchRestPrSnapshot, fetchRows: fetchHeadCheckRows, restGhApi };
|
|
53305
53414
|
const dispatchGh = (spec, label) => (async () => {
|
|
@@ -53352,7 +53461,28 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
53352
53461
|
}
|
|
53353
53462
|
await dispatchGh(spec, "read run watch");
|
|
53354
53463
|
});
|
|
53355
|
-
const pr = read.command("pr").description("pull-request reads \u2014 view
|
|
53464
|
+
const pr = read.command("pr").description("pull-request reads \u2014 view/list/diff ride gh; queue uses fixed GraphQL; checks is REST-backed (deterministic closed-custody adapter; native was EMPTY only on the historical #3035 fixture)");
|
|
53465
|
+
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--json [fields]", "native gh JSON fields", jsonOpt).addOption(new Option("--state <state>", "open|closed|merged|all").choices(["open", "closed", "merged", "all"])).option("--base <branch>", "base branch", branchOpt).option("--head <branch>", "head branch", branchOpt).option("--label <label>", "filter by label", labelOpt).option("--search <query>", "viewer-free search", (v) => parseBrokerPrSearch(v)).option("--assignee <login>", "explicit assignee (viewer refused)", assigneeOpt).option("--limit <n>", "maximum PRs (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
53466
|
+
await dispatchGh(buildPrListSpec(o), "read pr list");
|
|
53467
|
+
});
|
|
53468
|
+
pr.command("diff").description("read a bounded PR diff on the App pool").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--name-only", "only changed file names").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
53469
|
+
await dispatchGh(buildPrDiffSpec(number, o), "read pr diff");
|
|
53470
|
+
});
|
|
53471
|
+
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").requiredOption("--repo <owner/repo>", "target repo", repoOpt).option("--json", "machine-readable mergeQueueEntry (null when not queued)").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
53472
|
+
const spec = buildPrQueueSpec(number, o);
|
|
53473
|
+
requireBrokerApp("read pr queue");
|
|
53474
|
+
try {
|
|
53475
|
+
process.exitCode = await runBrokerGh(async (args, opts) => {
|
|
53476
|
+
const result = JSON.parse(await ghRun(args, opts));
|
|
53477
|
+
if (result.errors?.length) throw new Error(JSON.stringify(result.errors));
|
|
53478
|
+
const pr2 = result.data?.repository?.pullRequest;
|
|
53479
|
+
if (!pr2) throw new Error("PR not found or not accessible on the App pool");
|
|
53480
|
+
return JSON.stringify(pr2.mergeQueueEntry) + "\n";
|
|
53481
|
+
}, spec);
|
|
53482
|
+
} catch (e) {
|
|
53483
|
+
return fail(`read pr queue: ${e.message}`);
|
|
53484
|
+
}
|
|
53485
|
+
});
|
|
53356
53486
|
pr.command("view").description("read a PR as gh JSON (comments/json; --json supports statusCheckRollup selection; 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("--comments", "include PR comments").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
53357
53487
|
let spec;
|
|
53358
53488
|
try {
|
|
@@ -53423,14 +53553,14 @@ var RUNS_QUERY_VALUE = {
|
|
|
53423
53553
|
created: /^[0-9:TZ.<>=-]+$/,
|
|
53424
53554
|
actor: /^[A-Za-z0-9_.\[\]-]+$/
|
|
53425
53555
|
};
|
|
53426
|
-
function validRunsQuery(query) {
|
|
53556
|
+
function validRunsQuery(query, allowed = RUNS_QUERY_VALUE) {
|
|
53427
53557
|
const seen = /* @__PURE__ */ new Set();
|
|
53428
53558
|
for (const pair of query.split("&")) {
|
|
53429
53559
|
const eq = pair.indexOf("=");
|
|
53430
53560
|
if (eq < 1) return false;
|
|
53431
53561
|
const key = pair.slice(0, eq);
|
|
53432
53562
|
const value = pair.slice(eq + 1);
|
|
53433
|
-
const re = Object.prototype.hasOwnProperty.call(
|
|
53563
|
+
const re = Object.prototype.hasOwnProperty.call(allowed, key) ? allowed[key] : void 0;
|
|
53434
53564
|
if (!re || seen.has(key) || !re.test(value)) return false;
|
|
53435
53565
|
seen.add(key);
|
|
53436
53566
|
}
|
|
@@ -53442,7 +53572,12 @@ var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+(?:/at
|
|
|
53442
53572
|
var ACTIONS_ARTIFACTS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/artifacts$`);
|
|
53443
53573
|
var ACTIONS_ARTIFACT_ZIP_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/artifacts/\\d+/zip$`);
|
|
53444
53574
|
var ACTIONS_JOB_LOGS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/jobs/\\d+/logs$`);
|
|
53445
|
-
var
|
|
53575
|
+
var RELEASE_LIST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases$`);
|
|
53576
|
+
var RELEASE_LIST_QUERY_VALUE = { per_page: RUNS_QUERY_VALUE.per_page, page: RUNS_QUERY_VALUE.page };
|
|
53577
|
+
var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
|
|
53578
|
+
var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
|
|
53579
|
+
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
53580
|
+
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
53446
53581
|
function parseApiGetPath(rawPath) {
|
|
53447
53582
|
const path2 = rawPath.trim();
|
|
53448
53583
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
@@ -53458,6 +53593,11 @@ function parseApiGetPath(rawPath) {
|
|
|
53458
53593
|
if (ACTIONS_ARTIFACTS_RE.test(path2)) return { kind: "actions-artifacts", path: path2, paginate: true };
|
|
53459
53594
|
if (ACTIONS_ARTIFACT_ZIP_RE.test(path2)) return { kind: "actions-artifact-zip", path: path2, paginate: false };
|
|
53460
53595
|
if (ACTIONS_JOB_LOGS_RE.test(path2)) return { kind: "actions-job-logs", path: path2, paginate: false };
|
|
53596
|
+
if (RELEASE_LIST_RE.test(base) && (q === -1 || validRunsQuery(query, RELEASE_LIST_QUERY_VALUE))) return { kind: "release-list", path: path2, paginate: false };
|
|
53597
|
+
if (RELEASE_LATEST_RE.test(path2)) return { kind: "release-latest", path: path2, paginate: false };
|
|
53598
|
+
const tag = RELEASE_TAG_RE.exec(path2)?.[1];
|
|
53599
|
+
if (tag && !tag.includes("..")) return { kind: "release-tag", path: path2, paginate: false };
|
|
53600
|
+
if (RELEASE_ASSET_RE.test(path2)) return { kind: "release-asset", path: path2, paginate: false };
|
|
53461
53601
|
if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
|
|
53462
53602
|
return { kind: "contents", path: path2, paginate: false };
|
|
53463
53603
|
}
|
|
@@ -53469,7 +53609,8 @@ function buildApiGetArgs(path2, accept) {
|
|
|
53469
53609
|
const req = parseApiGetPath(path2);
|
|
53470
53610
|
const args = ["api", req.path];
|
|
53471
53611
|
if (req.paginate) args.push("--paginate");
|
|
53472
|
-
const
|
|
53612
|
+
const requestedHeader = acceptHeaderFor(accept);
|
|
53613
|
+
const header = req.kind === "release-asset" ? "application/octet-stream" : requestedHeader;
|
|
53473
53614
|
if (header !== API_ACCEPT_HEADERS.json) args.push("-H", `Accept: ${header}`);
|
|
53474
53615
|
return args;
|
|
53475
53616
|
}
|
|
@@ -53486,16 +53627,16 @@ async function readAppApi(path2, runner = apiReadRunner) {
|
|
|
53486
53627
|
}
|
|
53487
53628
|
function registerApiReadCommands(program3, overrides) {
|
|
53488
53629
|
const spawn6 = overrides?.runner ?? apiReadRunner;
|
|
53489
|
-
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts)");
|
|
53630
|
+
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts, releases)");
|
|
53490
53631
|
api.command("get").description("GET one allowlisted REST path and print the response body to stdout (JSON, or raw file bytes with --accept raw); refuses everything outside the allowlist").option("--accept <type>", "response media type: json (default) or raw (file bytes)", (v) => {
|
|
53491
53632
|
acceptHeaderFor(v);
|
|
53492
53633
|
return v.trim().toLowerCase();
|
|
53493
|
-
}, "json").option("--out <file>", "write
|
|
53494
|
-
const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs";
|
|
53495
|
-
if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs`);
|
|
53496
|
-
const
|
|
53497
|
-
if (
|
|
53498
|
-
const env =
|
|
53634
|
+
}, "json").option("--out <file>", "write response bytes to a file (required for, and only valid with, actions artifact zips, job logs, and release assets)").argument("<path>", "allowlisted GET path (shapes named on refusal)", (v) => parseApiGetPath(v)).action(async (req, o) => {
|
|
53635
|
+
const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs" || req.kind === "release-asset";
|
|
53636
|
+
if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs and repos/{owner}/{repo}/releases/assets/{id}`);
|
|
53637
|
+
const appRoute = req.kind.startsWith("actions-") || req.kind.startsWith("release-");
|
|
53638
|
+
if (appRoute) requireBrokerApp("api get");
|
|
53639
|
+
const env = appRoute || process.env.MMI_ACTOR?.trim() !== "personal" ? brokerChildEnv() : void 0;
|
|
53499
53640
|
let body;
|
|
53500
53641
|
try {
|
|
53501
53642
|
body = await runApiGet(
|
|
@@ -57203,7 +57344,7 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
57203
57344
|
return reportWrite("org project set-deploy", res);
|
|
57204
57345
|
});
|
|
57205
57346
|
var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for every stage of your own repo \u2014 new-task-only; editing an existing task stays master-only (`org project set --var tenantTasks=...`)");
|
|
57206
|
-
projectTenantTasks.command("declare <name>").description(
|
|
57347
|
+
projectTenantTasks.command("declare <name>").description('register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?,output?:"file"}; required artifact tasks must carry {artifact} in the command argv.').option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", `task command as argv (repeatable; required unless --var carries it). An argv token that starts with "-" (python -m mod) is read as an option and the whole call fails to parse: declare such a command with --var '{"service":"<svc>","command":["python","-m","mod"],"stages":["dev"]}' instead (#7668)`).option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..10800 (1..3600 with a secretOutput)").option("--output <file>", "return {output} as a private 1-day Actions artifact (cannot combine with secretOutput)").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", 'full task object {service,command[],stages[],timeoutSeconds?,artifact?,output?:"file"} \u2014 use instead of the individual flags. A task carrying secretOutput:{path,vault:{repo,key},overwrite?} is master-only; declare that via `org project set --var tenantTasks=...`').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
|
|
57207
57348
|
const cfg = await loadConfig();
|
|
57208
57349
|
let target;
|
|
57209
57350
|
try {
|
|
@@ -57228,7 +57369,8 @@ projectTenantTasks.command("declare <name>").description("register a NEW tenant
|
|
|
57228
57369
|
command: o.command,
|
|
57229
57370
|
stages: o.stages.split(",").map((s) => s.trim()).filter(Boolean),
|
|
57230
57371
|
...o.timeoutSeconds !== void 0 ? { timeoutSeconds: Number(o.timeoutSeconds) } : {},
|
|
57231
|
-
...o.artifact !== void 0 ? { artifact: o.artifact } : {}
|
|
57372
|
+
...o.artifact !== void 0 ? { artifact: o.artifact } : {},
|
|
57373
|
+
...o.output !== void 0 ? { output: o.output } : {}
|
|
57232
57374
|
};
|
|
57233
57375
|
}
|
|
57234
57376
|
if (task && typeof task === "object" && task.secretOutput !== void 0) {
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -54,6 +54,7 @@ concurrency:
|
|
|
54
54
|
permissions:
|
|
55
55
|
actions: read # #8206: exact-SHA development workflow verdict
|
|
56
56
|
contents: read
|
|
57
|
+
pull-requests: read # #8343: source commit declarations omitted by queue squashes
|
|
57
58
|
# MMI-Hub#7542: the product image build below reads org private packages with the workflow's
|
|
58
59
|
# own ephemeral token (NODE_AUTH_TOKEN) — the same reader the central deploy uses.
|
|
59
60
|
packages: read
|