@comfanion/workflow 4.3.0 → 4.5.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.
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Code Index Status & Management Tool
3
+ *
4
+ * Check indexing status and trigger re-indexing.
5
+ * Supports multiple indexes: code, docs, config.
6
+ *
7
+ * Usage by model:
8
+ * codeindex({ action: "status" })
9
+ * codeindex({ action: "status", index: "docs" })
10
+ * codeindex({ action: "reindex", index: "code" })
11
+ * codeindex({ action: "list" })
12
+ */
13
+
14
+ import { tool } from "@opencode-ai/plugin"
15
+ import path from "path"
16
+ import fs from "fs/promises"
17
+ import { glob } from "glob"
18
+ import ignore from "ignore"
19
+
20
+ // Index presets (duplicated from vectorizer for independence)
21
+ const INDEX_PRESETS: Record<string, { pattern: string; description: string }> = {
22
+ code: {
23
+ pattern: '**/*.{js,ts,jsx,tsx,mjs,cjs,py,go,rs,java,kt,swift,c,cpp,h,hpp,cs,rb,php,scala,clj}',
24
+ description: 'Source code files'
25
+ },
26
+ docs: {
27
+ pattern: '**/*.{md,mdx,txt,rst,adoc}',
28
+ description: 'Documentation files'
29
+ },
30
+ config: {
31
+ pattern: '**/*.{yaml,yml,json,toml,ini,env,xml}',
32
+ description: 'Configuration files'
33
+ },
34
+ all: {
35
+ pattern: '**/*.{js,ts,jsx,tsx,mjs,cjs,py,go,rs,java,kt,swift,c,cpp,h,hpp,cs,rb,php,scala,clj,md,mdx,txt,rst,adoc,yaml,yml,json,toml}',
36
+ description: 'All supported files'
37
+ }
38
+ }
39
+
40
+ export default tool({
41
+ description: `Check codebase index status or trigger re-indexing for semantic search.
42
+
43
+ Actions:
44
+ - "status" → Show index statistics (specify index or see all)
45
+ - "list" → List all available indexes with stats
46
+ - "reindex" → Re-index files (specify which index)
47
+
48
+ Available indexes:
49
+ - "code" - Source code files
50
+ - "docs" - Documentation files
51
+ - "config" - Configuration files
52
+
53
+ Note: Initial indexing takes ~30s to load the embedding model.`,
54
+
55
+ args: {
56
+ action: tool.schema.enum(["status", "list", "reindex"]).describe("Action to perform"),
57
+ index: tool.schema.string().optional().default("code").describe("Index name for status/reindex: code, docs, config"),
58
+ },
59
+
60
+ async execute(args, context) {
61
+ const projectRoot = process.cwd()
62
+ const vectorizerDir = path.join(projectRoot, ".opencode", "vectorizer")
63
+ const vectorsDir = path.join(projectRoot, ".opencode", "vectors")
64
+
65
+ // Check if vectorizer is installed
66
+ const isInstalled = await fs.access(path.join(vectorizerDir, "node_modules"))
67
+ .then(() => true)
68
+ .catch(() => false)
69
+
70
+ if (!isInstalled) {
71
+ return `❌ Vectorizer not installed.
72
+
73
+ To install:
74
+ \`\`\`bash
75
+ npx opencode-workflow vectorizer install
76
+ \`\`\`
77
+
78
+ This will download the embedding model (~100MB) and set up the vector database.`
79
+ }
80
+
81
+ try {
82
+ const vectorizerModule = path.join(vectorizerDir, "index.js")
83
+ const { CodebaseIndexer, INDEX_PRESETS: PRESETS } = await import(`file://${vectorizerModule}`)
84
+
85
+ // LIST: Show all indexes
86
+ if (args.action === "list") {
87
+ const tempIndexer = await new CodebaseIndexer(projectRoot, "code").init()
88
+ const allStats = await tempIndexer.getAllStats()
89
+
90
+ let output = `## Codebase Index Overview\n\n`
91
+ output += `✅ **Vectorizer installed**\n\n`
92
+
93
+ if (allStats.length === 0) {
94
+ output += `⚠️ **No indexes created yet**\n\n`
95
+ output += `Create indexes with:\n`
96
+ output += `\`\`\`bash\n`
97
+ output += `npx opencode-workflow index --index code # Source code\n`
98
+ output += `npx opencode-workflow index --index docs # Documentation\n`
99
+ output += `npx opencode-workflow index --index config # Config files\n`
100
+ output += `\`\`\`\n\n`
101
+ } else {
102
+ output += `### Active Indexes\n\n`
103
+ for (const stat of allStats) {
104
+ output += `**📁 ${stat.indexName}** - ${stat.description}\n`
105
+ output += ` Files: ${stat.fileCount}, Chunks: ${stat.chunkCount}\n\n`
106
+ }
107
+ }
108
+
109
+ output += `### Available Presets\n\n`
110
+ for (const [name, preset] of Object.entries(PRESETS || INDEX_PRESETS) as [string, any][]) {
111
+ const exists = allStats.find((s: any) => s.indexName === name)
112
+ const status = exists ? "✅" : "⬜"
113
+ output += `${status} **${name}**: ${preset.description}\n`
114
+ }
115
+
116
+ output += `\n### Usage\n`
117
+ output += `\`\`\`\n`
118
+ output += `codesearch({ query: "your query", index: "code" })\n`
119
+ output += `codesearch({ query: "deployment guide", index: "docs" })\n`
120
+ output += `codesearch({ query: "api keys", searchAll: true })\n`
121
+ output += `\`\`\``
122
+
123
+ return output
124
+ }
125
+
126
+ // STATUS: Show specific index status
127
+ if (args.action === "status") {
128
+ const indexName = args.index || "code"
129
+ const hashesFile = path.join(vectorsDir, indexName, "hashes.json")
130
+
131
+ try {
132
+ const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
133
+ const stats = await indexer.getStats()
134
+
135
+ // Get sample files
136
+ const hashesContent = await fs.readFile(hashesFile, "utf8")
137
+ const hashes = JSON.parse(hashesContent)
138
+ const sampleFiles = Object.keys(hashes).slice(0, 5)
139
+
140
+ return `## Index Status: "${indexName}"
141
+
142
+ ✅ **Vectorizer installed**
143
+ ✅ **Index active**
144
+
145
+ **Description:** ${stats.description}
146
+ **Files indexed:** ${stats.fileCount}
147
+ **Chunks:** ${stats.chunkCount}
148
+
149
+ **Sample indexed files:**
150
+ ${sampleFiles.map(f => `- ${f}`).join("\n")}
151
+ ${stats.fileCount > 5 ? `- ... and ${stats.fileCount - 5} more` : ""}
152
+
153
+ **Usage:**
154
+ \`\`\`
155
+ codesearch({ query: "your search query", index: "${indexName}" })
156
+ \`\`\`
157
+
158
+ To re-index:
159
+ \`\`\`
160
+ codeindex({ action: "reindex", index: "${indexName}" })
161
+ \`\`\``
162
+
163
+ } catch {
164
+ return `## Index Status: "${indexName}"
165
+
166
+ ✅ **Vectorizer installed**
167
+ ⚠️ **Index "${indexName}" not created yet**
168
+
169
+ To create this index:
170
+ \`\`\`bash
171
+ npx opencode-workflow index --index ${indexName}
172
+ \`\`\`
173
+
174
+ Or use:
175
+ \`\`\`
176
+ codeindex({ action: "reindex", index: "${indexName}" })
177
+ \`\`\``
178
+ }
179
+ }
180
+
181
+ // REINDEX: Re-index specific index (do it directly, no shell)
182
+ if (args.action === "reindex") {
183
+ const indexName = args.index || "code"
184
+
185
+ try {
186
+ const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
187
+
188
+ // Get pattern from preset
189
+ const preset = (PRESETS || INDEX_PRESETS)[indexName]
190
+ const pattern = preset?.pattern || '**/*.{js,ts,py,go,md,yaml,json}'
191
+
192
+ // Load .gitignore
193
+ let ig = ignore()
194
+ try {
195
+ const gitignore = await fs.readFile(path.join(projectRoot, '.gitignore'), 'utf8')
196
+ ig = ig.add(gitignore)
197
+ } catch {}
198
+ ig.add(['node_modules', '.git', 'dist', 'build', '.opencode/vectors', '.opencode/vectorizer'])
199
+
200
+ // Find files
201
+ const files = await glob(pattern, { cwd: projectRoot, nodir: true })
202
+ const filtered = files.filter((f: string) => !ig.ignores(f))
203
+
204
+ let indexed = 0
205
+ let skipped = 0
206
+
207
+ for (const file of filtered) {
208
+ const filePath = path.join(projectRoot, file)
209
+ try {
210
+ const wasIndexed = await indexer.indexFile(filePath)
211
+ if (wasIndexed) {
212
+ indexed++
213
+ } else {
214
+ skipped++
215
+ }
216
+ } catch {
217
+ // Skip files that can't be read
218
+ }
219
+ }
220
+
221
+ // Unload model to free memory
222
+ await indexer.unloadModel()
223
+
224
+ const stats = await indexer.getStats()
225
+
226
+ return `## Re-indexing Complete ✅
227
+
228
+ **Index:** ${indexName}
229
+ **Description:** ${stats.description}
230
+ **Files found:** ${filtered.length}
231
+ **Files indexed:** ${indexed}
232
+ **Files unchanged:** ${skipped}
233
+ **Total chunks:** ${stats.chunkCount}
234
+
235
+ You can now use semantic search:
236
+ \`\`\`
237
+ codesearch({ query: "your search query", index: "${indexName}" })
238
+ \`\`\``
239
+
240
+ } catch (error: any) {
241
+ return `❌ Re-indexing failed: ${error.message}
242
+
243
+ Try:
244
+ 1. Check if vectorizer is installed: \`npx opencode-workflow vectorizer status\`
245
+ 2. Re-install vectorizer: \`npx opencode-workflow vectorizer install\``
246
+ }
247
+ }
248
+
249
+ return `Unknown action: ${args.action}. Use: status, list, or reindex`
250
+
251
+ } catch (error: any) {
252
+ return `❌ Error: ${error.message}`
253
+ }
254
+ },
255
+ })
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Semantic Code Search Tool
3
+ *
4
+ * Allows the AI model to search the codebase using semantic similarity.
5
+ * Uses local embeddings (all-MiniLM-L6-v2) and LanceDB vector store.
6
+ * Supports multiple indexes: code, docs, config, or search all.
7
+ *
8
+ * Usage by model:
9
+ * codesearch({ query: "authentication middleware", limit: 5 })
10
+ * codesearch({ query: "how to deploy", index: "docs" })
11
+ * codesearch({ query: "database config", index: "config" })
12
+ * codesearch({ query: "error handling", searchAll: true })
13
+ *
14
+ * Prerequisites:
15
+ * npx opencode-workflow vectorizer install
16
+ * npx opencode-workflow index --index code
17
+ * npx opencode-workflow index --index docs
18
+ */
19
+
20
+ import { tool } from "@opencode-ai/plugin"
21
+ import path from "path"
22
+ import fs from "fs/promises"
23
+
24
+ export default tool({
25
+ description: `Search the codebase semantically. Use this to find relevant code snippets, functions, or files based on meaning, not just text matching.
26
+
27
+ Available indexes:
28
+ - "code" (default) - Source code files (*.js, *.ts, *.py, *.go, etc.)
29
+ - "docs" - Documentation files (*.md, *.txt, etc.)
30
+ - "config" - Configuration files (*.yaml, *.json, etc.)
31
+ - searchAll: true - Search across all indexes
32
+
33
+ Examples:
34
+ - "authentication logic" → finds auth-related code
35
+ - "database connection handling" → finds DB setup code
36
+ - "how to deploy" with index: "docs" → finds deployment docs
37
+ - "API keys" with index: "config" → finds config with API settings
38
+
39
+ Prerequisites: Run 'npx opencode-workflow index --index <name>' first.`,
40
+
41
+ args: {
42
+ query: tool.schema.string().describe("Semantic search query describing what you're looking for"),
43
+ index: tool.schema.string().optional().default("code").describe("Index to search: code, docs, config, or custom name"),
44
+ limit: tool.schema.number().optional().default(5).describe("Number of results to return (default: 5)"),
45
+ searchAll: tool.schema.boolean().optional().default(false).describe("Search all indexes instead of just one"),
46
+ },
47
+
48
+ async execute(args, context) {
49
+ const projectRoot = process.cwd()
50
+ const vectorizerDir = path.join(projectRoot, ".opencode", "vectorizer")
51
+ const vectorizerModule = path.join(vectorizerDir, "index.js")
52
+
53
+ // Check if vectorizer is installed
54
+ try {
55
+ await fs.access(path.join(vectorizerDir, "node_modules"))
56
+ } catch {
57
+ return `❌ Vectorizer not installed. Run: npx opencode-workflow vectorizer install`
58
+ }
59
+
60
+ try {
61
+ // Dynamic import of the vectorizer
62
+ const { CodebaseIndexer } = await import(`file://${vectorizerModule}`)
63
+
64
+ let allResults: any[] = []
65
+ const limit = args.limit || 5
66
+ const indexName = args.index || "code"
67
+
68
+ if (args.searchAll) {
69
+ // Search all indexes
70
+ const tempIndexer = await new CodebaseIndexer(projectRoot, "code").init()
71
+ const indexes = await tempIndexer.listIndexes()
72
+
73
+ if (indexes.length === 0) {
74
+ return `❌ No indexes found. Run: npx opencode-workflow index --index code`
75
+ }
76
+
77
+ for (const idx of indexes) {
78
+ const indexer = await new CodebaseIndexer(projectRoot, idx).init()
79
+ const results = await indexer.search(args.query, limit)
80
+ allResults.push(...results.map((r: any) => ({ ...r, _index: idx })))
81
+ }
82
+
83
+ // Sort by distance and take top N
84
+ allResults.sort((a, b) => (a._distance || 0) - (b._distance || 0))
85
+ allResults = allResults.slice(0, limit)
86
+
87
+ } else {
88
+ // Search specific index
89
+ const hashesFile = path.join(projectRoot, ".opencode", "vectors", indexName, "hashes.json")
90
+ try {
91
+ await fs.access(hashesFile)
92
+ } catch {
93
+ return `❌ Index "${indexName}" not found. Run: npx opencode-workflow index --index ${indexName}`
94
+ }
95
+
96
+ const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
97
+ const results = await indexer.search(args.query, limit)
98
+ allResults = results.map((r: any) => ({ ...r, _index: indexName }))
99
+ }
100
+
101
+ if (allResults.length === 0) {
102
+ const scope = args.searchAll ? "any index" : `index "${indexName}"`
103
+ return `No results found in ${scope} for: "${args.query}"\n\nTry:\n- Different keywords\n- Re-index with: npx opencode-workflow index --index ${indexName} --force`
104
+ }
105
+
106
+ // Format results for the model
107
+ const scope = args.searchAll ? "all indexes" : `index "${indexName}"`
108
+ let output = `## Search Results for: "${args.query}" (${scope})\n\n`
109
+
110
+ for (let i = 0; i < allResults.length; i++) {
111
+ const r = allResults[i]
112
+ const score = r._distance ? (1 - r._distance).toFixed(3) : "N/A"
113
+ const indexLabel = args.searchAll ? ` [${r._index}]` : ""
114
+
115
+ output += `### ${i + 1}. ${r.file}${indexLabel}\n`
116
+ output += `**Relevance:** ${score}\n\n`
117
+ output += "```\n"
118
+ // Truncate long content
119
+ const content = r.content.length > 500
120
+ ? r.content.substring(0, 500) + "\n... (truncated)"
121
+ : r.content
122
+ output += content
123
+ output += "\n```\n\n"
124
+ }
125
+
126
+ output += `---\n*Found ${allResults.length} results. Use Read tool to see full files.*`
127
+
128
+ return output
129
+
130
+ } catch (error: any) {
131
+ return `❌ Search failed: ${error.message}\n\nTry re-indexing: npx opencode-workflow index --index ${args.index || "code"} --force`
132
+ }
133
+ },
134
+ })