@comfanion/workflow 4.34.0 → 4.36.1

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.34.0",
3
+ "version": "4.36.1",
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-24T12:20:16.212Z",
3
+ "buildDate": "2026-01-24T14:49:34.378Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -16,12 +16,12 @@ tools:
16
16
  glob: true
17
17
  grep: true
18
18
  list: true
19
- skill: false # No skill loading - just execute
19
+ skill: true # No skill loading - just execute
20
20
  question: false # No questions - execute or fail
21
21
  bash: true # Full bash for tests, builds
22
22
  webfetch: false # No web access
23
- todowrite: false # Subagent - no todo
24
- todoread: false
23
+ todowrite: true # Not available for subagents (Claude internal tools)
24
+ todoread: true # Not available for subagents
25
25
  lsp: true # Code intelligence
26
26
 
27
27
  # Permissions - fast execution, no prompts
@@ -48,14 +48,57 @@ permission:
48
48
  <r>Find and use `**/project-context.md` and `CLAUDE.md` as source of truth</r>
49
49
  </rules>
50
50
 
51
- <dev-story-workflow hint="When executing dev-story skill">
51
+ <dev-story-workflow hint="When executing /dev-story command" critical="FOLLOW THIS EXACTLY">
52
+ <!-- PHASE 1: SETUP -->
52
53
  <step n="1">READ the entire story file BEFORE any implementation</step>
53
54
  <step n="2">Load project-context.md and CLAUDE.md if available</step>
54
- <step n="3">Execute tasks/subtasks IN ORDER as written in story file</step>
55
- <step n="4">For each task: follow red-green-refactor cycle</step>
56
- <step n="5">Mark task [x] ONLY when implementation AND tests are complete</step>
57
- <step n="6">Run full test suite after each task - NEVER proceed with failing tests</step>
55
+ <step n="3">CREATE TODO LIST from story tasks using todowrite:
56
+ - Each task becomes a TODO item
57
+ - Set priority based on task order (first = high)
58
+ - All tasks start as "pending"
59
+ </step>
60
+ <step n="4">Mark story status as "in-progress"</step>
61
+
62
+ <!-- PHASE 2: IMPLEMENTATION LOOP -->
63
+ <step n="5">FOR EACH TASK in order:
64
+ a) Update TODO: mark current task as "in_progress"
65
+ b) Call @coder with specific task instructions:
66
+ - Include task requirements
67
+ - Include acceptance criteria
68
+ - Include relevant file paths
69
+ - Request: test first, then implement
70
+ c) VERIFY @coder result:
71
+ - Check tests exist and pass
72
+ - Check implementation matches AC
73
+ - If failed: retry or HALT
74
+ d) Update TODO: mark task as "completed"
75
+ e) Update story file: mark task [x]
76
+ f) Run test suite - HALT if failures
77
+ </step>
78
+
79
+ <!-- PHASE 3: FINALIZATION -->
80
+ <step n="6">Run FULL test suite - all tests must pass</step>
81
+ <step n="7">Update story file: File List, Change Log, Dev Agent Record</step>
82
+ <step n="8">Clear TODO list (all done)</step>
83
+ <step n="9">Mark story status as "review"</step>
58
84
  </dev-story-workflow>
85
+
86
+ <todo-usage hint="How to use TODO for tracking">
87
+ <create>
88
+ todowrite([
89
+ { id: "story-task-1", content: "Task 1: Create entity", status: "pending", priority: "high" },
90
+ { id: "story-task-2", content: "Task 2: Add repository", status: "pending", priority: "medium" },
91
+ ...
92
+ ])
93
+ </create>
94
+ <update-progress>
95
+ todoread() → get current list
96
+ todowrite([...list with task.status = "in_progress"])
97
+ </update-progress>
98
+ <mark-complete>
99
+ todowrite([...list with task.status = "completed"])
100
+ </mark-complete>
101
+ </todo-usage>
59
102
  </activation>
60
103
 
61
104
  <persona>
@@ -1,6 +1,6 @@
1
1
  # /dev-story Command
2
2
 
3
- Implement a story using red-green-refactor cycle.
3
+ Implement a story using red-green-refactor cycle with TODO tracking.
4
4
 
5
5
  ## Usage
6
6
 
