agentleFS
buildinternetbuildinternet/releases/skills/creating-releases-json/SKILL.md

creating-releases-json

Skill14 starsChanged 13 days agoApache-2.0View on GitHub

Create or update a releases.json manifest so registries and agents can find where a company or project publishes its release notes. Use this whenever someone wants to "list our product on Releases", "add a releases.json", "get indexed on Releases Index", "here's our website, make us a manifest", declare a changelog / updates page / RSS feed / GitHub releases / App Store listing / CHANGELOG for discovery, publish a /.well-known/releases.json, or edit an existing one — even if they don't say "releases.json" by name. The usual input is just a company website or domain (no codebase required); the manifest is a small, schema-validated file the owner hosts on their domain or repo. This skill discovers the real publish locations, models products without over-fragmenting, writes the correctly-scoped file, validates it locally, and guides publishing it.

  • Sends data out

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 8 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Search the codebase and the company's site for every place product updates are published.

    line 74
  2. Very specific:

    Add "title" to name a location, and "canonical": true on the single primary entry when several locations describe one product/repo scope …

    line 100
  3. Distinctive:

    Use this when the changelog lives in git and the owner will push it in with the publish-changelog GitHub Action, instead of the registry …

    line 109
  4. Distinctive:

    Do not invent this for a changelog the registry can already fetch.

    line 128
  5. Very specific:

    Rule of thumb: if every product entry you're about to write shares one changelog, you want zero products and one top-level releases[].

    line 173
  6. Very specific:

    Start from the matching example in examples/ and adapt it:

    line 181
  7. Very specific:

    Put company-wide tags at the root.

    line 207
  8. Very specific:

    Pinning (optional): If the org/product is already in a registry and you want the file bound to exact records instead of matched by URL, add …

    line 213
  9. Distinctive:

    Run the bundled zero-dependency validator (Node 18+ or Bun, no install) — it checks the full contract, including rules the plain JSON …

    line 225
  10. Distinctive:

    If you do have the repo,: write it into place:

    line 255
  11. Very specific:

    Repo scope: commit releases.json at the repository root.

    line 260
  12. Distinctive:

    After it's deployed and publicly reachable, confirm it end-to-end:

    line 262
  13. Very specific:

    Read the current file and run it through scripts/validate.mjs first to see where it stands (it may predate a rule).

    line 288
  14. Distinctive:

    Add newly-found locations, keeping within the caps and the one-canonical rule.

    line 291
  15. Very specific:

    If it's still a v1 file ("version": 1 or profile-only fields like a nested org),

    line 292
  16. Distinctive:

    Read it when a company has many offerings and you're unsure how to split them.

    line 300

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Creating a releases.json manifest

releases.json is a small, owner-declared file that states what products a company ships and where it publishes release notes — a changelog, an updates page, an RSS/Atom feed, GitHub Releases, an App Store listing, or a hosted CHANGELOG file. Any registry or agent that reads the format (starting with releases.sh) learns where the release notes live directly from the owner instead of guessing.

Authority comes from where the file lives, not from what it claims. A file at https://{domain}/.well-known/releases.json speaks for that whole company; a releases.json at a repository root speaks only for that repo. That is the entire trust model — if you can publish the file at the URL, you can declare what it says.

Your job with this skill: figure out which scope applies, discover the locations where release notes actually live (never invent them), model the company's products without over-fragmenting, write a correctly-shaped file, validate it, and help get it published.

How you'll be asked to run this

Most of the time there is no codebase — someone just says "here's our website, make us a releases.json". That's the primary path: you work from the live site (and GitHub org), browsing to find where updates are published, and you hand back a finished file plus instructions on where to host it. You cannot commit it for them.

Less often you're working inside a repo — the one that serves the company's site, or a single product/library repo — in which case you can also read the codebase and write the file into place. Both paths use the same steps below; only Step 1 (scope) and Step 5 (publish) differ.

