@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 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
- **The Claude Code power set — with a fallback for everywhere else.** The skill
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. Hooks, subagents and commands
113
- exist only inside Claude Code — so the canon makes the fallback a rule: **every
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 (not Claude Code, recommended plugin absent, tool or MCP server absent).
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.0",
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.0",
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.0"
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-only set (`references/claude-code-plugin.md`); other agents ignore it, so
79
- a skill DEPENDING on one is broken everywhere else. Outside spec ∪ host = typo.
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
- only Claude Code has both. A script that `pip install`s or curls is a Claude
91
- Code skill — say so in `compatibility` or drop it. Nothing syncs between
92
- surfaces; git is the source of truth.
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** — they read MANIFESTS
183
- only, so front-matter rules live in your own `test/validate.py`, which needs a
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 | Claude Code | Codex | Cursor | skills CLI / API | Detect by |
147
- |---|---|---|---|---|---|
148
- | Hooks | yes | no | no | no | host docs / config |
149
- | Subagents | yes | **yes (native)** | no | no | runtime probe |
150
- | MCP servers | yes | **yes (native)** | varies | no | runtime probe |
151
- | `/commands` | yes | no | no | no | host docs |
152
- | Plugin path vars | yes | no | no | no | env presence |
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**: everything here is Claude-Code-specific and is
22
- ignored by other agents — so nothing here may be load-bearing for a skill that
23
- must also run on Cursor, Codex, or the skills CLI.
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
- - **It validates the MANIFEST, whatever the docs promise.** The troubleshooting
57
- table says the command checks "`plugin.json`, skill/agent/command frontmatter,
58
- and `hooks/hooks.json`"; on 2.1.212 a `SKILL.md` carrying an invented
59
- front-matter key passed `--strict` untouched, and the output names only the
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 — a list works here and breaks everywhere else. Claude Code also
210
- reads:
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.** Substitution into text and export
301
- into a process are different things: a hook script sees `CLAUDE_PLUGIN_ROOT` in
302
- its environment, a `Bash` tool call does not (measured empty on 2.1.220). So a
303
- skill that *prints* the variable gets a real path, and a skill that tells the
304
- agent to *run* a command containing it gets `/skills/...` and a missing-file
305
- error. Put runnable scripts in the plugin's `bin/`, which lands on the Bash
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:** all four are Claude Code inventions. A skill that must also run
310
- on other agents references bundled files by **relative path** and treats the
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 on non-Claude agents, which install only
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.** Only `skills/<skill>/` reaches non-Claude channels, so
87
- scripts and skeletons live inside it. `hooks/`, `agents/` and `commands/` reach
88
- Claude Code alone: they are accelerators, and the skill body owes each one a
89
- written fallback (`references/host-capabilities.md`).
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. It reads the MANIFEST only: front-matter rules stay in
105
- your own validator, whatever the troubleshooting table promises.
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
- - **Only Claude Code has plugins.** The check gates the `~/.claude` write alone;
257
- installs into other agents' skill directories are untouched by it.
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
- Everything on this page exists only inside Claude Code. A skill that *needs* one
28
- of them is broken on Cursor, Codex, the skills CLI, the Claude API and claude.ai
29
- — which is most of where skills run. So:
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
- Everything in that table is Claude-Code-only. `scripts/` is the exception that
52
- travels: it lives inside the skill directory, so every channel ships it.
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: on any other host there are no subagents. The skill body must describe
158
- the same procedure inline, so a Cursor session does the work in one context.
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: elsewhere there is no `/command`. The skill's own description must
183
- carry the trigger phrases that reach the same behavior in plain language.
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 one accelerator that travels. Keep it inside the skill directory
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
- **The trap that costs the most here: `${CLAUDE_PLUGIN_ROOT}` does not work in a
192
- command you tell the agent to run.** It is substituted into skill, command and
193
- agent *text*, and it is exported to *hook and monitor* processes — but it is
194
- **not** in the Bash tool's environment. Measured on Claude Code 2.1.220:
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 it means nothing on any other host.
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
- - **Not Claude Code** (Cursor, Codex, skills CLI, API): hooks, subagents and
251
- `/commands` do not exist. Run <procedure> inline; the bundled `scripts/` still
252
- work wherever `python3` does.
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
- - [ ] No command the agent is told to RUN contains `${CLAUDE_PLUGIN_ROOT}` — it is
277
- empty in the Bash tool; ship a `bin/` wrapper and call it by name
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
- (not Claude Code / recommended plugin absent / tool absent); hooks that
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
- - Nothing syncs — each surface is a separate deployment
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
- ## Nothing syncs — each surface is a separate deployment
44
+ ## Deploy per channel; account sync is a specific exception
45
45
 
46
- A skill uploaded to the API is not on claude.ai and not in Claude Code, in any
47
- direction. There is no sync mechanism and none is planned in the docs. Keep the
48
- skill directory in git as the single source of truth and treat every surface as a
49
- publish target — which is exactly what the distribution matrix in
50
- `references/distribution.md` automates for the filesystem-based agents.
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. A skill whose only path to data is an HTTP call is a Claude
62
- Code skill; say so in `compatibility`.
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), block scalars
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 = {}, "map"
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 _unquote(v)
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
- _check_name(a, fm, fm_lines, name_on_disk, rel)
465
- _check_description(a, fm, fm_lines, rel, house)
466
- _check_optional_fields(a, fm, fm_lines, rel)
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)