@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 CHANGED
@@ -438,13 +438,14 @@ program
438
438
  });
439
439
  }
440
440
 
441
- // Preserve vectorizer and vectors by moving them temporarily
442
- const tempVectorizer = path.join(process.cwd(), '.vectorizer-temp');
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(vectorizerDir, tempVectorizer, { overwrite: true });
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
- // Restore vectorizer and vectors
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(tempVectorizer, vectorizerDir, { overwrite: true });
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, vectorsDir, { overwrite: true });
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 was preserved.'));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.7.1",
3
+ "version": "4.9.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:28:33.562Z",
3
+ "buildDate": "2026-01-24T09:33:58.989Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -2,7 +2,7 @@
2
2
  * Code Index Status & Management Tool
3
3
  *
4
4
  * Check indexing status and trigger re-indexing.
5
- * Supports multiple indexes: code, docs, config.
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
- // Index presets for documentation
20
- const INDEX_PRESETS: Record<string, { pattern: string; description: string }> = {
21
- code: { pattern: '**/*.{js,ts,go,py,...}', description: 'Source code files' },
22
- docs: { pattern: '**/*.{md,txt,...}', description: 'Documentation files' },
23
- config: { pattern: '**/*.{yaml,json,...}', description: 'Configuration files' },
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 (specify index or see all)
68
+ - "status" → Show index statistics
31
69
  - "list" → List all available indexes with stats
32
- - "reindex" → Re-index files (specify which index)
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: Initial indexing takes ~30s to load the embedding model.`,
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 will download the embedding model (~100MB) and set up the vector database.`
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 result = execSync("node .opencode/vectorizer/list-indexes.js 2>/dev/null || echo '{}'", {
73
- cwd: projectRoot,
74
- encoding: "utf8",
75
- timeout: 30000
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
- output += `### Usage\n`
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 += `codesearch({ query: "your query", index: "code" })\n`
116
- output += `codesearch({ query: "how to deploy", index: "docs" })\n`
117
- output += `\`\`\``
118
-
119
- return output
120
-
121
- } catch (error: any) {
122
- return `❌ Error listing indexes: ${error.message}`
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 = INDEX_PRESETS[indexName]?.description || "Custom index"
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
- \`\`\`bash
156
- npx @comfanion/workflow index --index ${indexName}
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
- \`\`\`bash
167
- npx @comfanion/workflow index --index ${indexName}
168
- # Or with specific directory:
169
- npx @comfanion/workflow index --index ${indexName} --dir src/
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 CLI
205
+ // REINDEX: Re-index using LOCAL vectorizer (no npm!)
175
206
  if (args.action === "reindex") {
176
207
  try {
177
- execSync(`npx @comfanion/workflow index --index ${indexName}`, {
178
- cwd: projectRoot,
179
- encoding: "utf8",
180
- timeout: 300000, // 5 min
181
- stdio: "pipe"
182
- })
183
-
184
- // Get stats after indexing
185
- const hashesFile = path.join(vectorsDir, indexName, "hashes.json")
186
- const hashes = JSON.parse(await fs.readFile(hashesFile, "utf8"))
187
- const fileCount = Object.keys(hashes).length
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
- **Files indexed:** ${fileCount}
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
- Try manually:
255
+ Make sure vectorizer is installed:
203
256
  \`\`\`bash
204
- npx @comfanion/workflow index --index ${indexName} --force
257
+ npx @comfanion/workflow vectorizer install
205
258
  \`\`\``
206
259
  }
207
260
  }