@ridit/lens 0.3.2 → 0.3.3
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/dist/index.mjs +1541 -264
- package/package.json +1 -1
- package/src/commands/commit.tsx +10 -2
- package/src/commands/watch.tsx +56 -0
- package/src/components/timeline/TimelineRunner.tsx +0 -7
- package/src/components/watch/WatchRunner.tsx +929 -0
- package/src/index.tsx +144 -110
- package/src/utils/watch.ts +307 -0
package/src/index.tsx
CHANGED
|
@@ -1,110 +1,144 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import "./utils/tools/registry";
|
|
3
|
-
import { render } from "ink";
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import { RepoCommand } from "./commands/repo";
|
|
6
|
-
import { InitCommand } from "./commands/provider";
|
|
7
|
-
import { ReviewCommand } from "./commands/review";
|
|
8
|
-
import { TaskCommand } from "./commands/task";
|
|
9
|
-
import { ChatCommand } from "./commands/chat";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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
|
-
.option(
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.option("--
|
|
85
|
-
.option("--
|
|
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
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
+
import "./utils/tools/registry";
|
|
3
|
+
import { render } from "ink";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { RepoCommand } from "./commands/repo";
|
|
6
|
+
import { InitCommand } from "./commands/provider";
|
|
7
|
+
import { ReviewCommand } from "./commands/review";
|
|
8
|
+
import { TaskCommand } from "./commands/task";
|
|
9
|
+
import { ChatCommand } from "./commands/chat";
|
|
10
|
+
import { WatchCommand } from "./commands/watch";
|
|
11
|
+
import { TimelineCommand } from "./commands/timeline";
|
|
12
|
+
import { CommitCommand } from "./commands/commit";
|
|
13
|
+
import { registerBuiltins } from "./utils/tools/builtins";
|
|
14
|
+
import { loadAddons } from "./utils/addons/loadAddons";
|
|
15
|
+
|
|
16
|
+
registerBuiltins();
|
|
17
|
+
await loadAddons();
|
|
18
|
+
|
|
19
|
+
const program = new Command();
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command("stalk <url>")
|
|
23
|
+
.alias("repo")
|
|
24
|
+
.description("Analyze a remote repository")
|
|
25
|
+
.action((url) => {
|
|
26
|
+
render(<RepoCommand url={url} />);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command("provider")
|
|
31
|
+
.description("Configure AI providers")
|
|
32
|
+
.action(() => {
|
|
33
|
+
render(<InitCommand />);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command("judge [path]")
|
|
38
|
+
.alias("review")
|
|
39
|
+
.description("Review a local codebase")
|
|
40
|
+
.action((inputPath) => {
|
|
41
|
+
render(<ReviewCommand path={inputPath ?? "."} />);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
program
|
|
45
|
+
.command("cook <text>")
|
|
46
|
+
.alias("task")
|
|
47
|
+
.description("Apply a natural language change to the codebase")
|
|
48
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
49
|
+
.action((text: string, opts: { path: string }) => {
|
|
50
|
+
render(<TaskCommand prompt={text} path={opts.path} />);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command("vibe")
|
|
55
|
+
.alias("chat")
|
|
56
|
+
.description("Chat with your codebase — ask questions or make changes")
|
|
57
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
58
|
+
.action((opts: { path: string }) => {
|
|
59
|
+
render(<ChatCommand path={opts.path} />);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
program
|
|
63
|
+
.command("history")
|
|
64
|
+
.alias("timeline")
|
|
65
|
+
.description(
|
|
66
|
+
"Explore your code history — see commits, changes, and evolution",
|
|
67
|
+
)
|
|
68
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
69
|
+
.action((opts: { path: string }) => {
|
|
70
|
+
render(<TimelineCommand path={opts.path} />);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
program
|
|
74
|
+
.command("crimes [files...]")
|
|
75
|
+
.alias("commit")
|
|
76
|
+
.description(
|
|
77
|
+
"Generate a smart conventional commit message from staged changes or specific files",
|
|
78
|
+
)
|
|
79
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
80
|
+
.option(
|
|
81
|
+
"--auto",
|
|
82
|
+
"Stage all changes (or the given files) and commit without confirmation",
|
|
83
|
+
)
|
|
84
|
+
.option("--confirm", "Show preview before committing even when using --auto")
|
|
85
|
+
.option("--preview", "Show the generated message without committing")
|
|
86
|
+
.option("--push", "Push to remote after committing")
|
|
87
|
+
.action(
|
|
88
|
+
(
|
|
89
|
+
files: string[],
|
|
90
|
+
opts: {
|
|
91
|
+
path: string;
|
|
92
|
+
auto: boolean;
|
|
93
|
+
confirm: boolean;
|
|
94
|
+
preview: boolean;
|
|
95
|
+
push: boolean;
|
|
96
|
+
},
|
|
97
|
+
) => {
|
|
98
|
+
render(
|
|
99
|
+
<CommitCommand
|
|
100
|
+
path={opts.path}
|
|
101
|
+
files={files ?? []}
|
|
102
|
+
auto={opts.auto ?? false}
|
|
103
|
+
confirm={opts.confirm ?? false}
|
|
104
|
+
preview={opts.preview ?? false}
|
|
105
|
+
push={opts.push ?? false}
|
|
106
|
+
/>,
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
program
|
|
112
|
+
.command("watch <cmd>")
|
|
113
|
+
.alias("spy")
|
|
114
|
+
.description("Watch a dev command and get AI suggestions for errors")
|
|
115
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
116
|
+
.option("--clean", "Only show AI suggestions, hide raw logs")
|
|
117
|
+
.option("--fix-all", "Auto-apply fixes as errors are detected")
|
|
118
|
+
.option("--auto-restart", "Automatically re-run the command after a crash")
|
|
119
|
+
.option("--prompt <text>", "Extra context for the AI about your project")
|
|
120
|
+
.action(
|
|
121
|
+
(
|
|
122
|
+
cmd: string,
|
|
123
|
+
opts: {
|
|
124
|
+
path: string;
|
|
125
|
+
clean: boolean;
|
|
126
|
+
fixAll: boolean;
|
|
127
|
+
autoRestart: boolean;
|
|
128
|
+
prompt?: string;
|
|
129
|
+
},
|
|
130
|
+
) => {
|
|
131
|
+
render(
|
|
132
|
+
<WatchCommand
|
|
133
|
+
cmd={cmd}
|
|
134
|
+
path={opts.path}
|
|
135
|
+
clean={opts.clean ?? false}
|
|
136
|
+
fixAll={opts.fixAll ?? false}
|
|
137
|
+
autoRestart={opts.autoRestart ?? false}
|
|
138
|
+
prompt={opts.prompt}
|
|
139
|
+
/>,
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from "child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export type WatchProcess = {
|
|
6
|
+
kill: () => void;
|
|
7
|
+
onLog: (cb: (line: string, isErr: boolean) => void) => void;
|
|
8
|
+
onError: (cb: (chunk: ErrorChunk) => void) => void;
|
|
9
|
+
onExit: (cb: (code: number | null) => void) => void;
|
|
10
|
+
onInputRequest: (cb: (prompt: string) => void) => void;
|
|
11
|
+
sendInput: (text: string) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ErrorChunk = {
|
|
15
|
+
raw: string;
|
|
16
|
+
lines: string[];
|
|
17
|
+
contextBefore: string[];
|
|
18
|
+
filePath?: string;
|
|
19
|
+
lineNumber?: number;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type Suggestion = {
|
|
24
|
+
id: string;
|
|
25
|
+
errorSummary: string;
|
|
26
|
+
simplified: string;
|
|
27
|
+
fix: string;
|
|
28
|
+
filePath?: string;
|
|
29
|
+
patch?: { path: string; content: string; isNew: boolean };
|
|
30
|
+
timestamp: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const ERROR_PATTERNS = [
|
|
34
|
+
/error:/i,
|
|
35
|
+
/TypeError/,
|
|
36
|
+
/ReferenceError/,
|
|
37
|
+
/SyntaxError/,
|
|
38
|
+
/RangeError/,
|
|
39
|
+
/NameError/,
|
|
40
|
+
/AttributeError/,
|
|
41
|
+
/KeyError/,
|
|
42
|
+
/IndexError/,
|
|
43
|
+
/ImportError/,
|
|
44
|
+
/ModuleNotFoundError/,
|
|
45
|
+
/ZeroDivisionError/,
|
|
46
|
+
/ValueError/,
|
|
47
|
+
/RuntimeError/,
|
|
48
|
+
/Traceback \(most recent call last\)/,
|
|
49
|
+
/Cannot find module/,
|
|
50
|
+
/Cannot read propert/,
|
|
51
|
+
/is not defined/,
|
|
52
|
+
/is not a function/,
|
|
53
|
+
/Unhandled/,
|
|
54
|
+
/ENOENT/,
|
|
55
|
+
/EADDRINUSE/,
|
|
56
|
+
/failed to compile/i,
|
|
57
|
+
/Build failed/i,
|
|
58
|
+
/Module not found/i,
|
|
59
|
+
/unexpected token/i,
|
|
60
|
+
/Expected/,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const NOISE_PATTERNS = [
|
|
64
|
+
/^\s*at\s+/,
|
|
65
|
+
/^\s*\^+\s*$/,
|
|
66
|
+
/^\s*$/,
|
|
67
|
+
/^\s*warn/i,
|
|
68
|
+
/deprecat/i,
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const INPUT_REQUEST_PATTERNS = [
|
|
72
|
+
/:\s*$/,
|
|
73
|
+
/\?\s*$/,
|
|
74
|
+
/>\s*$/,
|
|
75
|
+
/input/i,
|
|
76
|
+
/enter\s+\w/i,
|
|
77
|
+
/type\s+\w/i,
|
|
78
|
+
/press\s+\w/i,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
function isErrorLine(line: string): boolean {
|
|
82
|
+
return ERROR_PATTERNS.some((p) => p.test(line));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isNoise(line: string): boolean {
|
|
86
|
+
return NOISE_PATTERNS.some((p) => p.test(line));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isInputRequest(line: string): boolean {
|
|
90
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
91
|
+
if (!stripped) return false;
|
|
92
|
+
return INPUT_REQUEST_PATTERNS.some((p) => p.test(stripped));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractFilePath(lines: string[]): {
|
|
96
|
+
filePath?: string;
|
|
97
|
+
lineNumber?: number;
|
|
98
|
+
} {
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const pyM = line.match(/File "([^"]+\.py)",\s*line\s*(\d+)/);
|
|
101
|
+
if (pyM) {
|
|
102
|
+
return { filePath: pyM[1], lineNumber: parseInt(pyM[2]!, 10) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const m = line.match(
|
|
106
|
+
/([./][\w./\\-]+\.(tsx?|jsx?|mjs|cjs|ts|js|py)):(\d+)/,
|
|
107
|
+
);
|
|
108
|
+
if (m) {
|
|
109
|
+
return { filePath: m[1], lineNumber: parseInt(m[3]!, 10) };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const absM = line.match(/\(([^)]+\.(tsx?|jsx?|ts|js)):(\d+)/);
|
|
113
|
+
if (absM) {
|
|
114
|
+
return { filePath: absM[1], lineNumber: parseInt(absM[3]!, 10) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function spawnWatch(cmd: string, cwd: string): WatchProcess {
|
|
121
|
+
const [bin, ...args] = cmd.split(/\s+/) as [string, ...string[]];
|
|
122
|
+
|
|
123
|
+
const child: ChildProcess = spawn(bin, args, {
|
|
124
|
+
cwd,
|
|
125
|
+
shell: true,
|
|
126
|
+
env: { ...process.env, FORCE_COLOR: "1" },
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const logCallbacks: ((line: string, isErr: boolean) => void)[] = [];
|
|
131
|
+
const errorCallbacks: ((chunk: ErrorChunk) => void)[] = [];
|
|
132
|
+
const exitCallbacks: ((code: number | null) => void)[] = [];
|
|
133
|
+
const inputRequestCallbacks: ((prompt: string) => void)[] = [];
|
|
134
|
+
|
|
135
|
+
const recentLines: string[] = [];
|
|
136
|
+
let errorBuffer: string[] = [];
|
|
137
|
+
let errorTimer: ReturnType<typeof setTimeout> | null = null;
|
|
138
|
+
const seenErrors = new Set<string>();
|
|
139
|
+
|
|
140
|
+
const flushError = () => {
|
|
141
|
+
if (errorBuffer.length === 0) return;
|
|
142
|
+
|
|
143
|
+
const raw = errorBuffer.join("\n");
|
|
144
|
+
const key = raw.slice(0, 120);
|
|
145
|
+
|
|
146
|
+
if (seenErrors.has(key)) {
|
|
147
|
+
errorBuffer = [];
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
seenErrors.add(key);
|
|
151
|
+
|
|
152
|
+
const { filePath, lineNumber } = extractFilePath(errorBuffer);
|
|
153
|
+
|
|
154
|
+
const chunk: ErrorChunk = {
|
|
155
|
+
raw,
|
|
156
|
+
lines: errorBuffer.filter((l) => !isNoise(l)).slice(0, 20),
|
|
157
|
+
contextBefore: recentLines.slice(-15),
|
|
158
|
+
filePath,
|
|
159
|
+
lineNumber,
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
errorCallbacks.forEach((cb) => cb(chunk));
|
|
164
|
+
errorBuffer = [];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const processLine = (line: string, isErr: boolean) => {
|
|
168
|
+
recentLines.push(line);
|
|
169
|
+
if (recentLines.length > 30) recentLines.shift();
|
|
170
|
+
|
|
171
|
+
logCallbacks.forEach((cb) => cb(line, isErr));
|
|
172
|
+
|
|
173
|
+
if (isErrorLine(line)) {
|
|
174
|
+
errorBuffer.push(line);
|
|
175
|
+
if (errorTimer) clearTimeout(errorTimer);
|
|
176
|
+
errorTimer = setTimeout(flushError, 300);
|
|
177
|
+
} else if (errorBuffer.length > 0) {
|
|
178
|
+
errorBuffer.push(line);
|
|
179
|
+
if (errorTimer) clearTimeout(errorTimer);
|
|
180
|
+
errorTimer = setTimeout(flushError, 300);
|
|
181
|
+
} else if (!isErr && isInputRequest(line)) {
|
|
182
|
+
inputRequestCallbacks.forEach((cb) => cb(line.trim()));
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
187
|
+
data
|
|
188
|
+
.toString()
|
|
189
|
+
.split("\n")
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.forEach((l) => processLine(l, false));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
child.stderr?.on("data", (data: Buffer) => {
|
|
195
|
+
data
|
|
196
|
+
.toString()
|
|
197
|
+
.split("\n")
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
.forEach((l) => processLine(l, true));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
child.on("close", (code) => {
|
|
203
|
+
if (errorTimer) clearTimeout(errorTimer);
|
|
204
|
+
flushError();
|
|
205
|
+
exitCallbacks.forEach((cb) => cb(code));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
kill: () => child.kill(),
|
|
210
|
+
onLog: (cb) => logCallbacks.push(cb),
|
|
211
|
+
onError: (cb) => errorCallbacks.push(cb),
|
|
212
|
+
onExit: (cb) => exitCallbacks.push(cb),
|
|
213
|
+
onInputRequest: (cb) => inputRequestCallbacks.push(cb),
|
|
214
|
+
sendInput: (text) => {
|
|
215
|
+
child.stdin?.write(text + "\n");
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function readFileContext(
|
|
221
|
+
filePath: string,
|
|
222
|
+
repoPath: string,
|
|
223
|
+
lineNumber?: number,
|
|
224
|
+
): string {
|
|
225
|
+
const candidates = [
|
|
226
|
+
filePath,
|
|
227
|
+
path.join(repoPath, filePath),
|
|
228
|
+
path.resolve(repoPath, filePath),
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
for (const p of candidates) {
|
|
232
|
+
if (!existsSync(p)) continue;
|
|
233
|
+
try {
|
|
234
|
+
const content = readFileSync(p, "utf-8");
|
|
235
|
+
if (!lineNumber) return content.slice(0, 3000);
|
|
236
|
+
|
|
237
|
+
const lines = content.split("\n");
|
|
238
|
+
const start = Math.max(0, lineNumber - 30);
|
|
239
|
+
const end = Math.min(lines.length, lineNumber + 30);
|
|
240
|
+
return lines
|
|
241
|
+
.slice(start, end)
|
|
242
|
+
.map((l, i) => `${start + i + 1}: ${l}`)
|
|
243
|
+
.join("\n");
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return "";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function readPackageJson(repoPath: string): string {
|
|
252
|
+
const p = path.join(repoPath, "package.json");
|
|
253
|
+
if (!existsSync(p)) return "";
|
|
254
|
+
try {
|
|
255
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8")) as Record<string, unknown>;
|
|
256
|
+
const deps = {
|
|
257
|
+
...((pkg.dependencies as object) ?? {}),
|
|
258
|
+
...((pkg.devDependencies as object) ?? {}),
|
|
259
|
+
};
|
|
260
|
+
return Object.keys(deps).slice(0, 30).join(", ");
|
|
261
|
+
} catch {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function buildWatchPrompt(
|
|
267
|
+
chunk: ErrorChunk,
|
|
268
|
+
fileContext: string,
|
|
269
|
+
deps: string,
|
|
270
|
+
repoPath: string,
|
|
271
|
+
): string {
|
|
272
|
+
return `You are a senior developer assistant watching a dev server. An error just occurred.
|
|
273
|
+
|
|
274
|
+
Error output:
|
|
275
|
+
\`\`\`
|
|
276
|
+
${chunk.lines.join("\n").slice(0, 2000)}
|
|
277
|
+
\`\`\`
|
|
278
|
+
|
|
279
|
+
${chunk.contextBefore.length > 0 ? `Log context (lines before error):\n\`\`\`\n${chunk.contextBefore.join("\n")}\n\`\`\`` : ""}
|
|
280
|
+
|
|
281
|
+
${fileContext ? `File content${chunk.lineNumber ? ` (around line ${chunk.lineNumber})` : ""}:\n\`\`\`\n${fileContext.slice(0, 2500)}\n\`\`\`` : ""}
|
|
282
|
+
|
|
283
|
+
${deps ? `Project dependencies: ${deps}` : ""}
|
|
284
|
+
Repo path: ${repoPath}
|
|
285
|
+
${chunk.filePath ? `Error in file: ${chunk.filePath}` : ""}
|
|
286
|
+
|
|
287
|
+
Respond ONLY with a JSON object (no markdown, no backticks) with this exact shape:
|
|
288
|
+
{
|
|
289
|
+
"errorSummary": "one line — what went wrong",
|
|
290
|
+
"simplified": "2-3 sentences in plain language — what this error means and why it usually happens",
|
|
291
|
+
"fix": "specific actionable fix — reference actual file names and line numbers if known",
|
|
292
|
+
"patch": null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
If you can provide a code fix, replace "patch" with:
|
|
296
|
+
{
|
|
297
|
+
"path": "relative/file/path.ts",
|
|
298
|
+
"content": "complete corrected file content",
|
|
299
|
+
"isNew": false
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
Rules:
|
|
303
|
+
- Be specific — mention actual files, variables, function names from the error
|
|
304
|
+
- Don't be generic ("check if variable is defined") — say WHERE
|
|
305
|
+
- patch should only be included when you are confident in the fix
|
|
306
|
+
- Keep simplified under 60 words`;
|
|
307
|
+
}
|