skill: make the peltier skill consumable across agent harnesses

The SKILL.md Agent Skills standard (agentskills.io) is now read natively by
Codex, Copilot, VS Code, Cursor, Gemini CLI, opencode, Goose, Amp, Zed,
Factory, and ZeroClaw — so the portable move is not per-harness adapter
files (forked content that drifts) but one canonical skill placed where
each harness actually looks.

- Spec-compliant frontmatter: license, compatibility (the Linux-only
  measurement story, stated up front), version/author/tags top-level
  (ZeroClaw's runtime reads those; the spec ignores unknown fields).
  reference/ renamed to references/ per the spec's conventional layout.
- .agents/skills/peltier/ mirror: the cross-tool convergence path (Codex,
  Zed, Cursor, Gemini CLI, Copilot, opencode, Goose, Amp). Byte-identical
  to the .claude canonical, enforced by CI — drift between the two copies
  is a red build, not a footnote.
- AGENTS.md at the repo root for the ~20 harnesses that read it (Claude
  Code does not; CLAUDE.md remains its file). Deliberately thin: it points
  at CLAUDE.md and the skill instead of restating rules.
- scripts/install-skill.sh: stamps a consumer repo at both discovery paths
  (--layout claude|agents|both), or emits a script-less --zeroclaw-variant.
  ZeroClaw's skill security policy blocks script files by default
  (skills.allow_scripts off) — its audit rejected the full skill and passes
  the variant, verified against the real zeroclaw binary. The variant loses
  nothing: preflight.sh still exists exactly once, in the peltier checkout
  the skill already requires, and SKILL.md now documents running it from
  there. Re-implementing preflight inline is named as a refusal condition.
- CI (skill-preflight): frontmatter upgraded to a full Agent Skills spec
  conformance check on BOTH copies (name rules, dir match, description and
  body limits, metadata string-map), plus mirror equality and a functional
  installer test (both layouts identical to canonical, variant carries no
  scripts/, nonexistent target refused).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 11:59:26 -04:00
co-authored by Claude Fable 5
parent ec73e277f1
commit 477c5472dd
13 changed files with 801 additions and 28 deletions
+48 -13
View File
@@ -105,23 +105,58 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: SKILL.md frontmatter is loadable
- name: SKILL.md conforms to the Agent Skills spec (both copies)
run: |
python3 - <<'PY'
import sys, yaml, pathlib
p = pathlib.Path(".claude/skills/peltier/SKILL.md")
text = p.read_text(encoding="utf-8")
if not text.startswith("---\n"):
sys.exit("SKILL.md must open with a --- frontmatter block")
fm = yaml.safe_load(text.split("---", 2)[1]) # raises on malformed YAML
for key in ("name", "description"):
if not isinstance(fm.get(key), str) or not fm[key].strip():
sys.exit(f"frontmatter '{key}' must be a non-empty string")
if fm["name"] != p.parent.name:
sys.exit(f"frontmatter name '{fm['name']}' != directory '{p.parent.name}'")
print(f"frontmatter OK: name={fm['name']}, description={len(fm['description'])} chars")
import re, sys, yaml, pathlib
for skill in (".claude/skills/peltier", ".agents/skills/peltier"):
p = pathlib.Path(skill) / "SKILL.md"
text = p.read_text(encoding="utf-8")
if not text.startswith("---\n"):
sys.exit(f"{p}: must open with a --- frontmatter block")
fm = yaml.safe_load(text.split("---", 2)[1]) # raises on malformed YAML
body = text.split("---", 2)[2]
for key in ("name", "description"):
if not isinstance(fm.get(key), str) or not fm[key].strip():
sys.exit(f"{p}: frontmatter '{key}' must be a non-empty string")
# agentskills.io spec: 1-64 chars, [a-z0-9-], no edge/double hyphens,
# name == parent dir; description <= 1024 chars; body <= 500 lines.
n = fm["name"]
if not re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", n) or len(n) > 64:
sys.exit(f"{p}: name '{n}' violates the Agent Skills name rules")
if n != p.parent.name:
sys.exit(f"{p}: name '{n}' != directory '{p.parent.name}'")
if len(fm["description"]) > 1024:
sys.exit(f"{p}: description exceeds 1024 chars")
if len(body.splitlines()) > 500:
sys.exit(f"{p}: body exceeds 500 lines")
if "metadata" in fm and not (
isinstance(fm["metadata"], dict)
and all(isinstance(k, str) and isinstance(v, str) for k, v in fm["metadata"].items())
):
sys.exit(f"{p}: metadata must be a string-to-string map")
print(f"{p}: spec OK (name={n}, description={len(fm['description'])} chars)")
PY
- name: .agents mirror is byte-identical to .claude canonical
run: |
diff -r .claude/skills/peltier .agents/skills/peltier
echo "mirror identical"
- name: Installer stamps both layouts + the ZeroClaw variant correctly
run: |
if sh scripts/install-skill.sh /tmp/no-such-dir 2>/dev/null; then
echo "FAIL: installer accepted a nonexistent target"; exit 1
fi
mkdir -p /tmp/consumer && sh scripts/install-skill.sh /tmp/consumer
diff -r .claude/skills/peltier /tmp/consumer/.claude/skills/peltier
diff -r .claude/skills/peltier /tmp/consumer/.agents/skills/peltier
sh scripts/install-skill.sh --zeroclaw-variant /tmp/zc-variant
test ! -e /tmp/zc-variant/scripts # script-less: ZeroClaw blocks script files
test -f /tmp/zc-variant/SKILL.md
diff -r .claude/skills/peltier/references /tmp/zc-variant/references
echo "installer OK: both layouts identical to canonical; zeroclaw variant has no scripts/"
- name: Preflight resolves inside a peltier checkout
run: |
sh .claude/skills/peltier/scripts/preflight.sh | tee /tmp/pf.txt