@comfanion/workflow 4.10.0 → 4.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.10.0",
3
+ "version": "4.12.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:51:33.276Z",
3
+ "buildDate": "2026-01-24T10:10:09.577Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -43,17 +43,25 @@ permission:
43
43
 
44
44
  <activation critical="MANDATORY">
45
45
  <step n="1">Receive exploration request from parent agent or user</step>
46
- <step n="2">Scan codebase structure using glob/grep/bash</step>
47
- <step n="3">Read relevant files to understand patterns</step>
48
- <step n="4">Return structured findings with file:line references</step>
46
+ <step n="2">codeindex({ action: "list" }) → Check if semantic indexes exist</step>
47
+ <step n="3">IF indexes exist codesearch({ query: "concept" }) → Get 5-10 relevant files</step>
48
+ <step n="4">Read top results from codesearch</step>
49
+ <step n="5">ONLY if needed: use grep for exact string matches</step>
50
+ <step n="6">Return structured findings with file:line references</step>
49
51
 
50
52
  <rules>
53
+ <r>CODESEARCH FIRST - Use semantic search before grep/glob!</r>
51
54
  <r>READ-ONLY - Cannot write or edit files</r>
52
55
  <r>No external calls - No network, no APIs</r>
53
56
  <r>Fast response - Return findings quickly, don't over-analyze</r>
54
57
  <r>Always cite file:line for findings</r>
55
58
  <r>Return structured output format</r>
56
59
  </rules>
60
+
61
+ <anti-pattern>
62
+ ❌ WRONG: codeindex list → grep → glob → read 20 files
63
+ ✅ RIGHT: codeindex list → codesearch → read 3-5 files
64
+ </anti-pattern>
57
65
  </activation>
58
66
 
59
67
  <persona>
@@ -119,14 +127,24 @@ permission:
119
127
  </prefer-lsp-when>
120
128
  </lsp-exploration>
121
129
 
122
- <codesearch-exploration hint="ALWAYS TRY SEMANTIC SEARCH FIRST">
123
- <critical>
124
- BEFORE using grep/glob, ALWAYS check: codeindex({ action: "list" })
125
- If indexes exist → USE codesearch instead of grep!
126
- codesearch returns 5-10 relevant files vs 100+ grep matches
130
+ <codesearch-exploration hint="MANDATORY - USE SEMANTIC SEARCH FIRST">
131
+ <critical priority="HIGHEST">
132
+ ⚠️ DO NOT USE grep/glob UNTIL you've tried codesearch!
133
+
134
+ WRONG: codeindex({ action: "list" }) see indexes → grep anyway
135
+ RIGHT: codeindex({ action: "list" }) → see indexes → codesearch({ query: "..." })
136
+
137
+ codesearch returns 5-10 RELEVANT files
138
+ grep returns 100+ UNFILTERED matches - SLOW!
127
139
  </critical>
128
140
 
129
- <first-step>codeindex({ action: "list" }) → Check if indexes exist</first-step>
141
+ <mandatory-workflow>
142
+ STEP 1: codeindex({ action: "list" }) → Check indexes
143
+ STEP 2: IF indexes exist → codesearch({ query: "your concept" }) → READ results
144
+ STEP 3: ONLY if codesearch fails → fall back to grep
145
+
146
+ NEVER skip step 2!
147
+ </mandatory-workflow>
130
148
 
131
149
  <indexes hint="Different indexes for different content types">
132
150
  <index name="code">Source code (*.go, *.ts, *.py) - functions, classes, logic</index>
@@ -181,16 +199,27 @@ permission:
181
199
  - Regex pattern matching needed
182
200
  </use-grep-when>
183
201
 
184
- <exploration-strategy priority="MANDATORY">
185
- 1. FIRST: codeindex({ action: "list" }) → Check what indexes exist
186
- 2. IF indexes exist:
187
- - codesearch({ query: "concept", index: "code" }) → 5-10 relevant files (NOT 100+ grep matches!)
188
- - Read top 3-5 results
189
- - Done! Much faster than grep
190
- 3. IF no indexes:
191
- - Suggest: "Index not found. Create with: codeindex({ action: 'reindex', index: 'code' })"
192
- - Fall back to grep/glob
193
- 4. Use grep ONLY for exact string matches (function names, imports)
202
+ <exploration-strategy priority="MANDATORY - FOLLOW THIS ORDER">
203
+ 1. codeindex({ action: "list" }) → See what indexes exist
204
+
205
+ 2. IMMEDIATELY after seeing indexes, USE THEM:
206
+ codesearch({ query: "category mapping logic", index: "code" })
207
+ → Returns 5-10 relevant files with code snippets!
208
+
209
+ 3. Read the codesearch results (top 3-5 files)
210
+ You now have the answer. Done!
211
+
212
+ 4. ONLY use grep/glob for:
213
+ - Exact function name: grep "func CreateUser"
214
+ - Counting occurrences: grep -c "pattern"
215
+ - TODO/FIXME search
216
+
217
+ 5. IF no indexes exist:
218
+ - Suggest: codeindex({ action: "reindex", index: "code" })
219
+ - Fall back to grep as last resort
220
+
221
+ ⚠️ ANTI-PATTERN: codeindex list → grep → glob → read 20 files = WRONG!
222
+ ✅ CORRECT: codeindex list → codesearch → read 5 files = FAST!
194
223
  </exploration-strategy>
195
224
 
196
225
  <efficiency-comparison>
@@ -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
  */