Skip to content
Back to skills

2 Feature

ASecurity

Loads markdown-defined skills from user / project / plugin scopes, tracks their enable state, and renders the active-skills directory that the harness attaches to user messages via the `skills-directory` reminder (see [`concepts/harness-channels.md`](../../concepts/harness-channels.md)).

  • 84 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
ai-agentsgogit

Works with

  • cli

Security analysis

A100/100

Pro scans all 19 files and shows the line behind each finding

Scanned October 2, 2026

npx -y skills add genai-io/san --skill 2-feature --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2 Feature?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2 Feature
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/genai-io-2-feature/badge)](https://www.skillsdirectory.com/skills/genai-io-2-feature)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
package: github.com/genai-io/san/internal/skill
layer: feature
---

# skill

Loads markdown-defined skills from user / project / plugin scopes, tracks
their enable state, and renders the active-skills directory that the
harness attaches to user messages via the `skills-directory` reminder
(see [`concepts/harness-channels.md`](../../concepts/harness-channels.md)).

## Purpose

A skill is a markdown file (with YAML frontmatter) that the model can be
made aware of (`active`), made invocable via slash command (`enabled`), or
hidden (`disabled`). This package:

1. Discovers skills across six scopes
   (`~/.claude/skills/`, `~/.san/plugins/*/skills/`, `~/.san/skills/`,
   `.claude/skills/`, `.san/plugins/*/skills/`, `.san/skills/`) with
   project overriding user overriding Claude-compat.
2. Persists per-skill state in user / project state stores.
3. Renders the active-skills block consumed by the `skills-directory`
   reminder provider in `internal/app`.

For the broader extension model see
[`concepts/extension-model.md`](../../concepts/extension-model.md). A
how-to-author-a-skill guide is tracked in `notes/tech-debt.md`.

## Contract

The package exposes `*Registry` directly. Skill consumers each use a
different subset of the registry surface — the TUI selector goes wide
(`List` / `StatesAt` / `SetState`), the slash-command flow uses
narrow lookups (`Get` / `FindByPartialName` / `SkillInvocationPrompt`),
the system-prompt builder uses one method (`PromptSection`), and the
session recorder attaches an observer (`SetStateChangeObserver`). No
shared narrow surface ⇒ no producer-side role interface earns its keep.

```go
package skill

// Registry is an opaque handle to the loaded skill set plus per-scope
// enabled-state stores. The type is exported so callers can hold and
// pass *Registry values; all fields are unexported.
type Registry struct { /* internal fields */ }

// Query
func (r *Registry) Get(name string) (*Skill, bool)
func (r *Registry) FindByPartialName(name string) *Skill
func (r *Registry) List() []*Skill
func (r *Registry) ListEnabled() []*Skill
func (r *Registry) ListActive() []*Skill
func (r *Registry) Count() int
func (r *Registry) IsEnabled(name string) bool

// State (used by the TUI selector)
func (r *Registry) SetState(name string, state SkillState, scope setting.Scope) error
func (r *Registry) StatesAt(scope setting.Scope) map[string]SkillState
func (r *Registry) SetEnabled(name string, enabled bool, scope setting.Scope) error
func (r *Registry) DisabledAt(scope setting.Scope) map[string]bool

// Rendering (consumed by the skills-directory reminder provider)
func (r *Registry) PromptSection() string
func (r *Registry) SkillInvocationPrompt(name string) string

// Recorder observer (used by the session recorder)
func (r *Registry) SetStateChangeObserver(cb StateChangeObserver)

// Package-level access
func Initialize(opts Options)
func Default() *Registry
func DefaultIfInit() *Registry        // nil if pre-Initialize
func SetDefaultRegistry(r *Registry)  // test-only
func ResetDefaultRegistry()           // test-only

// Skill, SkillState, SkillScope — see types.go for value types.
```

## Internals

- `Registry` (`registry.go`) is the only implementation, holding:
  - `skills []*Skill` — loaded by `loader`
  - `userStore`, `projectStore` — JSON-backed persistence of per-skill
    `SkillState`
  - `cwd` — for project-scope resolution
- `loader.go` walks the six scopes in priority order, parsing
  `SKILL.md` frontmatter and bundled resource directories
  (`scripts/` / `references/` / `assets/`).
- State (`disable` → `enable` → `active` → `disable`) cycles via
  `SkillState.NextState()`; the TUI's `/skill` flow uses this.
- Active skills render through `PromptSection()` and are delivered to
  the model via the `skills-directory` reminder attached to each user
  message; enable-only skills surface as slash commands but stay out of
  the model's awareness entirely.

## Lifecycle

- Construction: `Initialize(Options{CWD})` at app startup, before
  `internal/command` builds its slash-command list. Singleton thereafter.
- Mutation: `SetEnabled` writes through to user or project store
  immediately; in-memory `skills` slice is updated in place.
- Plugin sources are added post-init by `internal/plugin` via
  `AddPluginSkills`.
- Concurrency: registry mutations are mutex-guarded; reads are
  RWMutex-locked.

## Tests

```
internal/skill/skill_test.go            — loader, state cycling,
                                            scope priority, prompt rendering.
internal/skill/lazy_loading_test.go     — verifies content stays on disk
                                            until Instructions().
```

## See Also

- Code: `internal/skill/`
- Concepts: [`concepts/extension-model.md`](../../concepts/extension-model.md), [`concepts/harness-channels.md`](../../concepts/harness-channels.md)
- Related: [`packages/command.md`](command.md) (slash-command surface), [`packages/plugin.md`](plugin.md) (plugin-scoped skills), [`packages/reminder.md`](reminder.md) (the channel that delivers `PromptSection`)
- Layer: `feature` (see [`reference/dependency-rules.md`](../../reference/dependency-rules.md))

Files in this skill

  • agent.md3.4 KB
  • broker.md2.8 KB
  • command.md2.9 KB
  • cron.md2.3 KB
  • hook.md6.9 KB
  • inspector.md2.8 KB
  • llm.md8.5 KB
  • mcp.md5.4 KB
  • plugin.md6.7 KB
  • plugin.zh.md6.7 KB
  • reminder.md5.1 KB
  • search.md2.8 KB
  • selflearn.md9.5 KB
  • session.md4.2 KB
  • setting.md3.8 KB
  • skill.md5.1 KB
  • subagent.md5.4 KB
  • task.md2.9 KB
  • tool.md3.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…