Skip to content
Back to skills

Capability Bump

ASecurity

Change the JSON shape of a ynh command safely — decide whether it bumps CapabilitiesVersion, then update the schema, goldens and docs together. Use when adding, removing or changing any field in a --format json response.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentsrustgobash

Works with

  • cli

Security analysis

A100/100

Scanned October 20, 2026

npx -y skills add eyelock/ynh --skill capability-bump --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Capability Bump?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Capability Bump
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/eyelock-capability-bump/badge)](https://www.skillsdirectory.com/skills/eyelock-capability-bump)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: capability-bump
description: Change the JSON shape of a ynh command safely — decide whether it bumps CapabilitiesVersion, then update the schema, goldens and docs together. Use when adding, removing or changing any field in a --format json response.
---

# Change a structured-output shape

`config.CapabilitiesVersion` is the wire-protocol version every enveloped
response carries. Consumers — loop drivers, CI scripts, `ynd validate-output` —
read it to know what they are talking to.

`.claude/CLAUDE.md` states the rule. This skill is how you carry it out, because
the rule is a decision and the execution is four files that must move together.

## Step 1 — Decide: does this bump?

**Bumps** — a consumer must adapt:

- removing a field
- renaming a field
- changing a field's type
- narrowing a type (`string` → enum)
- tightening an enum (removing a member)
- making an optional field required
- changing the meaning of an existing value

**Does not bump** — a tolerant consumer is unaffected:

- adding an optional field
- adding a member to an open-set enum (consumers MUST tolerate unknowns, per
  `docs/cli-structured.md`)
- adding a whole new schema
- relaxing a constraint

**If unsure, treat it as a bump.** A needless bump costs a consumer one version
check. A missed one breaks them silently.

Note the asymmetry: *adding* an enum member does not bump, *removing* one does.
Open-set enums put the tolerance obligation on the consumer, which only works if
the set only ever grows.

## Step 2 — Know which responses are enveloped

**Most golden responses do not carry `capabilities` at all.** Many commands return a
bare array or object with no envelope:

```console
$ ynh version --format json
{ "version": "...", "capabilities": "0.8.0" }

$ ynh vendors --format json
[ { "name": "claude", ... } ]        ← bare array, no envelope, no version

$ ynh paths --format json
{ "home": "...", "config": "..." }   ← bare object, no version
```

Enveloped: `agent-run`, `baseline`, `check`, `check-calibrate`, `fork`, `info`,
`installed`, `list`, `version`.

**Derive the split rather than trusting a written list — it moves.** #271 added
five bare goldens and #302 added two more, so any number written here is wrong
within a fortnight:

```bash
for f in test/golden/*.json; do
  grep -q '"capabilities"' "$f" && echo "ENV  $f" || echo "bare $f"
done
```

Two consequences:

- **A bump touches every enveloped golden**, not just the command you changed —
  they all carry the version string.
- Changing the shape of a *non*-enveloped command still needs its schema and
  golden updated, but there is no version for a consumer to check. Worth raising
  rather than silently relying on.

## Step 3 — Move all four together

A change that lands in fewer than these is incomplete:

| What | Where |
|---|---|
| the code | `cmd/ynh/<command>.go` |
| the schema | `docs/schema/cli/<command>.schema.json` |
| the goldens | `test/golden/<command>.json` — **plus every enveloped golden if bumping** |
| the version | `internal/config/config.go` — `CapabilitiesVersion` |

Plus, when relevant: `docs/schema/plugin.schema.json` if a manifest field
changed, and `docs/schema-cli.md` if the contract narrative did.

**Pending removals ride along.** A field kept only to avoid a bump is listed in
`deprecatedUntilBump` (`internal/agent/deprecations_test.go`), and that test
fails as soon as `CapabilitiesVersion` moves. A bump for any reason must remove
each listed field (code, schema, goldens, docs) and its entry, or move the entry
to the new version on purpose. When you deprecate a field to avoid a bump, add
it there with its tracking issue.

Schemas are embedded from `docs/schema/` by `internal/clischema`, so the
published file and the one the binary validates against are the same tree. There
is no second copy to forget.

## The worked example — #254

The freshness gate is the cleanest recent bump to copy. It added `freshness` and
`freshness_basis` to check results, added `observes` to the sensor schema, and
took `CapabilitiesVersion` from `0.7.0` to `0.8.0`:

```
cmd/ynh/check.go
docs/schema/cli/check.schema.json
docs/schema/cli/check-calibrate.schema.json
docs/schema/plugin.schema.json
docs/schema-cli.md
internal/config/config.go
test/golden/{agent-run,baseline,check,check-calibrate,fork,info,installed,list,version}.json
```

Note the nine goldens. Only two are check-related; the other seven changed
solely because the version string in their envelope moved. **If your diff bumps
the version and touches one golden, you have missed eight.**

Worth asking whether it needed to bump at all: `freshness` was an added optional
field, which by the rule does not bump. It bumped because a `files` sensor's
*gating behaviour* changed — `absent` and `stale` began failing where they
previously reported. That is "changing the meaning of an existing field value",
and it is the subtle branch of the rule. **The shape is not the only thing a
consumer depends on.**

## Step 4 — Verify

```bash
make test FILE=./cmd/ynh          # golden comparisons live here
make test FILE=./internal/clischema
make check
```

Then check the contract by hand, the way a consumer would:

```bash
ynh <command> --format json | ynd validate-output --schema <command>
ynh schema <command>              # the embedded schema
ynh schema --all --format json    # every schema, as a manifest
```

The schema name is bare — `--schema version`, not `--schema cli/version`, even
though the file lives at `docs/schema/cli/version.schema.json`. The prefixed
form errors with `unknown schema`.

`ynd validate-output` exists so a harness author can assert against the contract
without writing a schema loader. If your change makes real output fail its own
published schema, that is the bug — not the test.

## Step 5 — Say so in the PR

State explicitly:

- whether it bumps, and **which rule clause** decided it
- the old and new version
- that goldens and schema moved with it

A reviewer cannot check "did this need a bump" without knowing which clause you
applied. `docs/cli-structured.md` is the contract consumers read; if the change
alters what it promises, it changes too.

## The failure this prevents

A consumer polls `ynh check --format json`, reads `capabilities`, and branches
on it. Change a field's meaning without bumping and that consumer keeps its old
branch, silently, against output that no longer means what it did.

Nothing fails. The gate goes on reporting, and the thing it reports is wrong —
which is the same class of failure `reference` fixtures exist to catch in
sensors, one layer up.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.