fix: 压缩 gh issue update 成功输出
This commit is contained in:
@@ -74,6 +74,8 @@ function sendJson(res: ServerResponse, status: number, payload: unknown): void {
|
||||
|
||||
async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockRequest[]; close: () => Promise<void> }> {
|
||||
const requests: MockRequest[] = [];
|
||||
let shorthandIssueBody = "HWLAB-style shorthand body fixture\n\nThis is generic CLI coverage, not product data.";
|
||||
let shorthandIssueUpdatedAt = "2026-05-20T03:00:00Z";
|
||||
const issue = {
|
||||
id: 2000,
|
||||
number: 20,
|
||||
@@ -90,13 +92,13 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
id: 7000,
|
||||
number: 7,
|
||||
title: "generic shorthand fixture",
|
||||
body: "HWLAB-style shorthand body fixture\n\nThis is generic CLI coverage, not product data.",
|
||||
body: shorthandIssueBody,
|
||||
state: "open",
|
||||
html_url: "https://github.com/pikasTech/HWLAB/issues/7",
|
||||
comments: 1,
|
||||
user: { login: "tester" },
|
||||
created_at: "2026-05-20T02:00:00Z",
|
||||
updated_at: "2026-05-20T03:00:00Z",
|
||||
updated_at: shorthandIssueUpdatedAt,
|
||||
};
|
||||
const boardIssueBodyInitial = [
|
||||
"# Code Queue",
|
||||
@@ -469,7 +471,7 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/HWLAB/issues/7") {
|
||||
sendJson(res, 200, shorthandIssue);
|
||||
sendJson(res, 200, { ...shorthandIssue, body: shorthandIssueBody, updated_at: shorthandIssueUpdatedAt });
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/24") {
|
||||
@@ -568,6 +570,13 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
sendJson(res, 200, { ...upsertBoardIssue, body: upsertBoardIssueBody, updated_at: upsertBoardIssueUpdatedAt });
|
||||
return;
|
||||
}
|
||||
if (req.method === "PATCH" && req.url === "/repos/pikasTech/HWLAB/issues/7") {
|
||||
const parsed = JSON.parse(body) as JsonRecord;
|
||||
shorthandIssueBody = String(parsed.body ?? shorthandIssueBody);
|
||||
shorthandIssueUpdatedAt = "2026-05-20T03:05:00Z";
|
||||
sendJson(res, 200, { ...shorthandIssue, body: shorthandIssueBody, updated_at: shorthandIssueUpdatedAt });
|
||||
return;
|
||||
}
|
||||
if (req.method === "POST" && req.url === "/repos/pikasTech/unidesk/issues/20/comments") {
|
||||
const parsed = JSON.parse(body) as JsonRecord;
|
||||
sendJson(res, 201, { id: 9001, body: String(parsed.body ?? ""), html_url: "https://github.com/pikasTech/unidesk/issues/20#issuecomment-9001", user: { login: "tester" }, created_at: "2026-05-20T06:00:00Z", updated_at: "2026-05-20T06:00:00Z" });
|
||||
@@ -1499,6 +1508,52 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(replaceDryRun.status === 0, "issue update replace dry-run should succeed", replaceDryRun.json ?? { stdout: replaceDryRun.stdout });
|
||||
const replaceData = dataOf(replaceDryRun.json ?? {});
|
||||
assertCondition(replaceData.command === "issue update" && replaceData.mode === "replace", "replace mode should be explicit", replaceData);
|
||||
const replaceDisclosure = replaceData.disclosure as JsonRecord;
|
||||
const replaceReadCommands = replaceData.readCommands as JsonRecord;
|
||||
assertCondition(replaceDisclosure.bodyOmitted === true && replaceDisclosure.dryRunBoundedPreview === true, "issue update dry-run should disclose compact body policy", replaceDisclosure);
|
||||
assertCondition(typeof replaceReadCommands.full === "string" && String(replaceReadCommands.full).includes("gh issue read 20"), "issue update dry-run should expose full body drill-down", replaceReadCommands);
|
||||
const replaceWouldPatch = replaceData.wouldPatch as JsonRecord;
|
||||
assertCondition(typeof replaceWouldPatch.bodySha === "string" && String(replaceWouldPatch.bodySha).length === 64, "issue update dry-run should include wouldPatch body sha", replaceWouldPatch);
|
||||
assertCondition(Number(replaceWouldPatch.bodyChars ?? 0) === Number(replaceData.bodyChars ?? 0), "issue update dry-run wouldPatch should include final body chars", replaceWouldPatch);
|
||||
|
||||
const compactLongBody = Array.from({ length: 260 }, (_, index) => `compact-success-line-${String(index + 1).padStart(4, "0")} ${"x".repeat(80)}`).join("\n");
|
||||
const compactLongFile = join(tmp, "compact-long-body.md");
|
||||
writeFileSync(compactLongFile, compactLongBody, "utf8");
|
||||
const compactUpdateRequestCountBefore = mock.requests.length;
|
||||
const compactUpdate = await runCli(["gh", "issue", "update", "7", "--repo", "pikasTech/HWLAB", "--mode", "replace", "--body-file", compactLongFile], env);
|
||||
assertCondition(compactUpdate.status === 0, "issue update non-dry-run compact success should succeed", compactUpdate.json ?? { stdout: compactUpdate.stdout, stderr: compactUpdate.stderr });
|
||||
assertCondition(compactUpdate.stdout.length < 20_000, "issue update compact success stdout should stay bounded for long bodies", { bytes: compactUpdate.stdout.length });
|
||||
assertCondition(!compactUpdate.stdout.includes("compact-success-line-0260"), "default issue update success stdout must not echo the full long body tail", { tail: compactUpdate.stdout.slice(-1000) });
|
||||
const compactUpdateData = dataOf(compactUpdate.json ?? {});
|
||||
const compactIssue = compactUpdateData.issue as JsonRecord;
|
||||
assertCondition(compactUpdateData.command === "issue update" && compactUpdateData.rest === true, "compact update should report REST success", compactUpdateData);
|
||||
assertCondition(!("body" in compactIssue), "default issue update success should omit issue.body", compactIssue);
|
||||
assertCondition(compactIssue.bodyOmitted === true && compactIssue.fullBodyIncluded === false, "compact issue summary should mark omitted full body", compactIssue);
|
||||
assertCondition(Number(compactIssue.bodyChars ?? 0) === compactLongBody.length, "compact issue summary should include bodyChars", compactIssue);
|
||||
assertCondition(typeof compactIssue.bodySha === "string" && String(compactIssue.bodySha).length === 64, "compact issue summary should include bodySha", compactIssue);
|
||||
assertCondition(String(compactIssue.bodyPreview ?? "").includes("compact-success-line-0001") && !String(compactIssue.bodyPreview ?? "").includes("compact-success-line-0260"), "compact issue summary should include only bounded preview", compactIssue);
|
||||
const compactConcurrency = compactUpdateData.concurrency as JsonRecord;
|
||||
assertCondition(compactConcurrency.checked === false && compactConcurrency.expectBodySha === null, "compact update should still report concurrency summary", compactConcurrency);
|
||||
const compactGuard = compactUpdateData.guard as JsonRecord;
|
||||
assertCondition(compactGuard.ok === true && typeof compactGuard.bodySha === "string", "compact update should keep guard/body sha summary", compactGuard);
|
||||
const compactDisclosure = compactUpdateData.disclosure as JsonRecord;
|
||||
assertCondition(compactDisclosure.bodyOmitted === true && compactDisclosure.fullBodyIncluded === false && compactDisclosure.defaultCompact === true, "compact update disclosure should be explicit", compactDisclosure);
|
||||
const compactCommands = compactUpdateData.readCommands as JsonRecord;
|
||||
assertCondition(String(compactCommands.body ?? "").includes("gh issue read 7 --repo pikasTech/HWLAB --json body"), "compact update should expose body read command", compactCommands);
|
||||
assertCondition(String(compactCommands.full ?? "").includes("--full") && String(compactCommands.raw ?? "").includes("--raw"), "compact update should expose full/raw drill-down", compactCommands);
|
||||
const compactUpdatePatchCount = mock.requests.slice(compactUpdateRequestCountBefore).filter((request) => request.method === "PATCH" && request.url === "/repos/pikasTech/HWLAB/issues/7").length;
|
||||
assertCondition(compactUpdatePatchCount === 1, "compact update should PATCH GitHub exactly once", { requests: mock.requests.slice(compactUpdateRequestCountBefore) });
|
||||
|
||||
const explicitFullBody = "# compact full body\n\nThis body is intentionally short enough to avoid global dump while still proving explicit disclosure.\n";
|
||||
const explicitFullFile = join(tmp, "explicit-full-body.md");
|
||||
writeFileSync(explicitFullFile, explicitFullBody, "utf8");
|
||||
const explicitFullUpdate = await runCli(["gh", "issue", "update", "7", "--repo", "pikasTech/HWLAB", "--mode", "replace", "--body-file", explicitFullFile, "--full"], env);
|
||||
assertCondition(explicitFullUpdate.status === 0, "issue update --full should succeed", explicitFullUpdate.json ?? { stdout: explicitFullUpdate.stdout, stderr: explicitFullUpdate.stderr });
|
||||
const explicitFullData = dataOf(explicitFullUpdate.json ?? {});
|
||||
const explicitFullIssue = explicitFullData.issue as JsonRecord;
|
||||
assertCondition(typeof explicitFullIssue.body === "string" && explicitFullIssue.body === explicitFullBody, "issue update --full should explicitly include the full body", explicitFullIssue);
|
||||
const explicitFullDisclosure = explicitFullData.disclosure as JsonRecord;
|
||||
assertCondition(explicitFullDisclosure.fullBodyIncluded === true && explicitFullDisclosure.explicitFullDisclosure === true, "issue update --full disclosure should mark full body inclusion", explicitFullDisclosure);
|
||||
|
||||
const commentCreate = await runCli(["gh", "issue", "comment", "create", "20", "--repo", "pikasTech/unidesk", "--body-file", appendFile], env);
|
||||
assertCondition(commentCreate.status === 0, "issue comment create should succeed", commentCreate.json ?? { stdout: commentCreate.stdout });
|
||||
@@ -1559,6 +1614,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"multiline Markdown and backticks are not polluted",
|
||||
"expect-updated-at stale write protection blocks PATCH",
|
||||
"issue update replace/append modes preserve Markdown",
|
||||
"issue update non-dry-run success defaults to compact output without full issue.body and exposes bodySha plus drill-down commands",
|
||||
"issue update --full explicitly includes full issue.body",
|
||||
"issue comment create/delete follows CRUD shape",
|
||||
"issue hard delete is structurally unsupported",
|
||||
],
|
||||
|
||||
+84
-13
@@ -1810,18 +1810,54 @@ function isGitHubError(value: unknown): value is GitHubErrorPayload {
|
||||
return typeof value === "object" && value !== null && (value as { ok?: unknown }).ok === false && "degradedReason" in value;
|
||||
}
|
||||
|
||||
function issueSummary(issue: GitHubIssue): Record<string, unknown> {
|
||||
function issueBodyReadCommands(repo: string, issueNumber: number): Record<string, string> {
|
||||
return {
|
||||
body: `bun scripts/cli.ts gh issue read ${issueNumber} --repo ${repo} --json body`,
|
||||
full: `bun scripts/cli.ts gh issue read ${issueNumber} --repo ${repo} --full`,
|
||||
raw: `bun scripts/cli.ts gh issue read ${issueNumber} --repo ${repo} --raw`,
|
||||
};
|
||||
}
|
||||
|
||||
function issueWriteDisclosure(options: GitHubOptions, repo: string, issueNumber: number, dryRun: boolean): Record<string, unknown> {
|
||||
const explicitFullDisclosure = options.raw || options.full;
|
||||
return {
|
||||
defaultCompact: dryRun || !explicitFullDisclosure,
|
||||
explicitFullDisclosure,
|
||||
fullBodyIncluded: explicitFullDisclosure && !dryRun,
|
||||
bodyOmitted: dryRun || !explicitFullDisclosure,
|
||||
dryRunBoundedPreview: dryRun,
|
||||
note: explicitFullDisclosure && !dryRun
|
||||
? "The returned issue object includes the full body because --full or --raw was explicitly requested."
|
||||
: "Default issue write output omits full issue.body; use readCommands.full/raw or gh issue read --json body when full text is needed.",
|
||||
readCommands: issueBodyReadCommands(repo, issueNumber),
|
||||
};
|
||||
}
|
||||
|
||||
function issueSummary(issue: GitHubIssue, options: { includeBody?: boolean; previewLineCount?: number } = {}): Record<string, unknown> {
|
||||
const body = issue.body ?? "";
|
||||
const includeBody = options.includeBody ?? true;
|
||||
const summary: Record<string, unknown> = {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
body: issue.body ?? "",
|
||||
state: issue.state,
|
||||
url: issue.html_url,
|
||||
author: issue.user?.login ?? null,
|
||||
createdAt: issue.created_at ?? null,
|
||||
updatedAt: issue.updated_at ?? null,
|
||||
commentCount: issue.comments ?? null,
|
||||
bodyChars: body.length,
|
||||
bodySha: bodySha(body),
|
||||
};
|
||||
if (includeBody) {
|
||||
summary.body = body;
|
||||
} else {
|
||||
summary.bodyOmitted = true;
|
||||
summary.fullBodyIncluded = false;
|
||||
summary.bodyPreview = preview(body);
|
||||
summary.bodyPreviewLines = previewLines(body, options.previewLineCount ?? 8);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
function labelSummary(label: string | { name?: string; color?: string; description?: string | null }): Record<string, unknown> {
|
||||
@@ -3388,6 +3424,8 @@ async function issueBoardRowMutation(
|
||||
return {
|
||||
...base,
|
||||
dryRun: true,
|
||||
disclosure: issueWriteDisclosure(options, repo, options.boardIssue, true),
|
||||
readCommands: issueBodyReadCommands(repo, options.boardIssue),
|
||||
wouldPatch: { issueNumber: options.boardIssue, bodySha: bodySha(planned.newBody), bodyChars: planned.newBody.length },
|
||||
note: !hasConcurrencyGuard
|
||||
? "Default dry-run because no concurrency expectation was supplied; no GitHub issue body was modified."
|
||||
@@ -3403,7 +3441,9 @@ async function issueBoardRowMutation(
|
||||
...base,
|
||||
dryRun: false,
|
||||
planned: false,
|
||||
issue: issueSummary(issue),
|
||||
issue: issueSummary(issue, { includeBody: options.raw || options.full }),
|
||||
disclosure: issueWriteDisclosure(options, repo, options.boardIssue, false),
|
||||
readCommands: issueBodyReadCommands(repo, options.boardIssue),
|
||||
concurrency: {
|
||||
checked: true,
|
||||
oldIssueUpdatedAt: boardIssue.updated_at ?? null,
|
||||
@@ -3543,6 +3583,8 @@ async function issueBoardRowUpdate(repo: string, token: string, issueNumber: num
|
||||
return {
|
||||
...base,
|
||||
dryRun: true,
|
||||
disclosure: issueWriteDisclosure(options, repo, options.boardIssue, true),
|
||||
readCommands: issueBodyReadCommands(repo, options.boardIssue),
|
||||
wouldPatch: { issueNumber: options.boardIssue, bodySha: bodySha(planned.newBody), bodyChars: planned.newBody.length },
|
||||
note: options.expectBodySha === undefined && options.expectUpdatedAt === undefined
|
||||
? "Default dry-run because no concurrency expectation was supplied; no GitHub issue body was modified."
|
||||
@@ -3558,7 +3600,9 @@ async function issueBoardRowUpdate(repo: string, token: string, issueNumber: num
|
||||
...base,
|
||||
dryRun: false,
|
||||
planned: false,
|
||||
issue: issueSummary(issue),
|
||||
issue: issueSummary(issue, { includeBody: options.raw || options.full }),
|
||||
disclosure: issueWriteDisclosure(options, repo, options.boardIssue, false),
|
||||
readCommands: issueBodyReadCommands(repo, options.boardIssue),
|
||||
concurrency: {
|
||||
checked: true,
|
||||
oldIssueUpdatedAt: boardIssue.updated_at ?? null,
|
||||
@@ -4926,6 +4970,8 @@ async function issueEdit(repo: string, token: string, issueNumber: number, optio
|
||||
issueNumber,
|
||||
mode: options.mode,
|
||||
...dryRunBody(repo, options.title, finalBody),
|
||||
disclosure: issueWriteDisclosure(options, repo, issueNumber, true),
|
||||
readCommands: issueBodyReadCommands(repo, issueNumber),
|
||||
guard,
|
||||
update: bodyUpdatePlan(commandName, repo, issueNumber, options.mode, body, { kind: "body-file", path: options.bodyFile ?? null }, oldIssue?.body ?? null),
|
||||
bodyOnlySafety: {
|
||||
@@ -4933,6 +4979,8 @@ async function issueEdit(repo: string, token: string, issueNumber: number, optio
|
||||
newBody: {
|
||||
bodyChars: finalBody.length,
|
||||
bodySha: bodySha(finalBody),
|
||||
bodyPreview: preview(finalBody),
|
||||
bodyPreviewLines: previewLines(finalBody),
|
||||
},
|
||||
},
|
||||
concurrency: {
|
||||
@@ -4941,7 +4989,14 @@ async function issueEdit(repo: string, token: string, issueNumber: number, optio
|
||||
expectBodySha: options.expectBodySha ?? null,
|
||||
note: "non-dry-run checks --expect-updated-at and --expect-body-sha against the current GitHub issue before PATCH",
|
||||
},
|
||||
wouldPatch: { title: options.title ?? null, bodyFromFile: options.bodyFile },
|
||||
wouldPatch: {
|
||||
issueNumber,
|
||||
title: options.title ?? null,
|
||||
bodyFromFile: options.bodyFile,
|
||||
mode: options.mode,
|
||||
bodyChars: finalBody.length,
|
||||
bodySha: bodySha(finalBody),
|
||||
},
|
||||
...(options.notifyClaudeQqBriefDiff
|
||||
? {
|
||||
commanderBriefNotification: commanderBriefNotificationPlan(issueNumber, finalBody, dryRunDiff ?? commanderBriefDiff("", finalBody), claudeQqConfig),
|
||||
@@ -4962,7 +5017,20 @@ async function issueEdit(repo: string, token: string, issueNumber: number, optio
|
||||
const concurrency = oldIssue === null
|
||||
? { checked: false, expectUpdatedAt: options.expectUpdatedAt ?? null, expectBodySha: options.expectBodySha ?? null }
|
||||
: { checked: true, oldIssueUpdatedAt: oldIssue.updated_at ?? null, oldBodySha: bodySha(oldIssue.body ?? ""), expectUpdatedAt: options.expectUpdatedAt ?? null, expectBodySha: options.expectBodySha ?? null };
|
||||
if (!options.notifyClaudeQqBriefDiff) return { ok: true, command: commandName, repo, issue: issueSummary(issue), mode: options.mode, guard, concurrency, rest: true };
|
||||
if (!options.notifyClaudeQqBriefDiff) {
|
||||
return {
|
||||
ok: true,
|
||||
command: commandName,
|
||||
repo,
|
||||
issue: issueSummary(issue, { includeBody: options.raw || options.full }),
|
||||
mode: options.mode,
|
||||
guard,
|
||||
concurrency,
|
||||
disclosure: issueWriteDisclosure(options, repo, issueNumber, false),
|
||||
readCommands: issueBodyReadCommands(repo, issueNumber),
|
||||
rest: true,
|
||||
};
|
||||
}
|
||||
|
||||
const diff = briefDiff ?? commanderBriefDiff(oldIssue?.body ?? "", finalBody);
|
||||
const claudeqq = diff.ok
|
||||
@@ -4978,9 +5046,11 @@ async function issueEdit(repo: string, token: string, issueNumber: number, optio
|
||||
ok: true,
|
||||
command: commandName,
|
||||
repo,
|
||||
issue: issueSummary(issue),
|
||||
issue: issueSummary(issue, { includeBody: options.raw || options.full }),
|
||||
guard,
|
||||
concurrency,
|
||||
disclosure: issueWriteDisclosure(options, repo, issueNumber, false),
|
||||
readCommands: issueBodyReadCommands(repo, issueNumber),
|
||||
rest: true,
|
||||
commanderBriefNotification: {
|
||||
issueNumber,
|
||||
@@ -5608,8 +5678,8 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh issue read <number|owner/repo#number> [--repo owner/name] [--json body,title,state,comments] [--raw|--full]",
|
||||
"bun scripts/cli.ts gh issue view <number|owner/repo#number> [--repo owner/name] [--raw|--full] [compatibility alias for issue read]",
|
||||
"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] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256] [--body-profile auto|code-queue-board|commander-brief] [--allow-short-body]",
|
||||
"bun scripts/cli.ts gh issue edit <number> --body-file <file> [compat alias for issue update --mode replace]",
|
||||
"bun scripts/cli.ts gh issue update <number> --mode replace|append --body-file <file> [--title title] [--repo owner/name] [--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> [--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> [--repo owner/name] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue comment delete <commentId> [--repo owner/name] [--dry-run]",
|
||||
@@ -5649,14 +5719,14 @@ export function ghHelp(): unknown {
|
||||
"issue list defaults to --state open and bounded --limit 30; supported --json fields are number,title,state,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 read is the canonical read path; view remains a compatibility alias. Read/view accept owner/repo#number shorthand and derive --repo unless an explicit conflicting --repo is supplied, which fails structurally with suggested commands. Read supports 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 read/view and gh pr read/view. They request the full supported read/view JSON field set while keeping default command output structured JSON.",
|
||||
"--raw and --full are explicit full-disclosure aliases for gh issue read/view/update/edit and gh pr 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.",
|
||||
"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 repeatable --label values and comma-separated labels; 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.",
|
||||
"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 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 old/new body length slots, body SHA, required heading checks, literal \\n detection, and shell-pollution signals. Non-dry-run can use --expect-updated-at or --expect-body-sha for stale-cache protection.",
|
||||
"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 can use --expect-updated-at or --expect-body-sha for stale-cache protection.",
|
||||
"Issue body stdin is intentionally unsupported in this CLI; write generated Markdown to a file and pass --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 Markdown writes intentionally use --body-file only; PR edit/update also accepts --body-file - for stdin when a runner already has reviewed Markdown on stdin.",
|
||||
@@ -5707,12 +5777,13 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
return validationError(command, options.repo, "--json field selection is only supported by gh issue read/view/list and gh pr read/view/list");
|
||||
}
|
||||
}
|
||||
if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && isIssueReadCommand(sub)) || top === "preflight" || (top === "pr" && (isPrReadCommand(sub) || sub === "preflight" || sub === "closeout")))) {
|
||||
if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && (isIssueReadCommand(sub) || sub === "update" || sub === "edit")) || top === "preflight" || (top === "pr" && (isPrReadCommand(sub) || sub === "preflight" || sub === "closeout")))) {
|
||||
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 read/view, gh pr read/view, and gh pr preflight/closeout.", {
|
||||
return validationError(command, options.repo, "--raw and --full are explicit full-disclosure aliases only for gh issue read/view/update/edit, gh pr read/view, and gh pr preflight/closeout.", {
|
||||
supportedCommands: [
|
||||
"bun scripts/cli.ts gh issue read owner/name#<number> --raw",
|
||||
"bun scripts/cli.ts gh issue read <number> --repo owner/name --json body,title,state,comments",
|
||||
"bun scripts/cli.ts gh issue update <number> --repo owner/name --body-file <file> --expect-body-sha <sha> --full",
|
||||
"bun scripts/cli.ts gh pr read owner/name#<number> --raw",
|
||||
`bun scripts/cli.ts gh pr read <number> --repo owner/name --json ${readViewSupportedJsonFields("pr")}`,
|
||||
"bun scripts/cli.ts gh pr preflight <number> --repo owner/name --full",
|
||||
|
||||
Reference in New Issue
Block a user