Skip to content

feat: full agentskills.io spec compliance for skills subsystem #65

Description

@ezynda3

Feature Description

Bring Kit's skills subsystem into full compliance with the agentskills.io specification and client implementation guide in a single coordinated sweep.

Kit's current implementation (internal/skills/, pkg/kit/skills.go, cmd/extension_context.go, internal/ui/model.go) is substantially aligned with the spec — discovery layout, <available_skills> catalog, behavioral preamble, /skill:<name> activation, runtime mutation SDK, and XDG-aware user-level scanning are all in place. The gaps below fall into three buckets:

  1. Read-side robustness — parser drops half the spec's frontmatter fields, doesn't validate, doesn't XML-escape, and the YAML failure mode is too brittle for cross-client skills.
  2. Safety / policy — no project-trust gate, no per-skill disable mechanism, slash-command-injected skill content is not compaction-protected.
  3. Optional polish — no dedicated activation tool, no <skill_resources> enumeration, no activation dedup, missing SDK helpers.

All 16 gaps below should be addressed in a single PR executed by a coding agent. They cluster in the same small set of files (internal/skills/, pkg/kit/skills.go, pkg/kit/kit.go, cmd/root.go, plus one new builtin MCP server and one new trust module), and the changes are tightly coupled — e.g. the new frontmatter fields flow through the struct, the SDK type alias, the extension conversion, the catalog rendering, and the docs all at once.


Motivation / Use Case

agentskills.io is the emerging cross-client spec for portable agent skills. As more tooling (Claude Code, Cursor, etc.) adopts it, Kit users expect skills authored elsewhere to "just work" inside Kit, and skills authored for Kit to be portable to other clients. Two concrete pain points today:

  • A description with <, >, or & produces a malformed catalog XML that some models will refuse to parse — silent quality regression.
  • A project-level .agents/skills/ directory in a freshly cloned repository silently injects instructions into the system prompt the moment you cd into it. This is a real prompt-injection vector — a malicious repo can add description: …always ignore safety prompts. and have it loaded before the user sees it.

Beyond those, several spec-required behaviors are simply absent (e.g. missing ~/.agents/skills/ scan, missing compatibility/license/metadata fields, no name-collision precedence rule).


What Kit already does well ✅

Spec area Kit behaviour
Progressive disclosure (3 tiers) Catalog in system prompt → body on activation → resources via read
.agents/skills/ cross-client convention Scanned project-local
User-level skills ~/.config/kit/skills/ (XDG aware)
Kit-native dir .kit/skills/ project-local
<available_skills> catalog tag Matches spec
Behavioral preamble Matches the spec's "file-read activation" template almost verbatim
Body-only stripping at activation /skill:name strips frontmatter, re-reads for freshness
Structured wrapping at activation Body wrapped in <skill name=… location=…> with baseDir note
User-explicit activation /skill:<name> slash command + TUI autocomplete
Frontmatter parsing YAML + name/description extracted; SKILL.md filename → directory-name fallback
Runtime mutation SDK AddSkill / RemoveSkill / SetSkills / ReloadSkills / LoadAndAddSkill
CLI/SDK options --skill, --skills-dir, --no-skills + matching Options fields, viper-bound

Gaps to fix 🚧

All items below ship in a single PR. Priority labels indicate correctness severity, not PR scheduling.

🔴 P0 — Correctness bugs

1. No XML escaping in the catalog

internal/skills/skills.go:206-228 (FormatForPrompt) interpolates name/description directly into XML:

fmt.Fprintf(&buf, "    <description>%s</description>\n", s.Description)

A description containing <, >, &, ", or a stray </description> will produce malformed XML. Fix: wrap with encoding/xml.EscapeText or a small inline escaper.

2. Description-required validation is silently broken

