1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
import { mapWithConcurrency, DB_FANOUT_LIMIT } from "./concurrency";
export interface IndexRepoResult {
indexed: number;
skipped: number;
model: string;
error?: string;
}
export interface IndexRepoDeps {
getTree: (
owner: string,
repo: string,
ref: string,
path?: string
) => Promise<Array<{ name: string; type: string; path?: string }>>;
indexChangedFiles: (args: {
repositoryId: string;
ownerName: string;
repoName: string;
commitSha: string;
changedPaths: string[];
}) => Promise<{ indexed: number; skipped: number; model: string }>;
}
async function realDeps(): Promise<IndexRepoDeps> {
const { getTree } = await import("../git/repository");
const { indexChangedFiles } = await import("./semantic-index");
return { getTree: getTree as IndexRepoDeps["getTree"], indexChangedFiles };
}
const SKIP_DIRS = new Set([
".git", "node_modules", "dist", "build", "out", "target", "vendor",
".next", ".cache", "coverage", "__pycache__", ".venv", "venv",
]);
const MAX_DEPTH = 6;
async function collectPaths(
deps: IndexRepoDeps,
owner: string,
repo: string,
ref: string
): Promise<string[]> {
const files: string[] = [];
let level: string[] = [""];
for (let depth = 0; depth < MAX_DEPTH && level.length > 0; depth++) {
const results = await mapWithConcurrency(level, DB_FANOUT_LIMIT, async (dir) => {
try {
return await deps.getTree(owner, repo, ref, dir || undefined);
} catch {
return [];
}
});
const next: string[] = [];
for (let i = 0; i < results.length; i++) {
const dir = level[i];
for (const entry of results[i] ?? []) {
const full = entry.path ?? (dir ? `${dir}/${entry.name}` : entry.name);
if (entry.type === "tree") {
if (SKIP_DIRS.has(entry.name)) continue;
next.push(full);
} else if (entry.type === "blob") {
files.push(full);
}
}
}
level = next;
}
return files;
}
export async function indexExistingRepo(
args: {
repositoryId: string;
owner: string;
repoName: string;
ref: string;
commitSha: string;
},
injected?: IndexRepoDeps
): Promise<IndexRepoResult> {
try {
const deps = injected ?? (await realDeps());
const paths = await collectPaths(deps, args.owner, args.repoName, args.ref);
if (paths.length === 0) {
return { indexed: 0, skipped: 0, model: "none" };
}
return await deps.indexChangedFiles({
repositoryId: args.repositoryId,
ownerName: args.owner,
repoName: args.repoName,
commitSha: args.commitSha,
changedPaths: paths,
});
} catch (err) {
return {
indexed: 0,
skipped: 0,
model: "none",
error: err instanceof Error ? err.message : String(err),
};
}
}
|