opencodekit 0.13.2 → 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.
Files changed (29) hide show
  1. package/dist/index.js +16 -4
  2. package/dist/template/.opencode/AGENTS.md +13 -4
  3. package/dist/template/.opencode/README.md +98 -2
  4. package/dist/template/.opencode/command/brainstorm.md +25 -2
  5. package/dist/template/.opencode/command/finish.md +21 -4
  6. package/dist/template/.opencode/command/handoff.md +17 -0
  7. package/dist/template/.opencode/command/implement.md +38 -0
  8. package/dist/template/.opencode/command/plan.md +32 -0
  9. package/dist/template/.opencode/command/research.md +61 -5
  10. package/dist/template/.opencode/command/resume.md +31 -0
  11. package/dist/template/.opencode/command/start.md +31 -0
  12. package/dist/template/.opencode/command/triage.md +16 -1
  13. package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
  14. package/dist/template/.opencode/memory/project/conventions.md +31 -0
  15. package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-8d00d272-cb80-463b-9774-7120a1c994e7.txn +0 -0
  16. package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/1-a3bea825-dad3-47dd-a6d6-ff41b76ff7b0.txn +0 -0
  17. package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
  18. package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/2.manifest +0 -0
  19. package/dist/template/.opencode/memory/vector_db/memories.lance/data/001010101000000101110001f998d04b63936ff83f9a34152d.lance +0 -0
  20. package/dist/template/.opencode/memory/vector_db/memories.lance/data/010000101010000000010010701b3840d38c2b5f275da99978.lance +0 -0
  21. package/dist/template/.opencode/opencode.json +587 -523
  22. package/dist/template/.opencode/package.json +3 -1
  23. package/dist/template/.opencode/plugin/memory.ts +610 -0
  24. package/dist/template/.opencode/tool/memory-embed.ts +183 -0
  25. package/dist/template/.opencode/tool/memory-index.ts +769 -0
  26. package/dist/template/.opencode/tool/memory-search.ts +358 -66
  27. package/dist/template/.opencode/tool/observation.ts +301 -12
  28. package/dist/template/.opencode/tool/repo-map.ts +451 -0
  29. package/package.json +16 -4
