fraude-code 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/README.md +68 -0
- package/dist/index.js +179297 -0
- package/package.json +88 -0
- package/src/agent/agent.ts +475 -0
- package/src/agent/contextManager.ts +141 -0
- package/src/agent/index.ts +14 -0
- package/src/agent/pendingChanges.ts +270 -0
- package/src/agent/prompts/AskPrompt.txt +10 -0
- package/src/agent/prompts/FastPrompt.txt +40 -0
- package/src/agent/prompts/PlannerPrompt.txt +51 -0
- package/src/agent/prompts/ReviewerPrompt.txt +57 -0
- package/src/agent/prompts/WorkerPrompt.txt +33 -0
- package/src/agent/subagents/askAgent.ts +37 -0
- package/src/agent/subagents/extractionAgent.ts +123 -0
- package/src/agent/subagents/fastAgent.ts +45 -0
- package/src/agent/subagents/managerAgent.ts +36 -0
- package/src/agent/subagents/relationAgent.ts +76 -0
- package/src/agent/subagents/researchSubAgent.ts +79 -0
- package/src/agent/subagents/reviewerSubAgent.ts +42 -0
- package/src/agent/subagents/workerSubAgent.ts +42 -0
- package/src/agent/tools/bashTool.ts +94 -0
- package/src/agent/tools/descriptions/bash.txt +47 -0
- package/src/agent/tools/descriptions/edit.txt +7 -0
- package/src/agent/tools/descriptions/glob.txt +4 -0
- package/src/agent/tools/descriptions/grep.txt +8 -0
- package/src/agent/tools/descriptions/lsp.txt +20 -0
- package/src/agent/tools/descriptions/plan.txt +3 -0
- package/src/agent/tools/descriptions/read.txt +9 -0
- package/src/agent/tools/descriptions/todo.txt +12 -0
- package/src/agent/tools/descriptions/write.txt +8 -0
- package/src/agent/tools/editTool.ts +44 -0
- package/src/agent/tools/globTool.ts +59 -0
- package/src/agent/tools/grepTool.ts +343 -0
- package/src/agent/tools/lspTool.ts +429 -0
- package/src/agent/tools/planTool.ts +118 -0
- package/src/agent/tools/readTool.ts +78 -0
- package/src/agent/tools/rememberTool.ts +91 -0
- package/src/agent/tools/testRunnerTool.ts +77 -0
- package/src/agent/tools/testTool.ts +44 -0
- package/src/agent/tools/todoTool.ts +224 -0
- package/src/agent/tools/writeTool.ts +33 -0
- package/src/commands/COMMANDS.ts +38 -0
- package/src/commands/cerebras/auth.ts +27 -0
- package/src/commands/cerebras/index.ts +31 -0
- package/src/commands/forget.ts +29 -0
- package/src/commands/google/auth.ts +24 -0
- package/src/commands/google/index.ts +31 -0
- package/src/commands/groq/add_model.ts +60 -0
- package/src/commands/groq/auth.ts +24 -0
- package/src/commands/groq/index.ts +33 -0
- package/src/commands/index.ts +65 -0
- package/src/commands/knowledge.ts +92 -0
- package/src/commands/log.ts +32 -0
- package/src/commands/mistral/auth.ts +27 -0
- package/src/commands/mistral/index.ts +31 -0
- package/src/commands/model/index.ts +145 -0
- package/src/commands/models/index.ts +16 -0
- package/src/commands/ollama/index.ts +29 -0
- package/src/commands/openrouter/add_model.ts +64 -0
- package/src/commands/openrouter/auth.ts +24 -0
- package/src/commands/openrouter/index.ts +33 -0
- package/src/commands/remember.ts +48 -0
- package/src/commands/serve.ts +31 -0
- package/src/commands/session/index.ts +21 -0
- package/src/commands/usage.ts +15 -0
- package/src/commands/visualize.ts +773 -0
- package/src/components/App.tsx +55 -0
- package/src/components/IntroComponent.tsx +70 -0
- package/src/components/LoaderComponent.tsx +68 -0
- package/src/components/OutputRenderer.tsx +88 -0
- package/src/components/SettingsRenderer.tsx +23 -0
- package/src/components/input/CommandSuggestions.tsx +41 -0
- package/src/components/input/FileSuggestions.tsx +61 -0
- package/src/components/input/InputBox.tsx +371 -0
- package/src/components/output/CheckpointView.tsx +13 -0
- package/src/components/output/CommandView.tsx +13 -0
- package/src/components/output/CommentView.tsx +12 -0
- package/src/components/output/ConfirmationView.tsx +179 -0
- package/src/components/output/ContextUsage.tsx +62 -0
- package/src/components/output/DiffView.tsx +202 -0
- package/src/components/output/ErrorView.tsx +14 -0
- package/src/components/output/InteractiveServerView.tsx +69 -0
- package/src/components/output/KnowledgeView.tsx +220 -0
- package/src/components/output/MarkdownView.tsx +15 -0
- package/src/components/output/ModelSelectView.tsx +71 -0
- package/src/components/output/ReasoningView.tsx +21 -0
- package/src/components/output/ToolCallView.tsx +45 -0
- package/src/components/settings/ModelList.tsx +250 -0
- package/src/components/settings/TokenUsage.tsx +274 -0
- package/src/config/schema.ts +19 -0
- package/src/config/settings.ts +229 -0
- package/src/index.tsx +100 -0
- package/src/parsers/tree-sitter-python.wasm +0 -0
- package/src/providers/providers.ts +71 -0
- package/src/services/PluginLoader.ts +123 -0
- package/src/services/cerebras.ts +69 -0
- package/src/services/embeddingService.ts +229 -0
- package/src/services/google.ts +65 -0
- package/src/services/graphSerializer.ts +248 -0
- package/src/services/groq.ts +23 -0
- package/src/services/knowledgeOrchestrator.ts +286 -0
- package/src/services/mistral.ts +79 -0
- package/src/services/ollama.ts +109 -0
- package/src/services/openrouter.ts +23 -0
- package/src/services/symbolExtractor.ts +277 -0
- package/src/store/useFraudeStore.ts +123 -0
- package/src/store/useSettingsStore.ts +38 -0
- package/src/theme.ts +26 -0
- package/src/types/Agent.ts +147 -0
- package/src/types/CommandDefinition.ts +8 -0
- package/src/types/Model.ts +94 -0
- package/src/types/OutputItem.ts +24 -0
- package/src/types/PluginContext.ts +55 -0
- package/src/types/TokenUsage.ts +5 -0
- package/src/types/assets.d.ts +4 -0
- package/src/utils/agentCognition.ts +1152 -0
- package/src/utils/fileSuggestions.ts +111 -0
- package/src/utils/index.ts +17 -0
- package/src/utils/initFraude.ts +8 -0
- package/src/utils/logger.ts +24 -0
- package/src/utils/lspClient.ts +1415 -0
- package/src/utils/paths.ts +24 -0
- package/src/utils/queryHandler.ts +227 -0
- package/src/utils/router.ts +278 -0
- package/src/utils/streamHandler.ts +132 -0
- package/src/utils/treeSitterQueries.ts +125 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { getLSPClient } from "@/utils/lspClient";
|
|
5
|
+
import pendingChanges from "@/agent/pendingChanges";
|
|
6
|
+
import { projectPath } from "@/utils";
|
|
7
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
8
|
+
import DESCRIPTION from "./descriptions/lsp.txt" with { type: "text" };
|
|
9
|
+
|
|
10
|
+
const { updateOutput } = useFraudeStore.getState();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find a symbol's position in the file content
|
|
14
|
+
* Returns the line and character where the symbol starts
|
|
15
|
+
*/
|
|
16
|
+
function findSymbolPosition(
|
|
17
|
+
content: string,
|
|
18
|
+
symbolName: string,
|
|
19
|
+
occurrence: number = 1,
|
|
20
|
+
): { line: number; character: number } | null {
|
|
21
|
+
const lines = content.split("\n");
|
|
22
|
+
let found = 0;
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < lines.length; i++) {
|
|
25
|
+
const line = lines[i]!;
|
|
26
|
+
let searchStart = 0;
|
|
27
|
+
|
|
28
|
+
while (true) {
|
|
29
|
+
const idx = line.indexOf(symbolName, searchStart);
|
|
30
|
+
if (idx === -1) break;
|
|
31
|
+
|
|
32
|
+
// Check it's a word boundary (not part of a larger identifier)
|
|
33
|
+
const beforeChar = idx > 0 ? line[idx - 1] : " ";
|
|
34
|
+
const afterChar =
|
|
35
|
+
idx + symbolName.length < line.length
|
|
36
|
+
? line[idx + symbolName.length]
|
|
37
|
+
: " ";
|
|
38
|
+
const isWordBoundary =
|
|
39
|
+
!/[a-zA-Z0-9_]/.test(beforeChar!) && !/[a-zA-Z0-9_]/.test(afterChar!);
|
|
40
|
+
|
|
41
|
+
if (isWordBoundary) {
|
|
42
|
+
found++;
|
|
43
|
+
if (found === occurrence) {
|
|
44
|
+
return { line: i + 1, character: idx + 1 }; // 1-indexed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
searchStart = idx + 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* LSP Tool - Code analysis using symbol names (LLM-friendly)
|
|
57
|
+
*/
|
|
58
|
+
const lspTool = tool({
|
|
59
|
+
description: DESCRIPTION,
|
|
60
|
+
strict: true,
|
|
61
|
+
inputSchema: z.object({
|
|
62
|
+
command: z
|
|
63
|
+
.enum([
|
|
64
|
+
"analyze",
|
|
65
|
+
"lookup",
|
|
66
|
+
"info",
|
|
67
|
+
"references",
|
|
68
|
+
"implementation",
|
|
69
|
+
"symbols",
|
|
70
|
+
"workspace_symbols",
|
|
71
|
+
"call_hierarchy",
|
|
72
|
+
"incoming_calls",
|
|
73
|
+
"outgoing_calls",
|
|
74
|
+
])
|
|
75
|
+
.describe(
|
|
76
|
+
"analyze: check for errors | lookup: find definition | info: get type/docs | references: find usages | implementation: find implementations | symbols: list document symbols | workspace_symbols: search workspace | call_hierarchy: get call hierarchy | incoming_calls: who calls this | outgoing_calls: what this calls",
|
|
77
|
+
),
|
|
78
|
+
filePath: z.string().describe("Relative path to the file"),
|
|
79
|
+
symbol: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(
|
|
83
|
+
"Name of the symbol (required for lookup/info/references/implementation/call_hierarchy/incoming_calls/outgoing_calls)",
|
|
84
|
+
),
|
|
85
|
+
query: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Search query for workspace_symbols command"),
|
|
89
|
+
occurrence: z
|
|
90
|
+
.number()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Which occurrence of the symbol to use (default: 1 = first)"),
|
|
93
|
+
}),
|
|
94
|
+
execute: async ({ command, filePath, symbol, query, occurrence = 1 }) => {
|
|
95
|
+
const client = getLSPClient(process.cwd());
|
|
96
|
+
// Resolve path, handling @/ alias which maps to project root
|
|
97
|
+
let fullPath = filePath;
|
|
98
|
+
if (fullPath.startsWith("@/")) {
|
|
99
|
+
fullPath = fullPath.replace("@/", "");
|
|
100
|
+
fullPath = path.resolve(process.cwd(), fullPath);
|
|
101
|
+
} else {
|
|
102
|
+
fullPath = path.resolve(process.cwd(), filePath);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get file content including pending changes
|
|
106
|
+
const content = await pendingChanges.getLatestContent(fullPath);
|
|
107
|
+
|
|
108
|
+
if (!content) {
|
|
109
|
+
return `Error: File not found: ${filePath}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!client.isSupported(filePath)) {
|
|
113
|
+
const supported = client.getSupportedExtensions().join(", ");
|
|
114
|
+
return `Error: File type not supported. Supported: ${supported}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
let result = "";
|
|
119
|
+
switch (command) {
|
|
120
|
+
case "analyze": {
|
|
121
|
+
const { errors, warnings } = await client.getDiagnostics(
|
|
122
|
+
fullPath,
|
|
123
|
+
content,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
127
|
+
result = "✓ No errors or warnings found.";
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parts: string[] = [];
|
|
132
|
+
if (errors.length > 0) {
|
|
133
|
+
parts.push(
|
|
134
|
+
`ERRORS (${errors.length}):\n${errors.map((e) => ` ✗ ${e}`).join("\n")}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (warnings.length > 0) {
|
|
138
|
+
parts.push(
|
|
139
|
+
`WARNINGS (${warnings.length}):\n${warnings.map((w) => ` ⚠ ${w}`).join("\n")}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
result = parts.join("\n\n");
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case "lookup": {
|
|
147
|
+
if (!symbol) {
|
|
148
|
+
return "Error: 'symbol' is required. Provide the name of the function, class, or variable to look up.";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
152
|
+
if (!pos) {
|
|
153
|
+
return `Symbol '${symbol}' not found in file. Check the spelling or try a different occurrence.`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const def = await client.findDefinition(
|
|
157
|
+
fullPath,
|
|
158
|
+
content,
|
|
159
|
+
pos.line,
|
|
160
|
+
pos.character,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!def) {
|
|
164
|
+
return `No definition found for '${symbol}'. It may be a built-in or the LSP couldn't resolve it.`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
result = `DEFINITION OF '${symbol}':\n File: ${def.file}\n Line: ${def.line}`;
|
|
168
|
+
if (def.preview) {
|
|
169
|
+
result += `\n\nCODE:\n${def.preview}`;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case "info": {
|
|
175
|
+
if (!symbol) {
|
|
176
|
+
return "Error: 'symbol' is required. Provide the name of the symbol to get info about.";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
180
|
+
if (!pos) {
|
|
181
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const info = await client.getSymbolInfo(
|
|
185
|
+
fullPath,
|
|
186
|
+
content,
|
|
187
|
+
pos.line,
|
|
188
|
+
pos.character,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!info) {
|
|
192
|
+
return `No type information found for '${symbol}'.`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
result = `INFO FOR '${symbol}':\n${info}`;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case "references": {
|
|
200
|
+
if (!symbol) {
|
|
201
|
+
return "Error: 'symbol' is required. Provide the name of the symbol to find references for.";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
205
|
+
if (!pos) {
|
|
206
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const refs = await client.findReferences(
|
|
210
|
+
fullPath,
|
|
211
|
+
content,
|
|
212
|
+
pos.line,
|
|
213
|
+
pos.character,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (refs.length === 0) {
|
|
217
|
+
return `No references found for '${symbol}'.`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Group by file
|
|
221
|
+
const grouped: Record<string, number[]> = {};
|
|
222
|
+
for (const ref of refs) {
|
|
223
|
+
const shortPath = ref.file.replace(projectPath(""), "");
|
|
224
|
+
if (!grouped[shortPath]) grouped[shortPath] = [];
|
|
225
|
+
grouped[shortPath]!.push(ref.line);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const parts = Object.entries(grouped).map(
|
|
229
|
+
([file, lines]) => ` ${file}: lines ${lines.join(", ")}`,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
result = `REFERENCES TO '${symbol}' (${refs.length} total):\n${parts.join("\n")}`;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case "implementation": {
|
|
237
|
+
if (!symbol) {
|
|
238
|
+
return "Error: 'symbol' is required. Provide the name of the interface or method.";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
242
|
+
if (!pos) {
|
|
243
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const impls = await client.findImplementation(
|
|
247
|
+
fullPath,
|
|
248
|
+
content,
|
|
249
|
+
pos.line,
|
|
250
|
+
pos.character,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (impls.length === 0) {
|
|
254
|
+
return `No implementations found for '${symbol}'.`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const parts = impls.map((impl) => {
|
|
258
|
+
const shortPath = impl.file.replace(projectPath(""), "");
|
|
259
|
+
let res = ` ${shortPath}:${impl.line}`;
|
|
260
|
+
if (impl.preview) {
|
|
261
|
+
res += `\n ${impl.preview.split("\n")[0]}`;
|
|
262
|
+
}
|
|
263
|
+
return res;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
result = `IMPLEMENTATIONS OF '${symbol}' (${impls.length} total):\n${parts.join("\n")}`;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case "symbols": {
|
|
271
|
+
const symbols = await client.getDocumentSymbols(fullPath, content);
|
|
272
|
+
|
|
273
|
+
if (symbols.length === 0) {
|
|
274
|
+
return "No symbols found in document.";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const formatSymbol = (
|
|
278
|
+
sym: { name: string; kind: string; line: number; children?: any[] },
|
|
279
|
+
indent: string = "",
|
|
280
|
+
): string => {
|
|
281
|
+
let res = `${indent}${sym.kind} ${sym.name} (line ${sym.line})`;
|
|
282
|
+
if (sym.children && sym.children.length > 0) {
|
|
283
|
+
for (const child of sym.children) {
|
|
284
|
+
res += "\n" + formatSymbol(child, indent + " ");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return res;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const lines = symbols.map((s) => formatSymbol(s));
|
|
291
|
+
result = `SYMBOLS IN ${filePath} (${symbols.length} top-level):\n${lines.join("\n")}`;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case "workspace_symbols": {
|
|
296
|
+
if (!query) {
|
|
297
|
+
return "Error: 'query' is required for workspace_symbols. Provide a search term.";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const symbols = await client.searchWorkspaceSymbols(query, fullPath);
|
|
301
|
+
|
|
302
|
+
if (symbols.length === 0) {
|
|
303
|
+
return `No symbols matching '${query}' found in workspace.`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const lines = symbols.slice(0, 50).map((s) => {
|
|
307
|
+
const shortPath = s.file.replace(projectPath(""), "");
|
|
308
|
+
return ` ${s.kind} ${s.name} - ${shortPath}:${s.line}`;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
result = `WORKSPACE SYMBOLS MATCHING '${query}' (${symbols.length} found):\n${lines.join("\n")}`;
|
|
312
|
+
if (symbols.length > 50) {
|
|
313
|
+
result += `\n ... and ${symbols.length - 50} more`;
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case "call_hierarchy": {
|
|
319
|
+
if (!symbol) {
|
|
320
|
+
return "Error: 'symbol' is required. Provide the name of the function or method.";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
324
|
+
if (!pos) {
|
|
325
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const items = await client.prepareCallHierarchy(
|
|
329
|
+
fullPath,
|
|
330
|
+
content,
|
|
331
|
+
pos.line,
|
|
332
|
+
pos.character,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (items.length === 0) {
|
|
336
|
+
return `No call hierarchy found for '${symbol}'. It may not be a function/method.`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const lines = items.map((item) => {
|
|
340
|
+
const shortPath = item.file.replace(projectPath(""), "");
|
|
341
|
+
return ` ${item.kind} ${item.name} - ${shortPath}:${item.line}`;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
result = `CALL HIERARCHY FOR '${symbol}':\n${lines.join("\n")}`;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
case "incoming_calls": {
|
|
349
|
+
if (!symbol) {
|
|
350
|
+
return "Error: 'symbol' is required. Provide the name of the function or method.";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
354
|
+
if (!pos) {
|
|
355
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const calls = await client.getIncomingCalls(
|
|
359
|
+
fullPath,
|
|
360
|
+
content,
|
|
361
|
+
pos.line,
|
|
362
|
+
pos.character,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
if (calls.length === 0) {
|
|
366
|
+
return `No incoming calls found for '${symbol}'.`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const lines = calls.map((call) => {
|
|
370
|
+
const shortPath = call.file.replace(projectPath(""), "");
|
|
371
|
+
return ` ${call.name} - ${shortPath}:${call.line}`;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
result = `FUNCTIONS CALLING '${symbol}' (${calls.length} total):\n${lines.join("\n")}`;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case "outgoing_calls": {
|
|
379
|
+
if (!symbol) {
|
|
380
|
+
return "Error: 'symbol' is required. Provide the name of the function or method.";
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const pos = findSymbolPosition(content, symbol, occurrence);
|
|
384
|
+
if (!pos) {
|
|
385
|
+
return `Symbol '${symbol}' not found in file.`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const calls = await client.getOutgoingCalls(
|
|
389
|
+
fullPath,
|
|
390
|
+
content,
|
|
391
|
+
pos.line,
|
|
392
|
+
pos.character,
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
if (calls.length === 0) {
|
|
396
|
+
return `No outgoing calls found from '${symbol}'.`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const lines = calls.map((call) => {
|
|
400
|
+
const shortPath = call.file.replace(projectPath(""), "");
|
|
401
|
+
return ` ${call.name} - ${shortPath}:${call.line}`;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
result = `FUNCTIONS CALLED BY '${symbol}' (${calls.length} total):\n${lines.join("\n")}`;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
default:
|
|
409
|
+
return "Unknown command.";
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
updateOutput(
|
|
413
|
+
"toolCall",
|
|
414
|
+
JSON.stringify({
|
|
415
|
+
action: `LSP: ${command}`,
|
|
416
|
+
details: `${projectPath(filePath)} ${symbol ? `(${symbol})` : ""}`,
|
|
417
|
+
result: result,
|
|
418
|
+
}),
|
|
419
|
+
{ dontOverride: true },
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
return result;
|
|
423
|
+
} catch (err: any) {
|
|
424
|
+
return `LSP Error: ${err.message}`;
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
export default lspTool;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
5
|
+
|
|
6
|
+
const { updateOutput } = useFraudeStore.getState();
|
|
7
|
+
|
|
8
|
+
// Path configuration
|
|
9
|
+
const FRAUDE_DIR = path.join(process.cwd(), ".fraude");
|
|
10
|
+
const PLAN_FILE = path.join(FRAUDE_DIR, "plan.md");
|
|
11
|
+
|
|
12
|
+
// Ensure .fraude directory exists
|
|
13
|
+
async function ensureDir(): Promise<void> {
|
|
14
|
+
const dir = Bun.file(FRAUDE_DIR);
|
|
15
|
+
if (!(await Bun.file(PLAN_FILE).exists())) {
|
|
16
|
+
await Bun.write(PLAN_FILE, "# Implementation Plan\n\n*No plan yet.*\n");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Read the current plan
|
|
21
|
+
async function readPlan(): Promise<string> {
|
|
22
|
+
await ensureDir();
|
|
23
|
+
const file = Bun.file(PLAN_FILE);
|
|
24
|
+
return await file.text();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Write/overwrite the plan
|
|
28
|
+
async function writePlan(content: string): Promise<void> {
|
|
29
|
+
await ensureDir();
|
|
30
|
+
await Bun.write(PLAN_FILE, content);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const planTool = tool({
|
|
34
|
+
description: `Manage the implementation plan stored in .fraude/plan.md.
|
|
35
|
+
ALWAYS read the plan at the start of a turn to see what to do.
|
|
36
|
+
Update the plan when tasks are assigned or completed.
|
|
37
|
+
|
|
38
|
+
Operations:
|
|
39
|
+
- read: Get the current plan
|
|
40
|
+
- write: Replace the entire plan with new content
|
|
41
|
+
- append: Add content to the end of the plan
|
|
42
|
+
- clear: Reset the plan to empty`,
|
|
43
|
+
strict: true,
|
|
44
|
+
inputSchema: z.object({
|
|
45
|
+
operation: z
|
|
46
|
+
.enum(["read", "write", "append", "clear"])
|
|
47
|
+
.describe("The operation to perform on the plan"),
|
|
48
|
+
content: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Content to write or append (required for write/append)"),
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
execute: async ({ operation, content }) => {
|
|
55
|
+
switch (operation) {
|
|
56
|
+
case "read": {
|
|
57
|
+
const plan = await readPlan();
|
|
58
|
+
updateOutput(
|
|
59
|
+
"toolCall",
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
action: "Read Plan",
|
|
62
|
+
details: `${plan.split("\n").length} lines`,
|
|
63
|
+
result: "✓",
|
|
64
|
+
}),
|
|
65
|
+
{ dontOverride: true },
|
|
66
|
+
);
|
|
67
|
+
return plan;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case "write": {
|
|
71
|
+
if (!content) throw new Error("Content required for write operation");
|
|
72
|
+
await writePlan(content);
|
|
73
|
+
updateOutput(
|
|
74
|
+
"toolCall",
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
action: "Wrote Plan",
|
|
77
|
+
details: `${content.split("\n").length} lines`,
|
|
78
|
+
result: "✓",
|
|
79
|
+
}),
|
|
80
|
+
{ dontOverride: true },
|
|
81
|
+
);
|
|
82
|
+
return "Plan updated successfully.";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "append": {
|
|
86
|
+
if (!content) throw new Error("Content required for append operation");
|
|
87
|
+
const existing = await readPlan();
|
|
88
|
+
await writePlan(existing + "\n" + content);
|
|
89
|
+
updateOutput(
|
|
90
|
+
"toolCall",
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
action: "Appended to Plan",
|
|
93
|
+
details: `+${content.split("\n").length} lines`,
|
|
94
|
+
result: "✓",
|
|
95
|
+
}),
|
|
96
|
+
{ dontOverride: true },
|
|
97
|
+
);
|
|
98
|
+
return "Content appended to plan.";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "clear": {
|
|
102
|
+
await writePlan("# Implementation Plan\n\n*No plan yet.*\n");
|
|
103
|
+
updateOutput(
|
|
104
|
+
"toolCall",
|
|
105
|
+
JSON.stringify({
|
|
106
|
+
action: "Cleared Plan",
|
|
107
|
+
details: "Reset to empty",
|
|
108
|
+
result: "✓",
|
|
109
|
+
}),
|
|
110
|
+
{ dontOverride: true },
|
|
111
|
+
);
|
|
112
|
+
return "Plan cleared.";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export default planTool;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { projectPath } from "@/utils";
|
|
5
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
6
|
+
import pendingChanges from "@/agent/pendingChanges";
|
|
7
|
+
import DESCRIPTION from "./descriptions/read.txt" with { type: "text" };
|
|
8
|
+
|
|
9
|
+
const { updateOutput } = useFraudeStore.getState();
|
|
10
|
+
|
|
11
|
+
const readTool = tool({
|
|
12
|
+
description: DESCRIPTION,
|
|
13
|
+
strict: true,
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
filePath: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe("The path to the file to read. Base path is the project root."),
|
|
18
|
+
offset: z.coerce
|
|
19
|
+
.number()
|
|
20
|
+
.describe("The line number to start reading from (0-based)")
|
|
21
|
+
.optional(),
|
|
22
|
+
limit: z.coerce
|
|
23
|
+
.number()
|
|
24
|
+
.describe("The number of lines to read (defaults to 500)")
|
|
25
|
+
.optional(),
|
|
26
|
+
}),
|
|
27
|
+
execute: async ({
|
|
28
|
+
filePath,
|
|
29
|
+
offset = 0,
|
|
30
|
+
limit = 500,
|
|
31
|
+
}: {
|
|
32
|
+
filePath: string;
|
|
33
|
+
offset?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}) => {
|
|
36
|
+
if (!path.isAbsolute(filePath)) {
|
|
37
|
+
filePath = path.join(process.cwd(), filePath);
|
|
38
|
+
}
|
|
39
|
+
const file = Bun.file(filePath);
|
|
40
|
+
const isStaged = pendingChanges
|
|
41
|
+
.getChanges()
|
|
42
|
+
.some((c) => c.path === filePath);
|
|
43
|
+
if (!isStaged && !(await file.exists())) {
|
|
44
|
+
updateOutput(
|
|
45
|
+
"toolCall",
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
action: "Error Reading " + projectPath(filePath),
|
|
48
|
+
details: "File doesn't exist",
|
|
49
|
+
result: "",
|
|
50
|
+
}),
|
|
51
|
+
{ dontOverride: true },
|
|
52
|
+
);
|
|
53
|
+
throw new Error("File doesn't exist");
|
|
54
|
+
}
|
|
55
|
+
const text = await pendingChanges.getLatestContent(filePath);
|
|
56
|
+
const lines = text.split("\n");
|
|
57
|
+
const lastLine = Math.min(offset + limit, lines.length);
|
|
58
|
+
const result = lines.slice(offset, lastLine).join("\n");
|
|
59
|
+
updateOutput(
|
|
60
|
+
"toolCall",
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
action: "Analyzing " + projectPath(filePath),
|
|
63
|
+
details: "#L" + (offset + 1) + "-" + lastLine,
|
|
64
|
+
result: result,
|
|
65
|
+
}),
|
|
66
|
+
{ dontOverride: true },
|
|
67
|
+
);
|
|
68
|
+
if (text.trim() === "") {
|
|
69
|
+
throw new Error("File is empty");
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
result,
|
|
73
|
+
lastLine,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export default readTool;
|