@comfanion/workflow 4.36.1 → 4.36.3

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
@@ -576,6 +576,12 @@ program
576
576
  spinner.text = 'Restoring config.yaml...';
577
577
  let mergedConfig = configBackup;
578
578
 
579
+ // Extract user's custom exclude patterns before merge
580
+ const userExcludeMatch = configBackup.match(/exclude:\s*\n((?:\s+-\s+.+\n?)+)/);
581
+ const userExcludes = userExcludeMatch
582
+ ? userExcludeMatch[1].match(/-\s+(.+)/g)?.map(m => m.replace(/^-\s+/, '').trim()) || []
583
+ : [];
584
+
579
585
  // Add vectorizer section if missing
580
586
  if (!mergedConfig.includes('vectorizer:')) {
581
587
  const newConfigPath = path.join(targetDir, 'config.yaml');
@@ -593,6 +599,30 @@ program
593
599
  }
594
600
  }
595
601
 
602
+ // Merge user's custom excludes into vectorizer section
603
+ if (userExcludes.length > 0) {
604
+ const newConfigPath = path.join(targetDir, 'config.yaml');
605
+ const newConfig = await fs.readFile(newConfigPath, 'utf8');
606
+ const defaultExcludeMatch = newConfig.match(/exclude:\s*\n((?:\s+-\s+.+\n?)+)/);
607
+ const defaultExcludes = defaultExcludeMatch
608
+ ? defaultExcludeMatch[1].match(/-\s+(.+)/g)?.map(m => m.replace(/^-\s+/, '').trim()) || []
609
+ : [];
610
+
611
+ // Find excludes that user added (not in defaults)
612
+ const customExcludes = userExcludes.filter(e => !defaultExcludes.includes(e));
613
+
614
+ if (customExcludes.length > 0) {
615
+ // Add custom excludes to the merged config
616
+ const excludeInsertPoint = mergedConfig.match(/exclude:\s*\n((?:\s+-\s+.+\n?)+)/);
617
+ if (excludeInsertPoint) {
618
+ const insertAfter = excludeInsertPoint.index + excludeInsertPoint[0].length;
619
+ const customLines = customExcludes.map(e => ` - ${e}`).join('\n') + '\n';
620
+ mergedConfig = mergedConfig.slice(0, insertAfter) + customLines + mergedConfig.slice(insertAfter);
621
+ console.log(chalk.green(` ✅ Preserved ${customExcludes.length} custom exclude patterns`));
622
+ }
623
+ }
624
+ }
625
+
596
626
  await fs.writeFile(configPath, mergedConfig);
597
627
 
598
628
  // Install plugin dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.36.1",
3
+ "version": "4.36.3",
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-24T14:49:34.378Z",
3
+ "buildDate": "2026-01-24T14:58:40.429Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -22,18 +22,22 @@ if (!DEBUG) {
22
22
  const INDEX_PRESETS = {
23
23
  code: {
24
24
  pattern: '**/*.{js,ts,jsx,tsx,mjs,cjs,py,go,rs,java,kt,swift,c,cpp,h,hpp,cs,rb,php,scala,clj}',
25
- description: 'Source code files'
25
+ ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/.opencode/**', '**/docs/**', '**/vendor/**', '**/__pycache__/**'],
26
+ description: 'Source code files (excludes docs, vendor, node_modules)'
26
27
  },
27
28
  docs: {
28
- pattern: '**/*.{md,mdx,txt,rst,adoc}',
29
- description: 'Documentation files'
29
+ pattern: 'docs/**/*.{md,mdx,txt,rst,adoc}',
30
+ ignore: [],
31
+ description: 'Documentation in docs/ folder'
30
32
  },
31
33
  config: {
32
34
  pattern: '**/*.{yaml,yml,json,toml,ini,env,xml}',
35
+ ignore: ['**/node_modules/**', '**/.git/**', '**/.opencode/**'],
33
36
  description: 'Configuration files'
34
37
  },
35
38
  all: {
36
39
  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}',
40
+ ignore: ['**/node_modules/**', '**/.git/**', '**/.opencode/**'],
37
41
  description: 'All supported files'
38
42
  }
