@comfanion/usethis_search 3.0.0-dev.0 → 3.0.0-dev.10
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/api.ts +92 -0
- package/file-indexer.ts +14 -1
- package/index.ts +20 -6
- package/package.json +5 -3
- package/tools/codeindex.ts +173 -7
- package/tools/search.ts +1 -1
- package/vectorizer/analyzers/lsp-analyzer.ts +225 -94
- package/vectorizer/analyzers/lsp-client.ts +369 -0
- package/vectorizer/graph-builder.ts +106 -3
- package/vectorizer/graph-db.ts +192 -0
- package/vectorizer/{index.js → index.ts} +114 -11
- package/vectorizer/usage-tracker.ts +204 -0
- package/tools/read-interceptor.ts +0 -54
package/api.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usethis_search API
|
|
3
|
+
*
|
|
4
|
+
* Exports internal functions for plugin-to-plugin communication.
|
|
5
|
+
* Used by Mind plugin for graph-based workspace management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { GraphDB } from "./vectorizer/graph-db"
|
|
9
|
+
|
|
10
|
+
// Global GraphDB instance (shared across plugins)
|
|
11
|
+
let graphDBInstance: GraphDB | null = null
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize API with GraphDB instance
|
|
15
|
+
*/
|
|
16
|
+
export function initGraphAPI(db: GraphDB): void {
|
|
17
|
+
graphDBInstance = db
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get related files for a given file path
|
|
22
|
+
*
|
|
23
|
+
* @param filePath - File path to get relations for
|
|
24
|
+
* @param maxDepth - Maximum graph depth to traverse (default: 1)
|
|
25
|
+
* @returns Array of related files with relation type and weight
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
* ```javascript
|
|
29
|
+
* const related = await getRelatedFiles("src/auth/login.ts", 1)
|
|
30
|
+
* // Returns:
|
|
31
|
+
* [
|
|
32
|
+
* { path: "src/types/User.ts", relation: "imports", weight: 0.9 },
|
|
33
|
+
* { path: "src/auth/BaseAuth.ts", relation: "extends", weight: 0.95 },
|
|
34
|
+
* { path: "src/routes/api.ts", relation: "used_by", weight: 0.8 }
|
|
35
|
+
* ]
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export async function getRelatedFiles(
|
|
39
|
+
filePath: string,
|
|
40
|
+
maxDepth: number = 1
|
|
41
|
+
): Promise<{path: string, relation: string, weight: number}[]> {
|
|
42
|
+
if (!graphDBInstance) {
|
|
43
|
+
console.warn("[usethis_search API] GraphDB not initialized. Returning empty array.")
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const chunkId = `file:${filePath}`
|
|
49
|
+
const related = await graphDBInstance.getRelatedFiles(chunkId, maxDepth)
|
|
50
|
+
|
|
51
|
+
// Filter out the input file itself (it might appear in the graph)
|
|
52
|
+
const filtered = related.filter(r => r.path !== filePath)
|
|
53
|
+
|
|
54
|
+
return filtered
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`[usethis_search API] Error getting related files for ${filePath}:`, error)
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if graph API is available
|
|
63
|
+
*/
|
|
64
|
+
export function isGraphAPIAvailable(): boolean {
|
|
65
|
+
return graphDBInstance !== null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get all graph entries for a file (both incoming and outgoing)
|
|
70
|
+
*/
|
|
71
|
+
export async function getGraphEntries(filePath: string) {
|
|
72
|
+
if (!graphDBInstance) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const chunkId = `file:${filePath}`
|
|
78
|
+
const [outgoing, incoming] = await Promise.all([
|
|
79
|
+
graphDBInstance.getOutgoing(chunkId),
|
|
80
|
+
graphDBInstance.getIncoming(chunkId),
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
imports: outgoing.filter(t => t.predicate === "imports"),
|
|
85
|
+
extends: outgoing.filter(t => t.predicate === "extends"),
|
|
86
|
+
used_by: incoming,
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`[usethis_search API] Error getting graph entries for ${filePath}:`, error)
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
package/file-indexer.ts
CHANGED
|
@@ -3,7 +3,8 @@ import path from "path"
|
|
|
3
3
|
import fs from "fs/promises"
|
|
4
4
|
import fsSync from "fs"
|
|
5
5
|
|
|
6
|
-
import { CodebaseIndexer } from "./vectorizer/index.
|
|
6
|
+
import { CodebaseIndexer } from "./vectorizer/index.ts"
|
|
7
|
+
import { initGraphAPI } from "./api"
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* File Indexer Plugin
|
|
@@ -252,7 +253,19 @@ async function ensureIndexOnSessionStart(
|
|
|
252
253
|
for (const [indexName, indexConfig] of Object.entries(config.indexes)) {
|
|
253
254
|
if (!indexConfig.enabled) continue
|
|
254
255
|
const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
|
|
256
|
+
|
|
255
257
|
try {
|
|
258
|
+
// Initialize graph API for Mind plugin integration
|
|
259
|
+
try {
|
|
260
|
+
const graphDB = (indexer as any).graphDB
|
|
261
|
+
if (graphDB) {
|
|
262
|
+
initGraphAPI(graphDB)
|
|
263
|
+
log("Graph API initialized for Mind plugin")
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
debug("Failed to initialize graph API:", error)
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
const indexExists = await hasIndex(projectRoot, indexName)
|
|
257
270
|
const health = await indexer.checkHealth(config.exclude)
|
|
258
271
|
|
package/index.ts
CHANGED
|
@@ -2,20 +2,34 @@ import type { Plugin } from "@opencode-ai/plugin"
|
|
|
2
2
|
|
|
3
3
|
import search from "./tools/search"
|
|
4
4
|
import codeindex from "./tools/codeindex"
|
|
5
|
-
import readInterceptor from "./tools/read-interceptor"
|
|
6
5
|
import FileIndexerPlugin from "./file-indexer"
|
|
7
6
|
|
|
8
|
-
const UsethisSearchPlugin: Plugin = async (
|
|
9
|
-
|
|
7
|
+
const UsethisSearchPlugin: Plugin = async ({ directory, client }) => {
|
|
8
|
+
// Start file indexer (background indexing + event handling)
|
|
9
|
+
let fileIndexerEvent: ((args: any) => Promise<void>) | null = null
|
|
10
|
+
try {
|
|
11
|
+
const hooks = await FileIndexerPlugin({ directory, client } as any)
|
|
12
|
+
fileIndexerEvent = hooks?.event || null
|
|
13
|
+
} catch {
|
|
14
|
+
// file indexer init failed — tools still work, just no auto-indexing
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
return {
|
|
12
|
-
...fileIndexerHooks,
|
|
13
18
|
tool: {
|
|
14
19
|
search,
|
|
15
20
|
codeindex,
|
|
16
|
-
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
event: async (args: any) => {
|
|
24
|
+
if (fileIndexerEvent) {
|
|
25
|
+
try {
|
|
26
|
+
await fileIndexerEvent(args)
|
|
27
|
+
} catch {
|
|
28
|
+
// non-fatal
|
|
29
|
+
}
|
|
30
|
+
}
|
|
17
31
|
},
|
|
18
32
|
}
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
export default UsethisSearchPlugin
|
|
35
|
+
export default UsethisSearchPlugin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comfanion/usethis_search",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.10",
|
|
4
4
|
"description": "OpenCode plugin: semantic search with graph-based context (v3: graph relations, 1-hop context, LSP + regex analyzers)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"index.ts",
|
|
15
|
+
"api.ts",
|
|
15
16
|
"file-indexer.ts",
|
|
16
17
|
"tools/search.ts",
|
|
17
18
|
"tools/codeindex.ts",
|
|
18
|
-
"
|
|
19
|
-
"vectorizer/index.js",
|
|
19
|
+
"vectorizer/index.ts",
|
|
20
20
|
"vectorizer/content-cleaner.ts",
|
|
21
21
|
"vectorizer/metadata-extractor.ts",
|
|
22
22
|
"vectorizer/bm25-index.ts",
|
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
"vectorizer/query-cache.ts",
|
|
25
25
|
"vectorizer/search-metrics.ts",
|
|
26
26
|
"vectorizer/graph-db.ts",
|
|
27
|
+
"vectorizer/usage-tracker.ts",
|
|
27
28
|
"vectorizer/graph-builder.ts",
|
|
28
29
|
"vectorizer/analyzers/regex-analyzer.ts",
|
|
29
30
|
"vectorizer/analyzers/lsp-analyzer.ts",
|
|
31
|
+
"vectorizer/analyzers/lsp-client.ts",
|
|
30
32
|
"vectorizer/chunkers/markdown-chunker.ts",
|
|
31
33
|
"vectorizer/chunkers/code-chunker.ts",
|
|
32
34
|
"vectorizer/chunkers/chunker-factory.ts",
|
package/tools/codeindex.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { tool } from "@opencode-ai/plugin"
|
|
|
9
9
|
import path from "path"
|
|
10
10
|
import fs from "fs/promises"
|
|
11
11
|
|
|
12
|
-
import { CodebaseIndexer } from "../vectorizer/index.
|
|
12
|
+
import { CodebaseIndexer } from "../vectorizer/index.ts"
|
|
13
13
|
|
|
14
14
|
const INDEX_EXTENSIONS: Record<string, string[]> = {
|
|
15
15
|
code: [".js", ".ts", ".jsx", ".tsx", ".go", ".py", ".rs", ".java", ".kt", ".swift", ".c", ".cpp", ".h", ".cs", ".rb", ".php"],
|
|
@@ -61,6 +61,7 @@ Actions:
|
|
|
61
61
|
- "list" → List all available indexes with stats
|
|
62
62
|
- "reindex" → Re-index files using local vectorizer
|
|
63
63
|
- "test" → Run gold dataset quality tests (if configured)
|
|
64
|
+
- "validate-graph" → Validate graph consistency (orphaned triples, broken chunk refs)
|
|
64
65
|
|
|
65
66
|
Available indexes:
|
|
66
67
|
- "code" - Source code files
|
|
@@ -68,7 +69,7 @@ Available indexes:
|
|
|
68
69
|
- "config" - Configuration files`,
|
|
69
70
|
|
|
70
71
|
args: {
|
|
71
|
-
action: tool.schema.enum(["status", "list", "reindex", "test"]).describe("Action to perform"),
|
|
72
|
+
action: tool.schema.enum(["status", "list", "reindex", "test", "validate-graph"]).describe("Action to perform"),
|
|
72
73
|
index: tool.schema.string().optional().default("code").describe("Index name: code, docs, config"),
|
|
73
74
|
dir: tool.schema.string().optional().describe("Directory to index (default: project root)"),
|
|
74
75
|
},
|
|
@@ -170,11 +171,27 @@ Available indexes:
|
|
|
170
171
|
|
|
171
172
|
let indexed = 0
|
|
172
173
|
let skipped = 0
|
|
173
|
-
|
|
174
|
+
const total = files.length
|
|
175
|
+
|
|
176
|
+
// FR-053: Progress reporting during indexing + graph building
|
|
177
|
+
const progressLines: string[] = []
|
|
178
|
+
for (let i = 0; i < files.length; i++) {
|
|
179
|
+
const filePath = files[i]
|
|
174
180
|
try {
|
|
175
181
|
const wasIndexed = await indexer.indexFile(filePath)
|
|
176
|
-
if (wasIndexed)
|
|
177
|
-
|
|
182
|
+
if (wasIndexed) {
|
|
183
|
+
indexed++
|
|
184
|
+
// Log progress at 10%, 25%, 50%, 75%, 100% milestones
|
|
185
|
+
const pct = Math.round(((i + 1) / total) * 100)
|
|
186
|
+
if (pct === 10 || pct === 25 || pct === 50 || pct === 75 || pct === 100) {
|
|
187
|
+
const msg = `Building index + graph: ${i + 1}/${total} files (${pct}%)`
|
|
188
|
+
if (progressLines.length === 0 || progressLines[progressLines.length - 1] !== msg) {
|
|
189
|
+
progressLines.push(msg)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
skipped++
|
|
194
|
+
}
|
|
178
195
|
} catch {}
|
|
179
196
|
}
|
|
180
197
|
|
|
@@ -184,13 +201,21 @@ Available indexes:
|
|
|
184
201
|
let output = `## Re-indexing Complete\n\n`
|
|
185
202
|
output += `**Index:** ${indexName}\n`
|
|
186
203
|
output += `**Directory:** ${args.dir || "(project root)"}\n`
|
|
187
|
-
output += `**Files found:** ${
|
|
204
|
+
output += `**Files found:** ${total}\n`
|
|
188
205
|
output += `**Files indexed:** ${indexed}\n`
|
|
189
206
|
output += `**Files unchanged:** ${skipped}\n`
|
|
190
207
|
output += `**Total chunks:** ${stats.chunkCount}\n`
|
|
191
208
|
if (stats.features) {
|
|
192
209
|
output += `**Chunking:** ${stats.features.chunking}\n`
|
|
193
210
|
}
|
|
211
|
+
|
|
212
|
+
if (progressLines.length > 0) {
|
|
213
|
+
output += `\n**Build Progress:**\n`
|
|
214
|
+
for (const line of progressLines) {
|
|
215
|
+
output += `- ${line}\n`
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
194
219
|
return output
|
|
195
220
|
} catch (error: any) {
|
|
196
221
|
return `Re-indexing failed: ${error.message || String(error)}`
|
|
@@ -273,6 +298,147 @@ Available indexes:
|
|
|
273
298
|
}
|
|
274
299
|
}
|
|
275
300
|
|
|
276
|
-
|
|
301
|
+
// NFR-031: Graph validation
|
|
302
|
+
if (args.action === "validate-graph") {
|
|
303
|
+
try {
|
|
304
|
+
const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
|
|
305
|
+
|
|
306
|
+
// Access internal graphDB and db
|
|
307
|
+
const graphDB = (indexer as any).graphDB
|
|
308
|
+
const db = (indexer as any).db
|
|
309
|
+
|
|
310
|
+
if (!graphDB) {
|
|
311
|
+
await indexer.unloadModel()
|
|
312
|
+
return `## Graph Validation: "${indexName}"\n\nNo graph database found. Run reindex first.`
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 1. Get all triples from graph
|
|
316
|
+
let allTriples: any[] = []
|
|
317
|
+
try {
|
|
318
|
+
allTriples = await graphDB.getAllTriples()
|
|
319
|
+
} catch (e: any) {
|
|
320
|
+
await indexer.unloadModel()
|
|
321
|
+
return `## Graph Validation: "${indexName}"\n\n**Error:** Failed to read graph database: ${e.message || String(e)}\n\nThe graph database may be corrupted. Run: codeindex({ action: "reindex", index: "${indexName}" })`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 2. Get all chunk IDs from vector DB
|
|
325
|
+
const knownChunkIds = new Set<string>()
|
|
326
|
+
const tables = await db.tableNames()
|
|
327
|
+
if (tables.includes("chunks")) {
|
|
328
|
+
const table = await db.openTable("chunks")
|
|
329
|
+
try {
|
|
330
|
+
const rows = await table.search([0]).limit(100000).execute()
|
|
331
|
+
for (const row of rows) {
|
|
332
|
+
if (row.chunk_id) knownChunkIds.add(row.chunk_id)
|
|
333
|
+
}
|
|
334
|
+
} catch (e: any) {
|
|
335
|
+
await indexer.unloadModel()
|
|
336
|
+
return `## Graph Validation: "${indexName}"\n\n**Error:** Failed to read vector database: ${e.message || String(e)}\n\nThe vector database may be corrupted. Run: codeindex({ action: "reindex", index: "${indexName}" })`
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 3. Validate: find orphaned triples (subject or object points to non-existent chunk)
|
|
341
|
+
const orphanedSubjects: Array<{ triple: string; missingId: string }> = []
|
|
342
|
+
const orphanedObjects: Array<{ triple: string; missingId: string }> = []
|
|
343
|
+
const predicateCounts: Record<string, number> = {}
|
|
344
|
+
const sourceCounts: Record<string, number> = {}
|
|
345
|
+
const fileCounts: Record<string, number> = {}
|
|
346
|
+
|
|
347
|
+
for (const t of allTriples) {
|
|
348
|
+
// Count predicates/sources
|
|
349
|
+
predicateCounts[t.predicate] = (predicateCounts[t.predicate] || 0) + 1
|
|
350
|
+
sourceCounts[t.source] = (sourceCounts[t.source] || 0) + 1
|
|
351
|
+
fileCounts[t.file] = (fileCounts[t.file] || 0) + 1
|
|
352
|
+
|
|
353
|
+
// Check subject (skip meta: prefixed subjects)
|
|
354
|
+
if (!t.subject.startsWith("meta:") && t.subject.startsWith("chunk_") && !knownChunkIds.has(t.subject)) {
|
|
355
|
+
orphanedSubjects.push({
|
|
356
|
+
triple: `${t.subject} --[${t.predicate}]--> ${t.object}`,
|
|
357
|
+
missingId: t.subject,
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check object (skip non-chunk objects like file paths, hashes)
|
|
362
|
+
if (t.object.startsWith("chunk_") && !knownChunkIds.has(t.object)) {
|
|
363
|
+
orphanedObjects.push({
|
|
364
|
+
triple: `${t.subject} --[${t.predicate}]--> ${t.object}`,
|
|
365
|
+
missingId: t.object,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 4. Get file metadata stats
|
|
371
|
+
let fileMeta: Array<{ filePath: string; hash: string; timestamp: number }> = []
|
|
372
|
+
try {
|
|
373
|
+
fileMeta = await graphDB.getAllFileMeta()
|
|
374
|
+
} catch (e: any) {
|
|
375
|
+
// Non-fatal - continue validation without metadata
|
|
376
|
+
console.warn(`Warning: Failed to get file metadata: ${e.message || String(e)}`)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await indexer.unloadModel()
|
|
380
|
+
|
|
381
|
+
// 5. Build report
|
|
382
|
+
const totalOrphaned = orphanedSubjects.length + orphanedObjects.length
|
|
383
|
+
const isHealthy = totalOrphaned === 0
|
|
384
|
+
|
|
385
|
+
let output = `## Graph Validation: "${indexName}"\n\n`
|
|
386
|
+
output += `**Status:** ${isHealthy ? "HEALTHY" : "ISSUES FOUND"}\n\n`
|
|
387
|
+
|
|
388
|
+
output += `### Statistics\n`
|
|
389
|
+
output += `- **Total triples:** ${allTriples.length}\n`
|
|
390
|
+
output += `- **Known chunk IDs:** ${knownChunkIds.size}\n`
|
|
391
|
+
output += `- **Files with graph metadata:** ${fileMeta.length}\n`
|
|
392
|
+
output += `- **Unique files in graph:** ${Object.keys(fileCounts).length}\n\n`
|
|
393
|
+
|
|
394
|
+
output += `### Edge Types\n`
|
|
395
|
+
for (const [pred, count] of Object.entries(predicateCounts).sort((a, b) => b[1] - a[1])) {
|
|
396
|
+
output += `- **${pred}:** ${count}\n`
|
|
397
|
+
}
|
|
398
|
+
output += `\n`
|
|
399
|
+
|
|
400
|
+
output += `### Edge Sources\n`
|
|
401
|
+
for (const [source, count] of Object.entries(sourceCounts).sort((a, b) => b[1] - a[1])) {
|
|
402
|
+
output += `- **${source}:** ${count}\n`
|
|
403
|
+
}
|
|
404
|
+
output += `\n`
|
|
405
|
+
|
|
406
|
+
if (totalOrphaned > 0) {
|
|
407
|
+
output += `### Orphaned References (${totalOrphaned})\n\n`
|
|
408
|
+
|
|
409
|
+
if (orphanedSubjects.length > 0) {
|
|
410
|
+
output += `**Broken subjects** (${orphanedSubjects.length}):\n`
|
|
411
|
+
for (const o of orphanedSubjects.slice(0, 10)) {
|
|
412
|
+
output += `- \`${o.missingId}\` in: ${o.triple}\n`
|
|
413
|
+
}
|
|
414
|
+
if (orphanedSubjects.length > 10) {
|
|
415
|
+
output += `- ... and ${orphanedSubjects.length - 10} more\n`
|
|
416
|
+
}
|
|
417
|
+
output += `\n`
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (orphanedObjects.length > 0) {
|
|
421
|
+
output += `**Broken objects** (${orphanedObjects.length}):\n`
|
|
422
|
+
for (const o of orphanedObjects.slice(0, 10)) {
|
|
423
|
+
output += `- \`${o.missingId}\` in: ${o.triple}\n`
|
|
424
|
+
}
|
|
425
|
+
if (orphanedObjects.length > 10) {
|
|
426
|
+
output += `- ... and ${orphanedObjects.length - 10} more\n`
|
|
427
|
+
}
|
|
428
|
+
output += `\n`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
output += `**Recommendation:** Run \`codeindex({ action: "reindex", index: "${indexName}" })\` to rebuild the graph.\n`
|
|
432
|
+
} else {
|
|
433
|
+
output += `### Integrity\nAll chunk references are valid. No orphaned triples found.\n`
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return output
|
|
437
|
+
} catch (error: any) {
|
|
438
|
+
return `Graph validation failed: ${error.message || String(error)}`
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return `Unknown action: ${args.action}. Use: status, list, reindex, test, or validate-graph`
|
|
277
443
|
},
|
|
278
444
|
})
|
package/tools/search.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { tool } from "@opencode-ai/plugin"
|
|
|
10
10
|
import path from "path"
|
|
11
11
|
import fs from "fs/promises"
|
|
12
12
|
|
|
13
|
-
import { CodebaseIndexer } from "../vectorizer/index.
|
|
13
|
+
import { CodebaseIndexer } from "../vectorizer/index.ts"
|
|
14
14
|
|
|
15
15
|
export default tool({
|
|
16
16
|
description: `Search the codebase semantically. Use this to find relevant code snippets, functions, or files based on meaning, not just text matching.
|