@@ -18,16 +18,48 @@ This command invokes the **Dev** agent (Amelia).
18
18
 
19
19
  ## Process
20
20
 
21
+ ### Phase 1: Setup
21
22
  1. Find or load story file
22
23
  2. Load project context (CLAUDE.md, project-context.md)
23
- 3. Mark story as `in-progress`
24
- 4. For each task/subtask:
24
+ 3. **Create TODO list from story tasks** (for progress tracking)
25
+ 4. Mark story as `in-progress`
26
+
27
+ ### Phase 2: Implementation (for each task)
28
+ 5. **Mark task as `in_progress` in TODO**
29
+ 6. Delegate to @coder:
25
30
  - 🔴 RED: Write failing test
26
31
  - 🟢 GREEN: Implement minimal code to pass
27
32
  - 🔵 REFACTOR: Improve while keeping tests green
28
- 5. Mark task complete `[x]` only when tests pass
29
- 6. Update story file (File List, Change Log, Dev Agent Record)
30
- 7. Mark story as `review` when all tasks complete
33
+ 7. Verify @coder result (tests pass)
34
+ 8. **Mark task as `completed` in TODO**
35
+ 9. Mark task `[x]` in story file
36
+
37
+ ### Phase 3: Finalization
38
+ 10. Run full test suite
39
+ 11. Update story file (File List, Change Log, Dev Agent Record)
40
+ 12. **Clear TODO** (all tasks done)
41
+ 13. Mark story as `review`
42
+
43
+ ## TODO Workflow
44
+
45
+ ```
46
+ ┌─────────────────────────────────────────────────┐
47
+ │ @dev reads story → creates TODO: │
48
+ │ ┌─────────────────────────────────────────┐ │
49
+ │ │ [ ] Task 1: Create User entity │ │
50
+ │ │ [ ] Task 2: Add repository │ │
51
+ │ │ [ ] Task 3: Write integration tests │ │
52
+ │ └─────────────────────────────────────────┘ │
53
+ │ │
54
+ │ For each task: │
55
+ │ 1. @dev marks [→] in_progress in TODO │
56
+ │ 2. @dev calls @coder with task details │
57
+ │ 3. @coder implements (no TODO access) │
58
+ │ 4. @dev verifies result │
59
+ │ 5. @dev marks [✓] completed in TODO │
60
+ │ 6. @dev marks [x] in story file │
61
+ └─────────────────────────────────────────────────┘
62
+ ```
31
63
 
32
64
  ## Skills Loaded
33
65
 
@@ -233,7 +233,7 @@ vectorizer:
233
233
  auto_index: true
234
234
 
235
235
  # Debounce time in ms (wait before indexing after file change)
236
- debounce_ms: 2000
236
+ debounce_ms: 1000
237
237
 
238
238
  # Indexes to maintain
239
239
  indexes:
@@ -1,30 +1,51 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin"
2
2
  import path from "path"
3
3
  import fs from "fs/promises"
4
+ import fsSync from "fs"
4
5
 
5
6
  /**
6
7
  * File Indexer Plugin
7
8
  *
8
9
  * Automatically manages semantic search indexes:
9
- * - On session start/resume: freshen existing indexes (update stale files)
10
+ * - On plugin load (opencode startup): freshen existing indexes
10
11
  * - On file edit: queue file for reindexing (debounced)
11
12
  *
12
13
  * Configuration in .opencode/config.yaml:
13
14
  * vectorizer:
14
15
  * enabled: true # Master switch
15
16
  * auto_index: true # Enable this plugin
16
- * debounce_ms: 2000 # Wait time before indexing
17
+ * debounce_ms: 1000 # Wait time before indexing
17
18
  *
18
19
  * Debug mode: set DEBUG=file-indexer or DEBUG=* to see logs
19
20
  */
20
21
 
21
22
  const DEBUG = process.env.DEBUG?.includes('file-indexer') || process.env.DEBUG === '*'
22
23
 
