/ hooks / useSkillsChange.ts
useSkillsChange.ts
 1  import { useCallback, useEffect } from 'react'
 2  import type { Command } from '../commands.js'
 3  import {
 4    clearCommandMemoizationCaches,
 5    clearCommandsCache,
 6    getCommands,
 7  } from '../commands.js'
 8  import { onGrowthBookRefresh } from '../services/analytics/growthbook.js'
 9  import { logError } from '../utils/log.js'
10  import { skillChangeDetector } from '../utils/skills/skillChangeDetector.js'
11  
12  /**
13   * Keep the commands list fresh across two triggers:
14   *
15   * 1. Skill file changes (watcher) — full cache clear + disk re-scan, since
16   *    skill content changed on disk.
17   * 2. GrowthBook init/refresh — memo-only clear, since only `isEnabled()`
18   *    predicates may have changed. Handles commands like /btw whose gate
19   *    reads a flag that isn't in the disk cache yet on first session after
20   *    a flag rename: getCommands() runs before GB init (main.tsx:2855 vs
21   *    showSetupScreens at :3106), so the memoized list is baked with the
22   *    default. Once init populates remoteEvalFeatureValues, re-filter.
23   */
24  export function useSkillsChange(
25    cwd: string | undefined,
26    onCommandsChange: (commands: Command[]) => void,
27  ): void {
28    const handleChange = useCallback(async () => {
29      if (!cwd) return
30      try {
31        // Clear all command caches to ensure fresh load
32        clearCommandsCache()
33        const commands = await getCommands(cwd)
34        onCommandsChange(commands)
35      } catch (error) {
36        // Errors during reload are non-fatal - log and continue
37        if (error instanceof Error) {
38          logError(error)
39        }
40      }
41    }, [cwd, onCommandsChange])
42  
43    useEffect(() => skillChangeDetector.subscribe(handleChange), [handleChange])
44  
45    const handleGrowthBookRefresh = useCallback(async () => {
46      if (!cwd) return
47      try {
48        clearCommandMemoizationCaches()
49        const commands = await getCommands(cwd)
50        onCommandsChange(commands)
51      } catch (error) {
52        if (error instanceof Error) {
53          logError(error)
54        }
55      }
56    }, [cwd, onCommandsChange])
57  
58    useEffect(
59      () => onGrowthBookRefresh(handleGrowthBookRefresh),
60      [handleGrowthBookRefresh],
61    )
62  }