@@ -27,6 +27,12 @@ type Manager struct {
27 activeSkills []*Skill
27 activeSkills []*Skill
28 states []*SkillState
28 states []*SkillState
30+ // resolvedPaths are the expanded SkillsPaths used during discovery.
31+ // Stored so Catalog/ReadContent can label skills without
33+ resolvedPaths []string
30 broker *pubsub.Broker[Event]
36 broker *pubsub.Broker[Event]
31 globalMirror bool
37 globalMirror bool
@@ -44,6 +50,23 @@ func WithGlobalMirror() ManagerOption {
53+// WithResolvedPaths stores the expanded skills directory paths that
54+// were used during discovery. Catalog and ReadContent use these for
56+func WithResolvedPaths(paths []string) ManagerOption {
57+ return func(m *Manager) {
58+ m.resolvedPaths = paths
62+// WithWorkingDir stores the workspace working directory. Catalog and
63+// ReadContent use it to distinguish project skills from user skills.
64+func WithWorkingDir(dir string) ManagerOption {
65+ return func(m *Manager) {
47// NewManager constructs a workspace-scoped Manager with the given
70// NewManager constructs a workspace-scoped Manager with the given
48// pre-computed discovery results. The slices are stored as-is; callers
71// pre-computed discovery results. The slices are stored as-is; callers
49// should not mutate them afterwards.
72// should not mutate them afterwards.
@@ -78,6 +101,18 @@ func (m *Manager) ActiveSkills() []*Skill {
78 return m.activeSkills
101 return m.activeSkills
104+// ResolvedPaths returns the expanded skills directory paths stored at
105+// construction time.
106+func (m *Manager) ResolvedPaths() []string {
107+ return m.resolvedPaths
110+// WorkingDir returns the workspace working directory stored at
111+// construction time.
112+func (m *Manager) WorkingDir() string {
81// States returns a clone of the latest discovery state snapshot.
116// States returns a clone of the latest discovery state snapshot.
82func (m *Manager) States() []*SkillState {
117func (m *Manager) States() []*SkillState {
83 m.mu.RLock()
118 m.mu.RLock()
@@ -140,18 +175,8 @@ func DiscoverFromConfig(cfg DiscoveryConfig) (allSkills, activeSkills []*Skill,
140 discovered := append([]*Skill(nil), builtin...)
175 discovered := append([]*Skill(nil), builtin...)
142 var userStates []*SkillState
177 var userStates []*SkillState
143− var userPaths []string
178+ userPaths := cfg.ResolvePaths()
144− if len(cfg.SkillsPaths) > 0 {
179+ if len(userPaths) > 0 {
145− userPaths = make([]string, 0, len(cfg.SkillsPaths))
146− for _, pth := range cfg.SkillsPaths {
147− expanded := home.Long(pth)
148− if strings.HasPrefix(expanded, "$") && cfg.Resolver != nil {
149− if resolved, err := cfg.Resolver(expanded); err == nil {
153− userPaths = append(userPaths, expanded)
155 var userSkills []*Skill
180 var userSkills []*Skill
156 userSkills, userStates = DiscoverWithStates(userPaths)
181 userSkills, userStates = DiscoverWithStates(userPaths)
157 discovered = append(discovered, userSkills...)
182 discovered = append(discovered, userSkills...)
@@ -175,6 +200,28 @@ func DiscoverFromConfig(cfg DiscoveryConfig) (allSkills, activeSkills []*Skill,
175type DiscoveryConfig struct {
200type DiscoveryConfig struct {
176 SkillsPaths []string
201 SkillsPaths []string
177 DisabledSkills []string
202 DisabledSkills []string
178 // Resolver expands $VAR-style references in paths. May be nil.
204 // Resolver expands $VAR-style references in paths. May be nil.
179 Resolver func(string) (string, error)
205 Resolver func(string) (string, error)
208+// ResolvePaths expands home-directory and $VAR references in
209+// SkillsPaths. This is the canonical path-resolution logic used by
210+// DiscoverFromConfig; callers that need the resolved list (e.g. for
211+// Catalog labels) can call this directly.
212+func (c DiscoveryConfig) ResolvePaths() []string {
213+ if len(c.SkillsPaths) == 0 {
216+ out := make([]string, 0, len(c.SkillsPaths))
217+ for _, pth := range c.SkillsPaths {
218+ expanded := home.Long(pth)
219+ if strings.HasPrefix(expanded, "$") && c.Resolver != nil {
220+ if resolved, err := c.Resolver(expanded); err == nil {
224+ out = append(out, expanded)