@comfanion/workflow 4.7.1 → 4.9.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/bin/cli.js +20 -10
- package/package.json +1 -1
- package/src/build-info.json +1 -1
- package/src/opencode/tools/codeindex.ts +138 -85
package/bin/cli.js
CHANGED
|
@@ -438,13 +438,14 @@ program
|
|
|
438
438
|
});
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
// Preserve vectorizer and vectors by moving them temporarily
|
|
442
|
-
const
|
|
441
|
+
// Preserve vectorizer node_modules and vectors by moving them temporarily
|
|
442
|
+
const tempNodeModules = path.join(process.cwd(), '.vectorizer-node_modules-temp');
|
|
443
443
|
const tempVectors = path.join(process.cwd(), '.vectors-temp');
|
|
444
|
+
const vectorizerNodeModules = path.join(vectorizerDir, 'node_modules');
|
|
444
445
|
|
|
445
446
|
if (hasVectorizer) {
|
|
446
|
-
spinner.text = 'Preserving vectorizer...';
|
|
447
|
-
await fs.move(
|
|
447
|
+
spinner.text = 'Preserving vectorizer dependencies...';
|
|
448
|
+
await fs.move(vectorizerNodeModules, tempNodeModules, { overwrite: true });
|
|
448
449
|
}
|
|
449
450
|
if (hasVectors) {
|
|
450
451
|
spinner.text = 'Preserving vector indexes...';
|
|
@@ -455,18 +456,27 @@ program
|
|
|
455
456
|
spinner.text = 'Removing old files...';
|
|
456
457
|
await fs.remove(targetDir);
|
|
457
458
|
|
|
458
|
-
// Copy new files
|
|
459
|
+
// Copy new files (including updated vectorizer source)
|
|
459
460
|
spinner.text = 'Installing new version...';
|
|
460
461
|
await fs.copy(OPENCODE_SRC, targetDir);
|
|
461
462
|
|
|
462
|
-
//
|
|
463
|
+
// Copy new vectorizer source files
|
|
464
|
+
if (await fs.pathExists(VECTORIZER_SRC)) {
|
|
465
|
+
spinner.text = 'Updating vectorizer...';
|
|
466
|
+
const newVectorizerDir = path.join(targetDir, 'vectorizer');
|
|
467
|
+
await fs.ensureDir(newVectorizerDir);
|
|
468
|
+
await fs.copy(path.join(VECTORIZER_SRC, 'index.js'), path.join(newVectorizerDir, 'index.js'));
|
|
469
|
+
await fs.copy(path.join(VECTORIZER_SRC, 'package.json'), path.join(newVectorizerDir, 'package.json'));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Restore vectorizer node_modules and vectors
|
|
463
473
|
if (hasVectorizer) {
|
|
464
|
-
spinner.text = 'Restoring vectorizer...';
|
|
465
|
-
await fs.move(
|
|
474
|
+
spinner.text = 'Restoring vectorizer dependencies...';
|
|
475
|
+
await fs.move(tempNodeModules, path.join(targetDir, 'vectorizer', 'node_modules'), { overwrite: true });
|
|
466
476
|
}
|
|
467
477
|
if (hasVectors) {
|
|
468
478
|
spinner.text = 'Restoring vector indexes...';
|
|
469
|
-
await fs.move(tempVectors,
|
|
479
|
+
await fs.move(tempVectors, path.join(targetDir, 'vectors'), { overwrite: true });
|
|
470
480
|
}
|
|
471
481
|
|
|
472
482
|
// Restore user's config.yaml
|
|
@@ -482,7 +492,7 @@ program
|
|
|
482
492
|
|
|
483
493
|
console.log(chalk.green('✅ Your config.yaml was preserved.'));
|
|
484
494
|
if (hasVectorizer) {
|
|
485
|
-
console.log(chalk.green('✅ Vectorizer
|
|
495
|
+
console.log(chalk.green('✅ Vectorizer updated (node_modules preserved).'));
|
|
486
496
|
}
|
|
487
497
|
if (hasVectors) {
|
|
488
498
|
console.log(chalk.green('✅ Vector indexes were preserved.'));
|
package/package.json
CHANGED
package/src/build-info.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Code Index Status & Management Tool
|
|
3
3
|
*
|
|
4
4
|
* Check indexing status and trigger re-indexing.
|
|
5
|
-
*
|
|
5
|
+
* Uses LOCAL vectorizer - no npm calls needed.
|
|
6
6
|
*
|
|
7
7
|
* Usage by model:
|
|
8
8
|
* codeindex({ action: "status" })
|
|
@@ -14,38 +14,78 @@
|
|
|
14
14
|
import { tool } from "@opencode-ai/plugin"
|
|
15
15
|
import path from "path"
|
|
16
16
|
import fs from "fs/promises"
|
|
17
|
-
import { execSync } from "child_process"
|
|
18
17
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
code:
|
|
22
|
-
docs:
|
|
23
|
-
config:
|
|
18
|
+
// File extensions for each index type
|
|
19
|
+
const INDEX_EXTENSIONS: Record<string, string[]> = {
|
|
20
|
+
code: ['.js', '.ts', '.jsx', '.tsx', '.go', '.py', '.rs', '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.cs', '.rb', '.php'],
|
|
21
|
+
docs: ['.md', '.mdx', '.txt', '.rst', '.adoc'],
|
|
22
|
+
config: ['.yaml', '.yml', '.json', '.toml', '.ini', '.xml'],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const INDEX_DESCRIPTIONS: Record<string, string> = {
|
|
26
|
+
code: 'Source code files',
|
|
27
|
+
docs: 'Documentation files',
|
|
28
|
+
config: 'Configuration files',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Simple recursive file walker (no external deps)
|
|
32
|
+
async function walkDir(dir: string, extensions: string[], ignore: string[] = []): Promise<string[]> {
|
|
33
|
+
const files: string[] = []
|
|
34
|
+
|
|
35
|
+
async function walk(currentDir: string) {
|
|
36
|
+
try {
|
|
37
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true })
|
|
38
|
+
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const fullPath = path.join(currentDir, entry.name)
|
|
41
|
+
const relativePath = path.relative(dir, fullPath)
|
|
42
|
+
|
|
43
|
+
// Skip ignored directories
|
|
44
|
+
if (ignore.some(ig => relativePath.startsWith(ig) || entry.name === ig)) {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
await walk(fullPath)
|
|
50
|
+
} else if (entry.isFile()) {
|
|
51
|
+
const ext = path.extname(entry.name).toLowerCase()
|
|
52
|
+
if (extensions.includes(ext)) {
|
|
53
|
+
files.push(fullPath)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await walk(dir)
|
|
61
|
+
return files
|
|
24
62
|
}
|
|
25
63
|
|
|
26
64
|
export default tool({
|
|
27
65
|
description: `Check codebase index status or trigger re-indexing for semantic search.
|
|
28
66
|
|
|
29
67
|
Actions:
|
|
30
|
-
- "status" → Show index statistics
|
|
68
|
+
- "status" → Show index statistics
|
|
31
69
|
- "list" → List all available indexes with stats
|
|
32
|
-
- "reindex" → Re-index files (
|
|
70
|
+
- "reindex" → Re-index files using LOCAL vectorizer (no npm needed)
|
|
33
71
|
|
|
34
72
|
Available indexes:
|
|
35
|
-
- "code" - Source code files
|
|
36
|
-
- "docs" - Documentation files
|
|
37
|
-
- "config" - Configuration files
|
|
73
|
+
- "code" - Source code files (.js, .ts, .go, .py, etc.)
|
|
74
|
+
- "docs" - Documentation files (.md, .txt, etc.)
|
|
75
|
+
- "config" - Configuration files (.yaml, .json, etc.)
|
|
38
76
|
|
|
39
|
-
Note:
|
|
77
|
+
Note: First indexing takes ~30s to load embedding model.`,
|
|
40
78
|
|
|
41
79
|
args: {
|
|
42
80
|
action: tool.schema.enum(["status", "list", "reindex"]).describe("Action to perform"),
|
|
43
81
|
index: tool.schema.string().optional().default("code").describe("Index name: code, docs, config"),
|
|
82
|
+
dir: tool.schema.string().optional().describe("Directory to index (default: project root)"),
|
|
44
83
|
},
|
|
45
84
|
|
|
46
85
|
async execute(args, context) {
|
|
47
86
|
const projectRoot = process.cwd()
|
|
48
87
|
const vectorizerDir = path.join(projectRoot, ".opencode", "vectorizer")
|
|
88
|
+
const vectorizerModule = path.join(vectorizerDir, "index.js")
|
|
49
89
|
const vectorsDir = path.join(projectRoot, ".opencode", "vectors")
|
|
50
90
|
|
|
51
91
|
// Check if vectorizer is installed
|
|
@@ -56,71 +96,59 @@ Note: Initial indexing takes ~30s to load the embedding model.`,
|
|
|
56
96
|
if (!isInstalled) {
|
|
57
97
|
return `❌ Vectorizer not installed.
|
|
58
98
|
|
|
59
|
-
To install:
|
|
99
|
+
To install, run in terminal:
|
|
60
100
|
\`\`\`bash
|
|
61
101
|
npx @comfanion/workflow vectorizer install
|
|
62
102
|
\`\`\`
|
|
63
103
|
|
|
64
|
-
This
|
|
104
|
+
This downloads the embedding model (~100MB).`
|
|
65
105
|
}
|
|
66
106
|
|
|
67
107
|
const indexName = args.index || "code"
|
|
68
108
|
|
|
69
109
|
// LIST: Show all indexes
|
|
70
110
|
if (args.action === "list") {
|
|
111
|
+
let output = `## Codebase Index Overview\n\n`
|
|
112
|
+
output += `✅ **Vectorizer installed**\n\n`
|
|
113
|
+
|
|
114
|
+
const indexes: string[] = []
|
|
71
115
|
try {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Fallback: read hashes files directly
|
|
79
|
-
let output = `## Codebase Index Overview\n\n`
|
|
80
|
-
output += `✅ **Vectorizer installed**\n\n`
|
|
81
|
-
|
|
82
|
-
const indexes: string[] = []
|
|
83
|
-
try {
|
|
84
|
-
const entries = await fs.readdir(vectorsDir, { withFileTypes: true })
|
|
85
|
-
for (const entry of entries) {
|
|
86
|
-
if (entry.isDirectory()) {
|
|
87
|
-
indexes.push(entry.name)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch {}
|
|
91
|
-
|
|
92
|
-
if (indexes.length === 0) {
|
|
93
|
-
output += `⚠️ **No indexes created yet**\n\n`
|
|
94
|
-
output += `Create indexes with:\n`
|
|
95
|
-
output += `\`\`\`bash\n`
|
|
96
|
-
output += `npx @comfanion/workflow index --index code\n`
|
|
97
|
-
output += `npx @comfanion/workflow index --index docs --dir docs/\n`
|
|
98
|
-
output += `\`\`\`\n`
|
|
99
|
-
} else {
|
|
100
|
-
output += `### Active Indexes\n\n`
|
|
101
|
-
for (const idx of indexes) {
|
|
102
|
-
try {
|
|
103
|
-
const hashesPath = path.join(vectorsDir, idx, "hashes.json")
|
|
104
|
-
const hashes = JSON.parse(await fs.readFile(hashesPath, "utf8"))
|
|
105
|
-
const fileCount = Object.keys(hashes).length
|
|
106
|
-
const desc = INDEX_PRESETS[idx]?.description || "Custom index"
|
|
107
|
-
output += `**📁 ${idx}** - ${desc}\n`
|
|
108
|
-
output += ` Files: ${fileCount}\n\n`
|
|
109
|
-
} catch {}
|
|
116
|
+
const entries = await fs.readdir(vectorsDir, { withFileTypes: true })
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
indexes.push(entry.name)
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
} catch {}
|
|
123
|
+
|
|
124
|
+
if (indexes.length === 0) {
|
|
125
|
+
output += `⚠️ **No indexes created yet**\n\n`
|
|
126
|
+
output += `Create indexes:\n`
|
|
114
127
|
output += `\`\`\`\n`
|
|
115
|
-
output += `
|
|
116
|
-
output += `
|
|
117
|
-
output +=
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
output += `codeindex({ action: "reindex", index: "code" })\n`
|
|
129
|
+
output += `codeindex({ action: "reindex", index: "docs", dir: "docs/" })\n`
|
|
130
|
+
output += `\`\`\`\n`
|
|
131
|
+
} else {
|
|
132
|
+
output += `### Active Indexes\n\n`
|
|
133
|
+
for (const idx of indexes) {
|
|
134
|
+
try {
|
|
135
|
+
const hashesPath = path.join(vectorsDir, idx, "hashes.json")
|
|
136
|
+
const hashes = JSON.parse(await fs.readFile(hashesPath, "utf8"))
|
|
137
|
+
const fileCount = Object.keys(hashes).length
|
|
138
|
+
const desc = INDEX_DESCRIPTIONS[idx] || "Custom index"
|
|
139
|
+
output += `**📁 ${idx}** - ${desc}\n`
|
|
140
|
+
output += ` Files: ${fileCount}\n\n`
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
123
143
|
}
|
|
144
|
+
|
|
145
|
+
output += `### Usage\n`
|
|
146
|
+
output += `\`\`\`\n`
|
|
147
|
+
output += `codesearch({ query: "your query", index: "code" })\n`
|
|
148
|
+
output += `codesearch({ query: "how to deploy", index: "docs" })\n`
|
|
149
|
+
output += `\`\`\``
|
|
150
|
+
|
|
151
|
+
return output
|
|
124
152
|
}
|
|
125
153
|
|
|
126
154
|
// STATUS: Show specific index status
|
|
@@ -132,7 +160,7 @@ This will download the embedding model (~100MB) and set up the vector database.`
|
|
|
132
160
|
const hashes = JSON.parse(hashesContent)
|
|
133
161
|
const fileCount = Object.keys(hashes).length
|
|
134
162
|
const sampleFiles = Object.keys(hashes).slice(0, 5)
|
|
135
|
-
const desc =
|
|
163
|
+
const desc = INDEX_DESCRIPTIONS[indexName] || "Custom index"
|
|
136
164
|
|
|
137
165
|
return `## Index Status: "${indexName}"
|
|
138
166
|
|
|
@@ -152,8 +180,8 @@ codesearch({ query: "your search query", index: "${indexName}" })
|
|
|
152
180
|
\`\`\`
|
|
153
181
|
|
|
154
182
|
To re-index:
|
|
155
|
-
\`\`\`
|
|
156
|
-
|
|
183
|
+
\`\`\`
|
|
184
|
+
codeindex({ action: "reindex", index: "${indexName}" })
|
|
157
185
|
\`\`\``
|
|
158
186
|
|
|
159
187
|
} catch {
|
|
@@ -163,33 +191,58 @@ npx @comfanion/workflow index --index ${indexName}
|
|
|
163
191
|
⚠️ **Index "${indexName}" not created yet**
|
|
164
192
|
|
|
165
193
|
To create this index:
|
|
166
|
-
\`\`\`
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
194
|
+
\`\`\`
|
|
195
|
+
codeindex({ action: "reindex", index: "${indexName}" })
|
|
196
|
+
\`\`\`
|
|
197
|
+
|
|
198
|
+
Or with specific directory:
|
|
199
|
+
\`\`\`
|
|
200
|
+
codeindex({ action: "reindex", index: "${indexName}", dir: "src/" })
|
|
170
201
|
\`\`\``
|
|
171
202
|
}
|
|
172
203
|
}
|
|
173
204
|
|
|
174
|
-
// REINDEX: Re-index using
|
|
205
|
+
// REINDEX: Re-index using LOCAL vectorizer (no npm!)
|
|
175
206
|
if (args.action === "reindex") {
|
|
176
207
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
// Import local vectorizer
|
|
209
|
+
const { CodebaseIndexer } = await import(`file://${vectorizerModule}`)
|
|
210
|
+
const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
|
|
211
|
+
|
|
212
|
+
// Determine directory and extensions
|
|
213
|
+
const baseDir = args.dir
|
|
214
|
+
? path.resolve(projectRoot, args.dir)
|
|
215
|
+
: projectRoot
|
|
216
|
+
const extensions = INDEX_EXTENSIONS[indexName] || INDEX_EXTENSIONS.code
|
|
217
|
+
|
|
218
|
+
// Find files using simple walker
|
|
219
|
+
const ignoreList = ['node_modules', '.git', 'dist', 'build', '.opencode', 'vendor', '__pycache__']
|
|
220
|
+
const files = await walkDir(baseDir, extensions, ignoreList)
|
|
221
|
+
|
|
222
|
+
let indexed = 0
|
|
223
|
+
let skipped = 0
|
|
224
|
+
|
|
225
|
+
for (const filePath of files) {
|
|
226
|
+
try {
|
|
227
|
+
const wasIndexed = await indexer.indexFile(filePath)
|
|
228
|
+
if (wasIndexed) indexed++
|
|
229
|
+
else skipped++
|
|
230
|
+
} catch {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Unload model to free memory
|
|
234
|
+
await indexer.unloadModel()
|
|
235
|
+
|
|
236
|
+
const stats = await indexer.getStats()
|
|
188
237
|
|
|
189
238
|
return `## Re-indexing Complete ✅
|
|
190
239
|
|
|
191
240
|
**Index:** ${indexName}
|
|
192
|
-
**
|
|
241
|
+
**Directory:** ${args.dir || "(project root)"}
|
|
242
|
+
**Files found:** ${files.length}
|
|
243
|
+
**Files indexed:** ${indexed}
|
|
244
|
+
**Files unchanged:** ${skipped}
|
|
245
|
+
**Total chunks:** ${stats.chunkCount}
|
|
193
246
|
|
|
194
247
|
You can now use semantic search:
|
|
195
248
|
\`\`\`
|
|
@@ -199,9 +252,9 @@ codesearch({ query: "your search query", index: "${indexName}" })
|
|
|
199
252
|
} catch (error: any) {
|
|
200
253
|
return `❌ Re-indexing failed: ${error.message}
|
|
201
254
|
|
|
202
|
-
|
|
255
|
+
Make sure vectorizer is installed:
|
|
203
256
|
\`\`\`bash
|
|
204
|
-
npx @comfanion/workflow
|
|
257
|
+
npx @comfanion/workflow vectorizer install
|
|
205
258
|
\`\`\``
|
|
206
259
|
}
|
|
207
260
|
}
|