Skip to content

MR品質ゲート設計

Issue #7: MRレビューワークフロー・報告資料テンプレート設計


1. 概要

MRの品質を自動的に検証し、基準を満たさないMRのマージを防止する仕組みを設計する。

品質ゲートの目的:

  • レビュワーの負担軽減(基本的な品質は自動で担保)
  • MRテンプレートの記入漏れ防止
  • コード品質の一定水準維持
  • マージ条件の自動制御

2. GitLab CI パイプライン構成

2.1 パイプライン全体図

┌──────────────────────────────────────────────────────────────────┐
│                     MR パイプライン                                │
├──────────┬──────────┬──────────┬──────────┬──────────────────────┤
│ Stage 1  │ Stage 2  │ Stage 3  │ Stage 4  │ Stage 5             │
│ validate │ lint     │ test     │ build    │ report              │
├──────────┼──────────┼──────────┼──────────┼──────────────────────┤
│ template │ eslint   │ unit     │ build    │ coverage-report     │
│ -check   │ prettier │ integr.  │ check    │ quality-summary     │
│          │ stylelint│ e2e(opt) │          │                     │
│          │ secrets  │          │          │                     │
└──────────┴──────────┴──────────┴──────────┴──────────────────────┘

2.2 .gitlab-ci.yml 構成

yaml
stages:
  - validate
  - lint
  - test
  - build
  - report

# ========================================
# Stage 1: validate - テンプレート準拠チェック
# ========================================
template-check:
  stage: validate
  image: alpine:latest
  script:
    - apk add --no-cache bash jq curl
    - chmod +x scripts/check-mr-template.sh
    - bash scripts/check-mr-template.sh
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

# ========================================
# Stage 2: lint - コード品質チェック
# ========================================
eslint:
  stage: lint
  image: node:20-alpine
  script:
    - npm ci
    - npx eslint . --format gitlab
  artifacts:
    reports:
      codequality: gl-codequality.json
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

prettier:
  stage: lint
  image: node:20-alpine
  script:
    - npm ci
    - npx prettier --check .
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

secret-detection:
  stage: lint
  image: alpine:latest
  script:
    - apk add --no-cache git
    - git log --all --diff-filter=A --name-only --pretty=format: | sort -u > new_files.txt
    - chmod +x scripts/check-secrets.sh
    - bash scripts/check-secrets.sh
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

# ========================================
# Stage 3: test - テスト実行
# ========================================
unit-test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm run test -- --coverage
  artifacts:
    reports:
      junit: junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
  coverage: '/All files\s*\|\s*([\d.]+)/'
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

# ========================================
# Stage 4: build - ビルド確認
# ========================================
build-check:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - npm run build
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  allow_failure: false

# ========================================
# Stage 5: report - レポート生成
# ========================================
quality-summary:
  stage: report
  image: alpine:latest
  script:
    - echo "All quality gates passed"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  when: on_success

3. テンプレート必須項目の自動検証スクリプト

3.1 スクリプト設計: scripts/check-mr-template.sh

bash
#!/bin/bash
set -euo pipefail

# ============================================
# MRテンプレート必須項目チェックスクリプト
# ============================================
# GitLab CI から呼び出され、MR説明欄の
# テンプレート記入状況を検証する。
# ============================================

