From 0fc0fb73366fea0f922299eb32ea3dad4aff9381 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 10 Jul 2026 06:27:46 +0200 Subject: [PATCH] fix: avoid duplicate gh view bodies --- .agents/skills/unidesk-gh/SKILL.md | 1 + .agents/skills/unidesk-gh/references/issues.md | 1 + .agents/skills/unidesk-gh/references/pull-requests.md | 1 + docs/reference/cli.md | 2 ++ scripts/src/gh/auth-pr-read.ts | 10 +++++++++- scripts/src/gh/help.ts | 4 ++-- scripts/src/gh/issue-read.ts | 10 +++++++++- 7 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.agents/skills/unidesk-gh/SKILL.md b/.agents/skills/unidesk-gh/SKILL.md index 9167aa54..e258b1d0 100644 --- a/.agents/skills/unidesk-gh/SKILL.md +++ b/.agents/skills/unidesk-gh/SKILL.md @@ -16,6 +16,7 @@ GitHub issue/PR 正式读写必须走 `bun scripts/cli.ts gh ...` 或 `trans gh: - 规划型、多阶段、架构/API/平台运维类 issue 第一阶段必须 `P0 SPEC 先行`;细则见 [references/issues.md](references/issues.md)。 - `P0 SPEC 先行` 段不得写入硬编码阈值、采样周期、重试次数、并发数等可调参数;必须写明这些参数由指定 YAML/source-of-truth 控制,issue 只列配置路径、字段族和验收读取方式。 - `gh` 默认输出是 k8s 风格 text/table/summary/Next/Disclosure;脚本消费或全量排障必须显式用 `--json`、`--full` 或 `--raw`。 +- `gh issue view` / `gh pr view` 显式请求 `--json body,...` 时,完整正文只在 `.data.issue.body` / `.data.pullRequest.body` 出现一次;兼容 `.data.json.body` 不再复制大字段,迁移路径由 `.data.compatibility.bodyPath` / `bodyMigrationHint` 披露。 - 多行正文使用 quoted heredoc:`--body-stdin <<'EOF'`;issue close/reopen 生命周期评论只用 `--comment-stdin <<'EOF'`。不要把 Markdown 塞进 shell 参数。 - PR merge 只走 guarded `gh pr merge`;`gh pr create` 的 Next 默认是 `--merge --delete-branch`,只有确认 ancestry 可丢弃时才显式 `--squash`。 diff --git a/.agents/skills/unidesk-gh/references/issues.md b/.agents/skills/unidesk-gh/references/issues.md index 141f4180..3a28691c 100644 --- a/.agents/skills/unidesk-gh/references/issues.md +++ b/.agents/skills/unidesk-gh/references/issues.md @@ -5,6 +5,7 @@ Issue writes use `bun scripts/cli.ts gh ...` or the `trans gh:` virtual filesyst - Body and comments default to Chinese. - Recent issue comment progress should prefer `bun scripts/cli.ts gh issue comments --repo owner/name [--limit N] [--full|--raw]`; structured output is stable at `.data.comments`. - `gh issue view --json comments` remains the legacy compatibility path at `.data.json.comments`; when comments are requested it should disclose the preferred `gh issue comments ` migration path instead of silently pushing operators toward `/tmp` dump recovery. +- `gh issue view --json body,...` emits the full body once at `.data.issue.body`; `.data.json.body` is intentionally omitted, and `.data.compatibility.bodyPath` plus `bodyMigrationHint` disclose the stable machine-readable path. - New issues include `目标合并分支`. - Multi-stage architecture/API/platform issues begin with `P0 SPEC 先行`. - Long body text uses `--body-stdin`. diff --git a/.agents/skills/unidesk-gh/references/pull-requests.md b/.agents/skills/unidesk-gh/references/pull-requests.md index 53681766..49befb54 100644 --- a/.agents/skills/unidesk-gh/references/pull-requests.md +++ b/.agents/skills/unidesk-gh/references/pull-requests.md @@ -3,6 +3,7 @@ PR work uses guarded UniDesk GitHub commands: - `pr review-plan`, `pr diff --file`, and bounded file drill-down before review. +- `pr view --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`. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 7043caac..ec02f5c3 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -175,6 +175,8 @@ UniDesk/HWLAB Web 开发、HWLAB `web-probe run|script|observe|screenshot`、fak `gh issue view/read --json comments` 是常用人工 drill-down,不得默认输出完整 comment body 或同时在顶层和 `.data.json.comments` 重复整组 comments。默认只返回 comment metadata、body 字符数/SHA 和短 preview;`--full` 仍保持评论列表有界,只把 issue 字段扩到完整元数据。单条完整评论正文通过 `gh issue comment view --repo owner/name --full` 读取;`--raw` 才是显式展开所有评论正文的排障入口,可能触发 stdout dump。 +`gh issue view/read --json body,...` 与 `gh pr view/read --json body,...` 的完整正文只允许序列化一次,稳定路径分别是 `.data.issue.body` 与 `.data.pullRequest.body`。兼容选字段对象 `.data.json` 保留其他请求字段,但不再复制 `.data.json.body`;`.data.compatibility.bodyPath`、`omittedDuplicateBodyPath`、`duplicateBodyOmitted` 与 `bodyMigrationHint` 必须明确唯一读取路径和迁移原因。默认 bounded view 继续使用表格/摘要;显式 `--full` / `--raw` 继续展开相应元数据与正文,但真实单份内容仍超过 YAML stdout 预算时必须保留 dump metadata、SHA 与读取命令。 + `microservice proxy` 是面向人工验证和受控调试的私有后端入口。默认 method 为 GET;使用 `--body-json JSON`、`--body-file path` 或 `--body-stdin` 时默认 method 切换为 POST,也可显式加 `--method POST|PUT|PATCH|DELETE`,但 GET/HEAD 不允许携带请求体。所有请求仍受 config 中的 `allowedMethods` 和 `allowedPathPrefixes` 限制。为了避免 Pipeline snapshot 这类超大业务 JSON 造成 CLI 输出爆炸,响应 body 超过默认阈值时会返回 `bodyOmitted=true`、`bodyPreview`、`bodyBytes` 和 `rawHint`;`--raw` 仍受默认硬限额保护,需要完整 body 时显式添加 `--raw --full`,或用 `--max-body-bytes ` 调整预览阈值。正式 frontend 展示仍应优先使用业务控件和 `__unideskArrayLimit` 这类展示级裁剪参数,而不是默认倾倒完整 JSON。 ## Todo Note Resource CLI diff --git a/scripts/src/gh/auth-pr-read.ts b/scripts/src/gh/auth-pr-read.ts index 70ce4d0d..d9941140 100644 --- a/scripts/src/gh/auth-pr-read.ts +++ b/scripts/src/gh/auth-pr-read.ts @@ -181,6 +181,8 @@ export async function prRead(repo: string, token: string, number: number, jsonFi 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 ?? [], closeoutMetadata: prCloseoutMetadataError(metadata) }); const selectionSummary = metadata === null ? fullSummary : { ...fullSummary, ...prMetadataSummary(metadata) }; + const selectedJson = jsonFields === undefined ? null : selectedPrJson(selectionSummary, jsonFields); + if (includeBody && selectedJson !== null) delete selectedJson.body; return { ok: true, command: commandName, @@ -190,10 +192,16 @@ export async function prRead(repo: string, token: string, number: number, jsonFi ...(metadata === null ? {} : { closeoutMetadata: prCloseoutMetadata(metadata) }), ...(jsonFields === undefined ? {} : { jsonFields, - json: selectedPrJson(selectionSummary, jsonFields), + json: selectedJson, compatibility: { legacyJsonBodyPath: includeBody ? ".data.pullRequest.body" : null, + bodyPath: includeBody ? ".data.pullRequest.body" : null, + omittedDuplicateBodyPath: includeBody ? ".data.json.body" : null, + duplicateBodyOmitted: includeBody, bodyOmitted: !includeBody, + bodyMigrationHint: includeBody + ? "Read the full body from .data.pullRequest.body; .data.json.body is intentionally omitted so large bodies are emitted only once." + : null, }, }), }; diff --git a/scripts/src/gh/help.ts b/scripts/src/gh/help.ts index 342fbded..ef7c3f6c 100644 --- a/scripts/src/gh/help.ts +++ b/scripts/src/gh/help.ts @@ -73,7 +73,7 @@ export function ghHelp(): unknown { "issue list and pr list accept a single positional owner/repo as a compatibility alias for --repo owner/name. The positional repo and --repo must match if both are supplied; non-repo positionals fail structurally instead of falling back to the default repo.", "issue list defaults to --state open and bounded --limit 30; it paginates GitHub REST/Search pages internally when --limit exceeds GitHub's per-page cap and discloses pagination/rawCount/hasMore so operators do not mistake a single page for the full repository. --search uses GitHub Search Issues API with repo/type/state qualifiers for low-friction dedupe lookup before creating a new issue. --title-prefix filters the bounded listed issues locally by exact title startsWith, useful for [FEEDBACK] dedupe, and reports titleFilter input/output counts. Supported --json fields are number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels and unknown fields fail structurally.", "PR list defaults to --state all for compatibility with earlier UniDesk CLI behavior; supported states are open, closed, and all.", - "issue view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. View/read accept positional numbers, GitHub issue URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single issue/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create/scan-escape/cleanup-plan/board-audit/board-row list do not accept it. Comment view/read/update/edit/delete treat --number as commentId, not an issue number. View supports lifecycle fields closed/closedAt plus legacy --json field selection; explicit --json fields limit output even with --raw/--full; full issue body is included only when requested with --json body or when --json is omitted and --full/--raw requests all fields. For recent comment progress, prefer `gh issue comments `: it defaults to a bounded recent-comment table and structured output lives at `.data.comments` instead of `.data.json.comments`. `gh issue view --json comments` remains the compatibility path, now with explicit preferredCommand/migrationHint guidance when comments are requested or when large output is compacted. Use gh issue comment view --full for one full comment body. Unsupported fields fail structurally.", + "issue view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. View/read accept positional numbers, GitHub issue URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single issue/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create/scan-escape/cleanup-plan/board-audit/board-row list do not accept it. Comment view/read/update/edit/delete treat --number as commentId, not an issue number. View supports lifecycle fields closed/closedAt plus legacy --json field selection; explicit --json fields limit output even with --raw/--full. When body is requested, the full issue body is emitted once at `.data.issue.body`; `.data.json.body` is omitted and compatibility.bodyPath/bodyMigrationHint disclose the stable replacement path. For recent comment progress, prefer `gh issue comments `: it defaults to a bounded recent-comment table and structured output lives at `.data.comments` instead of `.data.json.comments`. `gh issue view --json comments` remains the compatibility path, now with explicit preferredCommand/migrationHint guidance when comments are requested or when large output is compacted. Use gh issue comment view --full for one full comment body. Unsupported fields fail structurally.", "issue attachment list/download scan issue body and comments for GitHub user attachment URLs (`https://github.com/user-attachments/assets/...`). list is read-only and returns bounded attachment metadata. download writes the selected attachment to --output or /tmp/unidesk-gh-attachments, returns bytes/SHA-256/content-type/path, redacts redirected signed URL query parameters, and never prints binary bytes.", "--raw and --full are explicit full-disclosure aliases for gh issue list/read/view/comments/update/edit/patch/comment view, gh pr list/read/view/comment view, and gh pr diff --file. For issue read/view commands, --full expands issue fields but keeps comment lists bounded; --raw is the explicit all-comment-body escape hatch. For `gh issue comments`, both --full and --raw keep the list bounded to recent comments and include full comment bodies in structured output. Use issue comment view --full for a single full comment body. For issue writes, default success output omits full issue.body and returns bodyChars/bodySha/bodyPreview plus readCommands; --full|--raw includes the full returned issue body only on commands that explicitly support full disclosure.", "CLI output larger than config/unidesk-cli.yaml output.maxStdoutBytes is automatically written to /tmp/unidesk-cli-output; stdout stays bounded with outputTruncated=true, warning text, dump file metadata, and drill-down read commands.", @@ -102,7 +102,7 @@ export function ghHelp(): unknown { "PR review-plan is the review-first bounded patch index. It uses GitHub REST PR files, prints changed files with additions/deletions/hunk counts/patch-line counts/default truncation flags, and emits one per-file gh pr diff --file drill-down command without creating a local worktree.", "PR diff --file reads one changed file patch from GitHub REST and prints only a bounded excerpt by default. Add --hunk N to inspect one hunk, --limit N to change displayed patch lines, or --full/--raw for explicit structured full-patch disclosure. --stat remains the no-patch file/stat compatibility path.", "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 view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. PR view/read accept positional numbers, GitHub PR URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single PR/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create do not accept it. PR comment update/edit/delete treat --number as commentId, not a PR number. PR view/read supports REST closeout fields stateDetail, closed, closedAt, merged, mergedAt, mergeCommit, headRefName, and baseRefName; explicit --json fields limit output even with --raw/--full. Mergeable, mergeStateStatus, and statusCheckRollup are fetched through GitHub GraphQL only when requested or when --json is omitted and --raw/--full requests full disclosure, and closeoutMetadata makes GraphQL errors plus UNKNOWN/null metadata explicit.", + "PR view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. PR view/read accept positional numbers, GitHub PR URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single PR/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create do not accept it. PR comment update/edit/delete treat --number as commentId, not a PR number. PR view/read supports REST closeout fields stateDetail, closed, closedAt, merged, mergedAt, mergeCommit, headRefName, and baseRefName; explicit --json fields limit output even with --raw/--full. When body is requested, the full PR body is emitted once at `.data.pullRequest.body`; `.data.json.body` is omitted and compatibility.bodyPath/bodyMigrationHint disclose the stable replacement path. Mergeable, mergeStateStatus, and statusCheckRollup are fetched through GitHub GraphQL only when requested or when --json is omitted and --raw/--full requests full disclosure, and closeoutMetadata makes GraphQL errors plus UNKNOWN/null metadata explicit.", "PR preflight/closeout accept the same owner/repo#number shorthand as PR view/read so merge readiness checks do not require repeating --repo after a PR URL has already been normalized.", "PR list does not fetch mergeability or statusCheckRollup; request those closeout fields with gh pr view --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup.", "PR preflight is a low-noise read-only closeout helper for explicit diagnosis only. It combines redacted auth capability, PR branch/state metadata, mergeability, mergeStateStatus, compact status check counts, and an explicit read-only policy. It is not a required step before gh pr merge. Use --full or --raw to include all fetched status contexts.", diff --git a/scripts/src/gh/issue-read.ts b/scripts/src/gh/issue-read.ts index 7ff86072..0a411b63 100644 --- a/scripts/src/gh/issue-read.ts +++ b/scripts/src/gh/issue-read.ts @@ -128,7 +128,9 @@ export function selectedIssueJson(issue: GitHubIssue, comments: GitHubComment[] const summary = issueSummary(issue); const selected: Record = {}; for (const field of fields) { - if (field === "comments") { + if (field === "body") { + continue; + } else if (field === "comments") { selected.comments = (comments ?? []).map(options.includeFullCommentBodies === true ? commentSummary : compactIssueViewCommentSummary); } else { selected[field] = summary[field]; @@ -189,7 +191,13 @@ export async function issueRead(repo: string, token: string, issueNumber: number json: selectedIssueJson(issue, comments, jsonFields, { includeFullCommentBodies }), compatibility: { legacyJsonBodyPath: includeBody ? ".data.issue.body" : null, + bodyPath: includeBody ? ".data.issue.body" : null, + omittedDuplicateBodyPath: includeBody ? ".data.json.body" : null, + duplicateBodyOmitted: includeBody, bodyOmitted: !includeBody, + bodyMigrationHint: includeBody + ? "Read the full body from .data.issue.body; .data.json.body is intentionally omitted so large bodies are emitted only once." + : null, ...commentsCompatibility, readCommands: { ...(includeBody ? {} : issueBodyReadCommands(repo, issueNumber)),