@mutmutco/claude-plugin 4.5.114 → 4.5.115
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 +639 -23
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -957,6 +957,8 @@ var init_house_map = __esm({
|
|
|
957
957
|
// register/run/park schedule rows under the one lane contract
|
|
958
958
|
// --- devops — shipping --------------------------------------------------------------------------
|
|
959
959
|
pr: "devops",
|
|
960
|
+
read: "devops",
|
|
961
|
+
// closed typed GitHub reads on the App pool (run/pr/issue broker, #8042)
|
|
960
962
|
api: "devops",
|
|
961
963
|
// allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
|
|
962
964
|
ci: "devops",
|
|
@@ -5746,6 +5748,19 @@ var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
|
5746
5748
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
5747
5749
|
var APP_ELIGIBLE_COMMANDS = /* @__PURE__ */ new Set([
|
|
5748
5750
|
"pr checks-wait",
|
|
5751
|
+
// #8042: App-native required-only read (`pr checks-required`) — REST check/status rows scoped by
|
|
5752
|
+
// fetchRequiredCheckContexts, never native `gh pr checks` for the required verdict (historical EMPTY
|
|
5753
|
+
// App table on the #3035 fixture/profile — REST is the deterministic adapter, not a claim native
|
|
5754
|
+
// always fails: same-token parity on later fixtures agrees).
|
|
5755
|
+
"pr checks-required",
|
|
5756
|
+
// #8042: the closed typed read broker — run/pr/issue families, REST pr-checks, allowlisted api get.
|
|
5757
|
+
"read run view",
|
|
5758
|
+
"read run list",
|
|
5759
|
+
"read run watch",
|
|
5760
|
+
"read pr view",
|
|
5761
|
+
"read pr checks",
|
|
5762
|
+
"read issue view",
|
|
5763
|
+
"read issue list",
|
|
5749
5764
|
"pr view",
|
|
5750
5765
|
"issue list",
|
|
5751
5766
|
"issue view",
|
|
@@ -6308,6 +6323,9 @@ async function ghReadEnv() {
|
|
|
6308
6323
|
const appToken = await pollToken();
|
|
6309
6324
|
return appToken ? { ...process.env, GH_TOKEN: appToken } : void 0;
|
|
6310
6325
|
}
|
|
6326
|
+
function ghApiWithEnv(env) {
|
|
6327
|
+
return (args) => defaultGhApi(args, env);
|
|
6328
|
+
}
|
|
6311
6329
|
var INCLUDED_HEADER_BLOCK = /^(?:\r?\n)?HTTP\/\d(?:\.\d)? (\d{3})[^\n]*\n(?:[^\n]+\n)*?\r?\n/gm;
|
|
6312
6330
|
function parseIncludedOutput(out) {
|
|
6313
6331
|
let status;
|
|
@@ -6361,7 +6379,7 @@ function observedCorePool(source, nowMs) {
|
|
|
6361
6379
|
const seen = source ? observedPool.get(source) : void 0;
|
|
6362
6380
|
return seen && nowMs - seen.atMs <= OBSERVED_POOL_MAX_AGE_MS && seen.reset * 1e3 > nowMs ? { remaining: seen.remaining, reset: seen.reset } : void 0;
|
|
6363
6381
|
}
|
|
6364
|
-
async function defaultGhApi(args) {
|
|
6382
|
+
async function defaultGhApi(args, env = process.env) {
|
|
6365
6383
|
const appToken = await pollToken();
|
|
6366
6384
|
const source = appToken ? "app" : "user";
|
|
6367
6385
|
const key = `${source}\0${args.join("\0")}`;
|
|
@@ -6372,7 +6390,9 @@ async function defaultGhApi(args) {
|
|
|
6372
6390
|
timeout: REST_GH_TIMEOUT_MS,
|
|
6373
6391
|
// Only on the App source: passing `env` at all replaces the inherited environment wholesale,
|
|
6374
6392
|
// and `gh` needs the rest of it (PATH, GH_HOST, config home). Ambient rides the inherited env.
|
|
6375
|
-
|
|
6393
|
+
// #8042: callers may pass a scrubbed env (brokerChildEnv) to pin the host and drop enterprise
|
|
6394
|
+
// credentials; the default is the process env (legacy waiter behavior, unchanged).
|
|
6395
|
+
...source === "app" && appToken ? { env: { ...env, GH_TOKEN: appToken } } : env === process.env ? {} : { env }
|
|
6376
6396
|
}));
|
|
6377
6397
|
} catch (e) {
|
|
6378
6398
|
const out = e.stdout;
|
|
@@ -6601,13 +6621,13 @@ async function fetchRestClosingGuardPayload(prNumber, repo, gh2 = defaultGhApi)
|
|
|
6601
6621
|
return { pr, commits };
|
|
6602
6622
|
}
|
|
6603
6623
|
async function fetchHeadCheckRuns(headSha, repo, gh2) {
|
|
6604
|
-
const runsOut = await gh2(["--paginate", `repos/${repo}/commits/${headSha}/check-runs?per_page=100`, "--jq", ".check_runs[] | {id, name, status, conclusion, app_id: .app.id, details_url}"]);
|
|
6624
|
+
const runsOut = await gh2(["--paginate", `repos/${repo}/commits/${headSha}/check-runs?per_page=100`, "--jq", ".check_runs[] | {id, name, status, conclusion, app_id: .app.id, details_url, started_at, completed_at}"]);
|
|
6605
6625
|
return dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
|
|
6606
6626
|
}
|
|
6607
6627
|
async function fetchHeadCheckEntries(headSha, repo, gh2) {
|
|
6608
6628
|
const [runs, statusesOut] = await Promise.all([
|
|
6609
6629
|
fetchHeadCheckRuns(headSha, repo, gh2),
|
|
6610
|
-
gh2(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state}"])
|
|
6630
|
+
gh2(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state, target_url, description}"])
|
|
6611
6631
|
]);
|
|
6612
6632
|
const statuses = parseNdjsonLines(statusesOut);
|
|
6613
6633
|
return [
|
|
@@ -6654,6 +6674,212 @@ async function pollRestPrChecksWithWorkflowEvidence(prNumber, repo, gh2 = defaul
|
|
|
6654
6674
|
async function pollRestCommitChecks(sha, repo, gh2 = defaultGhApi) {
|
|
6655
6675
|
return derivePollState(partitionByRequired(await fetchHeadCheckEntries(sha, repo, gh2), null).relevant);
|
|
6656
6676
|
}
|
|
6677
|
+
var GH_ZERO_TIME = "0001-01-01T00:00:00Z";
|
|
6678
|
+
var GH_PR_CHECKS_JSON_FIELDS = ["bucket", "completedAt", "description", "link", "name", "startedAt", "state"];
|
|
6679
|
+
function nativeStateForRun(run) {
|
|
6680
|
+
if (run.status !== "completed") {
|
|
6681
|
+
const raw2 = (run.status ?? "pending").toUpperCase();
|
|
6682
|
+
if (raw2 === "QUEUED" || raw2 === "IN_PROGRESS" || raw2 === "WAITING" || raw2 === "REQUESTED" || raw2 === "PENDING") return raw2;
|
|
6683
|
+
return "PENDING";
|
|
6684
|
+
}
|
|
6685
|
+
const raw = (run.conclusion ?? "").toUpperCase();
|
|
6686
|
+
if (raw === "SUCCESS" || raw === "SKIPPED" || raw === "NEUTRAL" || raw === "ERROR" || raw === "FAILURE" || raw === "TIMED_OUT" || raw === "ACTION_REQUIRED" || raw === "CANCELLED" || raw === "STALE") return raw;
|
|
6687
|
+
return "PENDING";
|
|
6688
|
+
}
|
|
6689
|
+
function nativeStateForStatus(state) {
|
|
6690
|
+
const raw = (state ?? "").toUpperCase();
|
|
6691
|
+
if (raw === "SUCCESS" || raw === "FAILURE" || raw === "ERROR" || raw === "PENDING" || raw === "EXPECTED") return raw;
|
|
6692
|
+
return "PENDING";
|
|
6693
|
+
}
|
|
6694
|
+
function bucketForNativeState(state) {
|
|
6695
|
+
if (state === "SUCCESS") return "pass";
|
|
6696
|
+
if (state === "SKIPPED" || state === "NEUTRAL") return "skipping";
|
|
6697
|
+
if (state === "FAILURE" || state === "ERROR" || state === "TIMED_OUT" || state === "ACTION_REQUIRED") return "fail";
|
|
6698
|
+
if (state === "CANCELLED") return "cancel";
|
|
6699
|
+
return "pending";
|
|
6700
|
+
}
|
|
6701
|
+
async function fetchHeadCheckRows(headSha, repo, gh2 = defaultGhApi) {
|
|
6702
|
+
const [runs, statusesOut] = await Promise.all([
|
|
6703
|
+
fetchHeadCheckRuns(headSha, repo, gh2),
|
|
6704
|
+
gh2(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state, target_url, description}"])
|
|
6705
|
+
]);
|
|
6706
|
+
const statuses = parseNdjsonLines(statusesOut);
|
|
6707
|
+
const rows = [];
|
|
6708
|
+
const nameAppIds = /* @__PURE__ */ new Map();
|
|
6709
|
+
for (const run of runs) {
|
|
6710
|
+
if (AUXILIARY_RELEASE_CHECK_CONTEXTS.has(run.name ?? "")) continue;
|
|
6711
|
+
const runName = run.name ?? `check-run ${run.id ?? "unknown"}`;
|
|
6712
|
+
if (typeof run.app_id === "number") {
|
|
6713
|
+
const known = nameAppIds.get(runName) ?? [];
|
|
6714
|
+
if (!known.includes(run.app_id)) known.push(run.app_id);
|
|
6715
|
+
nameAppIds.set(runName, known);
|
|
6716
|
+
}
|
|
6717
|
+
const state = nativeStateForRun(run);
|
|
6718
|
+
rows.push({
|
|
6719
|
+
bucket: bucketForNativeState(state),
|
|
6720
|
+
completedAt: typeof run.completed_at === "string" && run.completed_at ? run.completed_at : GH_ZERO_TIME,
|
|
6721
|
+
// Check-runs carry no description in this endpoint (no output join — never invented).
|
|
6722
|
+
description: "",
|
|
6723
|
+
link: typeof run.details_url === "string" && run.details_url ? run.details_url : "",
|
|
6724
|
+
name: runName,
|
|
6725
|
+
startedAt: typeof run.started_at === "string" && run.started_at ? run.started_at : GH_ZERO_TIME,
|
|
6726
|
+
state
|
|
6727
|
+
});
|
|
6728
|
+
}
|
|
6729
|
+
for (const s of statuses) {
|
|
6730
|
+
if (AUXILIARY_RELEASE_CHECK_CONTEXTS.has(s.context ?? "")) continue;
|
|
6731
|
+
const state = nativeStateForStatus(s.state);
|
|
6732
|
+
rows.push({
|
|
6733
|
+
bucket: bucketForNativeState(state),
|
|
6734
|
+
// Legacy statuses carry no timestamps: native-compatible zero, never omitted.
|
|
6735
|
+
completedAt: GH_ZERO_TIME,
|
|
6736
|
+
description: typeof s.description === "string" ? s.description : "",
|
|
6737
|
+
link: typeof s.target_url === "string" && s.target_url ? s.target_url : "",
|
|
6738
|
+
name: s.context ?? "unknown-status",
|
|
6739
|
+
startedAt: GH_ZERO_TIME,
|
|
6740
|
+
state
|
|
6741
|
+
});
|
|
6742
|
+
}
|
|
6743
|
+
return { rows, nameAppIds };
|
|
6744
|
+
}
|
|
6745
|
+
function identitiesFromProtectionPayload(parsed) {
|
|
6746
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
6747
|
+
throw new Error("malformed requirements metadata: protection body is not an object");
|
|
6748
|
+
}
|
|
6749
|
+
const { contexts, checks } = parsed;
|
|
6750
|
+
const entries = [];
|
|
6751
|
+
if (checks !== void 0) {
|
|
6752
|
+
if (!Array.isArray(checks)) throw new Error("malformed requirements metadata: protection checks is not an array");
|
|
6753
|
+
for (const c of checks) {
|
|
6754
|
+
entries.push(protectionCheckToEntry(c, "protection checks[]"));
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
if (contexts !== void 0) {
|
|
6758
|
+
if (!Array.isArray(contexts)) throw new Error("malformed requirements metadata: protection contexts is not an array");
|
|
6759
|
+
for (const c of contexts) {
|
|
6760
|
+
if (typeof c !== "string" || !c) throw new Error("malformed requirements metadata: protection contexts holds a non-string entry");
|
|
6761
|
+
if (!entries.some((e) => e.context === c)) entries.push({ context: c, appId: null });
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
if (!Array.isArray(checks) && !Array.isArray(contexts)) {
|
|
6765
|
+
throw new Error("malformed requirements metadata: protection body carries neither contexts nor checks");
|
|
6766
|
+
}
|
|
6767
|
+
return entries;
|
|
6768
|
+
}
|
|
6769
|
+
function protectionCheckToEntry(c, where) {
|
|
6770
|
+
if (typeof c?.context !== "string" || !c.context) {
|
|
6771
|
+
throw new Error(`malformed requirements metadata: ${where} holds an entry without a string context`);
|
|
6772
|
+
}
|
|
6773
|
+
return { context: c.context, appId: readAppId(c, where) };
|
|
6774
|
+
}
|
|
6775
|
+
function readAppId(c, where) {
|
|
6776
|
+
if (c.app_id === void 0 || c.app_id === null) {
|
|
6777
|
+
if (c.integration_id === void 0 || c.integration_id === null) return null;
|
|
6778
|
+
if (typeof c.integration_id !== "number") throw new Error(`malformed requirements metadata: ${where} holds a non-numeric integration id`);
|
|
6779
|
+
return c.integration_id;
|
|
6780
|
+
}
|
|
6781
|
+
if (typeof c.app_id !== "number") throw new Error(`malformed requirements metadata: ${where} holds a non-numeric app id`);
|
|
6782
|
+
return c.app_id;
|
|
6783
|
+
}
|
|
6784
|
+
function identitiesFromRulesetPayload(parsed) {
|
|
6785
|
+
if (!Array.isArray(parsed)) {
|
|
6786
|
+
throw new Error("malformed requirements metadata: ruleset body is not an array");
|
|
6787
|
+
}
|
|
6788
|
+
const entries = [];
|
|
6789
|
+
for (const rule of parsed) {
|
|
6790
|
+
if (!rule || typeof rule.type !== "string" || !rule.type) throw new Error("malformed requirements metadata: ruleset entry has no rule type");
|
|
6791
|
+
if (rule.type !== "required_status_checks") continue;
|
|
6792
|
+
const list = rule.parameters?.required_status_checks;
|
|
6793
|
+
if (!Array.isArray(list)) throw new Error("malformed requirements metadata: ruleset required_status_checks is not an array");
|
|
6794
|
+
for (const check of list) {
|
|
6795
|
+
entries.push(protectionCheckToEntry(check, "ruleset required_status_checks"));
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
return entries;
|
|
6799
|
+
}
|
|
6800
|
+
async function fetchRequiredCheckIdentities(repo, branch, gh2 = defaultGhApi) {
|
|
6801
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
6802
|
+
const keyOf = (context, appId) => `${context}\0${appId ?? ""}`;
|
|
6803
|
+
const add = (entry) => {
|
|
6804
|
+
const key = keyOf(entry.context, entry.appId);
|
|
6805
|
+
if (!byIdentity.has(key)) byIdentity.set(key, entry);
|
|
6806
|
+
};
|
|
6807
|
+
try {
|
|
6808
|
+
const raw = await gh2([`repos/${repo}/branches/${encodeURIComponent(branch)}/protection/required_status_checks`]);
|
|
6809
|
+
let parsed;
|
|
6810
|
+
try {
|
|
6811
|
+
parsed = JSON.parse(raw);
|
|
6812
|
+
} catch {
|
|
6813
|
+
throw new Error("malformed requirements metadata: protection body is not JSON");
|
|
6814
|
+
}
|
|
6815
|
+
for (const entry of identitiesFromProtectionPayload(parsed)) add(entry);
|
|
6816
|
+
} catch (e) {
|
|
6817
|
+
if (!isNotFoundError(e)) {
|
|
6818
|
+
if (e instanceof Error && e.message.startsWith("malformed requirements metadata")) throw e;
|
|
6819
|
+
return null;
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
try {
|
|
6823
|
+
const raw = await gh2([`repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]);
|
|
6824
|
+
let parsed;
|
|
6825
|
+
try {
|
|
6826
|
+
parsed = JSON.parse(raw);
|
|
6827
|
+
} catch {
|
|
6828
|
+
throw new Error("malformed requirements metadata: ruleset body is not JSON");
|
|
6829
|
+
}
|
|
6830
|
+
for (const entry of identitiesFromRulesetPayload(parsed)) add(entry);
|
|
6831
|
+
} catch (e) {
|
|
6832
|
+
if (!isNotFoundError(e)) {
|
|
6833
|
+
if (e instanceof Error && e.message.startsWith("malformed requirements metadata")) throw e;
|
|
6834
|
+
return null;
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
return [...byIdentity.values()];
|
|
6838
|
+
}
|
|
6839
|
+
async function readRequiredCheckRows(prNumber, repo, gh2 = defaultGhApi) {
|
|
6840
|
+
const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh2);
|
|
6841
|
+
let entries;
|
|
6842
|
+
try {
|
|
6843
|
+
entries = await fetchRequiredCheckIdentities(repo, snapshot.baseRef, gh2);
|
|
6844
|
+
} catch (e) {
|
|
6845
|
+
throw new Error(`required-context lookup failed for ${repo}@${snapshot.baseRef} (${readErrorText(e)}) \u2014 refusing a required verdict against unknown requirements`);
|
|
6846
|
+
}
|
|
6847
|
+
if (entries === null) {
|
|
6848
|
+
throw new Error(`required-context lookup failed for ${repo}@${snapshot.baseRef} (protection/ruleset read failed) \u2014 refusing a required verdict against unknown requirements`);
|
|
6849
|
+
}
|
|
6850
|
+
const appScope = [...entries].sort((a, b) => a.context.localeCompare(b.context));
|
|
6851
|
+
const required = [...new Set(appScope.map((e) => e.context))];
|
|
6852
|
+
const { rows: reported, nameAppIds } = await fetchHeadCheckRows(snapshot.headSha, repo, gh2);
|
|
6853
|
+
const scoped = required.length > 0;
|
|
6854
|
+
const requiredNames = new Set(required);
|
|
6855
|
+
const inScope = scoped ? reported.filter((row) => requiredNames.has(row.name)) : reported;
|
|
6856
|
+
const reportedNames = new Set(reported.map((row) => row.name));
|
|
6857
|
+
const missing = appScope.filter((entry) => !isRequirementMet(entry, reportedNames, nameAppIds)).map((entry) => entry.appId === null ? entry.context : `${entry.context} (app ${entry.appId})`);
|
|
6858
|
+
let state;
|
|
6859
|
+
if (missing.length > 0) {
|
|
6860
|
+
state = "pending";
|
|
6861
|
+
} else if (inScope.length === 0) {
|
|
6862
|
+
state = "no-checks-reported";
|
|
6863
|
+
} else {
|
|
6864
|
+
state = verdictFromBuckets(inScope.map((row) => row.bucket));
|
|
6865
|
+
}
|
|
6866
|
+
return { headSha: snapshot.headSha, baseRef: snapshot.baseRef, required, appScope, missing, rows: inScope, state, scoped };
|
|
6867
|
+
}
|
|
6868
|
+
function isRequirementMet(entry, reportedNames, nameAppIds) {
|
|
6869
|
+
if (entry.appId === null) return reportedNames.has(entry.context);
|
|
6870
|
+
return nameAppIds.get(entry.context)?.includes(entry.appId) ?? false;
|
|
6871
|
+
}
|
|
6872
|
+
function verdictFromBuckets(buckets) {
|
|
6873
|
+
const anyFail = buckets.includes("fail") || buckets.includes("cancel");
|
|
6874
|
+
const anyPending = buckets.includes("pending");
|
|
6875
|
+
if (buckets.length === 0) return "no-checks-reported";
|
|
6876
|
+
return anyFail ? anyPending ? "failing" : "failure" : anyPending ? "pending" : "success";
|
|
6877
|
+
}
|
|
6878
|
+
function checkRowsExitCode(state) {
|
|
6879
|
+
if (state === "success") return 0;
|
|
6880
|
+
if (state === "failure" || state === "failing") return 1;
|
|
6881
|
+
return 8;
|
|
6882
|
+
}
|
|
6657
6883
|
async function pollRestPrMergeable(prNumber, repo, gh2 = defaultGhApi) {
|
|
6658
6884
|
try {
|
|
6659
6885
|
return (await fetchRestPrSnapshot(prNumber, repo, gh2)).mergeable;
|
|
@@ -17713,10 +17939,10 @@ var rollout_plan_default = {
|
|
|
17713
17939
|
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)."
|
|
17714
17940
|
},
|
|
17715
17941
|
baseline: {
|
|
17716
|
-
version: "4.5.
|
|
17717
|
-
tag: "v4.5.
|
|
17718
|
-
commit: "
|
|
17719
|
-
npm: "@mutmutco/cli@4.5.
|
|
17942
|
+
version: "4.5.115",
|
|
17943
|
+
tag: "v4.5.115",
|
|
17944
|
+
commit: "8d0add650762",
|
|
17945
|
+
npm: "@mutmutco/cli@4.5.115"
|
|
17720
17946
|
},
|
|
17721
17947
|
exitCriterion: "fleet-n-of-n",
|
|
17722
17948
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17733,14 +17959,14 @@ var rollout_plan_default = {
|
|
|
17733
17959
|
repo: "mutmutco/mmi-hub",
|
|
17734
17960
|
role: "canary",
|
|
17735
17961
|
schedule: "train",
|
|
17736
|
-
v3Target: "v4.5.
|
|
17962
|
+
v3Target: "v4.5.115"
|
|
17737
17963
|
}
|
|
17738
17964
|
],
|
|
17739
17965
|
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.",
|
|
17740
17966
|
rollback: {
|
|
17741
17967
|
independent: true,
|
|
17742
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17743
|
-
v3Target: "v4.5.
|
|
17968
|
+
mechanism: "npm dist-tag latest -> 4.5.115 and redeploy the Hub Lambda from tag v4.5.115 (8d0add650762); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17969
|
+
v3Target: "v4.5.115 (@mutmutco/cli@4.5.115, tag commit 8d0add650762 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17744
17970
|
}
|
|
17745
17971
|
},
|
|
17746
17972
|
{
|
|
@@ -29524,7 +29750,7 @@ var PRIMARY_GROUPS = [
|
|
|
29524
29750
|
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
29525
29751
|
];
|
|
29526
29752
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin", "packages"]);
|
|
29527
|
-
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup"]);
|
|
29753
|
+
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup", "read"]);
|
|
29528
29754
|
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
29529
29755
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
29530
29756
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
@@ -29564,6 +29790,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
29564
29790
|
stage: { module_owner: "cli/src/stage-commands.ts", consumer: "agent-workflow" },
|
|
29565
29791
|
pr: { module_owner: "cli/src/pr-commands.ts", consumer: "agent-workflow" },
|
|
29566
29792
|
api: { module_owner: "cli/src/api-read.ts", consumer: "agent-workflow" },
|
|
29793
|
+
read: { module_owner: "cli/src/app-read-broker.ts", consumer: "agent-workflow" },
|
|
29567
29794
|
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
29568
29795
|
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
29569
29796
|
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
@@ -43185,6 +43412,7 @@ var SERVICE_NAME_RE = /^[A-Za-z0-9_.-]+$/;
|
|
|
43185
43412
|
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
43186
43413
|
var EXCLUDE_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
43187
43414
|
var EXCLUDE_LABEL = "mmi.runtime-env.exclude";
|
|
43415
|
+
var KEYS_LABEL = "mmi.runtime-env.keys";
|
|
43188
43416
|
var RUNTIME_ENV_OVERRIDE_RELPATH = "tmp/stage/runtime-env.override.yml";
|
|
43189
43417
|
function labelsMarkRuntimeEnv(labels) {
|
|
43190
43418
|
if (Array.isArray(labels)) {
|
|
@@ -43211,12 +43439,16 @@ function labelValue(labels, name) {
|
|
|
43211
43439
|
if (labels && typeof labels === "object") return String(labels[name] ?? "");
|
|
43212
43440
|
return "";
|
|
43213
43441
|
}
|
|
43214
|
-
function
|
|
43442
|
+
function labelPresent(labels, name) {
|
|
43443
|
+
if (Array.isArray(labels)) return labels.some((l) => typeof l === "string" && (l.trim() === name || l.slice(0, l.indexOf("=")).trim() === name));
|
|
43444
|
+
return !!labels && typeof labels === "object" && name in labels;
|
|
43445
|
+
}
|
|
43446
|
+
function parseRuntimeEnvExclude(value, service = "?", label = EXCLUDE_LABEL) {
|
|
43215
43447
|
if (!value.trim()) return [];
|
|
43216
43448
|
return value.split(",").map((raw) => {
|
|
43217
43449
|
const key = raw.trim();
|
|
43218
43450
|
if (!EXCLUDE_KEY_RE.test(key)) {
|
|
43219
|
-
throw new Error(`invalid ${
|
|
43451
|
+
throw new Error(`invalid ${label} entry on service ${service}: '${key}' (comma-separated env-var names matching ^[A-Z_][A-Z0-9_]*$)`);
|
|
43220
43452
|
}
|
|
43221
43453
|
return key;
|
|
43222
43454
|
});
|
|
@@ -43239,6 +43471,23 @@ function runtimeEnvExcludes(composeConfigJson) {
|
|
|
43239
43471
|
}
|
|
43240
43472
|
return out;
|
|
43241
43473
|
}
|
|
43474
|
+
function runtimeEnvSelects(composeConfigJson) {
|
|
43475
|
+
let parsed;
|
|
43476
|
+
try {
|
|
43477
|
+
parsed = JSON.parse(composeConfigJson);
|
|
43478
|
+
} catch {
|
|
43479
|
+
return {};
|
|
43480
|
+
}
|
|
43481
|
+
const services = parsed?.services;
|
|
43482
|
+
if (!services || typeof services !== "object" || Array.isArray(services)) return {};
|
|
43483
|
+
const out = {};
|
|
43484
|
+
for (const [name, svc] of Object.entries(services)) {
|
|
43485
|
+
const labels = svc?.labels;
|
|
43486
|
+
if (!SERVICE_NAME_RE.test(name) || !labelsMarkRuntimeEnv(labels) || !labelPresent(labels, KEYS_LABEL)) continue;
|
|
43487
|
+
out[name] = parseRuntimeEnvExclude(labelValue(labels, KEYS_LABEL), name, KEYS_LABEL);
|
|
43488
|
+
}
|
|
43489
|
+
return out;
|
|
43490
|
+
}
|
|
43242
43491
|
function runtimeEnvMarkedServices(composeConfigJson) {
|
|
43243
43492
|
let parsed;
|
|
43244
43493
|
try {
|
|
@@ -43250,12 +43499,16 @@ function runtimeEnvMarkedServices(composeConfigJson) {
|
|
|
43250
43499
|
if (!services || typeof services !== "object" || Array.isArray(services)) return [];
|
|
43251
43500
|
return Object.entries(services).filter(([name, svc]) => SERVICE_NAME_RE.test(name) && labelsMarkRuntimeEnv(svc?.labels)).map(([name]) => name).sort();
|
|
43252
43501
|
}
|
|
43253
|
-
function renderRuntimeEnvOverride(services, keys, excludes = {}) {
|
|
43502
|
+
function renderRuntimeEnvOverride(services, keys, excludes = {}, selects = {}) {
|
|
43254
43503
|
const safeServices = services.filter((s) => SERVICE_NAME_RE.test(s)).sort();
|
|
43255
43504
|
const safeKeys = [...new Set(keys.filter((k) => ENV_KEY_RE.test(k)))].sort();
|
|
43256
43505
|
const keysFor = (service) => {
|
|
43257
43506
|
const skip = new Set(excludes[service] ?? []);
|
|
43258
|
-
|
|
43507
|
+
const picked = selects[service];
|
|
43508
|
+
for (const k of picked ?? []) {
|
|
43509
|
+
if (!safeKeys.includes(k)) throw new Error(`${KEYS_LABEL} on service ${service} names '${k}', which is not a declared runtime key`);
|
|
43510
|
+
}
|
|
43511
|
+
return safeKeys.filter((k) => !skip.has(k) && (!picked || picked.includes(k)));
|
|
43259
43512
|
};
|
|
43260
43513
|
const withKeys = safeServices.filter((s) => keysFor(s).length);
|
|
43261
43514
|
if (!withKeys.length) return null;
|
|
@@ -43678,7 +43931,7 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
43678
43931
|
} catch {
|
|
43679
43932
|
return {};
|
|
43680
43933
|
}
|
|
43681
|
-
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson));
|
|
43934
|
+
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
43682
43935
|
if (!body) return {};
|
|
43683
43936
|
const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
43684
43937
|
(0, import_node_fs39.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
@@ -51577,6 +51830,352 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
51577
51830
|
});
|
|
51578
51831
|
}
|
|
51579
51832
|
|
|
51833
|
+
// src/app-read-broker.ts
|
|
51834
|
+
init_cli_shared();
|
|
51835
|
+
var OWNER_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
51836
|
+
var JSON_FIELD_RE = /^[A-Za-z0-9_,]+$/;
|
|
51837
|
+
var ID_RE = /^\d+$/;
|
|
51838
|
+
function parseBrokerRepo(raw, flag) {
|
|
51839
|
+
if (raw === void 0) return void 0;
|
|
51840
|
+
const repo = raw.trim();
|
|
51841
|
+
if (!OWNER_REPO_RE.test(repo)) {
|
|
51842
|
+
throw new Error(`read: ${flag} must be owner/repo on github.com (received '${raw}') \u2014 hosts, URLs and empty segments are refused`);
|
|
51843
|
+
}
|
|
51844
|
+
return repo;
|
|
51845
|
+
}
|
|
51846
|
+
function parseBrokerId(raw, what) {
|
|
51847
|
+
if (!ID_RE.test(raw.trim())) {
|
|
51848
|
+
throw new Error(`read: ${what} must be a number (received '${raw}') \u2014 URLs and branch names are refused`);
|
|
51849
|
+
}
|
|
51850
|
+
return raw.trim();
|
|
51851
|
+
}
|
|
51852
|
+
function parseBrokerInt(raw, flag, min, max) {
|
|
51853
|
+
const n = Number(raw);
|
|
51854
|
+
if (!Number.isInteger(n) || n < min || n > max) {
|
|
51855
|
+
throw new Error(`read: ${flag} must be an integer ${min}-${max} (received '${raw}')`);
|
|
51856
|
+
}
|
|
51857
|
+
return n;
|
|
51858
|
+
}
|
|
51859
|
+
function parseBrokerJsonFields(raw, allowed) {
|
|
51860
|
+
if (raw === void 0 || raw === false) return void 0;
|
|
51861
|
+
if (raw === true) return void 0;
|
|
51862
|
+
const value = raw.trim();
|
|
51863
|
+
if (!JSON_FIELD_RE.test(value) || value.split(",").some((f) => !f)) {
|
|
51864
|
+
throw new Error(`read: --json fields must be comma-separated names (received '${raw}')`);
|
|
51865
|
+
}
|
|
51866
|
+
if (allowed) {
|
|
51867
|
+
const unknown = value.split(",").filter((f) => !allowed.includes(f));
|
|
51868
|
+
if (unknown.length > 0) {
|
|
51869
|
+
throw new Error(`read: unknown --json field(s): ${unknown.join(", ")} \u2014 one of: ${allowed.join(",")}`);
|
|
51870
|
+
}
|
|
51871
|
+
}
|
|
51872
|
+
return value;
|
|
51873
|
+
}
|
|
51874
|
+
var BROKER_JSON_FLAGS_NOTE = "formatter flags are refused (--jq/--template can read child credentials; --web opens a browser) \u2014 use --json with field selection";
|
|
51875
|
+
function parseBrokerIssueState(raw) {
|
|
51876
|
+
if (raw === void 0) return void 0;
|
|
51877
|
+
const v = raw.trim().toLowerCase();
|
|
51878
|
+
if (v !== "open" && v !== "closed" && v !== "all") {
|
|
51879
|
+
throw new Error(`read: --state must be one of: open, closed, all (received '${raw}')`);
|
|
51880
|
+
}
|
|
51881
|
+
return v;
|
|
51882
|
+
}
|
|
51883
|
+
var RUN_LIST_STATUSES = ["queued", "completed", "in_progress", "requested", "waiting", "pending", "action_required", "cancelled", "failure", "neutral", "skipped", "stale", "startup_failure", "success", "timed_out"];
|
|
51884
|
+
function parseBrokerRunStatus(raw) {
|
|
51885
|
+
if (raw === void 0) return void 0;
|
|
51886
|
+
const v = raw.trim().toLowerCase();
|
|
51887
|
+
if (!RUN_LIST_STATUSES.includes(v)) {
|
|
51888
|
+
throw new Error(`read: --status must be one of: ${RUN_LIST_STATUSES.join("|")} (received '${raw}')`);
|
|
51889
|
+
}
|
|
51890
|
+
return v;
|
|
51891
|
+
}
|
|
51892
|
+
function parseBrokerAssignee(raw) {
|
|
51893
|
+
if (raw === void 0) return void 0;
|
|
51894
|
+
const v = raw.trim();
|
|
51895
|
+
if (/^@?me$/i.test(v) || /^viewer$/i.test(v)) {
|
|
51896
|
+
throw new Error(`read: --assignee '${raw}' resolves the viewer \u2014 viewer-scoped reads stay personal, never the App pool`);
|
|
51897
|
+
}
|
|
51898
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(v.replace(/^@/, ""))) {
|
|
51899
|
+
throw new Error(`read: --assignee must be a login (received '${raw}')`);
|
|
51900
|
+
}
|
|
51901
|
+
return v;
|
|
51902
|
+
}
|
|
51903
|
+
var SINGLE_SHOT_TIMEOUT_MS = 6e4;
|
|
51904
|
+
function withRepo(args, repo) {
|
|
51905
|
+
return repo ? [...args, "--repo", repo] : args;
|
|
51906
|
+
}
|
|
51907
|
+
function withJson(args, jsonFields, jsonFlag) {
|
|
51908
|
+
if (jsonFlag === void 0 || jsonFlag === false) return args;
|
|
51909
|
+
return typeof jsonFields === "string" ? [...args, "--json", jsonFields] : [...args, "--json"];
|
|
51910
|
+
}
|
|
51911
|
+
function buildRunViewSpec(id, o) {
|
|
51912
|
+
const args = withJson(withRepo(["run", "view", parseBrokerId(id, "run id"), ...o.attempt !== void 0 ? ["--attempt", String(parseBrokerInt(o.attempt, "--attempt", 1, 1e3))] : []], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
51913
|
+
if (o.exitStatus) args.push("--exit-status");
|
|
51914
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
51915
|
+
}
|
|
51916
|
+
function buildRunListSpec(o) {
|
|
51917
|
+
const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
51918
|
+
const workflow = parseBrokerWorkflow(o.workflow);
|
|
51919
|
+
if (workflow) args.push("--workflow", workflow);
|
|
51920
|
+
const branch = parseBrokerBranch(o.branch);
|
|
51921
|
+
if (branch) args.push("--branch", branch);
|
|
51922
|
+
const status = parseBrokerRunStatus(o.status);
|
|
51923
|
+
if (status) args.push("--status", status);
|
|
51924
|
+
if (o.limit !== void 0) args.push("--limit", String(parseBrokerInt(o.limit, "--limit", 1, 100)));
|
|
51925
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
51926
|
+
}
|
|
51927
|
+
function buildRunWatchSpec(id, o) {
|
|
51928
|
+
const args = withRepo(["run", "watch", parseBrokerId(id, "run id")], parseBrokerRepo(o.repo, "--repo"));
|
|
51929
|
+
if (o.exitStatus) args.push("--exit-status");
|
|
51930
|
+
if (o.compact) args.push("--compact");
|
|
51931
|
+
args.push("--interval", String(parseBrokerInt(o.interval ?? "60", "--interval", 60, 300)));
|
|
51932
|
+
const timeoutMin = o.timeoutMin !== void 0 ? parseBrokerInt(o.timeoutMin, "--timeout", 1, 120) : 30;
|
|
51933
|
+
return { program: "gh", args, timeoutMs: timeoutMin * 6e4 };
|
|
51934
|
+
}
|
|
51935
|
+
function buildPrViewSpec(ref, o) {
|
|
51936
|
+
const args = withJson(withRepo(["pr", "view", parseBrokerId(ref, "PR number")], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
51937
|
+
if (o.comments) args.push("--comments");
|
|
51938
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
51939
|
+
}
|
|
51940
|
+
function buildPrChecksSpec(number, o) {
|
|
51941
|
+
return {
|
|
51942
|
+
program: "rest-checks",
|
|
51943
|
+
number: parseBrokerId(number, "PR number"),
|
|
51944
|
+
repo: parseBrokerRepo(o.repo, "--repo"),
|
|
51945
|
+
required: o.required === true,
|
|
51946
|
+
jsonFields: parseBrokerJsonFields(o.json, GH_PR_CHECKS_JSON_FIELDS)
|
|
51947
|
+
};
|
|
51948
|
+
}
|
|
51949
|
+
function buildIssueViewSpec(number, o) {
|
|
51950
|
+
const args = withJson(withRepo(["issue", "view", parseBrokerId(number, "issue number")], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
51951
|
+
if (o.comments) args.push("--comments");
|
|
51952
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
51953
|
+
}
|
|
51954
|
+
function parseBrokerWorkflow(raw) {
|
|
51955
|
+
if (raw === void 0) return void 0;
|
|
51956
|
+
if (!/^[A-Za-z0-9_./-]{1,200}$/.test(raw)) throw new Error(`read: --workflow must be a workflow name or file (received '${raw}')`);
|
|
51957
|
+
return raw;
|
|
51958
|
+
}
|
|
51959
|
+
function parseBrokerBranch(raw) {
|
|
51960
|
+
if (raw === void 0) return void 0;
|
|
51961
|
+
if (!/^[A-Za-z0-9_./-]{1,200}$/.test(raw)) throw new Error(`read: --branch must be a branch name (received '${raw}')`);
|
|
51962
|
+
return raw;
|
|
51963
|
+
}
|
|
51964
|
+
function parseBrokerSearch(raw) {
|
|
51965
|
+
if (raw === void 0) return void 0;
|
|
51966
|
+
if (!raw.trim()) throw new Error("read: --search must not be empty");
|
|
51967
|
+
return raw;
|
|
51968
|
+
}
|
|
51969
|
+
function parseBrokerLabel(raw) {
|
|
51970
|
+
if (raw === void 0) return void 0;
|
|
51971
|
+
if (!/^[A-Za-z0-9_.,: /-]{1,200}$/.test(raw)) throw new Error(`read: --label must be label text (received '${raw}')`);
|
|
51972
|
+
return raw;
|
|
51973
|
+
}
|
|
51974
|
+
function buildIssueListSpec(o) {
|
|
51975
|
+
const args = withJson(withRepo(["issue", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
51976
|
+
const search = parseBrokerSearch(o.search);
|
|
51977
|
+
if (search !== void 0) args.push("--search", search);
|
|
51978
|
+
const assignee = parseBrokerAssignee(o.assignee);
|
|
51979
|
+
if (assignee) args.push("--assignee", assignee);
|
|
51980
|
+
const label = parseBrokerLabel(o.label);
|
|
51981
|
+
if (label !== void 0) args.push("--label", label);
|
|
51982
|
+
const state = parseBrokerIssueState(o.state);
|
|
51983
|
+
if (state) args.push("--state", state);
|
|
51984
|
+
if (o.limit !== void 0) args.push("--limit", String(parseBrokerInt(o.limit, "--limit", 1, 100)));
|
|
51985
|
+
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
51986
|
+
}
|
|
51987
|
+
function brokerChildEnv(base = process.env) {
|
|
51988
|
+
const env = { ...base };
|
|
51989
|
+
delete env.GITHUB_TOKEN;
|
|
51990
|
+
delete env.GH_ENTERPRISE_TOKEN;
|
|
51991
|
+
delete env.GITHUB_ENTERPRISE_TOKEN;
|
|
51992
|
+
delete env.GH_ENTERPRISE_URL;
|
|
51993
|
+
delete env.GITHUB_ENTERPRISE_URL;
|
|
51994
|
+
delete env.GH_REPO;
|
|
51995
|
+
delete env.GITHUB_REPOSITORY;
|
|
51996
|
+
delete env.GH_DEBUG;
|
|
51997
|
+
env.GH_HOST = "github.com";
|
|
51998
|
+
return env;
|
|
51999
|
+
}
|
|
52000
|
+
function redactPrivateCredential(text, env) {
|
|
52001
|
+
const credential = env.GH_TOKEN;
|
|
52002
|
+
if (!credential) return text;
|
|
52003
|
+
return text.split(credential).join("[redacted-credential]");
|
|
52004
|
+
}
|
|
52005
|
+
function ghExitCodeOf(error) {
|
|
52006
|
+
const code = error.code;
|
|
52007
|
+
return typeof code === "number" ? code : null;
|
|
52008
|
+
}
|
|
52009
|
+
async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(chunk)) {
|
|
52010
|
+
const env = brokerChildEnv();
|
|
52011
|
+
try {
|
|
52012
|
+
write(redactPrivateCredential(await run(spec.args, { timeoutMs: spec.timeoutMs, env }), env));
|
|
52013
|
+
return 0;
|
|
52014
|
+
} catch (e) {
|
|
52015
|
+
const err = e;
|
|
52016
|
+
if (typeof err.stdout === "string" && err.stdout) write(redactPrivateCredential(err.stdout, env));
|
|
52017
|
+
const code = ghExitCodeOf(e);
|
|
52018
|
+
if (code !== null) {
|
|
52019
|
+
if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
|
|
52020
|
+
return code;
|
|
52021
|
+
}
|
|
52022
|
+
throw new Error(`read: gh ${spec.args.slice(0, 2).join(" ")} did not finish on the App pool (${err.message ?? e}) \u2014 re-run the same command to refresh (same custody); reads never fall back to personal`);
|
|
52023
|
+
}
|
|
52024
|
+
}
|
|
52025
|
+
async function runBrokerRestChecks(deps, spec, resolveRepo2) {
|
|
52026
|
+
const repo = await resolveRepo2(spec.repo);
|
|
52027
|
+
const ghApi = deps.restGhApi(brokerChildEnv());
|
|
52028
|
+
if (!spec.required) {
|
|
52029
|
+
const snapshot = await deps.fetchSnapshot(spec.number, repo, ghApi).catch((e) => {
|
|
52030
|
+
throw new Error(`read pr checks: cannot resolve PR #${spec.number} on ${repo}: ${e?.message ?? e}`);
|
|
52031
|
+
});
|
|
52032
|
+
const { rows } = await deps.fetchRows(snapshot.headSha, repo, ghApi).catch((e) => {
|
|
52033
|
+
throw new Error(`read pr checks: check read failed for PR #${spec.number} on ${repo}: ${e?.message ?? e}`);
|
|
52034
|
+
});
|
|
52035
|
+
const state = verdictFromBuckets(rows.map((row) => row.bucket));
|
|
52036
|
+
return { rows, state, exitCode: checkRowsExitCode(state) };
|
|
52037
|
+
}
|
|
52038
|
+
const read = await deps.readRequired(spec.number, repo, ghApi).catch((e) => {
|
|
52039
|
+
throw new Error(`read pr checks: ${e?.message ?? e}`);
|
|
52040
|
+
});
|
|
52041
|
+
return { rows: read.rows, state: read.state, exitCode: checkRowsExitCode(read.state) };
|
|
52042
|
+
}
|
|
52043
|
+
function requireBrokerApp(label) {
|
|
52044
|
+
if (process.env.MMI_ACTOR !== "app") {
|
|
52045
|
+
fail(`${label}: App-only read \u2014 re-run with spawn-scoped MMI_ACTOR=app (reads never fall back to personal; interactive use keeps gh and the human commands)`);
|
|
52046
|
+
}
|
|
52047
|
+
}
|
|
52048
|
+
function registerPrChecksRequiredCommand(prRoot, resolveRepo2, overrides) {
|
|
52049
|
+
const deps = {
|
|
52050
|
+
readRequired: readRequiredCheckRows,
|
|
52051
|
+
fetchSnapshot: fetchRestPrSnapshot,
|
|
52052
|
+
fetchRows: fetchHeadCheckRows,
|
|
52053
|
+
restGhApi: overrides?.restGhApi ?? ((env) => ghApiWithEnv(env))
|
|
52054
|
+
};
|
|
52055
|
+
prRoot.command("checks-required").description(`required-only PR check read on the PR head (#8042): the GitHub merge-button verdict \u2014 REST check-runs + commit statuses scoped to GitHub's own required-status-check contexts (branch protection + rulesets), printed as native \`gh pr checks --json\` rows. Single shot, no wait. Never the ALL-checks waiter (#5336). REST is the deterministic closed-custody adapter: native returned an EMPTY table on the historical #3035 fixture/profile, and same-token parity on later fixtures agrees \u2014 not a claim native always fails. Reads merged/closed PRs like native does \u2014 the merge gate (not this read) decides what a state means. Exit 0 = required checks all pass (or no required contexts: every reported check passes); exit 1 = a required check FAILED or the read itself failed; exit 8 = required checks still pending (native pending code)`).option("--json [fields]", "machine-readable native-shaped rows (default: all known fields; comma-separated subset of: " + GH_PR_CHECKS_JSON_FIELDS.join(",") + ")", (v) => parseBrokerJsonFields(v, GH_PR_CHECKS_JSON_FIELDS)).option("--repo <owner/repo>", "target repo (defaults to the current repo)", (v) => parseBrokerRepo(v, "--repo")).argument("<number>", "PR number", (v) => parseBrokerId(v, "PR number")).action(async (number, o) => {
|
|
52056
|
+
let spec;
|
|
52057
|
+
try {
|
|
52058
|
+
spec = buildPrChecksSpec(number, { repo: o.repo, json: o.json, required: true });
|
|
52059
|
+
} catch (e) {
|
|
52060
|
+
return fail(e.message);
|
|
52061
|
+
}
|
|
52062
|
+
await dispatchRestChecksRead(deps, resolveRepo2, "pr checks-required", spec, o.json);
|
|
52063
|
+
});
|
|
52064
|
+
}
|
|
52065
|
+
function selectCheckRowFields(rows, jsonFields) {
|
|
52066
|
+
if (jsonFields === void 0) return rows.map((row) => ({ ...row }));
|
|
52067
|
+
const fields = jsonFields.split(",");
|
|
52068
|
+
return rows.map((row) => Object.fromEntries(fields.map((f) => [f, row[f]])));
|
|
52069
|
+
}
|
|
52070
|
+
async function dispatchRestChecksRead(deps, resolveRepo2, label, spec, json) {
|
|
52071
|
+
requireBrokerApp(label);
|
|
52072
|
+
let result;
|
|
52073
|
+
try {
|
|
52074
|
+
result = await runBrokerRestChecks(deps, spec, resolveRepo2);
|
|
52075
|
+
} catch (e) {
|
|
52076
|
+
return fail(`${label}: ${e.message}`);
|
|
52077
|
+
}
|
|
52078
|
+
if (json !== void 0 && json !== false) {
|
|
52079
|
+
printLine(JSON.stringify(selectCheckRowFields(result.rows, spec.jsonFields)));
|
|
52080
|
+
} else {
|
|
52081
|
+
for (const row of result.rows) printLine(`${row.state} ${row.bucket} ${row.name}${row.link ? ` ${row.link}` : ""}`);
|
|
52082
|
+
if (result.rows.length === 0) printLine(`${label}: no checks reported`);
|
|
52083
|
+
}
|
|
52084
|
+
process.exitCode = result.exitCode;
|
|
52085
|
+
}
|
|
52086
|
+
function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
52087
|
+
const ghRun = overrides?.ghRunner ?? (async (args, opts) => (await execFileP("gh", args, { timeout: opts.timeoutMs, env: opts.env })).stdout);
|
|
52088
|
+
const restGhApi = overrides?.restGhApi ?? ((env) => ghApiWithEnv(env));
|
|
52089
|
+
const deps = { readRequired: readRequiredCheckRows, fetchSnapshot: fetchRestPrSnapshot, fetchRows: fetchHeadCheckRows, restGhApi };
|
|
52090
|
+
const dispatchGh = (spec, label) => (async () => {
|
|
52091
|
+
requireBrokerApp(label);
|
|
52092
|
+
try {
|
|
52093
|
+
process.exitCode = await runBrokerGh(ghRun, spec);
|
|
52094
|
+
} catch (e) {
|
|
52095
|
+
return fail(`${label}: ${e.message}`);
|
|
52096
|
+
}
|
|
52097
|
+
})();
|
|
52098
|
+
const dispatchRestChecks = (spec, label, json) => dispatchRestChecksRead(deps, resolveRepo2, label, spec, json);
|
|
52099
|
+
const repoOpt = (v) => parseBrokerRepo(v, "--repo");
|
|
52100
|
+
const idOpt = (what) => (v) => parseBrokerId(v, what);
|
|
52101
|
+
const intOpt = (flag, min, max) => (v) => String(parseBrokerInt(v, flag, min, max));
|
|
52102
|
+
const jsonOpt = (v) => parseBrokerJsonFields(v);
|
|
52103
|
+
const checksJsonOpt = (v) => parseBrokerJsonFields(v, GH_PR_CHECKS_JSON_FIELDS);
|
|
52104
|
+
const workflowOpt = (v) => parseBrokerWorkflow(v);
|
|
52105
|
+
const branchOpt = (v) => parseBrokerBranch(v);
|
|
52106
|
+
const searchOpt = (v) => parseBrokerSearch(v);
|
|
52107
|
+
const labelOpt = (v) => parseBrokerLabel(v);
|
|
52108
|
+
const runStatusOpt = (v) => parseBrokerRunStatus(v);
|
|
52109
|
+
const assigneeOpt = (v) => parseBrokerAssignee(v);
|
|
52110
|
+
const read = program3.command("read").description(`closed typed GitHub reads on the App pool (${BROKER_JSON_FLAGS_NOTE})`);
|
|
52111
|
+
const run = read.command("run").description("workflow-run reads (view/list/watch) \u2014 stdout and exit codes pass gh through verbatim");
|
|
52112
|
+
run.command("view").description("read a workflow run (attempt/exit-status/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("--exit-status", "exit non-zero when the run failed").option("--attempt <n>", "the attempt number of the run (1-1000)", intOpt("--attempt", 1, 1e3)).argument("<id>", "workflow run number", idOpt("run id")).action(async (id, o) => {
|
|
52113
|
+
let spec;
|
|
52114
|
+
try {
|
|
52115
|
+
spec = buildRunViewSpec(id, o);
|
|
52116
|
+
} catch (e) {
|
|
52117
|
+
return fail(e.message);
|
|
52118
|
+
}
|
|
52119
|
+
await dispatchGh(spec, "read run view");
|
|
52120
|
+
});
|
|
52121
|
+
run.command("list").description("list workflow runs (workflow/branch/status/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("--limit <n>", "maximum runs to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
52122
|
+
let spec;
|
|
52123
|
+
try {
|
|
52124
|
+
spec = buildRunListSpec(o);
|
|
52125
|
+
} catch (e) {
|
|
52126
|
+
return fail(e.message);
|
|
52127
|
+
}
|
|
52128
|
+
await dispatchGh(spec, "read run list");
|
|
52129
|
+
});
|
|
52130
|
+
run.command("watch").description("watch a run until it finishes (exit-status passthrough; --timeout bounds the watch \u2014 expiry fails explicit App-pool, refresh re-runs the same command)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--exit-status", "exit non-zero when the run fails").option("--compact", "show only relevant/failed steps").option("--interval <n>", "refresh interval in seconds, 60-300 (pool pacing floor; default 60)", intOpt("--interval", 60, 300)).option("--timeout <minutes>", "watch bound in minutes, 1-120 (default 30)", intOpt("--timeout", 1, 120), "30").argument("<id>", "workflow run number", idOpt("run id")).action(async (id, o) => {
|
|
52131
|
+
let spec;
|
|
52132
|
+
try {
|
|
52133
|
+
spec = buildRunWatchSpec(id, { repo: o.repo, exitStatus: o.exitStatus, compact: o.compact, interval: o.interval, timeoutMin: o.timeout });
|
|
52134
|
+
} catch (e) {
|
|
52135
|
+
return fail(e.message);
|
|
52136
|
+
}
|
|
52137
|
+
await dispatchGh(spec, "read run watch");
|
|
52138
|
+
});
|
|
52139
|
+
const pr = read.command("pr").description("pull-request reads \u2014 view rides gh; checks is REST-backed (deterministic closed-custody adapter; native was EMPTY only on the historical #3035 fixture)");
|
|
52140
|
+
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) => {
|
|
52141
|
+
let spec;
|
|
52142
|
+
try {
|
|
52143
|
+
spec = buildPrViewSpec(number, o);
|
|
52144
|
+
} catch (e) {
|
|
52145
|
+
return fail(e.message);
|
|
52146
|
+
}
|
|
52147
|
+
await dispatchGh(spec, "read pr view");
|
|
52148
|
+
});
|
|
52149
|
+
pr.command("checks").description(`PR checks as native --json rows (REST-backed; --required scopes to GitHub's required contexts, failed lookup errors instead of falling back; exit 0 pass, 1 fail, 8 pending; --watch/--fail-fast/--interval/--web have no route \u2014 unsupported by name)`).option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", `machine-readable rows (subset of: ${GH_PR_CHECKS_JSON_FIELDS.join(",")})`, checksJsonOpt).option("--required", "only checks GitHub requires for merge").argument("<number>", "PR number (explicit \u2014 current-branch inference is unsupported)", idOpt("PR number")).action(async (number, o) => {
|
|
52150
|
+
let spec;
|
|
52151
|
+
try {
|
|
52152
|
+
spec = buildPrChecksSpec(number, o);
|
|
52153
|
+
} catch (e) {
|
|
52154
|
+
return fail(e.message);
|
|
52155
|
+
}
|
|
52156
|
+
await dispatchRestChecks(spec, "read pr checks", o.json);
|
|
52157
|
+
});
|
|
52158
|
+
const issue = read.command("issue").description("issue reads (view/list) \u2014 stdout and exit codes pass gh through verbatim");
|
|
52159
|
+
issue.command("view").description("read an issue as gh JSON (comments/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("--comments", "include issue comments").argument("<number>", "issue number", idOpt("issue number")).action(async (number, o) => {
|
|
52160
|
+
let spec;
|
|
52161
|
+
try {
|
|
52162
|
+
spec = buildIssueViewSpec(number, o);
|
|
52163
|
+
} catch (e) {
|
|
52164
|
+
return fail(e.message);
|
|
52165
|
+
}
|
|
52166
|
+
await dispatchGh(spec, "read issue view");
|
|
52167
|
+
});
|
|
52168
|
+
issue.command("list").description("list/search issues (search/assignee/label/state/limit/json; viewer pronouns refused \u2014 viewer-scoped reads stay personal)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--search <query>", "search issues", searchOpt).option("--assignee <login>", "filter by assignee (viewer pronouns refused)", assigneeOpt).option("--label <label>", "filter by label", labelOpt).addOption(new Option("--state <state>", "open|closed|all").choices(["open", "closed", "all"])).option("--limit <n>", "maximum issues to fetch (1-100)", intOpt("--limit", 1, 100)).action(async (o) => {
|
|
52169
|
+
let spec;
|
|
52170
|
+
try {
|
|
52171
|
+
spec = buildIssueListSpec(o);
|
|
52172
|
+
} catch (e) {
|
|
52173
|
+
return fail(e.message);
|
|
52174
|
+
}
|
|
52175
|
+
await dispatchGh(spec, "read issue list");
|
|
52176
|
+
});
|
|
52177
|
+
}
|
|
52178
|
+
|
|
51580
52179
|
// src/api-read.ts
|
|
51581
52180
|
init_cli_shared();
|
|
51582
52181
|
var API_ACCEPT_HEADERS = {
|
|
@@ -51597,13 +52196,19 @@ var PULL_FILES_RE = new RegExp(`^repos/${NAME}/${NAME}/pulls/\\d+/files$`);
|
|
|
51597
52196
|
var COMPARE_RE = new RegExp(`^repos/${NAME}/${NAME}/compare/${FREE}\\.\\.\\.${FREE}$`);
|
|
51598
52197
|
var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
51599
52198
|
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
51600
|
-
var
|
|
52199
|
+
var ACTIONS_RUNS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs$`);
|
|
52200
|
+
var ACTIONS_RUN_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+$`);
|
|
52201
|
+
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/jobs$`);
|
|
52202
|
+
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, repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs";
|
|
51601
52203
|
function parseApiGetPath(rawPath) {
|
|
51602
52204
|
const path2 = rawPath.trim();
|
|
51603
52205
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
51604
52206
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
51605
52207
|
if (PULL_FILES_RE.test(path2)) return { kind: "pull-files", path: path2, paginate: true };
|
|
51606
52208
|
if (COMPARE_RE.test(path2)) return { kind: "compare", path: path2, paginate: false };
|
|
52209
|
+
if (ACTIONS_RUNS_RE.test(path2)) return { kind: "actions-runs", path: path2, paginate: true };
|
|
52210
|
+
if (ACTIONS_RUN_RE.test(path2)) return { kind: "actions-run", path: path2, paginate: false };
|
|
52211
|
+
if (ACTIONS_JOBS_RE.test(path2)) return { kind: "actions-jobs", path: path2, paginate: true };
|
|
51607
52212
|
const q = path2.indexOf("?");
|
|
51608
52213
|
const base = q === -1 ? path2 : path2.slice(0, q);
|
|
51609
52214
|
const query = q === -1 ? "" : path2.slice(q + 1);
|
|
@@ -51623,14 +52228,21 @@ function buildApiGetArgs(path2, accept) {
|
|
|
51623
52228
|
async function runApiGet(run, path2, accept) {
|
|
51624
52229
|
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
51625
52230
|
}
|
|
51626
|
-
function registerApiReadCommands(program3) {
|
|
51627
|
-
const
|
|
51628
|
-
api.command("
|
|
52231
|
+
function registerApiReadCommands(program3, overrides) {
|
|
52232
|
+
const spawn6 = overrides?.runner ?? (async (args, timeoutMs, env) => (await execFileP("gh", args, { timeout: timeoutMs, ...env ? { env } : {} })).stdout);
|
|
52233
|
+
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs)");
|
|
52234
|
+
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) => {
|
|
52235
|
+
acceptHeaderFor(v);
|
|
52236
|
+
return v.trim().toLowerCase();
|
|
52237
|
+
}, "json").argument("<path>", "allowlisted GET path (shapes named on refusal)", (v) => parseApiGetPath(v)).action(async (req, o) => {
|
|
52238
|
+
const actionsRoute = req.kind.startsWith("actions-");
|
|
52239
|
+
if (actionsRoute) requireBrokerApp("api get");
|
|
52240
|
+
const env = actionsRoute || process.env.MMI_ACTOR === "app" ? brokerChildEnv() : void 0;
|
|
51629
52241
|
let body;
|
|
51630
52242
|
try {
|
|
51631
52243
|
body = await runApiGet(
|
|
51632
|
-
async (args, timeoutMs) => (
|
|
51633
|
-
|
|
52244
|
+
async (args, timeoutMs) => spawn6(args, timeoutMs, env),
|
|
52245
|
+
req.path,
|
|
51634
52246
|
o.accept
|
|
51635
52247
|
);
|
|
51636
52248
|
} catch (e) {
|
|
@@ -55350,6 +55962,10 @@ registerDiscoveryCommands(program2);
|
|
|
55350
55962
|
registerExplainCommand(program2);
|
|
55351
55963
|
registerPrLifecycleCommands(program2);
|
|
55352
55964
|
registerApiReadCommands(program2);
|
|
55965
|
+
registerAppReadBrokerCommands(program2, async (repo) => requireRepo(repo));
|
|
55966
|
+
var prBrokerRoot = program2.commands.find((c) => c.name() === "pr");
|
|
55967
|
+
if (!prBrokerRoot) throw new Error("read broker: pr root must be registered before pr checks-required");
|
|
55968
|
+
registerPrChecksRequiredCommand(prBrokerRoot, async (repo) => requireRepo(repo));
|
|
55353
55969
|
registerSessionReport(program2);
|
|
55354
55970
|
registerLearningClosureRateCommand(program2);
|
|
55355
55971
|
registerLearningPickupCommand(program2);
|
package/cli/package.json
CHANGED