// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-default-render import { isRecord } from "./notify-claudeqq"; import { ghShort, ghTable, ghText } from "./render"; import type { GitHubCommandResult } from "./types"; const STRUCTURED_FLAGS = new Set(["--json", "--full", "--raw"]); export function withGhDefaultRendered(args: string[], value: unknown): unknown { if (wantsStructuredGhOutput(args)) return value; if (isGhHelpPayload(value)) { return { ...value, ok: true, command: typeof value.command === "string" ? value.command : "gh", contentType: "text/plain", renderedText: renderGhHelp(value), }; } if (!isGitHubCommandResult(value)) return value; if (typeof value.renderedText === "string") return value; return { ...value, contentType: "text/plain", renderedText: renderGhDefaultText(value), }; } function wantsStructuredGhOutput(args: string[]): boolean { return args.some((arg) => STRUCTURED_FLAGS.has(arg)); } function isGitHubCommandResult(value: unknown): value is GitHubCommandResult { return isRecord(value) && typeof value.ok === "boolean" && typeof value.command === "string" && typeof value.repo === "string"; } function isGhHelpPayload(value: unknown): value is Record { return isRecord(value) && typeof value.command === "string" && (Array.isArray(value.usage) || Array.isArray(value.notes) || typeof value.fullHelpCommand === "string"); } 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); if (command === "pr review-plan") return renderPrReviewPlan(result); if (command === "pr diff" && isRecord(result.file)) return renderPrDiffFile(result); if (command.includes("comment")) return renderCommentResult(result); if (command === "auth status") return renderAuthStatus(result); if (command.startsWith("repo ")) return renderRepoResult(result); if (command.startsWith("issue board-row")) return renderBoardRowResult(result); if (command === "issue board-audit") return renderBoardAudit(result); if (command === "issue attachment list" || command === "issue attachment download") return renderAttachmentResult(result); if (command === "issue stale-close") return renderStaleClose(result); if (command === "issue scan-escape" || command === "issue cleanup-plan") return renderScanEscape(result); if (command === "issue close" || command === "issue reopen") return renderIssueLifecycle(result); if (command === "issue update" || command === "issue edit" || command === "issue patch") return renderIssueWrite(result); if (command === "pr update" || command === "pr edit" || command === "pr close" || command === "pr reopen") return renderPrWrite(result); return renderGenericResult(result); } function renderGhHelp(help: Record): string { const usage = Array.isArray(help.usage) ? help.usage.filter((item): item is string => typeof item === "string") : []; const notes = Array.isArray(help.notes) ? help.notes.filter((item): item is string => typeof item === "string") : []; const usageRows = usage.slice(0, 24).map((line, index) => [String(index + 1), ghShort(line, 132)]); const lines = [ `${ghText(help.command)} help`, "", usageRows.length > 0 ? ghTable(["#", "USAGE"], usageRows) : ghText(help.message), "", "Summary:", ` usage=${usage.length} shown=${usageRows.length} notes=${notes.length} output=text`, ]; if (notes.length > 0) lines.push("", "Notes:", ...notes.slice(0, 8).map((note) => ` ${ghShort(note, 160)}`)); const fullHelpCommand = typeof help.fullHelpCommand === "string" ? help.fullHelpCommand : "bun scripts/cli.ts gh --help"; lines.push("", "Next:", ` ${fullHelpCommand}`, " bun scripts/cli.ts gh --help"); lines.push("", "Disclosure:", " help output is bounded; use scoped help for a specific command or --raw where supported for structured metadata."); return lines.join("\n"); } function renderGhError(result: GitHubCommandResult): string { const details = record(result.details); const message = ghText(result.message ?? details.message); const reason = ghText(result.degradedReason ?? details.degradedReason ?? "failed"); const lines = [ `gh ${result.command} (failed)`, "", ghTable(["COMMAND", "STATUS", "REASON", "MESSAGE"], [[ result.command, "failed", ghShort(reason, 36), ghShort(message, 120), ]]), "", "Summary:", ` repo=${ghText(result.repo)} runnerDisposition=${ghText(result.runnerDisposition ?? details.runnerDisposition)}`, ]; const supportedCommands = Array.isArray(result.supportedCommands) ? result.supportedCommands.filter((item): item is string => typeof item === "string") : []; if (supportedCommands.length > 0) lines.push("", "Next:", ...supportedCommands.slice(0, 4).map((command) => ` ${command}`)); lines.push("", "Disclosure:", " default error output is compact; rerun with explicit --json/--full/--raw where supported for structured diagnostics."); return lines.join("\n"); } function isIssueReadResult(result: GitHubCommandResult): boolean { return (result.command === "issue view" || result.command === "issue read") && isRecord(result.issue); } function isPrReadResult(result: GitHubCommandResult): boolean { return (result.command === "pr view" || result.command === "pr read") && isRecord(result.pullRequest); } function renderIssueView(result: GitHubCommandResult): string { const issue = record(result.issue); const number = ghText(issue.number); const body = bodyInfo(issue); const readCommands = record(record(result.compatibility).readCommands); const lines = [ `gh ${result.command} (observed)`, "", ghTable(["ISSUE", "STATE", "UPDATED", "COMMENTS", "BODY", "TITLE"], [[ number === "-" ? "-" : `#${number}`, ghText(issue.state), shortDate(issue.updatedAt), ghText(issue.commentCount), body.label, ghShort(ghText(issue.title), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} url=${ghText(issue.url)}`, ` 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`)}`, "", "Disclosure:", " default view omits full issue.body and comment bodies; use --json body/comments, --full, or --raw for explicit structured disclosure.", ]; 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); const body = bodyInfo(pr); const head = record(pr.head); const base = record(pr.base); return [ `gh ${result.command} (observed)`, "", ghTable(["PR", "STATE", "BASE", "HEAD", "DRAFT", "BODY", "TITLE"], [[ number === "-" ? "-" : `#${number}`, ghText(pr.stateDetail ?? pr.state), ghText(base.ref ?? pr.baseRefName), ghText(head.ref ?? pr.headRefName), ghText(pr.draft), body.label, ghShort(ghText(pr.title), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} url=${ghText(pr.url)}`, ` author=${ghText(pr.author)} merged=${ghText(pr.merged)} bodySha=${ghText(pr.bodySha)} bodyPreview=${body.preview}`, "", "Next:", ` bun scripts/cli.ts gh pr files ${number} --repo ${result.repo}`, ` bun scripts/cli.ts gh pr view ${number} --repo ${result.repo} --json body,title,state,head,base`, ` bun scripts/cli.ts gh pr view ${number} --repo ${result.repo} --full`, "", "Disclosure:", " default view omits full pullRequest.body; use --json body, --full, or --raw for explicit structured disclosure.", ].join("\n"); } function renderPrList(result: GitHubCommandResult): string { const prs = arrayOfRecords(result.pullRequests); const rows = prs.slice(0, 40).map((pr) => { const head = record(pr.head); const base = record(pr.base); return [ `#${ghText(pr.number)}`, ghText(pr.state), shortDate(pr.updatedAt), ghText(base.ref), ghText(head.ref), ghText(pr.draft), ghShort(ghText(pr.title), 96), ]; }); return [ "gh pr list (observed)", "", ghTable(["PR", "STATE", "UPDATED", "BASE", "HEAD", "DRAFT", "TITLE"], rows), "", "Summary:", ` repo=${ghText(result.repo)} state=${ghText(result.state)} count=${ghText(result.count)} limit=${ghText(result.limit)}`, "", "Next:", " bun scripts/cli.ts gh pr view --repo " + ghText(result.repo), "", "Disclosure:", " default list omits PR bodies and closeout metadata; use --json fields, --full, or --raw for structured output.", ].join("\n"); } function renderPrFiles(result: GitHubCommandResult): string { const files = arrayOfRecords(result.files); const rows = files.slice(0, 40).map((file) => [ ghShort(ghText(file.filename), 84), ghText(file.status), ghText(file.additions), ghText(file.deletions), ghText(file.changes), ]); const summary = record(result.summary); const truncation = record(result.truncation); const next = record(result.next); const lines = [ `gh ${result.command} (observed)`, "", ghTable(["FILE", "STATUS", "ADD", "DEL", "CHANGES"], rows), "", "Summary:", ` repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} files=${ghText(summary.files)} returned=${ghText(result.filesReturned)} truncated=${ghText(truncation.truncated)}`, ` additions=${ghText(summary.additions)} deletions=${ghText(summary.deletions)} changes=${ghText(summary.changes)}`, ]; if (typeof next.command === "string") lines.push("", "Next:", ` ${next.command}`); lines.push("", "Disclosure:", " default view is a bounded file/stat table and never emits raw diffs."); return lines.join("\n"); } function renderPrReviewPlan(result: GitHubCommandResult): string { const files = arrayOfRecords(result.files); const rows = files.slice(0, 40).map((file) => { const patch = record(file.patch); return [ ghText(file.index), ghShort(ghText(file.filename), 72), ghText(file.status), ghText(file.additions), ghText(file.deletions), ghText(patch.hunks), ghText(patch.lines), ghText(patch.defaultTruncated), ]; }); const summary = record(result.summary); const truncation = record(result.truncation); const drillDown = files .map((file) => record(file.drillDown).patch) .filter((command): command is string => typeof command === "string") .slice(0, 40); const next = record(result.next); const lines = [ "gh pr review-plan (observed)", "", ghTable(["#", "FILE", "STATUS", "ADD", "DEL", "HUNKS", "PATCH_LINES", "PATCH_TRUNC"], rows), "", "Summary:", ` repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} files=${ghText(summary.files)} returned=${ghText(result.filesReturned)} truncated=${ghText(truncation.truncated)}`, ` additions=${ghText(summary.additions)} deletions=${ghText(summary.deletions)} changes=${ghText(summary.changes)}`, "", "Next:", ]; if (drillDown.length > 0) lines.push(...drillDown.map((command, index) => ` [${index + 1}] ${command}`)); else lines.push(` ${ghText(next.metadata)}`); if (typeof next.files === "string") lines.push(` ${next.files}`); lines.push("", "Disclosure:", " default review-plan is a bounded changed-file index; it omits patch bodies and prints per-file drill-down commands."); return lines.join("\n"); } function renderPrDiffFile(result: GitHubCommandResult): string { const file = record(result.file); const patch = record(result.patch); const selectedHunk = record(patch.hunk); const patchLines = Array.isArray(patch.lines) ? patch.lines.filter((line): line is string => typeof line === "string") : []; const next = record(result.next); const lines = [ "gh pr diff (observed)", "", ghTable(["FILE", "STATUS", "ADD", "DEL", "HUNKS", "SELECTED", "LINES", "SHOWN", "TRUNC"], [[ ghShort(ghText(file.filename), 72), ghText(file.status), ghText(file.additions), ghText(file.deletions), ghText(file.hunks), ghText(selectedHunk.index ?? "file"), ghText(patch.linesTotal), ghText(patch.linesShown), ghText(patch.truncated), ]]), "", "Patch:", ...patchLines.map((line) => ` ${line}`), "", "Summary:", ` repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} file=${ghText(file.filename)} redactions=${ghText(patch.redactions)} rawDiffIncluded=${ghText(result.rawDiffIncluded)}`, "", "Next:", ` ${ghText(next.reviewPlan)}`, ` ${ghText(next.nextHunk ?? next.fullFilePatch)}`, "", "Disclosure:", " default diff output is a bounded patch excerpt; use --hunk N for one hunk or --full/--raw for explicit structured full-patch disclosure.", ]; return lines.join("\n"); } function renderCommentResult(result: GitHubCommandResult): string { const comment = record(result.comment); const status = result.dryRun === true ? "dry-run" : result.deleted === true ? "deleted" : result.ok ? "ok" : "failed"; const id = ghText(result.commentId ?? comment.id); const readCommands = record(result.readCommands); const lines = [ `gh ${result.command} (${status})`, "", ghTable(["COMMENT", "STATUS", "BODY", "UPDATED", "URL"], [[ id, status, `${ghText(result.bodyChars ?? comment.bodyChars)} chars`, shortDate(comment.updatedAt), ghShort(ghText(comment.url), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} bodySha=${ghText(result.bodySha ?? comment.bodySha)} preview=${previewFrom(comment)}`, ]; if (typeof readCommands.full === "string" || typeof readCommands.comments === "string") lines.push("", "Next:", ` ${ghText(readCommands.comments ?? readCommands.full)}`); lines.push("", "Disclosure:", " default comment output omits full body; use comment view --full or --raw for explicit disclosure."); return lines.join("\n"); } function renderAuthStatus(result: GitHubCommandResult): string { const token = record(result.token); const gh = record(result.gh); const probes = record(result.probes); const rows = [ ["token", token.present === true ? "ok" : "missing", `source=${ghText(token.source)} ghFallback=${ghText(token.ghFallbackAttempted)}`], ["gh-binary", gh.binaryFound === true ? "ok" : "missing", `path=${ghText(gh.path)}`], ["rest-api", probeStatus(probes.restApi), ghText(probes.restApi)], ["repo", probeStatus(probes.repo), probeDetail(probes.repo)], ["issue-read", probeStatus(probes.issueRead), probeDetail(probes.issueRead)], ]; return [ `gh auth status (${result.ok ? "ok" : "failed"})`, "", ghTable(["CHECK", "STATUS", "DETAIL"], rows), "", "Summary:", ` repo=${ghText(result.repo)} restFallback=${ghText(result.restFallback)} valuesPrinted=false`, "", "Disclosure:", " token values are never printed; use --raw only where supported for structured diagnostics.", ].join("\n"); } function renderRepoResult(result: GitHubCommandResult): string { const repo = record(result.repository); const planned = record(result.planned); const status = result.dryRun === true ? "dry-run" : result.command === "repo create" ? "created" : "observed"; return [ `gh ${result.command} (${status})`, "", ghTable(["REPO", "STATUS", "PRIVATE", "URL", "DESCRIPTION"], [[ ghText(repo.fullName ?? result.repo), status, ghText(repo.private ?? planned.visibility), ghShort(ghText(repo.url ?? repo.htmlUrl), 96), ghShort(ghText(repo.description ?? planned.description), 80), ]]), "", "Summary:", ` repo=${ghText(result.repo)} dryRun=${ghText(result.dryRun)}`, "", "Disclosure:", " default repo output is compact; use --raw where supported for request metadata.", ].join("\n"); } function renderBoardRowResult(result: GitHubCommandResult): string { const rows = arrayOfRecords(result.rows); const row = isRecord(result.row) ? [result.row] : []; const shown = rows.length > 0 ? rows : row; const tableRows = shown.slice(0, 30).map((item) => [ `#${ghText(item.issueNumber ?? item.number)}`, ghText(item.section ?? item.sectionKind ?? result.state), ghShort(ghText(item.status ?? item.githubStatus ?? item.summary), 28), ghShort(ghText(item.progress ?? item.focus ?? item.raw), 96), ]); const boardIssue = record(result.boardIssue); const lines = [ `gh ${result.command} (${result.dryRun === true ? "dry-run" : "ok"})`, "", ghTable(["ISSUE", "SECTION", "STATUS", "SUMMARY"], tableRows), "", "Summary:", ` repo=${ghText(result.repo)} board=#${ghText(boardIssue.number)} issue=#${ghText(result.issueNumber)} count=${ghText(result.count)} operation=${ghText(result.operation)}`, ]; lines.push("", "Disclosure:", " default board-row output is a bounded row summary; use --full/--raw where supported for mutation plans."); return lines.join("\n"); } function renderBoardAudit(result: GitHubCommandResult): string { const sections = arrayOfRecords(result.sections); const rows = sections.map((section) => [ ghText(section.kind), ghShort(ghText(section.heading), 80), ghText(section.rows), ghText(section.headerLine), ]); const summary = record(result.summary); return [ "gh issue board-audit (observed)", "", ghTable(["KIND", "HEADING", "ROWS", "LINE"], rows), "", "Summary:", ` repo=${ghText(result.repo)} board=#${ghText(record(result.boardIssue).number)} sections=${ghText(summary.parsedSections)} rows=${ghText(summary.parsedRows)} warnings=${ghText(summary.parserWarnings)}`, "", "Disclosure:", " default board audit prints parsed section counts only; use --raw for full parser details.", ].join("\n"); } function renderAttachmentResult(result: GitHubCommandResult): string { const attachments = arrayOfRecords(result.attachments); const rows = attachments.map((attachment) => [ ghText(attachment.index), ghText(attachment.source), ghText(attachment.assetId), ghShort(ghText(attachment.url), 96), ]); const output = record(result.output); const next = record(result.next); const table = rows.length > 0 ? ghTable(["INDEX", "SOURCE", "ASSET", "URL"], rows) : result.command === "issue attachment download" ? ghTable(["OUTPUT", "BYTES", "SHA256", "TYPE"], [[ghText(output.path), ghText(output.bytes), ghShort(ghText(output.sha256), 16), ghText(output.contentType)]]) : "No attachments found."; const lines = [ `gh ${result.command} (${result.dryRun === true ? "dry-run" : "ok"})`, "", table, "", "Summary:", ` repo=${ghText(result.repo)} issue=#${ghText(record(result.issue).number)} count=${ghText(result.count)} commentsScanned=${ghText(result.commentsScanned)}`, ]; if (typeof next.command === "string") lines.push("", "Next:", ` ${next.command}`); lines.push("", "Disclosure:", " default attachment output prints metadata/path only and never prints binary bytes."); return lines.join("\n"); } function renderStaleClose(result: GitHubCommandResult): string { const stale = record(result.stale); const closed = record(result.closed); return [ `gh issue stale-close (${result.dryRun === true ? "dry-run" : "applied"})`, "", ghTable(["SCOPE", "COUNT", "RETURNED", "OMITTED"], [ ["stale", ghText(stale.count), ghText(stale.returned), ghText(stale.omitted)], ["closed", ghText(closed.count), ghText(closed.returned), ghText(closed.omitted)], ]), "", "Summary:", ` repo=${ghText(result.repo)} scanned=${ghText(result.scannedCount)} stale=${ghText(result.staleCount)} closed=${ghText(result.closedCount)} failed=${ghText(result.failedCount)}`, "", "Disclosure:", " default stale-close output is a lifecycle summary; use --raw for candidate arrays and failure details.", ].join("\n"); } function renderScanEscape(result: GitHubCommandResult): string { const summary = record(result.summary); return [ `gh ${result.command} (observed)`, "", ghTable(["FINDINGS", "POLLUTION", "EXPLANATORY", "RISK", "BODY_RISK", "COMMENT_ERRORS"], [[ ghText(summary.findings), ghText(summary.suspectedPollution), ghText(summary.explanatoryMention), ghText(summary.risk), ghText(summary.bodyRisks), ghText(summary.commentScanErrors), ]]), "", "Summary:", ` repo=${ghText(result.repo)} scannedIssues=${ghText(result.scannedIssues)} scannedComments=${ghText(result.scannedComments)} suggestions=${ghText(summary.suggestions)}`, "", "Disclosure:", " default scan output is a bounded summary; use --raw for full findings.", ].join("\n"); } function renderIssueLifecycle(result: GitHubCommandResult): string { const issue = record(result.issue); const comment = record(result.comment); return [ `gh ${result.command} (${result.dryRun === true ? "dry-run" : "updated"})`, "", ghTable(["ISSUE", "STATE", "COMMENT", "UPDATED", "TITLE"], [[ `#${ghText(result.issueNumber ?? issue.number)}`, ghText(issue.state ?? record(result.wouldPatch).state), ghText(comment.id ?? "-"), shortDate(issue.updatedAt), ghShort(ghText(issue.title), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} bodySha=${ghText(issue.bodySha)} fullBodyIncluded=${ghText(record(result.disclosure).fullBodyIncluded)}`, "", "Disclosure:", " default lifecycle output omits full issue.body; use readCommands.full/raw for explicit disclosure.", ].join("\n"); } function renderIssueWrite(result: GitHubCommandResult): string { const issue = record(result.issue); const bodyChange = record(result.bodyChange); return [ `gh ${result.command} (${result.dryRun === true ? "dry-run" : "updated"})`, "", ghTable(["ISSUE", "MODE", "BODY", "DELTA", "UPDATED", "TITLE"], [[ `#${ghText(result.issueNumber ?? issue.number)}`, ghText(result.mode ?? record(result.wouldPatch).mode), `${ghText(result.bodyChars ?? issue.bodyChars ?? bodyChange.newBodyChars)} chars`, ghText(bodyChange.deltaChars), shortDate(issue.updatedAt ?? bodyChange.newUpdatedAt), ghShort(ghText(issue.title ?? result.title), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} bodySha=${ghText(result.bodySha ?? issue.bodySha ?? bodyChange.newBodySha)} fullBodyIncluded=${ghText(record(result.disclosure).fullBodyIncluded)}`, "", "Disclosure:", " default write output omits full issue.body and prints body SHA/chars plus drill-down commands.", ].join("\n"); } function renderPrWrite(result: GitHubCommandResult): string { const pr = record(result.pullRequest); return [ `gh ${result.command} (${result.dryRun === true ? "dry-run" : "updated"})`, "", ghTable(["PR", "STATE", "CHANGED", "URL", "TITLE"], [[ `#${ghText(result.number ?? pr.number)}`, ghText(pr.stateDetail ?? pr.state ?? record(result.wouldPatch).state), arrayText(result.changedFields), ghShort(ghText(result.url ?? pr.url), 96), ghShort(ghText(pr.title), 96), ]]), "", "Summary:", ` repo=${ghText(result.repo)} rest=${ghText(result.rest)} graphQl=${ghText(result.graphQl)}`, "", "Disclosure:", " default PR write output is compact; use pr view --json body or --full for explicit body disclosure.", ].join("\n"); } function renderGenericResult(result: GitHubCommandResult): string { const rows = Object.entries(result) .filter(([key]) => !["ok", "command", "repo", "renderedText", "contentType"].includes(key)) .slice(0, 16) .map(([key, value]) => [key, valueSummary(value)]); return [ `gh ${result.command} (${result.ok ? "ok" : "failed"})`, "", ghTable(["FIELD", "VALUE"], rows), "", "Summary:", ` repo=${ghText(result.repo)}`, "", "Disclosure:", " default output is compact; use explicit --json/--full/--raw where supported for structured details.", ].join("\n"); } function record(value: unknown): Record { return isRecord(value) ? value : {}; } function arrayOfRecords(value: unknown): Record[] { return Array.isArray(value) ? value.filter(isRecord) : []; } function bodyInfo(source: Record): { label: string; preview: string } { const chars = ghText(source.bodyChars); const preview = previewFrom(source); return { label: chars === "-" ? "-" : `${chars} chars`, preview }; } function previewFrom(source: Record): string { if (typeof source.bodyPreview === "string") return ghShort(source.bodyPreview.replace(/\s+/gu, " "), 160); if (typeof source.body === "string") return ghShort(source.body.replace(/\s+/gu, " "), 160); if (typeof source.preview === "string") return ghShort(source.preview.replace(/\s+/gu, " "), 160); return "-"; } function shortDate(value: unknown): string { if (typeof value !== "string" || value.length === 0) return "-"; return ghShort(value, 19); } function probeStatus(value: unknown): string { if (value === "ok") return "ok"; if (value === "skipped") return "skipped"; if (isRecord(value) && value.ok === true) return "ok"; if (isRecord(value) && value.ok === false) return "failed"; return "-"; } function probeDetail(value: unknown): string { if (isRecord(value)) return valueSummary(value); return ghText(value); } function arrayText(value: unknown): string { return Array.isArray(value) ? value.map(ghText).join(",") || "-" : ghText(value); } function valueSummary(value: unknown): string { if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return ghShort(ghText(value), 120); if (Array.isArray(value)) return `array(${value.length})`; if (isRecord(value)) { const keys = Object.keys(value); const title = ghText(value.title ?? value.name ?? value.number ?? value.id ?? value.path ?? value.command); return title === "-" ? `object(${keys.slice(0, 5).join(",")})` : ghShort(title, 120); } return ghShort(String(value), 120); }