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,229 @@
|
|
|
1
|
+
import { pipeline, type FeatureExtractionPipeline } from "@xenova/transformers";
|
|
2
|
+
import { connect, type Connection, type Table } from "@lancedb/lancedb";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Embedding Service - Local embeddings with transformers.js + LanceDB storage
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
class EmbeddingService {
|
|
10
|
+
private static instance: EmbeddingService;
|
|
11
|
+
private extractor: FeatureExtractionPipeline | null = null;
|
|
12
|
+
private db: Connection | null = null;
|
|
13
|
+
private table: Table | null = null;
|
|
14
|
+
private initPromise: Promise<void> | null = null;
|
|
15
|
+
|
|
16
|
+
// Model: all-MiniLM-L6-v2 produces 384-dim embeddings
|
|
17
|
+
private static MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
18
|
+
private static TABLE_NAME = "facts";
|
|
19
|
+
|
|
20
|
+
private constructor() {}
|
|
21
|
+
|
|
22
|
+
static getInstance(): EmbeddingService {
|
|
23
|
+
if (!EmbeddingService.instance) {
|
|
24
|
+
EmbeddingService.instance = new EmbeddingService();
|
|
25
|
+
}
|
|
26
|
+
return EmbeddingService.instance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async init(): Promise<void> {
|
|
30
|
+
if (this.initPromise) return this.initPromise;
|
|
31
|
+
this.initPromise = this.doInit();
|
|
32
|
+
return this.initPromise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private async doInit(): Promise<void> {
|
|
36
|
+
// Initialize transformers.js pipeline
|
|
37
|
+
this.extractor = await pipeline(
|
|
38
|
+
"feature-extraction",
|
|
39
|
+
EmbeddingService.MODEL_ID,
|
|
40
|
+
{
|
|
41
|
+
// Use ONNX runtime for Bun compatibility
|
|
42
|
+
revision: "main",
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Initialize LanceDB
|
|
47
|
+
const dbPath = path.join(process.cwd(), ".fraude", "lancedb");
|
|
48
|
+
this.db = await connect(dbPath);
|
|
49
|
+
|
|
50
|
+
// Try to open existing table, or we'll create on first insert
|
|
51
|
+
try {
|
|
52
|
+
this.table = await this.db.openTable(EmbeddingService.TABLE_NAME);
|
|
53
|
+
} catch {
|
|
54
|
+
// Table doesn't exist yet, will create on first insert
|
|
55
|
+
this.table = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate embedding for text
|
|
61
|
+
*/
|
|
62
|
+
async embed(text: string): Promise<number[]> {
|
|
63
|
+
await this.init();
|
|
64
|
+
if (!this.extractor) throw new Error("Extractor not initialized");
|
|
65
|
+
|
|
66
|
+
const output = await this.extractor(text, {
|
|
67
|
+
pooling: "mean",
|
|
68
|
+
normalize: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Convert to regular array
|
|
72
|
+
return Array.from(output.data as Float32Array);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Store a fact with its embedding
|
|
77
|
+
*/
|
|
78
|
+
async store(fact: {
|
|
79
|
+
id: string;
|
|
80
|
+
type: string;
|
|
81
|
+
content: string;
|
|
82
|
+
data?: string;
|
|
83
|
+
timestamp: number;
|
|
84
|
+
sessionId: string;
|
|
85
|
+
confidence: number;
|
|
86
|
+
}): Promise<void> {
|
|
87
|
+
await this.init();
|
|
88
|
+
if (!this.db) throw new Error("Database not initialized");
|
|
89
|
+
|
|
90
|
+
const embedding = await this.embed(fact.content);
|
|
91
|
+
|
|
92
|
+
const record = {
|
|
93
|
+
id: fact.id,
|
|
94
|
+
type: fact.type,
|
|
95
|
+
content: fact.content,
|
|
96
|
+
data: fact.data || "",
|
|
97
|
+
timestamp: fact.timestamp,
|
|
98
|
+
sessionId: fact.sessionId,
|
|
99
|
+
confidence: fact.confidence,
|
|
100
|
+
vector: embedding,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (!this.table) {
|
|
104
|
+
// Create table with first record
|
|
105
|
+
this.table = await this.db.createTable(EmbeddingService.TABLE_NAME, [
|
|
106
|
+
record,
|
|
107
|
+
]);
|
|
108
|
+
} else {
|
|
109
|
+
// Upsert: delete existing record with same ID before adding
|
|
110
|
+
try {
|
|
111
|
+
await this.table.delete(`id = '${fact.id}'`);
|
|
112
|
+
} catch {
|
|
113
|
+
// Record might not exist, continue with insert
|
|
114
|
+
}
|
|
115
|
+
await this.table.add([record]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Search for similar facts using vector similarity
|
|
121
|
+
*/
|
|
122
|
+
async search(
|
|
123
|
+
query: string,
|
|
124
|
+
limit: number = 5,
|
|
125
|
+
threshold: number = 0.5,
|
|
126
|
+
): Promise<
|
|
127
|
+
Array<{
|
|
128
|
+
id: string;
|
|
129
|
+
type: string;
|
|
130
|
+
content: string;
|
|
131
|
+
data: string;
|
|
132
|
+
timestamp: number;
|
|
133
|
+
sessionId: string;
|
|
134
|
+
confidence: number;
|
|
135
|
+
score: number;
|
|
136
|
+
}>
|
|
137
|
+
> {
|
|
138
|
+
await this.init();
|
|
139
|
+
if (!this.table) return [];
|
|
140
|
+
|
|
141
|
+
const queryEmbedding = await this.embed(query);
|
|
142
|
+
|
|
143
|
+
const results = await this.table
|
|
144
|
+
.search(queryEmbedding)
|
|
145
|
+
.limit(limit)
|
|
146
|
+
.toArray();
|
|
147
|
+
|
|
148
|
+
// Filter by threshold and map results
|
|
149
|
+
return results
|
|
150
|
+
.filter((r) => {
|
|
151
|
+
// LanceDB returns _distance (lower is better) or _relevance (higher is better)
|
|
152
|
+
const score = 1 - (r._distance || 0);
|
|
153
|
+
return score >= threshold;
|
|
154
|
+
})
|
|
155
|
+
.map((r) => ({
|
|
156
|
+
id: r.id as string,
|
|
157
|
+
type: r.type as string,
|
|
158
|
+
content: r.content as string,
|
|
159
|
+
data: r.data as string,
|
|
160
|
+
timestamp: r.timestamp as number,
|
|
161
|
+
sessionId: r.sessionId as string,
|
|
162
|
+
confidence: r.confidence as number,
|
|
163
|
+
score: 1 - (r._distance || 0),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Delete a fact by ID
|
|
169
|
+
*/
|
|
170
|
+
async delete(id: string): Promise<void> {
|
|
171
|
+
await this.init();
|
|
172
|
+
if (!this.table) return;
|
|
173
|
+
|
|
174
|
+
await this.table.delete(`id = '${id}'`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Clear all data (drops table)
|
|
179
|
+
*/
|
|
180
|
+
async clear(): Promise<void> {
|
|
181
|
+
await this.init();
|
|
182
|
+
if (!this.db) return;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await this.db.dropTable(EmbeddingService.TABLE_NAME);
|
|
186
|
+
this.table = null;
|
|
187
|
+
} catch {
|
|
188
|
+
// Table might not exist
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get all facts (for migration/debugging)
|
|
194
|
+
*/
|
|
195
|
+
async getAll(): Promise<
|
|
196
|
+
Array<{
|
|
197
|
+
id: string;
|
|
198
|
+
type: string;
|
|
199
|
+
content: string;
|
|
200
|
+
data: string;
|
|
201
|
+
timestamp: number;
|
|
202
|
+
}>
|
|
203
|
+
> {
|
|
204
|
+
await this.init();
|
|
205
|
+
if (!this.table) return [];
|
|
206
|
+
|
|
207
|
+
const results = await this.table.query().toArray();
|
|
208
|
+
return results.map((r) => ({
|
|
209
|
+
id: r.id as string,
|
|
210
|
+
type: r.type as string,
|
|
211
|
+
content: r.content as string,
|
|
212
|
+
data: r.data as string,
|
|
213
|
+
timestamp: r.timestamp as number,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Close connections
|
|
219
|
+
*/
|
|
220
|
+
async close(): Promise<void> {
|
|
221
|
+
// LanceDB doesn't require explicit close, but reset state
|
|
222
|
+
this.table = null;
|
|
223
|
+
this.db = null;
|
|
224
|
+
this.extractor = null;
|
|
225
|
+
this.initPromise = null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default EmbeddingService;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { UpdateSettings } from "@/config/settings";
|
|
2
|
+
import useSettingsStore from "../store/useSettingsStore";
|
|
3
|
+
import type { Model } from "../types/Model";
|
|
4
|
+
|
|
5
|
+
const getSettings = () => useSettingsStore.getState();
|
|
6
|
+
|
|
7
|
+
class GoogleClient {
|
|
8
|
+
async syncGoogleModels() {
|
|
9
|
+
if (!getSettings().google_api_key && !process.env.GOOGLE_API_KEY) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const savedModels = getSettings().models;
|
|
13
|
+
const existingGoogleModels = savedModels.filter((m) => m.type === "google");
|
|
14
|
+
const otherModels = savedModels.filter((m) => m.type !== "google");
|
|
15
|
+
|
|
16
|
+
const models: Model[] = [
|
|
17
|
+
{
|
|
18
|
+
type: "google",
|
|
19
|
+
name: "gemini-3-flash-preview",
|
|
20
|
+
modified_at: new Date().toISOString(),
|
|
21
|
+
digest: "",
|
|
22
|
+
usage: {
|
|
23
|
+
promptTokens: 0,
|
|
24
|
+
completionTokens: 0,
|
|
25
|
+
totalTokens: 0,
|
|
26
|
+
},
|
|
27
|
+
details: {
|
|
28
|
+
provider: "google",
|
|
29
|
+
context_length: 100000,
|
|
30
|
+
},
|
|
31
|
+
} as Model,
|
|
32
|
+
{
|
|
33
|
+
type: "google",
|
|
34
|
+
name: "gemini-2.5-flash-lite",
|
|
35
|
+
modified_at: new Date().toISOString(),
|
|
36
|
+
digest: "",
|
|
37
|
+
usage: {
|
|
38
|
+
promptTokens: 0,
|
|
39
|
+
completionTokens: 0,
|
|
40
|
+
totalTokens: 0,
|
|
41
|
+
},
|
|
42
|
+
details: {
|
|
43
|
+
provider: "google",
|
|
44
|
+
context_length: 100000,
|
|
45
|
+
},
|
|
46
|
+
} as Model,
|
|
47
|
+
].map((model) => {
|
|
48
|
+
const existing = existingGoogleModels.find((m) => m.name === model.name);
|
|
49
|
+
if (existing) {
|
|
50
|
+
return {
|
|
51
|
+
...model,
|
|
52
|
+
digest: existing.digest || "",
|
|
53
|
+
usage: existing.usage || model.usage,
|
|
54
|
+
details: { ...model.details, ...existing.details },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return model;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const mergedModels = [...otherModels, ...models];
|
|
61
|
+
await UpdateSettings({ models: mergedModels });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default new GoogleClient();
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphSerializer - Optimized serialization of knowledge graph subsets for LLM context.
|
|
3
|
+
*
|
|
4
|
+
* Converts graph data into concise, LLM-friendly markdown format that maximizes
|
|
5
|
+
* information density while staying within context window limits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Fact } from "@/utils/agentCognition";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
export interface SerializationOptions {
|
|
12
|
+
maxSymbols?: number;
|
|
13
|
+
maxDecisions?: number;
|
|
14
|
+
maxFacts?: number;
|
|
15
|
+
includeRelations?: boolean;
|
|
16
|
+
format?: "markdown" | "compact";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEFAULT_OPTIONS: SerializationOptions = {
|
|
20
|
+
maxSymbols: 10,
|
|
21
|
+
maxDecisions: 5,
|
|
22
|
+
maxFacts: 5,
|
|
23
|
+
includeRelations: true,
|
|
24
|
+
format: "markdown",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class GraphSerializer {
|
|
28
|
+
/**
|
|
29
|
+
* Serialize a collection of facts into LLM-friendly context.
|
|
30
|
+
*/
|
|
31
|
+
static serialize(
|
|
32
|
+
facts: Fact[],
|
|
33
|
+
relations?: Array<{ source: string; target: string; type: string }>,
|
|
34
|
+
options: SerializationOptions = {},
|
|
35
|
+
): string {
|
|
36
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
37
|
+
|
|
38
|
+
if (opts.format === "compact") {
|
|
39
|
+
return GraphSerializer.serializeCompact(facts, relations, opts);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return GraphSerializer.serializeMarkdown(facts, relations, opts);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Markdown format - human-readable, good for general context.
|
|
47
|
+
*/
|
|
48
|
+
private static serializeMarkdown(
|
|
49
|
+
facts: Fact[],
|
|
50
|
+
relations?: Array<{ source: string; target: string; type: string }>,
|
|
51
|
+
opts: SerializationOptions = DEFAULT_OPTIONS,
|
|
52
|
+
): string {
|
|
53
|
+
if (facts.length === 0) return "";
|
|
54
|
+
|
|
55
|
+
const parts: string[] = [];
|
|
56
|
+
|
|
57
|
+
// Group facts by type
|
|
58
|
+
const byType = GraphSerializer.groupByType(facts);
|
|
59
|
+
|
|
60
|
+
// Code entities (new types: file, module, function, class, interface, variable, symbol)
|
|
61
|
+
const codeTypes = [
|
|
62
|
+
"file",
|
|
63
|
+
"module",
|
|
64
|
+
"function",
|
|
65
|
+
"class",
|
|
66
|
+
"interface",
|
|
67
|
+
"variable",
|
|
68
|
+
"symbol",
|
|
69
|
+
"concept",
|
|
70
|
+
];
|
|
71
|
+
const codeEntities = facts.filter((f) => codeTypes.includes(f.type));
|
|
72
|
+
if (codeEntities.length > 0) {
|
|
73
|
+
parts.push("## Code Context\n");
|
|
74
|
+
|
|
75
|
+
// Group by file
|
|
76
|
+
const byFile = new Map<string, Fact[]>();
|
|
77
|
+
for (const c of codeEntities.slice(0, opts.maxSymbols!)) {
|
|
78
|
+
const file = (c.data?.file as string) || "unknown";
|
|
79
|
+
const list = byFile.get(file) || [];
|
|
80
|
+
list.push(c);
|
|
81
|
+
byFile.set(file, list);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const [file, symbols] of byFile) {
|
|
85
|
+
const fileName = path.basename(file);
|
|
86
|
+
parts.push(`### ${fileName}\n`);
|
|
87
|
+
for (const sym of symbols) {
|
|
88
|
+
const sig = sym.data?.signature ? ` ${sym.data.signature}` : "";
|
|
89
|
+
const exp = sym.data?.exported ? "export " : "";
|
|
90
|
+
parts.push(
|
|
91
|
+
`- \`${exp}${sym.data?.kind || "Symbol"}: ${sym.data?.symbol}${sig}\``,
|
|
92
|
+
);
|
|
93
|
+
if (sym.data?.docstring) {
|
|
94
|
+
const doc = (sym.data.docstring as string).slice(0, 80);
|
|
95
|
+
parts.push(` - ${doc}${doc.length >= 80 ? "..." : ""}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
parts.push("");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Decisions
|
|
103
|
+
const decisions = byType.get("decision") || [];
|
|
104
|
+
if (decisions.length > 0) {
|
|
105
|
+
parts.push("## Project Decisions\n");
|
|
106
|
+
for (const d of decisions.slice(0, opts.maxDecisions!)) {
|
|
107
|
+
parts.push(`- ${d.content}`);
|
|
108
|
+
}
|
|
109
|
+
parts.push("");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Facts
|
|
113
|
+
const factItems = byType.get("fact") || [];
|
|
114
|
+
if (factItems.length > 0) {
|
|
115
|
+
parts.push("## Known Facts\n");
|
|
116
|
+
for (const f of factItems.slice(0, opts.maxFacts!)) {
|
|
117
|
+
parts.push(`- ${f.content}`);
|
|
118
|
+
}
|
|
119
|
+
parts.push("");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Relations (optional)
|
|
123
|
+
if (opts.includeRelations && relations && relations.length > 0) {
|
|
124
|
+
parts.push("## Relationships\n");
|
|
125
|
+
const relationsByType = new Map<string, number>();
|
|
126
|
+
for (const r of relations) {
|
|
127
|
+
relationsByType.set(r.type, (relationsByType.get(r.type) || 0) + 1);
|
|
128
|
+
}
|
|
129
|
+
for (const [type, count] of relationsByType) {
|
|
130
|
+
parts.push(`- ${type}: ${count} connections`);
|
|
131
|
+
}
|
|
132
|
+
parts.push("");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parts.join("\n").trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compact format - minimal tokens, maximum information density.
|
|
140
|
+
*/
|
|
141
|
+
private static serializeCompact(
|
|
142
|
+
facts: Fact[],
|
|
143
|
+
relations?: Array<{ source: string; target: string; type: string }>,
|
|
144
|
+
opts: SerializationOptions = DEFAULT_OPTIONS,
|
|
145
|
+
): string {
|
|
146
|
+
if (facts.length === 0) return "";
|
|
147
|
+
|
|
148
|
+
const lines: string[] = [];
|
|
149
|
+
const byType = GraphSerializer.groupByType(facts);
|
|
150
|
+
|
|
151
|
+
// Code entities
|
|
152
|
+
const codeTypes = [
|
|
153
|
+
"file",
|
|
154
|
+
"module",
|
|
155
|
+
"function",
|
|
156
|
+
"class",
|
|
157
|
+
"interface",
|
|
158
|
+
"variable",
|
|
159
|
+
"symbol",
|
|
160
|
+
"concept",
|
|
161
|
+
];
|
|
162
|
+
const codeEntities = facts.filter((f) => codeTypes.includes(f.type));
|
|
163
|
+
if (codeEntities.length > 0) {
|
|
164
|
+
lines.push("[code]");
|
|
165
|
+
for (const c of codeEntities.slice(0, opts.maxSymbols!)) {
|
|
166
|
+
const kind =
|
|
167
|
+
(c.data?.kind as string)?.charAt(0) || c.type.charAt(0).toUpperCase();
|
|
168
|
+
const name = c.data?.symbol || "?";
|
|
169
|
+
const sig = c.data?.signature || "";
|
|
170
|
+
const file = c.data?.file
|
|
171
|
+
? `@${path.basename(c.data.file as string)}`
|
|
172
|
+
: "";
|
|
173
|
+
lines.push(`${kind}:${name}${sig}${file}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Decisions: brief
|
|
178
|
+
const decisions = byType.get("decision") || [];
|
|
179
|
+
if (decisions.length > 0) {
|
|
180
|
+
lines.push("[dec]");
|
|
181
|
+
for (const d of decisions.slice(0, opts.maxDecisions!)) {
|
|
182
|
+
lines.push(d.content.slice(0, 100));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return lines.join("\n");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create a focused context for a specific file.
|
|
191
|
+
*/
|
|
192
|
+
static serializeFileContext(
|
|
193
|
+
filePath: string,
|
|
194
|
+
symbols: Fact[],
|
|
195
|
+
relatedFacts: Fact[],
|
|
196
|
+
): string {
|
|
197
|
+
const parts: string[] = [
|
|
198
|
+
`## File: ${path.basename(filePath)}\n`,
|
|
199
|
+
`Path: \`${filePath}\`\n`,
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
if (symbols.length > 0) {
|
|
203
|
+
parts.push("### Symbols\n");
|
|
204
|
+
for (const sym of symbols) {
|
|
205
|
+
const sig = sym.data?.signature ? `: ${sym.data.signature}` : "";
|
|
206
|
+
parts.push(`- **${sym.data?.kind}** \`${sym.data?.symbol}\`${sig}`);
|
|
207
|
+
}
|
|
208
|
+
parts.push("");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (relatedFacts.length > 0) {
|
|
212
|
+
parts.push("### Related Knowledge\n");
|
|
213
|
+
for (const f of relatedFacts.slice(0, 5)) {
|
|
214
|
+
parts.push(`- [${f.type}] ${f.content}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return parts.join("\n").trim();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create summary statistics for a graph subset.
|
|
223
|
+
*/
|
|
224
|
+
static summarize(
|
|
225
|
+
facts: Fact[],
|
|
226
|
+
relations?: Array<{ source: string; target: string; type: string }>,
|
|
227
|
+
): string {
|
|
228
|
+
const byType = GraphSerializer.groupByType(facts);
|
|
229
|
+
const counts = Array.from(byType.entries())
|
|
230
|
+
.map(([type, list]) => `${type}:${list.length}`)
|
|
231
|
+
.join(" ");
|
|
232
|
+
|
|
233
|
+
const relCount = relations?.length || 0;
|
|
234
|
+
return `[${facts.length} facts (${counts}), ${relCount} relations]`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private static groupByType(facts: Fact[]): Map<string, Fact[]> {
|
|
238
|
+
const byType = new Map<string, Fact[]>();
|
|
239
|
+
for (const fact of facts) {
|
|
240
|
+
const list = byType.get(fact.type) || [];
|
|
241
|
+
list.push(fact);
|
|
242
|
+
byType.set(fact.type, list);
|
|
243
|
+
}
|
|
244
|
+
return byType;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default GraphSerializer;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import useSettingsStore from "../store/useSettingsStore";
|
|
2
|
+
|
|
3
|
+
const getSettings = () => useSettingsStore.getState();
|
|
4
|
+
|
|
5
|
+
class GroqClient {
|
|
6
|
+
async getModelData(model: string) {
|
|
7
|
+
const url = `https://api.groq.com/openai/v1/models/${model}`;
|
|
8
|
+
const options = {
|
|
9
|
+
method: "GET",
|
|
10
|
+
headers: { Authorization: `Bearer ${getSettings().groq_api_key}` },
|
|
11
|
+
};
|
|
12
|
+
const response = await fetch(url, options);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Failed to fetch Groq model ${model}: ${response.status}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const data: any = await response.json();
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default new GroqClient();
|