fix: expose PR closeout metadata
This commit is contained in:
+60
-8
@@ -22,6 +22,8 @@ const ISSUE_VIEW_JSON_FIELDS = ["body", "title", "state", "comments", "number",
|
||||
const ISSUE_LIST_JSON_FIELDS = ["number", "title", "state", "url", "updatedAt", "createdAt", "author", "labels"] as const;
|
||||
const PR_LIST_JSON_FIELDS = ["body", "title", "state", "number", "url", "author", "head", "base", "draft", "createdAt", "updatedAt"] as const;
|
||||
const PR_READ_JSON_FIELDS = ["body", "title", "state", "stateDetail", "number", "url", "author", "head", "base", "draft", "createdAt", "updatedAt", "closed", "closedAt", "merged", "mergedAt", "mergeCommit", "headRefName", "baseRefName", "mergeable", "mergeStateStatus", "statusCheckRollup"] as const;
|
||||
const PR_CLOSEOUT_JSON_FIELDS = ["mergeable", "mergeStateStatus", "statusCheckRollup"] as const;
|
||||
const PR_CLOSEOUT_VIEW_JSON = "headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup";
|
||||
const ISSUE_LIST_STATES = ["open", "closed", "all"] as const;
|
||||
const BODY_UPDATE_MODES = ["replace", "append"] as const;
|
||||
const BOARD_MUTATION_SECTIONS = ["open", "closed"] as const;
|
||||
@@ -594,6 +596,17 @@ function parseIssueListJsonFields(requested: string[] | undefined): IssueListJso
|
||||
}
|
||||
|
||||
function parsePrListJsonFields(requested: string[] | undefined): PrListJsonField[] | undefined {
|
||||
if (requested !== undefined) {
|
||||
const listFields = new Set<string>(PR_LIST_JSON_FIELDS);
|
||||
const closeoutFields = requested.filter((field) => (PR_CLOSEOUT_JSON_FIELDS as readonly string[]).includes(field));
|
||||
const unsupported = requested.filter((field) => !listFields.has(field));
|
||||
if (closeoutFields.length > 0) {
|
||||
throw new Error(`unsupported gh pr list --json closeout field(s): ${closeoutFields.join(", ")}; use gh pr view <number> --json ${PR_CLOSEOUT_VIEW_JSON} for mergeability/statusCheckRollup metadata; pr list supported fields: ${PR_LIST_JSON_FIELDS.join(",")}`);
|
||||
}
|
||||
if (unsupported.length > 0) {
|
||||
throw new Error(`unsupported gh pr list --json field(s): ${unsupported.join(", ")}; supported fields: ${PR_LIST_JSON_FIELDS.join(",")}; closeout metadata requires gh pr view <number> --json ${PR_CLOSEOUT_VIEW_JSON}`);
|
||||
}
|
||||
}
|
||||
return validateJsonFields("gh pr list", requested, PR_LIST_JSON_FIELDS);
|
||||
}
|
||||
|
||||
@@ -3854,6 +3867,47 @@ function prMetadataSummary(metadata: GitHubPullRequestGraphqlMetadata): Record<s
|
||||
};
|
||||
}
|
||||
|
||||
function prMergeBoundary(): Record<string, unknown> {
|
||||
return {
|
||||
runnerAllowed: ["pr create", "pr update/edit", "pr comment", "pr read/view", "pr close"],
|
||||
ordinaryRunnerFinalActionAllowed: true,
|
||||
commanderRequiredWhen: ["conflicts", "failed required checks", "production/runtime/release/security/database scope", "ambiguous task boundary"],
|
||||
hostAllowedToolsAfterReview: ["system gh pr merge", "GitHub UI merge/close"],
|
||||
unideskCliMergeSupported: false,
|
||||
degradedReason: "unsupported-command",
|
||||
};
|
||||
}
|
||||
|
||||
function prCloseoutMetadata(metadata: GitHubPullRequestGraphqlMetadata): Record<string, unknown> {
|
||||
const summary = prMetadataSummary(metadata);
|
||||
const missingOrUnknownFields = PR_CLOSEOUT_JSON_FIELDS.filter((field) => {
|
||||
const value = summary[field];
|
||||
if (value === null || value === undefined) return true;
|
||||
return typeof value === "string" && value.toUpperCase() === "UNKNOWN";
|
||||
});
|
||||
return {
|
||||
ok: missingOrUnknownFields.length === 0,
|
||||
source: "github-graphql",
|
||||
missingOrUnknownFields,
|
||||
advice: missingOrUnknownFields.length === 0
|
||||
? "Closeout GraphQL metadata is present; still review checks, branch scope, and task boundary before final action."
|
||||
: "Some closeout GraphQL metadata is missing or unknown; retry or cross-check with system gh/GitHub UI before treating the PR as merge-ready.",
|
||||
mergeBoundary: prMergeBoundary(),
|
||||
};
|
||||
}
|
||||
|
||||
function prCloseoutMetadataError(error: GitHubErrorPayload): Record<string, unknown> {
|
||||
return {
|
||||
ok: false,
|
||||
source: "github-graphql",
|
||||
degradedReason: error.degradedReason,
|
||||
runnerDisposition: error.runnerDisposition,
|
||||
message: error.message,
|
||||
advice: "Closeout GraphQL metadata was not available; retry or use a read-only system gh/GitHub UI cross-check before deciding merge readiness.",
|
||||
mergeBoundary: prMergeBoundary(),
|
||||
};
|
||||
}
|
||||
|
||||
function needsPrGraphqlMetadata(fields: readonly string[] | undefined): boolean {
|
||||
if (fields === undefined) return false;
|
||||
return fields.some((field) => field === "mergeable" || field === "mergeStateStatus" || field === "statusCheckRollup");
|
||||
@@ -5453,7 +5507,7 @@ async function prRead(repo: string, token: string, number: number, jsonFields: P
|
||||
if (isGitHubError(pr)) return commandError(commandName, repo, pr, { number });
|
||||
const summary = prSummary(pr);
|
||||
const metadata = needsPrGraphqlMetadata(jsonFields) ? await prGraphqlMetadata(repo, token, number) : null;
|
||||
if (isGitHubError(metadata)) return commandError(commandName, repo, metadata, { number, phase: "fetch-pr-closeout-metadata", pullRequest: summary, requestedJsonFields: jsonFields ?? [] });
|
||||
if (isGitHubError(metadata)) return commandError(commandName, repo, metadata, { number, phase: "fetch-pr-closeout-metadata", pullRequest: summary, requestedJsonFields: jsonFields ?? [], closeoutMetadata: prCloseoutMetadataError(metadata) });
|
||||
const selectionSummary = metadata === null ? summary : { ...summary, ...prMetadataSummary(metadata) };
|
||||
return {
|
||||
ok: true,
|
||||
@@ -5461,6 +5515,7 @@ async function prRead(repo: string, token: string, number: number, jsonFields: P
|
||||
repo,
|
||||
...(disclosure === null ? {} : { disclosure }),
|
||||
pullRequest: summary,
|
||||
...(metadata === null ? {} : { closeoutMetadata: prCloseoutMetadata(metadata) }),
|
||||
...(jsonFields === undefined ? {} : { jsonFields, json: selectedPrJson(selectionSummary, jsonFields) }),
|
||||
};
|
||||
}
|
||||
@@ -5539,7 +5594,8 @@ export function ghHelp(): unknown {
|
||||
"comment delete is supported because GitHub supports deleting issue comments; issue/pr hard delete is unsupported and close is the lifecycle alternative.",
|
||||
"PR files is the canonical compact changed-file/stat summary. It uses GitHub REST, returns bounded file rows, additions/deletions/changes when available, truncation metadata, and a next command for full details. Raw diff patches are not emitted by default; gh pr diff <number> --stat is a compatibility alias for the same JSON summary.",
|
||||
"PR edit/update PATCHes /repos/{owner}/{repo}/pulls/{number} through REST only, never GitHub Projects Classic GraphQL/projectCards, and returns low-noise JSON with repo, PR number, changedFields, url, and body size/SHA metadata instead of echoing the full body.",
|
||||
"PR read is the canonical read path; view remains a compatibility alias. PR read/view accept owner/repo#number shorthand and derive --repo unless an explicit conflicting --repo is supplied, which fails structurally with suggested commands. PR read/view supports REST closeout fields stateDetail, closed, closedAt, merged, mergedAt, mergeCommit, headRefName, and baseRefName; mergeable, mergeStateStatus, and statusCheckRollup are fetched through GitHub GraphQL only when requested or when --raw/--full requests full disclosure.",
|
||||
"PR read is the canonical read path; view remains a compatibility alias. PR read/view accept owner/repo#number shorthand and derive --repo unless an explicit conflicting --repo is supplied, which fails structurally with suggested commands. PR read/view supports REST closeout fields stateDetail, closed, closedAt, merged, mergedAt, mergeCommit, headRefName, and baseRefName; mergeable, mergeStateStatus, and statusCheckRollup are fetched through GitHub GraphQL only when requested or when --raw/--full requests full disclosure, and closeoutMetadata makes GraphQL errors plus UNKNOWN/null metadata explicit.",
|
||||
"PR list does not fetch mergeability or statusCheckRollup; request those closeout fields with gh pr view <number> --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup.",
|
||||
"PR preflight is a low-noise read-only closeout helper. It combines redacted auth capability, PR branch/state metadata, mergeability, mergeStateStatus, compact status check counts, and the explicit UniDesk REST CLI no-merge policy. Use --full or --raw to include all fetched status contexts.",
|
||||
"PR create/edit/update/comment are safe-write operations with dry-run planning; merge is intentionally unsupported in this phase.",
|
||||
],
|
||||
@@ -5836,12 +5892,8 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
"PR merge is intentionally unsupported by the UniDesk REST CLI; PR-bound GPT-5.5 runners may self-close/merge ordinary in-boundary PRs after checks using repo-owned GitHub paths, while high-risk or ambiguous PRs stay commander-reviewed.",
|
||||
{
|
||||
closeoutBoundary: {
|
||||
runnerAllowed: ["pr create", "pr update/edit", "pr comment", "pr read/view", "pr close"],
|
||||
ordinaryRunnerFinalActionAllowed: true,
|
||||
commanderRequiredWhen: ["conflicts", "failed required checks", "production/runtime/release/security/database scope", "ambiguous task boundary"],
|
||||
hostAllowedToolsAfterReview: ["system gh pr merge", "GitHub UI merge/close"],
|
||||
unideskCliMergeSupported: false,
|
||||
degradedReason: "unsupported-command",
|
||||
...prMergeBoundary(),
|
||||
readOnlyCloseoutCommand: `bun scripts/cli.ts gh pr view ${third ?? "<number>"} --repo ${options.repo} --json ${PR_CLOSEOUT_VIEW_JSON}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user