Skip to content

Commit a0c5a45

Browse files
Add missing web source files and fix gitignore lib/ rule
- 22 frontend files (API clients, hooks, utils) were excluded by the root gitignore lib/ rule meant for Python build dirs. Narrowed the rule to /lib/ (root only) so packages/web/src/lib/ is tracked. - Added skip-existing to PyPI publish step to avoid 400 on re-releases.
1 parent 69d5997 commit a0c5a45

24 files changed

Lines changed: 913 additions & 2 deletions

.github/workflows/publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,5 @@ jobs:
131131

132132
- name: Publish to PyPI
133133
uses: pypa/gh-action-pypi-publish@release/v1
134+
with:
135+
skip-existing: true

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ dist/
1010
downloads/
1111
eggs/
1212
.eggs/
13-
lib/
14-
lib64/
13+
# Python build dirs (only at root, not packages/web/src/lib/)
14+
/lib/
15+
/lib64/
1516
parts/
1617
sdist/
1718
var/
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { apiGet, apiPost, apiPatch } from "./client";
2+
import type {
3+
DeadCodeFindingResponse,
4+
DeadCodePatchRequest,
5+
DeadCodeSummaryResponse,
6+
} from "./types";
7+
8+
export async function listDeadCode(
9+
repoId: string,
10+
opts?: {
11+
kind?: string;
12+
min_confidence?: number;
13+
status?: string;
14+
safe_only?: boolean;
15+
limit?: number;
16+
},
17+
): Promise<DeadCodeFindingResponse[]> {
18+
return apiGet<DeadCodeFindingResponse[]>(`/api/repos/${repoId}/dead-code`, opts);
19+
}
20+
21+
export async function analyzeDeadCode(repoId: string): Promise<{ job_id: string }> {
22+
return apiPost<{ job_id: string }>(`/api/repos/${repoId}/dead-code/analyze`);
23+
}
24+
25+
export async function getDeadCodeSummary(repoId: string): Promise<DeadCodeSummaryResponse> {
26+
return apiGet<DeadCodeSummaryResponse>(`/api/repos/${repoId}/dead-code/summary`);
27+
}
28+
29+
export async function patchDeadCodeFinding(
30+
findingId: string,
31+
data: DeadCodePatchRequest,
32+
): Promise<DeadCodeFindingResponse> {
33+
return apiPatch<DeadCodeFindingResponse>(`/api/dead-code/${findingId}`, data);
34+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { apiGet, apiPost, apiPatch } from "./client";
2+
import type {
3+
DecisionCreate,
4+
DecisionHealthResponse,
5+
DecisionRecordResponse,
6+
DecisionStatusUpdate,
7+
} from "./types";
8+
9+
export async function listDecisions(
10+
repoId: string,
11+
opts?: {
12+
status?: string;
13+
source?: string;
14+
tag?: string;
15+
module?: string;
16+
include_proposed?: boolean;
17+
limit?: number;
18+
},
19+
): Promise<DecisionRecordResponse[]> {
20+
return apiGet<DecisionRecordResponse[]>(`/api/repos/${repoId}/decisions`, opts);
21+
}
22+
23+
export async function getDecision(
24+
repoId: string,
25+
decisionId: string,
26+
): Promise<DecisionRecordResponse> {
27+
return apiGet<DecisionRecordResponse>(
28+
`/api/repos/${repoId}/decisions/${decisionId}`,
29+
);
30+
}
31+
32+
export async function createDecision(
33+
repoId: string,
34+
data: DecisionCreate,
35+
): Promise<DecisionRecordResponse> {
36+
return apiPost<DecisionRecordResponse>(`/api/repos/${repoId}/decisions`, data);
37+
}
38+
39+
export async function patchDecision(
40+
repoId: string,
41+
decisionId: string,
42+
data: DecisionStatusUpdate,
43+
): Promise<DecisionRecordResponse> {
44+
return apiPatch<DecisionRecordResponse>(
45+
`/api/repos/${repoId}/decisions/${decisionId}`,
46+
data,
47+
);
48+
}
49+
50+
export async function getDecisionHealth(
51+
repoId: string,
52+
): Promise<DecisionHealthResponse> {
53+
return apiGet<DecisionHealthResponse>(
54+
`/api/repos/${repoId}/decisions/health`,
55+
);
56+
}

packages/web/src/lib/api/git.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { apiGet } from "./client";
2+
import type {
3+
GitMetadataResponse,
4+
HotspotResponse,
5+
OwnershipEntry,
6+
GitSummaryResponse,
7+
} from "./types";
8+
9+
export async function getGitMetadata(
10+
repoId: string,
11+
filePath: string,
12+
): Promise<GitMetadataResponse> {
13+
return apiGet<GitMetadataResponse>(`/api/repos/${repoId}/git-metadata`, {
14+
file_path: filePath,
15+
});
16+
}
17+
18+
export async function getHotspots(
19+
repoId: string,
20+
limit = 20,
21+
): Promise<HotspotResponse[]> {
22+
return apiGet<HotspotResponse[]>(`/api/repos/${repoId}/hotspots`, { limit });
23+
}
24+
25+
export async function getOwnership(
26+
repoId: string,
27+
granularity: "file" | "module" = "module",
28+
): Promise<OwnershipEntry[]> {
29+
return apiGet<OwnershipEntry[]>(`/api/repos/${repoId}/ownership`, { granularity });
30+
}
31+
32+
export async function getCoChanges(
33+
repoId: string,
34+
filePath: string,
35+
minCount = 3,
36+
): Promise<{ file_path: string; co_change_partners: Array<{ file_path: string; co_change_count: number }> }> {
37+
return apiGet(`/api/repos/${repoId}/co-changes`, {
38+
file_path: filePath,
39+
min_count: minCount,
40+
});
41+
}
42+
43+
export async function getGitSummary(repoId: string): Promise<GitSummaryResponse> {
44+
return apiGet<GitSummaryResponse>(`/api/repos/${repoId}/git-summary`);
45+
}

packages/web/src/lib/api/graph.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { apiGet } from "./client";
2+
import type {
3+
DeadCodeGraphResponse,
4+
EgoGraphResponse,
5+
GraphExportResponse,
6+
GraphPathResponse,
7+
HotFilesGraphResponse,
8+
ModuleGraphResponse,
9+
NodeSearchResult,
10+
} from "./types";
11+
12+
export async function getGraph(repoId: string): Promise<GraphExportResponse> {
13+
return apiGet<GraphExportResponse>(`/api/graph/${repoId}`);
14+
}
15+
16+
export async function getGraphPath(
17+
repoId: string,
18+
from: string,
19+
to: string,
20+
): Promise<GraphPathResponse> {
21+
return apiGet<GraphPathResponse>(`/api/graph/${repoId}/path`, { from, to });
22+
}
23+
24+
export async function getModuleGraph(repoId: string): Promise<ModuleGraphResponse> {
25+
return apiGet<ModuleGraphResponse>(`/api/graph/${repoId}/modules`);
26+
}
27+
28+
export async function getEgoGraph(
29+
repoId: string,
30+
nodeId: string,
31+
hops = 2,
32+
): Promise<EgoGraphResponse> {
33+
return apiGet<EgoGraphResponse>(`/api/graph/${repoId}/ego`, { node_id: nodeId, hops });
34+
}
35+
36+
export async function searchNodes(
37+
repoId: string,
38+
q: string,
39+
limit = 10,
40+
): Promise<NodeSearchResult[]> {
41+
return apiGet<NodeSearchResult[]>(`/api/graph/${repoId}/nodes/search`, { q, limit });
42+
}
43+
44+
export async function getArchitectureGraph(repoId: string): Promise<GraphExportResponse> {
45+
return apiGet<GraphExportResponse>(`/api/graph/${repoId}/entry-points`);
46+
}
47+
48+
export async function getDeadCodeGraph(repoId: string): Promise<DeadCodeGraphResponse> {
49+
return apiGet<DeadCodeGraphResponse>(`/api/graph/${repoId}/dead-nodes`);
50+
}
51+
52+
export async function getHotFilesGraph(
53+
repoId: string,
54+
days = 30,
55+
limit = 25,
56+
): Promise<HotFilesGraphResponse> {
57+
return apiGet<HotFilesGraphResponse>(`/api/graph/${repoId}/hot-files`, { days, limit });
58+
}

packages/web/src/lib/api/health.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { apiGet } from "./client";
2+
import type { HealthResponse } from "./types";
3+
4+
export async function getHealth(): Promise<HealthResponse> {
5+
return apiGet<HealthResponse>("/health");
6+
}

packages/web/src/lib/api/jobs.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { apiGet } from "./client";
2+
import type { JobResponse } from "./types";
3+
4+
export async function listJobs(opts?: {
5+
repo_id?: string;
6+
status?: string;
7+
limit?: number;
8+
offset?: number;
9+
}): Promise<JobResponse[]> {
10+
return apiGet<JobResponse[]>("/api/jobs", opts);
11+
}
12+
13+
export async function getJob(jobId: string): Promise<JobResponse> {
14+
return apiGet<JobResponse>(`/api/jobs/${jobId}`);
15+
}
16+
17+
/** Returns the SSE stream URL for a job. Use with EventSource or the useSSE hook. */
18+
export function getJobStreamUrl(jobId: string): string {
19+
const base = process.env.NEXT_PUBLIC_REPOWISE_API_URL ?? "";
20+
return `${base}/api/jobs/${jobId}/stream`;
21+
}

packages/web/src/lib/api/pages.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { apiGet, apiPost } from "./client";
2+
import type { PageResponse, PageVersionResponse } from "./types";
3+
4+
export async function listPages(
5+
repoId: string,
6+
opts?: { page_type?: string; sort_by?: string; order?: string; limit?: number; offset?: number },
7+
): Promise<PageResponse[]> {
8+
return apiGet<PageResponse[]>("/api/pages", { repo_id: repoId, ...opts });
9+
}
10+
11+
/** Fetch all pages for a repo, auto-paginating through the 500-item backend limit. */
12+
export async function listAllPages(
13+
repoId: string,
14+
opts?: { page_type?: string; sort_by?: string; order?: string },
15+
): Promise<PageResponse[]> {
16+
const PAGE_SIZE = 500;
17+
const all: PageResponse[] = [];
18+
let offset = 0;
19+
20+
while (true) {
21+
const batch = await listPages(repoId, { ...opts, limit: PAGE_SIZE, offset });
22+
all.push(...batch);
23+
if (batch.length < PAGE_SIZE) break;
24+
offset += PAGE_SIZE;
25+
}
26+
27+
return all;
28+
}
29+
30+
/** Get page by its ID using the query-param endpoint (avoids path conflicts) */
31+
export async function getPageById(pageId: string): Promise<PageResponse> {
32+
return apiGet<PageResponse>("/api/pages/lookup", { page_id: pageId });
33+
}
34+
35+
/** Get page versions by ID */
36+
export async function getPageVersions(
37+
pageId: string,
38+
limit = 50,
39+
): Promise<PageVersionResponse[]> {
40+
return apiGet<PageVersionResponse[]>("/api/pages/lookup/versions", {
41+
page_id: pageId,
42+
limit,
43+
});
44+
}
45+
46+
/** Force-regenerate a page by ID */
47+
export async function regeneratePage(pageId: string): Promise<{ job_id: string }> {
48+
return apiPost<{ job_id: string }>(`/api/pages/lookup/regenerate?page_id=${encodeURIComponent(pageId)}`);
49+
}

packages/web/src/lib/api/repos.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { apiGet, apiPost, apiPatch } from "./client";
2+
import type { RepoCreate, RepoUpdate, RepoResponse, JobResponse, RepoStatsResponse } from "./types";
3+
4+
export async function listRepos(): Promise<RepoResponse[]> {
5+
return apiGet<RepoResponse[]>("/api/repos");
6+
}
7+
8+
export async function getRepo(repoId: string): Promise<RepoResponse> {
9+
return apiGet<RepoResponse>(`/api/repos/${repoId}`);
10+
}
11+
12+
export async function createRepo(data: RepoCreate): Promise<RepoResponse> {
13+
return apiPost<RepoResponse>("/api/repos", data);
14+
}
15+
16+
export async function updateRepo(repoId: string, data: RepoUpdate): Promise<RepoResponse> {
17+
return apiPatch<RepoResponse>(`/api/repos/${repoId}`, data);
18+
}
19+
20+
export async function syncRepo(repoId: string): Promise<JobResponse> {
21+
return apiPost<JobResponse>(`/api/repos/${repoId}/sync`);
22+
}
23+
24+
export async function fullResyncRepo(repoId: string): Promise<JobResponse> {
25+
return apiPost<JobResponse>(`/api/repos/${repoId}/full-resync`);
26+
}
27+
28+
export async function getRepoStats(repoId: string): Promise<RepoStatsResponse> {
29+
return apiGet<RepoStatsResponse>(`/api/repos/${repoId}/stats`);
30+
}

0 commit comments

Comments
 (0)