The one rule that matters most

Only declare locations that really exist and respond. The manifest states plain facts about where updates are published. A guessed or aspirational URL is worse than omitting it — it creates a broken source. Before writing any locator, confirm the URL resolves (fetch it, or check it against the codebase/site). If you cannot verify a location, leave it out and say so.

Step 1 — Choose the scope

There are two scopes. They share one schema but declare different things:

ScopeLives atDeclares
Domainhttps://{domain}/.well-known/releases.jsonCompany identity, products[], and top-level releases[]
Reporeleases.json at a repository rootA product binding + where this repo's releases land

Default to domain scope — it's what "here's our website, make us a manifest" wants, and it's the only scope that can describe the whole company and multiple products. Choose repo scope only when you're clearly operating inside one product/library repo and the goal is to bind that repo to a product. A repo file cannot declare company identity; a domain file cannot speak for a repo it doesn't host. If genuinely unsure, ask which one they want.

Step 2 — Discover the real publish locations

Search the codebase and the company's site for every place product updates are published. Look for, and verify each one responds:

  • Changelog / what's-new / updates pages — /changelog, /whats-new, /releases, updates.<domain>, a "Product updates" blog category.
  • RSS/Atom feeds — often linked from the changelog page's <head> (<link rel="alternate" type="application/rss+xml">), or a Canny/Beamer/Headway feed.
  • GitHub repos with Releases — repos under the org that cut tagged releases or keep a CHANGELOG.md. Use owner/repo.
  • App Store listings — for mobile apps whose release notes live in the store.
  • Hosted CHANGELOG documents — a raw CHANGELOG.md served at a URL (not in GitHub).

Map each finding to a locator key:

KeyUse it forNotes
urlThe page a person reads — changelog / updates / what's-newThe canonical human page. https:// only.
feedThe RSS/Atom feed machines followMay be third-party-hosted (Canny, Beamer…). https:// only.
githubA repo whose Releases are the record"owner/repo". In a repo file only, "self" = this repo.
appstoreAn App Store listing URLhttps:// only.
fileA raw changelog document at a URL (hosted CHANGELOG.md)https:// only.
publish"push" — the owner publishes this changelog; the registry does not poll itRequires github and path. See below.
pathChangelog file or glob inside that repoOnly with publish: "push". Relative, no ...

One releases[] entry can combine keys when they describe the same source — most commonly url + feed (the page a human reads and the feed a machine follows for it). Add "title" to name a location, and "canonical": true on the single primary entry when several locations describe one product/repo scope (at most one canonical per scope).

source.url is for humans. If a URL is purely a machine endpoint nobody would visit, it belongs in feed/file, not url.

Direct publishing (publish: "push")

Use this when the changelog lives in git and the owner will push it in with the publish-changelog GitHub Action, instead of the registry polling a feed or a page. It uses the same releases[] entry as every other locator, with github naming the repo:

{
  "github": "acme/docs",
  "path": "changelog/**/*.mdx",
  "publish": "push"
}
  • github is "owner/repo", or "self" in a repo file.
  • path is the file (CHANGELOG.md → Action input changelog-path) or glob (changelog/**/*.mdx → changelog-glob). Relative only.
  • A single file with no wildcard is the usual Keep-a-Changelog case; a glob is directory mode (one MDX/Markdown file per release).

Do not invent this for a changelog the registry can already fetch. It is for owners who publish on merge. The registry creates the push-fed source only after the domain's ownership claim is verified; until then the locator is recorded and left unmaterialized. POST /v1/listing/validate lists the three steps that follow: verify the domain, mint a publish token (Account → Webhooks & API), add the Action pointed at path.

A plain github entry without publish still means "poll this repo's GitHub releases". Do not set publish on a feed or a page.

