Why: repair_placeholders ran inside a command substitution, so a failing sed left an emptied file behind and the script still exited 0 reporting success. - write via tmp file and abort on sed or mv failure - re-check the target before moving staging into place - stage in a dot-prefixed mktemp dir so a killed run leaves no fake skill - source apm claims in deployment-modes.md; tag untyped code blocks Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
3.8 KiB
source_keys
| source_keys | ||
|---|---|---|
|
Deployment Modes
Skills deploy standalone, or as part of an APM package (an apm.yml-governed .apm/ tree, compiled via apm compile). Some consumers also receive a package through a host's plugin install, which copies it into a cache. All modes resolve relative paths from the skill root — the SKILL.md body works the same in any of them. Differences only arise when referencing files outside the skill directory.
Cache isolation (host plugin install)
When a host installs a plugin, it copies the plugin directory to a cache. Only the plugin's own files are copied. Any path that leaves the skill directory breaks post-install:
../other-skill/validate.sh # breaks
plugins/<plugin>/.apm/skills/other/ # breaks
../../shared/utils.sh # breaks
Fix: duplicate the file into the skill's own scripts/ or assets/. There is no plugin-level shared/ mechanism — the spec defines no cross-skill sharing, and ../ paths are broken by construction.
Compiled output (APM package mode)
For a package (an apm.yml-governed .apm/ source tree), the deployable artifact is generated by apm compile per target harness — not produced by copying the raw .apm/ directory wholesale the way a plugin cache install copies a plugin directory. The same self-containment rule still applies at the skill level: file references inside .apm/skills/<name>/ must not reach outside that skill's own directory.
../other-skill/validate.sh # breaks
.apm/skills/other-skill/ # breaks
../../shared/utils.sh # breaks
Fix: duplicate the file into the skill's own scripts/ or assets/, same as plugin mode. apm.yml's includes: list (when explicit, not auto) controls what gets published from the package, but it is not a cross-skill sharing mechanism — each skill directory must still stand alone.
Env vars (Claude Code plugin install only)
Claude Code injects these when it loads the plugin from its install cache. Other harnesses do not, and neither does standalone mode — so they are Claude Code-specific, unlike the target-neutral ${PLUGIN_ROOT} hook token that apm rewrites per target.
| Variable | Value |
|---|---|
${CLAUDE_PLUGIN_ROOT} |
Absolute path to the plugin's install directory. Changes on update. |
${CLAUDE_PLUGIN_DATA} |
Persistent directory that survives updates. Use for node_modules, generated state, caches. |
Neither belongs in SKILL.md body text, since standalone deployments won't have them. Hook commands are not authored here: hook authoring, including which script-path token to use (the target-neutral ${PLUGIN_ROOT}), belongs to primitive-author.
Standalone mode
Deployed directly to ~/.agents/skills/<name>/. No plugin context, no env vars injected. All file references must resolve within the skill directory. Skill invocations (e.g. /factory-audit) work if the called skill is also installed.
Cross-tool portability
SKILL.md is portable — the same file works in Claude Code and Copilot CLI, whether deployed standalone or compiled from an APM package. apm.yml is the source manifest: it is itself tool-agnostic (one file describes the package regardless of target), but apm compile produces per-target compiled output — a Claude Code plugin tree, a Copilot CLI tree, etc. — from it. A legacy hand-authored plugin.json is tool-specific and sits outside the apm.yml-based flow. Hooks are apm primitives under .apm/hooks/, authored by primitive-author.
Shared assets between skills
If two skills in the same plugin need the same file, duplicate it into each skill's assets/ or scripts/. Add a comment in both copies noting the mirror relationship so they stay in sync when the spec changes.