Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release (GitHub release from tag)

# Closes a measured gap: five tags exist (v1.2.3 … v1.2.7), one GitHub release
# does (v1.2.5). Tagging and releasing were two separate manual acts, and the
# second one kept being forgotten. Now the tag does both.
#
# This does NOT publish to npm — publish-mcp.yml owns that. This only creates the
# GitHub release that should always have accompanied the tag.

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: Existing tag to create the release for (backfill)
type: string
required: true

permissions:
contents: write # needed to create a release

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0

- name: Work out the version
id: v
run: |
TAG="${{ inputs.tag || github.ref_name }}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

# The release notes ARE the changelog section. Writing them twice means
# writing them differently — and then neither is authoritative.
- name: Cut the changelog section for this version
id: notes
run: |
VERSION="${{ steps.v.outputs.version }}"
awk -v v="$VERSION" '
$0 ~ "^## \\[" v "\\]" { seen = 1; next }
seen && /^## / { exit }
seen { print }
' CHANGELOG.md > notes.md
if [ ! -s notes.md ]; then
echo "::error::CHANGELOG.md has no non-empty section for $VERSION."
echo "The tag exists, the entry does not. Write it, then re-run this workflow"
echo "with workflow_dispatch and tag=${{ steps.v.outputs.tag }}."
exit 1
fi
echo "--- release notes ---"; cat notes.md

- name: Create the release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ steps.v.outputs.tag }}"
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists — nothing to do."
exit 0
fi
gh release create "$TAG" --title "$TAG" --notes-file notes.md --verify-tag
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,48 @@ Code, comments, docs, and this changelog are English (EN migration 2026-07-03).

## [Unreleased]

### Fixed
- npm audit gate: one high-severity advisory resolved on `main` (`4d6fb59`). **Not on npm yet** — the newest published version is 1.2.7, which predates this fix.

### Changed
- `deps`: `@playwright/test` 1.62.0 → 1.62.1 (#21).

### Documentation
- `llms-install.md` — agent-readable install guide for Cline, Claude, and stdio (#20).

## [1.2.7] - 2026-08-03

### Fixed
- `motion_audit` now surfaces the WCAG 2.2.2 warnings the validator already computed but discarded before returning (#18). The checks existed; the output dropped them.

### Changed
- Tool descriptions rewritten to say what each tool does, without the marketing register — the release was named "honest short descriptions" for that reason.
- `npm version` now syncs `plugin.json` and the README status row alongside `package.json` and `server.json` (#16), so a release can no longer leave one surface behind.
- `deps`: `brace-expansion` and `fast-uri` bumped to clear the audit gate (#17).

### Added
- CI waits for npm to actually serve a version before publishing the MCP-registry entry (`d5e77ab`) — publishing the manifest first produced a registry entry pointing at a version nobody could install.

## [1.2.6] - 2026-07-27

### Added
- Claude Code plugin bundling the MCP server plus the `motion` and `audit` skills (`f115ee8`), with the marketplace manifest (#5).
- Code of Conduct (Contributor Covenant 2.1) and the project logo (`fb8a3d3`).

### Changed
- README states plainly that compile output is vanilla GSAP JS plus CSS, and that WAAPI lowering is internal (ADR-0001/0002, #6). The earlier wording let readers infer WAAPI output.
- `deps`: MCP SDK 1.30.0 with a regenerated lockfile to unblock the audit gate (#15); `eslint` 10.6.0 → 10.7.0 (#10); `@commitlint/cli` 21.2.0 → 21.2.1 (#9).
- `ci`: `actions/checkout` 4 → 7 (#7), `actions/setup-node` 6.4.0 → 7.0.0 (#8).

## [1.2.5] - 2026-07-15

### Changed
- Package description and keywords rewritten for accessibility discoverability, plus disambiguation from the unrelated Android `MotionSpec` API and from Motion Specialties (`46d915a`, `60b965c`).
- `server.json` declares the remote endpoint; the MCP-registry workflow publishes it (`60b965c`).

### Documentation
- CHANGELOG entries for 1.2.3 and 1.2.4, which had been released without one (#4). The same gap then repeated for 1.2.5–1.2.7 — see the `prepublishOnly` gate added under `Unreleased`.

## [1.2.4] - 2026-07-12

### Changed
Expand Down
50 changes: 50 additions & 0 deletions bin/changelog-gate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env node
/**
* changelog-gate.js — refuses to publish a version that has no CHANGELOG section.
*
* WHY THIS EXISTS
* The previous gate only checked that CHANGELOG.md *existed*. It did, continuously,
* while 1.2.5, 1.2.6 and 1.2.7 shipped to npm without an entry — three releases in
* a row. A file-exists check cannot catch that; it was green the whole time.
*
* This is the cause of that gap, and it is a few lines.
*/
const fs = require("fs");

const { version } = JSON.parse(fs.readFileSync("package.json", "utf8"));

if (!fs.existsSync("CHANGELOG.md")) {
console.error("changelog-gate: CHANGELOG.md is missing.");
process.exit(1);
}

const changelog = fs.readFileSync("CHANGELOG.md", "utf8");
// Match the Keep-a-Changelog heading exactly: `## [1.2.7]` — optionally dated.
const heading = new RegExp(
`^##\\s*\\[${version.replace(/\./g, "\\.")}\\]`, "m");

if (!heading.test(changelog)) {
console.error(
`\nchangelog-gate: refusing to publish ${version}.\n\n` +
` CHANGELOG.md has no "## [${version}]" section.\n\n` +
` Add one before publishing. An entry written after the fact is written\n` +
` from the commit log; one written now is written from memory, and the\n` +
` difference shows.\n`);
process.exit(1);
}

// An empty section is not an entry.
//
// Careful with the split: `rest` begins with the heading itself, so splitting on
// /^##\s/m puts an empty string first and the section body second. Taking [0]
// reports every section as empty — including full ones. Drop the heading line
// first, then split.
const rest = changelog.slice(changelog.search(heading));
const nachUeberschrift = rest.slice(rest.indexOf("\n") + 1);
const body = nachUeberschrift.split(/^##\s/m)[0].trim();
if (!body) {
console.error(`\nchangelog-gate: the "## [${version}]" section is empty.\n`);
process.exit(1);
}

console.log(`changelog-gate: ${version} is documented (${body.split("\n").length} lines).`);
Loading