Step 2b — Model products correctly (don't over-fragment)

This is where agents most often go wrong. products[] is optional, and a manifest with just a top-level releases[] is often the right answer. Only reach for products[] when the company genuinely ships multiple things that publish releases in different places. The test for whether something deserves its own product entry is simple:

Does it have its own release location? A product earns an entry only if it has a release stream distinct from the company changelog — its own repo, its own feed, its own changelog page, or its own App Store listing.

Marketing structure is not release structure. Companies segment their site into "capabilities", "solutions", or "modules" for buyers; that segmentation almost never maps to distinct release feeds. Follow where the releases are published, not how the site is organized.

Two anti-patterns to actively avoid:

  • One product per marketing capability, all pointing at the same changelog. If you find yourself writing eight products — "Analytics", "Session Replay", "Experiment", …— that all carry the identical url/feed, stop. Those aren't eight products; they're one company with one changelog. Declare it once in the top-level releases[] and drop the redundant product entries. Duplicated locators just dedup back to one source and add noise.
  • One product per language SDK. A company with client libraries in Python, JS, Swift, Kotlin, Go, … has one developer/SDK offering, not one product per language. Bundle the libraries as multiple github locators under a single product (e.g. "API & SDKs" or "SDKs"), mark the primary/most-active one canonical. If there are more than 8 repos (the per-product cap), list the most active or the monorepo — don't spill into extra products to fit them all.

When separate products are right: each has its own release location. Vercel is the model — Next.js (github: vercel/next.js), Turborepo (vercel/turborepo), the AI SDK (vercel/ai), and v0 (v0.app/changelog) each publish releases independently, so each is a real product entry with its own canonical locator.

Rule of thumb: if every product entry you're about to write shares one changelog, you want zero products and one top-level releases[]. Add products only for the pieces that break out into their own release stream.

For worked before/after examples of all three cases, see references/modeling-products.md.

Step 3 — Write the file

Start from the matching example in examples/ and adapt it:

  • examples/domain-minimal.json — the smallest useful file: version + releases[].
  • examples/domain-products.json — company identity + products[] + registry pin.
  • examples/repo.json — a repo-root file with a product binding and github: "self".

Rules to honor (the validator in Step 4 enforces all of these):

  • "version": 2 is required. v1 files are no longer read. $schema (https://releases.sh/schemas/releases.json) is optional but recommended — it gives editors autocomplete and validation.
  • Every releases[] entry needs ≥1 locator (url/feed/github/appstore/file). publish: "push" is a mode on a github locator and also requires path.
  • url/feed/appstore/file/avatar must be https://. (website/docs/support on a product may be any valid URL.)
  • Limits: ≤ 24 products, ≤ 8 locations per product, and ≤ 32 locations per file total (top-level releases[] plus every product's releases[] combined).
  • Don't over-declare identity. name, description, category, tags, avatar, social shape how the org appears but are all optional — the locations are the point. Fill them only from facts you can confirm.
  • Domain scope is flat — no org wrapper; the root object is the org. Only a domain file may use products[], name, avatar, tags. Only a repo file may use product (singular) and github: "self".
  • Products carry a fixed, small set of fields and nothing else — the schema is strict and rejects unknown keys. A product may have: name (required), slug, kind, category, description, website, docs, support, social, tags, archived, releases. Put company-wide tags at the root; use a product's tags only for labels specific to that product. Any key outside this set (or the org-only fields like products/avatar) fails validation.
  • Taxonomy is advisory. category, kind, tags are suggestions; unrecognized values are ignored by the registry, never an error. Don't agonize over exact values.

Pinning (optional). If the org/product is already in a registry and you want the file bound to exact records instead of matched by URL, add a registries block. IDs live on the org/product pages. The block is namespaced so other registries can coexist:

"registries": { "releases.sh": { "org": "org_abc123" } }

(In a repo file, use "product": "prd_…" instead.) Unknown registries/keys are ignored.

Step 4 — Validate locally

Run the bundled zero-dependency validator (Node 18+ or Bun, no install) — it checks the full contract, including rules the plain JSON Schema can't express (at-least-one-locator, at-most-one-canonical, the 32-total cap):

node <skill-dir>/scripts/validate.mjs path/to/releases.json

It auto-detects domain vs repo scope; pass --scope domain|repo to force it. A ✓ with exit code 0 means valid; a ✗ lists every problem with a JSON path. Fix and re-run until it passes. The canonical schema (kept in sync at https://releases.sh/schemas/releases.json, snapshot at references/schema.json) is the tie-breaker if anything is ambiguous.

Also confirm the file is syntactically clean JSON and, if you added $schema, that it points at https://releases.sh/schemas/releases.json.

Step 5 — Publish and confirm it's live

If you don't have the repo (the common "here's our website" case), you can't publish it for them — hand back the finished file and tell them exactly where it goes:

  • Save the file where they can grab it and tell them its target URL: https://{domain}/.well-known/releases.json.
  • Explain the one requirement: it must be served at that exact path, over HTTPS, with Content-Type: application/json and no auth wall or redirect. How they do that depends on their host (a public/.well-known/ directory for most static/Next.js sites, an app route, or their CDN's static-file rules — the same mechanism that serves their robots.txt).
  • Then Step 2 of the confirmation below is something they run after deploying.

If you do have the repo, write it into place:

  • Domain scope: wherever that project serves /.well-known/ — e.g. Next.js public/.well-known/releases.json, a static site's public root, or an app route serving that exact path. Match how it already serves robots.txt.
  • Repo scope: commit releases.json at the repository root.

After it's deployed and publicly reachable, confirm it end-to-end:

  1. Fetch the live URL and check it returns HTTP 200 with valid JSON (no auth wall, no HTML redirect):
    curl -fsSL https://{domain}/.well-known/releases.json | node <skill-dir>/scripts/validate.mjs /dev/stdin
  2. Optional — ask Releases Index to validate the live domain (no account needed; reads the file the same way the daily sweep will):
    curl -fsS -X POST https://api.releases.sh/v1/listing/validate \
      -H 'content-type: application/json' \
      -d '{"domain":"{domain}"}'
    The response previews the parsed identity, per-product location counts, and how each locator will be tiered. A publish: "push" locator comes back as kind: "push" with setupSteps (verify the domain, mint a publish token, add the Action). This is read-only — it does not create a listing.

Then stop. There's nothing else to run: the registry re-reads the file on a regular sweep, so future edits are picked up automatically. Feeds/GitHub/App Store locations go live after a quick automated check; plain web pages are reviewed by a person before any crawling starts. The file is fill-if-empty and never overwrites curator decisions, so it's safe to leave in place.

Updating an existing manifest

  1. Read the current file and run it through scripts/validate.mjs first to see where it stands (it may predate a rule).
  2. Re-verify that every already-declared location still responds; drop dead ones.
  3. Add newly-found locations, keeping within the caps and the one-canonical rule.
  4. If it's still a v1 file ("version": 1 or profile-only fields like a nested org), rewrite it to v2 — v1 is no longer read. Keep only what maps to the v2 shape above.
  5. Validate again, then redeploy. No re-submission step is needed.

Reference

  • references/modeling-products.md — worked good/bad examples of the product-modeling rules in Step 2b (capability sprawl, one-product-per-SDK, and when separate products are right). Read it when a company has many offerings and you're unsure how to split them.
  • references/schema.json — the bundled JSON Schema snapshot (canonical: https://releases.sh/schemas/releases.json).
  • examples/ — minimal domain, full domain-with-products, and repo-root files.
  • Owner-facing prose docs: https://releases.sh/docs/listing (fuller narrative + a copy-paste agent prompt; this skill is the operational version of that guidance).
  • To get an org into the registry in the first place (if it isn't listed yet), the owner can submit a changelog URL at https://releases.sh/submit, or the live-domain POST /v1/listing/validate → activate lane can create a listing from a published file.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.