From 1ed621a0674f73d8b2fefcbc2f7061c5f4f0e5a6 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 4 Jul 2026 12:10:30 +0000 Subject: [PATCH] fix: add gh issue comments command --- .agents/skills/unidesk-gh/SKILL.md | 1 + .../skills/unidesk-gh/references/issues.md | 1 + scripts/src/gh/client.ts | 7 +- scripts/src/gh/default-render.ts | 35 +++++++++ scripts/src/gh/help.ts | 10 ++- scripts/src/gh/index.ts | 15 +++- scripts/src/gh/issue-read.ts | 78 ++++++++++++++++++- scripts/src/gh/options.ts | 17 +++- scripts/src/gh/types.ts | 2 + 9 files changed, 152 insertions(+), 14 deletions(-) diff --git a/.agents/skills/unidesk-gh/SKILL.md b/.agents/skills/unidesk-gh/SKILL.md index 98490905..355cedab 100644 --- a/.agents/skills/unidesk-gh/SKILL.md +++ b/.agents/skills/unidesk-gh/SKILL.md @@ -26,6 +26,7 @@ bun scripts/cli.ts gh auth status --repo pikasTech/unidesk bun scripts/cli.ts gh issue list --repo pikasTech/unidesk --state open --limit 30 bun scripts/cli.ts gh issue view --repo pikasTech/unidesk bun scripts/cli.ts gh issue view --repo pikasTech/unidesk --json body,title,state +bun scripts/cli.ts gh issue comments --repo pikasTech/unidesk --limit 8 bun scripts/cli.ts gh issue create --repo pikasTech/unidesk --title "标题" --body-stdin bun scripts/cli.ts gh pr list --repo pikasTech/unidesk --state all --limit 10 bun scripts/cli.ts gh pr review-plan --repo pikasTech/unidesk diff --git a/.agents/skills/unidesk-gh/references/issues.md b/.agents/skills/unidesk-gh/references/issues.md index b7171fbf..43c62144 100644 --- a/.agents/skills/unidesk-gh/references/issues.md +++ b/.agents/skills/unidesk-gh/references/issues.md @@ -3,6 +3,7 @@ Issue writes use `bun scripts/cli.ts gh ...` or the `trans gh:` virtual filesystem. - 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`. - New issues include `目标合并分支`. - Multi-stage architecture/API/platform issues begin with `P0 SPEC 先行`. - Long body text uses `--body-stdin`. diff --git a/scripts/src/gh/client.ts b/scripts/src/gh/client.ts index ccf7dd65..d06bd2b7 100644 --- a/scripts/src/gh/client.ts +++ b/scripts/src/gh/client.ts @@ -362,10 +362,11 @@ export function issueBodyReadCommands(repo: string, issueNumber: number): Record export function issueCommentReadCommands(repo: string, issueNumber: number): Record { return { - comments: `bun scripts/cli.ts gh issue view ${issueNumber} --repo ${repo} --json comments`, + comments: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo}`, comment: `bun scripts/cli.ts gh issue comment view --repo ${repo} --full`, - full: `bun scripts/cli.ts gh issue view ${issueNumber} --repo ${repo} --full`, - raw: `bun scripts/cli.ts gh issue view ${issueNumber} --repo ${repo} --raw`, + full: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo} --full`, + raw: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo} --raw`, + issue: `bun scripts/cli.ts gh issue view ${issueNumber} --repo ${repo}`, }; } diff --git a/scripts/src/gh/default-render.ts b/scripts/src/gh/default-render.ts index c73acbe3..b1c1726e 100644 --- a/scripts/src/gh/default-render.ts +++ b/scripts/src/gh/default-render.ts @@ -47,6 +47,7 @@ function renderGhDefaultText(result: GitHubCommandResult): string { if (result.ok === false) return renderGhError(result); const command = result.command; if (isIssueReadResult(result)) return renderIssueView(result); + if (command === "issue comments") return renderIssueComments(result); if (isPrReadResult(result)) return renderPrView(result); if (command === "pr list") return renderPrList(result); if (command === "pr files" || command === "pr diff --stat") return renderPrFiles(result); @@ -138,6 +139,7 @@ function renderIssueView(result: GitHubCommandResult): string { ` author=${ghText(issue.author)} bodySha=${ghText(issue.bodySha)} bodyPreview=${body.preview}`, "", "Next:", + ` ${ghText(readCommands.comments ?? `bun scripts/cli.ts gh issue comments ${number} --repo ${result.repo}`)}`, ` ${ghText(readCommands.body ?? `bun scripts/cli.ts gh issue view ${number} --repo ${result.repo} --json body`)}`, ` ${ghText(readCommands.full ?? `bun scripts/cli.ts gh issue view ${number} --repo ${result.repo} --full`)}`, "", @@ -147,6 +149,39 @@ function renderIssueView(result: GitHubCommandResult): string { return lines.join("\n"); } +function renderIssueComments(result: GitHubCommandResult): string { + const issue = record(result.issue); + const comments = arrayOfRecords(result.comments); + const commentRange = record(result.commentRange); + const readCommands = record(result.readCommands); + const rows = comments.slice(0, 40).map((comment) => [ + ghText(comment.id), + ghText(comment.author), + shortDate(comment.createdAt), + ghShort(ghText(comment.url), 40), + ghShort(previewFrom(comment), 72), + ]); + const lines = [ + "gh issue comments (observed)", + "", + rows.length > 0 + ? ghTable(["COMMENT", "AUTHOR", "CREATED", "URL", "PREVIEW"], rows) + : "No comments found.", + "", + "Summary:", + ` repo=${ghText(result.repo)} issue=#${ghText(issue.number ?? result.issueNumber)} total=${ghText(result.totalComments)} returned=${ghText(result.returned)} omitted=${ghText(result.omitted)}`, + ` range=${ghText(commentRange.from)}..${ghText(commentRange.to)} title=${ghShort(ghText(issue.title), 96)}`, + "", + "Next:", + ` ${ghText(readCommands.full ?? `bun scripts/cli.ts gh issue comments ${ghText(issue.number ?? result.issueNumber)} --repo ${result.repo} --full`)}`, + ` ${ghText(readCommands.comment ?? `bun scripts/cli.ts gh issue comment view --repo ${result.repo} --full`)}`, + "", + "Disclosure:", + " default comments output is a bounded recent-comment table with body preview; use --full/--raw for structured full bodies.", + ]; + return lines.join("\n"); +} + function renderPrView(result: GitHubCommandResult): string { const pr = record(result.pullRequest); const number = ghText(pr.number); diff --git a/scripts/src/gh/help.ts b/scripts/src/gh/help.ts index 3f27cd44..2010188b 100644 --- a/scripts/src/gh/help.ts +++ b/scripts/src/gh/help.ts @@ -15,6 +15,7 @@ export function ghHelp(): unknown { "bun scripts/cli.ts gh issue list [owner/repo] [--state open|closed|all] [--limit N] [--search text] [--title-prefix text] [--label label[,label...]]... [--repo owner/name] [--json number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels] [--raw|--full]", "bun scripts/cli.ts gh issue view [--repo owner/name] [--number N compat] [--json body,title,state,closed,closedAt,comments,commentCount] [--raw|--full]", "bun scripts/cli.ts gh issue read [--repo owner/name] [--number N compat] [--raw|--full] [compatibility alias for issue view]", + "bun scripts/cli.ts gh issue comments [--repo owner/name] [--number N compat] [--limit N] [--full|--raw] [bounded recent comment progress path]", "bun scripts/cli.ts gh issue attachment list [--repo owner/name] [--number N compat]", "bun scripts/cli.ts gh issue attachment download [--repo owner/name] [--number N compat] [--attachment index|assetId|url] [--output path] [--dry-run]", "bun scripts/cli.ts gh issue create --title (--body-stdin|--body-file <file|->) [--label label[,label...]]... [--repo owner/name] [--dry-run]", @@ -72,9 +73,9 @@ 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. Comment lists always stay bounded except --raw; use gh issue comment view <commentId> --full for a single 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; 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 <number>`: 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. Use gh issue comment view <commentId> --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/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. Use issue comment view <commentId> --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.", + "--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 <commentId> --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.", "issue create accepts --body-stdin or --body-file <file|-> plus repeatable --label values and comma-separated labels; inline --body is intentionally unsupported for issue creation. Dry-run prints the parsed labels and non-dry-run sends them in the GitHub REST create-issue payload.", "--body-stdin is the first-class heredoc/stdin source for Markdown bodies. Use quoted heredoc syntax such as bun scripts/cli.ts gh issue comment create 1 --body-stdin <<'EOF' so real newlines, backticks, and tables are read as stdin bytes instead of shell arguments.", @@ -157,8 +158,11 @@ export function ghScopedHelpNotes(tokens: string[]): string[] { ]; if (key === "issue comment" || key.startsWith("issue comment ")) { notes.push("Issue comments use `--body-stdin` or `--body-file <file|->` for Markdown bodies; inline `--body` is only for short single-line comments."); - notes.push("Use `issue comment view <commentId> --full` to read one full comment body; issue-level `--json comments --full` keeps the comment list bounded."); + notes.push("Use `issue comment view <commentId> --full` to read one full comment body; use `issue comments <number>` for the bounded recent-comment issue view."); notes.push("Use `issue comment update/edit` for wording fixes, `issue comment patch` for apply_patch-style local edits, and `issue comment delete` only for intentional removal."); + } else if (key === "issue comments") { + notes.push("Use `issue comments <number>` as the low-friction recent-progress path; default output is bounded and structured output is at `.data.comments`."); + notes.push("`--full` or `--raw` includes full bodies for the bounded recent list without changing the stable top-level `comments` path."); } else if (key === "issue close" || key === "issue reopen") { notes.push("Issue close/reopen can post a lifecycle comment with `--comment`, `--comment-stdin`, or `--comment-file <file|->` before changing state."); notes.push("For long closeout evidence, prefer `--comment-stdin` with a quoted heredoc."); diff --git a/scripts/src/gh/index.ts b/scripts/src/gh/index.ts index 9f7d72fc..aaf90076 100644 --- a/scripts/src/gh/index.ts +++ b/scripts/src/gh/index.ts @@ -11,7 +11,7 @@ import { commentDelete, commentPatch, commentUpdate, commentView, issueComment, import { issueScanEscape } from "./escape-scan"; import { ghHelp, ghScopedHelp } from "./help"; import { issueList } from "./issue-list"; -import { issueRead, issueView } from "./issue-read"; +import { issueComments, issueRead, issueView } from "./issue-read"; import { repoCreate, repoView } from "./issue-summary"; import { issueCreate, issueEdit, issuePatch } from "./issue-write"; import { allowsNumberTargetAlias, isIssueReadCommand, isPrReadCommand, optionValue, optionWasProvided, parseOptions } from "./options"; @@ -57,12 +57,13 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult const isIssueCommentReadCommand = top === "issue" && sub === "comment" && (third === "view" || third === "read"); const isPrCommentReadCommand = top === "pr" && sub === "comment" && (third === "view" || third === "read"); const isPrDiffFileCommand = top === "pr" && sub === "diff" && optionWasProvided(args, "--file"); - if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && (isIssueReadCommand(sub) || sub === "list" || sub === "update" || sub === "edit" || sub === "patch")) || isIssueCommentReadCommand || top === "preflight" || (top === "pr" && (isPrReadCommand(sub) || sub === "list" || sub === "preflight" || sub === "closeout")) || isPrCommentReadCommand || isPrDiffFileCommand)) { + if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && (isIssueReadCommand(sub) || sub === "comments" || sub === "list" || sub === "update" || sub === "edit" || sub === "patch")) || isIssueCommentReadCommand || top === "preflight" || (top === "pr" && (isPrReadCommand(sub) || sub === "list" || sub === "preflight" || sub === "closeout")) || isPrCommentReadCommand || isPrDiffFileCommand)) { const command = [top, sub].filter((value): value is string => value !== undefined).join(" ") || "gh"; - return validationError(command, options.repo, "--raw and --full are explicit full-disclosure aliases only for gh issue list/read/view/update/edit/patch/comment view, gh pr list/read/view/comment view, gh pr preflight/closeout, and gh pr diff --file.", { + return validationError(command, options.repo, "--raw and --full are explicit full-disclosure aliases only for gh issue list/read/view/comments/update/edit/patch/comment view, gh pr list/read/view/comment view, gh pr preflight/closeout, and gh pr diff --file.", { supportedCommands: [ "bun scripts/cli.ts gh issue list --repo owner/name --limit 200 --full", "bun scripts/cli.ts gh issue view owner/name#<number> --raw", + "bun scripts/cli.ts gh issue comments <number> --repo owner/name --limit 8 --full", "bun scripts/cli.ts gh issue view <number> --repo owner/name --json body,title,state,comments", "bun scripts/cli.ts gh issue comment view <commentId> --repo owner/name --full", "bun scripts/cli.ts gh issue update <number> --repo owner/name --body-stdin --full <<'EOF'\n<reviewed body>\nEOF", @@ -306,6 +307,14 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult if (missing !== null || token === null) return missing ?? authRequired(options.repo, "issue comment create", { present: false, source: null, ghFallbackAttempted: true }); return withNumberOptionHint(issueComment(resolved.repo, token, issueNumber, { ...options, repo: resolved.repo }), resolved); } + if (sub === "comments") { + const resolved = resolvePositionalIssueReference(args, 2, "issue comments", options); + if (isGitHubCommandResult(resolved)) return resolved; + const { token, probe } = resolveToken(true); + const missing = authRequired(resolved.repo, "issue comments", probe); + if (missing !== null || token === null) return missing ?? authRequired(resolved.repo, "issue comments", { present: false, source: null, ghFallbackAttempted: true }); + return withNumberOptionHint(issueComments(resolved.repo, token, resolved.number, options.limit, { includeFullCommentBodies: options.full || options.raw }), resolved); + } if (sub === "scan-escape" || sub === "cleanup-plan") { const { token, probe } = resolveToken(true); const commandName = sub === "cleanup-plan" ? "issue cleanup-plan" : "issue scan-escape"; diff --git a/scripts/src/gh/issue-read.ts b/scripts/src/gh/issue-read.ts index 3e03f6e0..85c3f567 100644 --- a/scripts/src/gh/issue-read.ts +++ b/scripts/src/gh/issue-read.ts @@ -10,9 +10,13 @@ import { commentSummary, compactCommentSummary, compactIssueViewCommentSummary } import { GITHUB_REST_PAGE_SIZE } from "./types"; import type { GitHubCommandResult, GitHubComment, GitHubErrorPayload, GitHubIssue, GitHubIssueListPage, GitHubIssueListResult, GitHubIssueSearchResponse, IssueListState, IssueViewJsonField } from "./types"; -export async function listIssueComments(token: string, repo: string, issueNumber: number): Promise<GitHubComment[] | GitHubErrorPayload> { +export async function listIssueComments(token: string, repo: string, issueNumber: number, options: { page?: number; perPage?: number } = {}): Promise<GitHubComment[] | GitHubErrorPayload> { const { owner, name } = repoParts(repo); - return githubRequest<GitHubComment[]>(token, "GET", `/repos/${owner}/${name}/issues/${issueNumber}/comments?per_page=100`); + const params = new URLSearchParams({ + per_page: String(options.perPage ?? GITHUB_REST_PAGE_SIZE), + page: String(options.page ?? 1), + }); + return githubRequest<GitHubComment[]>(token, "GET", `/repos/${owner}/${name}/issues/${issueNumber}/comments?${params.toString()}`); } export function githubSearchLabelQualifier(label: string): string { @@ -99,6 +103,22 @@ export async function getIssueComment(token: string, repo: string, commentId: nu return githubRequest<GitHubComment>(token, "GET", `/repos/${owner}/${name}/issues/comments/${commentId}`); } +async function listRecentIssueComments(token: string, repo: string, issueNumber: number, totalComments: number, limit: number): Promise<GitHubComment[] | GitHubErrorPayload> { + if (totalComments <= 0 || limit <= 0) return []; + const boundedLimit = Math.min(limit, GITHUB_REST_PAGE_SIZE); + const pages: GitHubComment[][] = []; + let collected = 0; + let page = Math.max(1, Math.ceil(totalComments / GITHUB_REST_PAGE_SIZE)); + while (page >= 1 && collected < boundedLimit) { + const pageComments = await listIssueComments(token, repo, issueNumber, { page, perPage: GITHUB_REST_PAGE_SIZE }); + if (isGitHubError(pageComments)) return pageComments; + pages.unshift(pageComments); + collected += pageComments.length; + page -= 1; + } + return pages.flat().slice(-boundedLimit); +} + export function selectedIssueJson(issue: GitHubIssue, comments: GitHubComment[] | null, fields: IssueViewJsonField[] | undefined, options: { includeFullCommentBodies?: boolean } = {}): Record<string, unknown> | null { if (fields === undefined) return null; const summary = issueSummary(issue); @@ -151,3 +171,57 @@ export async function issueRead(repo: string, token: string, issueNumber: number export async function issueView(repo: string, token: string, issueNumber: number, jsonFields: IssueViewJsonField[] | undefined, disclosure: Record<string, unknown> | null = null, options: { includeFullCommentBodies?: boolean } = {}): Promise<GitHubCommandResult> { return issueRead(repo, token, issueNumber, jsonFields, "issue view", disclosure, options); } + +export async function issueComments(repo: string, token: string, issueNumber: number, limit: number, options: { includeFullCommentBodies?: boolean } = {}): Promise<GitHubCommandResult> { + const issue = await getIssue(token, repo, issueNumber); + if (isGitHubError(issue)) return commandError("issue comments", repo, issue, { issueNumber }); + const totalComments = Math.max(0, issue.comments ?? 0); + const boundedLimit = Math.min(limit, GITHUB_REST_PAGE_SIZE); + const includeFullCommentBodies = options.includeFullCommentBodies === true; + const comments = await listRecentIssueComments(token, repo, issueNumber, totalComments, boundedLimit); + if (isGitHubError(comments)) return commandError("issue comments", repo, comments, { + issueNumber, + issue: issueSummary(issue, { includeBody: false, includePreview: false }), + }); + const shownFrom = comments.length === 0 ? 0 : Math.max(1, totalComments - comments.length + 1); + return { + ok: true, + command: "issue comments", + repo, + issue: issueSummary(issue, { includeBody: false, includePreview: false }), + issueNumber, + limit: boundedLimit, + totalComments, + returned: comments.length, + omitted: Math.max(0, totalComments - comments.length), + commentRange: { + from: shownFrom, + to: comments.length === 0 ? 0 : shownFrom + comments.length - 1, + total: totalComments, + }, + comments: comments.map(includeFullCommentBodies ? commentSummary : compactCommentSummary), + disclosure: { + boundedRecentComments: true, + fullCommentBodiesIncluded: includeFullCommentBodies, + bodyOmitted: !includeFullCommentBodies, + note: includeFullCommentBodies + ? "issue comments returns a bounded recent-comment list with full bodies because --full or --raw was explicitly requested." + : "issue comments defaults to bounded recent-comment previews; use --full or --raw for full bodies, or issue comment view <commentId> --full for a single comment.", + }, + compatibility: { + commentsPath: ".data.comments", + issuePath: ".data.issue", + authorField: "string|null", + previewField: includeFullCommentBodies ? null : "bodyPreview", + fullBodyField: includeFullCommentBodies ? "body" : null, + nestedIssueViewCommentsPath: ".data.json.comments", + }, + readCommands: { + self: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo}`, + full: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo} --limit ${boundedLimit} --full`, + raw: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo} --limit ${boundedLimit} --raw`, + comment: `bun scripts/cli.ts gh issue comment view <commentId> --repo ${repo} --full`, + issue: `bun scripts/cli.ts gh issue view ${issueNumber} --repo ${repo}`, + }, + }; +} diff --git a/scripts/src/gh/options.ts b/scripts/src/gh/options.ts index 8367777e..4cda8c06 100644 --- a/scripts/src/gh/options.ts +++ b/scripts/src/gh/options.ts @@ -1,7 +1,7 @@ // SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split // Moved mechanically from scripts/src/gh.ts:614-978. -import { BOARD_GITHUB_STATUSES, BOARD_MUTATION_SECTIONS, BOARD_ROW_FIELDS, BODY_UPDATE_MODES, CODE_QUEUE_BOARD_TARGET_ISSUE, DEFAULT_REPO, DEFAULT_STALE_CLOSE_INACTIVE_HOURS, GH_FLAG_OPTIONS, GH_VALUE_OPTIONS, ISSUE_LIST_JSON_FIELDS, ISSUE_LIST_STATES, ISSUE_VIEW_JSON_FIELDS, MAX_ISSUE_LIST_LIMIT, MAX_PR_FILES_LIMIT, MAX_STALE_CLOSE_INACTIVE_HOURS, PR_CLOSEOUT_JSON_FIELDS, PR_CLOSEOUT_VIEW_JSON, PR_LIST_JSON_FIELDS, PR_READ_JSON_FIELDS } from "./types"; +import { BOARD_GITHUB_STATUSES, BOARD_MUTATION_SECTIONS, BOARD_ROW_FIELDS, BODY_UPDATE_MODES, CODE_QUEUE_BOARD_TARGET_ISSUE, DEFAULT_ISSUE_COMMENTS_LIMIT, DEFAULT_REPO, DEFAULT_STALE_CLOSE_INACTIVE_HOURS, GH_FLAG_OPTIONS, GH_VALUE_OPTIONS, ISSUE_LIST_JSON_FIELDS, ISSUE_LIST_STATES, ISSUE_VIEW_JSON_FIELDS, MAX_ISSUE_LIST_LIMIT, MAX_PR_FILES_LIMIT, MAX_STALE_CLOSE_INACTIVE_HOURS, PR_CLOSEOUT_JSON_FIELDS, PR_CLOSEOUT_VIEW_JSON, PR_LIST_JSON_FIELDS, PR_READ_JSON_FIELDS } from "./types"; import type { BoardGithubStatus, BoardMutationSection, BoardRowField, BoardRowUpsertValues, BodyUpdateMode, GitHubOptions, IssueBodyProfileOption, IssueListJsonField, IssueListState, IssueViewJsonField, PrListJsonField, PrListState, PrReadJsonField, PullRequestMergeMethod, RepoVisibility } from "./types"; export function optionValue(args: string[], name: string): string | undefined { @@ -158,7 +158,7 @@ export function isPrReadCommand(sub: string | undefined): boolean { export function allowsNumberTargetAlias(top: string | undefined, sub: string | undefined, third: string | undefined): boolean { if (top === "preflight") return true; if (top === "issue") { - if (sub === "read" || sub === "view" || sub === "edit" || sub === "update" || sub === "patch" || sub === "close" || sub === "reopen" || sub === "delete") return true; + if (sub === "read" || sub === "view" || sub === "comments" || sub === "edit" || sub === "update" || sub === "patch" || sub === "close" || sub === "reopen" || sub === "delete") return true; if (sub === "comment") return true; if (sub === "attachment" && (third === "list" || third === "download")) return true; if (sub === "board-row" && ["get", "update", "add", "move", "delete", "upsert"].includes(third ?? "")) return true; @@ -335,7 +335,18 @@ export function parseOptions(args: string[]): GitHubOptions { dryRun: hasFlag(args, "--dry-run"), raw: hasFlag(args, "--raw"), full: hasFlag(args, "--full"), - limit: positiveIntegerOption(args, "--limit", top === "issue" && sub === "board-audit" ? 100 : top === "issue" && sub === "stale-close" ? MAX_ISSUE_LIST_LIMIT : 30, limitMax), + limit: positiveIntegerOption( + args, + "--limit", + top === "issue" && sub === "board-audit" + ? 100 + : top === "issue" && sub === "stale-close" + ? MAX_ISSUE_LIST_LIMIT + : top === "issue" && sub === "comments" + ? DEFAULT_ISSUE_COMMENTS_LIMIT + : 30, + limitMax, + ), inactiveHours: positiveNumberOption(args, "--inactive-hours", DEFAULT_STALE_CLOSE_INACTIVE_HOURS, MAX_STALE_CLOSE_INACTIVE_HOURS), boardIssue: positiveIntegerSingleOption(args, "--board-issue", CODE_QUEUE_BOARD_TARGET_ISSUE), knownMetaIssues: positiveIntegerValuesOption(args, "--known-meta-issue"), diff --git a/scripts/src/gh/types.ts b/scripts/src/gh/types.ts index 8f628068..d262c506 100644 --- a/scripts/src/gh/types.ts +++ b/scripts/src/gh/types.ts @@ -25,6 +25,8 @@ export const GITHUB_REST_PAGE_SIZE = 100; export const MAX_ISSUE_LIST_LIMIT = 1000; +export const DEFAULT_ISSUE_COMMENTS_LIMIT = 8; + export const DEFAULT_STALE_CLOSE_INACTIVE_HOURS = 48; export const MAX_STALE_CLOSE_INACTIVE_HOURS = 24 * 365 * 10;