@mutmutco/claude-plugin 4.5.113 → 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 +666 -29
- 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;
|
|
@@ -6885,6 +7111,20 @@ async function fetchRestCorePool(gh2 = defaultGhApi, nowMs = Date.now()) {
|
|
|
6885
7111
|
}
|
|
6886
7112
|
|
|
6887
7113
|
// src/claim-marker-client.ts
|
|
7114
|
+
async function selectLivenessClient(client, appClient) {
|
|
7115
|
+
if (appClient) return appClient;
|
|
7116
|
+
let token;
|
|
7117
|
+
try {
|
|
7118
|
+
token = await pollToken();
|
|
7119
|
+
} catch (e) {
|
|
7120
|
+
const failing = async () => {
|
|
7121
|
+
throw e;
|
|
7122
|
+
};
|
|
7123
|
+
return { ...client, rest: failing, restPaginate: failing, graphql: failing };
|
|
7124
|
+
}
|
|
7125
|
+
if (!token) return client;
|
|
7126
|
+
return createGitHubClient({ token: async () => token });
|
|
7127
|
+
}
|
|
6888
7128
|
function withAppClaimMarkerRead(client, appClient) {
|
|
6889
7129
|
const commentsPath = /^repos\/[^/]+\/[^/]+\/issues\/\d+\/comments$/;
|
|
6890
7130
|
return {
|
|
@@ -13850,10 +14090,10 @@ function laneResumeEvidence(evidence) {
|
|
|
13850
14090
|
return `prior claim by lane ${describeClaimMarker(evidence.marker)} (@${evidence.markerBy}, ${formatClaimAge(evidence.markerAgeMs)} old) is verifiably not running on this host \u2014 its local session transcript is gone or stale${artifacts.length ? `; resuming over ${artifacts.join("; ")}` : ""}`;
|
|
13851
14091
|
}
|
|
13852
14092
|
async function checkLaneContest(client, item, actor = describeSessionIdentity(), viewerLogin, appClient) {
|
|
13853
|
-
const
|
|
13854
|
-
let evidence = await gatherClaimLiveness(
|
|
14093
|
+
const livenessClient = await selectLivenessClient(client, appClient);
|
|
14094
|
+
let evidence = await gatherClaimLiveness(livenessClient, item.repository, item.number, openPullsFetcher(livenessClient));
|
|
13855
14095
|
if (evidence.failed.some((channel) => channel !== "session")) {
|
|
13856
|
-
evidence = await gatherClaimLiveness(
|
|
14096
|
+
evidence = await gatherClaimLiveness(livenessClient, item.repository, item.number, openPullsFetcher(livenessClient));
|
|
13857
14097
|
}
|
|
13858
14098
|
const ownership = laneOwnership(evidence.marker, actor);
|
|
13859
14099
|
const sameOwner = Boolean(viewerLogin && evidence.markerBy && evidence.markerBy.toLowerCase() === viewerLogin.toLowerCase());
|
|
@@ -13874,13 +14114,19 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
|
|
|
13874
14114
|
};
|
|
13875
14115
|
}
|
|
13876
14116
|
function laneContestParts(contest) {
|
|
13877
|
-
const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? "an unreadable lane (its claim-marker comments could not be read, so no holder can be named)" : "another lane";
|
|
14117
|
+
const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? "an unreadable lane (its claim-marker comments could not be read, so no holder can be named)" : contest.live.length === 0 && contest.unverifiable.length > 0 ? "an unknown holder (no claim marker was read and liveness could not be fully verified, so no holder can be named)" : "another lane";
|
|
13878
14118
|
const seen = contest.live.join("; ");
|
|
13879
14119
|
const unreadable = contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : "";
|
|
13880
14120
|
return { holder, why: [seen, unreadable].filter(Boolean).join("; ") };
|
|
13881
14121
|
}
|
|
14122
|
+
function isUnknownHold(contest) {
|
|
14123
|
+
return !contest.marker && contest.live.length === 0 && contest.unverifiable.length > 0;
|
|
14124
|
+
}
|
|
13882
14125
|
function laneContestMessage(ref, contest, verb) {
|
|
13883
14126
|
const { holder, why } = laneContestParts(contest);
|
|
14127
|
+
if (isUnknownHold(contest)) {
|
|
14128
|
+
return verb === "claim" ? `${ref} cannot be verified as free: ${holder} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 re-run with --force to take it anyway` : `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)`;
|
|
14129
|
+
}
|
|
13884
14130
|
return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); re-run with --force to take it anyway` : `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)`;
|
|
13885
14131
|
}
|
|
13886
14132
|
function ghError(e) {
|
|
@@ -14547,8 +14793,9 @@ async function unclaimBoardIssue(options, deps = {}) {
|
|
|
14547
14793
|
}
|
|
14548
14794
|
if (!options.holderDead) {
|
|
14549
14795
|
const { holder, why } = laneContestParts(contest);
|
|
14796
|
+
const still = isUnknownHold(contest) ? `${holder} (${why}); the liveness reads failed` : `${holder} still shows live evidence of active work (${why})`;
|
|
14550
14797
|
throw new Error(withGatedWriteChainHint(
|
|
14551
|
-
`refusing to unclaim ${item.ref} with --force: ${
|
|
14798
|
+
`refusing to unclaim ${item.ref} with --force: ${still}; --force no longer overrides a live lane alone \u2014 re-run with --holder-dead once you have evidence the holder's session is dead, or wait for its lane to go stale (#6625)`
|
|
14552
14799
|
));
|
|
14553
14800
|
}
|
|
14554
14801
|
}
|
|
@@ -17692,10 +17939,10 @@ var rollout_plan_default = {
|
|
|
17692
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)."
|
|
17693
17940
|
},
|
|
17694
17941
|
baseline: {
|
|
17695
|
-
version: "4.5.
|
|
17696
|
-
tag: "v4.5.
|
|
17697
|
-
commit: "
|
|
17698
|
-
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"
|
|
17699
17946
|
},
|
|
17700
17947
|
exitCriterion: "fleet-n-of-n",
|
|
17701
17948
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17712,14 +17959,14 @@ var rollout_plan_default = {
|
|
|
17712
17959
|
repo: "mutmutco/mmi-hub",
|
|
17713
17960
|
role: "canary",
|
|
17714
17961
|
schedule: "train",
|
|
17715
|
-
v3Target: "v4.5.
|
|
17962
|
+
v3Target: "v4.5.115"
|
|
17716
17963
|
}
|
|
17717
17964
|
],
|
|
17718
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.",
|
|
17719
17966
|
rollback: {
|
|
17720
17967
|
independent: true,
|
|
17721
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17722
|
-
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)"
|
|
17723
17970
|
}
|
|
17724
17971
|
},
|
|
17725
17972
|
{
|
|
@@ -29503,7 +29750,7 @@ var PRIMARY_GROUPS = [
|
|
|
29503
29750
|
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
29504
29751
|
];
|
|
29505
29752
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin", "packages"]);
|
|
29506
|
-
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup"]);
|
|
29753
|
+
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup", "read"]);
|
|
29507
29754
|
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
29508
29755
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
29509
29756
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
@@ -29543,6 +29790,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
29543
29790
|
stage: { module_owner: "cli/src/stage-commands.ts", consumer: "agent-workflow" },
|
|
29544
29791
|
pr: { module_owner: "cli/src/pr-commands.ts", consumer: "agent-workflow" },
|
|
29545
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" },
|
|
29546
29794
|
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
29547
29795
|
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
29548
29796
|
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
@@ -43080,7 +43328,7 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
43080
43328
|
}
|
|
43081
43329
|
}
|
|
43082
43330
|
if (state?.toLowerCase() === "closed") continue;
|
|
43083
|
-
const contest = await checkContest(client, { repository: repo, number }, actor, viewerLogin);
|
|
43331
|
+
const contest = await checkContest(client, { repository: repo, number }, actor, viewerLogin, deps.appClient);
|
|
43084
43332
|
if (!contest.contested) continue;
|
|
43085
43333
|
const ref = `${repo}#${number}`;
|
|
43086
43334
|
const commentsPath = `repos/${repo}/issues/${number}/comments`;
|
|
@@ -43164,6 +43412,7 @@ var SERVICE_NAME_RE = /^[A-Za-z0-9_.-]+$/;
|
|
|
43164
43412
|
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
43165
43413
|
var EXCLUDE_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
43166
43414
|
var EXCLUDE_LABEL = "mmi.runtime-env.exclude";
|
|
43415
|
+
var KEYS_LABEL = "mmi.runtime-env.keys";
|
|
43167
43416
|
var RUNTIME_ENV_OVERRIDE_RELPATH = "tmp/stage/runtime-env.override.yml";
|
|
43168
43417
|
function labelsMarkRuntimeEnv(labels) {
|
|
43169
43418
|
if (Array.isArray(labels)) {
|
|
@@ -43190,12 +43439,16 @@ function labelValue(labels, name) {
|
|
|
43190
43439
|
if (labels && typeof labels === "object") return String(labels[name] ?? "");
|
|
43191
43440
|
return "";
|
|
43192
43441
|
}
|
|
43193
|
-
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) {
|
|
43194
43447
|
if (!value.trim()) return [];
|
|
43195
43448
|
return value.split(",").map((raw) => {
|
|
43196
43449
|
const key = raw.trim();
|
|
43197
43450
|
if (!EXCLUDE_KEY_RE.test(key)) {
|
|
43198
|
-
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_]*$)`);
|
|
43199
43452
|
}
|
|
43200
43453
|
return key;
|
|
43201
43454
|
});
|
|
@@ -43218,6 +43471,23 @@ function runtimeEnvExcludes(composeConfigJson) {
|
|
|
43218
43471
|
}
|
|
43219
43472
|
return out;
|
|
43220
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
|
+
}
|
|
43221
43491
|
function runtimeEnvMarkedServices(composeConfigJson) {
|
|
43222
43492
|
let parsed;
|
|
43223
43493
|
try {
|
|
@@ -43229,12 +43499,16 @@ function runtimeEnvMarkedServices(composeConfigJson) {
|
|
|
43229
43499
|
if (!services || typeof services !== "object" || Array.isArray(services)) return [];
|
|
43230
43500
|
return Object.entries(services).filter(([name, svc]) => SERVICE_NAME_RE.test(name) && labelsMarkRuntimeEnv(svc?.labels)).map(([name]) => name).sort();
|
|
43231
43501
|
}
|
|
43232
|
-
function renderRuntimeEnvOverride(services, keys, excludes = {}) {
|
|
43502
|
+
function renderRuntimeEnvOverride(services, keys, excludes = {}, selects = {}) {
|
|
43233
43503
|
const safeServices = services.filter((s) => SERVICE_NAME_RE.test(s)).sort();
|
|
43234
43504
|
const safeKeys = [...new Set(keys.filter((k) => ENV_KEY_RE.test(k)))].sort();
|
|
43235
43505
|
const keysFor = (service) => {
|
|
43236
43506
|
const skip = new Set(excludes[service] ?? []);
|
|
43237
|
-
|
|
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)));
|
|
43238
43512
|
};
|
|
43239
43513
|
const withKeys = safeServices.filter((s) => keysFor(s).length);
|
|
43240
43514
|
if (!withKeys.length) return null;
|
|
@@ -43657,7 +43931,7 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
43657
43931
|
} catch {
|
|
43658
43932
|
return {};
|
|
43659
43933
|
}
|
|
43660
|
-
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson));
|
|
43934
|
+
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
43661
43935
|
if (!body) return {};
|
|
43662
43936
|
const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
43663
43937
|
(0, import_node_fs39.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
@@ -51556,6 +51830,352 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
51556
51830
|
});
|
|
51557
51831
|
}
|
|
51558
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
|
+
|
|
51559
52179
|
// src/api-read.ts
|
|
51560
52180
|
init_cli_shared();
|
|
51561
52181
|
var API_ACCEPT_HEADERS = {
|
|
@@ -51576,13 +52196,19 @@ var PULL_FILES_RE = new RegExp(`^repos/${NAME}/${NAME}/pulls/\\d+/files$`);
|
|
|
51576
52196
|
var COMPARE_RE = new RegExp(`^repos/${NAME}/${NAME}/compare/${FREE}\\.\\.\\.${FREE}$`);
|
|
51577
52197
|
var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
51578
52198
|
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
51579
|
-
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";
|
|
51580
52203
|
function parseApiGetPath(rawPath) {
|
|
51581
52204
|
const path2 = rawPath.trim();
|
|
51582
52205
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
51583
52206
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
51584
52207
|
if (PULL_FILES_RE.test(path2)) return { kind: "pull-files", path: path2, paginate: true };
|
|
51585
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 };
|
|
51586
52212
|
const q = path2.indexOf("?");
|
|
51587
52213
|
const base = q === -1 ? path2 : path2.slice(0, q);
|
|
51588
52214
|
const query = q === -1 ? "" : path2.slice(q + 1);
|
|
@@ -51602,14 +52228,21 @@ function buildApiGetArgs(path2, accept) {
|
|
|
51602
52228
|
async function runApiGet(run, path2, accept) {
|
|
51603
52229
|
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
51604
52230
|
}
|
|
51605
|
-
function registerApiReadCommands(program3) {
|
|
51606
|
-
const
|
|
51607
|
-
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;
|
|
51608
52241
|
let body;
|
|
51609
52242
|
try {
|
|
51610
52243
|
body = await runApiGet(
|
|
51611
|
-
async (args, timeoutMs) => (
|
|
51612
|
-
|
|
52244
|
+
async (args, timeoutMs) => spawn6(args, timeoutMs, env),
|
|
52245
|
+
req.path,
|
|
51613
52246
|
o.accept
|
|
51614
52247
|
);
|
|
51615
52248
|
} catch (e) {
|
|
@@ -55329,6 +55962,10 @@ registerDiscoveryCommands(program2);
|
|
|
55329
55962
|
registerExplainCommand(program2);
|
|
55330
55963
|
registerPrLifecycleCommands(program2);
|
|
55331
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));
|
|
55332
55969
|
registerSessionReport(program2);
|
|
55333
55970
|
registerLearningClosureRateCommand(program2);
|
|
55334
55971
|
registerLearningPickupCommand(program2);
|
package/cli/package.json
CHANGED