Skip to content

Align output with the agentskills.io specification#6

Merged
bueti merged 2 commits into
mainfrom
v0.4-spec-compliance
Apr 18, 2026
Merged

Align output with the agentskills.io specification#6
bueti merged 2 commits into
mainfrom
v0.4-spec-compliance

Conversation

@bueti
Copy link
Copy Markdown
Owner

@bueti bueti commented Apr 18, 2026

Summary

Brings skillgen's output and validation in line with https://agentskills.io/specification. Four coordinated changes. The output-layout change is breaking but targeted — no public consumers depend on the old <name>.md flat form yet.

Directory layout (breaking)

Skills now write as <name>/SKILL.md inside the target directory instead of a flat <name>.md. This is what the spec requires and what Claude Code's skill loader actually looks for. Skills from prior versions were silently ignored by real loaders.

  • Skill.FilenameSkill.Path (<prefix><name>/SKILL.md).
  • New Skill.Dir() returns the directory name.
  • WriteTo creates the subdirectory before writing.

Spec-standard frontmatter

Three new annotations under every target:

  • skill.licenselicense:
  • skill.compatibilitycompatibility: (max 500 chars)
  • skill.metadata.<key>metadata: map, prefix-pattern so authors add arbitrary keys without a wrapper API. Keys emitted in sorted order.

FrontmatterField gains a Map field; scalar vs. map emission dispatched in writeFrontmatterField. targetFrontmatterbuildFrontmatter, composing spec-standard fields first and target extensions second.

Spec-limit lint rules

Generator.Lint() now also checks the rendered skills against spec hard/soft limits:

  • name empty / > 64 / bad format → error (invalid names break loaders)
  • description empty / > 1024 → error
  • compatibility > 500 → error
  • body > 5000 tokens → warning (spec recommendation)
  • body > 500 lines → warning (spec recommendation)

skillNamePattern enforces the spec regex ^[a-z0-9]+(?:-[a-z0-9]+)*$. SpecMax* constants exported for external consumers.

Per-skill budget warnings

skills generate now emits a per-skill warning when any one skill body exceeds the 5000-token spec recommendation, alongside the existing aggregate warning. The aggregate alone was misleading: a CLI with many small skills could stay under the aggregate cap while individual leaves still burned too much context.

Test plan

  • go build ./...
  • go test ./... — 14 new tests in spec_test.go pass alongside existing suite
  • go vet ./...
  • golangci-lint run ./... — 0 issues
  • go run ./example skills generate --dir /tmp/out produces /tmp/out/mytool/SKILL.md with valid spec frontmatter
  • go run ./example skills lint still passes without errors on the test CLI
  • CI matrix (Linux / macOS / Windows on Go 1.25) once it runs on the PR

Migration (for any consumers)

  1. Skill.FilenameSkill.Path; Path now contains the directory too.
  2. Any scripts that read <dir>/<name>.md should read <dir>/<name>/SKILL.md.

bueti added 2 commits April 18, 2026 20:31
Brings skillgen's output and validation in line with
https://agentskills.io/specification. Four coordinated changes.

Directory layout
----------------
Skills now write as <name>/SKILL.md inside the target directory
instead of a flat <name>.md. This is what the spec requires — and,
not coincidentally, what Claude Code's skill loader actually looks
for. Skills generated by prior versions were ignored by real loaders,
which explains reports that agents never found them.

Skill.Filename → Skill.Path, holding the full relative path
(<prefix><name>/SKILL.md). Skill.Dir() returns the directory name.
WriteTo creates the subdirectory before writing the file.

Spec-standard frontmatter
-------------------------
Three new annotations fill the gaps we had under every target:

- skill.license → `license:` field (SPDX name or bundled file ref).
- skill.compatibility → `compatibility:` field (env requirements,
  max 500 chars).
- skill.metadata.<key> → `metadata:` map, prefix pattern so authors
  can add arbitrary keys without a wrapper API. Keys emitted sorted
  for determinism.

FrontmatterField gains a Map field for map-typed values;
writeFrontmatterField dispatches between scalar and map emission.
targetFrontmatter is replaced by buildFrontmatter, which composes
spec-standard fields first and target-specific ones second.

Spec-limit lint rules
---------------------
Generator.Lint now rounds out the previous per-command checks with
spec-enforcement against the rendered skills:

- name empty / > 64 chars / bad format → error (hard spec rule;
  invalid names break loaders, which is why we promote to error
  rather than warning).
- description empty / > 1024 chars → error.
- compatibility > 500 chars → error.
- body > 5000 tokens → warning (spec recommendation).
- body > 500 lines → warning (spec recommendation).

skillNamePattern matches ^[a-z0-9]+(?:-[a-z0-9]+)*$ — enforces
lowercase alphanumerics and hyphens with no leading/trailing/
consecutive hyphens, exactly as the spec requires.

SpecMax* constants are exported so consumers writing their own
validation can mirror the same limits.

Per-skill budget warnings
-------------------------
`skills generate` now warns once per skill whose body exceeds the
5000-token spec recommendation, in addition to the existing
aggregate warning. The aggregate threshold was misleading: a CLI
with 20 small skills could stay under the aggregate cap while each
leaf still burned too much context on its own.

Tests
-----
14 new tests in spec_test.go covering directory layout, prefixed
directories, split-mode layout, all three new frontmatter
annotations (with sorted key emission and empty-value filtering),
and every new lint rule (positive + negative cases).

Docs
----
README gains an "Output layout" section, rewritten annotations
table including license / compatibility / metadata / allowed-tools,
and a Linting section call-out for spec enforcement. CHANGELOG
under Unreleased gets Changed (breaking) and Added entries.
Windows CI failed because filepath.Join uses backslash there, so
Skill.Path came out "mytool\SKILL.md" and the exact-string tests
fell over. More importantly, Skill.Path is a manifest-style
identifier that ships in YAML and git — it should be platform-
neutral regardless of where skills generate runs.

Switch to path.Join / path.Dir (always forward slash) for Skill.Path
and Skill.Dir. WriteTo still uses filepath.Join(dir, s.Path) at
write time, which correctly converts to OS-specific separators.
@bueti bueti merged commit 82e865f into main Apr 18, 2026
4 checks passed
@bueti bueti deleted the v0.4-spec-compliance branch April 18, 2026 18:38
@bueti bueti mentioned this pull request Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant