@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.
package/bin/cli.js CHANGED
@@ -7,11 +7,63 @@ import ora from 'ora';
7
7
  import fs from 'fs-extra';
8
8
  import path from 'path';
9
9
  import { fileURLToPath } from 'url';
10
+ import { execSync } from 'child_process';
10
11
 
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  const PACKAGE_DIR = path.join(__dirname, '..');
13
14
  const OPENCODE_SRC = path.join(PACKAGE_DIR, 'src', 'opencode');
14
15
  const REPO_TEMPLATES_SRC = path.join(PACKAGE_DIR, 'src', 'repo-structure');
16
+ const VECTORIZER_SRC = path.join(PACKAGE_DIR, 'src', 'vectorizer');
17
+
18
+ /**
19
+ * Install vectorizer module with dependencies
20
+ */
21
+ async function installVectorizer(opencodeDir) {
22
+ console.log('');
23
+ const spinner = ora('Installing vectorizer & caching...').start();
24
+
25
+ try {
26
+ const vectorizerDir = path.join(opencodeDir, 'vectorizer');
27
+
28
+ // Copy vectorizer source files
29
+ spinner.text = 'Copying vectorizer files...';
30
+ await fs.ensureDir(vectorizerDir);
31
+ await fs.copy(VECTORIZER_SRC, vectorizerDir);
32
+
33
+ // Run npm install
34
+ spinner.text = 'Installing dependencies (~100MB, may take a minute)...';
35
+ execSync('npm install --no-audit --no-fund', {
36
+ cwd: vectorizerDir,
37
+ stdio: 'pipe',
38
+ timeout: 300000 // 5 min timeout
39
+ });
40
+
41
+ // Add to .gitignore
42
+ const gitignorePath = path.join(opencodeDir, '.gitignore');
43
+ let gitignore = '';
44
+ try { gitignore = await fs.readFile(gitignorePath, 'utf8'); } catch {}
45
+ if (!gitignore.includes('vectors/')) {
46
+ gitignore += '\n# Vectorizer cache\nvectors/\nvectorizer/node_modules/\n';
47
+ await fs.writeFile(gitignorePath, gitignore);
48
+ }
49
+
50
+ spinner.succeed(chalk.green('Vectorizer installed!'));
51
+
52
+ console.log(chalk.cyan('\nšŸ” Vectorizer ready:'));
53
+ console.log(`
54
+ Index codebase: ${chalk.cyan('npx opencode-workflow index')}
55
+ Search code: ${chalk.cyan('npx opencode-workflow search "your query"')}
56
+ `);
57
+
58
+ return true;
59
+
60
+ } catch (error) {
61
+ spinner.fail(chalk.yellow('Vectorizer installation failed (optional)'));
62
+ console.log(chalk.gray(` Error: ${error.message}`));
63
+ console.log(chalk.gray(' You can install later with: npx opencode-workflow vectorizer install'));
64
+ return false;
65
+ }
66
+ }
15
67
 
16
68
  const program = new Command();
17
69
 
@@ -28,6 +80,7 @@ program
28
80
  .option('--tdd', 'Use TDD methodology')
29
81
  .option('--stub', 'Use STUB methodology')
30
82
  .option('--full', 'Create full repo structure')
83
+ .option('--vectorizer', 'Install vectorizer for semantic code search')
31
84
  .action(async (options) => {
32
85
  console.log(chalk.blue.bold('\nšŸš€ OpenCode Workflow v3.7\n'));
33
86
 
@@ -43,6 +96,7 @@ program
43
96
  jira_url: 'https://your-domain.atlassian.net',
44
97
  jira_project: 'PROJ',
45
98
  create_repo_structure: false,
99
+ install_vectorizer: false,
46
100
  project_name: path.basename(process.cwd())
47
101
  };
48
102
 
@@ -155,6 +209,12 @@ program
155
209
  name: 'create_repo_structure',
156
210
  message: 'Create full repository structure (README, CONTRIBUTING, .gitignore, docs/)?',
157
211
  default: options.full || false
212
+ },
213
+ {
214
+ type: 'confirm',
215
+ name: 'install_vectorizer',
216
+ message: 'Install vectorizer & caching? (semantic code search, ~100MB)',
217
+ default: false
158
218
  }
