fix: reduce noise for gh issue view comments
This commit is contained in:
@@ -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 <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 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, now with explicit preferredCommand/migrationHint guidance when comments are requested or when large output is compacted. 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/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.",
|
||||
|
||||
@@ -10,6 +10,10 @@ import { commentSummary, compactCommentSummary, compactIssueViewCommentSummary }
|
||||
import { GITHUB_REST_PAGE_SIZE } from "./types";
|
||||
import type { GitHubCommandResult, GitHubComment, GitHubErrorPayload, GitHubIssue, GitHubIssueListPage, GitHubIssueListResult, GitHubIssueSearchResponse, IssueListState, IssueViewJsonField } from "./types";
|
||||
|
||||
function preferredIssueCommentsCommand(repo: string, issueNumber: number): string {
|
||||
return `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo}`;
|
||||
}
|
||||
|
||||
export async function listIssueComments(token: string, repo: string, issueNumber: number, options: { page?: number; perPage?: number } = {}): Promise<GitHubComment[] | GitHubErrorPayload> {
|
||||
const { owner, name } = repoParts(repo);
|
||||
const params = new URLSearchParams({
|
||||
@@ -133,19 +137,50 @@ export function selectedIssueJson(issue: GitHubIssue, comments: GitHubComment[]
|
||||
return selected;
|
||||
}
|
||||
|
||||
function issueCommentsCompatibility(repo: string, issueNumber: number, comments: GitHubComment[] | null, includeFullCommentBodies: boolean): Record<string, unknown> {
|
||||
const preferredCommand = preferredIssueCommentsCommand(repo, issueNumber);
|
||||
return {
|
||||
commentsCompacted: comments !== null && !includeFullCommentBodies,
|
||||
commentBodiesOmitted: comments !== null && !includeFullCommentBodies,
|
||||
fullCommentBodiesIncluded: comments !== null && includeFullCommentBodies,
|
||||
commentsPath: comments === null ? null : ".data.json.comments",
|
||||
preferredCommand: comments === null ? null : preferredCommand,
|
||||
migrationHint: comments === null
|
||||
? null
|
||||
: "For bounded recent comment progress, prefer gh issue comments <number>; gh issue view --json comments remains the legacy compatibility path.",
|
||||
readCommands: {
|
||||
...(comments !== null && !includeFullCommentBodies ? issueCommentReadCommands(repo, issueNumber) : {}),
|
||||
...(comments === null ? {} : { preferred: preferredCommand }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function issueRead(repo: string, token: string, issueNumber: number, jsonFields: IssueViewJsonField[] | undefined, commandName = "issue read", disclosure: Record<string, unknown> | null = null, options: { includeFullCommentBodies?: boolean } = {}): Promise<GitHubCommandResult> {
|
||||
const issue = await getIssue(token, repo, issueNumber);
|
||||
if (isGitHubError(issue)) return commandError(commandName, repo, issue, { issueNumber });
|
||||
const needsComments = jsonFields === undefined || jsonFields.includes("comments");
|
||||
const includeBody = jsonFields === undefined || jsonFields.includes("body");
|
||||
const requestedCommentsField = jsonFields?.includes("comments") === true;
|
||||
const includeFullCommentBodies = options.includeFullCommentBodies === true;
|
||||
const comments = needsComments ? await listIssueComments(token, repo, issueNumber) : null;
|
||||
if (isGitHubError(comments)) return commandError(commandName, repo, comments, { issueNumber, issue: issueSummary(issue, { includeBody, includePreview: false }) });
|
||||
const commentsCompatibility = issueCommentsCompatibility(repo, issueNumber, comments, includeFullCommentBodies);
|
||||
return {
|
||||
ok: true,
|
||||
command: commandName,
|
||||
repo,
|
||||
...(disclosure === null ? {} : { disclosure }),
|
||||
...((disclosure === null && !requestedCommentsField) ? {} : {
|
||||
disclosure: {
|
||||
...(disclosure ?? {}),
|
||||
...(requestedCommentsField ? {
|
||||
preferredCommand: commentsCompatibility.preferredCommand,
|
||||
commentsPath: commentsCompatibility.commentsPath,
|
||||
legacyCompatibilityPath: ".data.json.comments",
|
||||
migrationHint: commentsCompatibility.migrationHint,
|
||||
boundedAlternative: "gh issue comments returns a bounded recent-comment summary and is the preferred human/operator path.",
|
||||
} : {}),
|
||||
},
|
||||
}),
|
||||
issue: issueSummary(issue, { includeBody, includePreview: false }),
|
||||
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(issueNumber, issue.body ?? ""),
|
||||
...(comments === null || jsonFields !== undefined ? {} : { comments: comments.map(includeFullCommentBodies ? commentSummary : compactCommentSummary) }),
|
||||
@@ -155,13 +190,10 @@ export async function issueRead(repo: string, token: string, issueNumber: number
|
||||
compatibility: {
|
||||
legacyJsonBodyPath: includeBody ? ".data.issue.body" : null,
|
||||
bodyOmitted: !includeBody,
|
||||
commentsCompacted: comments !== null && !includeFullCommentBodies,
|
||||
commentBodiesOmitted: comments !== null && !includeFullCommentBodies,
|
||||
fullCommentBodiesIncluded: comments !== null && includeFullCommentBodies,
|
||||
commentsPath: comments === null ? null : ".data.json.comments",
|
||||
...commentsCompatibility,
|
||||
readCommands: {
|
||||
...(includeBody ? {} : issueBodyReadCommands(repo, issueNumber)),
|
||||
...(comments !== null && !includeFullCommentBodies ? issueCommentReadCommands(repo, issueNumber) : {}),
|
||||
...recordOrEmpty(commentsCompatibility.readCommands),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -217,7 +249,7 @@ export async function issueComments(repo: string, token: string, issueNumber: nu
|
||||
nestedIssueViewCommentsPath: ".data.json.comments",
|
||||
},
|
||||
readCommands: {
|
||||
self: `bun scripts/cli.ts gh issue comments ${issueNumber} --repo ${repo}`,
|
||||
self: preferredIssueCommentsCommand(repo, issueNumber),
|
||||
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`,
|
||||
@@ -225,3 +257,7 @@ export async function issueComments(repo: string, token: string, issueNumber: nu
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function recordOrEmpty(value: unknown): Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
@@ -292,6 +292,8 @@ function summarizeEnvelope(envelope: JsonEnvelope<unknown>): Record<string, unkn
|
||||
const issueRecord = issue as Record<string, unknown>;
|
||||
summary.issue = pickSummary(issueRecord, ["number", "title", "state", "url", "bodyChars", "commentCount"]);
|
||||
}
|
||||
const issueCommentsMigration = summarizeIssueCommentsMigration(source);
|
||||
if (issueCommentsMigration !== null) summary.issueCommentsMigration = issueCommentsMigration;
|
||||
const pullRequest = source.pullRequest;
|
||||
if (typeof pullRequest === "object" && pullRequest !== null) {
|
||||
const prRecord = pullRequest as Record<string, unknown>;
|
||||
@@ -319,6 +321,24 @@ function summarizeArrayCounts(source: Record<string, unknown>): Record<string, n
|
||||
return result;
|
||||
}
|
||||
|
||||
function summarizeIssueCommentsMigration(source: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const command = typeof source.command === "string" ? source.command : null;
|
||||
if (command !== "issue view" && command !== "issue read") return null;
|
||||
const jsonFields = Array.isArray(source.jsonFields) ? source.jsonFields.filter((value): value is string => typeof value === "string") : [];
|
||||
if (!jsonFields.includes("comments")) return null;
|
||||
const compatibility = recordOrNull(source.compatibility);
|
||||
const readCommands = recordOrNull(compatibility?.readCommands);
|
||||
const disclosure = recordOrNull(source.disclosure);
|
||||
const preferredCommand = stringOrNull(compatibility?.preferredCommand)
|
||||
?? stringOrNull(readCommands?.preferred)
|
||||
?? stringOrNull(disclosure?.preferredCommand);
|
||||
return {
|
||||
legacyCommentsPath: stringOrNull(compatibility?.commentsPath) ?? ".data.json.comments",
|
||||
preferredCommand,
|
||||
migrationHint: stringOrNull(compatibility?.migrationHint) ?? stringOrNull(disclosure?.migrationHint),
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeDebugDispatch(source: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const dispatch = recordOrNull(source.dispatch);
|
||||
const wait = recordOrNull(source.wait);
|
||||
|
||||
Reference in New Issue
Block a user