SonarQube 이슈 수만 개, AI 에이전트로 자동 처리하기

Table of Contents
- 핵심 아이디어
- 플러그인 아키텍처
- 디렉토리 구조
- 7개 스킬의 역할
- 워크플로우: 이슈 하나의 여정
- 명령어 흐름
- 1단계: 이슈 수집
- 2단계: 이슈 배정
- 3단계: 자동 처리 (담당 이슈 전체 병렬, MAX_CONCURRENT_AGENTS 제어)
- 상태 머신
- 스킬 간 데이터 흐름
- 핵심 설계: AI 에이전트 제어
- 독립 리뷰 에이전트
- 기술적 차단 (settings.json)
- Red Flags — 에이전트 일탈 방지 9대 규칙
- TDD 게이팅
- 특성화 테스트 패턴 (Characterization Test)
- 8단계 리포트 체인
- 커밋 메시지 (복사용)
- PR 설명 (복사용)
- 협업 구조: Google Sheets 기반
- 다인 동시 작업
- 리더 분배 → 팀원 실행 흐름
- Git Worktree 격리
- 추적과 분석: SQLite + 대시보드
- SQLite 추적 DB
- 자동 상태 추적
- HTML 대시보드
- 프로젝트별 전문화
- Coding Rules 주입
- Jira 모드 / 리포트 모드
- 디버그 모드
- Bash 실행 9대 원칙
- 실전 시나리오
- 1. 이슈 수집
- 2. 담당자별 배정
- 3. 자동 처리
- 4. 현황 확인
- 5. 완료 이슈 승인
- 6. 수동 커밋 & PR (07_final_deliverable.md 참조)
- 정리
- 기술 스택
SonarQube를 여러 프로젝트에 적용하면 수천~수만 개의 이슈가 발생한다.
10개는 괜찮고, 100개는 힘들고, 수만 개는 불가능하다. 기존 수동 프로세스는 이랬다.
대시보드 확인 → 이슈 열람 → 분석 → 수정 → 테스트 → PR → 리뷰
누가 어떤 이슈를 처리 중인지 파악이 안 되고, 사람마다 분석 깊이와 수정 품질이 다르고, 같은 이슈를 두 사람이 동시에 처리하는 일도 생겼다. 수정 근거와 분석 과정이 기록으로 남지도 않았다.
그래서 AI 에이전트가 이슈를 하나씩 가져가서 전체 파이프라인을 수행하는 시스템을 만들었다.
핵심 아이디어
Google Sheets를 작업 대기열로 사용하고, Claude Code 서브에이전트가 이슈를 하나씩 가져가서 전체 파이프라인을 수행한다.
SonarQube API → Google Sheets(작업 대기열) → Claude Code 서브에이전트 × 5 병렬
↕ ↓
여러 담당자가 배정 분석 → 리뷰 → 개발 → 리뷰 → 테스트 → 완료
↓
8단계 리포트 + SQLite 추적 DB
설계 과정에서 기존에 사용하던 도구들의 패턴을 차용했다.
| 차용 출처 | 적용 영역 | 설명 |
|---|---|---|
| FAS-BackEnd FAS-MASTER SKILL | 프로그램 수정 규칙 | 프로젝트별 코딩 컨벤션과 아키텍처 규칙을 CODING_RULES_PATH로 주입 |
| SuperSkills | SQLite 로그 저장 | 실행 추적, 상태 전이, 에러 누적을 SQLite DB로 기록 |
| SuperPowers | TDD 개발 방식 | 특성화 테스트(Characterization Test) 패턴과 TDD 게이팅 판정 |
플러그인 아키텍처
디렉토리 구조
claude-plugin-sonar/
├── .claude-plugin/plugin.json # 플러그인 매니페스트
├── settings.json # 보안 정책 + 이벤트 훅
├── .env.example # 환경 설정 템플릿
├── guides/ # 에이전트 행동 규칙
│ ├── bash-execution-rules.md # Bash 실행 9대 원칙
│ └── red-flags.md # 에이전트 일탈 방지 9대 규칙
├── hooks/
│ └── save-history.sh # 디버그 모드 대화 기록 저장
├── skills/ # 7개 스킬 (마이크로서비스 구조)
│ ├── sonar/ # 오케스트레이터 (유일한 진입점)
│ ├── sonar-intake/ # 이슈 수집
│ ├── sonar-analyze/ # 분석 + Jira/리포트 생성
│ ├── sonar-develop/ # 코드 수정 + TDD
│ ├── sonar-review/ # 독립 검증 에이전트
│ ├── sonar-common/ # 공유 라이브러리
│ └── sonar-dashboard/ # HTML 대시보드 생성
└── data/ # SonarQube JSON 캐시
7개 스킬의 역할
| 스킬 | 역할 | 호출 방식 |
|---|---|---|
sonar | 오케스트레이터 — 전체 흐름 제어 | /sonar (유일한 진입점) |
sonar-intake | SonarQube API → Google Sheets 업로드 | 오케스트레이터가 호출 |
sonar-analyze | 근본 원인 분석, 해결 방안, TDD 판정 | 오케스트레이터가 호출 |
sonar-review | 독립 검증 (분석/수정 모두 검증) | 오케스트레이터가 호출 |
sonar-develop | 코드 수정 + 테스트 + 최종 산출물 | 오케스트레이터가 호출 |
sonar-common | 공유 유틸리티 (Sheets, SQLite 등) | 다른 스킬이 import |
sonar-dashboard | HTML KPI 대시보드 생성 | /sonar-dashboard |
워크플로우: 이슈 하나의 여정
명령어 흐름
# 1단계: 이슈 수집
/sonar intake
# 2단계: 이슈 배정
/sonar claim 5 # 개인에게 N개 배정
/sonar claim --distribute # 팀 멤버에게 균등 분배
# 3단계: 자동 처리 (담당 이슈 전체 병렬, MAX_CONCURRENT_AGENTS 제어)
/sonar run상태 머신
대기 → CLAIMED → ANALYZING ⟷ REVIEW_ANALYSIS → JIRA_CREATED
↓ (4회 실패)
BLOCKED
JIRA_CREATED → DEVELOPING ⟷ REVIEW_FIX → TESTING → DONE
↓ (4회 실패)
BLOCKED
각 전이마다 리포트가 생성되고, 스프레드시트 상태가 실시간 업데이트되며, SQLite 추적 DB에 실행 기록이 남는다.
스킬 간 데이터 흐름
sonar (오케스트레이터)
│
├─ sonar-intake → SonarQube API 이슈 수집 → Google Sheets 업로드
│
├─ sonar-analyze → 01_analysis_report.md / 03_jira_created.md
│
├─ sonar-review("analysis") → 02_analysis_review.md (PASS/FAIL)
│
├─ sonar-develop → 04_fix_report.md / 06_test_report.md / 07_final_deliverable.md / 08_cto_approval.md
│
└─ sonar-review("fix") → 05_fix_review.md (PASS/FAIL)
핵심 설계: AI 에이전트 제어
독립 리뷰 에이전트
분석하는 에이전트 ≠ 검증하는 에이전트
sonar-analyze → 01_analysis_report.md → sonar-review (별개의 서브에이전트)
↓
PASS → 다음 단계
FAIL → 피드백 반영 후 재시도 (최대 3회)
4회 실패 → BLOCKED (사람 개입)
코드 수정도 동일한 구조다. sonar-develop이 수정하면, sonar-review가 독립적으로 검증한다.
기술적 차단 (settings.json)
에이전트의 위험 행동을 시스템 레벨에서 차단한다.
{
"permissions": {
"deny": [
"Bash(git commit*)",
"Bash(git push*)",
"Bash(gh pr create*)",
"Bash(git worktree remove*)",
"Bash(rm -rf *)",
"Bash(git reset --hard*)"
]
}
}AI는 산출물을 작성하고, 실행은 사람이 한다.
Red Flags — 에이전트 일탈 방지 9대 규칙
| # | 규칙 | AI의 변명 | 현실 |
|---|---|---|---|
| 1 | 이슈 그룹화 금지 | "같은 파일이니 묶으면 효율적" | 맥락, 영향범위, 수정 방법이 다르다 |
| 2 | 상태 건너뛰기 금지 | "간단한 수정이라 리뷰 생략 가능" | 간단한 수정이 사이드이펙트가 가장 많다 |
| 3 | 자동 커밋/PR 금지 | "검증 완료했으니 커밋해도 안전" | 에이전트 검증 ≠ 사용자 검증 |
| 4 | 워크플로우 조기 종료 금지 | — | — |
| 5 | 리뷰 결과 무시 금지 | — | — |
| 6 | 스프레드시트 상태 확인 생략 금지 | — | — |
| 7 | 보고서 생략 금지 | — | — |
| 8 | Worktree 삭제 금지 | — | — |
| 9 | 동시 작업 충돌 방지 | — | — |
모든 규칙에 "합리적 변명 vs 현실" 대조표를 포함했다.
| 변명 | 현실 |
|---|---|
| "이번 한 번만 예외로" | 예외 없음. 한 번이 선례가 된다 |
| "시간을 절약하기 위해" | 잘못된 결과를 되돌리는 시간이 더 크다 |
| "사용자가 원할 것이다" | 사용자 의도를 추측하지 마라 |
TDD 게이팅
모든 이슈에 테스트를 작성하는 것은 비효율적이다. 분석 단계에서 TDD 필요 여부를 판정한다.
| TDD_REQUIRED (테스트 필수) | TDD_SKIP (테스트 생략) |
|---|---|
| BUG 또는 VULNERABILITY | CODE_SMELL + MINOR/INFO |
| SECURITY 또는 RELIABILITY | 변수명/메서드명 변경만 |
| 함수 시그니처/반환값 변경 | 주석/문서/로깅 변경 |
| 영향 함수 3개 이상 리팩터링 | 미사용 import 제거 |
| 기존 테스트가 없는 영역 | 코드 포맷팅 |
특성화 테스트 패턴 (Characterization Test)
1. 수정 전 동작을 캡처하는 테스트 작성
2. BEFORE 실행 → GREEN 확인 (현재 동작 보존 검증)
3. 코드 수정
4. AFTER 실행 → GREEN 확인 (기존 동작 미파괴 검증)
핵심: "이상적인 동작"이 아니라 "현재 동작"을 기준으로 테스트한다. 목표는 코드 품질 개선이지, 기존 로직 변경이 아니다.
8단계 리포트 체인
이슈 1건 처리 시 8개의 리포트 파일이 생성된다.
reports/{issue_key}/
├── 01_analysis_report.md ← 근본 원인 분석, 해결 방안, TDD 판정
├── 02_analysis_review.md ← 독립 에이전트의 분석 검증 (PASS/FAIL)
├── 03_jira_created.md ← Jira 티켓 생성 확인
├── 04_fix_report.md ← 수정 내역, 변경 파일, 사이드이펙트
├── 05_fix_review.md ← 독립 에이전트의 수정 검증 (PASS/FAIL)
├── 06_test_report.md ← 테스트 실행 결과
├── 07_final_deliverable.md ← 커밋 메시지 + PR 설명 (복사용)
└── 08_cto_approval.md ← 전체 과정 종합, 승인 요청
07_final_deliverable.md는 이런 식이다.
## 커밋 메시지 (복사용)
fix(UserService): Remove unused parameter 'name' from validateEmail
- Removed unused parameter flagged by SonarQube rule java:S1172
- No behavioral changes, verified by characterization tests
Issue: ODIN-123
## PR 설명 (복사용)
...사람은 리포트를 검토하고, 동의하면 커밋 메시지를 복사해서 커밋/PR을 올린다.
협업 구조: Google Sheets 기반
다인 동시 작업
방법 1: 개별 claim — 각 담당자가 원하는 만큼 가져간다.
담당자 A: /sonar claim 10 → 10개 이슈 배정 → /sonar run
담당자 B: /sonar claim 5 → 5개 이슈 배정 → /sonar run
담당자 C: /sonar claim 20 → 20개 이슈 배정 → /sonar run방법 2: 균등 분배 — 전체 대기 이슈를 팀에게 한 번에 분배한다.
/sonar claim --distribute # TEAM_MEMBERS 환경변수 사용
/sonar claim --distribute --members "A,B,C" # 멤버 직접 지정심각도 순(BLOCKER → HIGH → MEDIUM → LOW → INFO)으로 정렬한 뒤 라운드로빈으로 배정한다. 10개 이슈에 3명이면 A: 4개, B: 3개, C: 3개.
리더 분배 → 팀원 실행 흐름
리더 (1회)
1. /sonar intake ← SonarQube → Sheets 수집
2. /sonar claim --distribute ← 심각도 순 라운드로빈 분배
↓
Google Sheets (SSOT)
3,000개 이슈 / 상태: CLAIMED / 담당자 자동 배정 완료
↓
A: 1,000개 / B: 1,000개 / C: 1,000개
팀원 각자 (동시)
팀원 A: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
팀원 B: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
팀원 C: /sonar run → 서브에이전트 ×5 병렬 → 분석 → 리뷰 → 개발 → 테스트
↓
3,000개 이슈 처리 완료 → reports/ + worktrees/
리더가 2개 명령어로 준비하면, 팀원은
/sonar run한 줄이면 끝.
- Google Sheets = Single Source of Truth (중앙 상태 저장소)
- 누가 어떤 이슈를 처리 중인지 실시간 확인
- 각 담당자는 자기 로컬 환경에서 독립 실행
- 서브에이전트 최대 5개 병렬 처리
Git Worktree 격리
worktrees/
├── ODIN-123/ ← 이슈 1 (담당자 A) — fix/ODIN-123 브랜치
├── ODIN-124/ ← 이슈 2 (담당자 A) — fix/ODIN-124 브랜치
├── ODIN-125/ ← 이슈 3 (담당자 B) — fix/ODIN-125 브랜치
각 이슈가 독립된 브랜치에서 작업되므로 서로 간섭하지 않는다.
추적과 분석: SQLite + 대시보드
SQLite 추적 DB
-- 이슈별 실행 추적
issue_executions: execution_id, issue_key, phase, status, attempt_number, duration_ms, error_message
-- 상태 전이 감사 로그
state_transitions: issue_key, from_status, to_status, triggered_by, notes
-- 에러 누적 기록
error_log: issue_key, phase, error_type, error_message, review_suggestions자동 상태 추적
_STATUS_AUTO_TRACKING = {
'ANALYZING': ('start', 'analyze'),
'JIRA_CREATED': ('complete', 'analyze', 'success'),
'DEVELOPING': ('start', 'develop'),
'DONE': ('complete', 'develop', 'success'),
'BLOCKED': ('complete_all', 'blocked'),
}스프레드시트 상태 변경 시 SQLite에 자동으로 기록된다.
HTML 대시보드
/sonar-dashboard성공률, 단계별 평균 소요 시간, 에러 패턴, 이슈별 타임라인 등 KPI를 시각화한다.
프로젝트별 전문화
Coding Rules 주입
CODING_RULES_PATH=/path/to/project-rulesproject-rules/
├── SKILL.md ← 핵심 규칙 개요
├── references/ ← 상세 가이드 (architecture.md, patterns.md, testing.md)
├── templates/ ← 코드 패턴 예시 (service.java, controller.java)
└── scripts/ ← 유틸리티 (linter.sh, formatter.sh)
프로젝트 A는 Spring Boot 컨벤션, 프로젝트 B는 Node.js 패턴. sonar-develop이 코드 수정 전에 규칙을 로드하여 준수한다.
Jira 모드 / 리포트 모드
| 모드 | 설정 | 동작 |
|---|---|---|
| Jira 모드 | JIRA_ENABLED=true | 분석 완료 후 Jira 티켓 자동 생성 |
| 리포트 모드 | JIRA_ENABLED=false | Jira 없이 리포트만 생성 (나중에 티켓 전환 가능) |
디버그 모드
.env에서 DEBUG_MODE=true 설정 시:
history/{issue_key}/
├── 20250205_143000_subagent_general-purpose.jsonl ← 원본 트랜스크립트
├── 20250205_143000_summary.md ← 요약 리포트
└── 20250205_143000_hook_metadata.json ← 메타데이터
Claude Code hooks 기능으로 Stop, SubagentStop 이벤트 발생 시 자동 저장된다.
Bash 실행 9대 원칙
| # | 원칙 | 핵심 |
|---|---|---|
| 1 | 동적 프로젝트 루트 | CLAUDE_PROJECT_DIR 우선, 없으면 git rev-parse |
| 2 | .env 로딩 필수 | API 토큰 하드코딩 절대 금지 |
| 3 | 에러 출력 캡처 | 2>&1로 stderr 캡처 |
| 4 | API 응답 검증 | HTTP 상태 코드 분리 확인 |
| 5 | 파이프라인 안전 | set -euo pipefail |
| 6 | 디렉토리 변경 격리 | 서브셸로 cd 격리 |
| 7 | 표준 템플릿 | 모든 스크립트 시작 구조 통일 |
| 8 | JSON 파싱 안전 | jq 결과 항상 검증 |
| 9 | 출력 표준화 | 성공/실패 메시지 형식 통일 |
실전 시나리오
프로젝트 3개, 이슈 3,000개, 담당자 5명인 상황을 가정한다.
# 1. 이슈 수집
/sonar intake project-a
/sonar intake project-b
/sonar intake project-c
# 2. 담당자별 배정
/sonar claim --distribute --members "A,B,C,D,E"
# 3. 자동 처리
/sonar run
# 4. 현황 확인
/sonar status
# 5. 완료 이슈 승인
/sonar approve ODIN-123
# 6. 수동 커밋 & PR (07_final_deliverable.md 참조)
git add . && git commit -m "복사한 커밋 메시지"
gh pr create --title "제목" --body "복사한 PR 설명"정리
| 문제 | 해결 |
|---|---|
| 수만 개 이슈 수동 처리 불가 | AI 서브에이전트 5개 병렬 자동 처리 |
| 혼자서 모든 프로젝트 감당 불가 | Google Sheets 기반 다인 협업 구조 |
| 처리 과정 추적 불가 | 8단계 리포트 + SQLite 감사 로그 |
| AI의 임의 행동 | settings.json 기술적 차단 + Red Flags 규칙 |
| 분석/수정 품질 불균일 | 독립 리뷰 에이전트 + 최대 4회 재시도 |
| 불필요한 테스트 작성 | TDD 게이팅 |
| 프로젝트별 코딩 규칙 미준수 | CODING_RULES_PATH 주입 |
| 디버깅 어려움 | DEBUG_MODE 전체 대화 기록 보존 |
기술 스택
| 영역 | 기술 |
|---|---|
| 오케스트레이션 | Claude Code Skills (SKILL.md 기반 멀티 에이전트) |
| 상태 관리 | Google Sheets API (gspread + oauth2client) |
| 이슈 소스 | SonarQube REST API |
| 이슈 트래킹 | Jira REST API (선택) |
| 코드 격리 | Git Worktree |
| 데이터 저장 | SQLite (추적 DB) |
| 대시보드 | HTML 생성 (generate_dashboard.py) |
| 보안 | settings.json permissions.deny |
| 디버그 | Claude Code Hooks (save-history.sh) |
| 테스트 | 특성화 테스트 패턴 (TDD 게이팅) |
"AI가 할 수 있는 것은 AI가, 사람만 할 수 있는 것은 사람이"
AI는 분석, 수정, 테스트, 리포트를 작성한다. 사람은 리포트를 검토하고, 커밋하고, PR을 올리고, 최종 승인한다.
AI의 산출물을 사람이 검증하는 구조가 전체 시스템의 신뢰를 만든다.