Back to Hooks

AppVersion Drift Check

Stop

Warns when package.json has drifted from appversion.json once Claude finishes a turn. The Claude Code counterpart to AppVersion's git pre-push hook — it reports drift and tells you which command repairs it, but never writes, blocks, or bumps on its own.

semverversioningreleasegitautomation
By Amirreza Jolani
5Updated 1 week agoJavaScriptMIT

Hook Script

#!/bin/bash
# AppVersion Drift Check
#
# Stop hook: after Claude finishes a turn, verify that package.json (and any
# other configured JSON file) still matches the version in appversion.json.
#
# AppVersion ships its own enforcement as a git pre-push hook via
# "appversion install-hook". That one blocks a drifted push. This is the
# Claude Code counterpart: it surfaces drift much earlier, while you are still
# in the session, and only ever warns.

# Only relevant in a project that tracks its version this way. No appversion.json
# means there is nothing to compare against, so stay silent.
[ -f appversion.json ] || exit 0

# Locate the skill's helper script. Marketplace installs land under
# ~/.claude/plugins; "npx skills add" and manual symlinks land under
# ~/.claude/skills; a repo vendoring the skill has it in ./skills.
SCRIPT=""
for CANDIDATE in \
  "./skills/appversion/scripts/appversion.js" \
  "$HOME/.claude/skills/appversion/scripts/appversion.js" \
  "$HOME/.claude/plugins/appversion-skill/skills/appversion/scripts/appversion.js"; do
  if [ -f "$CANDIDATE" ]; then
    SCRIPT="$CANDIDATE"
    break
  fi
done

# Marketplace layouts nest the plugin directory, so fall back to a bounded search
if [ -z "$SCRIPT" ] && [ -d "$HOME/.claude/plugins" ]; then
  SCRIPT=$(find "$HOME/.claude/plugins" -maxdepth 6 \
    -path "*appversion/scripts/appversion.js" -print -quit 2>/dev/null)
fi

# Skill not installed here. Nothing to report, and nagging about a missing
# optional tool would be noise on every single turn.
if [ -z "$SCRIPT" ] || [ ! -f "$SCRIPT" ]; then
  exit 0
fi

# "check" is read-only. It exits 0 when in sync (or when appversion.json is
# absent) and non-zero only on genuine drift, naming each offending file.
if OUTPUT=$(node "$SCRIPT" check --path . 2>&1); then
  exit 0
fi

echo "AppVersion: version drift detected"
echo ""
echo "$OUTPUT"
echo ""
echo "Repair it with:   node $SCRIPT sync --path ."
echo "Or cut the next version:   node $SCRIPT bump --auto --path ."

# Warn, never block. A Stop hook that exits non-zero interrupts the session,
# and stale version metadata is not worth derailing the work in progress.
exit 0

Settings Configuration

{
  "hooks": {
    "Stop": [
      {
        "command": "./hooks/appversion-drift-check.sh"
      }
    ]
  }
}

How to use

  1. Create a hooks directory in your project: mkdir hooks
  2. Save the hook script as hooks/appversion-drift-check.sh
  3. Make it executable: chmod +x hooks/appversion-drift-check.sh
  4. Add the configuration to your Claude Code settings
  5. Restart Claude Code to apply changes

appversion

An agent skill that turns "cut a release" into a guided, reviewable flow — and stops two specific mistakes: forgetting to update package.json, and guessing whether a change is major, minor, or patch.

It analyzes the commits since your last version, recommends a SemVer bump with reasoning, applies it to appversion.json + package.json (and any other configured file), updates the changelog, and creates the git tag + GitHub Release. Optional read-only enrichment from Jira, Plane, Shortcut, ClickUp, or Linear.

One source of truth — skills/appversion/ — with thin per-agent adapters at the repo root, so the same skill runs under Claude Code, Gemini CLI, Codex, Cursor, opencode, and GitHub Copilot.

Installation

Installation differs by harness. If you use more than one, install it separately for each.

Claude Code

Plugin marketplace (recommended — gets the skill and the /appversion:* commands). These are slash commands: run them inside an interactive claude session, not in your shell.

/plugin marketplace add AmirrezaJolani/appversion-skill
/plugin install appversion@appversion-skill

skills CLI — installs just the skill, no commands. This one is a shell command:

npx skills add AmirrezaJolani/appversion-skill --skill appversion

Manual symlink — no plugin system, no network:

git clone https://github.com/AmirrezaJolani/appversion-skill.git
ln -s "$PWD/appversion-skill/skills/appversion" ~/.claude/skills/appversion

Verify it worked by asking Claude to "bump the version" in a project, or by running /appversion:package if you installed via the marketplace.

Other agents

AgentEntry point
Gemini CLIgemini-extension.json + GEMINI.md (imports the SKILL)
Codex.codex-plugin/plugin.json
Cursor.cursor-plugin/plugin.json
opencode.opencode/INSTALL.md
GitHub Copilot.github/copilot-instructions.md

