feat: add heredoc body input to gh cli
This commit is contained in:
+57
-43
@@ -60,7 +60,7 @@ const GH_VALUE_OPTIONS = new Set([
|
||||
"--tasks", "--summary", "--focus", "--validation", "--progress", "--number", "--pr",
|
||||
"--search", "--inactive-hours", "--comment", "--comment-file", "--description",
|
||||
]);
|
||||
const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--private", "--public", "--auto-init"]);
|
||||
const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--private", "--public", "--auto-init"]);
|
||||
const MIN_SAFE_BODY_SCAN_CHARS = MIN_SAFE_ISSUE_BODY_CHARS;
|
||||
const ISSUE_SCAN_MAX_FINDINGS = 60;
|
||||
const ISSUE_BODY_PROFILES = {
|
||||
@@ -100,7 +100,7 @@ type IssueBodyProfileOption = "auto" | IssueBodyProfileName;
|
||||
type EscapeFindingClassification = "suspected-pollution" | "explanatory-mention" | "risk";
|
||||
type EscapeFindingSeverity = "low" | "medium" | "high";
|
||||
type EscapeBodyKind = "issue-body" | "comment-body";
|
||||
type CleanupSuggestionAction = "rewrite-issue-body-with-body-file" | "review-comment-manually" | "review-body-length" | "no-cleanup-needed";
|
||||
type CleanupSuggestionAction = "rewrite-issue-body-with-body-stdin" | "review-comment-manually" | "review-body-length" | "no-cleanup-needed";
|
||||
type BoardSectionKind = "open" | "closed";
|
||||
type BoardRequiredColumn = typeof BOARD_AUDIT_REQUIRED_COLUMNS[number];
|
||||
type BoardColumnKind = BoardRequiredColumn | "focus" | "githubStatus" | "category" | "summary";
|
||||
@@ -219,7 +219,7 @@ interface EscapeCleanupSuggestion {
|
||||
reason: string;
|
||||
findings: Array<Pick<EscapeMatchFinding, "kind" | "classification" | "severity" | "lineNumber" | "column" | "snippet" | "match">>;
|
||||
plannedCommand?: string;
|
||||
bodyFileHint?: string;
|
||||
bodyInputHint?: string;
|
||||
preview?: {
|
||||
before: string;
|
||||
after: string;
|
||||
@@ -868,6 +868,15 @@ function resolveRepoOption(args: string[]): string {
|
||||
return explicitRepo ?? DEFAULT_REPO;
|
||||
}
|
||||
|
||||
function stdinAliasFileOption(args: string[], fileOption: string, stdinFlag: string): string | undefined {
|
||||
const file = optionValue(args, fileOption);
|
||||
const stdin = hasFlag(args, stdinFlag);
|
||||
if (file !== undefined && stdin) {
|
||||
throw new Error(`${fileOption} and ${stdinFlag} are mutually exclusive; use one body source`);
|
||||
}
|
||||
return stdin ? "-" : file;
|
||||
}
|
||||
|
||||
function parseOptions(args: string[]): GitHubOptions {
|
||||
validateKnownOptions(args);
|
||||
const [top, sub] = args;
|
||||
@@ -894,9 +903,9 @@ function parseOptions(args: string[]): GitHubOptions {
|
||||
search: optionValue(args, "--search"),
|
||||
title: optionValue(args, "--title"),
|
||||
body: optionValue(args, "--body"),
|
||||
bodyFile: optionValue(args, "--body-file"),
|
||||
bodyFile: stdinAliasFileOption(args, "--body-file", "--body-stdin"),
|
||||
comment: optionValue(args, "--comment"),
|
||||
commentFile: optionValue(args, "--comment-file"),
|
||||
commentFile: stdinAliasFileOption(args, "--comment-file", "--comment-stdin"),
|
||||
repoDescription: optionValue(args, "--description"),
|
||||
repoVisibility: parseRepoVisibility(args),
|
||||
repoAutoInit: hasFlag(args, "--auto-init"),
|
||||
@@ -1299,7 +1308,7 @@ function readMarkdownBodyFileOrStdin(path: string): { body: string; bodySource:
|
||||
}
|
||||
|
||||
function readOptionalMarkdownBody(options: GitHubOptions, command: string): { body: string; bodySource: Record<string, unknown> } | null {
|
||||
if (options.bodyFile !== undefined && options.body !== undefined) throw new Error(`${command} accepts only one body source: --body-file or --body`);
|
||||
if (options.bodyFile !== undefined && options.body !== undefined) throw new Error(`${command} accepts only one body source: --body-file/--body-stdin or --body`);
|
||||
if (options.bodyFile !== undefined) {
|
||||
return readMarkdownBodyFileOrStdin(options.bodyFile);
|
||||
}
|
||||
@@ -1308,7 +1317,7 @@ function readOptionalMarkdownBody(options: GitHubOptions, command: string): { bo
|
||||
body: options.body,
|
||||
bodySource: {
|
||||
kind: "inline",
|
||||
warning: options.body.includes("\n") ? "inline body contains real newlines; --body-file is safer for generated Markdown" : "inline body is intended only for short single-line text",
|
||||
warning: options.body.includes("\n") ? "inline body contains real newlines; --body-stdin with a quoted heredoc is safer for generated Markdown" : "inline body is intended only for short single-line text",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1318,7 +1327,7 @@ function readOptionalMarkdownBody(options: GitHubOptions, command: string): { bo
|
||||
function readMarkdownBody(options: GitHubOptions, command: string): { body: string; bodySource: Record<string, unknown> } {
|
||||
const body = readOptionalMarkdownBody(options, command);
|
||||
if (body !== null) return body;
|
||||
throw new Error(`${command} requires --body-file <file> or --body <text>`);
|
||||
throw new Error(`${command} requires --body-stdin, --body-file <file|->, or --body <text>`);
|
||||
}
|
||||
|
||||
function secretLikeInlineFindings(body: string): string[] {
|
||||
@@ -1332,27 +1341,27 @@ function secretLikeInlineFindings(body: string): string[] {
|
||||
|
||||
function readIssueCommentBody(options: GitHubOptions): { body: string; bodySource: Record<string, unknown> } {
|
||||
if (options.bodyFile !== undefined && options.body !== undefined) {
|
||||
throw new Error("issue comment create accepts only one body source: --body-file or --body");
|
||||
throw new Error("issue comment create accepts only one body source: --body-file/--body-stdin or --body");
|
||||
}
|
||||
if (options.bodyFile !== undefined) {
|
||||
return readMarkdownBodyFileOrStdin(options.bodyFile);
|
||||
}
|
||||
if (options.body === undefined) throw new Error("issue comment create requires --body-file <file> or --body <text>");
|
||||
if (options.body === undefined) throw new Error("issue comment create requires --body-stdin, --body-file <file|->, or --body <text>");
|
||||
const body = options.body;
|
||||
const trimmed = body.trim();
|
||||
const shellPollution = shellPollutionEvidence(body);
|
||||
const secretLike = secretLikeInlineFindings(body);
|
||||
if (trimmed.length === 0) {
|
||||
throw new Error("issue comment create --body must not be blank; use --body-file for reviewed Markdown");
|
||||
throw new Error("issue comment create --body must not be blank; use --body-stdin with a quoted heredoc for reviewed Markdown");
|
||||
}
|
||||
if (body.length > MAX_INLINE_ISSUE_COMMENT_BODY_CHARS) {
|
||||
throw new Error(`issue comment create --body is limited to ${MAX_INLINE_ISSUE_COMMENT_BODY_CHARS} characters; use --body-file for long Markdown`);
|
||||
throw new Error(`issue comment create --body is limited to ${MAX_INLINE_ISSUE_COMMENT_BODY_CHARS} characters; use --body-stdin with a quoted heredoc for long Markdown`);
|
||||
}
|
||||
if (body.includes("\n") || body.includes("\r")) {
|
||||
throw new Error("issue comment create --body supports short single-line text only; use --body-file for multiline Markdown");
|
||||
throw new Error("issue comment create --body supports short single-line text only; use --body-stdin with a quoted heredoc for multiline Markdown");
|
||||
}
|
||||
if (shellPollution.length > 0) {
|
||||
throw new Error(`issue comment create --body contains shell-pollution signals (${shellPollution.join(",")}); use --body-file with reviewed Markdown bytes`);
|
||||
throw new Error(`issue comment create --body contains shell-pollution signals (${shellPollution.join(",")}); use --body-stdin with a quoted heredoc for reviewed Markdown bytes`);
|
||||
}
|
||||
if (secretLike.length > 0) {
|
||||
throw new Error(`issue comment create --body appears to contain secret-like text (${secretLike.join(",")}); refusing to print or submit it`);
|
||||
@@ -1362,7 +1371,7 @@ function readIssueCommentBody(options: GitHubOptions): { body: string; bodySourc
|
||||
bodySource: {
|
||||
kind: "inline",
|
||||
maxInlineBodyChars: MAX_INLINE_ISSUE_COMMENT_BODY_CHARS,
|
||||
warning: "inline issue comments are intended only for short single-line text; use --body-file for Markdown or generated content",
|
||||
warning: "inline issue comments are intended only for short single-line text; use --body-stdin with a quoted heredoc for Markdown or generated content",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1370,10 +1379,10 @@ function readIssueCommentBody(options: GitHubOptions): { body: string; bodySourc
|
||||
function readIssueLifecycleCommentBody(options: GitHubOptions, command: string): { body: string; bodySource: Record<string, unknown> } | null {
|
||||
if (options.comment === undefined && options.commentFile === undefined) return null;
|
||||
if (options.comment !== undefined && options.commentFile !== undefined) {
|
||||
throw new Error(`${command} --comment and --comment-file are mutually exclusive`);
|
||||
throw new Error(`${command} --comment and --comment-file/--comment-stdin are mutually exclusive`);
|
||||
}
|
||||
if (options.body !== undefined || options.bodyFile !== undefined) {
|
||||
throw new Error(`${command} --comment or --comment-file cannot be combined with --body or --body-file`);
|
||||
throw new Error(`${command} --comment/--comment-file/--comment-stdin cannot be combined with --body/--body-file/--body-stdin`);
|
||||
}
|
||||
if (options.commentFile !== undefined) {
|
||||
return readIssueCommentBody({ ...options, body: undefined, bodyFile: options.commentFile });
|
||||
@@ -1593,7 +1602,7 @@ function codeQueueBoardCommanderBriefHint(boardIssueNumber: number, body?: strin
|
||||
? "Move HWLAB product/user issue tracking to pikasTech/HWLAB; keep #20 limited to UniDesk commander/Code Queue/CLI/infra governance."
|
||||
: "Use #20 only for UniDesk commander/Code Queue/CLI/infra governance rows.",
|
||||
forbiddenHeadings: ["## 更新 YYYY-MM-DD HH:mm 北京时间", "## YYYY-MM-DD HH:mm 北京时间指挥更新", "### YYYY-MM-DD HH:mm CST:..."],
|
||||
suggestedCommand: "bun scripts/cli.ts gh issue update <daily-brief-issue> --mode replace --body-file <file> --body-profile commander-brief --expect-body-sha <sha>",
|
||||
suggestedCommand: "bun scripts/cli.ts gh issue update <daily-brief-issue> --mode replace --body-stdin --body-profile commander-brief --expect-body-sha <sha> <<'EOF'\n<reviewed body>\nEOF",
|
||||
detected: commanderBriefDetected || hwlabProductDetected,
|
||||
detectedHeadings: headings,
|
||||
commanderBrief: {
|
||||
@@ -5275,6 +5284,11 @@ async function prComment(repo: string, token: string, issueNumber: number, optio
|
||||
issueNumber,
|
||||
pr: prSummary(prResult),
|
||||
comment: commentSummary(comment),
|
||||
bodySource: bodySource.bodySource,
|
||||
source: String(bodySource.bodySource.kind ?? "unknown"),
|
||||
bodyChars: body.length,
|
||||
bodySha: bodySha(body),
|
||||
bodyPreview: preview(body),
|
||||
request: {
|
||||
method: "POST",
|
||||
path: `/repos/${owner}/${name}/issues/${issueNumber}/comments`,
|
||||
@@ -5297,7 +5311,7 @@ async function prUpdate(repo: string, token: string, number: number, options: Gi
|
||||
return validationError(commandName, repo, error instanceof Error ? error.message : String(error), { number });
|
||||
}
|
||||
if (bodySource === null && options.title === undefined) {
|
||||
return validationError(commandName, repo, `${commandName} requires --title <title>, --body-file <file|->, or --body <text>`, { number });
|
||||
return validationError(commandName, repo, `${commandName} requires --title <title>, --body-stdin, --body-file <file|->, or --body <text>`, { number });
|
||||
}
|
||||
if (options.mode === "append" && bodySource === null) {
|
||||
return validationError(commandName, repo, `${commandName} --mode append requires a body source`, { number });
|
||||
@@ -5657,7 +5671,7 @@ async function issueBoardAudit(repo: string, token: string, options: GitHubOptio
|
||||
async function issueCreate(repo: string, token: string, options: GitHubOptions): Promise<GitHubCommandResult> {
|
||||
if (options.title === undefined) throw new Error("issue create requires --title <title>");
|
||||
if (options.body !== undefined) {
|
||||
return validationError("issue create", repo, "issue create does not support --body; use --body-file <file|-> for Markdown", {
|
||||
return validationError("issue create", repo, "issue create does not support --body; use --body-stdin with a quoted heredoc for Markdown", {
|
||||
title: options.title,
|
||||
bodySource: "inline",
|
||||
});
|
||||
@@ -5699,7 +5713,7 @@ async function issueCreate(repo: string, token: string, options: GitHubOptions):
|
||||
|
||||
async function issueEdit(repo: string, token: string, issueNumber: number, options: GitHubOptions, commandName = "issue edit"): Promise<GitHubCommandResult> {
|
||||
if (options.bodyFile === undefined) {
|
||||
return validationError(commandName, repo, `${commandName} requires --body-file <file|->`, { issueNumber });
|
||||
return validationError(commandName, repo, `${commandName} requires --body-stdin or --body-file <file|->`, { issueNumber });
|
||||
}
|
||||
let bodyInput: { body: string; bodySource: Record<string, unknown> };
|
||||
try {
|
||||
@@ -6401,10 +6415,10 @@ function summarizeCleanupSuggestion(findings: EscapeScanEntry[]): EscapeCleanupS
|
||||
url: first.url,
|
||||
classification: suspected ? "suspected-pollution" : "risk",
|
||||
action: first.bodyKind === "issue-body"
|
||||
? (suspected ? "rewrite-issue-body-with-body-file" : "review-body-length")
|
||||
? (suspected ? "rewrite-issue-body-with-body-stdin" : "review-body-length")
|
||||
: "review-comment-manually",
|
||||
reason: suspected
|
||||
? "suspected shell escape pollution should be rewritten from a body file"
|
||||
? "suspected shell escape pollution should be rewritten from heredoc/stdin"
|
||||
: bodyRisk
|
||||
? "body length/null risk should be reviewed before any write"
|
||||
: "no cleanup needed",
|
||||
@@ -6419,8 +6433,8 @@ function summarizeCleanupSuggestion(findings: EscapeScanEntry[]): EscapeCleanupS
|
||||
})),
|
||||
...(first.bodyKind === "issue-body"
|
||||
? {
|
||||
plannedCommand: `bun scripts/cli.ts gh issue update ${first.issueNumber} --mode replace --body-file <file> --dry-run`,
|
||||
bodyFileHint: "write a cleaned Markdown body to a file, then pass it with --body-file; use a quoted heredoc to stage the file instead of inline shell text",
|
||||
plannedCommand: `bun scripts/cli.ts gh issue update ${first.issueNumber} --mode replace --body-stdin --dry-run <<'EOF'\n<cleaned body>\nEOF`,
|
||||
bodyInputHint: "pass cleaned Markdown through --body-stdin with a quoted heredoc; --body-file is only for reusable files",
|
||||
preview: {
|
||||
before: first.cleanupPreview?.before ?? first.snippet,
|
||||
after: first.cleanupPreview?.after ?? first.snippet,
|
||||
@@ -6700,13 +6714,13 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh issue list [owner/repo] [--state open|closed|all] [--limit N] [--search 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 <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--json body,title,state,closed,closedAt,comments] [--raw|--full]",
|
||||
"bun scripts/cli.ts gh issue read <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--raw|--full] [compatibility alias for issue view]",
|
||||
"bun scripts/cli.ts gh issue create --title <title> --body-file <file|-> [--label label[,label...]]... [--repo owner/name] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue update <number> --mode replace|append --body-file <file|-> [--title title] [--repo owner/name] [--number N compat] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256] [--body-profile auto|code-queue-board|commander-brief] [--allow-short-body] [--full|--raw]",
|
||||
"bun scripts/cli.ts gh issue edit <number> --body-file <file|-> [--repo owner/name] [--number N compat] [--full|--raw] [compat alias for issue update --mode replace]",
|
||||
"bun scripts/cli.ts gh issue edit 24 --body-file <file> --notify-claudeqq-brief-diff [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue comment create <number> --body-file <file|->|--body <short-text> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue create --title <title> (--body-stdin|--body-file <file|->) [--label label[,label...]]... [--repo owner/name] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue update <number> --mode replace|append (--body-stdin|--body-file <file|->) [--title title] [--repo owner/name] [--number N compat] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256] [--body-profile auto|code-queue-board|commander-brief] [--allow-short-body] [--full|--raw]",
|
||||
"bun scripts/cli.ts gh issue edit <number> (--body-stdin|--body-file <file|->) [--repo owner/name] [--number N compat] [--full|--raw] [compat alias for issue update --mode replace]",
|
||||
"bun scripts/cli.ts gh issue edit 24 --body-stdin --notify-claudeqq-brief-diff [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue comment create <number> (--body-stdin|--body-file <file|->|--body <short-text>) [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue comment delete <commentId> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue close|reopen <number> [--repo owner/name] [--number N compat] [--comment <short-text>|--comment-file <file|->] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue close|reopen <number> [--repo owner/name] [--number N compat] [--comment <short-text>|--comment-stdin|--comment-file <file|->] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue stale-close [--repo owner/name] [--inactive-hours N] [--limit N] [--label label[,label...]]... [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue delete <number> [unsupported: use close]",
|
||||
"bun scripts/cli.ts gh issue scan-escape [--repo owner/name] [--limit N] [--dry-run]",
|
||||
@@ -6727,10 +6741,10 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh pr read <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--raw|--full] [compatibility alias for pr view]",
|
||||
"bun scripts/cli.ts gh pr preflight <number|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [number may appear before or after options]",
|
||||
"bun scripts/cli.ts gh pr closeout <number|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [number may appear before or after options; compatibility alias for pr preflight]",
|
||||
"bun scripts/cli.ts gh pr create --title <title> --body-file <file>|--body <text> --base <branch> --head <branch> [--repo owner/name] [--draft] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr edit <number> [--title title] [--body-file <file>|--body-file -|--body <text>] [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr update <number> --mode replace|append [--body-file <file>|--body-file -|--body <text>] [--title title] [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr comment create <number> --body-file <file>|--body <text> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr create --title <title> (--body-stdin|--body-file <file|->|--body <text>) --base <branch> --head <branch> [--repo owner/name] [--draft] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr edit <number> [--title title] [--body-stdin|--body-file <file|->|--body <text>] [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr update <number> --mode replace|append [--body-stdin|--body-file <file|->|--body <text>] [--title title] [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr comment create <number> (--body-stdin|--body-file <file|->|--body <text>) [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr comment delete <commentId> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr close|reopen <number> [--repo owner/name] [--number N compat] [--dry-run]",
|
||||
"bun scripts/cli.ts gh pr merge <number> [--repo owner/name] [--number N compat] [--merge|--squash|--rebase] [--delete-branch] [--dry-run]",
|
||||
@@ -6747,17 +6761,17 @@ export function ghHelp(): unknown {
|
||||
"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 delete treats --number as commentId, not an issue number. View supports lifecycle fields closed/closedAt plus legacy --json field selection such as --json body and still exposes .data.issue.body for compatibility; unsupported fields fail structurally.",
|
||||
"--raw and --full are explicit full-disclosure aliases for gh issue list/read/view/update/edit and gh pr list/read/view. 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.",
|
||||
"GitHub CLI output larger than 20 KiB is automatically written to /tmp/unidesk-cli-output/*.json; stdout stays bounded JSON with outputTruncated=true, the dump path, total bytes/lines, and head/tail previews.",
|
||||
"issue create accepts --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-file is the recommended source for Markdown bodies so real newlines, backticks, and tables are read as file bytes instead of shell arguments.",
|
||||
"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.",
|
||||
"update defaults to --mode replace; --mode append reads the current body and appends file bytes so real newlines, backticks, and Markdown tables are preserved.",
|
||||
"issue edit is a compatibility alias for issue update --mode replace.",
|
||||
"issue update --body-file accepts files or - for stdin, refuses literal null, blank, and too-short bodies by default. Use --allow-short-body only for intentional short writes; #20 requires its board heading, warns when HWLAB product/user issue routing appears in favor of pikasTech/HWLAB, and still rejects commander brief update sections; commander-brief requires its stable heading on legacy #24 plus daily rolling brief issues titled YYYY-MM-DD 指挥简报(北京时间).",
|
||||
"issue update accepts --body-stdin or --body-file <file|->, refuses literal null, blank, and too-short bodies by default. Use --allow-short-body only for intentional short writes; #20 requires its board heading, warns when HWLAB product/user issue routing appears in favor of pikasTech/HWLAB, and still rejects commander brief update sections; commander-brief requires its stable heading on legacy #24 plus daily rolling brief issues titled YYYY-MM-DD 指挥简报(北京时间).",
|
||||
"issue update dry-run reports bounded bodyPreview/bodyPreviewLines, old/new body length slots, body SHA, required heading checks, literal \\n detection, shell-pollution signals, guard/concurrency summary, wouldPatch, and readCommands without printing an unbounded full body. Non-dry-run automatically reads current issue metadata before PATCH and returns oldBodySha/updatedAt; --expect-updated-at or --expect-body-sha remain available for explicit stale-cache protection.",
|
||||
"issue comment create accepts --body-file <file|-> for Markdown/generated content and --body only for short single-line text. Blank, multiline, shell-polluted, secret-like, and overlong inline bodies fail structurally.",
|
||||
"issue close/reopen default success output is compact and omits full issue.body. Optional --comment <short-text> or --comment-file <file|-> posts a bounded lifecycle comment before the state change and aborts the state change if the comment POST fails. --comment-file is the recommended path for generated Markdown closeout evidence; --comment remains the short inline form. Use gh issue view <number> --json body or --full/--raw on view when full text is needed.",
|
||||
"issue comment create accepts --body-stdin or --body-file <file|-> for Markdown/generated content and --body only for short single-line text. Blank, multiline, shell-polluted, secret-like, and overlong inline bodies fail structurally.",
|
||||
"issue close/reopen default success output is compact and omits full issue.body. Optional --comment <short-text>, --comment-stdin, or --comment-file <file|-> posts a bounded lifecycle comment before the state change and aborts the state change if the comment POST fails. --comment-stdin is the first-class heredoc path for generated Markdown closeout evidence; --comment remains the short inline form. Use gh issue view <number> --json body or --full/--raw on view when full text is needed.",
|
||||
"issue stale-close is the reusable lifecycle cleanup path for policies such as closing open issues inactive for more than 48 hours. It selects open issues by GitHub updatedAt older than observedAt - --inactive-hours, treats comments and state changes as activity, filters pull requests, supports --dry-run, and returns bounded candidate/closed/failure summaries without echoing full bodies.",
|
||||
"For one-shot issue writes, pipe reviewed Markdown through stdin: cat body.md | bun scripts/cli.ts gh issue update <number> --repo owner/name --body-file - or gh issue comment create <number> --body-file -. When staging a body file from a shell, use a quoted heredoc such as cat <<'EOF' > /tmp/body.md so backticks and backslashes are not expanded before --body-file reads the file.",
|
||||
"For JSON request bodies in other CLI namespaces, prefer --body-file or --body-stdin over long inline shell arguments. GitHub issue/PR Markdown writes use --body-file <file|-> for long or multiline content.",
|
||||
"For one-shot issue writes, prefer quoted heredoc stdin: bun scripts/cli.ts gh issue update <number> --repo owner/name --body-stdin <<'EOF' ... EOF or gh issue comment create <number> --body-stdin <<'EOF' ... EOF. --body-file <file|-> remains available for reusable files and pipes.",
|
||||
"For JSON request bodies in other CLI namespaces, prefer --body-file or --body-stdin over long inline shell arguments. GitHub issue/PR Markdown writes use --body-stdin for heredoc/stdin or --body-file <file|-> for reusable files.",
|
||||
"issue scan-escape classifies literal \\n findings as suspected-pollution, explanatory-mention, or risk, and emits cleanupSuggestions with body/comment ids plus diff-like previews. cleanup-plan is an alias that remains dry-run/read-only.",
|
||||
"issue board-audit is read-only and defaults to repo pikasTech/unidesk plus board issue #20. It reads only the board issue body, returns body size/SHA and parsed Markdown board sections, and no longer validates GitHub open/closed issue coverage against OPEN/CLOSED tables. The legacy coverage fields remain present as empty arrays/zero counts for compatibility.",
|
||||
"issue board-row list/get reuse the board-audit table parser and are read-only. board-row update changes one table cell by issue number, returns old/new row, body SHA, body guard and request plan, and defaults to dry-run unless --expect-updated-at or --expect-body-sha is supplied for the guarded PATCH. Field aliases map status and validation to the 验收状态 column, tasks to 相关 Code Queue 任务, and focus to 当前关注点.",
|
||||
@@ -6813,7 +6827,7 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
"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 view <number> --repo owner/name --json body,title,state,comments",
|
||||
"cat body.md | bun scripts/cli.ts gh issue update <number> --repo owner/name --body-file - --full",
|
||||
"bun scripts/cli.ts gh issue update <number> --repo owner/name --body-stdin --full <<'EOF'\n<reviewed body>\nEOF",
|
||||
"bun scripts/cli.ts gh pr list --repo owner/name --limit 100 --full",
|
||||
"bun scripts/cli.ts gh pr view owner/name#<number> --raw",
|
||||
`bun scripts/cli.ts gh pr view <number> --repo owner/name --json ${readViewSupportedJsonFields("pr")}`,
|
||||
|
||||
Reference in New Issue
Block a user