diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
new file mode 100644
index 0000000..8048919
--- /dev/null
+++ b/.gitea/workflows/test.yml
@@ -0,0 +1,826 @@
+name: 🧪 Playbook 测试套件
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch: # 允许手动触发
+
+concurrency:
+ group: test-${{ github.repository }}-${{ github.ref }}
+ cancel-in-progress: true
+
+# ==========================================
+# 🔧 配置区域 - 测试参数
+# ==========================================
+env:
+ # ===== 测试环境配置 =====
+ # 测试工作目录
+ WORKSPACE_DIR: "/home/workspace"
+ TEST_WORKSPACE: "/home/workspace/playbook-test"
+
+ # ===== 测试覆盖率目标 =====
+ # Shell 脚本测试覆盖率目标(百分比)
+ SHELL_COVERAGE_TARGET: "80"
+ # 模板验证通过率目标(百分比)
+ TEMPLATE_VALIDATION_TARGET: "100"
+
+ # ===== 测试输出配置 =====
+ # 是否生成详细测试报告
+ VERBOSE_OUTPUT: "true"
+ # 测试报告格式(tap/junit/markdown)
+ REPORT_FORMAT: "markdown"
+
+ # ===== 颜色输出配置 =====
+ # 启用彩色输出
+ FORCE_COLOR: "1"
+ TERM: "xterm-256color"
+
+jobs:
+ # ==========================================
+ # Job 1: 环境检查与准备
+ # ==========================================
+ setup:
+ name: 🔍 环境检查
+ runs-on: ubuntu-22.04
+
+ outputs:
+ scripts-changed: ${{ steps.changes.outputs.scripts }}
+ templates-changed: ${{ steps.changes.outputs.templates }}
+ docs-changed: ${{ steps.changes.outputs.docs }}
+
+ steps:
+ - name: 📥 准备仓库
+ run: |
+ echo "========================================"
+ echo "📥 准备仓库到 WORKSPACE_DIR"
+ echo "========================================"
+
+ REPO_NAME="${{ github.event.repository.name }}"
+ REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
+ TOKEN="${{ secrets.WORKFLOW }}"
+ if [ -n "$TOKEN" ]; then
+ REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
+ else
+ REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
+ fi
+
+ if [ -d "$REPO_DIR" ]; then
+ if [ -d "$REPO_DIR/.git" ]; then
+ cd "$REPO_DIR"
+ git clean -fdx
+ git reset --hard
+ git fetch --all --tags --force --prune --prune-tags
+ else
+ rm -rf "$REPO_DIR"
+ fi
+ fi
+
+ if [ ! -d "$REPO_DIR/.git" ]; then
+ mkdir -p "${{ env.WORKSPACE_DIR }}"
+ git clone "$REPO_URL" "$REPO_DIR"
+ cd "$REPO_DIR"
+ fi
+
+ TARGET_SHA="${{ github.sha }}"
+ TARGET_REF="${{ github.ref }}"
+ if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
+ git checkout -f "$TARGET_SHA"
+ else
+ if [ -n "$TARGET_REF" ]; then
+ git fetch origin "$TARGET_REF"
+ git checkout -f FETCH_HEAD
+ else
+ git checkout -f "${{ github.ref_name }}"
+ fi
+ fi
+
+ git config --global --add safe.directory "$REPO_DIR"
+ echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
+
+ - name: 🔍 检测变更文件
+ id: changes
+ run: |
+ echo "========================================"
+ echo "🔍 检测变更文件"
+ echo "========================================"
+
+ cd "$REPO_DIR"
+
+ # 如果是手动触发或主分支push,测试所有内容
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ] || [ "${{ github.event_name }}" = "push" ]; then
+ echo "scripts=true" >> $GITHUB_OUTPUT
+ echo "templates=true" >> $GITHUB_OUTPUT
+ echo "docs=true" >> $GITHUB_OUTPUT
+ echo "✓ 手动触发或主分支push,测试所有内容"
+ else
+ # PR:只测试变更的部分
+ SCRIPTS_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^scripts/' || echo "")
+ TEMPLATES_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^templates/' || echo "")
+ DOCS_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^(docs/|.agents/)' || echo "")
+
+ if [ -n "$SCRIPTS_CHANGED" ]; then
+ echo "scripts=true" >> $GITHUB_OUTPUT
+ echo "✓ 检测到脚本变更"
+ else
+ echo "scripts=false" >> $GITHUB_OUTPUT
+ fi
+
+ if [ -n "$TEMPLATES_CHANGED" ]; then
+ echo "templates=true" >> $GITHUB_OUTPUT
+ echo "✓ 检测到模板变更"
+ else
+ echo "templates=false" >> $GITHUB_OUTPUT
+ fi
+
+ if [ -n "$DOCS_CHANGED" ]; then
+ echo "docs=true" >> $GITHUB_OUTPUT
+ echo "✓ 检测到文档变更"
+ else
+ echo "docs=false" >> $GITHUB_OUTPUT
+ fi
+ fi
+
+ echo "========================================"
+
+ - name: 📊 显示测试环境信息
+ run: |
+ echo "========================================"
+ echo "📊 测试环境信息"
+ echo "========================================"
+ echo "🖥️ 操作系统: $(lsb_release -ds)"
+ echo "🐚 Shell: $SHELL"
+ echo "🐍 Python: $(python3 --version)"
+ echo "📦 Git: $(git --version)"
+ echo "========================================"
+
+ # ==========================================
+ # Job 2: Shell 脚本测试
+ # ==========================================
+ test-scripts:
+ name: 🐚 Shell 脚本测试
+ runs-on: ubuntu-22.04
+ needs: setup
+ if: needs.setup.outputs.scripts-changed == 'true'
+
+ strategy:
+ fail-fast: false
+ matrix:
+ script-group:
+ - name: sync_standards
+ scripts: "sync_standards.sh"
+ - name: vendor_playbook
+ scripts: "vendor_playbook.sh"
+ - name: install_codex_skills
+ scripts: "install_codex_skills.sh"
+
+ steps:
+ - name: 📥 准备仓库
+ run: |
+ echo "========================================"
+ echo "📥 准备仓库到 WORKSPACE_DIR"
+ echo "========================================"
+
+ REPO_NAME="${{ github.event.repository.name }}"
+ REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
+ TOKEN="${{ secrets.WORKFLOW }}"
+ if [ -n "$TOKEN" ]; then
+ REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
+ else
+ REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
+ fi
+
+ if [ -d "$REPO_DIR" ]; then
+ if [ -d "$REPO_DIR/.git" ]; then
+ cd "$REPO_DIR"
+ git clean -fdx
+ git reset --hard
+ git fetch --all --tags --force --prune --prune-tags
+ else
+ rm -rf "$REPO_DIR"
+ fi
+ fi
+
+ if [ ! -d "$REPO_DIR/.git" ]; then
+ mkdir -p "${{ env.WORKSPACE_DIR }}"
+ git clone "$REPO_URL" "$REPO_DIR"
+ cd "$REPO_DIR"
+ fi
+
+ TARGET_SHA="${{ github.sha }}"
+ TARGET_REF="${{ github.ref }}"
+ if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
+ git checkout -f "$TARGET_SHA"
+ else
+ if [ -n "$TARGET_REF" ]; then
+ git fetch origin "$TARGET_REF"
+ git checkout -f FETCH_HEAD
+ else
+ git checkout -f "${{ github.ref_name }}"
+ fi
+ fi
+
+ git config --global --add safe.directory "$REPO_DIR"
+ echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
+
+ - name: 🔧 安装 bats-core
+ run: |
+ echo "========================================"
+ echo "🔧 安装 bats-core 测试框架"
+ echo "========================================"
+
+ sudo apt-get update
+ sudo apt-get install -y bats
+
+ echo ""
+ echo "✓ bats 版本: $(bats --version)"
+ echo "========================================"
+
+ - name: 🧪 运行 ${{ matrix.script-group.name }} 测试
+ run: |
+ echo "========================================"
+ echo "🧪 测试脚本组: ${{ matrix.script-group.name }}"
+ echo "========================================"
+
+ cd "$REPO_DIR/tests/scripts"
+
+ # 运行对应的测试文件
+ if [ -f "test_${{ matrix.script-group.name }}.bats" ]; then
+ bats --formatter tap "test_${{ matrix.script-group.name }}.bats" | tee "${{ matrix.script-group.name }}_test_results.tap"
+ TEST_EXIT_CODE=${PIPESTATUS[0]}
+
+ echo ""
+ if [ $TEST_EXIT_CODE -eq 0 ]; then
+ echo "✅ ${{ matrix.script-group.name }} 测试通过"
+ else
+ echo "❌ ${{ matrix.script-group.name }} 测试失败"
+ exit 1
+ fi
+ else
+ echo "⚠️ 未找到测试文件: test_${{ matrix.script-group.name }}.bats"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 📊 上传测试结果
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: script-test-results-${{ matrix.script-group.name }}
+ path: ${{ env.REPO_DIR }}/tests/scripts/*_test_results.tap
+ retention-days: 30
+
+ # ==========================================
+ # Job 3: 模板验证测试
+ # ==========================================
+ test-templates:
+ name: 📄 模板验证测试
+ runs-on: ubuntu-22.04
+ needs: setup
+ if: needs.setup.outputs.templates-changed == 'true'
+
+ strategy:
+ fail-fast: false
+ matrix:
+ template-type:
+ - name: python
+ validator: validate_python_templates.sh
+ - name: cpp
+ validator: validate_cpp_templates.sh
+ - name: ci
+ validator: validate_ci_templates.sh
+
+ steps:
+ - name: 📥 准备仓库
+ run: |
+ echo "========================================"
+ echo "📥 准备仓库到 WORKSPACE_DIR"
+ echo "========================================"
+
+ REPO_NAME="${{ github.event.repository.name }}"
+ REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
+ TOKEN="${{ secrets.WORKFLOW }}"
+ if [ -n "$TOKEN" ]; then
+ REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
+ else
+ REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
+ fi
+
+ if [ -d "$REPO_DIR" ]; then
+ if [ -d "$REPO_DIR/.git" ]; then
+ cd "$REPO_DIR"
+ git clean -fdx
+ git reset --hard
+ git fetch --all --tags --force --prune --prune-tags
+ else
+ rm -rf "$REPO_DIR"
+ fi
+ fi
+
+ if [ ! -d "$REPO_DIR/.git" ]; then
+ mkdir -p "${{ env.WORKSPACE_DIR }}"
+ git clone "$REPO_URL" "$REPO_DIR"
+ cd "$REPO_DIR"
+ fi
+
+ TARGET_SHA="${{ github.sha }}"
+ TARGET_REF="${{ github.ref }}"
+ if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
+ git checkout -f "$TARGET_SHA"
+ else
+ if [ -n "$TARGET_REF" ]; then
+ git fetch origin "$TARGET_REF"
+ git checkout -f FETCH_HEAD
+ else
+ git checkout -f "${{ github.ref_name }}"
+ fi
+ fi
+
+ git config --global --add safe.directory "$REPO_DIR"
+ echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
+
+ - name: 🔧 安装验证工具
+ run: |
+ echo "========================================"
+ echo "🔧 安装 ${{ matrix.template-type.name }} 模板验证工具"
+ echo "========================================"
+
+ case "${{ matrix.template-type.name }}" in
+ python)
+ python3 -m pip install --upgrade pip
+ pip install toml tomli jsonschema yamllint
+ echo "✓ Python 验证工具已安装"
+ ;;
+ cpp)
+ sudo apt-get update
+ sudo apt-get install -y cmake clang-format
+ echo "✓ C++ 验证工具已安装"
+ ;;
+ ci)
+ pip install yamllint
+ echo "✓ CI 验证工具已安装"
+ ;;
+ esac
+
+ echo "========================================"
+
+ - name: 🧪 验证 ${{ matrix.template-type.name }} 模板
+ run: |
+ echo "========================================"
+ echo "🧪 验证模板类型: ${{ matrix.template-type.name }}"
+ echo "========================================"
+
+ cd "$REPO_DIR/tests/templates"
+
+ if [ -f "${{ matrix.template-type.validator }}" ]; then
+ chmod +x "${{ matrix.template-type.validator }}"
+ ./"${{ matrix.template-type.validator }}"
+
+ if [ $? -eq 0 ]; then
+ echo "✅ ${{ matrix.template-type.name }} 模板验证通过"
+ else
+ echo "❌ ${{ matrix.template-type.name }} 模板验证失败"
+ exit 1
+ fi
+ else
+ echo "⚠️ 未找到验证脚本: ${{ matrix.template-type.validator }}"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 📊 上传验证报告
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: template-validation-${{ matrix.template-type.name }}
+ path: ${{ env.REPO_DIR }}/tests/templates/*_validation_report.txt
+ retention-days: 30
+
+ # ==========================================
+ # Job 4: 集成测试
+ # ==========================================
+ test-integration:
+ name: 🔗 集成测试
+ runs-on: ubuntu-22.04
+ needs: [setup, test-scripts, test-templates]
+ if: |
+ always() &&
+ (needs.test-scripts.result == 'success' || needs.test-scripts.result == 'skipped') &&
+ (needs.test-templates.result == 'success' || needs.test-templates.result == 'skipped')
+
+ steps:
+ - name: 📥 准备仓库
+ run: |
+ echo "========================================"
+ echo "📥 准备仓库到 WORKSPACE_DIR"
+ echo "========================================"
+
+ REPO_NAME="${{ github.event.repository.name }}"
+ REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
+ TOKEN="${{ secrets.WORKFLOW }}"
+ if [ -n "$TOKEN" ]; then
+ REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
+ else
+ REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
+ fi
+
+ if [ -d "$REPO_DIR" ]; then
+ if [ -d "$REPO_DIR/.git" ]; then
+ cd "$REPO_DIR"
+ git clean -fdx
+ git reset --hard
+ git fetch --all --tags --force --prune --prune-tags
+ else
+ rm -rf "$REPO_DIR"
+ fi
+ fi
+
+ if [ ! -d "$REPO_DIR/.git" ]; then
+ mkdir -p "${{ env.WORKSPACE_DIR }}"
+ git clone "$REPO_URL" "$REPO_DIR"
+ cd "$REPO_DIR"
+ fi
+
+ TARGET_SHA="${{ github.sha }}"
+ TARGET_REF="${{ github.ref }}"
+ if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
+ git checkout -f "$TARGET_SHA"
+ else
+ if [ -n "$TARGET_REF" ]; then
+ git fetch origin "$TARGET_REF"
+ git checkout -f FETCH_HEAD
+ else
+ git checkout -f "${{ github.ref_name }}"
+ fi
+ fi
+
+ git config --global --add safe.directory "$REPO_DIR"
+ echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
+
+ - name: 🔧 准备测试环境
+ run: |
+ echo "========================================"
+ echo "🔧 准备集成测试环境"
+ echo "========================================"
+
+ mkdir -p "${{ env.TEST_WORKSPACE }}"
+ cd "${{ env.TEST_WORKSPACE }}"
+
+ # 创建测试项目目录
+ mkdir -p test-project-tsl
+ mkdir -p test-project-cpp
+ mkdir -p test-project-multi
+
+ echo "✓ 测试环境已准备"
+ echo "========================================"
+
+ - name: 🧪 测试场景1: TSL 项目标准同步
+ run: |
+ echo "========================================"
+ echo "🧪 测试场景1: TSL 项目标准同步"
+ echo "========================================"
+
+ cd "${{ env.TEST_WORKSPACE }}/test-project-tsl"
+
+ # 初始化 git 仓库
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+
+ # 模拟 subtree add(包含 .agents 等点目录,排除 .git)
+ mkdir -p docs/standards/playbook
+ tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
+
+ # 运行同步脚本
+ echo "▶ 运行 sync_standards.sh tsl"
+ sh docs/standards/playbook/scripts/sync_standards.sh tsl
+
+ # 验证结果
+ if [ -d ".agents/tsl" ] && [ -f ".agents/tsl/index.md" ]; then
+ echo "✅ TSL 规则集同步成功"
+ else
+ echo "❌ TSL 规则集同步失败"
+ exit 1
+ fi
+
+ if grep -q "# BEGIN playbook .gitattributes" .gitattributes 2>/dev/null; then
+ echo "✅ .gitattributes 更新成功"
+ else
+ echo "❌ .gitattributes 更新失败"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 🧪 测试场景2: C++ 项目标准同步
+ run: |
+ echo "========================================"
+ echo "🧪 测试场景2: C++ 项目标准同步"
+ echo "========================================"
+
+ cd "${{ env.TEST_WORKSPACE }}/test-project-cpp"
+
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+
+ mkdir -p docs/standards/playbook
+ tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
+
+ echo "▶ 运行 sync_standards.sh cpp"
+ sh docs/standards/playbook/scripts/sync_standards.sh cpp
+
+ if [ -d ".agents/cpp" ] && [ -f ".agents/cpp/index.md" ]; then
+ echo "✅ C++ 规则集同步成功"
+ else
+ echo "❌ C++ 规则集同步失败"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 🧪 测试场景3: 多语言项目标准同步
+ run: |
+ echo "========================================"
+ echo "🧪 测试场景3: 多语言项目标准同步"
+ echo "========================================"
+
+ cd "${{ env.TEST_WORKSPACE }}/test-project-multi"
+
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+
+ mkdir -p docs/standards/playbook
+ tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
+
+ echo "▶ 运行 sync_standards.sh tsl cpp"
+ sh docs/standards/playbook/scripts/sync_standards.sh tsl cpp
+
+ if [ -d ".agents/tsl" ] && [ -d ".agents/cpp" ] && [ -f ".agents/index.md" ]; then
+ echo "✅ 多语言规则集同步成功"
+ else
+ echo "❌ 多语言规则集同步失败"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 🧪 测试场景4: vendor_playbook 脚本
+ run: |
+ echo "========================================"
+ echo "🧪 测试场景4: vendor_playbook 脚本"
+ echo "========================================"
+
+ cd "${{ env.TEST_WORKSPACE }}"
+ mkdir -p test-project-vendor
+ cd test-project-vendor
+
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+
+ echo "▶ 运行 vendor_playbook.sh"
+ sh "$REPO_DIR/scripts/vendor_playbook.sh" . tsl
+
+ if [ -d "docs/standards/playbook" ] && [ -d ".agents/tsl" ]; then
+ echo "✅ vendor_playbook 脚本执行成功"
+ else
+ echo "❌ vendor_playbook 脚本执行失败"
+ exit 1
+ fi
+
+ echo "========================================"
+
+ - name: 🧹 清理测试环境
+ if: always()
+ run: |
+ echo "🧹 清理测试环境..."
+ chmod -R u+w "${{ env.TEST_WORKSPACE }}" 2>/dev/null || true
+ rm -rf "${{ env.TEST_WORKSPACE }}"
+ echo "✓ 清理完成"
+
+ # ==========================================
+ # Job 5: 文档一致性检查
+ # ==========================================
+ test-docs:
+ name: 📚 文档一致性检查
+ runs-on: ubuntu-22.04
+ needs: setup
+ if: needs.setup.outputs.docs-changed == 'true'
+
+ steps:
+ - name: 📥 准备仓库
+ run: |
+ echo "========================================"
+ echo "📥 准备仓库到 WORKSPACE_DIR"
+ echo "========================================"
+
+ REPO_NAME="${{ github.event.repository.name }}"
+ REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
+ TOKEN="${{ secrets.WORKFLOW }}"
+ if [ -n "$TOKEN" ]; then
+ REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
+ else
+ REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
+ fi
+
+ if [ -d "$REPO_DIR" ]; then
+ if [ -d "$REPO_DIR/.git" ]; then
+ cd "$REPO_DIR"
+ git clean -fdx
+ git reset --hard
+ git fetch --all --tags --force --prune --prune-tags
+ else
+ rm -rf "$REPO_DIR"
+ fi
+ fi
+
+ if [ ! -d "$REPO_DIR/.git" ]; then
+ mkdir -p "${{ env.WORKSPACE_DIR }}"
+ git clone "$REPO_URL" "$REPO_DIR"
+ cd "$REPO_DIR"
+ fi
+
+ TARGET_SHA="${{ github.sha }}"
+ TARGET_REF="${{ github.ref }}"
+ if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
+ git checkout -f "$TARGET_SHA"
+ else
+ if [ -n "$TARGET_REF" ]; then
+ git fetch origin "$TARGET_REF"
+ git checkout -f FETCH_HEAD
+ else
+ git checkout -f "${{ github.ref_name }}"
+ fi
+ fi
+
+ git config --global --add safe.directory "$REPO_DIR"
+ echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
+
+ - name: 🔍 检查文档链接有效性
+ run: |
+ echo "========================================"
+ echo "🔍 检查文档链接有效性"
+ echo "========================================"
+
+ cd "$REPO_DIR/tests/integration"
+
+ if [ -f "check_doc_links.sh" ]; then
+ chmod +x check_doc_links.sh
+ ./check_doc_links.sh
+
+ if [ $? -eq 0 ]; then
+ echo "✅ 文档链接检查通过"
+ else
+ echo "❌ 发现无效链接"
+ exit 1
+ fi
+ else
+ echo "⚠️ 未找到链接检查脚本,跳过"
+ fi
+
+ echo "========================================"
+
+ - name: 🔍 检查代理规则一致性
+ run: |
+ echo "========================================"
+ echo "🔍 检查代理规则一致性"
+ echo "========================================"
+
+ cd "$REPO_DIR"
+
+ # 检查 .agents/ 和 docs/ 中的规则是否一致
+ python3 << 'EOF'
+ import os
+ import sys
+ from pathlib import Path
+
+ errors = []
+
+ # 检查各语言的 .agents/ 目录
+ agents_base = Path(".agents")
+ for lang_dir in ["tsl", "cpp", "python"]:
+ agents_lang = agents_base / lang_dir
+ if not agents_lang.exists():
+ continue
+
+ # 检查必须存在的文件
+ required_files = ["index.md", "auth.md", "code_quality.md"]
+ for req_file in required_files:
+ file_path = agents_lang / req_file
+ if not file_path.exists():
+ errors.append(f"❌ 缺少文件: {file_path}")
+ elif file_path.stat().st_size == 0:
+ errors.append(f"❌ 文件为空: {file_path}")
+
+ if errors:
+ print("\n".join(errors))
+ sys.exit(1)
+ else:
+ print("✅ 代理规则一致性检查通过")
+ EOF
+
+ echo "========================================"
+
+ # ==========================================
+ # Job 6: 生成测试报告
+ # ==========================================
+ report:
+ name: 📊 生成测试报告
+ runs-on: ubuntu-22.04
+ needs: [setup, test-scripts, test-templates, test-integration, test-docs]
+ if: always()
+
+ steps:
+ - name: 📁 准备报告目录
+ run: |
+ mkdir -p "${{ env.WORKSPACE_DIR }}/test-results"
+
+ - name: 📥 下载所有测试结果
+ uses: actions/download-artifact@v4
+ with:
+ path: ${{ env.WORKSPACE_DIR }}/test-results
+
+ - name: 📊 生成综合报告
+ if: always()
+ run: |
+ echo "========================================"
+ echo "📊 生成测试综合报告"
+ echo "========================================"
+
+ cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
+ # 🧪 Playbook 测试报告
+
+ ## 📋 测试执行摘要
+
+ | 测试类型 | 状态 |
+ |---------|------|
+ EOFSUMMARY
+
+ # Shell 脚本测试
+ if [ "${{ needs.test-scripts.result }}" = "success" ]; then
+ echo "| 🐚 Shell 脚本测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-scripts.result }}" = "failure" ]; then
+ echo "| 🐚 Shell 脚本测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-scripts.result }}" = "skipped" ]; then
+ echo "| 🐚 Shell 脚本测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # 模板验证测试
+ if [ "${{ needs.test-templates.result }}" = "success" ]; then
+ echo "| 📄 模板验证测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-templates.result }}" = "failure" ]; then
+ echo "| 📄 模板验证测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-templates.result }}" = "skipped" ]; then
+ echo "| 📄 模板验证测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # 集成测试
+ if [ "${{ needs.test-integration.result }}" = "success" ]; then
+ echo "| 🔗 集成测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-integration.result }}" = "failure" ]; then
+ echo "| 🔗 集成测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-integration.result }}" = "skipped" ]; then
+ echo "| 🔗 集成测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # 文档检查
+ if [ "${{ needs.test-docs.result }}" = "success" ]; then
+ echo "| 📚 文档一致性检查 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-docs.result }}" = "failure" ]; then
+ echo "| 📚 文档一致性检查 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ needs.test-docs.result }}" = "skipped" ]; then
+ echo "| 📚 文档一致性检查 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ cat >> $GITHUB_STEP_SUMMARY << 'EOFEND'
+
+ ---
+
+ ## 🔗 相关链接
+
+ - 📝 [测试文档](tests/README.md)
+ - 🐛 [问题反馈](../../issues)
+ - 📖 [开发指南](docs/index.md)
+
+ ---
+
+
+
+ *🤖 由 [Gitea Actions](../../actions) 自动生成*
+
+ EOFEND
+
+ echo "*📅 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "
" >> $GITHUB_STEP_SUMMARY
+
+ echo "========================================"
+ echo "✅ 测试报告已生成"
+ echo "========================================"
diff --git a/docs/tsl/syntax_book/function.md b/docs/tsl/syntax_book/function.md
index 5053315..83ae17d 100644
--- a/docs/tsl/syntax_book/function.md
+++ b/docs/tsl/syntax_book/function.md
@@ -63,9 +63,7 @@ function/
### 方法 3:查看历史备份
-如果需要查看原始完整文件:
-
-- 备份文件:[function.md.backup](./function.md.backup)(保留完整内容)
+如需查看原始完整文件,请从历史版本获取(当前仓库不保留备份文件)。
## 💡 拆分的好处
diff --git a/docs/tsl/syntax_book/function/index.md b/docs/tsl/syntax_book/function/index.md
index 1cd79ab..455b2d6 100644
--- a/docs/tsl/syntax_book/function/index.md
+++ b/docs/tsl/syntax_book/function/index.md
@@ -77,5 +77,4 @@
---
-**原始文档**:本文档由 221,389 行的 `function.md` 拆分而来,原文件已保留为
-[`../function.md.backup`](../function.md.backup)
+**原始文档**:本文档由 221,389 行的 `function.md` 拆分而来,原始大文件不在当前仓库保留;如需查看,请从历史版本获取。
diff --git a/scripts/vendor_playbook.sh b/scripts/vendor_playbook.sh
index 428c03e..ad7316c 100644
--- a/scripts/vendor_playbook.sh
+++ b/scripts/vendor_playbook.sh
@@ -210,7 +210,7 @@ sh docs/standards/playbook/scripts/install_codex_skills.sh
## CI templates(可选)
-目标项目可复制启用的 CI 示例模板(如 Gitea Actions):`templates/ci/`。
+目标项目可复制启用的 CI 示例模板(如 Gitea Actions):\`templates/ci/\`。
EOF
cat >"$DEST_PREFIX/SOURCE.md" <.bats`:
+
+```bash
+#!/usr/bin/env bats
+
+setup() {
+ # 测试前准备
+ export TEST_DIR="$(mktemp -d)"
+}
+
+teardown() {
+ # 测试后清理
+ rm -rf "$TEST_DIR"
+}
+
+@test "描述测试内容" {
+ # 测试代码
+ [ -f "some_file" ]
+}
+```
+
+#### 添加新的模板验证
+
+在 `tests/templates/` 创建新文件 `validate__templates.sh`,参考现有脚本结构。
+
+#### 添加新的集成测试
+
+在 `tests/integration/` 创建新脚本,确保:
+
+1. 使用 `set -eu` 启用错误检测
+2. 输出清晰的测试进度
+3. 生成详细的报告文件
+4. 返回正确的退出码(0 = 成功,非 0 = 失败)
+
+## 📊 测试覆盖率目标
+
+- **Shell 脚本测试**:目标覆盖率 ≥ 80%
+- **模板验证测试**:目标通过率 = 100%
+- **集成测试**:目标通过率 = 100%
+- **文档链接有效性**:目标有效率 = 100%
+
+## 🐛 故障排查
+
+### bats 测试失败
+
+```bash
+# 使用 --verbose 查看详细输出
+bats --verbose test_sync_standards.bats
+
+# 使用 --trace 查看执行跟踪
+bats --trace test_sync_standards.bats
+```
+
+### 模板验证失败
+
+验证脚本会生成详细报告文件:
+
+- `tests/templates/python_validation_report.txt`
+- `tests/templates/cpp_validation_report.txt`
+- `tests/templates/ci_validation_report.txt`
+
+### 文档链接检查失败
+
+查看详细报告:
+
+```bash
+cat /tmp/doc_links_report.txt
+```
+
+## 🤝 贡献指南
+
+添加新功能时,请同步更新相应的测试:
+
+1. **修改脚本**(`scripts/`)→ 更新对应的 `.bats` 测试
+2. **修改模板**(`templates/`)→ 更新对应的验证脚本
+3. **修改文档**(`docs/`, `.agents/`)→ 运行文档链接检查
+4. **修改 CI workflow**(`.gitea/workflows/`)→ 验证 YAML 语法
+
+## 📖 相关文档
+
+- [开发规范索引](../docs/index.md)
+- [提交信息规范](../docs/common/commit_message.md)
+- [Gitea Actions 文档](https://docs.gitea.com/usage/actions/overview)
+- [bats-core 文档](https://bats-core.readthedocs.io/)
+
+## ❓ 常见问题
+
+### Q: 为什么测试在 CI 通过,但本地失败?
+
+A: 可能原因:
+
+- 环境差异(工具版本、路径)
+- 权限问题
+- Git 配置差异
+
+建议使用 Docker 容器复现 CI 环境。
+
+### Q: 如何跳过某些测试?
+
+A: bats 支持 `skip` 命令:
+
+```bash
+@test "某个测试" {
+ skip "原因说明"
+ # 测试代码
+}
+```
+
+### Q: 测试运行很慢,如何加速?
+
+A: 建议:
+
+1. 使用 bats 的 `--jobs` 参数并行运行
+2. 只运行变更相关的测试
+3. 使用 CI 的缓存机制
+
+---
+
+**测试套件维护者**: Playbook 团队
+**最后更新**: 2026-01-07
diff --git a/tests/integration/check_doc_links.sh b/tests/integration/check_doc_links.sh
new file mode 100644
index 0000000..1803222
--- /dev/null
+++ b/tests/integration/check_doc_links.sh
@@ -0,0 +1,220 @@
+#!/usr/bin/env sh
+# 文档链接有效性检查脚本
+
+set -eu
+
+echo "========================================"
+echo "🔗 文档链接有效性检查"
+echo "========================================"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+
+TOTAL_LINKS=0
+VALID_LINKS=0
+BROKEN_LINKS=0
+SKIPPED_LINKS=0
+
+BROKEN_LINKS_FILE="/tmp/broken_links.txt"
+REPORT_FILE="/tmp/doc_links_report.txt"
+
+> "$BROKEN_LINKS_FILE"
+> "$REPORT_FILE"
+
+echo "📁 Playbook 根目录: $PLAYBOOK_ROOT"
+echo ""
+
+# ============================================
+# 辅助函数
+# ============================================
+
+check_file_link() {
+ local source_file="$1"
+ local link_path="$2"
+ local link_line="$3"
+
+ TOTAL_LINKS=$((TOTAL_LINKS + 1))
+
+ # 处理相对路径
+ local source_dir
+ source_dir="$(dirname "$source_file")"
+
+ # 解析链接路径
+ local target_path="$link_path"
+
+ # 移除锚点
+ target_path="${target_path%%#*}"
+
+ # 跳过空链接
+ if [ -z "$target_path" ]; then
+ SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
+ return 0
+ fi
+
+ # 跳过外部链接(http/https)
+ if echo "$target_path" | grep -qE "^https?://"; then
+ SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
+ return 0
+ fi
+
+ # 跳过 mailto 链接
+ if echo "$target_path" | grep -q "^mailto:"; then
+ SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
+ return 0
+ fi
+
+ # 构建绝对路径
+ local absolute_path
+ if echo "$target_path" | grep -q "^/"; then
+ # 绝对路径(从仓库根)
+ absolute_path="$PLAYBOOK_ROOT$target_path"
+ else
+ # 相对路径
+ absolute_path="$source_dir/$target_path"
+ fi
+
+ # 规范化路径
+ absolute_path="$(cd "$(dirname "$absolute_path")" 2>/dev/null && pwd)/$(basename "$absolute_path")" || absolute_path=""
+
+ # 检查文件是否存在
+ if [ -n "$absolute_path" ] && [ -e "$absolute_path" ]; then
+ VALID_LINKS=$((VALID_LINKS + 1))
+ return 0
+ else
+ BROKEN_LINKS=$((BROKEN_LINKS + 1))
+ echo "❌ 断链: $source_file:$link_line" >> "$BROKEN_LINKS_FILE"
+ echo " 链接: $link_path" >> "$BROKEN_LINKS_FILE"
+ echo " 目标: $absolute_path" >> "$BROKEN_LINKS_FILE"
+ echo "" >> "$BROKEN_LINKS_FILE"
+ return 1
+ fi
+}
+
+extract_links() {
+ awk '
+ BEGIN { in_code = 0 }
+ {
+ line = $0
+ if (line ~ /^```/) { in_code = !in_code; next }
+ if (in_code) next
+
+ gsub(/`[^`]*`/, "", line)
+
+ while (match(line, /\[[^]]+\]\(([^)]+)\)/, m)) {
+ print NR "\t" m[1]
+ line = substr(line, RSTART + RLENGTH)
+ }
+
+ if (match(line, /^\[[^]]+\]:[[:space:]]*(.+)/, ref)) {
+ print NR "\t" ref[1]
+ }
+ }
+ ' "$1"
+}
+
+# ============================================
+# 查找并检查所有 Markdown 文件
+# ============================================
+
+echo "🔍 扫描 Markdown 文件..."
+
+cd "$PLAYBOOK_ROOT"
+
+MD_FILES=$(find . -name "*.md" \
+ -not -path "*/node_modules/*" \
+ -not -path "*/.git/*" \
+ -not -path "*/build/*" \
+ -not -path "*/dist/*" \
+ 2>/dev/null || true)
+
+FILE_COUNT=$(echo "$MD_FILES" | grep -c "^" || echo 0)
+echo "📄 找到 $FILE_COUNT 个 Markdown 文件"
+echo ""
+
+CURRENT_FILE_NUM=0
+
+for md_file in $MD_FILES; do
+ CURRENT_FILE_NUM=$((CURRENT_FILE_NUM + 1))
+
+ # 显示进度
+ if [ "$CURRENT_FILE_NUM" -eq 1 ] || [ $((CURRENT_FILE_NUM % 10)) -eq 0 ] || [ "$CURRENT_FILE_NUM" -eq "$FILE_COUNT" ]; then
+ echo "📖 处理中... [$CURRENT_FILE_NUM/$FILE_COUNT] $md_file"
+ fi
+
+ links_file="$(mktemp)"
+ extract_links "$md_file" > "$links_file"
+ while IFS="$(printf '\t')" read -r line_num link; do
+ check_file_link "$md_file" "$link" "$line_num" || true
+ done < "$links_file"
+ rm -f "$links_file"
+done
+
+echo ""
+echo "✅ 扫描完成"
+echo ""
+
+# ============================================
+# 生成检查报告
+# ============================================
+
+echo "========================================"
+echo "📊 链接检查结果统计"
+echo "========================================"
+echo "🔗 总链接数: $TOTAL_LINKS"
+echo "✅ 有效链接: $VALID_LINKS"
+echo "⏭️ 跳过链接: $SKIPPED_LINKS (外部/mailto)"
+echo "❌ 断开链接: $BROKEN_LINKS"
+
+if [ "$TOTAL_LINKS" -gt 0 ]; then
+ CHECKED_LINKS=$((TOTAL_LINKS - SKIPPED_LINKS))
+ if [ "$CHECKED_LINKS" -gt 0 ]; then
+ SUCCESS_RATE=$(awk "BEGIN {printf \"%.1f\", ($VALID_LINKS * 100.0) / $CHECKED_LINKS}")
+ echo "📈 有效率: $SUCCESS_RATE%"
+ fi
+fi
+
+echo ""
+
+# 写入报告
+{
+ echo "文档链接有效性检查报告"
+ echo "========================"
+ echo ""
+ echo "检查时间: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo "检查目录: $PLAYBOOK_ROOT"
+ echo ""
+ echo "统计结果:"
+ echo " 总链接数: $TOTAL_LINKS"
+ echo " 有效链接: $VALID_LINKS"
+ echo " 跳过链接: $SKIPPED_LINKS"
+ echo " 断开链接: $BROKEN_LINKS"
+ echo ""
+ if [ "$BROKEN_LINKS" -gt 0 ]; then
+ echo "断开链接详情:"
+ echo "=============="
+ cat "$BROKEN_LINKS_FILE"
+ fi
+} > "$REPORT_FILE"
+
+if [ "$BROKEN_LINKS" -gt 0 ]; then
+ echo "❌ 发现 $BROKEN_LINKS 个断开的链接"
+ echo ""
+ echo "断开链接详情:"
+ cat "$BROKEN_LINKS_FILE"
+ echo ""
+ echo "📄 详细报告: $REPORT_FILE"
+fi
+
+echo "========================================"
+
+# 清理临时文件(保留报告用于 CI)
+# rm -f "$BROKEN_LINKS_FILE"
+
+# 返回结果
+if [ "$BROKEN_LINKS" -eq 0 ]; then
+ echo "✅ 所有文档链接检查通过"
+ exit 0
+else
+ echo "❌ 文档链接检查失败"
+ exit 1
+fi
diff --git a/tests/scripts/test_install_codex_skills.bats b/tests/scripts/test_install_codex_skills.bats
new file mode 100644
index 0000000..9e93aa2
--- /dev/null
+++ b/tests/scripts/test_install_codex_skills.bats
@@ -0,0 +1,116 @@
+#!/usr/bin/env bats
+# install_codex_skills.sh 测试套件
+
+setup() {
+ export TEST_DIR="$(mktemp -d)"
+ export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ export SCRIPT_PATH="$PLAYBOOK_ROOT/scripts/install_codex_skills.sh"
+
+ # 创建临时 HOME 避免污染真实环境
+ export ORIGINAL_HOME="$HOME"
+ export HOME="$TEST_DIR/home"
+ mkdir -p "$HOME"
+
+ export CODEX_HOME="$HOME/.codex"
+ export SKILLS_DST_ROOT="$CODEX_HOME/skills"
+
+ cd "$TEST_DIR"
+}
+
+teardown() {
+ export HOME="$ORIGINAL_HOME"
+
+ if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
+ chmod -R u+w "$TEST_DIR" 2>/dev/null || true
+ rm -rf "$TEST_DIR"
+ fi
+}
+
+skip_if_no_skills() {
+ if [ ! -d "$PLAYBOOK_ROOT/codex/skills" ]; then
+ skip "No codex/skills directory found"
+ fi
+}
+
+first_skill_name() {
+ ls -1 "$PLAYBOOK_ROOT/codex/skills" 2>/dev/null | head -n 1
+}
+
+# ==============================================
+# 基础功能测试
+# ==============================================
+
+@test "install_codex_skills.sh 脚本存在且可执行" {
+ [ -f "$SCRIPT_PATH" ]
+}
+
+@test "安装 - 创建 skills 目录" {
+ skip_if_no_skills
+
+ [ ! -d "$SKILLS_DST_ROOT" ]
+
+ sh "$SCRIPT_PATH"
+
+ [ -d "$SKILLS_DST_ROOT" ]
+}
+
+@test "安装 - 复制 skill 目录" {
+ skip_if_no_skills
+
+ sh "$SCRIPT_PATH"
+
+ SKILL_DIRS=$(find "$SKILLS_DST_ROOT" -mindepth 1 -maxdepth 1 -type d)
+ [ -n "$SKILL_DIRS" ]
+
+ for dir in $SKILL_DIRS; do
+ [ -f "$dir/SKILL.md" ]
+ done
+}
+
+@test "安装 - 指定单个 skill" {
+ skip_if_no_skills
+
+ SKILL_NAME="$(first_skill_name)"
+ [ -n "$SKILL_NAME" ]
+
+ sh "$SCRIPT_PATH" "$SKILL_NAME"
+
+ [ -d "$SKILLS_DST_ROOT/$SKILL_NAME" ]
+ COUNT=$(find "$SKILLS_DST_ROOT" -mindepth 1 -maxdepth 1 -type d | wc -l)
+ [ "$COUNT" -eq 1 ]
+}
+
+@test "备份 - 同名 skill 目录会创建备份" {
+ skip_if_no_skills
+
+ SKILL_NAME="$(first_skill_name)"
+ [ -n "$SKILL_NAME" ]
+
+ mkdir -p "$SKILLS_DST_ROOT/$SKILL_NAME"
+ echo "marker" > "$SKILLS_DST_ROOT/$SKILL_NAME/marker.txt"
+
+ sh "$SCRIPT_PATH" "$SKILL_NAME"
+
+ BACKUP_DIRS=$(find "$SKILLS_DST_ROOT" -maxdepth 1 -type d -name "$SKILL_NAME.bak.*")
+ [ -n "$BACKUP_DIRS" ]
+}
+
+@test "错误处理 - 指定不存在 skill 报错" {
+ skip_if_no_skills
+
+ run sh "$SCRIPT_PATH" nonexistent-skill
+
+ [ "$status" -ne 0 ]
+}
+
+@test "幂等性 - 多次安装结果一致" {
+ skip_if_no_skills
+
+ sh "$SCRIPT_PATH"
+ CHECKSUM1=$(find "$SKILLS_DST_ROOT" -type f -name "SKILL.md" ! -path "$SKILLS_DST_ROOT"'/*.bak.*/*' -exec md5sum {} \; | sort | md5sum)
+
+ sh "$SCRIPT_PATH"
+ CHECKSUM2=$(find "$SKILLS_DST_ROOT" -type f -name "SKILL.md" ! -path "$SKILLS_DST_ROOT"'/*.bak.*/*' -exec md5sum {} \; | sort | md5sum)
+
+ [ "$CHECKSUM1" = "$CHECKSUM2" ]
+}
diff --git a/tests/scripts/test_sync_standards.bats b/tests/scripts/test_sync_standards.bats
new file mode 100644
index 0000000..0b864c0
--- /dev/null
+++ b/tests/scripts/test_sync_standards.bats
@@ -0,0 +1,251 @@
+#!/usr/bin/env bats
+# sync_standards.sh 测试套件
+
+# 测试辅助函数
+setup() {
+ # 创建临时测试目录
+ export TEST_DIR="$(mktemp -d)"
+ export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ # 初始化测试项目
+ cd "$TEST_DIR"
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+
+ # 模拟 playbook 快照目录
+ mkdir -p docs/standards/playbook
+ cp -r "$PLAYBOOK_ROOT"/{.agents,.gitattributes,docs,scripts} docs/standards/playbook/ 2>/dev/null || true
+
+ export SCRIPT_PATH="$TEST_DIR/docs/standards/playbook/scripts/sync_standards.sh"
+}
+
+teardown() {
+ # 清理测试目录
+ if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
+ rm -rf "$TEST_DIR"
+ fi
+}
+
+# ==============================================
+# 基础功能测试
+# ==============================================
+
+@test "sync_standards.sh 脚本存在且可执行" {
+ [ -f "$SCRIPT_PATH" ]
+}
+
+@test "sync_standards.sh 无参数时同步 tsl 规则集" {
+ cd "$TEST_DIR"
+ sh "$SCRIPT_PATH"
+
+ # 验证输出目录
+ [ -d ".agents/tsl" ]
+ [ -f ".agents/tsl/index.md" ]
+}
+
+@test "sync_standards.sh tsl - 同步 TSL 规则集" {
+ cd "$TEST_DIR"
+ sh "$SCRIPT_PATH" tsl
+
+ # 验证必须文件
+ [ -d ".agents/tsl" ]
+ [ -f ".agents/tsl/index.md" ]
+ [ -f ".agents/tsl/auth.md" ]
+ [ -f ".agents/tsl/code_quality.md" ]
+ [ -f ".agents/tsl/performance.md" ]
+ [ -f ".agents/tsl/testing.md" ]
+}
+
+@test "sync_standards.sh cpp - 同步 C++ 规则集" {
+ cd "$TEST_DIR"
+ sh "$SCRIPT_PATH" cpp
+
+ # 验证必须文件
+ [ -d ".agents/cpp" ]
+ [ -f ".agents/cpp/index.md" ]
+ [ -f ".agents/cpp/auth.md" ]
+ [ -f ".agents/cpp/code_quality.md" ]
+}
+
+@test "sync_standards.sh tsl cpp - 同步多个规则集" {
+ cd "$TEST_DIR"
+ sh "$SCRIPT_PATH" tsl cpp
+
+ # 验证两个规则集都存在
+ [ -d ".agents/tsl" ]
+ [ -d ".agents/cpp" ]
+ [ -f ".agents/index.md" ]
+}
+
+# ==============================================
+# .gitattributes 同步测试
+# ==============================================
+
+@test ".gitattributes - 默认模式创建 managed block" {
+ cd "$TEST_DIR"
+ [ ! -f ".gitattributes" ]
+
+ sh "$SCRIPT_PATH" tsl
+
+ [ -f ".gitattributes" ]
+ grep -q "# BEGIN playbook .gitattributes" .gitattributes
+ grep -q "# END playbook .gitattributes" .gitattributes
+}
+
+@test ".gitattributes - 保留现有内容" {
+ cd "$TEST_DIR"
+ echo "# My custom rules" > .gitattributes
+ echo "*.custom binary" >> .gitattributes
+
+ sh "$SCRIPT_PATH" tsl
+
+ grep -q "# My custom rules" .gitattributes
+ grep -q "*.custom binary" .gitattributes
+}
+
+@test ".gitattributes - 更新已存在的 managed block" {
+ cd "$TEST_DIR"
+ cat > .gitattributes << 'EOF'
+# BEGIN playbook .gitattributes
+# Old content
+# END playbook .gitattributes
+EOF
+
+ sh "$SCRIPT_PATH" tsl
+
+ # 验证 block 已更新(不再包含 "Old content")
+ ! grep -q "Old content" .gitattributes
+}
+
+# ==============================================
+# AGENTS.md 自动生成测试
+# ==============================================
+
+@test "AGENTS.md - 不存在时自动创建" {
+ cd "$TEST_DIR"
+ [ ! -f "AGENTS.md" ]
+
+ sh "$SCRIPT_PATH" tsl
+
+ [ -f "AGENTS.md" ]
+ grep -q ".agents/" AGENTS.md
+}
+
+@test "AGENTS.md - 已存在时不覆盖" {
+ cd "$TEST_DIR"
+ echo "# My custom AGENTS.md" > AGENTS.md
+
+ sh "$SCRIPT_PATH" tsl
+
+ grep -q "# My custom AGENTS.md" AGENTS.md
+}
+
+# ==============================================
+# 备份功能测试
+# ==============================================
+
+@test "备份 - .gitattributes 更新前创建备份" {
+ cd "$TEST_DIR"
+ echo "# Original content" > .gitattributes
+
+ sh "$SCRIPT_PATH" tsl
+
+ # 验证备份文件存在
+ [ -f ".gitattributes.bak."* ] || [ -f ".gitattributes.bak" ]
+}
+
+@test "备份 - .agents/ 更新前创建备份" {
+ cd "$TEST_DIR"
+ mkdir -p .agents/tsl
+ echo "# Old index" > .agents/tsl/index.md
+
+ sh "$SCRIPT_PATH" tsl
+
+ # 验证备份目录存在
+ [ -d ".agents/tsl.bak."* ] || [ -d ".agents/tsl.bak" ]
+}
+
+# ==============================================
+# 多语言项目测试
+# ==============================================
+
+@test "多语言 - TSL + C++ + Python 规则集共存" {
+ cd "$TEST_DIR"
+
+ # 复制 Python 规则集(如果存在)
+ if [ -d "$PLAYBOOK_ROOT/.agents/python" ]; then
+ cp -r "$PLAYBOOK_ROOT/.agents/python" docs/standards/playbook/.agents/
+ fi
+
+ sh "$SCRIPT_PATH" tsl cpp
+
+ # 验证规则集不互相覆盖
+ [ -d ".agents/tsl" ]
+ [ -d ".agents/cpp" ]
+
+ # 验证索引文件正确引用
+ [ -f ".agents/index.md" ]
+}
+
+# ==============================================
+# 错误处理测试
+# ==============================================
+
+@test "错误处理 - 未找到 playbook 快照时报错" {
+ cd "$TEST_DIR"
+ rm -rf docs/standards/playbook/.agents
+
+ run sh "$SCRIPT_PATH" tsl
+
+ [ "$status" -ne 0 ]
+}
+
+@test "错误处理 - 无效语言参数时报错" {
+ cd "$TEST_DIR"
+
+ run sh "$SCRIPT_PATH" invalid_lang
+
+ [ "$status" -ne 0 ]
+}
+
+# ==============================================
+# 环境变量配置测试
+# ==============================================
+
+@test "环境变量 - SYNC_GITATTR_MODE=skip 跳过 .gitattributes" {
+ cd "$TEST_DIR"
+ export SYNC_GITATTR_MODE=skip
+
+ sh "$SCRIPT_PATH" tsl
+
+ [ ! -f ".gitattributes" ]
+}
+
+@test "环境变量 - SYNC_GITATTR_MODE=overwrite 覆盖 .gitattributes" {
+ cd "$TEST_DIR"
+ echo "# Custom content" > .gitattributes
+ export SYNC_GITATTR_MODE=overwrite
+
+ sh "$SCRIPT_PATH" tsl
+
+ # 验证自定义内容被覆盖
+ ! grep -q "# Custom content" .gitattributes
+}
+
+# ==============================================
+# 幂等性测试
+# ==============================================
+
+@test "幂等性 - 多次执行结果一致" {
+ cd "$TEST_DIR"
+
+ # 第一次同步
+ sh "$SCRIPT_PATH" tsl
+ CHECKSUM1=$(find .agents/tsl -type f -exec md5sum {} \; | sort | md5sum)
+
+ # 第二次同步
+ sh "$SCRIPT_PATH" tsl
+ CHECKSUM2=$(find .agents/tsl -type f -exec md5sum {} \; | sort | md5sum)
+
+ [ "$CHECKSUM1" = "$CHECKSUM2" ]
+}
diff --git a/tests/scripts/test_vendor_playbook.bats b/tests/scripts/test_vendor_playbook.bats
new file mode 100644
index 0000000..7b11684
--- /dev/null
+++ b/tests/scripts/test_vendor_playbook.bats
@@ -0,0 +1,284 @@
+#!/usr/bin/env bats
+# vendor_playbook.sh 测试套件
+
+# 测试辅助函数
+setup() {
+ # 创建临时测试目录
+ export TEST_DIR="$(mktemp -d)"
+ export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ export SCRIPT_PATH="$PLAYBOOK_ROOT/scripts/vendor_playbook.sh"
+
+ # 创建目标项目目录
+ export TARGET_DIR="$(mktemp -d)"
+ cd "$TARGET_DIR"
+ git init
+ git config user.name "Test User"
+ git config user.email "test@example.com"
+}
+
+teardown() {
+ # 清理测试目录
+ if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
+ chmod -R u+w "$TEST_DIR" 2>/dev/null || true
+ rm -rf "$TEST_DIR"
+ fi
+ if [ -n "$TARGET_DIR" ] && [ -d "$TARGET_DIR" ]; then
+ chmod -R u+w "$TARGET_DIR" 2>/dev/null || true
+ rm -rf "$TARGET_DIR"
+ fi
+}
+
+# ==============================================
+# 基础功能测试
+# ==============================================
+
+@test "vendor_playbook.sh 脚本存在且可执行" {
+ [ -f "$SCRIPT_PATH" ]
+}
+
+@test "vendor_playbook.sh - 无参数时显示用法" {
+ cd "$TARGET_DIR"
+
+ run sh "$SCRIPT_PATH"
+
+ [ "$status" -ne 0 ]
+}
+
+@test "vendor_playbook.sh - 单语言 vendoring (tsl)" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证快照目录结构
+ [ -d "docs/standards/playbook" ]
+ [ -d "docs/standards/playbook/docs/common" ]
+ [ -d "docs/standards/playbook/docs/tsl" ]
+ [ -d "docs/standards/playbook/.agents/tsl" ]
+ [ -f "docs/standards/playbook/scripts/sync_standards.sh" ]
+}
+
+@test "vendor_playbook.sh - 多语言 vendoring (tsl cpp)" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl cpp
+
+ # 验证包含两种语言的文档
+ [ -d "docs/standards/playbook/docs/tsl" ]
+ [ -d "docs/standards/playbook/docs/cpp" ]
+ [ -d "docs/standards/playbook/.agents/tsl" ]
+ [ -d "docs/standards/playbook/.agents/cpp" ]
+}
+
+# ==============================================
+# 自动同步测试
+# ==============================================
+
+@test "vendor_playbook.sh - 自动执行 sync_standards" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证根目录已同步规则集
+ [ -d ".agents/tsl" ]
+ [ -f ".agents/tsl/index.md" ]
+ [ -f ".gitattributes" ]
+}
+
+@test "vendor_playbook.sh - 多语言自动同步" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl cpp
+
+ # 验证两个规则集都已同步
+ [ -d ".agents/tsl" ]
+ [ -d ".agents/cpp" ]
+ [ -f ".agents/index.md" ]
+}
+
+# ==============================================
+# SOURCE.md 生成测试
+# ==============================================
+
+@test "vendor_playbook.sh - 生成 SOURCE.md" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ [ -f "docs/standards/playbook/SOURCE.md" ]
+ grep -q "Source:" docs/standards/playbook/SOURCE.md
+ grep -q "Commit:" docs/standards/playbook/SOURCE.md
+}
+
+@test "vendor_playbook.sh - SOURCE.md 包含 commit hash" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证包含 commit hash(40个十六进制字符)
+ grep -E "[0-9a-f]{40}" docs/standards/playbook/SOURCE.md
+}
+
+@test "vendor_playbook.sh - SOURCE.md 包含时间戳" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证包含日期格式 YYYY-MM-DD
+ grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" docs/standards/playbook/SOURCE.md
+}
+
+# ==============================================
+# 裁剪功能测试
+# ==============================================
+
+@test "裁剪 - 仅包含指定语言的文档" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证包含 TSL 文档
+ [ -d "docs/standards/playbook/docs/tsl" ]
+
+ # 验证不包含其他语言(如果原本有 Python)
+ if [ -d "$PLAYBOOK_ROOT/docs/python" ]; then
+ [ ! -d "docs/standards/playbook/docs/python" ]
+ fi
+}
+
+@test "裁剪 - 始终包含 common 目录" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ [ -d "docs/standards/playbook/docs/common" ]
+ [ -f "docs/standards/playbook/docs/common/commit_message.md" ]
+}
+
+@test "裁剪 - 包含对应的模板文件" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" cpp
+
+ # 验证包含 C++ 模板
+ [ -d "docs/standards/playbook/templates/cpp" ]
+
+ # 验证不包含 Python 模板(如果指定了 cpp)
+ if [ -d "$PLAYBOOK_ROOT/templates/python" ]; then
+ [ ! -d "docs/standards/playbook/templates/python" ]
+ fi
+}
+
+@test "裁剪 - 包含通用 CI 模板" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ [ -d "docs/standards/playbook/templates/ci" ]
+}
+
+# ==============================================
+# 目标目录处理测试
+# ==============================================
+
+@test "目标目录 - 已存在时覆盖更新" {
+ cd "$TARGET_DIR"
+
+ # 首次 vendor
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 在快照中添加标记文件
+ touch docs/standards/playbook/OLD_MARKER
+
+ # 再次 vendor
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证旧标记不存在(已被覆盖)
+ [ ! -f "docs/standards/playbook/OLD_MARKER" ]
+}
+
+@test "目标目录 - 创建必要的父目录" {
+ cd "$TARGET_DIR"
+
+ # 确保 docs/standards 不存在
+ [ ! -d "docs/standards" ]
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ [ -d "docs/standards/playbook" ]
+}
+
+# ==============================================
+# 错误处理测试
+# ==============================================
+
+@test "错误处理 - 目标目录不存在时报错" {
+ cd "$TARGET_DIR"
+
+ run sh "$SCRIPT_PATH" /nonexistent/path tsl
+
+ [ "$status" -ne 0 ]
+}
+
+@test "错误处理 - 无效语言参数时报错" {
+ cd "$TARGET_DIR"
+
+ run sh "$SCRIPT_PATH" "$TARGET_DIR" invalid_lang
+
+ [ "$status" -ne 0 ]
+}
+
+@test "错误处理 - 目标目录不是 git 仓库时警告" {
+ cd "$TARGET_DIR"
+ rm -rf .git
+
+ run sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 应该给出警告但不失败
+ [ "$status" -eq 0 ]
+}
+
+# ==============================================
+# 完整性测试
+# ==============================================
+
+@test "完整性 - 验证所有必要文件已复制" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证关键文件
+ [ -f "docs/standards/playbook/README.md" ]
+ [ -f "docs/standards/playbook/docs/index.md" ]
+ [ -f "docs/standards/playbook/.gitattributes" ]
+ [ -f "docs/standards/playbook/scripts/sync_standards.sh" ]
+ [ -f "docs/standards/playbook/SOURCE.md" ]
+}
+
+@test "完整性 - 验证脚本可执行性" {
+ cd "$TARGET_DIR"
+
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+
+ # 验证同步脚本可执行
+ run sh docs/standards/playbook/scripts/sync_standards.sh tsl
+
+ [ "$status" -eq 0 ]
+}
+
+# ==============================================
+# 多次执行测试
+# ==============================================
+
+@test "幂等性 - 多次 vendor 结果一致" {
+ cd "$TARGET_DIR"
+
+ # 第一次 vendor
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+ CHECKSUM1=$(find docs/standards/playbook -type f -name "*.md" ! -name "SOURCE.md" -exec md5sum {} \; | sort | md5sum)
+
+ # 第二次 vendor
+ sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
+ CHECKSUM2=$(find docs/standards/playbook -type f -name "*.md" ! -name "SOURCE.md" -exec md5sum {} \; | sort | md5sum)
+
+ [ "$CHECKSUM1" = "$CHECKSUM2" ]
+}
diff --git a/tests/templates/validate_ci_templates.sh b/tests/templates/validate_ci_templates.sh
new file mode 100644
index 0000000..c696353
--- /dev/null
+++ b/tests/templates/validate_ci_templates.sh
@@ -0,0 +1,332 @@
+#!/usr/bin/env sh
+# CI 模板验证脚本
+
+set -eu
+
+echo "========================================"
+echo "🔧 CI 模板验证"
+echo "========================================"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/ci"
+
+VALIDATION_PASSED=0
+VALIDATION_FAILED=0
+ERRORS_FILE="/tmp/ci_template_validation_errors.txt"
+REPORT_FILE="$SCRIPT_DIR/ci_validation_report.txt"
+
+> "$ERRORS_FILE"
+> "$REPORT_FILE"
+
+echo "📁 模板目录: $TEMPLATES_DIR"
+echo ""
+
+# ============================================
+# 辅助函数
+# ============================================
+
+validate_file_exists() {
+ local file="$1"
+ local description="$2"
+
+ if [ -f "$file" ]; then
+ echo " ✅ $description: $(basename "$file")"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: $(basename "$file") - 文件不存在"
+ echo "文件不存在: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+validate_yaml_syntax() {
+ local file="$1"
+ local description="$2"
+
+ if ! command -v yamllint >/dev/null 2>&1; then
+ echo " ⚠️ $description: 跳过(yamllint 未安装)"
+ return 0
+ fi
+
+ if yamllint -d relaxed "$file" >/dev/null 2>&1; then
+ echo " ✅ $description: YAML 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: YAML 语法错误"
+ echo "YAML 语法错误: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+validate_workflow_structure() {
+ local file="$1"
+
+ echo " 📋 检查 workflow 结构:"
+
+ # 检查必要字段
+ if grep -q "^name:" "$file"; then
+ echo " ✓ 包含 name 字段"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 name 字段"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ if grep -q "^on:" "$file"; then
+ echo " ✓ 包含 on 触发器"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 on 触发器"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ if grep -q "^jobs:" "$file"; then
+ echo " ✓ 包含 jobs 定义"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 jobs 定义"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ # 检查 runs-on
+ if grep -q "runs-on:" "$file"; then
+ runner=$(grep "runs-on:" "$file" | head -1 | awk '{print $2}')
+ echo " ✓ 配置了 runner: $runner"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 runs-on 配置"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ # 检查 steps
+ if grep -q "steps:" "$file"; then
+ step_count=$(grep -c "^ - name:" "$file" || echo 0)
+ echo " ✓ 包含 $step_count 个步骤"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 steps 定义"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+}
+
+# ============================================
+# 查找并验证 Gitea workflow 文件
+# ============================================
+
+echo "🔍 查找 Gitea workflow 文件"
+GITEA_WORKFLOWS_DIR="$TEMPLATES_DIR/gitea/.gitea/workflows"
+
+if [ ! -d "$GITEA_WORKFLOWS_DIR" ]; then
+ echo "⚠️ Gitea workflows 目录不存在: $GITEA_WORKFLOWS_DIR"
+ echo "跳过 Gitea workflow 验证"
+else
+ WORKFLOW_FILES=$(find "$GITEA_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
+
+ if [ -z "$WORKFLOW_FILES" ]; then
+ echo "⚠️ 未找到 Gitea workflow 文件"
+ else
+ for workflow in $WORKFLOW_FILES; do
+ echo ""
+ echo "🔍 验证 $(basename "$workflow")"
+
+ if validate_file_exists "$workflow" "$(basename "$workflow")"; then
+ # 验证 YAML 语法
+ validate_yaml_syntax "$workflow" "$(basename "$workflow")"
+
+ # 验证 workflow 结构
+ validate_workflow_structure "$workflow"
+
+ # 检查是否包含中文注释(符合项目风格)
+ if grep -q "# .*[\u4e00-\u9fa5]" "$workflow" 2>/dev/null || grep -qP "[\x{4e00}-\x{9fa5}]" "$workflow" 2>/dev/null; then
+ echo " ✓ 包含中文注释(符合项目风格)"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 检查配置区域标记
+ if grep -q "# ====.*配置.*====" "$workflow"; then
+ echo " ✓ 包含配置区域标记"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 检查环境变量配置
+ if grep -q "^env:" "$workflow"; then
+ echo " ✓ 包含环境变量配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ fi
+ done
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 查找并验证 GitHub Actions workflow 文件
+# ============================================
+
+echo "🔍 查找 GitHub Actions workflow 文件"
+GITHUB_WORKFLOWS_DIR="$TEMPLATES_DIR/github/.github/workflows"
+
+if [ -d "$GITHUB_WORKFLOWS_DIR" ]; then
+ WORKFLOW_FILES=$(find "$GITHUB_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
+
+ if [ -n "$WORKFLOW_FILES" ]; then
+ for workflow in $WORKFLOW_FILES; do
+ echo ""
+ echo "🔍 验证 $(basename "$workflow")"
+
+ if validate_file_exists "$workflow" "$(basename "$workflow")"; then
+ # 验证 YAML 语法
+ validate_yaml_syntax "$workflow" "$(basename "$workflow")"
+
+ # 验证 workflow 结构
+ validate_workflow_structure "$workflow"
+ fi
+ done
+ fi
+else
+ echo "ℹ️ GitHub Actions workflows 目录不存在(可选)"
+fi
+
+echo ""
+
+# ============================================
+# 验证特定 workflow 模板
+# ============================================
+
+echo "🔍 验证特定 workflow 模板"
+
+# 检查 standards-check workflow
+STANDARDS_CHECK="$GITEA_WORKFLOWS_DIR/standards-check.yml"
+if [ -f "$STANDARDS_CHECK" ]; then
+ echo ""
+ echo "📋 验证 standards-check.yml:"
+
+ # 检查是否包含格式化检查
+ if grep -q "格式化\|format" "$STANDARDS_CHECK"; then
+ echo " ✓ 包含格式化检查"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 检查是否包含 lint 检查
+ if grep -q "lint\|检查" "$STANDARDS_CHECK"; then
+ echo " ✓ 包含 lint 检查"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+else
+ echo "ℹ️ 未找到 standards-check.yml(可选)"
+fi
+
+# 检查 test workflow
+TEST_WORKFLOW="$GITEA_WORKFLOWS_DIR/test.yml"
+if [ -f "$TEST_WORKFLOW" ]; then
+ echo ""
+ echo "📋 验证 test.yml:"
+
+ # 检查是否包含测试步骤
+ if grep -q "测试\|test" "$TEST_WORKFLOW"; then
+ echo " ✓ 包含测试步骤"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 检查是否配置了测试矩阵
+ if grep -q "strategy:" "$TEST_WORKFLOW" && grep -q "matrix:" "$TEST_WORKFLOW"; then
+ echo " ✓ 配置了测试矩阵"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+else
+ echo "ℹ️ 未找到 test.yml(可选)"
+fi
+
+echo ""
+
+# ============================================
+# 验证 README 文档
+# ============================================
+
+echo "🔍 验证 CI 模板文档"
+
+if [ -d "$TEMPLATES_DIR" ]; then
+ # 查找 README
+ README_FILE=""
+ for name in README.md readme.md README README.txt; do
+ if [ -f "$TEMPLATES_DIR/$name" ]; then
+ README_FILE="$TEMPLATES_DIR/$name"
+ break
+ fi
+ done
+
+ if [ -n "$README_FILE" ]; then
+ echo " ✅ 找到说明文档: $(basename "$README_FILE")"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+
+ # 检查文档内容
+ if grep -qi "使用\|usage\|how to" "$README_FILE"; then
+ echo " ✓ 包含使用说明"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ else
+ echo " ⚠️ 未找到 README 文档(建议添加)"
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 生成验证报告
+# ============================================
+
+echo "========================================"
+echo "📊 验证结果统计"
+echo "========================================"
+echo "✅ 通过: $VALIDATION_PASSED"
+echo "❌ 失败: $VALIDATION_FAILED"
+
+if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
+ echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+else
+ echo "📈 通过率: N/A (无测试项)"
+fi
+
+echo ""
+
+# 写入报告文件
+{
+ echo "CI 模板验证报告"
+ echo "===================="
+ echo ""
+ echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo "模板目录: $TEMPLATES_DIR"
+ echo ""
+ echo "统计结果:"
+ echo " 通过: $VALIDATION_PASSED"
+ echo " 失败: $VALIDATION_FAILED"
+ if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
+ echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+ fi
+ echo ""
+ if [ -s "$ERRORS_FILE" ]; then
+ echo "错误详情:"
+ cat "$ERRORS_FILE"
+ fi
+} > "$REPORT_FILE"
+
+echo "📄 详细报告: $REPORT_FILE"
+echo "========================================"
+
+# 清理临时文件
+rm -f "$ERRORS_FILE"
+
+# 返回结果
+if [ "$VALIDATION_FAILED" -eq 0 ]; then
+ echo "✅ 所有 CI 模板验证通过"
+ exit 0
+else
+ echo "❌ CI 模板验证失败 ($VALIDATION_FAILED 个错误)"
+ exit 1
+fi
diff --git a/tests/templates/validate_cpp_templates.sh b/tests/templates/validate_cpp_templates.sh
new file mode 100644
index 0000000..9e1f9c4
--- /dev/null
+++ b/tests/templates/validate_cpp_templates.sh
@@ -0,0 +1,353 @@
+#!/usr/bin/env sh
+# C++ 模板验证脚本
+
+set -eu
+
+echo "========================================"
+echo "⚙️ C++ 模板验证"
+echo "========================================"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/cpp"
+
+VALIDATION_PASSED=0
+VALIDATION_FAILED=0
+ERRORS_FILE="/tmp/cpp_template_validation_errors.txt"
+REPORT_FILE="$SCRIPT_DIR/cpp_validation_report.txt"
+
+> "$ERRORS_FILE"
+> "$REPORT_FILE"
+
+echo "📁 模板目录: $TEMPLATES_DIR"
+echo ""
+
+# ============================================
+# 辅助函数
+# ============================================
+
+validate_file_exists() {
+ local file="$1"
+ local description="$2"
+
+ if [ -f "$file" ]; then
+ echo " ✅ $description: $(basename "$file")"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: $(basename "$file") - 文件不存在"
+ echo "文件不存在: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+validate_cmake_syntax() {
+ local file="$1"
+ local description="$2"
+
+ # 基础语法检查
+ if [ ! -s "$file" ]; then
+ echo " ❌ $description: 文件为空"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+
+ # 检查必要的 CMake 命令
+ local has_errors=0
+
+ if ! grep -q "cmake_minimum_required" "$file"; then
+ echo " ✗ 缺少 cmake_minimum_required"
+ has_errors=1
+ fi
+
+ if ! grep -q "project(" "$file"; then
+ echo " ✗ 缺少 project()"
+ has_errors=1
+ fi
+
+ if [ $has_errors -eq 0 ]; then
+ echo " ✅ $description: CMake 基础语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: CMake 语法检查失败"
+ echo "CMake 语法错误: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+# ============================================
+# 验证 CMakeLists.txt
+# ============================================
+
+echo "🔍 验证 CMakeLists.txt"
+CMAKE_FILE="$TEMPLATES_DIR/CMakeLists.txt"
+
+if validate_file_exists "$CMAKE_FILE" "CMakeLists.txt"; then
+ # 验证 CMake 语法
+ validate_cmake_syntax "$CMAKE_FILE" "CMakeLists.txt"
+
+ echo " 📋 检查 C++23 配置:"
+
+ # 检查 C++23 标准
+ if grep -q "CMAKE_CXX_STANDARD 23" "$CMAKE_FILE"; then
+ echo " ✓ 配置了 C++23 标准"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 未配置 C++23 标准"
+ echo "未配置 C++23: $CMAKE_FILE" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ # 检查 Modules 支持
+ if grep -q "CMAKE_CXX_SCAN_FOR_MODULES" "$CMAKE_FILE"; then
+ echo " ✓ 启用了 C++ Modules 扫描"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ⚠️ 未启用 C++ Modules 扫描(可选)"
+ fi
+
+ # 检查 import std; 支持
+ if grep -q "CMAKE_EXPERIMENTAL_CXX_IMPORT_STD" "$CMAKE_FILE"; then
+ echo " ✓ 启用了 import std; 支持"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ⚠️ 未启用 import std; 支持(可选)"
+ fi
+
+ # 检查编译命令导出
+ if grep -q "CMAKE_EXPORT_COMPILE_COMMANDS" "$CMAKE_FILE"; then
+ echo " ✓ 启用了编译命令导出(用于 clangd)"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ⚠️ 未启用编译命令导出"
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .clang-format
+# ============================================
+
+echo "🔍 验证 .clang-format"
+CLANG_FORMAT="$TEMPLATES_DIR/.clang-format"
+
+if validate_file_exists "$CLANG_FORMAT" ".clang-format"; then
+ # 验证 YAML 语法
+ if command -v yamllint >/dev/null 2>&1; then
+ if yamllint -d relaxed "$CLANG_FORMAT" >/dev/null 2>&1; then
+ echo " ✓ YAML 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ YAML 语法错误"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ fi
+
+ # 验证关键配置
+ echo " 📋 检查格式化配置:"
+
+ if grep -q "^Language:" "$CLANG_FORMAT" && grep -q "Cpp" "$CLANG_FORMAT"; then
+ echo " ✓ 配置了 Language: Cpp"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ if grep -q "^BasedOnStyle:" "$CLANG_FORMAT"; then
+ style=$(grep "^BasedOnStyle:" "$CLANG_FORMAT" | awk '{print $2}')
+ echo " ✓ 基于风格: $style"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ if grep -q "^Standard:" "$CLANG_FORMAT"; then
+ std=$(grep "^Standard:" "$CLANG_FORMAT" | awk '{print $2}')
+ echo " ✓ C++ 标准: $std"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 检查缩进配置
+ if grep -q "^IndentWidth:" "$CLANG_FORMAT"; then
+ echo " ✓ 配置了缩进宽度"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .clangd
+# ============================================
+
+echo "🔍 验证 .clangd"
+CLANGD="$TEMPLATES_DIR/.clangd"
+
+if validate_file_exists "$CLANGD" ".clangd"; then
+ # 验证 YAML 语法
+ if command -v yamllint >/dev/null 2>&1; then
+ if yamllint -d relaxed "$CLANGD" >/dev/null 2>&1; then
+ echo " ✓ YAML 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ YAML 语法错误"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ fi
+
+ # 验证 CompileFlags
+ echo " 📋 检查 clangd 配置:"
+
+ if grep -q "CompileFlags:" "$CLANGD"; then
+ echo " ✓ 包含 CompileFlags 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+
+ # 检查 C++23 标准
+ if grep -q "std=c++23" "$CLANGD"; then
+ echo " ✓ 配置了 -std=c++23"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ fi
+
+ # 验证 CompilationDatabase
+ if grep -q "CompilationDatabase:" "$CLANGD"; then
+ echo " ✓ 配置了 CompilationDatabase 路径"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 验证 Index 配置
+ if grep -q "Index:" "$CLANGD"; then
+ echo " ✓ 配置了索引选项"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 conanfile.txt
+# ============================================
+
+echo "🔍 验证 conanfile.txt"
+CONANFILE="$TEMPLATES_DIR/conanfile.txt"
+
+if validate_file_exists "$CONANFILE" "conanfile.txt"; then
+ echo " 📋 检查 Conan 配置:"
+
+ # 验证必要的节
+ if grep -q "^\[requires\]" "$CONANFILE"; then
+ echo " ✓ 包含 [requires] 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ⚠️ 缺少 [requires] 配置(可选)"
+ fi
+
+ if grep -q "^\[generators\]" "$CONANFILE"; then
+ echo " ✓ 包含 [generators] 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+
+ # 检查 CMakeDeps 和 CMakeToolchain
+ if grep -q "CMakeDeps" "$CONANFILE"; then
+ echo " ✓ 配置了 CMakeDeps 生成器"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ if grep -q "CMakeToolchain" "$CONANFILE"; then
+ echo " ✓ 配置了 CMakeToolchain 生成器"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ fi
+
+ if grep -q "^\[options\]" "$CONANFILE"; then
+ echo " ✓ 包含 [options] 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 CMakeUserPresets.json
+# ============================================
+
+echo "🔍 验证 CMakeUserPresets.json"
+CMAKE_PRESETS="$TEMPLATES_DIR/CMakeUserPresets.json"
+
+if validate_file_exists "$CMAKE_PRESETS" "CMakeUserPresets.json"; then
+ # 验证 JSON 语法
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 -m json.tool "$CMAKE_PRESETS" >/dev/null 2>&1; then
+ echo " ✓ JSON 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ JSON 语法错误"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ fi
+
+ # 验证配置项
+ echo " 📋 检查 CMake Presets:"
+
+ if grep -q "\"version\"" "$CMAKE_PRESETS"; then
+ echo " ✓ 包含 version 字段"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ if grep -q "\"configurePresets\"" "$CMAKE_PRESETS"; then
+ echo " ✓ 包含 configurePresets"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ if grep -q "\"buildPresets\"" "$CMAKE_PRESETS"; then
+ echo " ✓ 包含 buildPresets"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 生成验证报告
+# ============================================
+
+echo "========================================"
+echo "📊 验证结果统计"
+echo "========================================"
+echo "✅ 通过: $VALIDATION_PASSED"
+echo "❌ 失败: $VALIDATION_FAILED"
+echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+echo ""
+
+# 写入报告文件
+{
+ echo "C++ 模板验证报告"
+ echo "===================="
+ echo ""
+ echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo "模板目录: $TEMPLATES_DIR"
+ echo ""
+ echo "统计结果:"
+ echo " 通过: $VALIDATION_PASSED"
+ echo " 失败: $VALIDATION_FAILED"
+ echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+ echo ""
+ if [ -s "$ERRORS_FILE" ]; then
+ echo "错误详情:"
+ cat "$ERRORS_FILE"
+ fi
+} > "$REPORT_FILE"
+
+echo "📄 详细报告: $REPORT_FILE"
+echo "========================================"
+
+# 清理临时文件
+rm -f "$ERRORS_FILE"
+
+# 返回结果
+if [ "$VALIDATION_FAILED" -eq 0 ]; then
+ echo "✅ 所有 C++ 模板验证通过"
+ exit 0
+else
+ echo "❌ C++ 模板验证失败 ($VALIDATION_FAILED 个错误)"
+ exit 1
+fi
diff --git a/tests/templates/validate_python_templates.sh b/tests/templates/validate_python_templates.sh
new file mode 100644
index 0000000..f97a7d1
--- /dev/null
+++ b/tests/templates/validate_python_templates.sh
@@ -0,0 +1,329 @@
+#!/usr/bin/env sh
+# Python 模板验证脚本
+
+set -eu
+
+echo "========================================"
+echo "🐍 Python 模板验证"
+echo "========================================"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/python"
+
+VALIDATION_PASSED=0
+VALIDATION_FAILED=0
+ERRORS_FILE="/tmp/python_template_validation_errors.txt"
+REPORT_FILE="$SCRIPT_DIR/python_validation_report.txt"
+
+> "$ERRORS_FILE"
+> "$REPORT_FILE"
+
+echo "📁 模板目录: $TEMPLATES_DIR"
+echo ""
+
+# ============================================
+# 辅助函数
+# ============================================
+
+validate_file_exists() {
+ local file="$1"
+ local description="$2"
+
+ if [ -f "$file" ]; then
+ echo " ✅ $description: $(basename "$file")"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: $(basename "$file") - 文件不存在"
+ echo "文件不存在: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+validate_toml_syntax() {
+ local file="$1"
+ local description="$2"
+
+ if ! command -v python3 >/dev/null 2>&1; then
+ echo " ⚠️ $description: 跳过(Python3 未安装)"
+ return 0
+ fi
+
+ if python3 << EOF
+import sys
+try:
+ import tomli
+except ImportError:
+ try:
+ import tomllib as tomli
+ except ImportError:
+ import toml as tomli
+
+try:
+ with open("$file", "rb") as f:
+ tomli.load(f)
+ sys.exit(0)
+except Exception as e:
+ print(f"TOML 语法错误: {e}", file=sys.stderr)
+ sys.exit(1)
+EOF
+ then
+ echo " ✅ $description: TOML 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ return 0
+ else
+ echo " ❌ $description: TOML 语法错误"
+ echo "TOML 语法错误: $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ return 1
+ fi
+}
+
+validate_required_sections() {
+ local file="$1"
+ local sections="$2"
+
+ for section in $sections; do
+ if grep -q "^\[$section\]" "$file"; then
+ echo " ✓ 包含 [$section] 配置"
+ else
+ echo " ✗ 缺少 [$section] 配置"
+ echo "缺少配置节: $section in $file" >> "$ERRORS_FILE"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ done
+}
+
+# ============================================
+# 验证 pyproject.toml
+# ============================================
+
+echo "🔍 验证 pyproject.toml"
+PYPROJECT="$TEMPLATES_DIR/pyproject.toml"
+
+if validate_file_exists "$PYPROJECT" "pyproject.toml"; then
+ # 验证 TOML 语法
+ validate_toml_syntax "$PYPROJECT" "pyproject.toml"
+
+ # 验证必要的配置节
+ echo " 📋 检查必要配置节:"
+ validate_required_sections "$PYPROJECT" "tool.black tool.isort tool.pytest.ini_options"
+
+ # 验证 black 配置
+ if grep -q "line-length = 80" "$PYPROJECT"; then
+ echo " ✓ black line-length 配置正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ black line-length 配置缺失或不正确"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ # 验证 isort 配置
+ if grep -q "profile = \"google\"" "$PYPROJECT"; then
+ echo " ✓ isort profile 配置正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ isort profile 配置缺失或不正确"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .flake8
+# ============================================
+
+echo "🔍 验证 .flake8"
+FLAKE8="$TEMPLATES_DIR/.flake8"
+
+if validate_file_exists "$FLAKE8" ".flake8"; then
+ # 验证 [flake8] 节
+ if grep -q "^\[flake8\]" "$FLAKE8"; then
+ echo " ✓ 包含 [flake8] 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+
+ # 验证 max-line-length
+ if grep -q "^max-line-length" "$FLAKE8"; then
+ echo " ✓ 配置了 max-line-length"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 max-line-length 配置"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+
+ # 验证 extend-ignore
+ if grep -q "^extend-ignore" "$FLAKE8" || grep -q "^ignore" "$FLAKE8"; then
+ echo " ✓ 配置了错误忽略规则"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ else
+ echo " ✗ 缺少 [flake8] 配置节"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .pylintrc
+# ============================================
+
+echo "🔍 验证 .pylintrc"
+PYLINTRC="$TEMPLATES_DIR/.pylintrc"
+
+if validate_file_exists "$PYLINTRC" ".pylintrc"; then
+ # 验证关键配置节
+ for section in "MASTER" "MESSAGES CONTROL" "FORMAT"; do
+ if grep -qi "^\[$section\]" "$PYLINTRC"; then
+ echo " ✓ 包含 [$section] 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ 缺少 [$section] 配置"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ done
+
+ # 验证 max-line-length
+ if grep -q "^max-line-length" "$PYLINTRC"; then
+ echo " ✓ 配置了 max-line-length"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .pre-commit-config.yaml
+# ============================================
+
+echo "🔍 验证 .pre-commit-config.yaml"
+PRECOMMIT="$TEMPLATES_DIR/.pre-commit-config.yaml"
+
+if validate_file_exists "$PRECOMMIT" ".pre-commit-config.yaml"; then
+ # 验证 YAML 语法
+ if command -v yamllint >/dev/null 2>&1; then
+ if yamllint -d relaxed "$PRECOMMIT" >/dev/null 2>&1; then
+ echo " ✓ YAML 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ YAML 语法错误"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ fi
+
+ # 验证包含 pre-commit hooks
+ if grep -q "^repos:" "$PRECOMMIT"; then
+ echo " ✓ 包含 repos 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+
+ # 检查常用 hooks
+ for hook in "black" "isort" "flake8"; do
+ if grep -q "$hook" "$PRECOMMIT"; then
+ echo " ✓ 配置了 $hook hook"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+ done
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 .editorconfig
+# ============================================
+
+echo "🔍 验证 .editorconfig"
+EDITORCONFIG="$TEMPLATES_DIR/.editorconfig"
+
+if validate_file_exists "$EDITORCONFIG" ".editorconfig"; then
+ # 验证包含 root 标记
+ if grep -q "^root = true" "$EDITORCONFIG"; then
+ echo " ✓ 包含 root = true"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+
+ # 验证 Python 配置节
+ if grep -q "^\[\*.py\]" "$EDITORCONFIG" || grep -q "^\[*.py\]" "$EDITORCONFIG"; then
+ echo " ✓ 包含 Python 文件配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 验证 VSCode 配置
+# ============================================
+
+echo "🔍 验证 .vscode/settings.json"
+VSCODE_SETTINGS="$TEMPLATES_DIR/.vscode/settings.json"
+
+if validate_file_exists "$VSCODE_SETTINGS" "settings.json"; then
+ # 验证 JSON 语法
+ if command -v python3 >/dev/null 2>&1; then
+ if python3 -m json.tool "$VSCODE_SETTINGS" >/dev/null 2>&1; then
+ echo " ✓ JSON 语法正确"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ else
+ echo " ✗ JSON 语法错误"
+ VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
+ fi
+ fi
+
+ # 验证 Python 相关配置
+ if grep -q "python" "$VSCODE_SETTINGS"; then
+ echo " ✓ 包含 Python 配置"
+ VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
+ fi
+fi
+
+echo ""
+
+# ============================================
+# 生成验证报告
+# ============================================
+
+echo "========================================"
+echo "📊 验证结果统计"
+echo "========================================"
+echo "✅ 通过: $VALIDATION_PASSED"
+echo "❌ 失败: $VALIDATION_FAILED"
+echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+echo ""
+
+# 写入报告文件
+{
+ echo "Python 模板验证报告"
+ echo "===================="
+ echo ""
+ echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo "模板目录: $TEMPLATES_DIR"
+ echo ""
+ echo "统计结果:"
+ echo " 通过: $VALIDATION_PASSED"
+ echo " 失败: $VALIDATION_FAILED"
+ echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
+ echo ""
+ if [ -s "$ERRORS_FILE" ]; then
+ echo "错误详情:"
+ cat "$ERRORS_FILE"
+ fi
+} > "$REPORT_FILE"
+
+echo "📄 详细报告: $REPORT_FILE"
+echo "========================================"
+
+# 清理临时文件
+rm -f "$ERRORS_FILE"
+
+# 返回结果
+if [ "$VALIDATION_FAILED" -eq 0 ]; then
+ echo "✅ 所有 Python 模板验证通过"
+ exit 0
+else
+ echo "❌ Python 模板验证失败 ($VALIDATION_FAILED 个错误)"
+ exit 1
+fi