|
| 1 | +# Design: Agent Skills Scanner |
| 2 | + |
| 3 | +**Date:** 2026-05-08 |
| 4 | +**Status:** Approved |
| 5 | + |
| 6 | +## Problem |
| 7 | + |
| 8 | +`vet endpoint scan` (and its alias `vet ai discover`) discovers installed AI tools, MCP servers, coding agents, and extensions — but not **skills** (agent skill directories). Skills are a first-class artifact of AI coding agents (Claude Code, Cursor, Windsurf, etc.) and should be part of the endpoint inventory synced to SafeDep Cloud. |
| 9 | + |
| 10 | +## Goal |
| 11 | + |
| 12 | +Extend `vet endpoint scan` and `vet ai discover` to also discover and sync agent skills installed on the endpoint. No new commands, no new sinks, no new flags — pure extension of the existing inventory pipeline. |
| 13 | + |
| 14 | +## Scope |
| 15 | + |
| 16 | +- Scan project-local skill directories (e.g., `.claude/skills/`) and global skill directories (e.g., `~/.claude/skills/`) |
| 17 | +- Each subdirectory = one skill; directory name = skill name |
| 18 | +- Emit one `inventory.Item` per discovered skill directory |
| 19 | +- Register as a new scanner kind `"agent-skill"` in the existing registry |
| 20 | +- `vet ai discover` pins to both `ai-tool` and `agent-skill` kinds |
| 21 | +- Cloud sync, local table, JSON report all work automatically through existing sinks |
| 22 | + |
| 23 | +## Out of Scope |
| 24 | + |
| 25 | +- Parsing skill file contents or frontmatter |
| 26 | +- Detecting enabled/disabled state |
| 27 | +- Any new CLI commands or flags |
| 28 | + |
| 29 | +## Architecture |
| 30 | + |
| 31 | +Pure extension following the `aitool` scanner pattern: |
| 32 | + |
| 33 | +``` |
| 34 | +pkg/inventory/scanners/ |
| 35 | +├── scanners.go ← add KindAgentSkill constant + descriptor |
| 36 | +└── skills/ |
| 37 | + ├── adapter.go ← implements inventory.Scanner |
| 38 | + └── translate.go ← skill discovery result → *inventory.Item |
| 39 | +``` |
| 40 | + |
| 41 | +`cmd/endpoint/scan.go`: update `RunAITool` to pin `[]string{KindAITool, KindAgentSkill}`. |
| 42 | + |
| 43 | +No changes to orchestrator, sinks, or any other command. |
| 44 | + |
| 45 | +## Agent Registry |
| 46 | + |
| 47 | +Each agent entry encodes the project-local path (relative to `cfg.ProjectDir`) and the global path (relative to `cfg.HomeDir`). Both paths are checked when the corresponding scope is enabled. |
| 48 | + |
| 49 | +Supported agents (from the canonical table): |
| 50 | + |
| 51 | +| Agent | Kind Flag | Project Path | Global Path | |
| 52 | +|---|---|---|---| |
| 53 | +| Amp, Kimi CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` | |
| 54 | +| Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` | |
| 55 | +| Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` | |
| 56 | +| IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` | |
| 57 | +| Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` | |
| 58 | +| OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` | |
| 59 | +| Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` | |
| 60 | +| CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` | |
| 61 | +| Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` | |
| 62 | +| Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` | |
| 63 | +| Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` | |
| 64 | +| Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` | |
| 65 | +| Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` | |
| 66 | +| Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` | |
| 67 | +| Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` | |
| 68 | +| Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` | |
| 69 | +| Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` | |
| 70 | +| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` | |
| 71 | +| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` | |
| 72 | +| Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` | |
| 73 | +| Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` | |
| 74 | +| iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` | |
| 75 | +| Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` | |
| 76 | +| Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` | |
| 77 | +| Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` | |
| 78 | +| MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` | |
| 79 | +| Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` | |
| 80 | +| Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` | |
| 81 | +| OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` | |
| 82 | +| OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` | |
| 83 | +| Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` | |
| 84 | +| Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` | |
| 85 | +| Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` | |
| 86 | +| Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` | |
| 87 | +| Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` | |
| 88 | +| Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` | |
| 89 | +| Windsurf | `windsurf` | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` | |
| 90 | +| Zencoder | `zencoder` | `.zencoder/skills/` | `~/.zencoder/skills/` | |
| 91 | +| Neovate | `neovate` | `.neovate/skills/` | `~/.neovate/skills/` | |
| 92 | +| Pochi | `pochi` | `.pochi/skills/` | `~/.pochi/skills/` | |
| 93 | +| AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` | |
| 94 | + |
| 95 | +Note: agents sharing the same project path (e.g., `amp`, `kimi-cli`, `replit`, `universal` all use `.agents/skills/`) are treated as separate entries — the `App` field on the emitted `Item` records which agent name the skill belongs to. Skills found in a shared path are emitted once per matching agent. |
| 96 | + |
| 97 | +## Data Model |
| 98 | + |
| 99 | +Each discovered skill directory produces one `*inventory.Item`: |
| 100 | + |
| 101 | +``` |
| 102 | +Kind = KindAgentSkill (9) |
| 103 | +Name = directory name (e.g. "yaad", "stop-slop") |
| 104 | +App = agent identifier (e.g. "claude-code") |
| 105 | +Scope = ScopeProject | ScopeSystem |
| 106 | +ConfigPath = absolute path to skill directory |
| 107 | +ItemIdentity = FNV-64a( app + "/" + kind + "/" + scope + "/" + name + "/" + configPath ) |
| 108 | +Enabled = nil |
| 109 | +Metadata = nil |
| 110 | +``` |
| 111 | + |
| 112 | +## Scanner Logic |
| 113 | + |
| 114 | +``` |
| 115 | +for each agentEntry in registry: |
| 116 | + for each (scope, dir) in [(ScopeProject, projectDir/agentEntry.ProjectPath), |
| 117 | + (ScopeSystem, homeDir/agentEntry.GlobalPath)]: |
| 118 | + if !cfg.ScopeEnabled(scope): continue |
| 119 | + if dir does not exist: continue |
| 120 | + for each entry in ReadDir(dir): |
| 121 | + if !entry.IsDir(): continue |
| 122 | + emit translate(agentEntry.App, scope, entry.Name(), dir/entry.Name()) |
| 123 | +``` |
| 124 | + |
| 125 | +## Item Identity |
| 126 | + |
| 127 | +Follows the same FNV-64a scheme used by the aitool scanner: |
| 128 | + |
| 129 | +```go |
| 130 | +ItemIdentity = fmt.Sprintf("%x", fnv64a(app + "/" + kindStr + "/" + scopeStr + "/" + name + "/" + configPath)) |
| 131 | +``` |
| 132 | + |
| 133 | +## Changes Summary |
| 134 | + |
| 135 | +| File | Change | |
| 136 | +|---|---| |
| 137 | +| `pkg/inventory/scanners/scanners.go` | Add `KindAgentSkill = "agent-skill"` constant; add descriptor to registry | |
| 138 | +| `pkg/inventory/scanners/skills/adapter.go` | New — Scanner implementation | |
| 139 | +| `pkg/inventory/scanners/skills/translate.go` | New — skill → Item translation | |
| 140 | +| `cmd/endpoint/scan.go` | `RunAITool` pins `[]string{KindAITool, KindAgentSkill}` | |
| 141 | + |
| 142 | +## Testing |
| 143 | + |
| 144 | +- Unit tests in `pkg/inventory/scanners/skills/adapter_test.go` using a temp directory tree |
| 145 | +- Unit tests in `pkg/inventory/scanners/skills/translate_test.go` for Item field correctness |
| 146 | +- Existing scenario tests and orchestrator tests are unaffected |
0 commit comments