24
+ let logFilePath: string | null = null
25
+
26
+ // Log to file only
27
+ function logFile(msg: string): void {
28
+ if (logFilePath) {
29
+ const timestamp = new Date().toISOString().slice(11, 19)
30
+ fsSync.appendFileSync(logFilePath, `${timestamp} ${msg}\n`)
31
+ }
32
+ }
33
+
34
+ // Log to console AND file
35
+ function log(msg: string): void {
36
+ console.log(`[file-indexer] ${msg}`)
37
+ logFile(msg)
38
+ }
39
+
40
+ function debug(msg: string): void {
41
+ if (DEBUG) log(msg)
42
+ }
43
+
23
44
  // Default config (used if config.yaml is missing or invalid)
24
45
  const DEFAULT_CONFIG = {
25
46
  enabled: true,
26
47
  auto_index: true,
27
- debounce_ms: 2000,
48
+ debounce_ms: 1000,
28
49
  indexes: {
29
50
  code: { enabled: true, extensions: ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.hpp', '.cs', '.rb', '.php', '.scala', '.clj'] },
30
51
  docs: { enabled: true, extensions: ['.md', '.mdx', '.txt', '.rst', '.adoc'] },
@@ -41,12 +62,91 @@ interface VectorizerConfig {
41
62
  exclude: string[]
42
63
  }
43
64
 
44
- const pendingFiles: Map<string, { indexName: string; timestamp: number }> = new Map()
65
+ // Fun messages based on file count and language
66
+ const FUN_MESSAGES = {
67
+ en: {
68
+ indexing: (files: number) => `Indexing ${files} files...`,
69
+ fun: (files: number, mins: number) => {
70
+ if (files < 20) return `Quick coffee? ☕`
71
+ if (files < 100) return `~${mins}min. Stretch break? 🧘`
72
+ if (files < 500) return `~${mins}min. Make coffee ☕ and relax 🛋️`
73
+ return `~${mins}min. Go touch grass 🌿 or take a nap 😴`
74
+ },
75
+ done: (files: number, duration: string) => {
76
+ if (files < 20) return `Done! ${files} files in ${duration}. Fast! 🚀`
77
+ if (files < 100) return `Indexed ${files} files in ${duration}. Let's go! 🎸`
78
+ return `${files} files in ${duration}. Worth the wait! 🎉`
79
+ },
80
+ fresh: () => `Everything's fresh! Nothing to do 😎`,
81
+ error: (msg: string) => `Oops! ${msg} 😬`
82
+ },
83
+ uk: {
84
+ indexing: (files: number) => `Індексую ${files} файлів...`,
85
+ fun: (files: number, mins: number) => {
86
+ if (files < 20) return `Швидка кава? ☕`
87
+ if (files < 100) return `~${mins}хв. Розімнись! 🧘`
88
+ if (files < 500) return `~${mins}хв. Зроби каву ☕ і відпочинь 🛋️`
89
+ return `~${mins}хв. Йди погуляй 🌿 або поспи 😴`
90
+ },
91
+ done: (files: number, duration: string) => {
92
+ if (files < 20) return `Готово! ${files} файлів за ${duration}. Швидко! 🚀`
93
+ if (files < 100) return `${files} файлів за ${duration}. Поїхали! 🎸`
94
+ return `${files} файлів за ${duration}. Варто було чекати! 🎉`
95
+ },
96
+ fresh: () => `Все свіже! Нічого робити 😎`,
97
+ error: (msg: string) => `Ой! ${msg} 😬`
98
+ },
99
+ ru: {
100
+ indexing: (files: number) => `Индексирую ${files} файлов...`,
101
+ fun: (files: number, mins: number) => {
102
+ if (files < 20) return `Кофе? ☕`
103
+ if (files < 100) return `~${mins}мин. Разомнись! 🧘`
104
+ if (files < 500) return `~${mins}мин. Сделай кофе ☕ и отдохни 🛋️`
105
+ return `~${mins}мин. Иди погуляй 🌿 или поспи 😴`
106
+ },
107
+ done: (files: number, duration: string) => {
108
+ if (files < 20) return `Готово! ${files} файлов за ${duration}. Быстро! 🚀`
109
+ if (files < 100) return `${files} файлов за ${duration}. Поехали! 🎸`
110
+ return `${files} файлов за ${duration}. Стоило подождать! 🎉`
111
+ },
112
+ fresh: () => `Всё свежее! Делать нечего 😎`,
113
+ error: (msg: string) => `Ой! ${msg} 😬`
114
+ }
115
+ }
45
116
 
46
- function debug(msg: string): void {
47
- if (DEBUG) console.log(`[file-indexer] ${msg}`)
117
+ type Lang = keyof typeof FUN_MESSAGES
118
+
119
+ async function getLanguage(projectRoot: string): Promise<Lang> {
120
+ try {
121
+ const configPath = path.join(projectRoot, ".opencode", "config.yaml")
122
+ const content = await fs.readFile(configPath, 'utf8')
123
+ const match = content.match(/communication_language:\s*["']?(\w+)["']?/i)
124
+ const lang = match?.[1]?.toLowerCase()
125
+ if (lang === 'ukrainian' || lang === 'uk') return 'uk'
126
+ if (lang === 'russian' || lang === 'ru') return 'ru'
127
+ return 'en'
128
+ } catch {
129
+ return 'en'
130
+ }
48
131
  }
49
132
 
133
+ function estimateTime(fileCount: number): number {
134
+ // Model loading: ~30 sec, then ~0.5 sec per file
135
+ const modelLoadTime = 30 // seconds
136
+ const perFileTime = 0.5 // seconds
137
+ const totalSeconds = modelLoadTime + (fileCount * perFileTime)
138
+ return Math.ceil(totalSeconds / 60) // minutes
139
+ }
140
+
141
+ function formatDuration(seconds: number): string {
142
+ if (seconds < 60) return `${Math.round(seconds)}s`
143
+ const mins = Math.floor(seconds / 60)
144
+ const secs = Math.round(seconds % 60)
145
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`
146
+ }
147
+
148
+ const pendingFiles: Map<string, { indexName: string; timestamp: number }> = new Map()
149
+
50
150
  async function loadConfig(projectRoot: string): Promise<VectorizerConfig> {
51
151
  try {
52
152
  const configPath = path.join(projectRoot, ".opencode", "config.yaml")
@@ -119,46 +219,120 @@ async function hasIndex(projectRoot: string, indexName: string): Promise<boolean
119
219
  }
120
220
  }
121
221
 
222
+ interface IndexResult {
223
+ totalFiles: number
224
+ elapsedSeconds: number
225
+ action: 'created' | 'rebuilt' | 'freshened' | 'skipped'
226
+ }
227
+
122
228
  /**
123
229
  * Ensure index exists and is fresh on session start
230
+ * Creates index if missing, freshens if exists
124
231
  */
125
- async function ensureIndexOnSessionStart(projectRoot: string, config: VectorizerConfig): Promise<void> {
232
+ async function ensureIndexOnSessionStart(
233
+ projectRoot: string,
234
+ config: VectorizerConfig,
235
+ onStart?: (totalFiles: number, estimatedMins: number) => void
236
+ ): Promise<IndexResult> {
237
+ let totalFiles = 0
238
+ let elapsedSeconds = 0
239
+ let action: IndexResult['action'] = 'skipped'
240
+
126
241
  if (!await isVectorizerInstalled(projectRoot)) {
127
- debug('Session start: vectorizer not installed, skipping')
128
- return
242
+ log(`Vectorizer not installed - run: npx @comfanion/workflow vectorizer install`)
243
+ return { totalFiles: 0, elapsedSeconds: 0, action: 'skipped' }
129
244
  }
130
245
 
131
246
  try {
132
247
  const vectorizerModule = path.join(projectRoot, ".opencode", "vectorizer", "index.js")
133
248
  const { CodebaseIndexer } = await import(`file://${vectorizerModule}`)
249
+ const overallStart = Date.now()
250
+
251
+ // First pass - count files and check health
252
+ let needsWork = false
253
+ let totalExpectedFiles = 0
134
254
 
135
- // Check each enabled index
136
255
  for (const [indexName, indexConfig] of Object.entries(config.indexes)) {
137
256
  if (!indexConfig.enabled) continue
138
-
257
+ const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
139
258
  const indexExists = await hasIndex(projectRoot, indexName)
140
259
 
141
260
  if (!indexExists) {
142
- // No index - need full indexing (but don't block session, just log)
143
- debug(`Session start: index "${indexName}" not found, run: npx @comfanion/workflow index --index ${indexName}`)
144
- continue
261
+ const health = await indexer.checkHealth(config.exclude)
262
+ totalExpectedFiles += health.expectedCount
263
+ needsWork = true
264
+ } else {
265
+ const health = await indexer.checkHealth(config.exclude)
266
+ if (health.needsReindex) {
267
+ totalExpectedFiles += health.expectedCount
268
+ needsWork = true
269
+ }
145
270
  }
271
+ await indexer.unloadModel()
272
+ }
273
+
274
+ // Notify about work to do
275
+ if (needsWork && onStart) {
276
+ onStart(totalExpectedFiles, estimateTime(totalExpectedFiles))
277
+ }
278
+
279
+ // Second pass - do the actual work
280
+ for (const [indexName, indexConfig] of Object.entries(config.indexes)) {
281
+ if (!indexConfig.enabled) continue
282
+
283
+ const indexExists = await hasIndex(projectRoot, indexName)
284
+ const startTime = Date.now()
146
285
 
147
- // Index exists - freshen it (update stale files)
148
- debug(`Session start: freshening index "${indexName}"...`)
149
286
  const indexer = await new CodebaseIndexer(projectRoot, indexName).init()
150
- const stats = await indexer.freshen()
151
287
 
152
- if (stats.updated > 0 || stats.deleted > 0) {
153
- debug(`Session start: ${indexName} - updated ${stats.updated}, deleted ${stats.deleted}`)
288
+ if (!indexExists) {
289
+ log(`Creating "${indexName}" index...`)
290
+ const stats = await indexer.indexAll((indexed: number, total: number, file: string) => {
291
+ if (indexed % 10 === 0 || indexed === total) {
292
+ logFile(`"${indexName}": ${indexed}/${total} - ${file}`)
293
+ }
294
+ }, config.exclude)
295
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1)
296
+ log(`"${indexName}": done ${stats.indexed} files (${elapsed}s)`)
297
+ totalFiles += stats.indexed
298
+ action = 'created'
154
299
  } else {
155
- debug(`Session start: ${indexName} - index is fresh`)
300
+ const health = await indexer.checkHealth(config.exclude)
301
+
302
+ if (health.needsReindex) {
303
+ log(`Rebuilding "${indexName}" (${health.reason}: ${health.currentCount} vs ${health.expectedCount} files)...`)
304
+ const stats = await indexer.indexAll((indexed: number, total: number, file: string) => {
305
+ if (indexed % 10 === 0 || indexed === total) {
306
+ logFile(`"${indexName}": ${indexed}/${total} - ${file}`)
307
+ }
308
+ }, config.exclude)
309
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1)
310
+ log(`"${indexName}": rebuilt ${stats.indexed} files (${elapsed}s)`)
311
+ totalFiles += stats.indexed
312
+ action = 'rebuilt'
313
+ } else {
314
+ log(`Freshening "${indexName}"...`)
315
+ const stats = await indexer.freshen()
316
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1)
317
+
318
+ if (stats.updated > 0 || stats.deleted > 0) {
319
+ log(`"${indexName}": +${stats.updated} -${stats.deleted} (${elapsed}s)`)
320
+ action = 'freshened'
321
+ } else {
322
+ log(`"${indexName}": fresh (${elapsed}s)`)
323
+ }
324
+ }
156
325
  }
157
326
 
158
327
  await indexer.unloadModel()
159
328
  }
329
+
330
+ elapsedSeconds = (Date.now() - overallStart) / 1000
331
+ log(`Indexes ready!`)
332
+ return { totalFiles, elapsedSeconds, action }
160
333
  } catch (e) {
161
- debug(`Session start error: ${(e as Error).message}`)
334
+ log(`Index error: ${(e as Error).message}`)
335
+ throw e
162
336
  }
163
337
  }
164
338
 
@@ -209,20 +383,63 @@ async function processPendingFiles(projectRoot: string, config: VectorizerConfig
209
383
  }
210
384
  }
211
385
 
212
- export const FileIndexerPlugin: Plugin = async ({ directory }) => {
386
+ export const FileIndexerPlugin: Plugin = async ({ directory, client }) => {
213
387
  let processingTimeout: NodeJS.Timeout | null = null
214
388
  let config = await loadConfig(directory)
215
389
 
390
+ // Toast helper
391
+ const toast = async (message: string, variant: 'info' | 'success' | 'error' = 'info') => {
392
+ try {
393
+ await client?.tui?.showToast?.({ body: { message, variant } })
394
+ } catch {}
395
+ }
396
+
397
+ // Always log plugin load
398
+ log(`Plugin loaded for: ${path.basename(directory)}`)
399
+
216
400
  // Check if plugin should be active
217
401
  if (!config.enabled || !config.auto_index) {
218
- debug(`Plugin disabled (enabled: ${config.enabled}, auto_index: ${config.auto_index})`)
402
+ log(`Plugin DISABLED (enabled: ${config.enabled}, auto_index: ${config.auto_index})`)
219
403
  return {
220
404
  event: async () => {}, // No-op
221
405
  }
222
406
  }
223
407
 
224
- debug(`Plugin loaded for: ${directory}`)
225
- debug(`Config: debounce=${config.debounce_ms}ms, exclude=${config.exclude.length} patterns`)
408
+ // Setup log file
409
+ logFilePath = path.join(directory, '.opencode', 'indexer.log')
410
+ fsSync.writeFileSync(logFilePath, '') // Clear old log
411
+
412
+ log(`Plugin ACTIVE`)
413
+
414
+ // Get language for fun messages
415
+ const lang = await getLanguage(directory)
416
+ const messages = FUN_MESSAGES[lang]
417
+
418
+ // Run indexing async (non-blocking) with toast notifications
419
+ // Small delay to let TUI initialize
420
+ setTimeout(async () => {
421
+ try {
422
+ const result = await ensureIndexOnSessionStart(
423
+ directory,
424
+ config,
425
+ // onStart callback - show 2 toasts
426
+ async (totalFiles, estimatedMins) => {
427
+ await toast(messages.indexing(totalFiles), 'info')
428
+ setTimeout(() => toast(messages.fun(totalFiles, estimatedMins), 'info'), 1500)
429
+ }
430
+ )
431
+
432
+ // Show result
433
+ if (result.action === 'skipped') {
434
+ toast(messages.fresh(), 'success')
435
+ } else {
436
+ const duration = formatDuration(result.elapsedSeconds)
437
+ toast(messages.done(result.totalFiles, duration), 'success')
438
+ }
439
+ } catch (e: any) {
440
+ toast(messages.error(e.message), 'error')
441
+ }
442
+ }, 1000)
226
443
 
227
444
  function queueFileForIndexing(filePath: string): void {
228
445
  const relativePath = path.relative(directory, filePath)
@@ -250,35 +467,15 @@ export const FileIndexerPlugin: Plugin = async ({ directory }) => {
250
467
  }, config.debounce_ms + 100)
251
468
  }
252
469
 
253
- // Track if we've already freshened this session
254
- let sessionFreshened = false
255
-
470
+ // Event handler for file changes (if events start working in future)
256
471
  return {
257
- event: async (ctx) => {
258
- const event = ctx.event
259
-
260
- // Freshen index on session start/resume
261
- if ((event.type === "session.started" || event.type === "session.resumed") && !sessionFreshened) {
262
- sessionFreshened = true
263
- debug(`${event.type}: checking indexes...`)
264
- // Run async, don't block
265
- ensureIndexOnSessionStart(directory, config).catch(e => debug(`Error: ${e.message}`))
266
- }
267
-
268
- if (event.type === "file.edited") {
269
- const props = (event as any).properties || {}
270
- const filePath = props.file || props.path || props.filePath
271
- if (filePath) {
272
- debug(`file.edited: ${filePath}`)
273
- queueFileForIndexing(filePath)
274
- }
275
- }
276
-
277
- if (event.type === "file.watcher.updated") {
472
+ event: async ({ event }) => {
473
+ // File edit events - queue for reindexing
474
+ if (event.type === "file.edited" || event.type === "file.watcher.updated") {
278
475
  const props = (event as any).properties || {}
279
476
  const filePath = props.file || props.path || props.filePath
280
477
  if (filePath) {
281
- debug(`file.watcher.updated: ${filePath}`)
478
+ debug(`${event.type}: ${filePath}`)
282
479
  queueFileForIndexing(filePath)
283
480
  }
284
481
  }