-
Notifications
You must be signed in to change notification settings - Fork 7
Document agent skill packaging, add AGENTS.md and package-skill skill #870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../.claude/skills | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| --- | ||
| name: package-skill | ||
| description: Creates spec.yaml configurations for packaging agent skills as OCI artifacts. Use when adding a new agent skill to Dockyard, creating a skills/*/spec.yaml file, or vendoring a third-party skill repository (e.g. from Anthropic, Datadog, HashiCorp, Hugging Face). Not for writing a brand-new skill's own content, or for MCP server packaging (use package-mcp-server for that). | ||
| --- | ||
|
|
||
| # Package Agent Skill for Dockyard | ||
|
|
||
| This skill helps you package agent skills for distribution via Dockyard skill artifacts (OCI artifacts published to `ghcr.io/stacklok/dockyard/skills/{name}`). | ||
|
|
||
| Unlike MCP servers — which Dockyard builds into container images from an npm/PyPI/Go package — skills are repackaged directly from a pinned commit in the upstream skill's git repository. There's no build step; `dockhand` clones the repo, validates `SKILL.md`, and repackages that directory as-is. | ||
|
|
||
| ## When to Use This Skill | ||
|
|
||
| Use this skill when: | ||
| - Adding a new agent skill to Dockyard | ||
| - Creating a `skills/{name}/spec.yaml` configuration file | ||
| - Vendoring one or more skills from a third-party repository | ||
| - The user mentions "package skill", "add skill", "skills spec.yaml", or asks to package skills from a named GitHub org/repo | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - The skill's `SKILL.md` must exist in a public git repository, reachable over HTTPS | ||
| - `go build -o build/dockhand ./cmd/dockhand` (or `task build-setup`) for local validate/build | ||
| - `task scan-skill-setup` (installs `cisco-ai-skill-scanner` via `uv`) for local scanning | ||
|
|
||
| ## Workflow | ||
|
|
||
| ### Step 1: Discover the skill(s) | ||
|
|
||
| If packaging from a repo the user names, clone it shallow and enumerate candidate `SKILL.md` files: | ||
|
|
||
| ```bash | ||
| git clone --depth 1 https://github.com/{org}/{repo} /tmp/{repo} | ||
| find /tmp/{repo} -iname SKILL.md | ||
| ``` | ||
|
|
||
| Treat the results as candidates, not an automatic package list: exclude test | ||
| fixtures, examples, and templates that are not intended for end users. For each | ||
| real skill, read its YAML frontmatter (`name`, `description`, and any | ||
| `license`/`version` fields). The upstream description is the source of truth | ||
| for `metadata.description`. | ||
|
|
||
| #### Verify redistribution rights | ||
|
|
||
| Before creating any specs, find an explicit license that covers the skill | ||
| content. Check the skill directory and repository root for `LICENSE`/`COPYING` | ||
| files and inspect any SPDX-style `license:` value in `SKILL.md` frontmatter. | ||
| The license must grant the rights needed to copy, modify, and redistribute the | ||
| skill in a public OCI artifact. | ||
|
|
||
| Public source is not the same as open source. A repository with no license is | ||
| not eligible for packaging unless the copyright holder separately grants the | ||
| necessary redistribution rights. Likewise, a link to product, API, developer, | ||
| or website terms is not sufficient unless those terms explicitly license the | ||
| repository content for redistribution. If the license is missing, ambiguous, | ||
| or non-redistributable, stop and surface the issue to the user rather than | ||
| creating specs. | ||
|
|
||
| Record the license and where it was found in the PR description. If | ||
| skill-scanner later reports `MANIFEST_MISSING_LICENSE` because the license is | ||
| at the repository root rather than in `SKILL.md`, cite that verified license in | ||
| the allowlist reason. | ||
|
|
||
| Get the pinned commit SHA once, up front, and reuse it for every skill from that repo: | ||
|
|
||
| ```bash | ||
| git -C /tmp/{repo} rev-parse HEAD | ||
| ``` | ||
|
|
||
| ### Step 2: Decide names — check for collisions | ||
|
|
||
| `skills/` is a single flat namespace shared by 150+ skills from many upstream sources. Before naming anything: | ||
|
|
||
| ```bash | ||
| ls skills/ | grep -x '{candidate-name}' | ||
| ``` | ||
|
|
||
| - If the skill's own upstream name is already specific and unlikely to collide (`claude-api`, `gh-stack`), use it as-is. | ||
| - If the repo ships **multiple** skills whose names are generic on their own (e.g. `provider-docs`, `windows-builder`, `push-to-registry`), prefix **all** of them with a short vendor tag: `hashicorp-provider-docs`, `dd-apm` (Datadog), `hf-cli` (Hugging Face). | ||
| - Prefer a single flat vendor prefix over splitting by upstream sub-project (e.g. `hashicorp-` alone, not separate `hashicorp-terraform-`/`hashicorp-packer-` prefixes) — splitting produces stutter whenever a skill's own name already contains the sub-project name (`hashicorp-terraform-terraform-test` reads worse than `hashicorp-terraform-test`). | ||
| - If there's any ambiguity in naming (flat vendor prefix vs. per-subproject, or no prefix at all), **ask the user** before creating two dozen directories — renaming later means re-running the whole scan/allowlist pass. | ||
|
|
||
| ### Step 3: Create spec.yaml | ||
|
|
||
| One directory per skill: `skills/{name}/spec.yaml`. | ||
|
|
||
| ```yaml | ||
| # {Skill Name} Skill | ||
| # Source: {source-repository-url} | ||
| # Will publish as: ghcr.io/stacklok/dockyard/skills/{name}:0.1.0 | ||
|
|
||
| metadata: | ||
| name: {name} | ||
| description: "{copy verbatim from upstream SKILL.md frontmatter description}" | ||
|
|
||
| spec: | ||
| repository: "{https-git-clone-url}" | ||
| ref: "{commit-sha}" # {branch} as of {date} — pin an exact commit, never a branch | ||
| path: "{path-to-skill-dir}" # omit entirely if SKILL.md is at repo root | ||
| version: "0.1.0" # ALWAYS 0.1.0 for a new skill — see Step 4 | ||
|
|
||
| provenance: | ||
| repository_uri: "{https-git-clone-url}" | ||
| repository_ref: "refs/heads/{branch}" # Renovate follows this branch | ||
| ``` | ||
| Long `description` values often need YAML block scalars (`>-` or a quoted | ||
| multi-line string) — let a YAML dumper handle escaping rather than hand- | ||
| wrapping; malformed quoting is the most common review-round-trip bug here. | ||
|
|
||
| ### Step 4: Version — always start at 0.1.0 | ||
|
|
||
| Do **not** copy an upstream release tag (e.g. a repo-wide `v1.0.0`) into `spec.version`, even if one exists. Dockyard owns semver for every vendored skill independently of upstream (see `docs/skill-versioning.md`) specifically because: | ||
| - Most upstream skill repos don't tag individual skills at all. | ||
| - Even when a repo does cut a release, it's typically a whole-repo tag that doesn't map to any single skill's actual changes — and several individual `SKILL.md` frontmatter blocks often carry their own, much lower, per-skill `version` (e.g. `0.0.1`, `0.1.0`) that would contradict a `1.0.0` Dockyard tag anyway. | ||
|
|
||
| Every new skill starts at `0.1.0`. If this comes up with the user, it's worth surfacing as a quick recommendation rather than silently picking one — but the answer is almost always 0.1.0. | ||
|
|
||
| ### Step 5: Validate | ||
|
|
||
| ```bash | ||
| build/dockhand validate-skill --config skills/{name}/spec.yaml | ||
| ``` | ||
|
|
||
| Fix any error before moving on — this clones the repo and actually checks `SKILL.md` exists at the given path. | ||
|
|
||
| ### Step 6: Scan and triage findings | ||
|
|
||
| ```bash | ||
| task scan-skill-setup # once per machine | ||
| task scan-skill -- skills/{name} | ||
| ``` | ||
|
|
||
| This is the expensive, judgment-heavy step. Read `scripts/skill-scan/README.md` if you haven't already. What to expect: | ||
|
|
||
| - **Warnings** (below the `HIGH` block threshold) don't fail the task — you can leave them, though citing the intentionally-accepted ones (like `MANIFEST_MISSING_LICENSE` when the upstream repo has a root-level `LICENSE` but no per-skill frontmatter field) in the allowlist keeps the scan summary self-documenting. | ||
| - **Blocking findings** (`HIGH`+, unallowlisted) fail the task and must be triaged one by one. | ||
|
|
||
| For each blocking finding, look at its `file_path`/`line_number`/`message` and decide: genuine issue, or false positive? Skill docs are prose- and example-heavy, so the overwhelming majority are false positives from pattern/keyword rules matching on things like: | ||
| - Shell variable expansion (`${TOKEN}`, `$HOME`) in documented setup commands | ||
| - Documented, vendor-official install one-liners (`curl -L .../release`, `sudo apt-get install`, an official Chocolatey/PowerShell bootstrap) | ||
| - Example IP addresses, placeholder credentials (`CLIENT_SECRET="your-secret"`), or attribute names like `password` in schema/code examples | ||
| - Words like "exfil", "override", "skip", "kill", "root" appearing in ordinary explanatory prose, not as an executable instruction | ||
|
|
||
| Add each as a `security.allowed_issues` entry, matching by `rule_id` (exact — prefer this) or `category` (broader, only when many distinct rule_ids share one clear rationale). **Every reason must cite the specific matched text and its file:line**, and explain concretely why it isn't a threat — not just "false positive": | ||
|
|
||
| ```yaml | ||
| security: | ||
| allowed_issues: | ||
| - rule_id: ATR_2026_00066 | ||
| reason: "FP: matched shell variable expansion (\`${TOKEN}\`) in a | ||
| documented setup command (SKILL.md:45) — standard shell syntax, | ||
| not injected secrets." | ||
| ``` | ||
|
|
||
| When packaging many skills from the same repo, findings cluster heavily by `rule_id` — collect all blocking findings across the batch first (group by skill + rule_id), write one templated-but-specific reason per rule_id, then customize per skill using that skill's actual matched text. Don't reuse a reason verbatim across skills without checking the cited location actually matches what's in *that* skill — genuinely different constructs can share a rule_id (e.g. an `iex (...)` PowerShell bootstrap vs. a `sudo apt-get install` line both trip the same "documented install command" rule but need different citations). | ||
|
|
||
| Never set `security.insecure_ignore: true` to bypass this — it disables the gate rather than documenting the finding. | ||
|
|
||
| Re-run `task scan-skill -- skills/{name}` until it exits clean. Repeat for every skill in the batch. | ||
|
|
||
| ### Step 7: Build (smoke test) | ||
|
|
||
| ```bash | ||
| build/dockhand build-skill --config skills/{name}/spec.yaml | ||
| ``` | ||
|
|
||
| No `--push` needed locally — this just proves the artifact packages successfully end to end. Doing this for at least one representative skill in a batch is enough; running it for all of them is optional but cheap. | ||
|
|
||
| ### Step 8: Commit | ||
|
|
||
| ```bash | ||
| git add skills/{name}/spec.yaml | ||
| git commit -s -m "Add {name} skill | ||
| Package {name} from {upstream-repo} at {commit-sha}. | ||
| Source: {upstream-repo-url}" | ||
| ``` | ||
|
|
||
| Use `git commit -s` — DCO Signed-off-by is required (see `CONTRIBUTING.md`). For a batch of skills from one repo, one commit covering all of them is fine. | ||
|
|
||
| ## Common Issues | ||
|
|
||
| | Issue | Solution | | ||
| |-------|----------| | ||
| | `validate-skill` fails: SKILL.md not found | Double check `spec.path` — it's relative to the repo root, not to `skills/{name}/` | | ||
| | `scan-skill` times out on a large batch | Run it per-skill in a loop rather than one long-running batch command; each clone+scan takes 5-15s | | ||
| | Same `rule_id` fires for unrelated reasons across skills | Verify the actual matched text before reusing a reason — don't copy-paste blind | | ||
| | Version bump CI failure after editing `spec.ref` later | Run `go run ./cmd/skillversionbump --base origin/main --write` — see `docs/skill-versioning.md` | | ||
| | Tempted to set `insecure_ignore: true` | Don't. Triage the finding instead, or ask the user if it's a genuine risk to accept explicitly with a reason | | ||
|
|
||
| ## See Also | ||
|
|
||
| - [docs/adding-skills.md](../../../docs/adding-skills.md) — Complete contribution guide | ||
| - [docs/skill-versioning.md](../../../docs/skill-versioning.md) — Why version always starts at 0.1.0 | ||
| - [docs/security.md](../../../docs/security.md) — Security scanning and attestation model | ||
| - `scripts/skill-scan/README.md` — Scanner wrapper scripts and allowlist mechanics | ||
| - [internal/skills/spec.go](../../../internal/skills/spec.go) — The actual validated spec.yaml fields | ||
| - [references/SPEC-YAML-REFERENCE.md](references/SPEC-YAML-REFERENCE.md) — Full spec.yaml field reference |
97 changes: 97 additions & 0 deletions
97
.claude/skills/package-skill/references/SPEC-YAML-REFERENCE.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # spec.yaml Full Reference (Agent Skills) | ||
|
|
||
| Complete reference for Dockyard agent skill specification files. Fields are | ||
| validated by `internal/skills/spec.go` (`LoadSkillSpec`/`validateSkillSpec`) | ||
| — that file is authoritative if this reference and the code ever disagree. | ||
|
|
||
| ## File Location | ||
|
|
||
| ``` | ||
| skills/{skill-name}/spec.yaml | ||
| ``` | ||
|
|
||
| There are no protocol subdirectories for skills (unlike MCP servers' | ||
| `npx/`/`uvx/`/`go/`) — everything lives directly under `skills/`. | ||
|
|
||
| ## Full Schema | ||
|
|
||
| ```yaml | ||
| # Comments documenting the skill (optional but conventional) | ||
| # Source: https://github.com/{org}/{repo} | ||
| # Will publish as: ghcr.io/stacklok/dockyard/skills/{name}:{version} | ||
|
|
||
| metadata: | ||
| name: string # Required: skill identifier — should match the | ||
| # directory name (lowercase, hyphens) | ||
| description: string # Optional but conventional: copy from upstream | ||
| # SKILL.md frontmatter `description` | ||
|
|
||
| spec: | ||
| repository: string # Required: HTTPS git clone URL. Must be https:// | ||
| # — validated, non-HTTPS URLs are rejected. | ||
| ref: string # Required: commit SHA, tag, or branch. Always | ||
| # use a commit SHA in practice — a moving | ||
| # branch/tag breaks reproducibility and confuses | ||
| # skillversionbump's diff-based heuristic. | ||
| path: string # Optional: subdirectory within the repo | ||
| # containing SKILL.md. Omit if SKILL.md is at | ||
| # the repo root. | ||
| version: string # Required: Dockyard-owned semver, used as the | ||
| # OCI tag. New skills always start at "0.1.0" | ||
| # regardless of any upstream version/tag — see | ||
| # docs/skill-versioning.md. | ||
|
|
||
| provenance: # Optional to the parser; required for Renovate | ||
| # to keep spec.ref current automatically | ||
| repository_uri: string # Same as spec.repository, restated for clarity | ||
| repository_ref: string # The branch the pinned commit came from and that | ||
| # Renovate should follow, e.g. "refs/heads/main" | ||
|
|
||
| security: # Optional — omit entirely if the scan is clean | ||
| allowed_issues: | ||
| - rule_id: string # Exact finding identifier from skill-scanner | ||
| # output (preferred — narrowest match) | ||
| # category: string # Alternative to rule_id: matches by category | ||
| # instead, broader — use only when many | ||
| # distinct rule_ids share one clear rationale | ||
| reason: string # Required with either rule_id or category. | ||
| # Must cite the specific matched text/location | ||
| # and explain concretely why it's not a threat. | ||
| insecure_ignore: boolean # Default false. NEVER set true to bypass a | ||
| # scan failure — it disables the security gate | ||
| # entirely rather than documenting findings. | ||
| # Reserved for cases the scanner genuinely | ||
| # cannot run against (rare). | ||
| ``` | ||
|
|
||
| ## Derived values | ||
|
|
||
| - **OCI tag**: `ghcr.io/stacklok/dockyard/skills/{metadata.name}:{spec.version}` (lowercased name) | ||
| - **Git reference URI** (used internally by ToolHive's git resolver): | ||
| `git://{host}/{path}[@{ref}][#{path}]`, built from `spec.repository`/`spec.ref`/`spec.path` | ||
|
|
||
| ## Fields that do NOT exist (common mistakes) | ||
|
|
||
| - `protocol` — MCP-server-only field, meaningless for skills | ||
| - `package` / `spec.package` — MCP-server-only; skills have no package registry | ||
| - `args` / `env` — MCP-server-only (baked into the runtime container); skills | ||
| don't run anything, they're just files | ||
| - `metadata.version` — version lives under `spec.version`, not `metadata` | ||
|
|
||
| ## Minimal valid example | ||
|
|
||
| ```yaml | ||
| metadata: | ||
| name: gh-stack | ||
| description: "Manage stacked branches and pull requests with the gh-stack GitHub CLI extension" | ||
|
|
||
| spec: | ||
| repository: "https://github.com/github/gh-stack" | ||
| ref: "14fc42ed9b6c376a53b2f999f138d3bd26dac546" | ||
| path: "skills/gh-stack" | ||
| version: "0.1.0" | ||
|
|
||
| provenance: | ||
| repository_uri: "https://github.com/github/gh-stack" | ||
| repository_ref: "refs/heads/main" | ||
| ``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.