@comfanion/workflow 4.9.1 → 4.11.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 +61 -4
- package/package.json +1 -1
- package/src/build-info.json +1 -1
- package/src/opencode/tools/codesearch.ts +14 -0
- package/src/vectorizer/index.js +48 -0
package/bin/cli.js
CHANGED
|
@@ -235,13 +235,41 @@ program
|
|
|
235
235
|
const spinner = ora('Initializing OpenCode Workflow...').start();
|
|
236
236
|
|
|
237
237
|
try {
|
|
238
|
-
// If updating,
|
|
238
|
+
// If updating, preserve vectorizer and indexes
|
|
239
|
+
const vectorizerDir = path.join(targetDir, 'vectorizer');
|
|
240
|
+
const vectorsDir = path.join(targetDir, 'vectors');
|
|
241
|
+
const vectorizerNodeModules = path.join(vectorizerDir, 'node_modules');
|
|
242
|
+
const tempNodeModules = path.join(process.cwd(), '.vectorizer-node_modules-temp');
|
|
243
|
+
const tempVectors = path.join(process.cwd(), '.vectors-temp');
|
|
244
|
+
|
|
245
|
+
let hadVectorizer = false;
|
|
246
|
+
let hadVectors = false;
|
|
247
|
+
|
|
239
248
|
if (await fs.pathExists(targetDir)) {
|
|
240
249
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
241
250
|
const backupDir = path.join(process.cwd(), `.opencode.backup-${timestamp}`);
|
|
242
251
|
|
|
252
|
+
// Check what we need to preserve
|
|
253
|
+
hadVectorizer = await fs.pathExists(vectorizerNodeModules);
|
|
254
|
+
hadVectors = await fs.pathExists(vectorsDir);
|
|
255
|
+
|
|
256
|
+
// Preserve vectorizer node_modules (100MB+, don't backup)
|
|
257
|
+
if (hadVectorizer) {
|
|
258
|
+
spinner.text = 'Preserving vectorizer dependencies...';
|
|
259
|
+
await fs.move(vectorizerNodeModules, tempNodeModules, { overwrite: true });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Preserve vector indexes
|
|
263
|
+
if (hadVectors) {
|
|
264
|
+
spinner.text = 'Preserving vector indexes...';
|
|
265
|
+
await fs.move(vectorsDir, tempVectors, { overwrite: true });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Create backup (without node_modules and vectors)
|
|
243
269
|
spinner.text = 'Creating backup...';
|
|
244
|
-
await fs.copy(targetDir, backupDir
|
|
270
|
+
await fs.copy(targetDir, backupDir, {
|
|
271
|
+
filter: (src) => !src.includes('node_modules') && !src.includes('vectors')
|
|
272
|
+
});
|
|
245
273
|
|
|
246
274
|
spinner.text = 'Removing old .opencode/...';
|
|
247
275
|
await fs.remove(targetDir);
|
|
@@ -254,6 +282,27 @@ program
|
|
|
254
282
|
// Copy .opencode structure (fresh, no old files)
|
|
255
283
|
await fs.copy(OPENCODE_SRC, targetDir);
|
|
256
284
|
|
|
285
|
+
// Copy updated vectorizer source files
|
|
286
|
+
if (await fs.pathExists(VECTORIZER_SRC)) {
|
|
287
|
+
spinner.text = 'Updating vectorizer...';
|
|
288
|
+
const newVectorizerDir = path.join(targetDir, 'vectorizer');
|
|
289
|
+
await fs.ensureDir(newVectorizerDir);
|
|
290
|
+
await fs.copy(path.join(VECTORIZER_SRC, 'index.js'), path.join(newVectorizerDir, 'index.js'));
|
|
291
|
+
await fs.copy(path.join(VECTORIZER_SRC, 'package.json'), path.join(newVectorizerDir, 'package.json'));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Restore vectorizer node_modules
|
|
295
|
+
if (hadVectorizer) {
|
|
296
|
+
spinner.text = 'Restoring vectorizer dependencies...';
|
|
297
|
+
await fs.move(tempNodeModules, path.join(targetDir, 'vectorizer', 'node_modules'), { overwrite: true });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Restore vector indexes
|
|
301
|
+
if (hadVectors) {
|
|
302
|
+
spinner.text = 'Restoring vector indexes...';
|
|
303
|
+
await fs.move(tempVectors, path.join(targetDir, 'vectors'), { overwrite: true });
|
|
304
|
+
}
|
|
305
|
+
|
|
257
306
|
// Update config.yaml with user values
|
|
258
307
|
spinner.text = 'Configuring...';
|
|
259
308
|
const configPath = path.join(targetDir, 'config.yaml');
|
|
@@ -332,8 +381,16 @@ program
|
|
|
332
381
|
|
|
333
382
|
spinner.succeed(chalk.green('OpenCode Workflow initialized!'));
|
|
334
383
|
|
|
335
|
-
//
|
|
336
|
-
if (
|
|
384
|
+
// Show what was preserved
|
|
385
|
+
if (hadVectorizer) {
|
|
386
|
+
console.log(chalk.green('✅ Vectorizer updated (dependencies preserved)'));
|
|
387
|
+
}
|
|
388
|
+
if (hadVectors) {
|
|
389
|
+
console.log(chalk.green('✅ Vector indexes preserved'));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Install vectorizer if requested (and not already installed)
|
|
393
|
+
if (config.install_vectorizer && !hadVectorizer) {
|
|
337
394
|
await installVectorizer(targetDir);
|
|
338
395
|
}
|
|
339
396
|
|
package/package.json
CHANGED
package/src/build-info.json
CHANGED
|
@@ -43,6 +43,7 @@ Prerequisites: Run 'npx @comfanion/workflow index --index <name>' first.`,
|
|
|
43
43
|
index: tool.schema.string().optional().default("code").describe("Index to search: code, docs, config, or custom name"),
|
|
44
44
|
limit: tool.schema.number().optional().default(5).describe("Number of results to return (default: 5)"),
|
|
45
45
|
searchAll: tool.schema.boolean().optional().default(false).describe("Search all indexes instead of just one"),
|
|
46
|
+
freshen: tool.schema.boolean().optional().default(true).describe("Auto-update stale files before searching (default: true)"),
|
|
46
47
|
},
|
|
47
48
|
|
|
48
49
|
async execute(args, context) {
|
|
@@ -65,6 +66,14 @@ Prerequisites: Run 'npx @comfanion/workflow index --index <name>' first.`,
|
|
|
65
66
|
const limit = args.limit || 5
|
|
66
67
|
const indexName = args.index || "code"
|
|
67
68
|
|
|
69
|
+
// Auto-freshen stale files before searching
|
|
70
|
+
let freshenStats = { updated: 0 }
|
|
71
|
+
if (args.freshen !== false) {
|
|
72
|
+
const tempIndexer = await new CodebaseIndexer(projectRoot, args.index || "code").init()
|
|
73
|
+
freshenStats = await tempIndexer.freshen()
|
|
74
|
+
await tempIndexer.unloadModel() // Free memory after freshen
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
if (args.searchAll) {
|
|
69
78
|
// Search all indexes
|
|
70
79
|
const tempIndexer = await new CodebaseIndexer(projectRoot, "code").init()
|
|
@@ -76,8 +85,12 @@ Prerequisites: Run 'npx @comfanion/workflow index --index <name>' first.`,
|
|
|
76
85
|
|
|
77
86
|
for (const idx of indexes) {
|
|
78
87
|
const indexer = await new CodebaseIndexer(projectRoot, idx).init()
|
|
88
|
+
if (args.freshen !== false) {
|
|
89
|
+
await indexer.freshen()
|
|
90
|
+
}
|
|
79
91
|
const results = await indexer.search(args.query, limit)
|
|
80
92
|
allResults.push(...results.map((r: any) => ({ ...r, _index: idx })))
|
|
93
|
+
await indexer.unloadModel() // Free memory after each index search
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
// Sort by distance and take top N
|
|
@@ -96,6 +109,7 @@ Prerequisites: Run 'npx @comfanion/workflow index --index <name>' first.`,
|
|
|
96
109
|
const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
|
|
97
110
|
const results = await indexer.search(args.query, limit)
|
|
98
111
|
allResults = results.map((r: any) => ({ ...r, _index: indexName }))
|
|
112
|
+
await indexer.unloadModel() // Free memory after search
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
if (allResults.length === 0) {
|
package/src/vectorizer/index.js
CHANGED
|
@@ -196,6 +196,54 @@ class CodebaseIndexer {
|
|
|
196
196
|
return results;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Freshen index - check for stale files and reindex only changed ones
|
|
201
|
+
* Returns { checked, updated, deleted } counts
|
|
202
|
+
*/
|
|
203
|
+
async freshen() {
|
|
204
|
+
let checked = 0;
|
|
205
|
+
let updated = 0;
|
|
206
|
+
let deleted = 0;
|
|
207
|
+
|
|
208
|
+
const indexedFiles = Object.keys(this.hashes);
|
|
209
|
+
|
|
210
|
+
for (const relPath of indexedFiles) {
|
|
211
|
+
checked++;
|
|
212
|
+
const filePath = path.join(this.root, relPath);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
216
|
+
const currentHash = this.fileHash(content);
|
|
217
|
+
|
|
218
|
+
if (this.hashes[relPath] !== currentHash) {
|
|
219
|
+
// File changed - reindex it
|
|
220
|
+
await this.indexFile(filePath);
|
|
221
|
+
updated++;
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
// File deleted or unreadable - remove from index
|
|
225
|
+
delete this.hashes[relPath];
|
|
226
|
+
deleted++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (deleted > 0) {
|
|
231
|
+
await this.saveHashes();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { checked, updated, deleted };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Index a single file by path (convenience method)
|
|
239
|
+
*/
|
|
240
|
+
async indexSingleFile(filePath) {
|
|
241
|
+
const absPath = path.isAbsolute(filePath)
|
|
242
|
+
? filePath
|
|
243
|
+
: path.join(this.root, filePath);
|
|
244
|
+
return await this.indexFile(absPath);
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
/**
|
|
200
248
|
* Get indexing statistics for this index
|
|
201
249
|
*/
|