From visual-changelog
Generate a visual changelog entry for the current PR. Runs git commands and test suites to gather data, outputs structured JSON, rendered by the visual changelog viewer.
How this skill is triggered — by the user, by Claude, or both
Slash command
/visual-changelog:changelogThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate a JSON changelog entry for the current branch's changes vs the base branch.
Generate a JSON changelog entry for the current branch's changes vs the base branch.
Before gathering data, run two checks:
BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "ERROR: on $BRANCH"
exit 1
fi
If on main/master, tell the user to switch to a feature branch and stop.
git status --porcelain
If this produces output, there are uncommitted or staged changes. Ask the user which option they prefer:
Option 1: Commit and continue — Ask the user for a commit message (or default to "WIP: pre-changelog"). Run:
git add -A
git commit -m "{message}"
Then proceed with normal changelog generation.
Option 2: Continue as draft — Proceed with generation but:
"draft": true on the entryprose section: "This entry was generated from uncommitted work and may not reflect the final state of the branch."commitHash should be the current HEAD (which won't include the uncommitted changes — that's expected for drafts)Option 3: Cancel — Stop. Tell the user to commit or stash their changes first.
Present these options to the user and wait for their choice before proceeding.
Determine the project root and prepare the entries directory:
PROJECT_ROOT=$(git rev-parse --show-toplevel)
ENTRIES_DIR="$PROJECT_ROOT/visual-changelog/entries"
# Create entries directory on first run
mkdir -p "$ENTRIES_DIR"
# Create local run script if it doesn't exist
if [ ! -f "$PROJECT_ROOT/visual-changelog/run.sh" ]; then
cat > "$PROJECT_ROOT/visual-changelog/run.sh" << SCRIPT
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="\$(cd "\$(dirname "\$0")/.." && pwd)"
exec "${CLAUDE_PLUGIN_ROOT}/run.sh" "\$PROJECT_ROOT"
SCRIPT
chmod +x "$PROJECT_ROOT/visual-changelog/run.sh"
fi
$ENTRIES_DIR/{id}.json42-auth-refactor), use that as the filename: 42-auth-refactor.json. Otherwise use the full branch slug: feature-new-login.json.${CLAUDE_PLUGIN_ROOT}/skills/changelog/schema/types.ts for the exact TypeScript types.Run these bash commands to populate the JSON. Always determine base ref first (default: main).
# Base ref detection
BASE=$(git merge-base HEAD main 2>/dev/null || echo "main")
# File statuses
git diff --name-status $BASE
# Per-file line counts
git diff --numstat $BASE
# Overall stats
git diff --stat $BASE
# Commit count
git rev-list --count $BASE..HEAD
# Current state
git branch --show-current
git rev-parse --short HEAD
# Untracked files (respects .gitignore)
git ls-files --others --exclude-standard
Include untracked files in both the file-list (with status: "untracked") and heatmap. For untracked files, estimate line counts using wc -l. Do NOT include files that would be ignored by .gitignore.
Exclude lock files from the heatmap and file-list sections. These files are: package-lock.json, yarn.lock, pnpm-lock.yaml, Gemfile.lock, Cargo.lock, poetry.lock, composer.lock, go.sum.
If lock files were changed, add a KPI card to the kpi-bar section: { "label": "Lock File Changes", "value": "{total lines changed in lock files}" }. This preserves the information without polluting the visual components.
Probe for test infrastructure. Do NOT use shell globs (*.config.*) for test discovery — they fail in zsh when no matches exist. Use find instead:
# Discover test config files
find . -maxdepth 3 -name "vitest.config.*" -o -name "jest.config.*" -o -name "pytest.ini" -o -name ".rspec" 2>/dev/null
# Check package.json for test scripts or jest config
cat package.json 2>/dev/null | grep -E '"test":|"jest"' || true
Match discovered files to runners:
| Config file | Runner | Command |
|---|---|---|
vitest.config.* or vite.config.* with test | vitest | npx vitest run --reporter=json |
jest.config.* or package.json has "jest" | jest | npx jest --json |
pytest.ini or pyproject.toml with [tool.pytest] or setup.cfg with [tool:pytest] | pytest | python -m pytest --tb=short -q |
Cargo.toml with [dev-dependencies] | cargo | cargo test -- --format=json (nightly) or cargo test 2>&1 |
.rspec or spec/ directory | rspec | bundle exec rspec --format json |
go.mod + *_test.go files | go test | go test ./... -json |
For each discovered runner:
test-suite section with title being a human-readable name (e.g. "Unit Tests (vitest)")Duration units: All test runners must normalize duration to seconds before writing to JSON.
If multiple test suites exist (e.g. separate unit + integration configs), create separate test-suite sections for each.
If NO test infrastructure is found, do not include any test-suite sections.
Discover existing entries by globbing $ENTRIES_DIR/*.json (excluding manifest.json). For each file, read it and extract the date field. Sort by date descending to find the most recent entry. If entries exist:
previousEntryId in the new entrytitle, compute differences in pass/fail/skip/totalConstruct a ChangelogEntry object. Include sections in this order:
kpi-bar (always) — cards for: Lines Added, Lines Removed, Files Changed, Commits. Include deltas if previous entry exists.
prose (always) — Write 1-2 paragraphs summarizing WHAT changed and WHY. Focus on the functional impact, not file-by-file narration. Be concise.
heatmap (always) — Build from git diff --numstat + untracked files. Exclude lock files. Parse each file path into directory tree. Each node's changes = sum of lines added + removed for all files under it. Leaf nodes are individual files.
test-suite (zero or more) — One per discovered test suite. Include deltas if a matching suite existed in the previous entry.
file-list (always) — From git diff --name-status + --numstat + git ls-files --others --exclude-standard. Exclude lock files. Map statuses: A=added, M=modified, D=deleted, R=renamed; untracked files get status: "untracked". For each file, include a reason field with a 1-sentence explanation of why this file was changed. Base this on the diff content — read the actual changes to understand the purpose. Keep it under 15 words. Focus on WHAT and WHY, not HOW.
mermaid (optional) — Include ONLY if the changes touch 3+ distinct top-level directories or introduce new modules/packages. Diagram should show the high-level module relationships affected by this PR. Use graph TD or graph LR. Keep it simple — 5-15 nodes max.
If a previous entry exists:
After writing the JSON, run the bundled validator:
node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-entry.js" "$ENTRIES_DIR/{filename}"
If it prints ERRORS, fix the JSON and re-validate. Do NOT write your own validation logic.
If the project doesn't already ignore the generated manifest, add visual-changelog/entries/manifest.json to .gitignore. The manifest is generated at serve-time and should never be committed.
Tell the user:
visual-changelog/entries/{filename}./visual-changelog/run.sh
npx claudepluginhub webifyservices/visual-changelog --plugin visual-changelogGenerates human-friendly changelogs from git history, PRs, or ref ranges. Follows Keep a Changelog format and polishes commit messages.
Generates structured changelogs and release notes from git history and PRs by parsing conventional commits and classifying changes. Useful for preparing releases.
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.