Files
fusion/packages/dashboard/app/api/git/github-import.ts
gsxdsm cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00

205 lines
7.5 KiB
TypeScript

/**
* FNXC:CodeOrganization 2026-07-20-10:00:
* GitHub issue/PR import client API peeled from legacy.ts.
*/
import type { Task } from "@fusion/core";
import { api } from "../client/client.js";
import { withProjectId } from "../client/health.js";
// --- GitHub Import API ---
/** GitHub issue returned by the fetch endpoint */
/*
FNXC:GitHubImport 2026-06-22-18:30:
The Import Tasks preview pane renders the FULL issue (full body + metadata), so the list response carries the complete body plus author/state.
The GitHub issue-list endpoint already returns the full (untruncated) `body`; no per-item detail fetch is needed. `author`/`state` are surfaced for the preview metadata row.
*/
export interface GitHubIssue {
number: number;
title: string;
body: string | null;
html_url: string;
labels: Array<{ name: string }>;
state?: "open" | "closed";
author?: string | null;
}
/** Fetch open GitHub issues from a repository */
export function apiFetchGitHubIssues(
owner: string,
repo: string,
limit?: number,
labels?: string[]
): Promise<GitHubIssue[]> {
return api<GitHubIssue[]>("/github/issues/fetch", {
method: "POST",
body: JSON.stringify({ owner, repo, limit, labels }),
});
}
/** Import a specific GitHub issue as a fn task */
/*
FNXC:GitHubImportTranslate 2026-07-15-14:10:
`targetLocale` forwards the panel's ACTIVE locale so an imported task carries the same translation the operator previewed.
The server also falls back to the global `language` setting, so this argument is not load-bearing for the common case — it exists for the one case the server cannot know: a surface whose locale was browser-detected while global `language` is unset (PR #2141 review, P1).
*/
export function apiImportGitHubIssue(owner: string, repo: string, issueNumber: number, projectId?: string, targetLocale?: string): Promise<Task> {
return api<Task>(withProjectId("/github/issues/import", projectId), {
method: "POST",
body: JSON.stringify({ owner, repo, issueNumber, ...(targetLocale ? { targetLocale } : {}) }),
});
}
/** Result of a batch import operation for a single issue */
export interface BatchImportResult {
issueNumber: number;
success: boolean;
taskId?: string;
error?: string;
skipped?: boolean;
retryAfter?: number;
}
/** Batch import multiple GitHub issues as fn tasks with throttling */
export function apiBatchImportGitHubIssues(
owner: string,
repo: string,
issueNumbers: number[],
delayMs?: number,
projectId?: string,
/** See apiImportGitHubIssue: batch import must carry translations identically. */
targetLocale?: string,
): Promise<{ results: BatchImportResult[] }> {
return api<{ results: BatchImportResult[] }>(withProjectId("/github/issues/batch-import", projectId), {
method: "POST",
body: JSON.stringify({ owner, repo, issueNumbers, delayMs, ...(targetLocale ? { targetLocale } : {}) }),
});
}
// --- GitHub Pull Request Import API ---
/*
FNXC:GitHubImport 2026-06-22-18:30:
The PR-list endpoint already returns the full (untruncated) `body`; the import preview renders it in full with no per-item detail fetch. `state`/`author` surface PR metadata in the preview.
*/
export interface GitHubPull {
number: number;
title: string;
body: string | null;
html_url: string;
headBranch: string;
baseBranch: string;
state?: "open" | "closed" | "merged";
author?: string | null;
}
/** Fetch open GitHub pull requests from a repository */
export function apiFetchGitHubPulls(
owner: string,
repo: string,
limit?: number
): Promise<GitHubPull[]> {
return api<GitHubPull[]>("/github/pulls/fetch", {
method: "POST",
body: JSON.stringify({ owner, repo, limit }),
});
}
/*
FNXC:GitHubImport 2026-06-23-01:00:
Per-PR detail for the Import Tasks PR preview pane. `gh pr list` (apiFetchGitHubPulls) returns only comment COUNT + no per-check status, so the preview fetches the FULL comment thread + per-check status ON SELECTION via this client fn (never for the whole list — too expensive).
`status` is the gh CheckRun status (queued/in_progress/completed) or StatusContext state; `conclusion` (success/failure/neutral/...) is present once a check completes.
*/
/*
FNXC:GitHubImport 2026-06-23-03:30:
Comment shape carries `authorAvatarUrl?` (optional, backward-compatible) and `authorIsBot` so the preview renders an avatar + human/bot badge per comment. `authorIsBot` is derived server-side (author type is a GitHub Bot OR login ends in `[bot]`); `authorAvatarUrl` is omitted for bots whose synthetic login does not resolve to a real avatar.
*/
export interface GitHubCommentDetail {
author: string;
body: string;
createdAt: string;
authorAvatarUrl?: string;
authorIsBot: boolean;
}
export interface GitHubPullDetail {
comments: GitHubCommentDetail[];
checks: Array<{ name: string; status: string; conclusion?: string; detailsUrl?: string }>;
}
/** Fetch the full comment thread + per-check status for a single GitHub PR (called on selection in the import preview). */
export function apiFetchGitHubPullDetail(repo: string, number: number): Promise<GitHubPullDetail> {
return api<GitHubPullDetail>("/github/pulls/detail", {
method: "POST",
body: JSON.stringify({ repo, number }),
});
}
/*
FNXC:GitHubImport 2026-06-23-03:15:
Per-issue detail for the Import Tasks issue preview pane. Mirrors apiFetchGitHubPullDetail: `gh issue list` has no comment thread, so the preview fetches the FULL comment thread ON SELECTION (never for the whole list).
Issues have no checks rollup, so only `comments` is returned.
*/
export interface GitHubIssueDetail {
comments: GitHubCommentDetail[];
}
/** Fetch the full comment thread for a single GitHub issue (called on selection in the import preview). */
export function apiFetchGitHubIssueDetail(repo: string, number: number): Promise<GitHubIssueDetail> {
return api<GitHubIssueDetail>("/github/issues/detail", {
method: "POST",
body: JSON.stringify({ repo, number }),
});
}
/** Close a GitHub issue (Close issue button in the import preview). */
export async function apiCloseGitHubIssue(repo: string, number: number): Promise<void> {
await api<{ ok: boolean }>("/github/issues/close", {
method: "POST",
body: JSON.stringify({ repo, number }),
});
}
/*
FNXC:GitHubImport 2026-07-17-12:00:
Posts a new comment to the upstream GitHub issue. This is deliberately separate from
apiImportGitHubComment, which creates a Fusion resolve-feedback task from an existing comment.
*/
export async function apiAddGitHubIssueComment(repo: string, number: number, body: string): Promise<void> {
await api<{ ok: boolean }>("/github/issues/comment", {
method: "POST",
body: JSON.stringify({ repo, number, body }),
});
}
/** Import a specific GitHub pull request as a fn review task */
export function apiImportGitHubPull(owner: string, repo: string, prNumber: number, projectId?: string): Promise<Task> {
return api<Task>(withProjectId("/github/pulls/import", projectId), {
method: "POST",
body: JSON.stringify({ owner, repo, prNumber }),
});
}
/**
* FNXC:GitHubImport 2026-07-16-18:05:
* Comment imports preserve the comment payload and issue/PR source context so the server can create a separately auditable resolve-feedback task without closing the detail window.
*/
export function apiImportGitHubComment(
params: {
owner: string;
repo: string;
number: number;
type: "issue" | "pull";
comment: Pick<GitHubCommentDetail, "author" | "body" | "createdAt">;
},
projectId?: string,
): Promise<Task> {
return api<Task>(withProjectId("/github/comments/import", projectId), {
method: "POST",
body: JSON.stringify(params),
});
}