@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 CHANGED
@@ -235,13 +235,41 @@ program
235
235
  const spinner = ora('Initializing OpenCode Workflow...').start();
236
236
 
237
237
  try {
238
- // If updating, create backup and remove old directory
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
- // Install vectorizer if requested
336
- if (config.install_vectorizer) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.9.1",
3
+ "version": "4.11.0",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "3.0.0",
3
- "buildDate": "2026-01-24T09:48:49.653Z",
3
+ "buildDate": "2026-01-24T10:06:05.371Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -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) {
@@ -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
  */