opencodekit 0.13.1 → 0.14.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 +2 -2
- package/dist/index.js +16 -4
- package/dist/template/.opencode/AGENTS.md +13 -4
- package/dist/template/.opencode/README.md +100 -4
- package/dist/template/.opencode/command/brainstorm.md +25 -2
- package/dist/template/.opencode/command/finish.md +21 -4
- package/dist/template/.opencode/command/handoff.md +17 -0
- package/dist/template/.opencode/command/implement.md +38 -0
- package/dist/template/.opencode/command/plan.md +32 -0
- package/dist/template/.opencode/command/research.md +61 -5
- package/dist/template/.opencode/command/resume.md +31 -0
- package/dist/template/.opencode/command/start.md +31 -0
- package/dist/template/.opencode/command/triage.md +16 -1
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/project/conventions.md +31 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-8d00d272-cb80-463b-9774-7120a1c994e7.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/1-a3bea825-dad3-47dd-a6d6-ff41b76ff7b0.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/2.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/001010101000000101110001f998d04b63936ff83f9a34152d.lance +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/010000101010000000010010701b3840d38c2b5f275da99978.lance +0 -0
- package/dist/template/.opencode/opencode.json +587 -511
- package/dist/template/.opencode/package.json +3 -1
- package/dist/template/.opencode/plugin/memory.ts +610 -0
- package/dist/template/.opencode/tool/memory-embed.ts +183 -0
- package/dist/template/.opencode/tool/memory-index.ts +769 -0
- package/dist/template/.opencode/tool/memory-search.ts +358 -66
- package/dist/template/.opencode/tool/observation.ts +301 -12
- package/dist/template/.opencode/tool/repo-map.ts +451 -0
- package/package.json +16 -4
|
@@ -1,10 +1,82 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { tool } from "@opencode-ai/plugin";
|
|
4
|
+
import { searchVectorStore } from "./memory-index";
|
|
4
5
|
|
|
5
6
|
interface SearchResult {
|
|
6
7
|
file: string;
|
|
7
8
|
matches: { line: number; content: string }[];
|
|
9
|
+
score?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SemanticResult {
|
|
13
|
+
file: string;
|
|
14
|
+
title: string;
|
|
15
|
+
preview: string;
|
|
16
|
+
type: string;
|
|
17
|
+
score?: number;
|
|
18
|
+
confidence?: string;
|
|
19
|
+
age_days?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Confidence decay factor based on age (Graphiti-inspired)
|
|
23
|
+
// Older observations with lower confidence rank lower
|
|
24
|
+
function applyConfidenceDecay(
|
|
25
|
+
results: SemanticResult[],
|
|
26
|
+
contents: Map<string, string>,
|
|
27
|
+
): SemanticResult[] {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
|
|
30
|
+
return results.map((result) => {
|
|
31
|
+
const content = contents.get(result.file) || "";
|
|
32
|
+
|
|
33
|
+
// Extract metadata from YAML frontmatter
|
|
34
|
+
const createdMatch = content.match(/created:\s*(.+)/);
|
|
35
|
+
const confidenceMatch = content.match(/confidence:\s*(\w+)/);
|
|
36
|
+
const validUntilMatch = content.match(/valid_until:\s*(.+)/);
|
|
37
|
+
const supersededByMatch = content.match(/superseded_by:\s*(.+)/);
|
|
38
|
+
|
|
39
|
+
// Check if superseded (should rank very low)
|
|
40
|
+
if (supersededByMatch && supersededByMatch[1] !== "null") {
|
|
41
|
+
return { ...result, score: (result.score || 1) * 0.1 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if expired
|
|
45
|
+
if (validUntilMatch && validUntilMatch[1] !== "null") {
|
|
46
|
+
const validUntil = new Date(validUntilMatch[1]).getTime();
|
|
47
|
+
if (validUntil < now) {
|
|
48
|
+
return { ...result, score: (result.score || 1) * 0.2 };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Calculate age in days
|
|
53
|
+
let ageDays = 0;
|
|
54
|
+
if (createdMatch) {
|
|
55
|
+
const created = new Date(createdMatch[1]).getTime();
|
|
56
|
+
ageDays = Math.floor((now - created) / (1000 * 60 * 60 * 24));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Confidence multiplier
|
|
60
|
+
const confidenceMultiplier: Record<string, number> = {
|
|
61
|
+
high: 1.0,
|
|
62
|
+
medium: 0.8,
|
|
63
|
+
low: 0.6,
|
|
64
|
+
};
|
|
65
|
+
const confidence = confidenceMatch?.[1] || "high";
|
|
66
|
+
const confMult = confidenceMultiplier[confidence] || 1.0;
|
|
67
|
+
|
|
68
|
+
// Age decay: lose 5% per 30 days, minimum 50%
|
|
69
|
+
const ageDecay = Math.max(0.5, 1 - (ageDays / 30) * 0.05);
|
|
70
|
+
|
|
71
|
+
const finalScore = (result.score || 1) * confMult * ageDecay;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...result,
|
|
75
|
+
score: finalScore,
|
|
76
|
+
confidence,
|
|
77
|
+
age_days: ageDays,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
8
80
|
}
|
|
9
81
|
|
|
10
82
|
async function searchDirectory(
|
|
@@ -20,8 +92,8 @@ async function searchDirectory(
|
|
|
20
92
|
const fullPath = path.join(dir, entry.name);
|
|
21
93
|
|
|
22
94
|
if (entry.isDirectory()) {
|
|
23
|
-
// Skip hidden directories
|
|
24
|
-
if (entry.name.startsWith(".")
|
|
95
|
+
// Skip hidden directories and vector_db
|
|
96
|
+
if (entry.name.startsWith(".") || entry.name === "vector_db") {
|
|
25
97
|
continue;
|
|
26
98
|
}
|
|
27
99
|
await searchDirectory(fullPath, pattern, results, baseDir);
|
|
@@ -50,14 +122,270 @@ async function searchDirectory(
|
|
|
50
122
|
}
|
|
51
123
|
}
|
|
52
124
|
|
|
125
|
+
async function keywordSearch(
|
|
126
|
+
query: string,
|
|
127
|
+
type: string | undefined,
|
|
128
|
+
limit: number,
|
|
129
|
+
): Promise<SearchResult[]> {
|
|
130
|
+
const memoryDir = path.join(process.cwd(), ".opencode/memory");
|
|
131
|
+
const beadsDir = path.join(process.cwd(), ".beads/artifacts");
|
|
132
|
+
const globalMemoryDir = path.join(
|
|
133
|
+
process.env.HOME || "",
|
|
134
|
+
".config/opencode/memory",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Create case-insensitive regex from query
|
|
138
|
+
let pattern: RegExp;
|
|
139
|
+
try {
|
|
140
|
+
pattern = new RegExp(query, "i");
|
|
141
|
+
} catch {
|
|
142
|
+
// Escape special chars if not valid regex
|
|
143
|
+
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
144
|
+
pattern = new RegExp(escaped, "i");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const results: SearchResult[] = [];
|
|
148
|
+
|
|
149
|
+
// Handle type filtering
|
|
150
|
+
if (type === "beads") {
|
|
151
|
+
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
152
|
+
} else if (type && type !== "all") {
|
|
153
|
+
const typeMap: Record<string, string> = {
|
|
154
|
+
handoffs: "handoffs",
|
|
155
|
+
research: "research",
|
|
156
|
+
templates: "_templates",
|
|
157
|
+
observations: "observations",
|
|
158
|
+
};
|
|
159
|
+
const subdir = typeMap[type];
|
|
160
|
+
if (subdir) {
|
|
161
|
+
const searchDir = path.join(memoryDir, subdir);
|
|
162
|
+
await searchDirectory(searchDir, pattern, results, memoryDir);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Search all: memory + beads
|
|
166
|
+
await searchDirectory(memoryDir, pattern, results, memoryDir);
|
|
167
|
+
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
168
|
+
await searchDirectory(globalMemoryDir, pattern, results, globalMemoryDir);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function semanticSearch(
|
|
175
|
+
query: string,
|
|
176
|
+
type: string | undefined,
|
|
177
|
+
limit: number,
|
|
178
|
+
): Promise<SemanticResult[]> {
|
|
179
|
+
const typeMap: Record<string, string> = {
|
|
180
|
+
handoffs: "handoff",
|
|
181
|
+
observations: "observation",
|
|
182
|
+
beads: "bead",
|
|
183
|
+
project: "project",
|
|
184
|
+
templates: "template",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const fileType = type && type !== "all" ? typeMap[type] : undefined;
|
|
188
|
+
const docs = await searchVectorStore(query, limit * 2, fileType); // Fetch extra for decay filtering
|
|
189
|
+
|
|
190
|
+
// Build content map for decay calculation
|
|
191
|
+
const contents = new Map<string, string>();
|
|
192
|
+
for (const doc of docs) {
|
|
193
|
+
try {
|
|
194
|
+
const content = await fs.readFile(doc.file_path, "utf-8");
|
|
195
|
+
contents.set(doc.file_path, content);
|
|
196
|
+
} catch {
|
|
197
|
+
// File not found, skip
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const results: SemanticResult[] = docs.map((doc) => ({
|
|
202
|
+
file: doc.file_path,
|
|
203
|
+
title: doc.title,
|
|
204
|
+
preview: doc.content_preview,
|
|
205
|
+
type: doc.file_type,
|
|
206
|
+
score: 1.0,
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
// Apply confidence decay and re-sort
|
|
210
|
+
const decayedResults = applyConfidenceDecay(results, contents);
|
|
211
|
+
decayedResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
212
|
+
|
|
213
|
+
return decayedResults.slice(0, limit);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function formatKeywordResults(
|
|
217
|
+
query: string,
|
|
218
|
+
results: SearchResult[],
|
|
219
|
+
limit: number,
|
|
220
|
+
): string {
|
|
221
|
+
if (results.length === 0) {
|
|
222
|
+
return `No keyword matches found for "${query}".\n\nTip: Try 'mode: semantic' for conceptual search, or run 'vector-store rebuild' first.`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let output = `# Keyword Search: "${query}"\n\n`;
|
|
226
|
+
output += `Found ${results.length} file(s) with matches.\n\n`;
|
|
227
|
+
|
|
228
|
+
for (const result of results) {
|
|
229
|
+
output += `## ${result.file}\n\n`;
|
|
230
|
+
const matchesToShow = result.matches.slice(0, limit);
|
|
231
|
+
for (const match of matchesToShow) {
|
|
232
|
+
output += `- **Line ${match.line}:** ${match.content}\n`;
|
|
233
|
+
}
|
|
234
|
+
if (result.matches.length > limit) {
|
|
235
|
+
output += `- ... and ${result.matches.length - limit} more matches\n`;
|
|
236
|
+
}
|
|
237
|
+
// Add LSP navigation hint for code files
|
|
238
|
+
if (
|
|
239
|
+
result.file.endsWith(".ts") ||
|
|
240
|
+
result.file.endsWith(".tsx") ||
|
|
241
|
+
result.file.endsWith(".js") ||
|
|
242
|
+
result.file.endsWith(".jsx") ||
|
|
243
|
+
result.file.endsWith(".py") ||
|
|
244
|
+
result.file.endsWith(".go") ||
|
|
245
|
+
result.file.endsWith(".rs")
|
|
246
|
+
) {
|
|
247
|
+
const lineNum = result.matches[0]?.line;
|
|
248
|
+
if (lineNum) {
|
|
249
|
+
output += `\n🔍 **LSP Nudge:**\n`;
|
|
250
|
+
output += ` \`lsp_lsp_goto_definition({ filePath: "${result.file}", line: ${lineNum}, character: 1 })\`\n`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
output += "\n";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return output;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function formatSemanticResults(
|
|
260
|
+
query: string,
|
|
261
|
+
results: SemanticResult[],
|
|
262
|
+
): string {
|
|
263
|
+
if (results.length === 0) {
|
|
264
|
+
return `No semantic matches found for "${query}".\n\nTip: Run 'vector-store rebuild' to index memory files first.`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let output = `# Semantic Search: "${query}"\n\n`;
|
|
268
|
+
output += `Found ${results.length} similar document(s).\n\n`;
|
|
269
|
+
|
|
270
|
+
const confidenceIcons: Record<string, string> = {
|
|
271
|
+
high: "🟢",
|
|
272
|
+
medium: "🟡",
|
|
273
|
+
low: "🔴",
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
for (const result of results) {
|
|
277
|
+
output += `## ${result.title}\n\n`;
|
|
278
|
+
output += `**File:** \`${result.file}\`\n`;
|
|
279
|
+
output += `**Type:** ${result.type}`;
|
|
280
|
+
if (result.confidence) {
|
|
281
|
+
const icon = confidenceIcons[result.confidence] || "";
|
|
282
|
+
output += ` | **Confidence:** ${icon} ${result.confidence}`;
|
|
283
|
+
}
|
|
284
|
+
if (result.age_days !== undefined && result.age_days > 0) {
|
|
285
|
+
output += ` | **Age:** ${result.age_days}d`;
|
|
286
|
+
}
|
|
287
|
+
output += "\n\n";
|
|
288
|
+
output += `${result.preview}...\n\n`;
|
|
289
|
+
|
|
290
|
+
// Add LSP navigation for code files
|
|
291
|
+
if (
|
|
292
|
+
result.file.endsWith(".ts") ||
|
|
293
|
+
result.file.endsWith(".tsx") ||
|
|
294
|
+
result.file.endsWith(".js") ||
|
|
295
|
+
result.file.endsWith(".jsx") ||
|
|
296
|
+
result.file.endsWith(".py") ||
|
|
297
|
+
result.file.endsWith(".go") ||
|
|
298
|
+
result.file.endsWith(".rs")
|
|
299
|
+
) {
|
|
300
|
+
output += `\n🔍 **LSP Nudge:**\n`;
|
|
301
|
+
output += ` Get symbols: \`lsp_lsp_document_symbols({ filePath: "${result.file}" })\`\n`;
|
|
302
|
+
output += ` Find references: \`lsp_lsp_find_references({ filePath: "${result.file}", line: 1, character: 1 })\`\n`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
output += "---\n\n";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return output;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function formatHybridResults(
|
|
312
|
+
query: string,
|
|
313
|
+
keywordResults: SearchResult[],
|
|
314
|
+
semanticResults: SemanticResult[],
|
|
315
|
+
limit: number,
|
|
316
|
+
): string {
|
|
317
|
+
let output = `# Hybrid Search: "${query}"\n\n`;
|
|
318
|
+
|
|
319
|
+
// Semantic results first (conceptual matches)
|
|
320
|
+
output += `## Semantic Matches (${semanticResults.length})\n\n`;
|
|
321
|
+
if (semanticResults.length === 0) {
|
|
322
|
+
output += `_No semantic matches. Run 'vector-store rebuild' to enable._\n\n`;
|
|
323
|
+
} else {
|
|
324
|
+
for (const result of semanticResults.slice(0, limit)) {
|
|
325
|
+
output += `- **${result.title}** (\`${result.file}\`)\n`;
|
|
326
|
+
output += ` ${result.preview.substring(0, 100)}...\n`;
|
|
327
|
+
// Add LSP hint for code files
|
|
328
|
+
if (
|
|
329
|
+
result.file.endsWith(".ts") ||
|
|
330
|
+
result.file.endsWith(".tsx") ||
|
|
331
|
+
result.file.endsWith(".js") ||
|
|
332
|
+
result.file.endsWith(".jsx") ||
|
|
333
|
+
result.file.endsWith(".py") ||
|
|
334
|
+
result.file.endsWith(".go") ||
|
|
335
|
+
result.file.endsWith(".rs")
|
|
336
|
+
) {
|
|
337
|
+
output += ` 🔍 **LSP Nudge:** \`lsp_lsp_document_symbols({ filePath: "${result.file}" })\`\n`;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
output += "\n";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Keyword results (exact matches)
|
|
344
|
+
output += `## Keyword Matches (${keywordResults.length})\n\n`;
|
|
345
|
+
if (keywordResults.length === 0) {
|
|
346
|
+
output += "_No exact keyword matches._\n\n";
|
|
347
|
+
} else {
|
|
348
|
+
for (const result of keywordResults.slice(0, limit)) {
|
|
349
|
+
output += `- **${result.file}**\n`;
|
|
350
|
+
for (const match of result.matches.slice(0, 2)) {
|
|
351
|
+
output += ` - Line ${match.line}: ${match.content.substring(0, 80)}...\n`;
|
|
352
|
+
}
|
|
353
|
+
// Add LSP hint for code files
|
|
354
|
+
if (
|
|
355
|
+
result.file.endsWith(".ts") ||
|
|
356
|
+
result.file.endsWith(".tsx") ||
|
|
357
|
+
result.file.endsWith(".js") ||
|
|
358
|
+
result.file.endsWith(".jsx") ||
|
|
359
|
+
result.file.endsWith(".py") ||
|
|
360
|
+
result.file.endsWith(".go") ||
|
|
361
|
+
result.file.endsWith(".rs")
|
|
362
|
+
) {
|
|
363
|
+
const lineNum = result.matches[0]?.line;
|
|
364
|
+
if (lineNum) {
|
|
365
|
+
output += ` 🔍 **LSP Nudge:** \`lsp_lsp_goto_definition({ filePath: "${result.file}", line: ${lineNum}, character: 1 })\`\n`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
output += "\n";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return output;
|
|
373
|
+
}
|
|
374
|
+
|
|
53
375
|
export default tool({
|
|
54
376
|
description:
|
|
55
|
-
"Search across all memory files using keywords or
|
|
377
|
+
"Search across all memory files using keywords, semantic similarity, or hybrid mode. Returns matching files with context. Useful for finding past decisions, research, or handoffs.",
|
|
56
378
|
args: {
|
|
57
379
|
query: tool.schema
|
|
58
380
|
.string()
|
|
59
381
|
.describe(
|
|
60
|
-
"Search query: keywords
|
|
382
|
+
"Search query: keywords, regex pattern, or natural language for semantic search",
|
|
383
|
+
),
|
|
384
|
+
mode: tool.schema
|
|
385
|
+
.enum(["keyword", "semantic", "hybrid"])
|
|
386
|
+
.optional()
|
|
387
|
+
.describe(
|
|
388
|
+
"Search mode: 'keyword' (default, regex matching), 'semantic' (vector similarity), 'hybrid' (both)",
|
|
61
389
|
),
|
|
62
390
|
type: tool.schema
|
|
63
391
|
.string()
|
|
@@ -65,76 +393,40 @@ export default tool({
|
|
|
65
393
|
.describe(
|
|
66
394
|
"Filter by type: 'all' (default), 'handoffs', 'research', 'templates', 'observations', 'beads'",
|
|
67
395
|
),
|
|
68
|
-
limit: tool.schema
|
|
69
|
-
.number()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe("Max results per file (default: 5)"),
|
|
396
|
+
limit: tool.schema.number().optional().describe("Max results (default: 5)"),
|
|
72
397
|
},
|
|
73
|
-
execute: async (args: {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
398
|
+
execute: async (args: {
|
|
399
|
+
query: string;
|
|
400
|
+
mode?: "keyword" | "semantic" | "hybrid";
|
|
401
|
+
type?: string;
|
|
402
|
+
limit?: number;
|
|
403
|
+
}) => {
|
|
404
|
+
const mode = args.mode || "keyword";
|
|
81
405
|
const limit = args.limit || 5;
|
|
82
406
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
pattern = new RegExp(args.query, "i");
|
|
87
|
-
} catch {
|
|
88
|
-
// Escape special chars if not valid regex
|
|
89
|
-
const escaped = args.query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
90
|
-
pattern = new RegExp(escaped, "i");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const results: SearchResult[] = [];
|
|
94
|
-
|
|
95
|
-
// Handle type filtering
|
|
96
|
-
if (args.type === "beads") {
|
|
97
|
-
// Search only bead artifacts
|
|
98
|
-
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
99
|
-
} else if (args.type && args.type !== "all") {
|
|
100
|
-
const typeMap: Record<string, string> = {
|
|
101
|
-
handoffs: "handoffs",
|
|
102
|
-
research: "research",
|
|
103
|
-
templates: "_templates",
|
|
104
|
-
observations: "observations",
|
|
105
|
-
};
|
|
106
|
-
const subdir = typeMap[args.type];
|
|
107
|
-
if (subdir) {
|
|
108
|
-
const searchDir = path.join(memoryDir, subdir);
|
|
109
|
-
await searchDirectory(searchDir, pattern, results, memoryDir);
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
// Search all: memory + beads
|
|
113
|
-
await searchDirectory(memoryDir, pattern, results, memoryDir);
|
|
114
|
-
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
115
|
-
await searchDirectory(globalMemoryDir, pattern, results, globalMemoryDir);
|
|
407
|
+
if (mode === "keyword") {
|
|
408
|
+
const results = await keywordSearch(args.query, args.type, limit);
|
|
409
|
+
return formatKeywordResults(args.query, results, limit);
|
|
116
410
|
}
|
|
117
411
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
412
|
+
if (mode === "semantic") {
|
|
413
|
+
const results = await semanticSearch(args.query, args.type, limit);
|
|
414
|
+
return formatSemanticResults(args.query, results);
|
|
120
415
|
}
|
|
121
416
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
output += `- ... and ${result.matches.length - limit} more matches\n`;
|
|
134
|
-
}
|
|
135
|
-
output += "\n";
|
|
417
|
+
if (mode === "hybrid") {
|
|
418
|
+
const [keywordResults, semanticResults] = await Promise.all([
|
|
419
|
+
keywordSearch(args.query, args.type, limit),
|
|
420
|
+
semanticSearch(args.query, args.type, limit),
|
|
421
|
+
]);
|
|
422
|
+
return formatHybridResults(
|
|
423
|
+
args.query,
|
|
424
|
+
keywordResults,
|
|
425
|
+
semanticResults,
|
|
426
|
+
limit,
|
|
427
|
+
);
|
|
136
428
|
}
|
|
137
429
|
|
|
138
|
-
return
|
|
430
|
+
return "Unknown search mode";
|
|
139
431
|
},
|
|
140
432
|
});
|