openclaw-skills/changelog-generator/SKILL.md
Parse conventional commits, determine semantic version bumps, and generate structured changelogs in Keep a Changelog format. Supports monorepo changelogs, GitHub Releases integration, and separates user-facing from developer changelogs.
npx skillsauth add seaworld008/commonly-used-high-value-skills changelog-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tier: POWERFUL
Category: Engineering
Domain: Release Management / Documentation
Parse conventional commits, determine semantic version bumps, and generate structured changelogs in Keep a Changelog format. Supports monorepo changelogs, GitHub Releases integration, and separates user-facing from developer changelogs.
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| Type | Changelog section | SemVer bump |
|------|------------------|-------------|
| feat | Added | minor |
| fix | Fixed | patch |
| perf | Changed | patch |
| refactor | Changed (internal) | patch |
| docs | — (omit or include) | patch |
| chore | — (omit) | patch |
| test | — (omit) | patch |
| build | — (omit) | patch |
| ci | — (omit) | patch |
| security | Security | patch |
| deprecated | Deprecated | minor |
| remove | Removed | major (if breaking) |
| BREAKING CHANGE: footer | — (major bump) | major |
| ! after type | — (major bump) | major |
feat(auth): add OAuth2 login with Google
fix(api): correct pagination offset calculation
feat!: rename /users endpoint to /accounts (BREAKING)
perf(db): add index on users.email column
security: patch XSS vulnerability in comment renderer
docs: update API reference for v2 endpoints
#!/usr/bin/env bash
# generate-changelog.sh — generate CHANGELOG entry for the latest release
set -euo pipefail
CURRENT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null || echo "")
DATE=$(date +%Y-%m-%d)
if [ -z "$CURRENT_TAG" ]; then
echo "No tags found. Create a tag first: git tag v1.0.0"
exit 1
fi
RANGE="${PREVIOUS_TAG:+${PREVIOUS_TAG}..}${CURRENT_TAG}"
echo "Generating changelog for: $RANGE"
# Parse commits
ADDED=""
CHANGED=""
DEPRECATED=""
REMOVED=""
FIXED=""
SECURITY=""
BREAKING=""
while IFS= read -r line; do
# Skip empty lines
[ -z "$line" ] && continue
# Detect type
if [[ "$line" =~ ^feat(\([^)]+\))?\!:\ (.+)$ ]]; then
desc="${BASH_REMATCH[2]}"
BREAKING="${BREAKING}- **BREAKING** ${desc}\n"
ADDED="${ADDED}- ${desc}\n"
elif [[ "$line" =~ ^feat(\([^)]+\))?:\ (.+)$ ]]; then
ADDED="${ADDED}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^fix(\([^)]+\))?:\ (.+)$ ]]; then
FIXED="${FIXED}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^perf(\([^)]+\))?:\ (.+)$ ]]; then
CHANGED="${CHANGED}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^security(\([^)]+\))?:\ (.+)$ ]]; then
SECURITY="${SECURITY}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^deprecated(\([^)]+\))?:\ (.+)$ ]]; then
DEPRECATED="${DEPRECATED}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^remove(\([^)]+\))?:\ (.+)$ ]]; then
REMOVED="${REMOVED}- ${BASH_REMATCH[2]}\n"
elif [[ "$line" =~ ^refactor(\([^)]+\))?:\ (.+)$ ]]; then
CHANGED="${CHANGED}- ${BASH_REMATCH[2]}\n"
fi
done < <(git log "${RANGE}" --pretty=format:"%s" --no-merges)
# Build output
OUTPUT="## [${CURRENT_TAG}] - ${DATE}\n\n"
[ -n "$BREAKING" ] && OUTPUT="${OUTPUT}### ⚠ BREAKING CHANGES\n${BREAKING}\n"
[ -n "$SECURITY" ] && OUTPUT="${OUTPUT}### Security\n${SECURITY}\n"
[ -n "$ADDED" ] && OUTPUT="${OUTPUT}### Added\n${ADDED}\n"
[ -n "$CHANGED" ] && OUTPUT="${OUTPUT}### Changed\n${CHANGED}\n"
[ -n "$DEPRECATED" ] && OUTPUT="${OUTPUT}### Deprecated\n${DEPRECATED}\n"
[ -n "$REMOVED" ] && OUTPUT="${OUTPUT}### Removed\n${REMOVED}\n"
[ -n "$FIXED" ] && OUTPUT="${OUTPUT}### Fixed\n${FIXED}\n"
printf "$OUTPUT"
# Optionally prepend to CHANGELOG.md
if [ "${1:-}" = "--write" ]; then
TEMP=$(mktemp)
printf "$OUTPUT" > "$TEMP"
if [ -f CHANGELOG.md ]; then
# Insert after the first line (# Changelog header)
head -n 1 CHANGELOG.md >> "$TEMP"
echo "" >> "$TEMP"
printf "$OUTPUT" >> "$TEMP"
tail -n +2 CHANGELOG.md >> "$TEMP"
else
echo "# Changelog" > CHANGELOG.md
echo "All notable changes to this project will be documented here." >> CHANGELOG.md
echo "" >> CHANGELOG.md
cat "$TEMP" >> CHANGELOG.md
fi
mv "$TEMP" CHANGELOG.md
echo "✅ CHANGELOG.md updated"
fi
#!/usr/bin/env python3
"""generate_changelog.py — parse conventional commits and emit Keep a Changelog"""
import subprocess
import re
import sys
from datetime import date
from dataclasses import dataclass, field
from typing import Optional
COMMIT_RE = re.compile(
r"^(?P<type>feat|fix|perf|refactor|docs|test|chore|build|ci|security|deprecated|remove)"
r"(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?: (?P<desc>.+)$"
)
SECTION_MAP = {
"feat": "Added",
"fix": "Fixed",
"perf": "Changed",
"refactor": "Changed",
"security": "Security",
"deprecated": "Deprecated",
"remove": "Removed",
}
@dataclass
class Commit:
type: str
scope: Optional[str]
breaking: bool
desc: str
body: str = ""
sha: str = ""
@dataclass
class ChangelogEntry:
version: str
date: str
added: list[str] = field(default_factory=list)
changed: list[str] = field(default_factory=list)
deprecated: list[str] = field(default_factory=list)
removed: list[str] = field(default_factory=list)
fixed: list[str] = field(default_factory=list)
security: list[str] = field(default_factory=list)
breaking: list[str] = field(default_factory=list)
def get_commits(from_tag: str, to_tag: str) -> list[Commit]:
range_spec = f"{from_tag}..{to_tag}" if from_tag else to_tag
result = subprocess.run(
["git", "log", range_spec, "--pretty=format:%H|%s|%b", "--no-merges"],
capture_output=True, text=True, check=True
)
commits = []
for line in result.stdout.splitlines():
if not line.strip():
continue
parts = line.split("|", 2)
sha = parts[0] if len(parts) > 0 else ""
subject = parts[1] if len(parts) > 1 else ""
body = parts[2] if len(parts) > 2 else ""
m = COMMIT_RE.match(subject)
if m:
commits.append(Commit(
type=m.group("type"),
scope=m.group("scope"),
breaking=m.group("breaking") == "!" or "BREAKING CHANGE" in body,
desc=m.group("desc"),
body=body,
sha=sha[:8],
))
return commits
def determine_bump(commits: list[Commit], current_version: str) -> str:
parts = current_version.lstrip("v").split(".")
major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
has_breaking = any(c.breaking for c in commits)
has_feat = any(c.type == "feat" for c in commits)
if has_breaking:
return f"v{major + 1}.0.0"
elif has_feat:
return f"v{major}.{minor + 1}.0"
else:
return f"v{major}.{minor}.{patch + 1}"
def build_entry(commits: list[Commit], version: str) -> ChangelogEntry:
entry = ChangelogEntry(version=version, date=date.today().isoformat())
for c in commits:
scope_prefix = f"**{c.scope}**: " if c.scope else ""
desc = f"{scope_prefix}{c.desc}"
if c.breaking:
entry.breaking.append(desc)
section = SECTION_MAP.get(c.type)
if section == "Added":
entry.added.append(desc)
elif section == "Fixed":
entry.fixed.append(desc)
elif section == "Changed":
entry.changed.append(desc)
elif section == "Security":
entry.security.append(desc)
elif section == "Deprecated":
entry.deprecated.append(desc)
elif section == "Removed":
entry.removed.append(desc)
return entry
def render_entry(entry: ChangelogEntry) -> str:
lines = [f"## [{entry.version}] - {entry.date}", ""]
sections = [
("⚠ BREAKING CHANGES", entry.breaking),
("Security", entry.security),
("Added", entry.added),
("Changed", entry.changed),
("Deprecated", entry.deprecated),
("Removed", entry.removed),
("Fixed", entry.fixed),
]
for title, items in sections:
if items:
lines.append(f"### {title}")
for item in items:
lines.append(f"- {item}")
lines.append("")
return "\n".join(lines)
if __name__ == "__main__":
tags = subprocess.run(
["git", "tag", "--sort=-version:refname"],
capture_output=True, text=True
).stdout.splitlines()
current_tag = tags[0] if tags else ""
previous_tag = tags[1] if len(tags) > 1 else ""
if not current_tag:
print("No tags found. Create a tag first.")
sys.exit(1)
commits = get_commits(previous_tag, current_tag)
entry = build_entry(commits, current_tag)
print(render_entry(entry))
For repos with multiple packages (e.g., pnpm workspaces, nx, turborepo):
# packages/api/CHANGELOG.md — API package only
# packages/ui/CHANGELOG.md — UI package only
# CHANGELOG.md — Root (affects all)
# Filter commits by package path
git log v1.2.0..v1.3.0 --pretty=format:"%s" -- packages/api/
With Changesets (recommended for monorepos):
# Install changesets
pnpm add -D @changesets/cli
pnpm changeset init
# Developer workflow: create a changeset for each PR
pnpm changeset
# → prompts for: which packages changed, bump type, description
# On release branch: version all packages
pnpm changeset version
# Publish and create GitHub release
pnpm changeset publish
#!/usr/bin/env bash
# create-github-release.sh
set -euo pipefail
VERSION=$(git describe --tags --abbrev=0)
NOTES=$(python3 generate_changelog.py)
# Using GitHub CLI
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes "$NOTES" \
--verify-tag
# Or via API
curl -s -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${REPO}/releases" \
-d "$(jq -n \
--arg tag "$VERSION" \
--arg name "Release $VERSION" \
--arg body "$NOTES" \
'{tag_name: $tag, name: $name, body: $body, draft: false}')"
## Version 2.3.0 — March 1, 2026
**New:** You can now log in with Google.
**Fixed:** Dashboard no longer freezes when loading large datasets.
**Improved:** Search results load 3x faster.
## [2.3.0] - 2026-03-01
### Added
- **auth**: OAuth2 Google login via passport-google (#234)
- **api**: GraphQL subscriptions for real-time updates (#241)
### Fixed
- **dashboard**: resolve infinite re-render on large datasets (closes #228)
### Performance
- **search**: switch from Elasticsearch to Typesense, P99 latency -67% (#239)
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git log
- name: Generate changelog
id: changelog
run: |
NOTES=$(python3 scripts/generate_changelog.py)
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body: ${{ steps.changelog.outputs.notes }}
generate_release_notes: false
--depth=1 in CI — git log needs full history; use fetch-depth: 0--no-mergescommitlint in CI[2.3.0]: https://github.com/org/repo/compare/v2.2.0...v2.3.0development
飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。当用户给出 doubao.com 的 /wiki/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。
tools
飞书画板:查询和编辑飞书云文档中的画板。支持导出画板为预览图片、导出原始节点结构、使用 DSL(转成 OpenAPI 格式)、PlantUML/Mermaid 格式更新画板内容。 当用户需要查看画板内容、导出画板图片、编辑画板,或是需要可视化表达架构、流程、组织关系、时间线、因果、对比等结构化信息时使用此 skill,无论是否提及\"画板\"。 ⚠️ 原 `lark-whiteboard-cli` skill 已合并至本 skill,若 skill 列表中同时存在 `lark-whiteboard-cli`,请忽略它,统一使用本 skill(`lark-whiteboard`),并提示用户运行 `npx skills remove lark-whiteboard-cli -g` 删除旧 skill。
testing
飞书视频会议:搜索历史会议、查询会议纪要产物(总结、待办、章节、逐字稿)、查询会议参会人快照。1. 查询已经结束的会议数量或详情时使用本技能(如历史日期|昨天|上周|今天已经开过的会议等场景),查询未开始的会议日程使用 lark-calendar 技能。2. 支持通过关键词、时间范围、组织者、参与者、会议室等筛选条件搜索会议。3. 获取或整理会议纪要、逐字稿、录制产物时使用本技能。4. 查询“谁参加过某会议”“参会人列表”等参会人快照信息用 vc meeting get --with-participants(任意时点可查,含已结束会议)。注意:**Agent 真实入会/离会、感知正在进行中会议的实时事件**请使用 lark-vc-agent 技能,本技能不覆盖写操作和会中事件流。
data-ai
飞书会议机器人入会、离会和会中事件读取。