@ridit/lens 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LENS.md +25 -0
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/dist/index.js +49363 -0
- package/package.json +38 -0
- package/src/colors.ts +1 -0
- package/src/commands/chat.tsx +23 -0
- package/src/commands/provider.tsx +224 -0
- package/src/commands/repo.tsx +120 -0
- package/src/commands/review.tsx +294 -0
- package/src/commands/task.tsx +36 -0
- package/src/commands/timeline.tsx +22 -0
- package/src/components/chat/ChatMessage.tsx +176 -0
- package/src/components/chat/ChatOverlays.tsx +329 -0
- package/src/components/chat/ChatRunner.tsx +732 -0
- package/src/components/provider/ApiKeyStep.tsx +243 -0
- package/src/components/provider/ModelStep.tsx +73 -0
- package/src/components/provider/ProviderTypeStep.tsx +54 -0
- package/src/components/provider/RemoveProviderStep.tsx +83 -0
- package/src/components/repo/DiffViewer.tsx +175 -0
- package/src/components/repo/FileReviewer.tsx +70 -0
- package/src/components/repo/FileViewer.tsx +60 -0
- package/src/components/repo/IssueFixer.tsx +666 -0
- package/src/components/repo/LensFileMenu.tsx +122 -0
- package/src/components/repo/NoProviderPrompt.tsx +28 -0
- package/src/components/repo/PreviewRunner.tsx +217 -0
- package/src/components/repo/ProviderPicker.tsx +76 -0
- package/src/components/repo/RepoAnalysis.tsx +343 -0
- package/src/components/repo/StepRow.tsx +69 -0
- package/src/components/task/TaskRunner.tsx +396 -0
- package/src/components/timeline/CommitDetail.tsx +274 -0
- package/src/components/timeline/CommitList.tsx +174 -0
- package/src/components/timeline/TimelineChat.tsx +167 -0
- package/src/components/timeline/TimelineRunner.tsx +1209 -0
- package/src/index.tsx +60 -0
- package/src/types/chat.ts +69 -0
- package/src/types/config.ts +20 -0
- package/src/types/repo.ts +42 -0
- package/src/utils/ai.ts +233 -0
- package/src/utils/chat.ts +833 -0
- package/src/utils/config.ts +61 -0
- package/src/utils/files.ts +104 -0
- package/src/utils/git.ts +155 -0
- package/src/utils/history.ts +86 -0
- package/src/utils/lensfile.ts +77 -0
- package/src/utils/llm.ts +81 -0
- package/src/utils/preview.ts +119 -0
- package/src/utils/repo.ts +69 -0
- package/src/utils/stats.ts +174 -0
- package/src/utils/thinking.tsx +191 -0
- package/tsconfig.json +24 -0
package/src/index.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { RepoCommand } from "./commands/repo";
|
|
5
|
+
import { InitCommand } from "./commands/provider";
|
|
6
|
+
import { ReviewCommand } from "./commands/review";
|
|
7
|
+
import { TaskCommand } from "./commands/task";
|
|
8
|
+
import { ChatCommand } from "./commands/chat";
|
|
9
|
+
import { TimelineCommand } from "./commands/timeline";
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command("repo <url>")
|
|
15
|
+
.description("Analyze a remote repository")
|
|
16
|
+
.action((url) => {
|
|
17
|
+
render(<RepoCommand url={url} />);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("provider")
|
|
22
|
+
.description("Configure AI providers")
|
|
23
|
+
.action(() => {
|
|
24
|
+
render(<InitCommand />);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("review [path]")
|
|
29
|
+
.description("Review a local codebase")
|
|
30
|
+
.action((inputPath) => {
|
|
31
|
+
render(<ReviewCommand path={inputPath ?? "."} />);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command("task <text>")
|
|
36
|
+
.description("Apply a natural language change to the codebase")
|
|
37
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
38
|
+
.action((text: string, opts: { path: string }) => {
|
|
39
|
+
render(<TaskCommand prompt={text} path={opts.path} />);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command("chat")
|
|
44
|
+
.description("Chat with your codebase — ask questions or make changes")
|
|
45
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
46
|
+
.action((opts: { path: string }) => {
|
|
47
|
+
render(<ChatCommand path={opts.path} />);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command("timeline")
|
|
52
|
+
.description(
|
|
53
|
+
"Explore your code history — see commits, changes, and evolution",
|
|
54
|
+
)
|
|
55
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
56
|
+
.action((opts: { path: string }) => {
|
|
57
|
+
render(<TimelineCommand path={opts.path} />);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { FilePatch, DiffLine } from "../components/repo/DiffViewer";
|
|
2
|
+
|
|
3
|
+
// ── Tool calls ────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type ToolCall =
|
|
6
|
+
| { type: "shell"; command: string }
|
|
7
|
+
| { type: "fetch"; url: string }
|
|
8
|
+
| { type: "read-file"; filePath: string }
|
|
9
|
+
| { type: "write-file"; filePath: string; fileContent: string }
|
|
10
|
+
| { type: "search"; query: string };
|
|
11
|
+
|
|
12
|
+
// ── Messages ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export type Message =
|
|
15
|
+
| { role: "user" | "assistant"; type: "text"; content: string }
|
|
16
|
+
| {
|
|
17
|
+
role: "assistant";
|
|
18
|
+
type: "tool";
|
|
19
|
+
toolName: "shell" | "fetch" | "read-file" | "write-file" | "search";
|
|
20
|
+
content: string;
|
|
21
|
+
result: string;
|
|
22
|
+
approved: boolean;
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
role: "assistant";
|
|
26
|
+
type: "plan";
|
|
27
|
+
content: string;
|
|
28
|
+
patches: FilePatch[];
|
|
29
|
+
applied: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ── Chat stage ────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export type ChatStage =
|
|
35
|
+
| { type: "picking-provider" }
|
|
36
|
+
| { type: "loading" }
|
|
37
|
+
| { type: "idle" }
|
|
38
|
+
| { type: "thinking" }
|
|
39
|
+
| { type: "error"; message: string }
|
|
40
|
+
| {
|
|
41
|
+
type: "permission";
|
|
42
|
+
tool: ToolCall;
|
|
43
|
+
pendingMessages: Message[];
|
|
44
|
+
resolve: (approved: boolean) => void;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
type: "preview";
|
|
48
|
+
patches: FilePatch[];
|
|
49
|
+
diffLines: DiffLine[][];
|
|
50
|
+
scrollOffset: number;
|
|
51
|
+
pendingMessages: Message[];
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
type: "viewing-file";
|
|
55
|
+
file: { path: string; isNew: boolean; patch: FilePatch };
|
|
56
|
+
diffLines: DiffLine[];
|
|
57
|
+
scrollOffset: number;
|
|
58
|
+
}
|
|
59
|
+
| { type: "clone-offer"; repoUrl: string; launchAnalysis?: boolean }
|
|
60
|
+
| { type: "cloning"; repoUrl: string }
|
|
61
|
+
| { type: "clone-exists"; repoUrl: string; repoPath: string }
|
|
62
|
+
| {
|
|
63
|
+
type: "clone-done";
|
|
64
|
+
repoUrl: string;
|
|
65
|
+
destPath: string;
|
|
66
|
+
fileCount: number;
|
|
67
|
+
launchAnalysis?: boolean;
|
|
68
|
+
}
|
|
69
|
+
| { type: "clone-error"; message: string };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ProviderType =
|
|
2
|
+
| "anthropic"
|
|
3
|
+
| "gemini"
|
|
4
|
+
| "openai"
|
|
5
|
+
| "ollama"
|
|
6
|
+
| "custom";
|
|
7
|
+
|
|
8
|
+
export type Provider = {
|
|
9
|
+
id: string;
|
|
10
|
+
type: ProviderType;
|
|
11
|
+
name: string;
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
model: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type Config = {
|
|
18
|
+
providers: Provider[];
|
|
19
|
+
defaultProviderId?: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type Step =
|
|
2
|
+
| { type: "cloning"; status: "pending" | "done" }
|
|
3
|
+
| { type: "folder-exists"; status: "pending"; repoPath: string }
|
|
4
|
+
| { type: "fetching-tree"; status: "pending" | "done" }
|
|
5
|
+
| { type: "reading-files"; status: "pending" | "done" }
|
|
6
|
+
| { type: "error"; message: string };
|
|
7
|
+
|
|
8
|
+
export type ReviewStage = "list" | "file";
|
|
9
|
+
|
|
10
|
+
export type FileTree = {
|
|
11
|
+
name: string;
|
|
12
|
+
children?: FileTree[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ImportantFile = {
|
|
16
|
+
path: string;
|
|
17
|
+
content: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type AIProvider =
|
|
21
|
+
| "anthropic"
|
|
22
|
+
| "gemini"
|
|
23
|
+
| "ollama"
|
|
24
|
+
| "openai"
|
|
25
|
+
| "custom";
|
|
26
|
+
|
|
27
|
+
export type AnalysisResult = {
|
|
28
|
+
overview: string;
|
|
29
|
+
importantFolders: string[];
|
|
30
|
+
missingConfigs: string[];
|
|
31
|
+
securityIssues: string[];
|
|
32
|
+
suggestions: string[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type PackageManager = "npm" | "yarn" | "pnpm" | "pip" | "unknown";
|
|
36
|
+
|
|
37
|
+
export type PreviewInfo = {
|
|
38
|
+
packageManager: PackageManager;
|
|
39
|
+
installCmd: string;
|
|
40
|
+
devCmd: string;
|
|
41
|
+
port: number | null;
|
|
42
|
+
};
|
package/src/utils/ai.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import type { Provider } from "../types/config";
|
|
5
|
+
import type { AnalysisResult, ImportantFile } from "../types/repo";
|
|
6
|
+
|
|
7
|
+
export function buildFileListPrompt(
|
|
8
|
+
repoUrl: string,
|
|
9
|
+
fileTree: string[],
|
|
10
|
+
): string {
|
|
11
|
+
return `You are a senior software engineer. You are about to analyze this repository:
|
|
12
|
+
Repository URL: ${repoUrl}
|
|
13
|
+
|
|
14
|
+
Here is the complete file tree (${fileTree.length} files):
|
|
15
|
+
${fileTree.join("\n")}
|
|
16
|
+
|
|
17
|
+
Your job is to select the files you need to read to fully understand what this project is, what it does, and how it works.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
- ALWAYS include package.json, tsconfig.json, README.md if they exist
|
|
21
|
+
- ALWAYS include ALL files inside src/ — especially index files, main entry points, and any files that reveal the project's purpose (components, hooks, utilities, exports)
|
|
22
|
+
- Include config files: vite.config, eslint.config, tailwind.config, etc.
|
|
23
|
+
- If there is a src/index.ts or src/main.ts or src/lib/index.ts, ALWAYS include it — these reveal what the project exports
|
|
24
|
+
- Do NOT skip source files just because there are many — pick up to 30 files
|
|
25
|
+
- Prefer breadth: pick at least one file from every folder under src/
|
|
26
|
+
|
|
27
|
+
Respond ONLY with a JSON array of file paths relative to repo root. No markdown, no explanation. Example:
|
|
28
|
+
["package.json", "src/main.ts", "src/components/Button.tsx"]`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildAnalysisPrompt(
|
|
32
|
+
repoUrl: string,
|
|
33
|
+
files: ImportantFile[],
|
|
34
|
+
): string {
|
|
35
|
+
const fileList = files
|
|
36
|
+
.map((f) => `### ${f.path}\n\`\`\`\n${f.content.slice(0, 3000)}\n\`\`\``)
|
|
37
|
+
.join("\n\n");
|
|
38
|
+
|
|
39
|
+
return `You are a senior software engineer analyzing a repository.
|
|
40
|
+
Repository URL: ${repoUrl}
|
|
41
|
+
|
|
42
|
+
Here are the file contents:
|
|
43
|
+
|
|
44
|
+
${fileList}
|
|
45
|
+
|
|
46
|
+
Analyze this repository thoroughly using the actual file contents above.
|
|
47
|
+
|
|
48
|
+
Important instructions:
|
|
49
|
+
- Read the actual source code carefully to determine what the project really is
|
|
50
|
+
- Look at every component, hook, utility and describe what it actually does
|
|
51
|
+
- importantFolders must describe EVERY folder with specifics: what files are in it, what they do, and why they matter
|
|
52
|
+
- suggestions must be specific to the actual code you read — reference real file names, real function names, real patterns you saw
|
|
53
|
+
- missingConfigs should only list things genuinely missing for THIS type of project
|
|
54
|
+
- securityIssues must reference actual file names and line patterns found
|
|
55
|
+
- overview must be specific: name the actual components/features/exports you saw, not just the tech stack
|
|
56
|
+
|
|
57
|
+
Respond ONLY with a JSON object (no markdown, no explanation) with this exact shape:
|
|
58
|
+
{
|
|
59
|
+
"overview": "3-5 sentences. Name the actual components, features, or exports you found. Describe what the project does, who would use it, and what makes it distinctive. Be specific — mention actual file names or component names.",
|
|
60
|
+
"importantFolders": [
|
|
61
|
+
"src/components: contains X, Y, Z components. ButtonComponent uses CVA for variants. Each component is exported from index.ts."
|
|
62
|
+
],
|
|
63
|
+
"missingConfigs": ["only configs genuinely missing and relevant — explain WHY each is missing for this specific project"],
|
|
64
|
+
"securityIssues": ["reference actual file names and patterns found"],
|
|
65
|
+
"suggestions": ["each suggestion must reference actual code — e.g. 'In src/components/Button.tsx, consider adding ...' not generic advice"]
|
|
66
|
+
}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseStringArray(text: string): string[] {
|
|
70
|
+
const cleaned = text.replace(/```json|```/g, "").trim();
|
|
71
|
+
const match = cleaned.match(/\[[\s\S]*\]/);
|
|
72
|
+
if (!match) return [];
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(match[0]) as string[];
|
|
75
|
+
} catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseResult(text: string): AnalysisResult {
|
|
81
|
+
const cleaned = text.replace(/```json|```/g, "").trim();
|
|
82
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
83
|
+
if (!jsonMatch) throw new Error(`No JSON found in response:\n${cleaned}`);
|
|
84
|
+
|
|
85
|
+
const parsed = JSON.parse(jsonMatch[0]) as Partial<AnalysisResult>;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
overview: parsed.overview ?? "No overview provided",
|
|
89
|
+
importantFolders: parsed.importantFolders ?? [],
|
|
90
|
+
missingConfigs: parsed.missingConfigs ?? [],
|
|
91
|
+
securityIssues: parsed.securityIssues ?? [],
|
|
92
|
+
suggestions: parsed.suggestions ?? [],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function checkOllamaInstalled(): Promise<boolean> {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
exec("ollama --version", (err) => resolve(!err));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getOllamaModels(): Promise<string[]> {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
exec("ollama list", (err, stdout) => {
|
|
105
|
+
if (err) return resolve([]);
|
|
106
|
+
const models = stdout
|
|
107
|
+
.trim()
|
|
108
|
+
.split("\n")
|
|
109
|
+
.slice(1)
|
|
110
|
+
.map((line) => line.split(/\s+/)[0] ?? "")
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
resolve(models);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function callModel(provider: Provider, prompt: string): Promise<string> {
|
|
118
|
+
switch (provider.type) {
|
|
119
|
+
case "anthropic": {
|
|
120
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
"x-api-key": provider.apiKey ?? "",
|
|
125
|
+
"anthropic-version": "2023-06-01",
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
model: provider.model,
|
|
129
|
+
max_tokens: 2048,
|
|
130
|
+
messages: [{ role: "user", content: prompt }],
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok)
|
|
134
|
+
throw new Error(`Anthropic API error: ${response.statusText}`);
|
|
135
|
+
const data = (await response.json()) as any;
|
|
136
|
+
return data.content
|
|
137
|
+
.filter((b: { type: string }) => b.type === "text")
|
|
138
|
+
.map((b: { text: string }) => b.text)
|
|
139
|
+
.join("");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case "gemini": {
|
|
143
|
+
const response = await fetch(
|
|
144
|
+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${provider.apiKey ?? ""}`,
|
|
145
|
+
{
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json" },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
if (!response.ok)
|
|
154
|
+
throw new Error(`Gemini API error: ${response.statusText}`);
|
|
155
|
+
const data = (await response.json()) as any;
|
|
156
|
+
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case "ollama": {
|
|
160
|
+
const baseUrl = provider.baseUrl ?? "http://localhost:11434";
|
|
161
|
+
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
model: provider.model,
|
|
166
|
+
prompt,
|
|
167
|
+
stream: false,
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) throw new Error(`Ollama error: ${response.statusText}`);
|
|
171
|
+
const data = (await response.json()) as any;
|
|
172
|
+
return data.response ?? "";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case "openai":
|
|
176
|
+
case "custom": {
|
|
177
|
+
const baseUrl = provider.baseUrl ?? "https://api.openai.com/v1";
|
|
178
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
Authorization: `Bearer ${provider.apiKey ?? ""}`,
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
model: provider.model,
|
|
186
|
+
messages: [{ role: "user", content: prompt }],
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
if (!response.ok)
|
|
190
|
+
throw new Error(`OpenAI-compat API error: ${response.statusText}`);
|
|
191
|
+
const data = (await response.json()) as any;
|
|
192
|
+
return data.choices?.[0]?.message?.content ?? "";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`Unknown provider type`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function requestFileList(
|
|
201
|
+
repoUrl: string,
|
|
202
|
+
repoPath: string,
|
|
203
|
+
fileTree: string[],
|
|
204
|
+
provider: Provider,
|
|
205
|
+
): Promise<ImportantFile[]> {
|
|
206
|
+
const prompt = buildFileListPrompt(repoUrl, fileTree);
|
|
207
|
+
const text = await callModel(provider, prompt);
|
|
208
|
+
const requestedPaths = parseStringArray(text);
|
|
209
|
+
|
|
210
|
+
const files: ImportantFile[] = [];
|
|
211
|
+
for (const filePath of requestedPaths) {
|
|
212
|
+
const fullPath = path.join(repoPath, filePath);
|
|
213
|
+
if (existsSync(fullPath)) {
|
|
214
|
+
try {
|
|
215
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
216
|
+
files.push({ path: filePath, content });
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return files;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function analyzeRepo(
|
|
224
|
+
repoUrl: string,
|
|
225
|
+
files: ImportantFile[],
|
|
226
|
+
provider: Provider,
|
|
227
|
+
): Promise<AnalysisResult> {
|
|
228
|
+
const prompt = buildAnalysisPrompt(repoUrl, files);
|
|
229
|
+
const text = await callModel(provider, prompt);
|
|
230
|
+
return parseResult(text);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const callModelRaw = callModel;
|