Core concepts

Versions and identity

A published version is a folder that never changes. Identical bytes reuse it, a skill keeps one identity through renames, and receipts follow the bytes wherever they go.

A version is a folder

Publishing appends skills/<name>/v<N>/ to the team repository. Nothing edits a committed version: the write guard refuses any path inside a version folder that already holds files, whichever command asks.

The next version is the highest existing number plus one, not the count plus one, so a skill with v1 and v4 publishes next as v5. Each number has one spelling: v1, v2, v10, never v0, v03 or v1.0.

There is no pinning. install refuses a ref with @<version> and copies skills/<name>/v<max>/ as it stands in your clone, which is the newest version your last sync brought down.

Each publish adds the next version to the end of the row, and a folder identical to an existing version adds nothing.

Identical bytes reuse the version

Publish digests your folder and compares it with every existing version of that name. If one matches, no version is minted:

Nothing to publish: deploy-check is identical to Version 3 and already in the acme marketplace.

So a publish is safe to run twice, and an eval-only change does not bump the number. Eval assets, and local receipts of those exact bytes, still reach the team on every publish, minted or not.

Which bytes count

One digest function serves the publish comparison, the eval receipt and the Library’s join to the Marketplace, so the three never disagree. It covers every file in the skill folder except:

  • evals/ at the top level, so adding a case does not mint a version
  • .git and .skillhub as the first path segment
  • .DS_Store and Thumbs.db anywhere

SKILL.md is canonicalized first, dropping exactly three frontmatter fields: license, metadata.id and metadata.author. Those are the fields publish manages, so two people’s copies of one skill have the same bytes. Everything else counts, including metadata.terum-category. File modes do not count, though the executable bit is carried into the repository.

Lineage and the skill id

A skill’s identity is a uuid in metadata.id. The folder name can change; the uuid cannot, and receipts, install records and profile entries are all keyed to it.

At publish, the uuid comes from the repository, not your file:

  1. If the team already publishes a skill by that name, you append to its uuid.
  2. Otherwise, if your SKILL.md declares a metadata.id no other published skill uses, it is kept.
  3. Otherwise a fresh uuid is minted. That covers a first publish, and a folder copied from another skill, which would otherwise graft its receipts onto the original’s history.

Inside the write, publish checks again that the name still carries that uuid, and refuses rather than append to someone else’s lineage.

What publish injects

Before it checks or digests anything, publish sets four frontmatter fields in SKILL.md:

FieldValue
licenseThe team’s policy.skill_license from team.json
metadata.idThe uuid resolved above
metadata.authorYour display name and email, as Name <email>
metadata.terum-categoryOnly when the file declares none

The edited file is written back to your folder after publish’s last question and before the push. A publish you cancel at any prompt leaves your folder byte-identical. One that fails after the write-back leaves the injected fields in place, and the next attempt derives them identically.

Unpublish

npx -y terum-skills@latest unpublish <skill>

Anyone on the team may run it, after typing the skill’s name to confirm. In one commit it removes every version folder, the skills/<name>/evals/ assets, every receipt under evals/<uuid>/, and the uuid from every team project and every member’s profile[].

It does not rewrite git history, where the bytes stay reachable. It does not touch anyone’s machine: installed copies keep working, installed[] is left alone, and each machine learns of the removal at its next sync.

Republishing starts again at v1, and the removed receipts and profile entries do not come back. The uuid does not restart with them: publish keeps your folder’s declared metadata.id, and mints a fresh one only when another published skill is using it.

The incumbent

An eval runs your local folder as the candidate. The incumbent is the committed version whose newest receipt is the most recent, not the highest number: a v2 re-evaluated today is a more current comparison than a v4 evaluated last month. Versions with the candidate’s exact bytes are excluded first. With no receipted version left, there is no incumbent arm, and a new version nobody has evaluated is never the incumbent.

Receipts follow the bytes

A receipt records the content_digest of the folder it evaluated, and the local store is keyed by that digest, not by name:

~/.terum/skills/evals/local/<64 hex>/<runId>/receipt.json
  • install copies a version’s committed receipts into your local store, so an installed skill shows its team score offline.
  • eval commits its own receipt when the bytes are already a published version, writing one file and minting nothing. --no-commit keeps it local.
  • publish attaches every local receipt of exactly the bytes being published. Runs of earlier bytes stay behind.

Because the key is the digest, a receipt follows the bytes through a rename, a move to another project, and a copy to another machine.

Full reference on GitHub