feat(FN-4777): complete Step 1 — add GitHub issue search API

Fusion-Task-Id: FN-4777
Fusion-Task-Lineage: ed00f639-c928-4c14-be1f-2b43db6022c7
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 12:27:49 -07:00
committed by gsxdsm
parent 6dde53ff0d
commit cfb44af446

View File

@@ -2211,6 +2211,107 @@ export class GitHubClient {
.slice(0, limit);
}
async searchIssues(
owner: string,
repo: string,
query: string,
options?: { limit?: number; state?: "open" | "closed" | "all" },
): Promise<Array<{
number: number;
title: string;
body: string | null;
html_url: string;
state: "open" | "closed";
updatedAt?: string;
}>> {
const requestedLimit = options?.limit ?? 10;
const limit = Math.min(Math.max(1, requestedLimit), 50);
const state = options?.state ?? "all";
if (this.hasGhAuth()) {
try {
const issues = await runGhJsonAsync<Array<{
number: number;
title: string;
body: string | null;
url: string;
state: "OPEN" | "CLOSED";
updatedAt: string;
isPullRequest?: boolean;
}>>([
"search",
"issues",
"--repo",
`${owner}/${repo}`,
"--state",
state,
"--limit",
String(limit),
"--json",
"number,title,body,url,state,updatedAt,isPullRequest",
"--",
query,
]);
return issues
.filter((issue) => !issue.isPullRequest)
.map((issue) => ({
number: issue.number,
title: issue.title,
body: issue.body,
html_url: issue.url,
state: this.mapGhIssueState(issue.state),
updatedAt: issue.updatedAt,
}));
} catch (err) {
if (!this.token) {
throw new Error(getGhErrorMessage(err));
}
}
}
if (!this.token) {
throw new Error("GitHub CLI (gh) is not available or not authenticated, and no GITHUB_TOKEN provided. Run 'gh auth login' to authenticate.");
}
const stateQualifier = state === "all" ? "" : ` state:${state}`;
const q = `${query} repo:${owner}/${repo}${stateQualifier} is:issue`;
const params = new URLSearchParams();
params.set("q", q);
params.set("per_page", String(Math.min(limit, 100)));
const url = `${this.baseUrl}/search/issues?${params.toString()}`;
const response = await fetch(url, { headers: this.buildHeaders() });
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const data = (await response.json()) as {
items?: Array<{
number: number;
title: string;
body: string | null;
html_url: string;
state: string;
updated_at: string;
pull_request?: unknown;
}>;
};
return (data.items ?? [])
.filter((issue) => !issue.pull_request)
.map((issue) => ({
number: issue.number,
title: issue.title,
body: issue.body,
html_url: issue.html_url,
state: this.mapIssueState(issue.state) ?? "open",
updatedAt: issue.updated_at,
}))
.slice(0, limit);
}
/**
* Fetch a single issue by number.
* Uses gh CLI if available, otherwise falls back to REST API.