39
43
  };
@@ -208,6 +212,43 @@ class CodebaseIndexer {
208
212
  return results;
209
213
  }
210
214
 
215
+ /**
216
+ * Check if index needs full reindex (files don't match current patterns)
217
+ * @param {string[]} extraIgnore - Additional patterns to ignore
218
+ * Returns { needsReindex, reason, currentCount, expectedCount }
219
+ */
220
+ async checkHealth(extraIgnore = []) {
221
+ const { glob } = await import('glob');
222
+ const preset = INDEX_PRESETS[this.indexName] || INDEX_PRESETS.code;
223
+
224
+ // Combine preset ignore with extra ignore patterns
225
+ const ignore = [...(preset.ignore || []), ...extraIgnore.map(p => `**/${p}/**`)];
226
+
227
+ const expectedFiles = await glob(preset.pattern, {
228
+ cwd: this.root,
229
+ nodir: true,
230
+ ignore
231
+ });
232
+
233
+ const indexedFiles = Object.keys(this.hashes);
234
+ const currentCount = indexedFiles.length;
235
+ const expectedCount = expectedFiles.length;
236
+
237
+ // Check if counts differ significantly (>20% difference or index is empty)
238
+ const diff = Math.abs(currentCount - expectedCount);
239
+ const threshold = Math.max(5, expectedCount * 0.2); // 20% or at least 5 files
240
+
241
+ if (currentCount === 0 && expectedCount > 0) {
242
+ return { needsReindex: true, reason: 'empty', currentCount, expectedCount };
243
+ }
244
+
245
+ if (diff > threshold) {
246
+ return { needsReindex: true, reason: 'mismatch', currentCount, expectedCount };
247
+ }
248
+
249
+ return { needsReindex: false, reason: 'ok', currentCount, expectedCount };
250
+ }
251
+
211
252
  /**
212
253
  * Freshen index - check for stale files and reindex only changed ones
213
254
  * Returns { checked, updated, deleted } counts
@@ -246,6 +287,46 @@ class CodebaseIndexer {
246
287
  return { checked, updated, deleted };
247
288
  }
248
289
 
290
+ /**
291
+ * Index all files matching the preset pattern
292
+ * @param {function} onProgress - Optional callback(indexed, total, currentFile)
293
+ * @param {string[]} extraIgnore - Additional patterns to ignore
294
+ * Returns { indexed, skipped } counts
295
+ */
296
+ async indexAll(onProgress = null, extraIgnore = []) {
297
+ const { glob } = await import('glob');
298
+ const preset = INDEX_PRESETS[this.indexName] || INDEX_PRESETS.code;
299
+
300
+ // Combine preset ignore with extra ignore patterns
301
+ const ignore = [...(preset.ignore || []), ...extraIgnore.map(p => `**/${p}/**`)];
302
+
303
+ const files = await glob(preset.pattern, {
304
+ cwd: this.root,
305
+ nodir: true,
306
+ ignore
307
+ });
308
+
309
+ let indexed = 0;
310
+ let skipped = 0;
311
+
312
+ for (const relPath of files) {
313
+ const filePath = path.join(this.root, relPath);
314
+ try {
315
+ const wasIndexed = await this.indexFile(filePath);
316
+ if (wasIndexed) {
317
+ indexed++;
318
+ if (onProgress) onProgress(indexed, files.length, relPath);
319
+ } else {
320
+ skipped++;
321
+ }
322
+ } catch (e) {
323
+ skipped++;
324
+ }
325
+ }
326
+
327
+ return { indexed, skipped, total: files.length };
328
+ }
329
+
249
330
  /**
250
331
  * Index a single file by path (convenience method)
251
332
  */