All of them point at the same skills/appversion/. See AGENTS.md.

Slash commands

Installed as a plugin, the skill exposes commands namespaced by the system they act on — so the work is organized by application:

CommandActs on
/appversion:packageThe version files — appversion.json, package.json, configured JSON + badges. Bump, --auto, check, sync, install-hook
/appversion:githubGit tag + GitHub Release
/appversion:jiraJira enrichment — config, JIRA_EMAIL/JIRA_API_TOKEN, PROJ-123 IDs
/appversion:linearLinear enrichment — LINEAR_API_KEY, team-key IDs
/appversion:planePlane enrichment — PLANE_API_TOKEN, host + workspace
/appversion:shortcutShortcut enrichment — SHORTCUT_API_TOKEN, sc-1234 IDs
/appversion:clickupClickUp enrichment — CLICKUP_API_TOKEN, CU-… or custom IDs
/appversion:releaseThe whole guided flow: analyze → recommend → bump → changelog → tag → Release

Each tracker command carries that provider's own config shape, environment variables, and ticket-ID format, so setup is copy-paste rather than guesswork.

Requirements

  • Node.js ≥ 18 (uses node:test and global fetch) — no third-party dependencies
  • git; gh (GitHub CLI) for the Release step

Use it

Ask your agent to cut a release ("bump the version", "release this"). It will analyze your commits and show an itemized recommendation before changing anything:

Since v1.2.0 — 6 commits across 3 PRs/branches:

  feat/PROJ-142  CSV export            → minor   new capability, backward compatible
  feat/APP-88    Bulk user import      → minor   new capability, backward compatible
  fix/PROJ-151   Pagination off-by-one → patch   backward-compatible bug fix

  Tally: 2 minor-level features + 1 patch-level fix
  → Recommended bump: MINOR (highest level wins)   → v1.2.0 → v1.3.0
  Proceed?

Commands

Everything is a plain CLI, so it works by hand, in CI, or driven by an agent. --path . targets the project you are versioning.

CommandWhat it does
initCreate appversion.json from the template
show [version|status|build|commit|full]Read the current version/status/build/commit
bump <major|minor|patch>Apply a bump; syncs package.json, configured JSON files, and badges
bump --autoInfer the level from Conventional Commits since the last tag, then apply it
buildIncrement build number/total and stamp the date
status <stable|rc|beta|alpha> [n]Set the release stage
checkExit non-zero if package.json/config files drift from appversion.json
syncRepair drifted files back to the current version
install-hookInstall a pre-push hook that runs check
tag [--push] [--message <m>]Create the annotated v<version> tag (won't clobber an existing one)
release [--notes <s>|--notes-file <f>]Push the tag and create the GitHub Release (needs gh)

Global flags: --path <dir>, --json, --dry-run (previews without writing anything).

Never forget again

S=skills/appversion/scripts/appversion.js

# let it decide the level from your commits, and apply it (package.json included)
node $S bump --auto --path .

# fail loudly if package.json ever drifts (great in CI)
node $S check --path .

# or enforce it locally: a forgotten sync now blocks the push
node $S install-hook --path .

bump --auto maps feat→minor, fix→patch, feat!/BREAKING→major.

Tag + GitHub Release

node $S tag --push --path .                                  # annotated v<version>, pushed
node $S release --notes-file NOTES.md --dry-run --path .     # preview the exact gh command
node $S release --notes-file NOTES.md --path .               # push tag + cut the Release

The full pipeline is scriptable: bump --auto → commit → tagrelease. Outward-facing steps (--push, release) only run when you invoke them — nothing pushes or releases on its own.

Issue tracker enrichment (optional, read-only)

Configure config.tracker in appversion.json (one object, or an array to use several at once) and ticket IDs found in your commits are resolved to real titles and links in the recommendation and changelog. Tokens come from environment variables; the skill never reads a local tracker app. See skills/appversion/references/tracker-integration.md.

Test

npm test   # == node --test

Layout

skills/appversion/     SKILL.md + scripts/ + references/   ← the skill itself
.claude-plugin/        plugin.json + marketplace.json      ← Claude Code
test/                  node:test suite

See skills/appversion/SKILL.md for the full procedure and skills/appversion/references/ for the schema and changelog format.

Contributing

Contributions are welcome — bug reports, new tracker adapters, docs fixes. The short version:

git clone https://github.com/AmirrezaJolani/appversion-skill.git
cd appversion-skill
npm test          # 60 tests, no install step — there are no dependencies

Then branch, write a failing test first, make it pass, and open a PR. Full guidelines, project layout, and the "adding a tracker" walkthrough are in CONTRIBUTING.md.

License

MIT — see LICENSE.

View source on GitHub