feat: add bounded GitHub Actions diagnostics
This commit is contained in:
@@ -54,6 +54,9 @@ bun scripts/cli.ts gh issue comments <number> --repo pikasTech/unidesk --limit 8
|
||||
bun scripts/cli.ts gh issue create --repo pikasTech/unidesk --title "标题" --body-stdin
|
||||
bun scripts/cli.ts gh issue comment create <number> --repo pikasTech/unidesk --body-stdin
|
||||
bun scripts/cli.ts gh pr list --repo pikasTech/unidesk --state all --limit 10
|
||||
bun scripts/cli.ts gh run list --repo owner/name --limit 10
|
||||
bun scripts/cli.ts gh run view <run-id> --repo owner/name
|
||||
bun scripts/cli.ts gh run logs <run-id> --job <job-id> --repo owner/name
|
||||
trans gh:/pikasTech/unidesk/pr/<number> cat
|
||||
trans gh:/pikasTech/unidesk/pr/<number> rg <pattern>
|
||||
bun scripts/cli.ts gh pr review-plan <number> --repo pikasTech/unidesk
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { redactAndTailActionLog } from "./gh/actions-runs";
|
||||
import { withGhDefaultRendered } from "./gh/default-render";
|
||||
|
||||
describe("GitHub Actions run diagnostics", () => {
|
||||
test("redacts credentials and keeps a bounded tail", () => {
|
||||
const lines = Array.from({ length: 200 }, (_, index) => `line-${index}`);
|
||||
lines.push("TOKEN=secret-value");
|
||||
lines.push("Authorization: Bearer abc.def.ghi");
|
||||
lines.push("clone https://user:password@example.com/repo.git failed");
|
||||
|
||||
const tail = redactAndTailActionLog(lines.join("\n"), 5, 1000);
|
||||
|
||||
expect(tail).not.toContain("secret-value");
|
||||
expect(tail).not.toContain("abc.def.ghi");
|
||||
expect(tail).not.toContain("user:password");
|
||||
expect(tail).toContain("<redacted>");
|
||||
expect(tail.split("\n")).toHaveLength(5);
|
||||
});
|
||||
|
||||
test("applies the character bound after line selection", () => {
|
||||
expect(redactAndTailActionLog("a".repeat(100), 10, 12)).toBe("a".repeat(12));
|
||||
});
|
||||
|
||||
test("renders startup failures and bounded logs without hiding evidence", () => {
|
||||
const startup = withGhDefaultRendered(["run", "view", "42"], {
|
||||
ok: true,
|
||||
command: "run view",
|
||||
repo: "owner/repo",
|
||||
run: { id: 42, workflow: "code scan", status: "completed", conclusion: "failure", commit: "abcdef", title: "push" },
|
||||
jobs: [],
|
||||
startupFailure: true,
|
||||
diagnosis: "workflow failed before any job was created",
|
||||
next: [],
|
||||
}) as { renderedText: string };
|
||||
expect(startup.renderedText).toContain("startupFailure=true");
|
||||
expect(startup.renderedText).toContain("workflow failed before any job was created");
|
||||
|
||||
const logs = withGhDefaultRendered(["run", "logs", "42"], {
|
||||
ok: true,
|
||||
command: "run logs",
|
||||
repo: "owner/repo",
|
||||
runId: 42,
|
||||
job: { id: 7, name: "test", status: "completed", conclusion: "failure" },
|
||||
logTail: "compile failed at source.c:10",
|
||||
lineLimit: 80,
|
||||
charLimit: 7000,
|
||||
truncated: true,
|
||||
}) as { renderedText: string };
|
||||
expect(logs.renderedText).toContain("compile failed at source.c:10");
|
||||
expect(logs.renderedText).toContain("valuesPrinted=false");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
import { commandError, githubRequest, githubTextRequest, isGitHubError, validationError } from "./client";
|
||||
|
||||
interface WorkflowRun {
|
||||
id: number;
|
||||
name: string;
|
||||
display_title: string;
|
||||
event: string;
|
||||
status: string;
|
||||
conclusion: string | null;
|
||||
head_branch: string;
|
||||
head_sha: string;
|
||||
run_number: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
interface WorkflowJob {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
conclusion: string | null;
|
||||
started_at: string | null;
|
||||
completed_at: string | null;
|
||||
html_url: string;
|
||||
steps?: Array<{ name: string; status: string; conclusion: string | null; number: number }>;
|
||||
}
|
||||
|
||||
interface WorkflowRunsResponse { total_count: number; workflow_runs: WorkflowRun[] }
|
||||
interface WorkflowJobsResponse { total_count: number; jobs: WorkflowJob[] }
|
||||
|
||||
function splitRepo(repo: string): [string, string] | null {
|
||||
const [owner, name, extra] = repo.split("/");
|
||||
return owner && name && extra === undefined ? [owner, name] : null;
|
||||
}
|
||||
|
||||
function compactRun(run: WorkflowRun): Record<string, unknown> {
|
||||
return {
|
||||
id: run.id,
|
||||
workflow: run.name,
|
||||
title: run.display_title,
|
||||
event: run.event,
|
||||
status: run.status,
|
||||
conclusion: run.conclusion,
|
||||
branch: run.head_branch,
|
||||
commit: run.head_sha,
|
||||
number: run.run_number,
|
||||
createdAt: run.created_at,
|
||||
updatedAt: run.updated_at,
|
||||
url: run.html_url,
|
||||
};
|
||||
}
|
||||
|
||||
function compactJob(job: WorkflowJob): Record<string, unknown> {
|
||||
return {
|
||||
id: job.id,
|
||||
name: job.name,
|
||||
status: job.status,
|
||||
conclusion: job.conclusion,
|
||||
startedAt: job.started_at,
|
||||
completedAt: job.completed_at,
|
||||
url: job.html_url,
|
||||
steps: (job.steps ?? []).map((step) => ({
|
||||
number: step.number,
|
||||
name: step.name,
|
||||
status: step.status,
|
||||
conclusion: step.conclusion,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function redactAndTailActionLog(text: string, lineLimit = 80, charLimit = 7000): string {
|
||||
const redacted = text
|
||||
.replace(/(https?:\/\/)[^/@\s]+@/giu, "$1<redacted>@")
|
||||
.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/giu, "$1<redacted>")
|
||||
.replace(/((?:PASSWORD|SECRET|TOKEN|API[_-]?KEY)\s*[=:]\s*)\S+/giu, "$1<redacted>");
|
||||
return redacted.split(/\r?\n/u).slice(-lineLimit).join("\n").slice(-charLimit);
|
||||
}
|
||||
|
||||
export async function actionRunList(repo: string, token: string, limit: number): Promise<Record<string, unknown>> {
|
||||
const parts = splitRepo(repo);
|
||||
if (parts === null) return validationError("run list", repo, "--repo must use owner/name format");
|
||||
const [owner, name] = parts;
|
||||
const result = await githubRequest<WorkflowRunsResponse>(token, "GET", `/repos/${owner}/${name}/actions/runs?per_page=${Math.min(limit, 100)}`);
|
||||
if (isGitHubError(result)) return commandError("run list", repo, result, { mutation: false });
|
||||
const runs = result.workflow_runs.map(compactRun);
|
||||
return {
|
||||
ok: true,
|
||||
command: "run list",
|
||||
repo,
|
||||
mutation: false,
|
||||
runs,
|
||||
count: runs.length,
|
||||
total: result.total_count,
|
||||
next: runs.length > 0 ? { view: `bun scripts/cli.ts gh run view ${String(result.workflow_runs[0]?.id)} --repo ${repo}` } : null,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadRunAndJobs(repo: string, token: string, runId: number): Promise<{ run: WorkflowRun; jobs: WorkflowJob[] } | Record<string, unknown>> {
|
||||
const parts = splitRepo(repo);
|
||||
if (parts === null) return validationError("run view", repo, "--repo must use owner/name format");
|
||||
const [owner, name] = parts;
|
||||
const run = await githubRequest<WorkflowRun>(token, "GET", `/repos/${owner}/${name}/actions/runs/${runId}`);
|
||||
if (isGitHubError(run)) return commandError("run view", repo, run, { runId, mutation: false });
|
||||
const jobs = await githubRequest<WorkflowJobsResponse>(token, "GET", `/repos/${owner}/${name}/actions/runs/${runId}/jobs?per_page=100`);
|
||||
if (isGitHubError(jobs)) return commandError("run view", repo, jobs, { runId, mutation: false });
|
||||
return { run, jobs: jobs.jobs };
|
||||
}
|
||||
|
||||
export async function actionRunView(repo: string, token: string, runId: number): Promise<Record<string, unknown>> {
|
||||
const loaded = await loadRunAndJobs(repo, token, runId);
|
||||
if (!("run" in loaded) || !("jobs" in loaded)) return loaded;
|
||||
const failedJobs = loaded.jobs.filter((job) => job.conclusion === "failure");
|
||||
const startupFailure = loaded.run.conclusion === "failure" && loaded.jobs.length === 0;
|
||||
return {
|
||||
ok: true,
|
||||
command: "run view",
|
||||
repo,
|
||||
mutation: false,
|
||||
run: compactRun(loaded.run),
|
||||
jobs: loaded.jobs.map(compactJob),
|
||||
failedJobIds: failedJobs.map((job) => job.id),
|
||||
startupFailure,
|
||||
diagnosis: startupFailure
|
||||
? "workflow failed before any job was created; inspect workflow syntax, triggers, permissions, and repository Actions settings"
|
||||
: null,
|
||||
next: failedJobs.map((job) => `bun scripts/cli.ts gh run logs ${runId} --job ${job.id} --repo ${repo}`),
|
||||
};
|
||||
}
|
||||
|
||||
export async function actionRunLogs(repo: string, token: string, runId: number, jobId: number): Promise<Record<string, unknown>> {
|
||||
const loaded = await loadRunAndJobs(repo, token, runId);
|
||||
if (!("run" in loaded) || !("jobs" in loaded)) return loaded;
|
||||
const job = loaded.jobs.find((candidate) => candidate.id === jobId);
|
||||
if (job === undefined) return validationError("run logs", repo, `job ${jobId} does not belong to run ${runId}`, { runId, jobId });
|
||||
const parts = splitRepo(repo);
|
||||
if (parts === null) return validationError("run logs", repo, "--repo must use owner/name format");
|
||||
const [owner, name] = parts;
|
||||
const log = await githubTextRequest(token, `/repos/${owner}/${name}/actions/jobs/${jobId}/logs`);
|
||||
if (isGitHubError(log)) return commandError("run logs", repo, log, { runId, jobId, mutation: false });
|
||||
const logTail = redactAndTailActionLog(log);
|
||||
return {
|
||||
ok: true,
|
||||
command: "run logs",
|
||||
repo,
|
||||
mutation: false,
|
||||
runId,
|
||||
job: compactJob(job),
|
||||
logTail,
|
||||
lineLimit: 80,
|
||||
charLimit: 7000,
|
||||
truncated: logTail.length < log.length,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
@@ -269,6 +269,44 @@ export async function githubRequest<T>(
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
export async function githubTextRequest(
|
||||
token: string,
|
||||
path: string,
|
||||
): Promise<string | GitHubErrorPayload> {
|
||||
let response: Response;
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
||||
try {
|
||||
response = await fetch(`${GITHUB_API}${path}`, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"User-Agent": USER_AGENT,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const classified = classifyGitHubFetchFailure(error);
|
||||
return errorPayload(classified.reason, classified.message, {
|
||||
request: { method: "GET", path },
|
||||
details: classified.details,
|
||||
retryable: classified.retryable,
|
||||
commanderAction: classified.commanderAction,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
return errorPayload(classifyHttpStatus(response.status, response.statusText, path, response), response.statusText, {
|
||||
status: response.status,
|
||||
request: { method: "GET", path },
|
||||
});
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export async function githubGraphqlRequest<T>(
|
||||
token: string,
|
||||
query: string,
|
||||
|
||||
@@ -50,6 +50,9 @@ function renderGhDefaultText(result: GitHubCommandResult): string {
|
||||
if (command === "issue comments") return renderIssueComments(result);
|
||||
if (isPrReadResult(result)) return renderPrView(result);
|
||||
if (command === "pr list") return renderPrList(result);
|
||||
if (command === "run list") return renderRunList(result);
|
||||
if (command === "run view") return renderRunView(result);
|
||||
if (command === "run logs") return renderRunLogs(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);
|
||||
@@ -683,6 +686,71 @@ function renderGenericResult(result: GitHubCommandResult): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderRunList(result: GitHubCommandResult): string {
|
||||
const runs = arrayOfRecords(result.runs);
|
||||
const rows = runs.map((run) => [
|
||||
ghText(run.id),
|
||||
ghShort(ghText(run.workflow), 28),
|
||||
ghText(run.status),
|
||||
ghText(run.conclusion),
|
||||
ghShort(ghText(run.branch), 20),
|
||||
ghShort(ghText(run.commit), 10),
|
||||
ghShort(ghText(run.title), 54),
|
||||
]);
|
||||
return [
|
||||
"gh run list (observed)",
|
||||
"",
|
||||
ghTable(["RUN", "WORKFLOW", "STATUS", "CONCLUSION", "BRANCH", "COMMIT", "TITLE"], rows),
|
||||
"",
|
||||
"Summary:",
|
||||
` repo=${ghText(result.repo)} count=${ghText(result.count)} total=${ghText(result.total)} mutation=false`,
|
||||
"",
|
||||
"Next:",
|
||||
` bun scripts/cli.ts gh run view <run-id> --repo ${ghText(result.repo)}`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderRunView(result: GitHubCommandResult): string {
|
||||
const run = record(result.run);
|
||||
const jobs = arrayOfRecords(result.jobs);
|
||||
const jobRows = jobs.map((job) => [ghText(job.id), ghShort(ghText(job.name), 36), ghText(job.status), ghText(job.conclusion)]);
|
||||
const stepRows = jobs.flatMap((job) => arrayOfRecords(job.steps)
|
||||
.filter((step) => step.conclusion === "failure" || step.status === "in_progress")
|
||||
.map((step) => [ghShort(ghText(job.name), 24), ghText(step.number), ghShort(ghText(step.name), 54), ghText(step.status), ghText(step.conclusion)]));
|
||||
const lines = [
|
||||
"gh run view (observed)",
|
||||
"",
|
||||
ghTable(["RUN", "WORKFLOW", "STATUS", "CONCLUSION", "COMMIT", "TITLE"], [[
|
||||
ghText(run.id), ghShort(ghText(run.workflow), 28), ghText(run.status), ghText(run.conclusion), ghShort(ghText(run.commit), 10), ghShort(ghText(run.title), 54),
|
||||
]]),
|
||||
"",
|
||||
ghTable(["JOB", "NAME", "STATUS", "CONCLUSION"], jobRows),
|
||||
];
|
||||
if (stepRows.length > 0) lines.push("", ghTable(["JOB", "STEP", "NAME", "STATUS", "CONCLUSION"], stepRows));
|
||||
if (result.startupFailure === true) lines.push("", "Diagnosis:", ` ${ghText(result.diagnosis)}`);
|
||||
const next = Array.isArray(result.next) ? result.next.filter((value): value is string => typeof value === "string") : [];
|
||||
if (next.length > 0) lines.push("", "Next:", ...next.map((command) => ` ${command}`));
|
||||
lines.push("", "Summary:", ` repo=${ghText(result.repo)} jobs=${jobs.length} startupFailure=${ghText(result.startupFailure)} mutation=false`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function renderRunLogs(result: GitHubCommandResult): string {
|
||||
const job = record(result.job);
|
||||
return [
|
||||
"gh run logs (observed)",
|
||||
"",
|
||||
`Run ${ghText(result.runId)} / job ${ghText(job.id)} (${ghText(job.name)})`,
|
||||
"",
|
||||
typeof result.logTail === "string" && result.logTail.length > 0 ? result.logTail : "<no log lines returned>",
|
||||
"",
|
||||
"Summary:",
|
||||
` repo=${ghText(result.repo)} status=${ghText(job.status)} conclusion=${ghText(job.conclusion)} truncated=${ghText(result.truncated)} mutation=false`,
|
||||
"",
|
||||
"Disclosure:",
|
||||
` bounded redacted tail: lines<=${ghText(result.lineLimit)} chars<=${ghText(result.charLimit)} valuesPrinted=false`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return isRecord(value) ? value : {};
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh issue board-row delete <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 [--dry-run] [--expect-body-sha sha256]",
|
||||
"bun scripts/cli.ts gh preflight <prNumber|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [compatibility alias for gh pr preflight]",
|
||||
"bun scripts/cli.ts gh pr list [owner/repo] [--repo owner/name] [--state open|closed|all] [--limit N] [--json number,title,state,url,updatedAt,createdAt,author,head,base,draft]",
|
||||
"bun scripts/cli.ts gh run list --repo owner/name [--limit N]",
|
||||
"bun scripts/cli.ts gh run view <run-id> --repo owner/name",
|
||||
"bun scripts/cli.ts gh run logs <run-id> --job <job-id> --repo owner/name",
|
||||
"bun scripts/cli.ts gh pr files <number> [--repo owner/name] [--number N compat] [--limit N] [number may appear before or after options]",
|
||||
"bun scripts/cli.ts gh pr diff <number> --stat [--repo owner/name] [--number N compat] [--limit N] [number may appear before or after options; compatibility alias for pr files; no raw diff]",
|
||||
"bun scripts/cli.ts gh pr review-plan <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--limit N] [bounded changed-file index plus per-file patch drill-down commands]",
|
||||
|
||||
+22
-1
@@ -2,6 +2,7 @@
|
||||
// Moved mechanically from scripts/src/gh.ts:8348-8845.
|
||||
|
||||
import { issueAttachmentDownload, issueAttachmentList } from "./attachments";
|
||||
import { actionRunList, actionRunLogs, actionRunView } from "./actions-runs";
|
||||
import { resolveToken } from "./auth-and-safety";
|
||||
import { authStatus, prFiles, prList, prRead, prView } from "./auth-pr-read";
|
||||
import { issueBoardAudit } from "./board-audit";
|
||||
@@ -58,7 +59,7 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
const isIssueCommentReadCommand = top === "issue" && sub === "comment" && (third === "view" || third === "read");
|
||||
const isPrCommentReadCommand = top === "pr" && sub === "comment" && (third === "view" || third === "read");
|
||||
const isPrDiffFileCommand = top === "pr" && sub === "diff" && optionWasProvided(args, "--file");
|
||||
if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && (isIssueReadCommand(sub) || sub === "comments" || sub === "list" || sub === "update" || sub === "edit" || sub === "patch")) || isIssueCommentReadCommand || top === "preflight" || (top === "pr" && (isPrReadCommand(sub) || sub === "list" || sub === "preflight" || sub === "closeout")) || isPrCommentReadCommand || isPrDiffFileCommand)) {
|
||||
if ((optionWasProvided(args, "--raw") || optionWasProvided(args, "--full")) && !((top === "issue" && (isIssueReadCommand(sub) || sub === "comments" || sub === "list" || sub === "update" || sub === "edit" || sub === "patch")) || isIssueCommentReadCommand || top === "preflight" || top === "run" || (top === "pr" && (isPrReadCommand(sub) || sub === "list" || sub === "preflight" || sub === "closeout")) || isPrCommentReadCommand || isPrDiffFileCommand)) {
|
||||
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 list/read/view/comments/update/edit/patch/comment view, gh pr list/read/view/comment view, gh pr preflight/closeout, and gh pr diff --file.", {
|
||||
supportedCommands: [
|
||||
@@ -111,6 +112,9 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
],
|
||||
});
|
||||
}
|
||||
if (optionWasProvided(args, "--job") && !(top === "run" && sub === "logs")) {
|
||||
return validationError([top, sub].filter(Boolean).join(" "), options.repo, "--job is only supported by gh run logs <run-id> --job <job-id>");
|
||||
}
|
||||
if (optionWasProvided(args, "--number") && !allowsNumberTargetAlias(top, sub, third)) {
|
||||
const command = [top, sub].filter((value): value is string => value !== undefined).join(" ") || "gh";
|
||||
const standardViewCommand = top === "issue" || top === "pr" ? `gh ${top} view` : "gh issue/pr view";
|
||||
@@ -220,6 +224,23 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
|
||||
if (top === "auth" && sub === "status") return authStatus(options.repo);
|
||||
|
||||
if (top === "run") {
|
||||
const commandName = `run ${sub ?? ""}`.trim();
|
||||
if (sub !== "list" && sub !== "view" && sub !== "logs") {
|
||||
return unsupportedCommand(commandName, options.repo, "run supported commands are list, view, and logs.");
|
||||
}
|
||||
const { token, probe } = resolveToken(options.repo, true);
|
||||
const missing = authRequired(options.repo, commandName, probe);
|
||||
if (missing !== null || token === null) return missing ?? authRequired(options.repo, commandName, { present: false, source: null, ghFallbackAttempted: true });
|
||||
if (sub === "list") return actionRunList(options.repo, token, options.limit);
|
||||
const runId = Number(third);
|
||||
if (!Number.isSafeInteger(runId) || runId <= 0) return validationError(commandName, options.repo, `${commandName} requires a positive positional run id`);
|
||||
if (sub === "view") return actionRunView(options.repo, token, runId);
|
||||
const jobId = Number(optionValue(args, "--job"));
|
||||
if (!Number.isSafeInteger(jobId) || jobId <= 0) return validationError(commandName, options.repo, "run logs requires --job <positive-job-id>");
|
||||
return actionRunLogs(options.repo, token, runId, jobId);
|
||||
}
|
||||
|
||||
if (top === "repo") {
|
||||
if (sub !== "view" && sub !== "create") {
|
||||
return unsupportedCommand(`repo ${sub ?? ""}`.trim(), options.repo, "repo supported commands are view and create.", {
|
||||
|
||||
@@ -97,6 +97,7 @@ export const GH_VALUE_OPTIONS = new Set([
|
||||
"--tasks", "--summary", "--focus", "--validation", "--progress", "--number", "--pr",
|
||||
"--search", "--title-prefix", "--inactive-hours", "--comment", "--comment-file", "--description",
|
||||
"--attachment", "--output", "--file", "--hunk", "--sync-node",
|
||||
"--job",
|
||||
]);
|
||||
|
||||
export const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--body-patch-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--keep-branch", "--skip-local-closeout", "--private", "--public", "--auto-init"]);
|
||||
|
||||
Reference in New Issue
Block a user