diff --git a/.claude/agents/game-designer.md b/.claude/agents/game-designer.md index 2adebff..b0701f6 100644 --- a/.claude/agents/game-designer.md +++ b/.claude/agents/game-designer.md @@ -243,6 +243,34 @@ GameConfig/ # 数值配置表 --- +## Git 工作流(develop 分支模型) + +项目使用 `develop` 长期开发分支。所有 agent 在 develop 上工作,迭代结束时由 PM 发起 PR 合并回 main。 + +### 开始工作 +```bash +bash .claude/scripts/git-workflow.sh ready +``` + +### 提交文档 +```bash +bash .claude/scripts/git-workflow.sh commit docs "中文描述" +``` +示例:`bash .claude/scripts/git-workflow.sh commit docs design "交互系统执行案"` + +### 推送 +```bash +bash .claude/scripts/git-workflow.sh push +``` + +### 完成工作后 +1. 推送文档到 develop +2. 通过 gitea-collab 在对应 Issue 下评论汇报进度 +3. 更新 Issue 标签为 `status:in-review` +4. 启动 `game-reviewer` 子代理 + +--- + ## 审查交接 完成工作并发布 Issue 评论后,启动审查流程: diff --git a/.claude/agents/game-dev.md b/.claude/agents/game-dev.md index dead7cd..3110749 100644 --- a/.claude/agents/game-dev.md +++ b/.claude/agents/game-dev.md @@ -221,6 +221,42 @@ skills: --- +## Git 工作流(develop 分支模型) + +项目使用 `develop` 长期开发分支。所有 agent 在 develop 上工作,迭代结束时由 PM 发起 PR 合并回 main。 + +### 开始开发 +```bash +bash .claude/scripts/git-workflow.sh ready +``` +确认在 develop 分支上且代码最新。 + +### 提交代码 +```bash +bash .claude/scripts/git-workflow.sh commit "中文描述" +``` +- ⚠️ 只提交自己修改的文件,避免提交其他 agent 的 WIP +- 蓝图(.uasset)和 C++ 分开提交 +- commit message subject ≤ 50 字符 + +### 推送 +```bash +bash .claude/scripts/git-workflow.sh push +``` + +### 完成开发后 +1. 推送代码到 develop +2. 通过 gitea-collab 在对应 Issue 下评论汇报进度 +3. 更新 Issue 标签为 `status:in-review` +4. 启动 `game-reviewer` 子代理 + +### 开发中发现 main 有更新 +```bash +bash .claude/scripts/git-workflow.sh catchup +``` + +--- + ## 代码规范 - C++ 和蓝图遵循 UE5 编码规范 diff --git a/.claude/agents/game-pm.md b/.claude/agents/game-pm.md index 9313f5c..5653bd9 100644 --- a/.claude/agents/game-pm.md +++ b/.claude/agents/game-pm.md @@ -103,6 +103,36 @@ skills: --- +## Git 工作流(develop 分支模型) + +项目使用 `develop` 长期开发分支,所有 agent 在 develop 上工作。 + +### PM 日常 +```bash +# 会话开始时同步 +bash .claude/scripts/git-workflow.sh ready +``` + +### 迭代完成 → 发布 +当所有 Issue 都 review:pass 且用户确认发布时: + +```bash +# 创建 develop → main 的 PR +bash .claude/scripts/git-workflow.sh release "迭代名称" +# 或手动: +# node .claude/skills/gitea-pm/gitea.js --role pm pr create --base main --head develop --title "标题" --body "描述" +``` + +通知用户在 Gitea 审查并合并 PR。 + +### 用户合并后同步 +```bash +bash .claude/scripts/git-workflow.sh post-release +``` +自动同步 main 并将 merge commit 同步回 develop。 + +--- + ## 决策框架 ### 你可以自主决定: diff --git a/.claude/refs/GIT-GUIDE-LITE.md b/.claude/refs/GIT-GUIDE-LITE.md index 65b0d2d..0d7441f 100644 --- a/.claude/refs/GIT-GUIDE-LITE.md +++ b/.claude/refs/GIT-GUIDE-LITE.md @@ -6,23 +6,30 @@ ## 分支策略 -**轻量 Feature Branch** — 单人开发,`main` 永远可玩可编译。 +**develop 长期开发分支模型** — 单人开发 + 多 Agent 协作。 + +``` +main ─────────── 永远可玩可编译,受保护,禁止直接推送 + │ + │ PR (Gitea) ← 里程碑/迭代结束时,PM 发起 + │ + └── develop ──── 长期分支,所有 agent 在此工作 +``` + +| 分支 | 生命期 | 说明 | +|---|---|---| +| `main` | 永久 | 稳定版本,受保护 | +| `develop` | 永久 | 日常开发分支,所有 agent 共用 | + +**辅助分支(可选,用于实验):** | 分支 | 前缀 | 生命期 | 说明 | |---|---|---|---| -| 主线 | `main` | 永久 | 稳定版本,不直接在上面开发 | -| 功能 | `feature/` | 合并后删 | 新系统、新功能 | -| 修复 | `fix/` | 合并后删 | Bug 修复 | | 原型 | `prototype/` | 实验完即删 | 技术验证,随便写 | | 发布 | `release/` | 永久 | 从 main 切出,版本标记 | -**命名规则:** `<类型>/<小写连字符描述>` -``` -feature/player-movement fix/camera-clipping release/v0.1 -``` - **流程:** -1. 从 `main` 拉分支 → 2. 开发并提交 → 3. 测试通过后合并回 `main` → 4. 删除分支 +1. 所有开发在 `develop` 上进行 → 2. 迭代完成时创建 PR → 3. 审查通过后合并回 `main` --- @@ -100,3 +107,33 @@ Host gitea micecola.top **内网地址**(家里用):`ssh://sc-gitea@192.168.1.22:2223/starmen1265/ChildHood.git` 完整规范见 → [GIT-GUIDE.md](GIT-GUIDE.md) + +--- + +## 自动化工作流 + +`git-workflow.sh` 脚本封装了常用 git 操作: + +```bash +# 开始工作:确保在 develop 上且最新 +bash .claude/scripts/git-workflow.sh ready + +# 提交修改 +bash .claude/scripts/git-workflow.sh commit "中文描述" + +# 推送到远程 +bash .claude/scripts/git-workflow.sh push + +# 查看状态 +bash .claude/scripts/git-workflow.sh status +``` + +### PM 发布流程 + +```bash +# 创建 develop → main PR +bash .claude/scripts/git-workflow.sh release "迭代名称" + +# 用户在 Gitea 合并后同步 +bash .claude/scripts/git-workflow.sh post-release +``` diff --git a/.claude/refs/GIT-GUIDE.md b/.claude/refs/GIT-GUIDE.md index 214ef9c..0c93dd2 100644 --- a/.claude/refs/GIT-GUIDE.md +++ b/.claude/refs/GIT-GUIDE.md @@ -7,81 +7,57 @@ ## 一、分支策略 -采用**轻量级 Feature Branch 模型**,避免企业级复杂流程,保持 `main` 分支随时可玩。 +采用 **develop 长期开发分支模型**,适合单人开发 + 多 AI Agent 协作场景。所有 agent 在 `develop` 上工作,迭代结束时通过 PR 合并回 `main`。 ### 1.1 分支总览 ``` -main ──────────────────────────────────────── 稳定主线 - │ - ├── feature/player-movement ───→ 合并后删除 - ├── feature/inventory-system ──→ 合并后删除 - ├── fix/camera-clipping ───────→ 合并后删除 - ├── prototype/procedural-gen ──→ 实验完即删 - └── release/v0.1 ──────────────→ 永久保留 +main ──────────── 永远可玩可编译,受保护,禁止直接推送 + │ + │ PR (Gitea) ← 里程碑/迭代结束时,PM 发起 + │ + └── develop ──── 长期分支,所有 agent 的日常工作分支 + │ + ├── 程序1 提交代码 + ├── 策划 提交文档 + ├── 程序2 提交代码 + └── 迭代就绪 → PR develop→main ``` ### 1.2 分支类型 -| 分支 | 前缀 | 用途 | 合并目标 | 生命周期 | -|---|---|---|---|---| -| 主线 | `main` | 稳定可玩版本,每次提交都能编译运行 | — | 永久 | -| 功能 | `feature/` | 新系统、新功能开发 | `main` | 完成后删除 | -| 修复 | `fix/` | Bug 修复 | `main` | 完成后删除 | -| 原型 | `prototype/` | 技术验证、玩法实验 | 合并或丢弃 | 实验完即删 | -| 发布 | `release/` | 版本发布标记,只从 `main` 切出 | — | 永久保留 | +| 分支 | 用途 | 生命周期 | +|---|---|---| +| `main` | 稳定可玩版本,受保护 | 永久 | +| `develop` | 日常开发,所有 agent 共用 | 永久 | +| `prototype/` | 技术验证、玩法实验 | 实验完即删 | +| `release/` | 版本发布标记,只从 main 切出 | 永久保留 | -### 1.3 命名规范 - -格式:`<类型>/<简短描述>`,全小写,用连字符分隔单词。 +### 1.3 工作流程 ``` -# ✅ 好的命名 -feature/player-movement -feature/inventory-system -feature/dialogue-tree -fix/camera-clipping-through-walls -fix/enemy-ai-stuck-on-navmesh -prototype/procedural-dungeon -release/v0.1 +1. 开始工作(agent 自动执行) + bash .claude/scripts/git-workflow.sh ready -# ❌ 不好的命名 -my-new-feature -fix-bug -test -feature/AddPlayerMovement +2. 开发并提交 + bash .claude/scripts/git-workflow.sh commit "描述" + +3. 推送到远程 + bash .claude/scripts/git-workflow.sh push + +4. 迭代完成,PM 创建 PR + bash .claude/scripts/git-workflow.sh release "迭代名称" + +5. 用户在 Gitea 合并后同步 + bash .claude/scripts/git-workflow.sh post-release ``` -### 1.4 工作流程 +### 1.4 核心原则 -``` -1. 开始新功能 - git checkout main - git pull - git checkout -b feature/xxx - -2. 开发过程中(随时提交) - git add . - git commit -m "wip(xxx): 进度描述" - -3. 功能完成,测试通过 - git checkout main - git merge feature/xxx - git branch -d feature/xxx # 删除已合并分支 - -4. 到达里程碑 - git checkout main - git tag -a v0.1.0 -m "第一个可玩原型" - git checkout -b release/v0.1 - git push origin release/v0.1 -``` - -### 1.5 核心原则 - -- **`main` 永远可玩**:坏了就留在 feature 分支继续改,不要合并进 main -- **功能分支粒度**:一个分支做一件事,不要在一个分支里同时做多个系统 -- **原型不怕乱**:`prototype/` 分支随便写,验证完再决定合并还是丢弃 -- **定期清理**:已合并的 feature/fix 分支及时删除,避免分支堆积 +- **`main` 永远可玩**:通过 develop → main PR 合并,不直接推送 +- **develop 是工作台**:所有 agent 在 develop 上并行工作 +- **不同文件不冲突**:各 agent 修改不同文件,可真正并行 +- **同一文件需协调**:两个 agent 修改同一文件时需顺序执行 --- diff --git a/.claude/scripts/git-workflow.sh b/.claude/scripts/git-workflow.sh new file mode 100644 index 0000000..08651d8 --- /dev/null +++ b/.claude/scripts/git-workflow.sh @@ -0,0 +1,326 @@ +#!/usr/bin/env bash +# ═══════════════════════════════════════════════════════════════ +# git-workflow.sh — ChildHood 项目 Git 工作流自动化 +# +# 基于 develop 长期开发分支模型。 +# 所有 agent 在 develop 上工作,迭代结束时通过 PR 合并回 main。 +# +# 用法: bash .claude/scripts/git-workflow.sh [args...] +# +# 命令: +# ready 确保在 develop 上且最新 +# commit "" 格式化提交 +# push 推送 develop 到远程 +# submit 一键 commit + push(从 Issue 拉取信息) +# release [milestone-name] 创建 develop→main 的 PR +# post-release 合并后同步 main 和 develop +# catchup 将 develop 合并最新 main 的内容 +# status 显示当前分支和状态 +# ═══════════════════════════════════════════════════════════════ + +set -euo pipefail + +# ─── 配置 ─── +DEVELOP_BRANCH="develop" +MAIN_BRANCH="main" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +GITEA_PM_CLI="node $PROJECT_ROOT/.claude/skills/gitea-pm/gitea.js" +GITEA_COLLAB_CLI="node $PROJECT_ROOT/.claude/skills/gitea-collab/gitea.js" + +# ─── 颜色 ─── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +info() { echo -e "${BLUE}ℹ️ $*${NC}"; } +ok() { echo -e "${GREEN}✅ $*${NC}"; } +warn() { echo -e "${YELLOW}⚠️ $*${NC}"; } +fail() { echo -e "${RED}❌ $*${NC}"; exit 1; } + +# ─── 当前分支 ─── +current_branch() { git rev-parse --abbrev-ref HEAD; } + +# ─── 检查是否有未提交的修改 ─── +has_changes() { [ -n "$(git status --porcelain)" ]; } + +# ─── 检测 UE5 二进制文件 ─── +has_ue5_binaries() { + git diff --cached --name-only 2>/dev/null | grep -qE '\.(uasset|umap)$' +} + +# ═══════════════════════════════════════════════════════════════ +# 命令实现 +# ═══════════════════════════════════════════════════════════════ + +cmd_ready() { + info "准备工作环境..." + + local branch + branch=$(current_branch) + + # 如果不在 develop 上,切换过去 + if [ "$branch" != "$DEVELOP_BRANCH" ]; then + if has_changes; then + warn "有未提交的修改,暂存中..." + git stash push --include-untracked -m "auto-stash-$(date +%s)" + fi + git checkout "$DEVELOP_BRANCH" 2>/dev/null || { + # 本地没有 develop 分支,从 main 创建 + info "本地没有 $DEVELOP_BRANCH 分支,从 $MAIN_BRANCH 创建..." + git checkout "$MAIN_BRANCH" + git pull origin "$MAIN_BRANCH" + git checkout -b "$DEVELOP_BRANCH" + } + fi + + # 拉取最新 develop + if git remote get-url origin &>/dev/null; then + info "拉取远程 $DEVELOP_BRANCH..." + git pull origin "$DEVELOP_BRANCH" 2>/dev/null || { + # 远程没有 develop,首次推送 + warn "远程没有 $DEVELOP_BRANCH 分支,将首次推送" + } + fi + + local commit_hash + commit_hash=$(git rev-parse --short HEAD) + ok "已在 $DEVELOP_BRANCH 分支上 (${commit_hash})" +} + +cmd_commit() { + local type="${1:-}" + local scope="${2:-}" + local message="${3:-}" + + if [ -z "$type" ] || [ -z "$scope" ] || [ -z "$message" ]; then + fail "用法: git-workflow.sh commit \"\"\n 示例: git-workflow.sh commit feat player \"实现角色跳跃\"" + fi + + # 校验分支 + if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then + warn "当前不在 $DEVELOP_BRANCH 分支上 (当前: $(current_branch))" + read -rp "继续提交? [y/N] " confirm + [[ "$confirm" != [yY] ]] && { info "已取消"; return; } + fi + + # 暂存所有修改 + if ! has_changes; then + warn "没有检测到修改" + return + fi + + git add -A + + # UE5 二进制文件检测 + if has_ue5_binaries; then + warn "检测到 .uasset/.umap 文件,请确保 commit message 清楚描述了变更内容" + fi + + # 格式化 commit message + local commit_msg="${type}(${scope}): ${message}" + + # subject ≤ 50 字符检查(仅消息部分) + local msg_len=${#commit_msg} + if [ "$msg_len" -gt 72 ]; then + warn "提交消息较长 ($msg_len 字符),建议精简" + fi + + git commit -m "$commit_msg" + ok "已提交: $commit_msg" +} + +cmd_push() { + local branch + branch=$(current_branch) + + info "推送 $branch 到远程..." + + # 设置上游分支(首次) + if ! git rev-parse --abbrev-ref "@{upstream}" &>/dev/null; then + git push -u origin "$branch" + else + git push origin "$branch" + fi + + ok "已推送 $branch 到远程" +} + +cmd_submit() { + local issue_number="${1:-}" + if [ -z "$issue_number" ]; then + fail "用法: git-workflow.sh submit " + fi + + info "提交工作 (Issue #$issue_number)..." + + # 从 Issue 拉取标题作为 commit 参考 + local issue_title="" + issue_title=$($GITEA_COLLAB_CLI --role dev issue get "$issue_number" --json 2>/dev/null | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"//') || true + + # 提交 + if has_changes; then + if [ -n "$issue_title" ]; then + info "Issue 标题: $issue_title" + fi + warn "有未提交的修改,请先运行: git-workflow.sh commit \"\"" + return + fi + + # 推送 + cmd_push + + ok "工作已提交到 $DEVELOP_BRANCH (关联 Issue #$issue_number)" +} + +cmd_release() { + local milestone_name="${1:-}" + + info "准备发布 PR ($DEVELOP_BRANCH → $MAIN_BRANCH)..." + + # 确保在 develop 上 + if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then + fail "请先切换到 $DEVELOP_BRANCH 分支" + fi + + # 确保本地最新 + git pull origin "$DEVELOP_BRANCH" + + # 获取里程碑名称 + if [ -z "$milestone_name" ]; then + # 尝试从当前里程碑获取 + milestone_name=$($GITEA_PM_CLI --role pm milestone list --state open --json 2>/dev/null | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"//') || "迭代发布" + if [ -z "$milestone_name" ]; then + milestone_name="迭代发布" + fi + fi + + local pr_title="Merge develop → main: ${milestone_name}" + + # 构造 PR body(汇总已关闭的 Issue) + local pr_body="## 发布合并\n\n将 \`${DEVELOP_BRANCH}\` 分支的成果合并到 \`${MAIN_BRANCH}\`。\n\n### 里程碑\n${milestone_name}" + + # 创建 PR + info "创建 PR: $pr_title" + local pr_result + pr_result=$($GITEA_PM_CLI --role pm pr create \ + --base "$MAIN_BRANCH" \ + --head "$DEVELOP_BRANCH" \ + --title "$pr_title" \ + --body "$(echo -e "$pr_body")" \ + --json 2>&1) || { + fail "创建 PR 失败: $pr_result" + } + + local pr_number + pr_number=$(echo "$pr_result" | grep -o '"number":[0-9]*' | head -1 | sed 's/"number"://') || true + + if [ -n "$pr_number" ]; then + ok "PR #$pr_number 已创建: $pr_title" + info "请在 Gitea 审查并合并: http://micecola.top:8418/starmen1265/ChildHood/pulls/$pr_number" + else + ok "PR 已创建,请查看 Gitea" + fi +} + +cmd_post_release() { + info "合并后同步..." + + # 同步 main + info "同步 $MAIN_BRANCH..." + git checkout "$MAIN_BRANCH" + git pull origin "$MAIN_BRANCH" + ok "$MAIN_BRANCH 已更新" + + # 切回 develop 并合并 main(保持 develop 包含 merge commit) + info "同步 $DEVELOP_BRANCH..." + git checkout "$DEVELOP_BRANCH" + git merge "$MAIN_BRANCH" --no-edit + git push origin "$DEVELOP_BRANCH" + ok "$DEVELOP_BRANCH 已同步(包含 $MAIN_BRANCH 的 merge commit)" + + local commit_hash + commit_hash=$(git rev-parse --short HEAD) + ok "完成,当前在 $DEVELOP_BRANCH (${commit_hash})" +} + +cmd_catchup() { + info "将 develop 跟上最新的 $MAIN_BRANCH..." + + if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then + fail "请先切换到 $DEVELOP_BRANCH 分支" + fi + + git fetch origin + git merge "origin/$MAIN_BRANCH" --no-edit + ok "$DEVELOP_BRANCH 已合并最新 $MAIN_BRANCH" +} + +cmd_status() { + local branch + branch=$(current_branch) + local commit_hash + commit_hash=$(git rev-parse --short HEAD) + local changed + changed=$(git status --porcelain | wc -l | tr -d ' ') + + echo -e "${BLUE}═══ Git 工作流状态 ═══${NC}" + echo " 当前分支: ${branch} (${commit_hash})" + + if [ "$branch" = "$DEVELOP_BRANCH" ]; then + echo -e " ${GREEN}✓ 在 develop 开发分支上${NC}" + elif [ "$branch" = "$MAIN_BRANCH" ]; then + echo -e " ${YELLOW}⚠ 在 main 分支上,请切换到 develop 开始工作${NC}" + else + echo -e " ${YELLOW}⚠ 在非标准分支上: $branch${NC}" + fi + + echo " 未提交修改: ${changed} 个文件" + + if [ "$changed" -gt 0 ]; then + echo "" + git status --short + fi + + # 远程同步状态 + if git rev-parse "@{upstream}" &>/dev/null; then + local ahead behind + ahead=$(git rev-list --count "@{upstream}..HEAD" 2>/dev/null || echo "0") + behind=$(git rev-list --count "HEAD..@{upstream}" 2>/dev/null || echo "0") + echo " 远程状态: ahead ${ahead}, behind ${behind}" + fi +} + +# ═══════════════════════════════════════════════════════════════ +# 入口 +# ═══════════════════════════════════════════════════════════════ + +case "${1:-}" in + ready) cmd_ready ;; + commit) shift; cmd_commit "$@" ;; + push) cmd_push ;; + submit) shift; cmd_submit "$@" ;; + release) shift; cmd_release "$@" ;; + post-release) cmd_post_release ;; + catchup) cmd_catchup ;; + status) cmd_status ;; + *) + echo "git-workflow.sh — ChildHood 项目 Git 工作流自动化" + echo "" + echo "用法: bash .claude/scripts/git-workflow.sh [args...]" + echo "" + echo "命令:" + echo " ready 确保在 develop 上且最新" + echo " commit \"\" 格式化提交" + echo " push 推送 develop 到远程" + echo " submit 推送并关联 Issue" + echo " release [milestone-name] 创建 develop→main PR" + echo " post-release 合并后同步 main 和 develop" + echo " catchup 将 develop 合并最新 main" + echo " status 显示当前分支和状态" + echo "" + echo "工作流模型: develop 长期开发分支 → 迭代完成时 PR 合并回 main" + ;; +esac diff --git a/.claude/skills/gitea-collab/SKILL.md b/.claude/skills/gitea-collab/SKILL.md index 8c533e8..01a5646 100644 --- a/.claude/skills/gitea-collab/SKILL.md +++ b/.claude/skills/gitea-collab/SKILL.md @@ -148,7 +148,43 @@ node .claude/skills/gitea-collab/gitea.js --role reviewer issue labels --- -## 7. 汇报模板 +## 7. PR 查询 + +非 PM 角色可以查看 PR 状态(创建和合并由 PM 负责): + +```bash +# 列出 PR +node .claude/skills/gitea-collab/gitea.js --role <角色名> pr list + +# 查看 PR 详情 +node .claude/skills/gitea-collab/gitea.js --role <角色名> pr get +``` + +--- + +## 8. Git 工作流 + +所有 agent 在 `develop` 长期开发分支上工作: + +```bash +# 开始工作时:确保在 develop 上 +bash .claude/scripts/git-workflow.sh ready + +# 提交代码/文档 +bash .claude/scripts/git-workflow.sh commit "中文描述" + +# 推送到远程 +bash .claude/scripts/git-workflow.sh push + +# 查看当前状态 +bash .claude/scripts/git-workflow.sh status +``` + +详见 `.claude/refs/GIT-GUIDE-LITE.md` 的"自动化工作流"章节。 + +--- + +## 9. 汇报模板 ### 策划汇报 diff --git a/.claude/skills/gitea-collab/gitea-api.js b/.claude/skills/gitea-collab/gitea-api.js index c5b2e8c..44a392c 100644 --- a/.claude/skills/gitea-collab/gitea-api.js +++ b/.claude/skills/gitea-collab/gitea-api.js @@ -222,6 +222,27 @@ class GiteaAPI { }); } + // ====== Pull Requests ====== + + listPullRequests(params = {}) { + return this.request('GET', '/pulls', null, { state: 'open', limit: 50, ...params }); + } + + getPullRequest(number) { + return this.request('GET', `/pulls/${number}`); + } + + createPullRequest(data) { + return this.request('POST', '/pulls', data); + } + + mergePullRequest(number, data = {}) { + return this.request('POST', `/pulls/${number}/merge`, { + Do: data.style || 'merge', + ...data, + }); + } + // ====== 辅助方法 ====== /** 通过名称查找标签,返回 ID(不存在则返回 null) */ diff --git a/.claude/skills/gitea-collab/gitea.js b/.claude/skills/gitea-collab/gitea.js index fd4e1f0..a45e82b 100644 --- a/.claude/skills/gitea-collab/gitea.js +++ b/.claude/skills/gitea-collab/gitea.js @@ -520,6 +520,84 @@ cmd.pipeline = { }, }; +// ──────── pr ──────── + +cmd.pr = { + async list(api, opts) { + const params = {}; + if (opts.options.state) params.state = opts.options.state; + if (opts.options.base) params.base = opts.options.base; + if (opts.options.head) params.head = opts.options.head; + + const prs = await api.listPullRequests(params); + out(prs); + if (!opts.options.json) { + outText(`\n🔀 Pull Requests (${prs.length || 0})\n`); + if (!prs || !prs.length) { outText(' (无)'); return; } + table( + ['#', '标题', '状态', 'Base', 'Head', '合并'], + prs.map(p => [ + p.number, + truncate(p.title, 35), + p.state, + p.base?.ref || '—', + p.head?.ref || '—', + p.merged ? '✅' : '—', + ]) + ); + } + }, + + async get(api, opts) { + const num = opts.rest[0]; + if (!num) { fail('用法: pr get '); return; } + const pr = await api.getPullRequest(num); + out(pr); + if (!opts.options.json) { + outText(`\n🔀 PR #${pr.number}: ${pr.title}`); + outText(` 状态: ${pr.state}`); + outText(` ${pr.base?.ref} ← ${pr.head?.ref}`); + outText(` 合并: ${pr.merged ? '✅ 已合并' : '❌ 未合并'}`); + if (pr.body) { + outText(`\n--- Body ---\n${pr.body.slice(0, 500)}${pr.body.length > 500 ? '…' : ''}`); + } + } + }, + + async create(api, opts) { + const base = opts.options.base || 'main'; + const head = opts.options.head; + const title = opts.options.title; + const body = opts.options.body || ''; + + if (!head || !title) { + fail('用法: pr create --head <分支> --title "标题" [--base main] [--body "描述"]'); + return; + } + + const pr = await api.createPullRequest({ base, head, title, body }); + out(pr); + if (!opts.options.json) { + ok(`PR #${pr.number} 已创建: ${pr.title}`); + outText(` ${pr.html_url}`); + } + }, + + async merge(api, opts) { + const num = opts.rest[0]; + if (!num) { fail('用法: pr merge [--style merge|squash|rebase]'); return; } + const style = opts.options.style || 'merge'; + + const pr = await api.getPullRequest(num); + if (pr.merged) { fail(`PR #${num} 已合并,无需重复操作`); return; } + if (pr.mergeable === false) { fail(`PR #${num} 存在冲突,无法自动合并。请先解决冲突。`); return; } + + const result = await api.mergePullRequest(num, { style }); + out(result); + if (!opts.options.json) ok(`PR #${num} 已合并 (${style})`); + }, +}; + // ──────── test ──────── cmd.test = { @@ -682,6 +760,11 @@ Gitea CLI — 项目管理工具 pipeline status 查看当前流水线进度 pipeline update <#> 更新 Pipeline 字段 --field <阶段|当前迭代> --value <值> + pr list 列出 Pull Requests [--state open|closed|all] [--base <分支>] [--head <分支>] + pr get <#> 查看 PR 详情 + pr create 创建 PR --head <分支> --title "标题" [--base main] [--body "描述"] + pr merge <#> 合并 PR [--style merge|squash|rebase] + test 验证所有 API 接口 角色自动映射: diff --git a/.claude/skills/gitea-pm/SKILL.md b/.claude/skills/gitea-pm/SKILL.md index 257958e..9863c71 100644 --- a/.claude/skills/gitea-pm/SKILL.md +++ b/.claude/skills/gitea-pm/SKILL.md @@ -218,7 +218,36 @@ Pipeline Tracker 是一个 Meta Issue,仅记录**无法从 Issue/Milestone 推 --- -## 8. 标签体系 +## 8. PR 操作(发布合并) + +迭代完成时,PM 负责创建 develop → main 的 PR: + +```bash +# 列出当前 PR +node .claude/skills/gitea-pm/gitea.js --role pm pr list + +# 创建发布 PR +bash .claude/scripts/git-workflow.sh release "迭代名称" + +# 或手动创建 +node .claude/skills/gitea-pm/gitea.js --role pm pr create \ + --base main --head develop \ + --title "Merge develop → main: 迭代名称" \ + --body "发布描述" + +# 查看 PR 详情 +node .claude/skills/gitea-pm/gitea.js --role pm pr get + +# 合并 PR(用户审查确认后) +node .claude/skills/gitea-pm/gitea.js --role pm pr merge --style merge + +# 合并后同步 +bash .claude/scripts/git-workflow.sh post-release +``` + +--- + +## 9. 标签体系 从 `gitea-config.json` 的 `labels` 字段定义。`label init` 命令从此配置创建标签。 diff --git a/.claude/skills/gitea-pm/gitea-api.js b/.claude/skills/gitea-pm/gitea-api.js index c5b2e8c..98c4524 100644 --- a/.claude/skills/gitea-pm/gitea-api.js +++ b/.claude/skills/gitea-pm/gitea-api.js @@ -222,6 +222,27 @@ class GiteaAPI { }); } + // ====== Pull Requests ====== + + listPullRequests(params = {}) { + return this.request('GET', '/pulls', null, { state: 'open', limit: 50, ...params }); + } + + getPullRequest(number) { + return this.request('GET', `/pulls/${number}`); + } + + createPullRequest(data) { + return this.request('POST', '/pulls', data); + } + + mergePullRequest(number, data = {}) { + return this.request('POST', `/pulls/${number}/merge`, { + Do: data.style || 'merge', // merge | squash | rebase + ...data, + }); + } + // ====== 辅助方法 ====== /** 通过名称查找标签,返回 ID(不存在则返回 null) */ diff --git a/.claude/skills/gitea-pm/gitea.js b/.claude/skills/gitea-pm/gitea.js index fd4e1f0..42caedb 100644 --- a/.claude/skills/gitea-pm/gitea.js +++ b/.claude/skills/gitea-pm/gitea.js @@ -520,6 +520,87 @@ cmd.pipeline = { }, }; +// ──────── pr ──────── + +cmd.pr = { + async list(api, opts) { + const params = {}; + if (opts.options.state) params.state = opts.options.state; + if (opts.options.base) params.base = opts.options.base; + if (opts.options.head) params.head = opts.options.head; + + const prs = await api.listPullRequests(params); + out(prs); + if (!opts.options.json) { + outText(`\n🔀 Pull Requests (${prs.length || 0})\n`); + if (!prs || !prs.length) { outText(' (无)'); return; } + table( + ['#', '标题', '状态', 'Base', 'Head', '合并'], + prs.map(p => [ + p.number, + truncate(p.title, 35), + p.state, + p.base?.ref || '—', + p.head?.ref || '—', + p.merged ? '✅' : '—', + ]) + ); + } + }, + + async get(api, opts) { + const num = opts.rest[0]; + if (!num) { fail('用法: pr get '); return; } + const pr = await api.getPullRequest(num); + out(pr); + if (!opts.options.json) { + outText(`\n🔀 PR #${pr.number}: ${pr.title}`); + outText(` 状态: ${pr.state}`); + outText(` ${pr.base?.ref} ← ${pr.head?.ref}`); + outText(` 合并: ${pr.merged ? '✅ 已合并' : '❌ 未合并'}`); + outText(` 创建: ${pr.created_at?.slice(0, 10)}`); + outText(` 更新: ${pr.updated_at?.slice(0, 10)}`); + if (pr.body) { + outText(`\n--- Body ---\n${pr.body.slice(0, 500)}${pr.body.length > 500 ? '…' : ''}`); + } + } + }, + + async create(api, opts) { + const base = opts.options.base || 'main'; + const head = opts.options.head; + const title = opts.options.title; + const body = opts.options.body || ''; + + if (!head || !title) { + fail('用法: pr create --head <分支> --title "标题" [--base main] [--body "描述"]'); + return; + } + + const pr = await api.createPullRequest({ base, head, title, body }); + out(pr); + if (!opts.options.json) { + ok(`PR #${pr.number} 已创建: ${pr.title}`); + outText(` ${pr.html_url}`); + } + }, + + async merge(api, opts) { + const num = opts.rest[0]; + if (!num) { fail('用法: pr merge [--style merge|squash|rebase]'); return; } + const style = opts.options.style || 'merge'; + + // 先检查 PR 状态 + const pr = await api.getPullRequest(num); + if (pr.merged) { fail(`PR #${num} 已合并,无需重复操作`); return; } + if (pr.mergeable === false) { fail(`PR #${num} 存在冲突,无法自动合并。请先解决冲突。`); return; } + + const result = await api.mergePullRequest(num, { style }); + out(result); + if (!opts.options.json) ok(`PR #${num} 已合并 (${style})`); + }, +}; + // ──────── test ──────── cmd.test = { @@ -682,6 +763,11 @@ Gitea CLI — 项目管理工具 pipeline status 查看当前流水线进度 pipeline update <#> 更新 Pipeline 字段 --field <阶段|当前迭代> --value <值> + pr list 列出 Pull Requests [--state open|closed|all] [--base <分支>] [--head <分支>] + pr get <#> 查看 PR 详情 + pr create 创建 PR --head <分支> --title "标题" [--base main] [--body "描述"] + pr merge <#> 合并 PR [--style merge|squash|rebase] + test 验证所有 API 接口 角色自动映射: