1package skills
2
3import "sync"
4
5// Tracker tracks which skills have been loaded (read) during a session.
6// It is safe for concurrent use.
7//
8// Note: Tracking is name-based and limited to active skills only. If a builtin
9// skill is overridden by a user skill, only the user skill (which is active)
10// can be marked as loaded. This prevents misattribution when reading builtin
11// files that have been overridden.
12type Tracker struct {
13 mu sync.RWMutex
14 loaded map[string]bool
15 activeNames map[string]bool // Set of active skill names (post-dedup, post-filter)
16}
17
18// NewTracker creates a new skill tracker with the given active skill names.
19// Only skills in activeSkills can be marked as loaded.
20func NewTracker(activeSkills []*Skill) *Tracker {
21 activeNames := make(map[string]bool, len(activeSkills))
22 for _, s := range activeSkills {
23 activeNames[s.Name] = true
24 }
25 return &Tracker{
26 loaded: make(map[string]bool),
27 activeNames: activeNames,
28 }
29}
30
31// MarkLoaded marks a skill as having been loaded.
32// Only marks as loaded if the skill is in the active set (not overridden/disabled).
33func (t *Tracker) MarkLoaded(name string) {
34 if t == nil {
35 return
36 }
37 t.mu.Lock()
38 defer t.mu.Unlock()
39 // Only track if this skill is actually active (not overridden by user skill).
40 if t.activeNames[name] {
41 t.loaded[name] = true
42 }
43}
44
45// IsLoaded returns true if the skill has been loaded.
46func (t *Tracker) IsLoaded(name string) bool {
47 if t == nil {
48 return false
49 }
50 t.mu.RLock()
51 defer t.mu.RUnlock()
52 return t.loaded[name]
53}