diff --git a/skills/generate-changelog/SKILL.md b/skills/generate-changelog/SKILL.md new file mode 100644 index 0000000..58cf5f3 --- /dev/null +++ b/skills/generate-changelog/SKILL.md @@ -0,0 +1,115 @@ +--- +name: generate-changelog +description: 根据当前项目 package.json 版本与 Git 提交记录生成中文版本日志。用于用户提出“生成版本日志”“生成 changelog”“发布新版本并生成更新记录”等请求时,自动读取当前版本号、定位上一版本、汇总版本区间 commit,并更新 docs/changelogs_cn.json(新增版本、生成中文 summary、按类型归类变更)。 +--- + +# generate-changelog + +按以下流程更新 `docs/changelogs_cn.json`。 + +## 1. 读取版本与日志现状 + +1. 读取 `package.json` 中的 `version` 作为 `currentVersion`。 +2. 读取 `docs/changelogs_cn.json`,确认是否已存在 `currentVersion`。 +3. 若已存在:停止新增流程,改为“就地更新该版本内容”。 +4. 计算 `previousVersion`: + - 若 `currentVersion` 已存在于 changelog:取它的下一条记录版本号。 + - 若 `currentVersion` 不存在于 changelog:取第一条记录版本号。 +5. 若无法读取 `previousVersion`,报错并停止。 + +## 2. 确定 commit 区间 + +优先按以下规则确定区间: + +1. 若存在 tag `v{currentVersion}`:使用 `v{previousVersion}..v{currentVersion}`。 +2. 若不存在 tag `v{currentVersion}`:使用 `v{previousVersion}..HEAD`。 +3. 若 `v{previousVersion}` 不存在:回退为从首个提交到 `HEAD`,并在结果中明确标注“缺少上一版本 tag,采用全量范围”。 + +使用命令: + +```bash +git log --no-merges --pretty=format:'%h%x09%s' +``` + +必要时读取正文: + +```bash +git show -s --format='%B' +``` + +## 3. 归类与清洗 + +将 commit 按以下类型归类到 `changes[].type`: + +- `feat` +- `fix` +- `refactor` +- `perf` +- `style` +- `docs` +- `test` +- `build` +- `ci` +- `chore` +- `revert` +- `other`(无法识别前缀时) + +归类规则: + +1. 优先从 Conventional Commit 前缀识别(如 `feat:`, `fix(scope):`)。 +2. 无前缀时,根据语义判断;无法判断放入 `other`。 +3. 忽略纯发布噪音提交(如仅版本号、仅锁文件且无业务影响的提交),除非用户明确要求保留。 +4. 将每条 item 改写为简洁中文短句,避免直接照搬英文标题。 + +## 4. 生成 summary + +为本版本生成 1 句中文 summary: + +1. 优先覆盖用户可感知变化(功能、修复、性能、体验)。 +2. 保持与历史风格一致(简洁、非营销语气)。 +3. 长度控制在 18-60 字。 + +## 5. 更新 JSON 文件 + +目标文件:`docs/changelogs_cn.json` + +更新规则: + +1. 构造新对象: + - `version`: `currentVersion` + - `date`: 当前日期(`YYYY-MM-DD`) + - `summary`: 新生成摘要 + - `changes`: 分类后的数组(每个元素为 `{ "type": string, "items": string[] }`) +2. 若不存在当前版本,插入到数组首位。 +3. 若已存在当前版本,替换该版本对象,但保持其在数组中的原位置。 +4. 保持 JSON 可读格式(2 空格缩进,UTF-8,无注释)。 +5. 写入后必须执行格式化,优先使用项目 Prettier,确保与“手动保存”风格一致: + +```bash +npx prettier --write docs/changelogs_cn.json +``` + +6. 若环境没有 Prettier,回退为 `JSON.stringify(..., null, 2)` 的最小格式保证,并在输出中明确提示“未执行 Prettier 格式化”。 + +## 6. 自检 + +写入后执行以下检查: + +1. JSON 语法合法。 +2. `version` 无重复。 +3. `changes` 中每个 `items` 至少 1 条。 +4. 与 commit 区间相比,无明显遗漏的关键变更(特别是 `feat` / `fix`)。 + +若自检失败,修复后再输出结果。 + +补充检查: + +5. 确认 `docs/changelogs_cn.json` 已经过 Prettier(若可用)。 + +## 参考文件 + +需要查看项目格式细节时,读取: + +- `references/changelog-format.md` +- `scripts/extract_commits_for_changelog.sh` +- `scripts/format_changelog.sh` diff --git a/skills/generate-changelog/agents/openai.yaml b/skills/generate-changelog/agents/openai.yaml new file mode 100644 index 0000000..ed72a0e --- /dev/null +++ b/skills/generate-changelog/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "生成版本日志" + short_description: "按版本区间提取提交并自动生成项目中文 changelog 文件" + default_prompt: "Use $generate-changelog to read package.json version, collect commits since the previous version, and update docs/changelogs_cn.json." diff --git a/skills/generate-changelog/references/changelog-format.md b/skills/generate-changelog/references/changelog-format.md new file mode 100644 index 0000000..9eb95b2 --- /dev/null +++ b/skills/generate-changelog/references/changelog-format.md @@ -0,0 +1,28 @@ +# changelog-format + +本项目中文更新日志文件:`docs/changelogs_cn.json` + +## 顶层结构 + +- 顶层为数组 +- 每个元素代表一个版本对象 +- 版本按时间倒序(最新在前) + +## 版本对象字段 + +- `version`: 版本号字符串(示例:`0.9.3`) +- `date`: 发布日期(`YYYY-MM-DD`) +- `summary`: 中文一句话摘要 +- `changes`: 分类变更数组 + +## changes 子项结构 + +- `type`: 分类类型(如 `feat`、`fix`、`refactor`、`chore`) +- `items`: 该分类下的中文要点数组 + +## 风格约束 + +- 保持简洁叙述,避免营销语。 +- `summary` 聚焦用户可感知变化。 +- `items` 使用短句,尽量一条表达一个改动。 +- 若某分类无条目,不写该分类。 diff --git a/skills/generate-changelog/scripts/extract_commits_for_changelog.sh b/skills/generate-changelog/scripts/extract_commits_for_changelog.sh new file mode 100755 index 0000000..0ea81ef --- /dev/null +++ b/skills/generate-changelog/scripts/extract_commits_for_changelog.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 读取项目根目录(默认取当前目录,可通过第一个参数覆盖) +ROOT_DIR="${1:-$(pwd)}" + +# 读取 package.json 中的当前版本号 +CURRENT_VERSION="$(node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(p.version||'');" "$ROOT_DIR/package.json")" +if [[ -z "$CURRENT_VERSION" ]]; then + echo "错误: 无法从 package.json 读取 version" >&2 + exit 1 +fi + +# 基于 changelog 计算上一版本号: +# - 若当前版本已存在于 changelog 中,取其后一条作为上一版本 +# - 若当前版本不存在,取首条作为上一版本 +CHANGELOG_META="$( + node -e " + const fs=require('fs'); + const arr=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); + const current=process.argv[2]; + const idx=arr.findIndex(i=>i?.version===current); + const exists=idx!==-1; + const prev=exists ? (arr[idx+1]?.version||'') : (arr?.[0]?.version||''); + process.stdout.write(JSON.stringify({exists, prev})); + " "$ROOT_DIR/docs/changelogs_cn.json" "$CURRENT_VERSION" +)" + +PREVIOUS_VERSION="$(node -e "const m=JSON.parse(process.argv[1]);process.stdout.write(m.prev||'');" "$CHANGELOG_META")" +CURRENT_EXISTS="$(node -e "const m=JSON.parse(process.argv[1]);process.stdout.write(String(Boolean(m.exists)));" "$CHANGELOG_META")" +if [[ -z "$PREVIOUS_VERSION" ]]; then + echo "错误: 无法从 docs/changelogs_cn.json 读取上一版本" >&2 + exit 1 +fi + +CURRENT_TAG="v${CURRENT_VERSION}" +PREVIOUS_TAG="v${PREVIOUS_VERSION}" +RANGE="" + +# 优先使用当前版本 tag;若不存在,则使用上一版本到 HEAD 的区间 +if git -C "$ROOT_DIR" rev-parse --verify "$CURRENT_TAG" >/dev/null 2>&1; then + RANGE="${PREVIOUS_TAG}..${CURRENT_TAG}" +else + RANGE="${PREVIOUS_TAG}..HEAD" +fi + +# 若上一版本 tag 不存在,回退为全量提交并提示原因 +if ! git -C "$ROOT_DIR" rev-parse --verify "$PREVIOUS_TAG" >/dev/null 2>&1; then + echo "WARN: 缺少上一版本 tag(${PREVIOUS_TAG}),回退为全量范围" >&2 + RANGE="$(git -C "$ROOT_DIR" rev-list --max-parents=0 HEAD | tail -n 1)..HEAD" +fi + +# 输出版本和区间,便于技能调用方记录上下文 +printf 'current_version=%s\n' "$CURRENT_VERSION" +printf 'previous_version=%s\n' "$PREVIOUS_VERSION" +printf 'current_exists=%s\n' "$CURRENT_EXISTS" +printf 'range=%s\n' "$RANGE" +echo "commits:" + +# 输出提交哈希与标题,使用制表符分隔便于后续解析 +# shellcheck disable=SC2016 +git -C "$ROOT_DIR" log --no-merges --pretty=format:'%h%x09%s' "$RANGE" diff --git a/skills/generate-changelog/scripts/format_changelog.sh b/skills/generate-changelog/scripts/format_changelog.sh new file mode 100755 index 0000000..32b8fd7 --- /dev/null +++ b/skills/generate-changelog/scripts/format_changelog.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 读取项目根目录(默认当前目录,可通过参数覆盖) +ROOT_DIR="${1:-$(pwd)}" +TARGET_FILE="$ROOT_DIR/docs/changelogs_cn.json" + +if [[ ! -f "$TARGET_FILE" ]]; then + echo "错误: 未找到 $TARGET_FILE" >&2 + exit 1 +fi + +# 优先使用 Prettier,保证与本地保存时格式一致 +if command -v npx >/dev/null 2>&1; then + npx --yes prettier --write "$TARGET_FILE" >/dev/null + echo "formatted_by=prettier" + exit 0 +fi + +# 兜底:若无 npx,则仅做最小 JSON 格式化保证 +node -e "const fs=require('fs');const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,'utf8'));fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');" "$TARGET_FILE" +echo "formatted_by=json_stringify_fallback" diff --git a/skills/sync-changelog/SKILL.md b/skills/sync-changelog/SKILL.md new file mode 100644 index 0000000..ceae525 --- /dev/null +++ b/skills/sync-changelog/SKILL.md @@ -0,0 +1,87 @@ +--- +name: sync-changelog +description: 将 docs/changelogs_cn.json 的当前版本日志生成适合英文母语者阅读的英文版本,更新 docs/changelogs_en.json,并在当前项目创建 release 提交;随后同步中英文 changelog 到同级仓库 ../chatlab.fun 并创建文档提交。用于用户提出“同步版本日志”“生成并同步 changelog”“发布前同步中英文日志”等请求。仅创建 commit,不执行 push。 +--- + +# sync-changelog + +按以下流程执行,任何一步失败都立即停止,不做 push。 + +## 1. 前置检查(必须) + +1. 当前仓库必须工作区干净: + - 执行 `git status --porcelain`。 + - 允许白名单改动:`package.json`、`docs/changelogs_cn.json`(这两个文件可作为本次任务前置输入)。 + - 若存在白名单外改动(包含已暂存/未暂存/未跟踪),立即退出并提示用户手动处理。 +2. 当前仓库必须在 `main`: + - 若不在 `main`,仅在工作区干净时执行 `git checkout main`。 +3. 目标仓库固定为 `../chatlab.fun`。 +4. 目标仓库也必须满足: + - 工作区干净,否则退出。 + - 位于 `main`,否则在干净前提下切换。 + +可复用脚本:`scripts/preflight_main_clean.sh` +当前仓库建议调用: + +```bash +scripts/preflight_main_clean.sh . "package.json,docs/changelogs_cn.json" +``` + +`chatlab.fun` 仓库仍需严格干净(不传白名单参数)。 + +## 2. 读取当前版本并校验文件 + +1. 从 `docs/changelogs_cn.json` 读取第一个对象作为当前版本。 +2. 读取版本号 `version`(例如 `0.9.6`)。 +3. 检查 `docs/changelogs_en.json` 是否存在: + - 不存在则立即退出,不允许自动创建。 + +## 3. 生成英文 changelog(AI 翻译) + +1. 将当前版本中文内容转写为英文,要求: + - 不做逐字直译。 + - 使用自然、简洁、适合英文母语用户的 release notes 语气。 + - 保持原始结构:`version/date/summary/changes(type/items)`。 +2. 更新 `docs/changelogs_en.json`: + - 若已存在该版本,替换该版本对象。 + - 若不存在,插入到数组首位。 +3. 写入后执行格式化(若项目有 Prettier,优先使用 Prettier)。 + +## 4. 在当前仓库创建发布提交 + +1. 提交文件必须包含: + - `docs/changelogs_cn.json` + - `docs/changelogs_en.json` +2. commit message:`release: v`(示例:`release: v0.9.6`)。 +3. 仅创建 commit,不 push。 + +可复用脚本:`scripts/commit_release_changelogs.sh` + +## 5. 同步到 chatlab.fun 并提交 + +1. 从当前仓库复制: + - `docs/changelogs_cn.json` -> `../chatlab.fun/docs/public/cn/changelogs.json` + - `docs/changelogs_en.json` -> `../chatlab.fun/docs/public/en/changelogs.json` +2. 目标路径必须存在;不存在则报错退出,不自动创建目录。 +3. 在 `../chatlab.fun` 提交: + - 仅提交上述两个文件。 + - commit message:`docs: changelogs update` +4. 仅创建 commit,不 push。 + +可复用脚本:`scripts/sync_to_chatlab_fun.sh` + +## 6. 输出结果 + +输出以下信息给用户: + +1. 当前版本号。 +2. 当前仓库 release commit hash。 +3. `chatlab.fun` 仓库 docs commit hash。 +4. 明确声明“未执行 push”。 + +## 参考 + +- `references/english-release-style.md` +- `scripts/preflight_main_clean.sh` +- `scripts/commit_release_changelogs.sh` +- `scripts/sync_to_chatlab_fun.sh` diff --git a/skills/sync-changelog/agents/openai.yaml b/skills/sync-changelog/agents/openai.yaml new file mode 100644 index 0000000..fd20203 --- /dev/null +++ b/skills/sync-changelog/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "同步版本日志" + short_description: "生成英文 changelog 并在双仓库创建提交(不推送)" + default_prompt: "Use $sync-changelog to translate the latest Chinese changelog entry into natural English, commit release notes in this repo, and sync both changelog files to ../chatlab.fun with a docs commit." diff --git a/skills/sync-changelog/references/english-release-style.md b/skills/sync-changelog/references/english-release-style.md new file mode 100644 index 0000000..4fa959c --- /dev/null +++ b/skills/sync-changelog/references/english-release-style.md @@ -0,0 +1,15 @@ +# English Release Style + +将中文版本日志转成英文时,遵循以下风格: + +1. 面向英文母语用户,自然表达,不逐字翻译。 +2. summary 控制在 1 句,简洁清晰,突出用户可感知变化。 +3. items 使用短句,动词开头优先(例如:"Improve...", "Fix...", "Add...")。 +4. 保留技术准确性,不夸大功能,不引入原文没有的信息。 +5. 保留原分类结构(feat/fix/docs/chore 等),仅改写文本。 + +## 示例(仅风格参考) + +中文:"优化 AI 对话底部配置状态展示" + +推荐英文:"Improve the configuration status display in the AI chat footer." diff --git a/skills/sync-changelog/scripts/commit_release_changelogs.sh b/skills/sync-changelog/scripts/commit_release_changelogs.sh new file mode 100755 index 0000000..dce018b --- /dev/null +++ b/skills/sync-changelog/scripts/commit_release_changelogs.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 在当前项目提交中英文 changelog,提交信息为 release: v +# 用法:commit_release_changelogs.sh +REPO_PATH="${1:-}" +if [[ -z "$REPO_PATH" ]]; then + echo "错误: 缺少仓库路径参数" >&2 + exit 1 +fi + +CN_FILE="$REPO_PATH/docs/changelogs_cn.json" +EN_FILE="$REPO_PATH/docs/changelogs_en.json" + +if [[ ! -f "$CN_FILE" || ! -f "$EN_FILE" ]]; then + echo "错误: changelog 文件不存在,无法提交" >&2 + exit 1 +fi + +VERSION="$(node -e "const fs=require('fs');const arr=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(arr?.[0]?.version||'');" "$CN_FILE")" +if [[ -z "$VERSION" ]]; then + echo "错误: 无法从 $CN_FILE 读取当前版本" >&2 + exit 1 +fi + +# 仅暂存指定文件,避免误提交其他改动。 +git -C "$REPO_PATH" add docs/changelogs_cn.json docs/changelogs_en.json + +# 若没有差异则不提交,避免空提交失败。 +if git -C "$REPO_PATH" diff --cached --quiet; then + echo "错误: 没有可提交的 changelog 变更" >&2 + exit 1 +fi + +MSG="release: v${VERSION}" +git -C "$REPO_PATH" commit -m "$MSG" >/dev/null + +git -C "$REPO_PATH" rev-parse --short HEAD diff --git a/skills/sync-changelog/scripts/preflight_main_clean.sh b/skills/sync-changelog/scripts/preflight_main_clean.sh new file mode 100755 index 0000000..1b0eedb --- /dev/null +++ b/skills/sync-changelog/scripts/preflight_main_clean.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 检查指定仓库是否干净,并确保位于 main 分支。 +# 用法:preflight_main_clean.sh [allowlist_csv] +REPO_PATH="${1:-}" +ALLOWLIST_CSV="${2:-}" +if [[ -z "$REPO_PATH" ]]; then + echo "错误: 缺少仓库路径参数" >&2 + exit 1 +fi + +if [[ ! -d "$REPO_PATH/.git" ]]; then + echo "错误: $REPO_PATH 不是 git 仓库" >&2 + exit 1 +fi + +# 支持白名单:仅允许白名单内文件有改动,其他改动一律报错退出。 +STATUS_OUTPUT="$(git -C "$REPO_PATH" status --porcelain)" +if [[ -n "$STATUS_OUTPUT" ]]; then + if [[ -z "$ALLOWLIST_CSV" ]]; then + echo "错误: $REPO_PATH 存在未提交或已暂存改动,请手动处理后重试" >&2 + exit 1 + fi + + ALLOWLIST_NL="" + IFS=',' read -ra ALLOW_PATHS <<<"$ALLOWLIST_CSV" + for allowed in "${ALLOW_PATHS[@]}"; do + # 清理参数中可能出现的空格,避免调用时误传格式。 + cleaned="$(echo "$allowed" | xargs)" + if [[ -n "$cleaned" ]]; then + ALLOWLIST_NL+="$cleaned"$'\n' + fi + done + + # 遍历所有改动路径,存在任意非白名单路径则失败。 + while IFS= read -r line; do + path="${line:3}" + if [[ "$path" == *" -> "* ]]; then + path="${path##* -> }" + fi + if ! printf '%s' "$ALLOWLIST_NL" | grep -Fxq "$path"; then + echo "错误: $REPO_PATH 存在白名单外改动: $path" >&2 + exit 1 + fi + done <<<"$STATUS_OUTPUT" +fi + +CURRENT_BRANCH="$(git -C "$REPO_PATH" rev-parse --abbrev-ref HEAD)" +if [[ "$CURRENT_BRANCH" != "main" ]]; then + # 仅在工作区干净时允许切换到 main。 + git -C "$REPO_PATH" checkout main >/dev/null +fi + +FINAL_BRANCH="$(git -C "$REPO_PATH" rev-parse --abbrev-ref HEAD)" +if [[ "$FINAL_BRANCH" != "main" ]]; then + echo "错误: 无法切换到 main 分支" >&2 + exit 1 +fi + +echo "ok: $REPO_PATH on main and clean" diff --git a/skills/sync-changelog/scripts/sync_to_chatlab_fun.sh b/skills/sync-changelog/scripts/sync_to_chatlab_fun.sh new file mode 100755 index 0000000..1927c7d --- /dev/null +++ b/skills/sync-changelog/scripts/sync_to_chatlab_fun.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 同步当前仓库 changelog 到 ../chatlab.fun 并提交 docs: changelogs update +# 用法:sync_to_chatlab_fun.sh +SOURCE_REPO="${1:-}" +TARGET_REPO="${2:-}" + +if [[ -z "$SOURCE_REPO" || -z "$TARGET_REPO" ]]; then + echo "错误: 缺少参数。用法: sync_to_chatlab_fun.sh " >&2 + exit 1 +fi + +SRC_CN="$SOURCE_REPO/docs/changelogs_cn.json" +SRC_EN="$SOURCE_REPO/docs/changelogs_en.json" +DST_CN="$TARGET_REPO/docs/public/cn/changelogs.json" +DST_EN="$TARGET_REPO/docs/public/en/changelogs.json" + +# 目标文件必须预先存在,不允许自动创建。 +if [[ ! -f "$DST_CN" || ! -f "$DST_EN" ]]; then + echo "错误: chatlab.fun 目标 changelog 文件不存在,请先手动准备" >&2 + exit 1 +fi + +if [[ ! -f "$SRC_CN" || ! -f "$SRC_EN" ]]; then + echo "错误: 源仓库 changelog 文件不存在" >&2 + exit 1 +fi + +cp "$SRC_CN" "$DST_CN" +cp "$SRC_EN" "$DST_EN" + +# 仅提交目标文档文件,避免提交其他改动。 +git -C "$TARGET_REPO" add docs/public/cn/changelogs.json docs/public/en/changelogs.json + +if git -C "$TARGET_REPO" diff --cached --quiet; then + echo "错误: chatlab.fun 没有可提交的 changelog 变更" >&2 + exit 1 +fi + +git -C "$TARGET_REPO" commit -m "docs: changelogs update" >/dev/null + +git -C "$TARGET_REPO" rev-parse --short HEAD