@ssheleg/make-skill 0.29.0 → 0.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +4 -4
- package/package.json +1 -1
- package/plugins/make-skill/.claude-plugin/plugin.json +1 -1
- package/plugins/make-skill/skills/make-skill/SKILL.md +9 -9
- package/plugins/make-skill/skills/make-skill/references/agent-skills-spec.md +18 -7
- package/plugins/make-skill/skills/make-skill/references/claude-code-plugin.md +22 -24
- package/plugins/make-skill/skills/make-skill/references/distribution.md +11 -8
- package/plugins/make-skill/skills/make-skill/references/host-capabilities.md +30 -21
- package/plugins/make-skill/skills/make-skill/references/retrofit.md +1 -1
- package/plugins/make-skill/skills/make-skill/references/surfaces.md +12 -9
- package/plugins/make-skill/skills/make-skill/scripts/audit_skill.py +69 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## v0.29.2 — portable procedures, host-specific capabilities
|
|
2
|
+
|
|
3
|
+
Authoring guidance now distinguishes the skills-CLI payload from native plugin
|
|
4
|
+
channels. It detects hooks, delegation, commands and MCP per host instead of
|
|
5
|
+
claiming every non-Claude runtime lacks them. Network requirements describe the
|
|
6
|
+
sandbox capability, and examples preserve an inline fallback.
|
|
7
|
+
|
|
8
|
+
The Claude validation guidance records a 2.1.296 positive/negative probe: missing
|
|
9
|
+
skill descriptions fail strict validation, while an unknown skill key still
|
|
10
|
+
passes. Tool grants and plugin text substitution are separated from restrictions
|
|
11
|
+
and exported shell variables. Native Codex packaging and optional skill metadata
|
|
12
|
+
are linked to current primary documentation.
|
|
13
|
+
|
|
14
|
+
## v0.29.1 — indented plain descriptions are measured correctly
|
|
15
|
+
|
|
16
|
+
The auditor now accepts a plain YAML description whose text begins on the
|
|
17
|
+
next indented line. It folds continuation lines, preserves paragraph breaks,
|
|
18
|
+
and respects comment boundaries instead of reporting a valid description as
|
|
19
|
+
missing. Bare typed values remain subject to the field's string requirement.
|
|
20
|
+
|
|
21
|
+
Unsupported forms in that path produce `FM_SUBSET_UNSUPPORTED`; field checks
|
|
22
|
+
remain unmeasured instead of making a missing-value claim. The parser stays
|
|
23
|
+
dependency-free, with an installed full YAML parser used only as an optional
|
|
24
|
+
oracle. The regression covers valid strings, actual missing/invalid values,
|
|
25
|
+
length limits, metadata maps, comments and the no-PyYAML path.
|
|
26
|
+
|
|
1
27
|
## v0.29.0 — the standard now reads a hook key the host ignores
|
|
2
28
|
|
|
3
29
|
Claude Code 2.1.270 began printing `hooks.json: unknown key "if" in
|
package/README.md
CHANGED
|
@@ -104,15 +104,15 @@ both manifests, in CI as its own job. That check is what caught this repo's own
|
|
|
104
104
|
`marketplace.json` shipping `homepage` and `repository` at a level where Claude
|
|
105
105
|
Code ignores them.
|
|
106
106
|
|
|
107
|
-
**
|
|
107
|
+
**Host capabilities — detect them and provide a fallback.** The skill
|
|
108
108
|
ships what it teaches: a `PostToolUse` **hook** that audits a `SKILL.md` the
|
|
109
109
|
moment you save one (and exits silently for every other write, in every other
|
|
110
110
|
project), a **subagent** for auditing a repo full of skills without crowding the
|
|
111
111
|
main thread, a `/skill-audit` **command**, and a stdlib **script** that does the
|
|
112
|
-
mechanical half of an audit deterministically.
|
|
113
|
-
|
|
112
|
+
mechanical half of an audit deterministically. Those bundled components use
|
|
113
|
+
Claude Code schemas; other hosts need their own activation checks. **Every
|
|
114
114
|
host capability is an accelerator with a written fallback**, for three named
|
|
115
|
-
cases (
|
|
115
|
+
cases (required host capability absent, recommended plugin absent, tool or MCP server absent).
|
|
116
116
|
A fallback you know but did not write is not a fallback.
|
|
117
117
|
|
|
118
118
|
**Evaluations, not vibes.** The canon requires every skill to carry at least
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/make-skill",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.2",
|
|
4
4
|
"description": "Create, retrofit, audit, and ship agent skills & Claude Code plugins the proven ssheleg way \u2014 conformance to the Agent Skills open standard AND Anthropic's platform rules (front-matter limits, disclosure budgets, per-surface runtime limits, the Skills API, evals) plus the Claude Code plugin reference (manifest schemas, component layout, claude plugin validate --strict), marketplace repo layout, version sync, validator + CI, multi-channel distribution (plugin, vercel skills CLI, npx, Cursor), npm gotchas, the review checklist for third-party skills, and MCP / A2A rules for protocol-connected skills. This package is the installer CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skill",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "make-skill",
|
|
4
4
|
"displayName": "Make Skill",
|
|
5
5
|
"description": "Create, retrofit, audit, and ship agent skills & Claude Code plugins the proven ssheleg way: conformance to the Agent Skills open standard, Anthropic's platform rules (surfaces, Skills API, evals) and the Claude Code plugin reference, marketplace repo layout, version sync, validator + CI, multi-channel distribution (plugin, vercel skills CLI, npx, Cursor), npm gotchas, end-to-end first publish, the review checklist for third-party skills, plus MCP / A2A references for protocol-connected skills.",
|
|
6
|
-
"version": "0.29.
|
|
6
|
+
"version": "0.29.2",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ssheleg",
|
|
9
9
|
"url": "https://x.com/sshlg93"
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: Authoring works on any agent. The bundled scripts/ need python3. Publishing steps need git, gh, node and npm; the plugin gates need the claude CLI. Not usable on the Claude API surface, which has no network and no runtime package install.
|
|
6
6
|
metadata:
|
|
7
7
|
author: ssheleg
|
|
8
|
-
version: "0.29.
|
|
8
|
+
version: "0.29.2"
|
|
9
9
|
homepage: https://github.com/ssheleg/make-skill
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -75,8 +75,8 @@ differences and the checklist: `references/agent-skills-spec.md`.
|
|
|
75
75
|
plugin listing nor an installed skill, and nothing errors, so the gap stays
|
|
76
76
|
open (all six repos here, 2026-07-30).
|
|
77
77
|
- **Host extensions are legal, never load-bearing.** Claude Code reads a further
|
|
78
|
-
host-
|
|
79
|
-
a
|
|
78
|
+
host-specific set (`references/claude-code-plugin.md`). Detect host support;
|
|
79
|
+
keep a portable fallback.
|
|
80
80
|
- Body **< 500 lines and < 5000 tokens**, and hold **5% headroom** — a body at
|
|
81
81
|
99% of budget turns the next correction into a fight with the validator.
|
|
82
82
|
Heavier material goes to `references/`, `scripts/`, `assets/` INSIDE the skill
|
|
@@ -86,10 +86,10 @@ differences and the checklist: `references/agent-skills-spec.md`.
|
|
|
86
86
|
- Gotchas stay in `SKILL.md`: the agent can't know to open a file about a trap
|
|
87
87
|
it doesn't know exists.
|
|
88
88
|
- **Write for the weakest surface you claim** (`references/surfaces.md`): the
|
|
89
|
-
Claude API container has NO network and NO package install, claude.ai varies
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
surfaces
|
|
89
|
+
Claude API container has NO network and NO package install, claude.ai varies.
|
|
90
|
+
Other coding hosts follow their sandbox policy. Declare network/interpreter
|
|
91
|
+
requirements and an offline/manual fallback. Deploy per channel; see
|
|
92
|
+
`references/surfaces.md` for account-sync exceptions.
|
|
93
93
|
|
|
94
94
|
House additions on top of the spec:
|
|
95
95
|
|
|
@@ -179,8 +179,8 @@ regardless:
|
|
|
179
179
|
- **Version sync (hard rule):** marketplace.json, plugin.json, package.json and
|
|
180
180
|
the top CHANGELOG entry carry the SAME semver, bumped together (+ a 5th point
|
|
181
181
|
if `SKILL.md` carries `metadata.version`).
|
|
182
|
-
- **Both `--strict` runs green, in CI, as their own job** —
|
|
183
|
-
|
|
182
|
+
- **Both `--strict` runs green, in CI, as their own job** — coverage varies by CLI version; keep
|
|
183
|
+
front-matter rules in your own `test/validate.py`, which needs a
|
|
184
184
|
negative self-test: a validator that can't fail is decoration. Ship `$schema`
|
|
185
185
|
and `displayName` in `plugin.json` AND the marketplace ENTRY (the marketplace
|
|
186
186
|
root takes neither).
|
|
@@ -143,13 +143,24 @@ Rules:
|
|
|
143
143
|
(Codex) provide subagents and MCP natively. DETECT the capability; do not
|
|
144
144
|
assume its absence. Presence is per host AND per version.
|
|
145
145
|
|
|
146
|
-
| Capability |
|
|
147
|
-
|
|
148
|
-
| Hooks |
|
|
149
|
-
| Subagents |
|
|
150
|
-
| MCP servers |
|
|
151
|
-
|
|
|
152
|
-
| Plugin
|
|
146
|
+
| Capability | Portable contract | Host-specific verification |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| Hooks | no portable skill hook lifecycle | inspect the host's supported events, payloads and installation scope |
|
|
149
|
+
| Subagents | procedure remains executable inline | discover the actual delegation tool; verify agent definition format separately |
|
|
150
|
+
| MCP servers | declare dependency and missing-server fallback | discover authenticated tools; do not hardcode vendor tool names |
|
|
151
|
+
| Commands | natural-language or explicit skill invocation remains possible | verify command registration, namespace and argument substitution |
|
|
152
|
+
| Plugin paths | resolve bundled files relative to the loaded skill directory | distinguish Markdown substitution from exported shell variables |
|
|
153
|
+
|
|
154
|
+
Current examples, checked 2026-10-09: [Codex subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
|
|
155
|
+
and [hooks](https://learn.chatgpt.com/docs/hooks) are real capabilities. Its
|
|
156
|
+
[plugin packaging](https://developers.openai.com/plugins/build/plugins) supports
|
|
157
|
+
portable root manifests and Codex compatibility layouts; public-directory and
|
|
158
|
+
manual-install hook eligibility differ. [Local skills](https://learn.chatgpt.com/docs/build-skills)
|
|
159
|
+
can have `agents/openai.yaml` for optional UI, invocation and tool dependencies.
|
|
160
|
+
Those files do not replace the portable skill or grant unavailable permissions.
|
|
161
|
+
|
|
162
|
+
The skills CLI installs files; an API/container surface has its own runtime.
|
|
163
|
+
Neither belongs in a single yes/no column with an interactive coding host.
|
|
153
164
|
|
|
154
165
|
And every NORM this skill enforces carries its **owner** (spec = the Agent
|
|
155
166
|
Skills standard, host = a runtime's own rule, house = this family), whether it
|
|
@@ -18,9 +18,9 @@ trusting a version-gated field in a new quarter.*
|
|
|
18
18
|
|
|
19
19
|
The [Agent Skills spec](https://agentskills.io/specification) (see
|
|
20
20
|
`references/agent-skills-spec.md`) is the portable floor. **This file is the
|
|
21
|
-
host layer on top of it
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
host layer on top of it**. Other hosts may support selected compatibility fields,
|
|
22
|
+
but cannot be assumed to implement this whole contract. Keep a portable skill
|
|
23
|
+
procedure and verify extensions separately for each claimed host and version.
|
|
24
24
|
|
|
25
25
|
## Contents
|
|
26
26
|
|
|
@@ -53,12 +53,10 @@ Both must exit 0. Rules that decide the outcome:
|
|
|
53
53
|
- Wrong **types** always fail (`keywords` as a string, not an array).
|
|
54
54
|
- It runs offline and needs no auth, so it belongs in CI:
|
|
55
55
|
`npm i -g @anthropic-ai/claude-code && claude plugin validate … --strict`.
|
|
56
|
-
- **
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
manifest it read. Keep front-matter rules in your own validator — this gate
|
|
61
|
-
does not cover them.
|
|
56
|
+
- **Coverage is versioned and partial.** The older 2.1.212 probe only inspected
|
|
57
|
+
manifests. On 2.1.296, a missing skill description fails `--strict`, while an
|
|
58
|
+
invented front-matter key still passes (probe dated 2026-10-09). Keep the house
|
|
59
|
+
validator; manifest success is neither complete format nor runtime acceptance.
|
|
62
60
|
|
|
63
61
|
## `plugin.json` — `.claude-plugin/plugin.json`
|
|
64
62
|
|
|
@@ -206,8 +204,11 @@ Portable floor (`name`, `description`, `license`, `compatibility`, `metadata`,
|
|
|
206
204
|
`allowed-tools`) is in `references/agent-skills-spec.md`. **`allowed-tools` is
|
|
207
205
|
looser here than in the spec**: Claude Code takes a space- OR comma-separated
|
|
208
206
|
string OR a YAML list, the spec takes only the space-separated string. Write the
|
|
209
|
-
spec form
|
|
210
|
-
|
|
207
|
+
spec form for portable authoring; list acceptance elsewhere is host-specific.
|
|
208
|
+
`allowed-tools` pre-approves named tools for the invocation turn; it does not
|
|
209
|
+
remove other tools and the grant clears on the next user message. Managed policy
|
|
210
|
+
may ignore these grants. This is host behavior, never permission supplied by a
|
|
211
|
+
portable skill. Claude Code also reads:
|
|
211
212
|
|
|
212
213
|
| Field | Effect |
|
|
213
214
|
|---|---|
|
|
@@ -297,18 +298,15 @@ They substitute in skill/agent content, hook and monitor commands, MCP
|
|
|
297
298
|
`command`/`args`/`env`/`workspaceFolder`. In shell-form commands, quote them:
|
|
298
299
|
`"${CLAUDE_PLUGIN_ROOT}"/scripts/x.sh`.
|
|
299
300
|
|
|
300
|
-
**They are NOT exported to the Bash tool.**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
tool's PATH, and call them by name — `references/host-capabilities.md` →
|
|
307
|
-
*Scripts*.
|
|
301
|
+
**They are NOT exported to the Bash tool.** A command in loaded Markdown can
|
|
302
|
+
contain a substituted absolute path and run correctly. A raw shell command that
|
|
303
|
+
expects the variable in its process environment cannot: the older 2.1.220 probe
|
|
304
|
+
measured that environment case. Use quoted Markdown substitution or a plugin
|
|
305
|
+
`bin/` wrapper; other hosts resolve bundled files relative to the loaded skill.
|
|
306
|
+
See `references/host-capabilities.md` → *Scripts*.
|
|
308
307
|
|
|
309
|
-
**Portability:**
|
|
310
|
-
|
|
311
|
-
variables as an optimization, not the contract.
|
|
308
|
+
**Portability:** verify each host's substitutions. A skill-relative path is the
|
|
309
|
+
portable contract; Claude's variables and plugin PATH are optional conveniences.
|
|
312
310
|
|
|
313
311
|
## Caching, symlinks, path traversal
|
|
314
312
|
|
|
@@ -320,8 +318,8 @@ orphaned versions are cleaned up ~14 days later. Consequences:
|
|
|
320
318
|
- Symlinks inside the plugin dir are preserved; symlinks to elsewhere in the
|
|
321
319
|
same marketplace are **dereferenced** (content copied); symlinks outside the
|
|
322
320
|
marketplace are skipped. This is why a meta-plugin can link sibling skills —
|
|
323
|
-
and why the same trick still breaks
|
|
324
|
-
the skill folder.
|
|
321
|
+
and why the same trick still breaks in the skills-CLI channel, which installs
|
|
322
|
+
only the skill folder. Test native plugin channels separately.
|
|
325
323
|
|
|
326
324
|
## Skills-directory plugins
|
|
327
325
|
|
|
@@ -83,10 +83,12 @@ registry fields and the risk table honestly. A skill is text an agent executes,
|
|
|
83
83
|
so "review before installing" belongs in writing — and anything that runs
|
|
84
84
|
without being asked belongs at the top of it.
|
|
85
85
|
|
|
86
|
-
**What travels where.**
|
|
87
|
-
scripts
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
**What travels where.** The skills-CLI channel copies `skills/<skill>/`, so
|
|
87
|
+
required scripts, skeletons and procedures must stay inside that directory.
|
|
88
|
+
Native plugin channels can carry additional components, including in Codex; their
|
|
89
|
+
schemas and activation differ. Never infer hook, agent or command registration
|
|
90
|
+
from a skill-only install. Each component needs a fallback and host readback
|
|
91
|
+
(`references/host-capabilities.md`).
|
|
90
92
|
|
|
91
93
|
**Version sync (hard rule):** `marketplace.json`, `plugin.json`, `package.json`
|
|
92
94
|
and the top CHANGELOG entry carry the SAME semver, bumped together; the validator
|
|
@@ -101,8 +103,8 @@ names: `references/claude-code-plugin.md`):
|
|
|
101
103
|
`displayName` in both, keep component paths `./`-relative. Unrecognized fields
|
|
102
104
|
are warnings the runtime tolerates and only `--strict` shows — that is how
|
|
103
105
|
`homepage`/`repository`, plugin-ENTRY fields, sat at the marketplace root of
|
|
104
|
-
this repo unnoticed.
|
|
105
|
-
|
|
106
|
+
this repo unnoticed. Recent versions also check selected skill metadata;
|
|
107
|
+
retain the house validator and negative probes because coverage is not complete.
|
|
106
108
|
- **`claude plugin details <name>@<marketplace>`** — the only view of what Claude
|
|
107
109
|
Code *thinks* the plugin contains and what it costs every session. Catches a
|
|
108
110
|
component listed twice and a description worth trimming.
|
|
@@ -253,8 +255,9 @@ The canon, for every bin installer that targets `~/.claude/skills/`:
|
|
|
253
255
|
- **Absence fails open; corruption never crashes.** A missing or unparsable
|
|
254
256
|
`installed_plugins.json` reads as "no plugin" — the fresh HOME is the common case,
|
|
255
257
|
and an installer that dies on a parse error refuses the machines that need it most.
|
|
256
|
-
- **
|
|
257
|
-
|
|
258
|
+
- **This guard covers the Claude channel.** It gates the `~/.claude` write alone.
|
|
259
|
+
Other hosts can have native plugins too; their provider resolution and lifecycle
|
|
260
|
+
need separate checks. This guard cannot establish their absence or precedence.
|
|
258
261
|
- **CI runs the plugin-present case, not only a fresh HOME.** A fake HOME whose
|
|
259
262
|
`installed_plugins.json` declares the plugin, asserting all three at once: the
|
|
260
263
|
non-zero exit, the remedy in the output, and that nothing was written — plus the
|
|
@@ -7,7 +7,9 @@ keep it working where those do not exist.
|
|
|
7
7
|
Sources: [Claude Code hooks reference](https://code.claude.com/docs/en/hooks) and
|
|
8
8
|
the [plugins reference](https://code.claude.com/docs/en/plugins-reference)
|
|
9
9
|
(*read 2026-08-03, Claude Code 2.1.212*), plus `references/claude-code-plugin.md`
|
|
10
|
-
for the manifest side.
|
|
10
|
+
for the manifest side. Portability and path-substitution corrections checked
|
|
11
|
+
2026-10-09 against the linked current docs; the older event examples are versioned
|
|
12
|
+
examples, not a promise that another runtime accepts their payloads.
|
|
11
13
|
|
|
12
14
|
## Contents
|
|
13
15
|
|
|
@@ -24,9 +26,10 @@ for the manifest side.
|
|
|
24
26
|
|
|
25
27
|
## The rule: accelerator, never precondition
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
The concrete schemas below describe Claude Code. Other coding hosts also provide
|
|
30
|
+
hooks, subagents, commands or MCP, with different contracts and versions. Detect
|
|
31
|
+
the actual capability and delivery channel; the skills CLI is an installer, not a
|
|
32
|
+
runtime. A directory copied successfully does not register a hook or agent. So:
|
|
30
33
|
|
|
31
34
|
> **Every host capability is an accelerator with a stated fallback. The skill
|
|
32
35
|
> must complete its job without it, more slowly and with less polish.**
|
|
@@ -48,8 +51,10 @@ but did not write is not a fallback.
|
|
|
48
51
|
| `lspServers`, `monitors`, `themes`, `outputStyles`, `workflows` | narrow, real, and rarely what a skill wants | always-on where they load | you can name the user who asked for it |
|
|
49
52
|
| `userConfig` | values prompted at enable time | a prompt in everyone's install | the skill genuinely cannot guess (a path, a workspace id) |
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
The table uses Claude Code component names and cost estimates. Do not apply them
|
|
55
|
+
as other hosts' schemas or token measurements. A skill-directory install carries
|
|
56
|
+
its own scripts and references; native plugin channels may carry more components.
|
|
57
|
+
Verify each advertised component in the receiving host before claiming support.
|
|
53
58
|
|
|
54
59
|
## Hooks — events, handlers, matchers, exit codes
|
|
55
60
|
|
|
@@ -154,8 +159,10 @@ an audit across twenty skills, a survey, a long verification — because its out
|
|
|
154
159
|
is a summary while its reading stays in its own context. It does not earn it when
|
|
155
160
|
the main thread needs the intermediate detail anyway.
|
|
156
161
|
|
|
157
|
-
Fallback:
|
|
158
|
-
|
|
162
|
+
Fallback: if this runtime exposes no suitable delegation tool, run the same
|
|
163
|
+
procedure inline and report that self-review is weaker than independent review.
|
|
164
|
+
If delegation exists, use its native contract; a Claude `agents/*.md` definition
|
|
165
|
+
is not evidence that another host registered that agent.
|
|
159
166
|
|
|
160
167
|
## Commands
|
|
161
168
|
|
|
@@ -179,19 +186,21 @@ body receives `$ARGUMENTS`. Two rules cost a debugging round each:
|
|
|
179
186
|
inside it drops the entire frontmatter block, leaving a command with no
|
|
180
187
|
description and no warning.
|
|
181
188
|
|
|
182
|
-
Fallback:
|
|
183
|
-
|
|
189
|
+
Fallback: if this host has no matching command registration, select the skill
|
|
190
|
+
through its description or explicit path and follow its procedure. Do not assume
|
|
191
|
+
a Claude command file, slash spelling or argument substitution works elsewhere.
|
|
184
192
|
|
|
185
193
|
## Scripts
|
|
186
194
|
|
|
187
|
-
The
|
|
195
|
+
The portable payload. Keep it inside the skill directory
|
|
188
196
|
(`scripts/`), stdlib-only, and invoke it by a path the agent can actually
|
|
189
197
|
resolve.
|
|
190
198
|
|
|
191
|
-
**
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
**Distinguish text substitution from a shell environment.** Claude Code replaces
|
|
200
|
+
`${CLAUDE_PLUGIN_ROOT}` in loaded Markdown; it does not export that variable to
|
|
201
|
+
commands the Bash tool runs. Current plugin docs also say monitor commands receive
|
|
202
|
+
substitution but no exported variables. The older direct-shell probe below
|
|
203
|
+
(Claude Code 2.1.220) demonstrates the environment case, not failed substitution:
|
|
195
204
|
|
|
196
205
|
```bash
|
|
197
206
|
$ echo "[${CLAUDE_PLUGIN_ROOT}]"
|
|
@@ -238,7 +247,7 @@ silently. Declare it in frontmatter `compatibility`, and write the branch:
|
|
|
238
247
|
|
|
239
248
|
Claude Code's manifest `dependencies` field can express a hard requirement
|
|
240
249
|
between plugins. Use it only when the skill genuinely cannot function — and know
|
|
241
|
-
that
|
|
250
|
+
that enforcement outside this plugin channel must be verified separately.
|
|
242
251
|
|
|
243
252
|
## The three degradation cases, written out
|
|
244
253
|
|
|
@@ -247,9 +256,9 @@ Put these in the skill body, in this shape:
|
|
|
247
256
|
```markdown
|
|
248
257
|
## Degradation
|
|
249
258
|
|
|
250
|
-
- **
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
- **Required host capability unavailable** (hook, delegation or command): run
|
|
260
|
+
<procedure> inline and state the missing enforcement or independence. Detect
|
|
261
|
+
actual tools first; bundled scripts still need their declared interpreter.
|
|
253
262
|
- **Recommended plugin absent** (<name>): <what is lost>. Continue with
|
|
254
263
|
<manual path>, and say once that the result is <weaker in this way>.
|
|
255
264
|
- **Tool or interpreter absent** (`python3`, `gh`, `npm`, an MCP server): state
|
|
@@ -273,6 +282,6 @@ what the agent reads at the exact moment something is missing.
|
|
|
273
282
|
exception list (see *Commands*); every `argument-hint` quoted
|
|
274
283
|
- [ ] Plugin agents carry no `hooks` / `mcpServers` / `permissionMode`
|
|
275
284
|
- [ ] Scripts are stdlib-only, inside the skill dir, invoked by a resolvable path
|
|
276
|
-
- [ ]
|
|
277
|
-
|
|
285
|
+
- [ ] Runnable paths use loaded-Markdown substitution, a `bin/` wrapper, or a
|
|
286
|
+
resolved skill-relative path; never depend on plugin variables in raw Bash env
|
|
278
287
|
- [ ] MCP and sibling-skill dependencies declared in `compatibility` with a branch
|
|
@@ -125,7 +125,7 @@ Report the table before changing anything, then fix.
|
|
|
125
125
|
14. **Host capabilities and their fallbacks**
|
|
126
126
|
(`references/host-capabilities.md`) if it ships a hook, subagent, command or
|
|
127
127
|
MCP server: the degradation contract written in the body for all three axes
|
|
128
|
-
(
|
|
128
|
+
(required host capability absent / recommended plugin absent / tool absent); hooks that
|
|
129
129
|
exit 0 silently when the event is not theirs; `PostToolUse` advising rather
|
|
130
130
|
than blocking; commands quoted and never named after a skill (a collision on
|
|
131
131
|
the recorded, dated exception list in `references/host-capabilities.md` —
|
|
@@ -13,7 +13,7 @@ limits before locking a contract.
|
|
|
13
13
|
## Contents
|
|
14
14
|
|
|
15
15
|
- The surface matrix — one table that decides portability
|
|
16
|
-
-
|
|
16
|
+
- Deploy per channel; account sync is a specific exception
|
|
17
17
|
- Runtime constraints authors get wrong
|
|
18
18
|
- Skills API — upload, version, attach
|
|
19
19
|
- claude.ai
|
|
@@ -41,13 +41,16 @@ and pull from GitHub. Both are true per-tenant, which means neither is something
|
|
|
41
41
|
to build on — treat claude.ai as "network may be off" and the skill still has to
|
|
42
42
|
work.
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Deploy per channel; account sync is a specific exception
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
API uploads and filesystem/plugin distributions remain separate deployment
|
|
47
|
+
channels. Keep versioned source in Git and verify each target. Current
|
|
48
|
+
[Claude Code documentation](https://code.claude.com/docs/en/skills#skills-synced-from-claudeai)
|
|
49
|
+
(read 2026-10-09) documents claude.ai account skills syncing into terminal sessions
|
|
50
|
+
from v2.1.273. It requires the supported account sign-in and policy; API-key,
|
|
51
|
+
bare/safe-mode and managed restrictions can prevent it. This is download-only:
|
|
52
|
+
local edits under `~/.claude/skills/synced/` are not uploaded and may be overwritten.
|
|
53
|
+
Do not infer sync to other coding hosts or overwrite the account-managed cache.
|
|
51
54
|
|
|
52
55
|
## Runtime constraints authors get wrong
|
|
53
56
|
|
|
@@ -58,8 +61,8 @@ Written once for the surface you happen to use, a skill silently fails elsewhere
|
|
|
58
61
|
[code execution tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool)
|
|
59
62
|
list) or state the dependency in `compatibility` and give a fallback path.
|
|
60
63
|
- **`curl`/`requests`/any fetch inside a script** — no network on the API, maybe
|
|
61
|
-
none on claude.ai.
|
|
62
|
-
|
|
64
|
+
none on claude.ai. Other coding agents can allow network access under their own
|
|
65
|
+
sandbox policy. Declare the capability, not a single vendor, in `compatibility`.
|
|
63
66
|
- **Global installs** (`npm i -g`, `pip install --user`) — discouraged even where
|
|
64
67
|
they work: the skill is a guest on the user's machine.
|
|
65
68
|
- **Absolute machine paths** (`/Users/you/...`) — nothing outside the skill
|
|
@@ -303,7 +303,8 @@ def parse_frontmatter(text):
|
|
|
303
303
|
"""A STRICT stdlib precheck of an explicitly bounded YAML subset — NOT full
|
|
304
304
|
YAML, and it does not pretend to be (FIX-MS-02.01).
|
|
305
305
|
|
|
306
|
-
The supported subset: top-level scalars (quoted or plain
|
|
306
|
+
The supported subset: top-level scalars (quoted or plain, including a plain
|
|
307
|
+
value starting on the next indented line), block scalars
|
|
307
308
|
(`>`/`|` and their chomping variants), inline flow sequences (`[a, b]`),
|
|
308
309
|
and ONE nested map. Everything else is out of subset and must be caught,
|
|
309
310
|
never waved through — a precheck that silently accepts what the real
|
|
@@ -325,12 +326,34 @@ def parse_frontmatter(text):
|
|
|
325
326
|
for a skill the Skills API rejects on upload (2026-08-16, B-63).
|
|
326
327
|
"""
|
|
327
328
|
parse_frontmatter.last_duplicates = []
|
|
329
|
+
parse_frontmatter.last_unsupported = {}
|
|
328
330
|
data, lines, key, mode = {}, {}, None, None
|
|
329
331
|
scalars = set()
|
|
330
332
|
duplicates = [] # (scope, key) pairs seen more than once
|
|
331
333
|
nested_seen = set()
|
|
334
|
+
blank_lines = 0
|
|
335
|
+
plain_closed = False
|
|
336
|
+
|
|
337
|
+
def plain_part(value):
|
|
338
|
+
# Only plain scalars: quotes and flow values use their existing path.
|
|
339
|
+
# A hash in a URL is data; whitespace followed by # starts a comment.
|
|
340
|
+
m = re.search(r"(?:^|\s)#", value)
|
|
341
|
+
return (value[:m.start()].rstrip(), True) if m else (value, False)
|
|
342
|
+
|
|
343
|
+
def unsupported(reason):
|
|
344
|
+
parse_frontmatter.last_unsupported[key] = reason
|
|
345
|
+
data[key] = None
|
|
346
|
+
scalars.discard(key)
|
|
347
|
+
|
|
332
348
|
for i, raw in enumerate(text.split("\n"), start=2): # +2: the opening '---'
|
|
333
349
|
if not raw.strip():
|
|
350
|
+
if mode == "plain":
|
|
351
|
+
blank_lines += 1
|
|
352
|
+
continue
|
|
353
|
+
if raw.lstrip().startswith("#") and (
|
|
354
|
+
raw[0] not in " \t" or mode in (None, "pending", "plain", "map")):
|
|
355
|
+
if mode == "plain":
|
|
356
|
+
plain_closed = True
|
|
334
357
|
continue
|
|
335
358
|
if raw[0] not in " \t":
|
|
336
359
|
m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", raw)
|
|
@@ -342,10 +365,14 @@ def parse_frontmatter(text):
|
|
|
342
365
|
duplicates.append(("top", key))
|
|
343
366
|
nested_seen = set()
|
|
344
367
|
lines[key] = i
|
|
368
|
+
blank_lines, plain_closed = 0, False
|
|
369
|
+
# Delay the map/scalar decision: `description:` can introduce either.
|
|
370
|
+
if val.startswith("#"):
|
|
371
|
+
val = ""
|
|
345
372
|
if val in (">", "|", ">-", "|-", ">+", "|+"):
|
|
346
373
|
data[key], mode = "", "block"
|
|
347
374
|
elif val == "":
|
|
348
|
-
data[key], mode =
|
|
375
|
+
data[key], mode = None, "pending"
|
|
349
376
|
else:
|
|
350
377
|
# Kept RAW here and finished at the end: a quoted scalar that
|
|
351
378
|
# spans lines carries its closing quote on the last one, so
|
|
@@ -353,10 +380,38 @@ def parse_frontmatter(text):
|
|
|
353
380
|
# buried in the middle of the folded value.
|
|
354
381
|
data[key], mode = val, "scalar"
|
|
355
382
|
scalars.add(key)
|
|
383
|
+
if val[0] not in "\"'[{":
|
|
384
|
+
data[key], plain_closed = plain_part(val)
|
|
385
|
+
mode = "plain"
|
|
386
|
+
elif mode == "pending":
|
|
387
|
+
value = raw.strip()
|
|
388
|
+
if re.match(r"^[A-Za-z0-9_-]+:(?:\s|$)", value):
|
|
389
|
+
data[key], mode = {}, "map"
|
|
390
|
+
m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", value)
|
|
391
|
+
nested_seen.add(m.group(1))
|
|
392
|
+
data[key][m.group(1)] = _typed_scalar(m.group(2).strip())
|
|
393
|
+
elif value[0] in "&*!{[|>" or re.match(r"^[-?:](?:\s|$)", value):
|
|
394
|
+
unsupported("indented value uses a YAML form outside the plain-scalar subset")
|
|
395
|
+
mode = "unsupported"
|
|
396
|
+
else:
|
|
397
|
+
data[key], mode = value, "scalar"
|
|
398
|
+
scalars.add(key)
|
|
399
|
+
if value[0] not in "\"'":
|
|
400
|
+
data[key], plain_closed = plain_part(value)
|
|
401
|
+
mode = "plain"
|
|
356
402
|
elif mode == "block":
|
|
357
403
|
data[key] = (data[key] + " " + raw.strip()).strip()
|
|
358
404
|
elif mode == "scalar":
|
|
359
405
|
data[key] = (data[key] + " " + raw.strip()).strip()
|
|
406
|
+
elif mode == "plain":
|
|
407
|
+
value, closed = plain_part(raw.strip())
|
|
408
|
+
if plain_closed or re.search(r":(?:\s|$)", value):
|
|
409
|
+
unsupported("plain scalar continuation follows a comment or contains a mapping separator")
|
|
410
|
+
mode = "unsupported"
|
|
411
|
+
else:
|
|
412
|
+
separator = "\n" * blank_lines if blank_lines else " "
|
|
413
|
+
data[key] += separator + value
|
|
414
|
+
blank_lines, plain_closed = 0, closed
|
|
360
415
|
elif mode == "map":
|
|
361
416
|
m = re.match(r"^\s+([A-Za-z0-9_-]+):\s*(.*)$", raw)
|
|
362
417
|
if m:
|
|
@@ -405,7 +460,7 @@ def _finish_scalar(v):
|
|
|
405
460
|
if len(v) >= 2 and v[0] == "[" and v[-1] == "]":
|
|
406
461
|
inner = v[1:-1].strip()
|
|
407
462
|
return [] if not inner else [_unquote(p.strip()) for p in inner.split(",")]
|
|
408
|
-
return
|
|
463
|
+
return _typed_scalar(v)
|
|
409
464
|
|
|
410
465
|
|
|
411
466
|
def _unquote(v):
|
|
@@ -436,6 +491,7 @@ def audit(skill_dir, house=False):
|
|
|
436
491
|
"the file, delimited by ---", rel, 1)
|
|
437
492
|
return a
|
|
438
493
|
fm, fm_lines = parse_frontmatter(m.group(1))
|
|
494
|
+
unsupported = dict(getattr(parse_frontmatter, "last_unsupported", {}))
|
|
439
495
|
body = text[m.end():]
|
|
440
496
|
|
|
441
497
|
dups = getattr(parse_frontmatter, "last_duplicates", [])
|
|
@@ -461,9 +517,16 @@ def audit(skill_dir, house=False):
|
|
|
461
517
|
a.ok("FM_YAML_CONFORMANCE", "the subset parse matches the installed "
|
|
462
518
|
"YAML parser", rel)
|
|
463
519
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
520
|
+
for key, reason in unsupported.items():
|
|
521
|
+
a.gap("FM_SUBSET_UNSUPPORTED", "%s: %s; field checks are unmeasured, "
|
|
522
|
+
"not evidence of a missing or invalid value" % (key, reason),
|
|
523
|
+
rel, fm_lines.get(key))
|
|
524
|
+
if "name" not in unsupported:
|
|
525
|
+
_check_name(a, fm, fm_lines, name_on_disk, rel)
|
|
526
|
+
if "description" not in unsupported:
|
|
527
|
+
_check_description(a, fm, fm_lines, rel, house)
|
|
528
|
+
measured = {k: v for k, v in fm.items() if k not in unsupported}
|
|
529
|
+
_check_optional_fields(a, measured, fm_lines, rel)
|
|
467
530
|
_check_keys(a, fm, fm_lines, rel)
|
|
468
531
|
_check_body_budget(a, body, rel, house)
|
|
469
532
|
_check_bundle(a, skill_dir, text, name_on_disk)
|