@@ -0,0 +1,769 @@
1
+ import { exec } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import * as lancedb from "@lancedb/lancedb";
6
+ import { tool } from "@opencode-ai/plugin";
7
+ import { generateEmbedding } from "./memory-embed";
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ // Configuration
12
+ const VECTOR_DB_PATH = ".opencode/memory/vector_db";
13
+ const TABLE_NAME = "memories";
14
+
15
+ interface MemoryDocument {
16
+ id: string;
17
+ file_path: string;
18
+ title: string;
19
+ content: string;
20
+ content_preview: string;
21
+ embedding: number[];
22
+ indexed_at: string;
23
+ file_type: string;
24
+ }
25
+
26
+ interface IndexResult {
27
+ indexed: number;
28
+ skipped: number;
29
+ errors: string[];
30
+ }
31
+
32
+ interface CodeDefinition {
33
+ name: string;
34
+ type: string; // function, class, method, interface, type
35
+ file_path: string;
36
+ line_start: number;
37
+ line_end: number;
38
+ signature: string;
39
+ content: string;
40
+ }
41
+
42
+ // AST-grep patterns for different languages
43
+ // Based on OpenCode LSP support: https://opencode.ai/docs/lsp
44
+ const AST_PATTERNS: Record<
45
+ string,
46
+ { lang: string; patterns: Record<string, string> }
47
+ > = {
48
+ // TypeScript/JavaScript family
49
+ ts: {
50
+ lang: "typescript",
51
+ patterns: {
52
+ function: "function $NAME($$$) { $$$ }",
53
+ async_function: "async function $NAME($$$) { $$$ }",
54
+ arrow_const: "const $NAME = ($$$) => $$$",
55
+ arrow_export: "export const $NAME = ($$$) => $$$",
56
+ class: "class $NAME { $$$ }",
57
+ interface: "interface $NAME { $$$ }",
58
+ type_alias: "type $NAME = $$$",
59
+ enum: "enum $NAME { $$$ }",
60
+ },
61
+ },
62
+ tsx: {
63
+ lang: "tsx",
64
+ patterns: {
65
+ function: "function $NAME($$$) { $$$ }",
66
+ component: "export function $NAME($$$) { $$$ }",
67
+ class: "class $NAME { $$$ }",
68
+ },
69
+ },
70
+ js: {
71
+ lang: "javascript",
72
+ patterns: {
73
+ function: "function $NAME($$$) { $$$ }",
74
+ async_function: "async function $NAME($$$) { $$$ }",
75
+ class: "class $NAME { $$$ }",
76
+ arrow_const: "const $NAME = ($$$) => $$$",
77
+ },
78
+ },
79
+ jsx: {
80
+ lang: "javascript",
81
+ patterns: {
82
+ function: "function $NAME($$$) { $$$ }",
83
+ component: "export function $NAME($$$) { $$$ }",
84
+ },
85
+ },
86
+ mjs: {
87
+ lang: "javascript",
88
+ patterns: { function: "function $NAME($$$) { $$$ }" },
89
+ },
90
+ cjs: {
91
+ lang: "javascript",
92
+ patterns: { function: "function $NAME($$$) { $$$ }" },
93
+ },
94
+
95
+ // Python
96
+ py: {
97
+ lang: "python",
98
+ patterns: {
99
+ function: "def $NAME($$$):",
100
+ async_function: "async def $NAME($$$):",
101
+ class: "class $NAME:",
102
+ class_inherit: "class $NAME($$$):",
103
+ },
104
+ },
105
+ pyi: {
106
+ lang: "python",
107
+ patterns: {
108
+ function: "def $NAME($$$) -> $$$:",
109
+ class: "class $NAME:",
110
+ },
111
+ },
112
+
113
+ // Go
114
+ go: {
115
+ lang: "go",
116
+ patterns: {
117
+ function: "func $NAME($$$) $$$",
118
+ method: "func ($$$) $NAME($$$) $$$",
119
+ struct: "type $NAME struct { $$$ }",
120
+ interface: "type $NAME interface { $$$ }",
121
+ },
122
+ },
123
+
124
+ // Rust
125
+ rs: {
126
+ lang: "rust",
127
+ patterns: {
128
+ function: "fn $NAME($$$) { $$$ }",
129
+ async_function: "async fn $NAME($$$) { $$$ }",
130
+ struct: "struct $NAME { $$$ }",
131
+ enum: "enum $NAME { $$$ }",
132
+ impl: "impl $NAME { $$$ }",
133
+ trait: "trait $NAME { $$$ }",
134
+ },
135
+ },
136
+
137
+ // Ruby
138
+ rb: {
139
+ lang: "ruby",
140
+ patterns: {
141
+ method: "def $NAME",
142
+ class: "class $NAME",
143
+ module: "module $NAME",
144
+ },
145
+ },
146
+
147
+ // PHP
148
+ php: {
149
+ lang: "php",
150
+ patterns: {
151
+ function: "function $NAME($$$)",
152
+ class: "class $NAME",
153
+ interface: "interface $NAME",
154
+ trait: "trait $NAME",
155
+ },
156
+ },
157
+
158
+ // Java
159
+ java: {
160
+ lang: "java",
161
+ patterns: {
162
+ method: "public $$$ $NAME($$$)",
163
+ class: "class $NAME",
164
+ interface: "interface $NAME",
165
+ },
166
+ },
167
+
168
+ // Kotlin
169
+ kt: {
170
+ lang: "kotlin",
171
+ patterns: {
172
+ function: "fun $NAME($$$)",
173
+ class: "class $NAME",
174
+ interface: "interface $NAME",
175
+ data_class: "data class $NAME($$$)",
176
+ },
177
+ },
178
+ kts: {
179
+ lang: "kotlin",
180
+ patterns: {
181
+ function: "fun $NAME($$$)",
182
+ },
183
+ },
184
+
185
+ // C/C++
186
+ c: {
187
+ lang: "c",
188
+ patterns: {
189
+ function: "$$$ $NAME($$$) { $$$ }",
190
+ struct: "struct $NAME { $$$ }",
191
+ },
192
+ },
193
+ cpp: {
194
+ lang: "cpp",
195
+ patterns: {
196
+ function: "$$$ $NAME($$$) { $$$ }",
197
+ class: "class $NAME { $$$ }",
198
+ struct: "struct $NAME { $$$ }",
199
+ },
200
+ },
201
+ h: { lang: "c", patterns: { struct: "struct $NAME { $$$ }" } },
202
+ hpp: { lang: "cpp", patterns: { class: "class $NAME { $$$ }" } },
203
+
204
+ // C#
205
+ cs: {
206
+ lang: "csharp",
207
+ patterns: {
208
+ method: "public $$$ $NAME($$$)",
209
+ class: "class $NAME",
210
+ interface: "interface $NAME",
211
+ struct: "struct $NAME",
212
+ },
213
+ },
214
+
215
+ // Swift
216
+ swift: {
217
+ lang: "swift",
218
+ patterns: {
219
+ function: "func $NAME($$$)",
220
+ class: "class $NAME",
221
+ struct: "struct $NAME",
222
+ protocol: "protocol $NAME",
223
+ },
224
+ },
225
+
226
+ // Dart
227
+ dart: {
228
+ lang: "dart",
229
+ patterns: {
230
+ function: "$$$ $NAME($$$) { $$$ }",
231
+ class: "class $NAME",
232
+ },
233
+ },
234
+
235
+ // Lua
236
+ lua: {
237
+ lang: "lua",
238
+ patterns: {
239
+ function: "function $NAME($$$)",
240
+ local_function: "local function $NAME($$$)",
241
+ },
242
+ },
243
+
244
+ // Elixir
245
+ ex: {
246
+ lang: "elixir",
247
+ patterns: {
248
+ function: "def $NAME($$$) do",
249
+ defp: "defp $NAME($$$) do",
250
+ module: "defmodule $NAME do",
251
+ },
252
+ },
253
+ exs: {
254
+ lang: "elixir",
255
+ patterns: {
256
+ function: "def $NAME($$$) do",
257
+ },
258
+ },
259
+
260
+ // Shell/Bash
261
+ sh: {
262
+ lang: "bash",
263
+ patterns: {
264
+ function: "function $NAME()",
265
+ function_alt: "$NAME() {",
266
+ },
267
+ },
268
+ bash: {
269
+ lang: "bash",
270
+ patterns: {
271
+ function: "function $NAME()",
272
+ },
273
+ },
274
+
275
+ // Vue/Svelte/Astro (component frameworks)
276
+ vue: {
277
+ lang: "vue",
278
+ patterns: {
279
+ script_setup: "<script setup>",
280
+ function: "function $NAME($$$)",
281
+ },
282
+ },
283
+ svelte: {
284
+ lang: "svelte",
285
+ patterns: {
286
+ script: "<script>",
287
+ function: "function $NAME($$$)",
288
+ },
289
+ },
290
+ };
291
+
292
+ async function extractCodeDefinitions(
293
+ srcDir: string,
294
+ ): Promise<{ definitions: CodeDefinition[]; errors: string[] }> {
295
+ const definitions: CodeDefinition[] = [];
296
+ const errors: string[] = [];
297
+
298
+ // Check if ast-grep is available
299
+ try {
300
+ await execAsync("sg --version");
301
+ } catch {
302
+ errors.push(
303
+ "ast-grep (sg) not installed. Run: npm install -g @ast-grep/cli",
304
+ );
305
+ return { definitions, errors };
306
+ }
307
+
308
+ // Find source files - use all extensions from AST_PATTERNS
309
+ const extensions = Object.keys(AST_PATTERNS);
310
+
311
+ for (const ext of extensions) {
312
+ const config = AST_PATTERNS[ext] || AST_PATTERNS.ts;
313
+
314
+ for (const [defType, pattern] of Object.entries(config.patterns)) {
315
+ try {
316
+ const { stdout } = await execAsync(
317
+ `sg --pattern '${pattern}' --lang ${config.lang} --json "${srcDir}"`,
318
+ { maxBuffer: 10 * 1024 * 1024 }, // 10MB buffer
319
+ );
320
+
321
+ if (!stdout.trim()) continue;
322
+
323
+ const matches = JSON.parse(stdout) as Array<{
324
+ file: string;
325
+ range: { start: { line: number }; end: { line: number } };
326
+ text: string;
327
+ metaVariables?: { single?: { NAME?: { text: string } } };
328
+ }>;
329
+
330
+ for (const match of matches) {
331
+ // Skip node_modules and common non-source directories
332
+ if (
333
+ match.file.includes("node_modules") ||
334
+ match.file.includes(".git") ||
335
+ match.file.includes("dist/") ||
336
+ match.file.includes("build/")
337
+ ) {
338
+ continue;
339
+ }
340
+
341
+ const name = match.metaVariables?.single?.NAME?.text || "anonymous";
342
+ const signature = match.text.split("\n")[0].substring(0, 200);
343
+
344
+ definitions.push({
345
+ name,
346
+ type: defType,
347
+ file_path: path.relative(process.cwd(), match.file),
348
+ line_start: match.range.start.line,
349
+ line_end: match.range.end.line,
350
+ signature,
351
+ content: match.text.substring(0, 2000), // Limit content size
352
+ });
353
+ }
354
+ } catch (err) {
355
+ // Pattern didn't match or other error, continue
356
+ const msg = err instanceof Error ? err.message : String(err);
357
+ if (!msg.includes("No matches found")) {
358
+ errors.push(`Pattern ${defType}: ${msg.substring(0, 100)}`);
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ return { definitions, errors };
365
+ }
366
+
367
+ async function indexCodeDefinitions(srcDir: string): Promise<IndexResult> {
368
+ const result: IndexResult = { indexed: 0, skipped: 0, errors: [] };
369
+
370
+ const { definitions, errors } = await extractCodeDefinitions(srcDir);
371
+ result.errors.push(...errors);
372
+
373
+ if (definitions.length === 0) {
374
+ return result;
375
+ }
376
+
377
+ // Open database
378
+ const dbPath = path.join(process.cwd(), VECTOR_DB_PATH);
379
+ await fs.mkdir(dbPath, { recursive: true });
380
+ const db = await lancedb.connect(dbPath);
381
+
382
+ // Get existing table or create new one
383
+ let existingDocs: Record<string, unknown>[] = [];
384
+ try {
385
+ const table = await db.openTable(TABLE_NAME);
386
+ const allDocs = await table.search([0]).limit(10000).toArray();
387
+ // Keep non-code documents
388
+ existingDocs = allDocs.filter((doc) => doc.file_type !== "code");
389
+ } catch {
390
+ // Table doesn't exist
391
+ }
392
+
393
+ const documents: Record<string, unknown>[] = [...existingDocs];
394
+
395
+ for (const def of definitions) {
396
+ try {
397
+ // Create searchable content
398
+ const searchContent = `${def.type} ${def.name}\n${def.signature}\n${def.content}`;
399
+
400
+ const embeddingResult = await generateEmbedding(
401
+ searchContent.substring(0, 8000),
402
+ );
403
+ if (!embeddingResult) {
404
+ result.skipped++;
405
+ continue;
406
+ }
407
+
408
+ documents.push({
409
+ id: `code_${def.file_path}_${def.name}_${def.line_start}`.replace(
410
+ /[\/\\]/g,
411
+ "_",
412
+ ),
413
+ file_path: def.file_path,
414
+ title: `${def.type}: ${def.name}`,
415
+ content: def.content,
416
+ content_preview: def.signature,
417
+ embedding: embeddingResult.embedding,
418
+ indexed_at: new Date().toISOString(),
419
+ file_type: "code",
420
+ // Code-specific fields
421
+ code_name: def.name,
422
+ code_type: def.type,
423
+ code_line_start: def.line_start,
424
+ code_line_end: def.line_end,
425
+ });
426
+
427
+ result.indexed++;
428
+ } catch (err) {
429
+ const msg = err instanceof Error ? err.message : String(err);
430
+ result.errors.push(`${def.file_path}:${def.name}: ${msg}`);
431
+ }
432
+ }
433
+
434
+ if (documents.length > 0) {
435
+ try {
436
+ await db.dropTable(TABLE_NAME);
437
+ } catch {
438
+ // Table doesn't exist
439
+ }
440
+ await db.createTable(TABLE_NAME, documents);
441
+ }
442
+
443
+ return result;
444
+ }
445
+
446
+ async function extractTitle(content: string): Promise<string> {
447
+ // Extract first H1 or H2 heading, or first line
448
+ const h1Match = content.match(/^#\s+(.+)$/m);
449
+ if (h1Match) return h1Match[1].trim();
450
+
451
+ const h2Match = content.match(/^##\s+(.+)$/m);
452
+ if (h2Match) return h2Match[1].trim();
453
+
454
+ const firstLine = content.split("\n")[0];
455
+ return firstLine?.trim().substring(0, 100) || "Untitled";
456
+ }
457
+
458
+ async function getMarkdownFiles(
459
+ dir: string,
460
+ files: string[] = [],
461
+ ): Promise<string[]> {
462
+ try {
463
+ const entries = await fs.readdir(dir, { withFileTypes: true });
464
+
465
+ for (const entry of entries) {
466
+ const fullPath = path.join(dir, entry.name);
467
+
468
+ if (entry.isDirectory()) {
469
+ // Skip vector_db and hidden directories
470
+ if (entry.name === "vector_db" || entry.name.startsWith(".")) {
471
+ continue;
472
+ }
473
+ await getMarkdownFiles(fullPath, files);
474
+ } else if (entry.name.endsWith(".md")) {
475
+ files.push(fullPath);
476
+ }
477
+ }
478
+ } catch {
479
+ // Directory doesn't exist
480
+ }
481
+
482
+ return files;
483
+ }
484
+
485
+ function getFileType(filePath: string): string {
486
+ if (filePath.includes("/observations/")) return "observation";
487
+ if (filePath.includes("/handoffs/")) return "handoff";
488
+ if (filePath.includes("/project/")) return "project";
489
+ if (filePath.includes("/_templates/")) return "template";
490
+ if (filePath.includes(".beads/")) return "bead";
491
+ return "memory";
492
+ }
493
+
494
+ async function indexMemoryFiles(
495
+ memoryDir: string,
496
+ beadsDir: string,
497
+ ): Promise<IndexResult> {
498
+ const result: IndexResult = { indexed: 0, skipped: 0, errors: [] };
499
+
500
+ // Collect all markdown files
501
+ const memoryFiles = await getMarkdownFiles(memoryDir);
502
+ const beadFiles = await getMarkdownFiles(beadsDir);
503
+ const allFiles = [...memoryFiles, ...beadFiles];
504
+
505
+ if (allFiles.length === 0) {
506
+ return result;
507
+ }
508
+
509
+ // Open or create database
510
+ const dbPath = path.join(process.cwd(), VECTOR_DB_PATH);
511
+ await fs.mkdir(dbPath, { recursive: true });
512
+
513
+ const db = await lancedb.connect(dbPath);
514
+
515
+ const documents: Record<string, unknown>[] = [];
516
+
517
+ for (const filePath of allFiles) {
518
+ try {
519
+ const content = await fs.readFile(filePath, "utf-8");
520
+
521
+ // Skip empty files
522
+ if (content.trim().length === 0) {
523
+ result.skipped++;
524
+ continue;
525
+ }
526
+
527
+ // Generate embedding
528
+ const embeddingResult = await generateEmbedding(
529
+ content.substring(0, 8000),
530
+ );
531
+ if (!embeddingResult) {
532
+ result.errors.push(`${filePath}: Failed to generate embedding`);
533
+ continue;
534
+ }
535
+
536
+ const relativePath = path.relative(process.cwd(), filePath);
537
+ const title = await extractTitle(content);
538
+
539
+ documents.push({
540
+ id: relativePath.replace(/[\/\\]/g, "_"),
541
+ file_path: relativePath,
542
+ title,
543
+ content,
544
+ content_preview: content.substring(0, 500),
545
+ embedding: embeddingResult.embedding,
546
+ indexed_at: new Date().toISOString(),
547
+ file_type: getFileType(filePath),
548
+ });
549
+
550
+ result.indexed++;
551
+ } catch (err) {
552
+ const msg = err instanceof Error ? err.message : String(err);
553
+ result.errors.push(`${filePath}: ${msg}`);
554
+ }
555
+ }
556
+
557
+ if (documents.length > 0) {
558
+ // Create or overwrite table
559
+ try {
560
+ await db.dropTable(TABLE_NAME);
561
+ } catch {
562
+ // Table doesn't exist, that's fine
563
+ }
564
+
565
+ await db.createTable(TABLE_NAME, documents);
566
+ }
567
+
568
+ return result;
569
+ }
570
+
571
+ async function searchVectorStore(
572
+ query: string,
573
+ topK = 5,
574
+ fileType?: string,
575
+ ): Promise<MemoryDocument[]> {
576
+ const dbPath = path.join(process.cwd(), VECTOR_DB_PATH);
577
+
578
+ try {
579
+ await fs.access(dbPath);
580
+ } catch {
581
+ return [];
582
+ }
583
+
584
+ const db = await lancedb.connect(dbPath);
585
+
586
+ let table: lancedb.Table;
587
+ try {
588
+ table = await db.openTable(TABLE_NAME);
589
+ } catch {
590
+ return [];
591
+ }
592
+
593
+ // Generate query embedding
594
+ const embeddingResult = await generateEmbedding(query);
595
+ if (!embeddingResult) {
596
+ return [];
597
+ }
598
+
599
+ let searchQuery = table.search(embeddingResult.embedding).limit(topK);
600
+
601
+ if (fileType) {
602
+ searchQuery = searchQuery.where(`file_type = '${fileType}'`);
603
+ }
604
+
605
+ const results = await searchQuery.toArray();
606
+
607
+ return results.map((row) => ({
608
+ id: row.id as string,
609
+ file_path: row.file_path as string,
610
+ title: row.title as string,
611
+ content: row.content as string,
612
+ content_preview: row.content_preview as string,
613
+ embedding: row.embedding as number[],
614
+ indexed_at: row.indexed_at as string,
615
+ file_type: row.file_type as string,
616
+ }));
617
+ }
618
+
619
+ export default tool({
620
+ description:
621
+ "Manage the vector store for semantic memory search. Rebuild index from memory files, index code definitions, or search for similar content.",
622
+ args: {
623
+ action: tool.schema
624
+ .enum(["rebuild", "search", "status", "index-code"])
625
+ .describe(
626
+ "Action: 'rebuild' to reindex memory files, 'index-code' to index code definitions, 'search' to find similar content, 'status' to check index state",
627
+ ),
628
+ query: tool.schema
629
+ .string()
630
+ .optional()
631
+ .describe("Search query (required for 'search' action)"),
632
+ limit: tool.schema
633
+ .number()
634
+ .optional()
635
+ .describe("Max results for search (default: 5)"),
636
+ type: tool.schema
637
+ .string()
638
+ .optional()
639
+ .describe(
640
+ "Filter by type: observation, handoff, project, template, bead, memory, code",
641
+ ),
642
+ path: tool.schema
643
+ .string()
644
+ .optional()
645
+ .describe("Source directory for 'index-code' action (default: 'src')"),
646
+ },
647
+ execute: async (args: {
648
+ action: "rebuild" | "search" | "status" | "index-code";
649
+ query?: string;
650
+ limit?: number;
651
+ type?: string;
652
+ path?: string;
653
+ }) => {
654
+ const memoryDir = path.join(process.cwd(), ".opencode/memory");
655
+ const beadsDir = path.join(process.cwd(), ".beads/artifacts");
656
+
657
+ if (args.action === "rebuild") {
658
+ const result = await indexMemoryFiles(memoryDir, beadsDir);
659
+
660
+ let output = "# Vector Store Rebuild Complete\n\n";
661
+ output += `- **Indexed:** ${result.indexed} files\n`;
662
+ output += `- **Skipped:** ${result.skipped} files (empty)\n`;
663
+ output += `- **Location:** ${VECTOR_DB_PATH}/\n`;
664
+
665
+ if (result.errors.length > 0) {
666
+ output += `\n## Errors (${result.errors.length})\n\n`;
667
+ for (const err of result.errors.slice(0, 10)) {
668
+ output += `- ${err}\n`;
669
+ }
670
+ if (result.errors.length > 10) {
671
+ output += `- ... and ${result.errors.length - 10} more\n`;
672
+ }
673
+ }
674
+
675
+ return output;
676
+ }
677
+
678
+ if (args.action === "index-code") {
679
+ const srcDir = args.path || "src";
680
+ const fullPath = path.join(process.cwd(), srcDir);
681
+
682
+ // Check if directory exists
683
+ try {
684
+ await fs.access(fullPath);
685
+ } catch {
686
+ return `Error: Directory '${srcDir}' not found.\n\nUsage: memory-index action=index-code path=src`;
687
+ }
688
+
689
+ const result = await indexCodeDefinitions(fullPath);
690
+
691
+ let output = "# Code Index Complete\n\n";
692
+ output += `- **Indexed:** ${result.indexed} code definitions\n`;
693
+ output += `- **Skipped:** ${result.skipped} (no embedding)\n`;
694
+ output += `- **Source:** ${srcDir}/\n`;
695
+ output += `- **Location:** ${VECTOR_DB_PATH}/\n`;
696
+
697
+ if (result.errors.length > 0) {
698
+ output += `\n## Errors (${result.errors.length})\n\n`;
699
+ for (const err of result.errors.slice(0, 10)) {
700
+ output += `- ${err}\n`;
701
+ }
702
+ if (result.errors.length > 10) {
703
+ output += `- ... and ${result.errors.length - 10} more\n`;
704
+ }
705
+ }
706
+
707
+ output += "\n## Indexed Types\n\n";
708
+ output +=
709
+ "Functions, classes, interfaces, types from TypeScript/JavaScript/Python files.\n";
710
+ output +=
711
+ "\nSearch with: `memory-search query='function name' mode=semantic type=code`";
712
+
713
+ return output;
714
+ }
715
+
716
+ if (args.action === "search") {
717
+ if (!args.query) {
718
+ return "Error: 'query' is required for search action";
719
+ }
720
+
721
+ const results = await searchVectorStore(
722
+ args.query,
723
+ args.limit || 5,
724
+ args.type,
725
+ );
726
+
727
+ if (results.length === 0) {
728
+ return `No results found for "${args.query}".\n\nTip: Run 'vector-store rebuild' first to index memory files.`;
729
+ }
730
+
731
+ let output = `# Semantic Search: "${args.query}"\n\n`;
732
+ output += `Found ${results.length} result(s).\n\n`;
733
+
734
+ for (const doc of results) {
735
+ output += `## ${doc.title}\n\n`;
736
+ output += `**File:** \`${doc.file_path}\`\n`;
737
+ output += `**Type:** ${doc.file_type}\n`;
738
+ output += `**Indexed:** ${doc.indexed_at}\n\n`;
739
+ output += `${doc.content_preview}...\n\n`;
740
+ output += "---\n\n";
741
+ }
742
+
743
+ return output;
744
+ }
745
+
746
+ if (args.action === "status") {
747
+ const dbPath = path.join(process.cwd(), VECTOR_DB_PATH);
748
+
749
+ try {
750
+ await fs.access(dbPath);
751
+ const db = await lancedb.connect(dbPath);
752
+ const table = await db.openTable(TABLE_NAME);
753
+ const count = await table.countRows();
754
+
755
+ return `# Vector Store Status\n\n- **Location:** ${VECTOR_DB_PATH}/\n- **Documents:** ${count}\n- **Table:** ${TABLE_NAME}\n\nUse 'vector-store rebuild' to reindex.`;
756
+ } catch {
757
+ return `# Vector Store Status\n\n- **Status:** Not initialized\n- **Location:** ${VECTOR_DB_PATH}/ (does not exist)\n\nRun 'vector-store rebuild' to create the index.`;
758
+ }
759
+ }
760
+
761
+ return "Unknown action";
762
+ },
763
+ });
764
+
765
+ // Export search function for use by memory-search.ts
766
+ export { searchVectorStore };
767
+
768
+ // Export rebuild function for use by memory-watcher plugin
769
+ export { indexMemoryFiles };