fix: reduce noise for gh issue view comments
This commit is contained in:
@@ -4,6 +4,7 @@ Issue writes use `bun scripts/cli.ts gh ...` or the `trans gh:` virtual filesyst
|
|||||||
|
|
||||||
- Body and comments default to Chinese.
|
- Body and comments default to Chinese.
|
||||||
- Recent issue comment progress should prefer `bun scripts/cli.ts gh issue comments <number> --repo owner/name [--limit N] [--full|--raw]`; structured output is stable at `.data.comments`.
|
- Recent issue comment progress should prefer `bun scripts/cli.ts gh issue comments <number> --repo owner/name [--limit N] [--full|--raw]`; structured output is stable at `.data.comments`.
|
||||||
|
- `gh issue view <number> --json comments` remains the legacy compatibility path at `.data.json.comments`; when comments are requested it should disclose the preferred `gh issue comments <number>` migration path instead of silently pushing operators toward `/tmp` dump recovery.
|
||||||
- New issues include `目标合并分支`.
|
- New issues include `目标合并分支`.
|
||||||
- Multi-stage architecture/API/platform issues begin with `P0 SPEC 先行`.
|
- Multi-stage architecture/API/platform issues begin with `P0 SPEC 先行`.
|
||||||
- Long body text uses `--body-stdin`.
|
- Long body text uses `--body-stdin`.
|
||||||
|
|||||||
@@ -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 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.",
|
"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.",
|
"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.",
|
"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.",
|
"--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.",
|
"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 { GITHUB_REST_PAGE_SIZE } from "./types";
|
||||||
import type { GitHubCommandResult, GitHubComment, GitHubErrorPayload, GitHubIssue, GitHubIssueListPage, GitHubIssueListResult, GitHubIssueSearchResponse, IssueListState, IssueViewJsonField } 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> {
|
export async function listIssueComments(token: string, repo: string, issueNumber: number, options: { page?: number; perPage?: number } = {}): Promise<GitHubComment[] | GitHubErrorPayload> {
|
||||||
const { owner, name } = repoParts(repo);
|
const { owner, name } = repoParts(repo);
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
@@ -133,19 +137,50 @@ export function selectedIssueJson(issue: GitHubIssue, comments: GitHubComment[]
|
|||||||
return selected;
|
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> {
|
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);
|
const issue = await getIssue(token, repo, issueNumber);
|
||||||
if (isGitHubError(issue)) return commandError(commandName, repo, issue, { issueNumber });
|
if (isGitHubError(issue)) return commandError(commandName, repo, issue, { issueNumber });
|
||||||
const needsComments = jsonFields === undefined || jsonFields.includes("comments");
|
const needsComments = jsonFields === undefined || jsonFields.includes("comments");
|
||||||
const includeBody = jsonFields === undefined || jsonFields.includes("body");
|
const includeBody = jsonFields === undefined || jsonFields.includes("body");
|
||||||
|
const requestedCommentsField = jsonFields?.includes("comments") === true;
|
||||||
const includeFullCommentBodies = options.includeFullCommentBodies === true;
|
const includeFullCommentBodies = options.includeFullCommentBodies === true;
|
||||||
const comments = needsComments ? await listIssueComments(token, repo, issueNumber) : null;
|
const comments = needsComments ? await listIssueComments(token, repo, issueNumber) : null;
|
||||||
if (isGitHubError(comments)) return commandError(commandName, repo, comments, { issueNumber, issue: issueSummary(issue, { includeBody, includePreview: false }) });
|
if (isGitHubError(comments)) return commandError(commandName, repo, comments, { issueNumber, issue: issueSummary(issue, { includeBody, includePreview: false }) });
|
||||||
|
const commentsCompatibility = issueCommentsCompatibility(repo, issueNumber, comments, includeFullCommentBodies);
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
command: commandName,
|
command: commandName,
|
||||||
repo,
|
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 }),
|
issue: issueSummary(issue, { includeBody, includePreview: false }),
|
||||||
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(issueNumber, issue.body ?? ""),
|
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(issueNumber, issue.body ?? ""),
|
||||||
...(comments === null || jsonFields !== undefined ? {} : { comments: comments.map(includeFullCommentBodies ? commentSummary : compactCommentSummary) }),
|
...(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: {
|
compatibility: {
|
||||||
legacyJsonBodyPath: includeBody ? ".data.issue.body" : null,
|
legacyJsonBodyPath: includeBody ? ".data.issue.body" : null,
|
||||||
bodyOmitted: !includeBody,
|
bodyOmitted: !includeBody,
|
||||||
commentsCompacted: comments !== null && !includeFullCommentBodies,
|
...commentsCompatibility,
|
||||||
commentBodiesOmitted: comments !== null && !includeFullCommentBodies,
|
|
||||||
fullCommentBodiesIncluded: comments !== null && includeFullCommentBodies,
|
|
||||||
commentsPath: comments === null ? null : ".data.json.comments",
|
|
||||||
readCommands: {
|
readCommands: {
|
||||||
...(includeBody ? {} : issueBodyReadCommands(repo, issueNumber)),
|
...(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",
|
nestedIssueViewCommentsPath: ".data.json.comments",
|
||||||
},
|
},
|
||||||
readCommands: {
|
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`,
|
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`,
|
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`,
|
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>;
|
const issueRecord = issue as Record<string, unknown>;
|
||||||
summary.issue = pickSummary(issueRecord, ["number", "title", "state", "url", "bodyChars", "commentCount"]);
|
summary.issue = pickSummary(issueRecord, ["number", "title", "state", "url", "bodyChars", "commentCount"]);
|
||||||
}
|
}
|
||||||
|
const issueCommentsMigration = summarizeIssueCommentsMigration(source);
|
||||||
|
if (issueCommentsMigration !== null) summary.issueCommentsMigration = issueCommentsMigration;
|
||||||
const pullRequest = source.pullRequest;
|
const pullRequest = source.pullRequest;
|
||||||
if (typeof pullRequest === "object" && pullRequest !== null) {
|
if (typeof pullRequest === "object" && pullRequest !== null) {
|
||||||
const prRecord = pullRequest as Record<string, unknown>;
|
const prRecord = pullRequest as Record<string, unknown>;
|
||||||
@@ -319,6 +321,24 @@ function summarizeArrayCounts(source: Record<string, unknown>): Record<string, n
|
|||||||
return result;
|
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 {
|
function summarizeDebugDispatch(source: Record<string, unknown>): Record<string, unknown> | null {
|
||||||
const dispatch = recordOrNull(source.dispatch);
|
const dispatch = recordOrNull(source.dispatch);
|
||||||
const wait = recordOrNull(source.wait);
|
const wait = recordOrNull(source.wait);
|
||||||
|
|||||||
Reference in New Issue
Block a user