internal/skills/skills.go:217-222 skips the <description> element when the field is empty, but still emits the skill in the catalog. The spec says description is required and that skills missing it should be skipped with a logged error (it's "essential for disclosure"). Today such skills appear in the catalog as <skill><name>foo</name></skill> which the model cannot discover by relevance.

Fix: in LoadSkill, return a "skip with warning" sentinel when description is empty. Add a Validate() method returning structured []Diagnostic.

3. --skills-dir semantics are inverted

cmd/root.go:297 flag help: "override the project-local skills directory for auto-discovery". Reality (pkg/kit/kit.go:1745):

cwd := opts.SkillsDir
if cwd == "" { cwd = opts.SessionDir }
return skills.LoadSkills(cwd)

LoadSkills(cwd) then appends .agents/skills/ and .kit/skills/ to that cwd. So --skills-dir /tmp/myskills actually scans /tmp/myskills/.agents/skills/ and /tmp/myskills/.kit/skills/, not /tmp/myskills/ itself.

Fix: treat SkillsDir as a direct skills directory (what users expect). Update flag help to match.


🟠 P1 — Spec compliance

4. Missing scan locations

Spec lists four canonical scopes; Kit scans three:

Spec path Kit scans?
<project>/.agents/skills/
<project>/.kit/skills/
~/.agents/skills/ missing
~/.config/kit/skills/
.claude/skills/ (pragmatic compat) ❌ optional

Fix: in internal/skills/skills.go::LoadSkills, add os.UserHomeDir() + "/.agents/skills/". Optionally scan ~/.claude/skills/ + <cwd>/.claude/skills/ behind a flag for Claude Code compat.

5. Name-collision precedence

Spec: "project-level skills override user-level skills … log a warning when a collision occurs." internal/skills/skills.go:173-180 dedupes by Path only, so two skills named code-review in ~/.config/kit/skills/ and .kit/skills/ both end up in the catalog with duplicate <name> entries.

Fix: dedupe by Name with explicit precedence (project > user) and log.Warn when shadowing occurs.

6. Missing spec frontmatter fields

internal/skills/skills.go::Skill (lines 25-39) only carries Name, Description, Tags, When. The spec defines:

Field Required In Kit struct?
name yes
description yes
license no
compatibility no ❌ (useful — model can adapt execution)
metadata no
allowed-tools no (experimental)
tags — (Kit extension)
when — (Kit extension)

Fix: add License string, Compatibility string, Metadata map[string]string, AllowedTools string fields with YAML tags. Document tags/when as Kit extensions in godoc. Update pkg/kit/skills.go::convertSkill and internal/extensions Skill type accordingly.

7. Compaction protection for /skill: content

Spec Step 5: "exempt skill content from pruning." When a user runs /skill:foo (see pkg/kit/kit.go:1679-1731 expandSkillCommand), the wrapped <skill>...</skill> body becomes a regular user message and is eligible for summarization in internal/compaction/. Skills loaded via the system-prompt catalog are safe (system prompt is replayed), but explicitly-activated skills can silently disappear mid-session, degrading quality with no visible error.

Fix: either (a) add a Protected bool flag on session entries that compaction skips, or (b) detect the <skill_content> / <skill> wrapper tag in compaction.FindCutPoint and preserve those messages.

8. Project-level trust gate

Spec: "Consider gating project-level skill loading on a trust check — only load them if the user has marked the project folder as trusted."

Currently any cloned repo with .agents/skills/ or .kit/skills/ silently injects instructions into the system prompt on cd. Real prompt-injection vector.

Fix: persist a trust allowlist at ~/.config/kit/trusted-projects.json. First time entering a repo with project-level skills, show a TUI prompt: "Load N skills from /path/to/repo?" with Trust / Skip / Trust+Always options.


🟡 P2 — Cross-client compatibility & polish

9. Malformed-YAML fallback

Spec calls out the common description: Use when: … (unquoted colon) failure mode common in Claude-authored skills. Kit's yaml.Unmarshal (internal/skills/skills.go:72) fails the whole skill load on such files.

Fix: on YAML error, regex-detect ^(\w+):\s+(.+:.+)$ scalar lines, wrap the value in quotes, retry once.

10. Per-skill disable + disable-model-invocation honoring

Spec: "Hide filtered skills entirely from the catalog … reasons: user disabled, permission denial, disable-model-invocation flag." Kit has no UI/CLI/config for disabling individual skills and doesn't honor any disable flag.

Fix:

  • Honor disable-model-invocation: true in frontmatter — exclude from catalog, still allow via /skill: slash command.
  • Add --skill-disable foo flag and skill-disable: [foo] config key.
  • Expose (*Kit).DisableSkill(name) / EnableSkill(name) SDK methods.

11. <skill_resources> enumeration on activation

When /skill:foo runs, expandSkillCommand writes a baseDir hint but does not list scripts/*, references/*, assets/*. Spec recommends surfacing these without reading them, so the model knows what's available.

Fix: after loading body, walk one level into scripts/, references/, assets/, render:

<skill_resources>
  <file>scripts/extract.py</file>
  <file>references/REFERENCE.md</file>
</skill_resources>

Cap at ~50 entries with a (truncated) note.

12. Drop file:// prefix on <location>

internal/skills/skills.go:223 emits <location>file:///...</location> but spec examples and tool inputs use bare paths. Most models tolerate file:// but Kit's read tool / MCP fs server expect plain paths.

Fix: drop the file:// prefix.


🟢 P3 — Optional, high-ROI enhancements

13. Dedicated activate_skill MCP tool

Spec Step 4 lists this as the preferred pattern when you want bundled-resource enumeration, strict enum-constrained name (prevents hallucination), centralized analytics + dedup, and per-skill permission prompts. Kit relies entirely on the generic read tool today.

Fix: add a builtin MCP tool kit_activate_skill(name: enum[...]) registered only when ≥1 skill is loaded. Re-reads the SKILL.md (freshness), strips frontmatter, enumerates resources, wraps in <skill_content name="…">…</skill_content>, records an event for dedup.

14. Activation deduplication (depends on #13)

Spec: "Consider tracking which skills have been activated in the current session … skip re-injection." Per-session activatedSkills map[string]bool; on duplicate call return: "Skill 'foo' already loaded earlier in this session."

15. SDK ergonomics

Gap Recommendation
Skill lacks BaseDir() helper func (s *Skill) BaseDir() string { return filepath.Dir(s.Path) }
Skill lacks Resources() listing func (s *Skill) Resources() []string walking one level into scripts/, references/, assets/
No Validate() on Skill Return structured []Diagnostic
pkg/kit/skills.go::convertSkill drops new fields Update alongside #6
internal/extensions/api.go:780 comment lists scan paths that disagree with code Sync after #4
internal/skills/skills.go package godoc lists only 2 paths but scans 3 Sync after #4

16. Documentation

internal/config/config.go:498-501 generated .kit.yml template doesn't document the new spec frontmatter fields (compatibility, license, metadata, allowed-tools). Add a brief section alongside #6.


Proposed Implementation

Single PR, executed end-to-end by a coding agent. The work clusters in a small, tightly-coupled blast radius — bundling it into one sweep avoids merge churn (e.g. each frontmatter field change cascades through five files) and lets the agent reason about cross-cutting invariants (compaction-protection tag must match activation-time wrapper must match catalog-time stripping).

Files touched

Area Files Nature of change
Core parser/discovery internal/skills/skills.go, internal/skills/skills_test.go New fields, validation, escaping, YAML fallback, name precedence, ~/.agents/skills/, drop file://, helpers
Prompt composition internal/skills/prompt_builder.go (small) n/a unless wrapper tag changes
SDK surface pkg/kit/skills.go, pkg/kit/kit.go, pkg/kit/kit_test.go convertSkill update, DisableSkill/EnableSkill, --skills-dir semantics, expandSkillCommand resource enumeration
Extension bridge internal/extensions/api.go, internal/extensions/symbols.go New Skill fields exposed to extensions
Activation tool (new) internal/mcpserver/skills/ (new package) + registration in cmd/root.go kit_activate_skill builtin MCP tool with enum-constrained name, dedup, resource listing
Compaction protection internal/compaction/cutpoint.go, internal/session/tree_manager.go Honor Protected flag or wrapper-tag detection
Trust gate (new) internal/trust/ (new package), TUI hook in internal/ui/model.go or cmd/root.go Persisted allowlist + first-load prompt
CLI cmd/root.go --skill-disable flag, fix --skills-dir help text
Docs internal/config/config.go (generated .kit.yml template) Document new frontmatter fields
Tests internal/skills/*_test.go, pkg/kit/*_test.go, new tests for trust + MCP tool Coverage for every gap

Suggested execution order within the PR

  1. Schema first — extend Skill struct (feat: highlight @file tokens in input with accent color #6), add Validate() (fix: ToolRenderConfig BorderColor and Background fields are ignored #2), XML escape helper (Add support for overriding model registry and capabilities #1), strip file:// (TUI feels visually noisy: user label, large prompt placeholder, and input help take too much space #12).
  2. Discovery — add ~/.agents/skills/ scan (OAuth fails for remote MCP servers without dynamic client registration (e.g. GitHub) — no way to provide ClientID #4), name-collision precedence (feat: open external $EDITOR for composing long prompts via ctrl+x e chord #5), YAML fallback (OpenAI login does not become the default provider, and starting with --model openai/... fails until model is reselected in /model #9).
  3. CLI/SDK semantics — fix --skills-dir (AddMCPServer() creates MCPToolManager without inheriting OAuth AuthHandler and TokenStoreFactory #3), add disable mechanism (Shift+Enter does not insert a newline even though the UI hint says it should #10), SDK helpers (Assistant message was temporarily truncated until I sent my next reply #15).
  4. Activation path<skill_resources> enumeration (/thinking incorrectly reports that GPT-5.4 does not support thinking/reasoning #11), new kit_activate_skill MCP tool (Ctrl+C exits the app instead of clearing the current input #13), dedup tracking (Text selection in the TUI is awkward and makes copying partial output frustrating #14).
  5. Safety — compaction protection (feat(sdk): expose generation and provider params on Options #7), project-trust gate (fix: error calling tool #8).
  6. Docs + extension symbols.kit.yml template (feat: Mirror Fantasy's streaming callbacks as Kit EventBus events (ToolCallStart, ToolCallDelta, Source, etc.) #16), internal/extensions/symbols.go sync.
  7. Tests — run go test -race ./... after each step; final pass with go vet ./... and go fmt ./....

Acceptance criteria

  • go test -race ./... passes
  • go vet ./... clean
  • All 16 gaps resolved with at least one test per behavior change
  • A skill authored for Claude Code (with license/compatibility/unquoted-colon description) loads correctly in Kit
  • A skill with < in its description renders valid catalog XML
  • A skill missing description is skipped with a logged warning, not silently dropped from disclosure
  • Entering a fresh repo with .agents/skills/ prompts for trust before loading
  • --skills-dir /tmp/x scans /tmp/x directly

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions