Merge pull request #1840 from pikasTech/fix/1838-pr-create-next
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

修正 gh pr create 默认 Next
This commit is contained in:
Lyon
2026-07-12 17:14:30 +08:00
committed by GitHub
6 changed files with 182 additions and 23 deletions
+70
View File
@@ -0,0 +1,70 @@
import { describe, expect, test } from "bun:test";
import { renderPrCreateTable } from "./gh/pr-commands";
describe("gh pr create default Next", () => {
test("created ready PR keeps bounded observe/review/preflight/status and suppresses manual delivery repair", () => {
const rendered = renderPrCreateTable({
ok: true,
command: "pr create",
repo: "pikasTech/unidesk",
pr: {
number: 1838,
title: "Bounded PR create next",
url: "https://github.com/pikasTech/unidesk/pull/1838",
draft: false,
head: { ref: "fix/1838-pr-create-next" },
base: { ref: "master" },
},
request: {
title: "Bounded PR create next",
base: "master",
head: "fix/1838-pr-create-next",
draft: false,
},
next: {
policy: "bounded-pr-create-next",
defaultState: "waiting-pr-monitor",
commands: {
observe: "bun scripts/cli.ts gh pr view 1838 --repo pikasTech/unidesk",
review: "bun scripts/cli.ts gh pr review-plan 1838 --repo pikasTech/unidesk",
preflight: "bun scripts/cli.ts gh pr preflight 1838 --repo pikasTech/unidesk",
status: "bun scripts/cli.ts gh pr view 1838 --repo pikasTech/unidesk --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup",
},
},
});
expect(rendered).toContain("nextState=waiting-pr-monitor");
expect(rendered).toContain("observe: bun scripts/cli.ts gh pr view 1838 --repo pikasTech/unidesk");
expect(rendered).toContain("review: bun scripts/cli.ts gh pr review-plan 1838 --repo pikasTech/unidesk");
expect(rendered).toContain("preflight: bun scripts/cli.ts gh pr preflight 1838 --repo pikasTech/unidesk");
expect(rendered).toContain("status: bun scripts/cli.ts gh pr view 1838 --repo pikasTech/unidesk --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup");
expect(rendered).toContain("$unidesk-subagent review/preflight/merge policy");
expect(rendered).not.toContain("gh pr merge 1838");
expect(rendered).not.toContain("preflight 1838 --repo pikasTech/unidesk --full");
expect(rendered).not.toContain("preflight 1838 --repo pikasTech/unidesk --raw");
expect(rendered).not.toContain("PipelineRun");
expect(rendered).not.toContain("Argo refresh");
expect(rendered).not.toContain("mirror sync");
});
test("dry-run keeps create guidance without suggesting post-create merge", () => {
const rendered = renderPrCreateTable({
ok: true,
command: "pr create",
repo: "pikasTech/unidesk",
dryRun: true,
draft: false,
request: {
title: "Bounded PR create next",
base: "master",
head: "fix/1838-pr-create-next",
draft: false,
},
});
expect(rendered).toContain("rerun without --dry-run to create the PR");
expect(rendered).toContain("observe: bun scripts/cli.ts gh pr view <pr-number> --repo pikasTech/unidesk");
expect(rendered).not.toContain("after creation: bun scripts/cli.ts gh pr merge");
expect(rendered).not.toContain("preflight <pr-number> --repo pikasTech/unidesk --full");
});
});
+33 -12
View File
@@ -11,6 +11,28 @@ import { commentSummary, ghShort, ghTable, ghText } from "./render";
import { branchInfo, branchSummary, compareBranches, compareSummary, prCommentPlannedOperation, prCreatePlannedOperation, prUrl, repoInfo, repoSummary } from "./repo-compare";
import type { BodyUpdateMode, GitHubCommandResult, GitHubComment, GitHubOptions, GitHubPullRequest } from "./types";
function prCreateNext(repo: string, number: string, status: "created" | "dry-run", draft: boolean): Record<string, unknown> {
const target = status === "created" && number !== "-" ? number : "<pr-number>";
return {
policy: "bounded-pr-create-next",
defaultState: draft ? "waiting-ready-for-review" : "waiting-pr-monitor",
states: {
"waiting-pr-monitor": "PR 已创建;默认等待自动 PR monitor 或主代理按 $unidesk-subagent 执行受控 review/preflight/merge。",
"review-or-check-blocker": "review、mergeability 或 check 未满足时只做有界 review/preflight/status 下钻。",
"automatic-delivery-anomaly": "合并后的自动链异常只做只读 status/history 诊断,修 owning YAML/controller/source,不手工补 CI/CD。",
},
commands: {
observe: `bun scripts/cli.ts gh pr view ${target} --repo ${repo}`,
review: `bun scripts/cli.ts gh pr review-plan ${target} --repo ${repo}`,
preflight: `bun scripts/cli.ts gh pr preflight ${target} --repo ${repo}`,
status: `bun scripts/cli.ts gh pr view ${target} --repo ${repo} --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup`,
...(draft ? { ready: `bun scripts/cli.ts gh pr ready ${target} --repo ${repo}` } : {}),
...(status === "dry-run" ? { create: "rerun without --dry-run to create the PR" } : {}),
},
suppressedDefaults: ["gh pr merge", "preflight --full", "preflight --raw", "manual CI/CD", "mirror sync", "PipelineRun", "Argo refresh"],
};
}
export function prEditBodyPlan(mode: BodyUpdateMode, input: { body: string; bodySource: Record<string, unknown> } | null, finalBody: string | undefined, existingBody: string | null): Record<string, unknown> | null {
if (input === null || finalBody === undefined) return null;
return {
@@ -42,6 +64,7 @@ export async function prCreate(repo: string, token: string, options: GitHubOptio
dryRun: true,
planned: true,
draft: options.draft,
next: prCreateNext(repo, "<pr-number>", "dry-run", options.draft),
...planned,
});
}
@@ -94,6 +117,7 @@ export async function prCreate(repo: string, token: string, options: GitHubOptio
draft: options.draft,
bodyChars: body.length,
},
next: prCreateNext(repo, String(pr.number), "created", pr.draft === true),
rest: true,
});
}
@@ -115,6 +139,8 @@ export function renderPrCreateTable(result: GitHubCommandResult): string {
const head = ghText(isRecord(pr.head) ? pr.head.ref : request.head ?? result.head);
const draft = request.draft === true || pr.draft === true || result.draft === true;
const status = result.dryRun === true ? "dry-run" : "created";
const next = isRecord(result.next) ? result.next : prCreateNext(ghText(result.repo), number === "-" ? "<pr-number>" : number, status, draft);
const commands = isRecord(next.commands) ? next.commands : {};
const rows = [[
number === "-" ? "-" : `#${number}`,
status,
@@ -130,23 +156,18 @@ export function renderPrCreateTable(result: GitHubCommandResult): string {
"",
"Summary:",
` repo=${ghText(result.repo)} url=${ghText(pr.url ?? pr.html_url ?? result.url)}`,
` nextState=${ghText(next.defaultState)} policy=${ghText(next.policy)}`,
"",
"Next:",
];
if (status === "created" && number !== "-") {
if (draft) lines.push(` bun scripts/cli.ts gh pr ready ${number} --repo ${ghText(result.repo)}`);
else lines.push(` bun scripts/cli.ts gh pr merge ${number} --repo ${ghText(result.repo)} --merge --delete-branch`);
lines.push(` bun scripts/cli.ts gh pr preflight ${number} --repo ${ghText(result.repo)} --full`);
lines.push(" Use --squash only when ancestry and merge-parent history are intentionally disposable.");
} else {
lines.push(" rerun without --dry-run to create the PR");
lines.push(draft
? ` after creation: bun scripts/cli.ts gh pr ready <pr-number> --repo ${ghText(result.repo)}`
: ` after creation: bun scripts/cli.ts gh pr merge <pr-number> --repo ${ghText(result.repo)} --merge --delete-branch`);
lines.push(" Use --squash only when ancestry and merge-parent history are intentionally disposable.");
if (typeof commands.create === "string") lines.push(` ${commands.create}`);
if (typeof commands.ready === "string") lines.push(` ${commands.ready}`);
for (const key of ["observe", "review", "preflight", "status"]) {
if (typeof commands[key] === "string") lines.push(` ${key}: ${commands[key]}`);
}
lines.push(" flow: main agent follows $unidesk-subagent review/preflight/merge policy; do not use full/raw disclosure or manual delivery repair by default.");
lines.push("", "Disclosure:");
lines.push(" default view is a bounded table; use gh pr view/preflight --full for structured metadata.");
lines.push(" default view is bounded; --full/--raw and manual merge/delivery commands require explicit disclosure or merge authorization.");
return lines.join("\n");
}