GITLAB_API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}"
MR_DESCRIPTION=$(curl -s --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" "${GITLAB_API_URL}" | jq -r '.description // ""')

ERRORS=()
WARNINGS=()

# --------------------------------------------
# 必須項目チェック関数
# --------------------------------------------
check_section() {
  local section_name="$1"
  local pattern="$2"
  local is_required="${3:-true}"

  if ! echo "${MR_DESCRIPTION}" | grep -qP "${pattern}"; then
    if [ "${is_required}" = "true" ]; then
      ERRORS+=("ERROR: 必須項目「${section_name}」が未記入です")
    else
      WARNINGS+=("WARNING: 推奨項目「${section_name}」が未記入です")
    fi
  fi
}

# 対応Issueセクションにプレースホルダーでなく実際のIssue番号が入っているか
check_issue_link() {
  if ! echo "${MR_DESCRIPTION}" | grep -qP 'Closes #\d+'; then
    ERRORS+=("ERROR: 「対応Issue」にIssue番号が記載されていません(Closes #<番号> の形式で記載してください)")
  fi
}

# --------------------------------------------
# 各必須項目のチェック実行
# --------------------------------------------

echo "=============================="
echo "MRテンプレート準拠チェック開始"
echo "=============================="

# 1. 対応Issue
check_issue_link

# 2. 変更の目的(プレースホルダーのコメントだけでないこと)
check_section "変更の目的" '## 実装概要[\s\S]*### 変更の目的\s*\n(?!<!--).+'

# 3. 変更内容(箇条書きが最低1項目あること)
check_section "変更内容" '### 変更内容[\s\S]*?- [^\s]'

# 4. 動作確認結果(正常系に最低1行のデータがあること)
check_section "動作確認結果(正常系)" '### 正常系の確認[\s\S]*?\| .+\| .+\| .+\| .+\| (OK|NG)'

# 5. テスト結果(チェックボックスが最低1つチェックされていること)
check_section "テスト結果" '\[x\].*テスト'

# 6. セルフチェックリスト(コーディング規約セクションのチェック)
check_section "セルフチェックリスト" '### コーディング規約[\s\S]*?\[x\]'

# 7. AI利用について(使用有無の明記)
check_section "AI利用について" '## AI利用について[\s\S]*?\[x\]'

# --------------------------------------------
# 結果出力
# --------------------------------------------
echo ""
echo "--- チェック結果 ---"

if [ ${#WARNINGS[@]} -gt 0 ]; then
  for warning in "${WARNINGS[@]}"; do
    echo "${warning}"
  done
fi

if [ ${#ERRORS[@]} -gt 0 ]; then
  echo ""
  for error in "${ERRORS[@]}"; do
    echo "${error}"
  done
  echo ""
  echo "=============================="
  echo "チェック結果: NG (${#ERRORS[@]} 件のエラー)"
  echo "=============================="
  echo ""
  echo "MRテンプレートの必須項目を記入してください。"
  echo "テンプレート: docs/workflows/mr-template.md を参照"
  exit 1
else
  echo ""
  echo "=============================="
  echo "チェック結果: OK"
  echo "=============================="
  exit 0
fi

3.2 秘密情報検出スクリプト: scripts/check-secrets.sh

bash
#!/bin/bash
set -euo pipefail

# ============================================
# 秘密情報漏洩チェックスクリプト
# ============================================

ERRORS=()

# 秘密情報パターン
PATTERNS=(
  'PRIVATE.?KEY'
  'SECRET.?KEY'
  'API.?KEY\s*=\s*["\x27][a-zA-Z0-9]'
  'TOKEN\s*=\s*["\x27][a-zA-Z0-9]'
  'PASSWORD\s*=\s*["\x27][a-zA-Z0-9]'
  'aws_access_key_id\s*='
  'aws_secret_access_key\s*='
)

# .env ファイルがコミットされていないか
if git diff --name-only origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD | grep -qE '\.env($|\.)'; then
  ERRORS+=("ERROR: .env ファイルがコミットに含まれています")
fi

# 差分ファイルに対して秘密情報パターンを検索
CHANGED_FILES=$(git diff --name-only origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD || true)

for file in ${CHANGED_FILES}; do
  if [ -f "${file}" ]; then
    for pattern in "${PATTERNS[@]}"; do
      if grep -qPi "${pattern}" "${file}" 2>/dev/null; then
        ERRORS+=("WARNING: ${file} に秘密情報の可能性があるパターンが検出されました (${pattern})")
      fi
    done
  fi
done

if [ ${#ERRORS[@]} -gt 0 ]; then
  echo "=============================="
  echo "秘密情報チェック結果: NG"
  echo "=============================="
  for error in "${ERRORS[@]}"; do
    echo "${error}"
  done
  exit 1
else
  echo "=============================="
  echo "秘密情報チェック結果: OK"
  echo "=============================="
  exit 0
fi

4. マージ条件

4.1 必須条件(全て満たすこと)

#条件制御方法
1CI パイプライン全ステージ通過GitLab: Pipelines must succeed
2レビュワー1名以上の ApproveGitLab: Required approvals: 1
3未解決の Discussion なしGitLab: All discussions must be resolved
4コンフリクトなしGitLab: 自動チェック
5MR作成者による自己承認なしGitLab: Prevent approval by author

4.2 GitLab プロジェクト設定

Settings > Merge requests > Merge request approvals:
  - Required approvals: 1
  - Eligible approvers: プロジェクトメンバー(Developer以上)
  - [x] Prevent approval by author
  - [x] Remove all approvals when commits are added

Settings > Merge requests > Merge checks:
  - [x] Pipelines must succeed
  - [x] All discussions must be resolved
  - [ ] Skipped pipelines are considered successful (OFF)

Settings > Repository > Protected branches (main/develop):
  - Allowed to merge: Maintainers
  - Allowed to push: No one (MR経由のみ)

5. 品質ゲート運用ルール

5.1 ゲート通過基準

ゲート基準失敗時の対応
テンプレートチェック必須7項目が全て記入済み作成者がテンプレートを修正して再プッシュ
lintエラー 0件作成者がコードを修正して再プッシュ
テスト全テストパス作成者がテスト失敗を修正して再プッシュ
ビルドビルド成功作成者がビルドエラーを修正して再プッシュ
秘密情報検出検出 0件作成者が秘密情報を除去して再プッシュ

5.2 例外対応

CI パイプラインが技術的な理由(インフラ障害等)で失敗する場合:

  1. Maintainer が原因を確認
  2. コード起因でない場合はパイプラインを再実行
  3. 再実行でも解消しない場合は PM 判断でマージ可否を決定

6. CI/CD 変数設定

パイプラインで使用する変数を GitLab CI/CD Variables に設定する。

変数名用途マスク保護
GITLAB_API_TOKENMR説明欄取得用APIトークンYesYes

設定場所: Settings > CI/CD > Variables


7. ディレクトリ構成

品質ゲート関連ファイルの配置:

project-root/
├── .gitlab-ci.yml                          # パイプライン定義
├── .gitlab/
│   └── merge_request_templates/
│       └── default.md                      # MRテンプレート
├── scripts/
│   ├── check-mr-template.sh                # テンプレート検証スクリプト
│   └── check-secrets.sh                    # 秘密情報検出スクリプト
└── docs/
    └── workflows/
        ├── mr-template.md                  # テンプレート設計書(本ドキュメント群)
        ├── review-workflow.md              # レビューワークフロー設計
        └── quality-gate.md                 # 品質ゲート設計(本ドキュメント)