159
219
  ]);
160
220
 
@@ -165,6 +225,7 @@ program
165
225
  if (options.stub) config.methodology = 'stub';
166
226
  if (options.jira) config.jira_enabled = true;
167
227
  if (options.full) config.create_repo_structure = true;
228
+ if (options.vectorizer) config.install_vectorizer = true;
168
229
  }
169
230
 
170
231
  const spinner = ora('Initializing OpenCode Workflow...').start();
@@ -267,6 +328,11 @@ program
267
328
 
268
329
  spinner.succeed(chalk.green('OpenCode Workflow initialized!'));
269
330
 
331
+ // Install vectorizer if requested
332
+ if (config.install_vectorizer) {
333
+ await installVectorizer(targetDir);
334
+ }
335
+
270
336
  // Show summary
271
337
  console.log(chalk.yellow('\nšŸ“ Created structure:'));
272
338
  console.log(`
@@ -461,10 +527,38 @@ program
461
527
  console.log(chalk.gray(' ā—‹ Jira credentials not set'));
462
528
  }
463
529
 
530
+ // Check Vectorizer
531
+ console.log(chalk.cyan('\nVectorizer (semantic search):'));
532
+ const vectorizerInstalled = await fs.pathExists(path.join(process.cwd(), '.opencode', 'vectorizer', 'node_modules'));
533
+ const vectorsExist = await fs.pathExists(path.join(process.cwd(), '.opencode', 'vectors', 'hashes.json'));
534
+
535
+ if (vectorizerInstalled) {
536
+ console.log(chalk.green(' āœ… Installed'));
537
+ if (vectorsExist) {
538
+ try {
539
+ const hashes = await fs.readJSON(path.join(process.cwd(), '.opencode', 'vectors', 'hashes.json'));
540
+ console.log(chalk.green(` āœ… Indexed (${Object.keys(hashes).length} files)`));
541
+ } catch {
542
+ console.log(chalk.gray(' ā—‹ Not indexed yet'));
543
+ }
544
+ } else {
545
+ console.log(chalk.gray(' ā—‹ Not indexed (run: npx opencode-workflow index)'));
546
+ }
547
+ } else {
548
+ console.log(chalk.gray(' ā—‹ Not installed (run: npx opencode-workflow vectorizer install)'));
549
+ }
550
+
551
+ // Check LSP env
552
+ if (process.env.OPENCODE_EXPERIMENTAL_LSP_TOOL === 'true' || process.env.OPENCODE_EXPERIMENTAL === 'true') {
553
+ console.log(chalk.green(' āœ… LSP tool enabled'));
554
+ } else {
555
+ console.log(chalk.gray(' ā—‹ LSP tool disabled (set OPENCODE_EXPERIMENTAL_LSP_TOOL=true)'));
556
+ }
557
+
464
558
  console.log('');
465
559
 
466
560
  if (hasErrors) {
467
- console.log(chalk.yellow('šŸ’” Run `npx create-opencode-workflow init` to fix missing files.\n'));
561
+ console.log(chalk.yellow('šŸ’” Run `npx opencode-workflow init` to fix missing files.\n'));
468
562
  } else {
469
563
  console.log(chalk.green.bold('āœ… All checks passed!\n'));
470
564
  }
@@ -484,4 +578,250 @@ program
484
578
  }
485
579
  });
486
580
 
581
+ // =============================================================================
582
+ // VECTORIZER COMMANDS
583
+ // =============================================================================
584
+
585
+ program
586
+ .command('vectorizer')
587
+ .description('Manage vectorizer for semantic code search')
588
+ .argument('<action>', 'install | status | uninstall')
589
+ .action(async (action) => {
590
+ const opencodeDir = path.join(process.cwd(), '.opencode');
591
+ const vectorizerDir = path.join(opencodeDir, 'vectorizer');
592
+
593
+ if (action === 'install') {
594
+ if (!await fs.pathExists(opencodeDir)) {
595
+ console.log(chalk.red('\nāŒ .opencode/ not found. Run `init` first.\n'));
596
+ process.exit(1);
597
+ }
598
+ await installVectorizer(opencodeDir);
599
+
600
+ } else if (action === 'status') {
601
+ const installed = await fs.pathExists(path.join(vectorizerDir, 'node_modules'));
602
+ const indexed = await fs.pathExists(path.join(process.cwd(), '.opencode', 'vectors', 'lancedb'));
603
+
604
+ console.log(chalk.blue.bold('\nšŸ” Vectorizer Status\n'));
605
+ console.log(installed
606
+ ? chalk.green(' āœ… Installed')
607
+ : chalk.gray(' ā—‹ Not installed (run: npx opencode-workflow vectorizer install)'));
608
+ console.log(indexed
609
+ ? chalk.green(' āœ… Codebase indexed')
610
+ : chalk.gray(' ā—‹ Not indexed (run: npx opencode-workflow index)'));
611
+
612
+ if (indexed) {
613
+ try {
614
+ const hashes = await fs.readJSON(path.join(process.cwd(), '.opencode', 'vectors', 'hashes.json'));
615
+ console.log(chalk.cyan(` šŸ“ ${Object.keys(hashes).length} files indexed`));
616
+ } catch {}
617
+ }
618
+ console.log('');
619
+
620
+ } else if (action === 'uninstall') {
621
+ const spinner = ora('Removing vectorizer...').start();
622
+ await fs.remove(vectorizerDir);
623
+ await fs.remove(path.join(process.cwd(), '.opencode', 'vectors'));
624
+ spinner.succeed(chalk.green('Vectorizer removed'));
625
+
626
+ } else {
627
+ console.log(chalk.red(`Unknown action: ${action}`));
628
+ console.log('Available: install, status, uninstall');
629
+ }
630
+ });
631
+
632
+ program
633
+ .command('index')
634
+ .description('Index codebase for semantic search')
635
+ .option('-i, --index <name>', 'Index name: code, docs, config, all, or custom', 'code')
636
+ .option('-p, --pattern <glob>', 'File pattern (overrides preset)')
637
+ .option('--force', 'Re-index all files (ignore cache)')
638
+ .option('--list', 'List all indexes and their stats')
639
+ .action(async (options) => {
640
+ const vectorizerDir = path.join(process.cwd(), '.opencode', 'vectorizer');
641
+
642
+ if (!await fs.pathExists(path.join(vectorizerDir, 'node_modules'))) {
643
+ console.log(chalk.red('\nāŒ Vectorizer not installed.'));
644
+ console.log(chalk.yellow('Run: npx opencode-workflow vectorizer install\n'));
645
+ process.exit(1);
646
+ }
647
+
648
+ const spinner = ora('Initializing indexer...').start();
649
+
650
+ try {
651
+ // Dynamic import of the vectorizer (need file:// URL for ESM)
652
+ const vectorizerPath = path.join(vectorizerDir, 'index.js');
653
+ const { CodebaseIndexer, INDEX_PRESETS } = await import(`file://${vectorizerPath}`);
654
+
655
+ // List all indexes
656
+ if (options.list) {
657
+ spinner.stop();
658
+ const indexer = await new CodebaseIndexer(process.cwd(), 'code').init();
659
+ const allStats = await indexer.getAllStats();
660
+
661
+ console.log(chalk.blue.bold('\nšŸ“Š Index Statistics\n'));
662
+
663
+ if (allStats.length === 0) {
664
+ console.log(chalk.gray(' No indexes found. Create one with:'));
665
+ console.log(chalk.cyan(' npx opencode-workflow index --index code'));
666
+ console.log(chalk.cyan(' npx opencode-workflow index --index docs\n'));
667
+ } else {
668
+ for (const stat of allStats) {
669
+ console.log(chalk.cyan(` šŸ“ ${stat.indexName}`));
670
+ console.log(chalk.gray(` ${stat.description}`));
671
+ console.log(` Files: ${stat.fileCount}, Chunks: ${stat.chunkCount}\n`);
672
+ }
673
+ }
674
+
675
+ console.log(chalk.yellow('Available presets:'));
676
+ for (const [name, preset] of Object.entries(INDEX_PRESETS)) {
677
+ console.log(` ${chalk.cyan(name)}: ${preset.description}`);
678
+ console.log(chalk.gray(` Pattern: ${preset.pattern}\n`));
679
+ }
680
+ return;
681
+ }
682
+
683
+ const indexName = options.index;
684
+ const indexer = await new CodebaseIndexer(process.cwd(), indexName).init();
685
+
686
+ // Get pattern from options or preset
687
+ const preset = INDEX_PRESETS[indexName];
688
+ const pattern = options.pattern || preset?.pattern || '**/*.{js,ts,jsx,tsx,py,go,rs,java,md,yaml,yml}';
689
+
690
+ spinner.text = `Initializing index: ${indexName}...`;
691
+
692
+ if (options.force) {
693
+ spinner.text = `Clearing index: ${indexName}...`;
694
+ await indexer.clear();
695
+ }
696
+
697
+ // Find files to index
698
+ spinner.text = 'Finding files...';
699
+ const { glob } = await import('glob');
700
+ const { default: ignore } = await import('ignore');
701
+
702
+ // Load .gitignore
703
+ let ig = ignore();
704
+ try {
705
+ const gitignore = await fs.readFile(path.join(process.cwd(), '.gitignore'), 'utf8');
706
+ ig = ig.add(gitignore);
707
+ } catch {}
708
+ ig.add(['node_modules', '.git', 'dist', 'build', '.opencode/vectors', '.opencode/vectorizer']);
709
+
710
+ const files = await glob(pattern, { cwd: process.cwd(), nodir: true });
711
+ const filtered = files.filter(f => !ig.ignores(f));
712
+
713
+ spinner.succeed(`Found ${filtered.length} files for index "${indexName}"`);
714
+
715
+ let indexed = 0;
716
+ let skipped = 0;
717
+
718
+ for (const file of filtered) {
719
+ const filePath = path.join(process.cwd(), file);
720
+ spinner.start(`[${indexName}] Indexing: ${file}`);
721
+
722
+ try {
723
+ const wasIndexed = await indexer.indexFile(filePath);
724
+ if (wasIndexed) {
725
+ indexed++;
726
+ } else {
727
+ skipped++;
728
+ }
729
+ } catch (e) {
730
+ spinner.warn(`Skipped ${file}: ${e.message}`);
731
+ }
732
+ }
733
+
734
+ spinner.succeed(chalk.green(`Index "${indexName}": ${indexed} indexed, ${skipped} unchanged`));
735
+
736
+ } catch (error) {
737
+ spinner.fail(chalk.red('Indexing failed'));
738
+ console.error(error);
739
+ }
740
+ });
741
+
742
+ program
743
+ .command('search')
744
+ .description('Semantic search in codebase')
745
+ .argument('<query>', 'Search query')
746
+ .option('-i, --index <name>', 'Index to search: code, docs, config, or custom', 'code')
747
+ .option('-n, --limit <number>', 'Number of results', '5')
748
+ .option('-a, --all', 'Search all indexes')
749
+ .action(async (query, options) => {
750
+ const vectorizerDir = path.join(process.cwd(), '.opencode', 'vectorizer');
751
+
752
+ if (!await fs.pathExists(path.join(vectorizerDir, 'node_modules'))) {
753
+ console.log(chalk.red('\nāŒ Vectorizer not installed.'));
754
+ console.log(chalk.yellow('Run: npx opencode-workflow vectorizer install\n'));
755
+ process.exit(1);
756
+ }
757
+
758
+ const spinner = ora('Searching...').start();
759
+
760
+ try {
761
+ // Dynamic import of the vectorizer (need file:// URL for ESM)
762
+ const vectorizerPath = path.join(vectorizerDir, 'index.js');
763
+ const { CodebaseIndexer } = await import(`file://${vectorizerPath}`);
764
+
765
+ let allResults = [];
766
+ const limit = parseInt(options.limit);
767
+
768
+ if (options.all) {
769
+ // Search all indexes
770
+ const tempIndexer = await new CodebaseIndexer(process.cwd(), 'code').init();
771
+ const indexes = await tempIndexer.listIndexes();
772
+
773
+ if (indexes.length === 0) {
774
+ spinner.stop();
775
+ console.log(chalk.yellow('\nNo indexes found. Run `npx opencode-workflow index` first.\n'));
776
+ return;
777
+ }
778
+
779
+ for (const indexName of indexes) {
780
+ spinner.text = `Searching index: ${indexName}...`;
781
+ const indexer = await new CodebaseIndexer(process.cwd(), indexName).init();
782
+ const results = await indexer.search(query, limit);
783
+ allResults.push(...results.map(r => ({ ...r, _index: indexName })));
784
+ }
785
+
786
+ // Sort by distance and take top N
787
+ allResults.sort((a, b) => (a._distance || 0) - (b._distance || 0));
788
+ allResults = allResults.slice(0, limit);
789
+
790
+ } else {
791
+ // Search specific index
792
+ const indexer = await new CodebaseIndexer(process.cwd(), options.index).init();
793
+ const results = await indexer.search(query, limit);
794
+ allResults = results.map(r => ({ ...r, _index: options.index }));
795
+ }
796
+
797
+ spinner.stop();
798
+
799
+ if (allResults.length === 0) {
800
+ const indexInfo = options.all ? 'any index' : `index "${options.index}"`;
801
+ console.log(chalk.yellow(`\nNo results found in ${indexInfo}.`));
802
+ console.log(chalk.gray('Try: npx opencode-workflow index --index code\n'));
803
+ return;
804
+ }
805
+
806
+ const searchScope = options.all ? 'all indexes' : `index "${options.index}"`;
807
+ console.log(chalk.blue.bold(`\nšŸ” Results for: "${query}" (${searchScope})\n`));
808
+
809
+ for (let i = 0; i < allResults.length; i++) {
810
+ const r = allResults[i];
811
+ const score = r._distance ? (1 - r._distance).toFixed(3) : 'N/A';
812
+ const indexLabel = options.all ? chalk.magenta(`[${r._index}] `) : '';
813
+ console.log(chalk.cyan(`${i + 1}. ${indexLabel}${r.file}`) + chalk.gray(` (score: ${score})`));
814
+ console.log(chalk.gray('─'.repeat(60)));
815
+ // Show first 5 lines of content
816
+ const preview = r.content.split('\n').slice(0, 5).join('\n');
817
+ console.log(preview);
818
+ console.log('');
819
+ }
820
+
821
+ } catch (error) {
822
+ spinner.fail(chalk.red('Search failed'));
823
+ console.error(error);
824
+ }
825
+ });
826
+
487
827
  program.parse();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.3.0",
4
- "description": "Initialize OpenCode Workflow system for AI-assisted development",
3
+ "version": "4.5.0",
4
+ "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "comfanion-workflow": "./bin/cli.js",
@@ -25,7 +25,10 @@
25
25
  "prd",
26
26
  "architecture",
27
27
  "tdd",
28
- "agile"
28
+ "agile",
29
+ "rag",
30
+ "vector-search",
31
+ "embeddings"
29
32
  ],
30
33
  "author": "OpenCode Team",
31
34
  "license": "MIT",
@@ -44,6 +47,8 @@
44
47
  "chalk": "^5.3.0",
45
48
  "commander": "^11.0.0",
46
49
  "fs-extra": "^11.1.0",
50
+ "glob": "^10.5.0",
51
+ "ignore": "^5.3.0",
47
52
  "inquirer": "^9.2.0",
48
53
  "ora": "^7.0.0"
49
54
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "3.0.0",
3
- "buildDate": "2026-01-23T20:59:12.140Z",
3
+ "buildDate": "2026-01-24T09:15:13.601Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -11,6 +11,7 @@
11
11
  "workflows",
12
12
  "checklists",
13
13
  "commands",
14
+ "tools",
14
15
  "opencode.json"
15
16
  ]
16
17
  }