fix: bound gh pr create next
This commit is contained in:
@@ -22,7 +22,8 @@ GitHub issue/PR 正式读写必须走 `bun scripts/cli.ts gh ...` 或 `trans gh:
|
||||
- 单次正文或 patch 必须直接从 heredoc 进入 stdin,禁止先写 `/tmp` 或工作区临时文件再用 `< file` 转交。
|
||||
- 只有需要复用或正式保留的文件才使用 `--body-file`。
|
||||
- 不要把 Markdown 塞进 shell 参数。
|
||||
- PR merge 只走 guarded `gh pr merge`;`gh pr create` 的 Next 默认是 `--merge --delete-branch`,只有确认 ancestry 可丢弃时才显式 `--squash`。
|
||||
- `gh pr create` 默认 Next 只给有界 `pr view`、`pr review-plan`、`pr preflight` 和 closeout status 下钻;不得默认提示 `preflight --full|--raw`、手工 CI/CD、mirror sync、PipelineRun 或 Argo refresh。
|
||||
- PR merge 只走 guarded `gh pr merge`;主代理按 `$unidesk-subagent` 完成 review/preflight/merge 授权判断后再执行,不把“禁止默认 Next”理解成“PR 永远不能由主代理受控合并”;只有确认 ancestry 可丢弃时才显式 `--squash`。
|
||||
|
||||
## 常用入口
|
||||
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
# GitHub Pull Requests
|
||||
|
||||
PR work uses guarded UniDesk GitHub commands:
|
||||
PR 工作必须使用受控 UniDesk GitHub 命令:
|
||||
|
||||
- `pr review-plan`, `pr diff --file`, and bounded file drill-down before review.
|
||||
- `pr view <number> --json body,...` emits the full body once at `.data.pullRequest.body`; `.data.json.body` is intentionally omitted, and `.data.compatibility.bodyPath` plus `bodyMigrationHint` disclose the stable machine-readable path.
|
||||
- `pr preflight` is optional read-only diagnosis; `pr merge` runs preflight internally.
|
||||
- `pr merge --merge` by default deletes the merged same-repo head branch, cleans a matching clean local `.worktree`, and fast-forwards the local main worktree on the PR base branch.
|
||||
- `pr merge --merge --sync-node JD01` additionally runs mapped node source-workspace sync when supported, currently HWLAB `v0.3`.
|
||||
- Use `--keep-branch` or `--skip-local-closeout` only when intentionally preserving post-merge state.
|
||||
- Use squash only when ancestry and semantic absorption are explicitly safe.
|
||||
- review 前先用 `pr review-plan`、`pr diff --file` 和有界文件下钻。
|
||||
- `pr view <number> --json body,...` 只在 `.data.pullRequest.body` 输出一次完整正文;`.data.json.body` 故意省略,并通过 `.data.compatibility.bodyPath` 与 `bodyMigrationHint` 披露稳定机器路径。
|
||||
- `pr create` 默认 Next 只给有界 observe/review/preflight/status:
|
||||
- `pr view`;
|
||||
- `pr review-plan`;
|
||||
- `pr preflight`;
|
||||
- 显式 closeout 字段 status。
|
||||
默认不得提示 `preflight --full|--raw`、手工 CI/CD、mirror sync、创建 PipelineRun 或 Argo refresh。
|
||||
- `pr preflight` 是可选只读诊断;`pr merge` 内部会自行执行 preflight。
|
||||
- 主代理受控合并仍然允许:
|
||||
- 主代理按 `$unidesk-subagent` 完成 review/preflight/merge 判断后使用 guarded `pr merge`;
|
||||
- 不得把 `pr create` Next 限制误解成“PR 永远不能人工受控合并”。
|
||||
- `pr merge --merge` 默认删除已合并的同 repo head branch,清理匹配且干净的本地 `.worktree`,并快进位于 PR base branch 的本地主工作区。
|
||||
- `pr merge --merge --sync-node JD01` 在支持时额外执行映射节点 source-workspace sync;当前用于 HWLAB `v0.3`。
|
||||
- 只有明确需要保留合并后状态时才使用 `--keep-branch` 或 `--skip-local-closeout`。
|
||||
- 只有 ancestry 和语义吸收都明确安全时才使用 squash。
|
||||
|
||||
Closeout should mention source branch, validation evidence and any residual risk.
|
||||
Closeout 应写明 source branch、验证证据和残余风险。
|
||||
|
||||
@@ -67,6 +67,6 @@
|
||||
|
||||
解决 [UniDesk #1837](https://github.com/pikasTech/unidesk/issues/1837):前向兼容 [AgentRun #319](https://github.com/pikasTech/agentrun/issues/319) 的 durable runnerAdmission 成功响应,在 text/JSON/YAML 有界投影中展示 mode/state/reason/disposition、run/command/dispatchIntent/plannedRunner identity、mutation/partialWrite/recoveredPriorPartialWrite、retryability 与正式只读下钻;明确 planned runner 不等于 Kubernetes Job 已创建。旧 manager 未返回该字段时保持现有合同;只做 render-only,不在 UniDesk 实现 dispatcher/session 状态机,先合并 consumer 再合并 AgentRun producer,最终用原 Artificer session 验收,完成任务后将详细报告写入[任务报告](./details/cli-output-progressive-disclosure/R3.7_Task_Report.md)。
|
||||
|
||||
### R3.8 [in_progress]
|
||||
### R3.8 [completed]
|
||||
|
||||
解决 [UniDesk #1838](https://github.com/pikasTech/unidesk/issues/1838):清理 gh pr create 默认输出中与自动 PR monitor/自动 CI/CD 冲突的手工 merge 和 preflight --full Next;默认只展示有界 PR observe/preflight/status,并以 typed 状态区分等待自动合并、review/check blocker 与自动链异常。手工 merge、--full/--raw 只在用户明确授权或显式披露时出现;同步修正相关 skill、长期参考和测试 fixture,禁止用错误提示诱导人工补链,完成任务后将详细报告写入[任务报告](./details/cli-output-progressive-disclosure/R3.8_Task_Report.md)。
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# R3.8 任务报告
|
||||
|
||||
## 结论
|
||||
|
||||
已解决 [UniDesk #1838](https://github.com/pikasTech/unidesk/issues/1838) 中 `gh pr create` 默认 Next 误导问题。
|
||||
|
||||
`gh pr create` 默认输出现在只给有界的 PR observe/review/preflight/status:
|
||||
|
||||
- `gh pr view <pr-number>`;
|
||||
- `gh pr review-plan <pr-number>`;
|
||||
- `gh pr preflight <pr-number>`;
|
||||
- `gh pr view <pr-number> --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup`。
|
||||
|
||||
默认 Next 不再提示:
|
||||
|
||||
- `gh pr merge ...`;
|
||||
- `preflight --full` 或 `preflight --raw`;
|
||||
- 手工 CI/CD;
|
||||
- mirror sync;
|
||||
- PipelineRun;
|
||||
- Argo refresh。
|
||||
|
||||
同时保留主代理受控合并边界:
|
||||
|
||||
- `gh pr create` 不把 merge 作为默认下一步;
|
||||
- 主代理仍可按 `$unidesk-subagent` 完成 review/preflight/merge 判断后,通过 guarded `gh pr merge` 执行授权合并;
|
||||
- 该限制不能解释成“PR 永远不能人工受控合并”。
|
||||
|
||||
## 变更
|
||||
|
||||
- `scripts/src/gh/pr-commands.ts`:
|
||||
- 增加 `bounded-pr-create-next` 结构化 `next`;
|
||||
- 增加 `waiting-pr-monitor`、`review-or-check-blocker`、`automatic-delivery-anomaly` typed 状态说明;
|
||||
- 默认文本 Next 改为 observe/review/preflight/status;
|
||||
- Disclosure 改为说明 `--full|--raw` 与 merge/delivery 命令需要显式披露或合并授权。
|
||||
- `scripts/src/gh-pr-create-render.test.ts`:
|
||||
- 固定 ready PR 创建后的默认 Next 合同;
|
||||
- 固定 dry-run 不再提示 post-create merge。
|
||||
- `.agents/skills/unidesk-gh/SKILL.md` 与 `references/pull-requests.md`:
|
||||
- 同步 `pr create` 默认 Next 边界;
|
||||
- 明确主代理受控 merge 仍然允许。
|
||||
|
||||
## 验证
|
||||
|
||||
- `bun test scripts/src/gh-pr-create-render.test.ts`:
|
||||
- 2 pass,0 fail。
|
||||
- `bun test scripts/src/gh-pr-ready.test.ts`:
|
||||
- 7 pass,0 fail。
|
||||
- `bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title "R3.8 dry-run" --base master --head fix/1838-pr-create-next --dry-run --body-stdin`:
|
||||
- 默认 Next 只包含 rerun、observe、review、preflight、status 和 `$unidesk-subagent` 流程提示;
|
||||
- 未出现 `gh pr merge`;
|
||||
- 未出现 `preflight --full`。
|
||||
|
||||
## 未执行
|
||||
|
||||
- 未合并 PR。
|
||||
- 未部署。
|
||||
- 未触发 CI/CD、mirror sync、PipelineRun 或 Argo refresh。
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user