Skip to content

baekchangjoon/hotspot-analysis

Repository files navigation

hotspot-analysis

🌐 한국어 (현재 문서) · English

CI Coverage Branch Coverage License: MIT Java 21 Skill Docs

Java 소스 파일과 메서드를 복합 핫스팟 점수(Composite Hotspot Score) 로 순위화합니다. 최근성 가중 변경 횟수, 인지 복잡도, 커버리지 공백을 결합해 역사적 증거상 버그가 가장 많이 살 만한 곳에 테스트 노력을 집중하게 합니다. Adam Tornhill의 Your Code as a Crime Scene 방법론에 기반합니다.

로컬 git 저장소를 분석해 파일과 메서드에 점수를 매기고 CSV / YAML / Markdown / HTML 리포트를 내보내는, 완전히 연결된 CLI입니다.


Claude Code 스킬로 설치

이 저장소는 Claude Code 플러그인 마켓플레이스이기도 합니다. 번들된 hotspot-analysis 스킬이 에이전트에게 CLI를 받아·설정·실행·해석하는 방법을 가르칩니다(릴리스 jar 자동 다운로드, 빌드 불필요). Claude Code 안에서:

/plugin marketplace add baekchangjoon/hotspot-analysis
/plugin install hotspot-analysis@hotspot-analysis
/reload-plugins

이후 "이 Java 저장소의 핫스팟 찾아줘" 처럼 요청하면, 모델이 스킬을 호출해 아래 워크플로를 수행합니다.

스킬만 설치 (플러그인 없이)

Agent Skills 호환 에이전트(Claude Code, Claude.ai, Cursor, Gemini CLI 등)는 스킬 폴더만 직접 복사해 쓸 수 있습니다:

git clone https://github.com/baekchangjoon/hotspot-analysis
cp -r hotspot-analysis/skills/hotspot-analysis ~/.claude/skills/

스킬의 scripts/get-jar.sh가 릴리스 jar를 자동으로 내려받으므로 빌드는 필요 없습니다(JDK 21 런타임만 필요). JDK 없이 쓰려면 아래 빠른 시작의 Docker 경로를 쓰세요.


기능

  • 두 가지 단위 — 파일 단위와 메서드 단위 핫스팟, 점수 기준 정렬.
  • 두 가지 VCS 소스 — 로컬 git 작업 트리(JGit) 또는 원격 GitHub(kohsuke github-api, WireMock으로 hermetic 테스트).
  • Java 21 파싱 — JavaParser 3.26이 record, sealed 타입, switch 식, 패턴 매칭을 이해.
  • YAML 설정 — 강타입, Jakarta Bean Validation 검증, 환경변수 치환.
  • 네 가지 출력 형식 — CSV(엑셀 친화), YAML(기계 판독), Markdown(PR 친화), HTML(브라우저에서 바로, 정렬·필터 가능, XSS 안전, 다크모드 대응).
  • CI 자체 분석 데모 — 매 CI 실행마다 다운로드해 브라우저로 열 수 있는 hotspot-demo-report-<N> 아티팩트 생성.
  • 모든 계층의 포괄적 테스트 — 계약 테스트 + 단위 + Spring Boot E2E, 실패 0.

사전 요구사항

용도 요구사항
빌드 PATH에 JDK 17+ (Gradle이 Java 21 툴체인을 자동 프로비저닝)
jar 실행 PATH에 JDK 21+java -version으로 확인
분석 대상 .git/ 폴더가 있는 디렉터리(실제 git 작업 트리)

빠른 시작

한 줄 설치 (hotspot 명령 설치)

jar를 직접 받는 대신, 설치 스크립트가 최신 릴리스 jar를 내려받고 PATH에 hotspot 래퍼를 설치합니다. 이후 java -jar hotspot.jar ... 대신 hotspot ...로 실행합니다(JDK 21 런타임 필요):

curl -fsSL https://raw.githubusercontent.com/baekchangjoon/hotspot-analysis/main/install.sh | bash

~/.local/share/hotspot/hotspot.jar에 jar를, ~/.local/bin/hotspot에 래퍼를 설치합니다(재실행해도 안전). ~/.local/bin이 PATH에 없으면 스크립트가 추가 방법을 안내합니다. 설치 위치·버전은 환경변수로 바꿀 수 있습니다(HOTSPOT_PREFIX, HOTSPOT_VERSION). 설치 후:

hotspot analyze            # 현재 디렉터리 (zero-config)

JDK 21 런타임이 필요합니다. 설치하기 어렵다면 아래 Docker 경로를 쓰세요. (Homebrew tap은 추후 제공 예정 — 그때까지는 위 스크립트를 권장합니다.)

아래는 jar를 직접 받아 java -jar로 실행하는 수동 경로입니다. 위 한 줄 설치를 쓴다면 예시의 java -jar hotspot.jarhotspot으로 바꿔 읽으세요.

1. jar 받기 (빌드 불필요)

최신 Release에서 self-contained 실행 jar를 내려받습니다(JDK 21 런타임만 있으면 됨):

curl -fsSL https://github.com/baekchangjoon/hotspot-analysis/releases/latest/download/hotspot.jar -o hotspot.jar

소스에서 빌드하려면(선택): ./gradlew bootJarbuild/libs/hotspot-*.jar. 아래 예시의 hotspot.jar를 그 경로로 바꾸세요.

JDK 없이 — Docker. 분석 대상 저장소를 /work에 마운트해 실행합니다. 설정 파일 없이 바로(zero-config):

docker run --rm -v "$PWD":/work ghcr.io/baekchangjoon/hotspot-analysis:latest analyze

설정 파일로 세밀하게 제어하려면:

docker run --rm -v "$PWD":/work ghcr.io/baekchangjoon/hotspot-analysis:latest \
  analyze --config /work/hotspot.yml

설정 없이 바로 실행 (zero-config)

설정 파일을 만들기 전에, 저장소 루트에서 바로 실행할 수 있습니다. git 루트· 모듈 레이아웃(단일/멀티)·JaCoCo 리포트·Spring API 분석을 자동 감지합니다:

java -jar hotspot.jar analyze            # 현재 디렉터리
java -jar hotspot.jar analyze /path/to/repo

감지 결과를 설정 파일로 저장해 손보려면:

java -jar hotspot.jar analyze --print-config > hotspot.yml

저장소 루트에서 실행하세요. 하위 디렉터리나 비-git 경로에서는 오류가 나며, 루트 경로를 [path]로 넘기거나 --config를 쓰라는 안내가 출력됩니다. linked git worktree도 지원합니다. 멀티 모듈에서 서브모듈별 JaCoCo 리포트는 자동 합산하지 않습니다(루트 레벨만 탐지). zero-config는 topN: 50 등 안전한 기본값을 쓰며(아래 수동 예시의 20과 다름), --print-config로 확인·조정할 수 있습니다. 세밀한 제어가 필요하면 아래의 --config 방식을 쓰세요.

2. 샘플 설정 생성

java -jar hotspot.jar init -o hotspot.yml

3. hotspot.yml 편집

analysis:
  target:
    type: local-git
    # .git/ 폴더가 있는 디렉터리여야 함
    path: /path/to/your/repo

  window:
    # 모드 A: 상대 윈도우("지금"부터 최근 N일) -- 안전한 기본값
    days: 365
    # 모드 B: 절대 ISO-8601 날짜 범위 (`days` 대신 사용)
    # since: "2024-01-01"
    # until: "2026-01-01"

  scope:
    granularity: [file, method]
    include:
      # 단일 모듈 Spring Boot 프로젝트
      - "src/main/java/**/*.java"
      # 멀티 모듈 Gradle/Maven 프로젝트 -- 서브모듈이 있으면 주석 해제
      # (ftgo, jhipster 등 대부분의 실제 스택).
      # - "**/src/main/java/**/*.java"
    exclude:
      - "**/generated/**"
      - "**/test/**"
      - "**/build/**"
      - "**/target/**"

  scoring:
    decayHalfLifeDays: 90   # 최근성 감쇠 반감기(일)
    # excludeCoverage: false  # true면 Composite = CC × Decay (커버리지는 관측용)

  # REST API 엔드포인트 핫스팟 (Spring 컨트롤러를 콜그래프 따라 집계).
  # RestAssured 테스트 생성 우선순위 입력. Spring 앱이면 켜세요.
  apiAnalysis:
    enabled: true
    sharedComponentMode: BOTH       # CUMULATIVE | SEPARATE | BOTH
    classpathDirectories:           # 콜그래프 심볼 해석 향상(선택)
      - build/libs

  # JaCoCo XML 리포트(선택). 실제 리포트가 있을 때만 주석을 해제하세요.
  # 커버리지가 점수에 반영됩니다(배수 1/(coverage+0.1)). 존재하지 않는 경로를
  # 가리키면 커버리지 없이 진행한다는 경고가 출력됩니다(점수는 왜곡되지 않음).
  # jacocoReportPath: build/reports/jacoco/test/jacocoTestReport.xml

output:
  # 대소문자 무시: csv | yaml | md | html (여러 개 허용)
  formats: [csv, yaml, md, html]
  apiLayout: BOTH       # COMBINED(hotspots.*에 통합) | STANDALONE(api_report.*) | BOTH
  path: ./hotspot-report
  topN: 20            # 0 = 무제한

4. 분석 실행

java -jar hotspot.jar analyze --config hotspot.yml

출력:

hotspot-report/
├── file_hotspots.csv      ← CSV는 단위별로 분리(파일 10열, 메서드 15열)
├── method_hotspots.csv
├── hotspots.yml           ← YAML/MD/HTML은 두 단위를 한 문서에 묶음
├── hotspots.md
└── hotspots.html          ← 아무 브라우저에서나 열기 — 정렬 가능 열 + 필터 박스
#   apiAnalysis.enabled=true 이고 apiLayout이 STANDALONE | BOTH면 다음이 추가됩니다:
#   api_hotspots.csv, shared_components.csv, api_report.{yml,md,html}
#   output.coverageBreakdown=true 이면 coverage_breakdown.yml 도 함께 출력됩니다.

콘솔에는 상위 핫스팟(복합 점수 기준)과, HTML을 출력했다면 hotspots.html의 절대 경로가 함께 표시됩니다. --quiet로 끌 수 있습니다.

CSV는 분리하고 YAML/MD/HTML은 합치는 이유? CSV는 단일 헤더의 표 형식이라 파일(10열)과 메서드(15열) 리포트가 헤더를 공유할 수 없어, 엑셀/시트 편의를 위해 두 파일로 내보냅니다. YAML, Markdown, HTML은 두 표를 자연스럽게 한 파일에 담는 문서 형식이라 — PR에 첨부(.md), 다운스트림 자동화에 투입(.yml), 또는 자체 완결형 증거 페이지로 브라우저에서 열기(.html)가 더 쉽습니다.

HTML 리포트 기능. hotspots.html은 단일 자체 완결형 파일입니다 (원격 CSS/JS 없음, CDN 없음). 아무 열 헤더나 클릭해 오름/내림차순 정렬; 검색 박스에 입력해 경로·클래스·메서드·매개변수로 행 필터링. 라이트/다크 모드는 브라우저 설정을 따릅니다. 사용자 제어 값은 모두 HTML 이스케이프되어, 악의적인 파일 경로가 스크립트 태그를 주입할 수 없습니다.


CLI 레퍼런스

Usage: hotspot [-hV] [COMMAND]

Description:
  Hotspot analysis CLI for Java codebases.

Options:
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.

Commands:
  analyze  Analyse a repository and emit hotspot reports.
  init     Generate a sample hotspot.yml configuration file.

hotspot analyze

옵션 필수 기본값 설명
[path] 현재 디렉터리 분석할 저장소 루트 경로(--config와 함께 사용 불가)
--config, -c <file> YAML 설정 파일 경로. 생략하면 [path](또는 현재 디렉터리)에서 자동 감지
--print-config off 자동 감지된 설정을 YAML로 stdout에 출력하고 종료(분석하지 않음). zero-config 모드 전용 — --config와 함께 쓸 수 없음
--quiet, -q off stdout 요약 출력 억제
--strict, -s off 결과가 비면 종료 코드 3 (윈도우 내 커밋 0건 또는 스코프 매칭 파일 0건). CI 게이팅용.
종료 코드 의미
0 분석 완료; 출력 기록됨
1 설정 무효 / 파이프라인 실패 / 미지원 대상
2 Picocli 사용법 오류
3 --strict 설정 상태에서 결과가 비어 있음

hotspot init

옵션 필수 기본값 설명
--output, -o <path> ./hotspot.yml 샘플 설정 저장 위치
--force, -f off 대상이 있으면 덮어쓰기

설정 스키마

경로 타입 비고
analysis.target.type local-git | github CLI는 local-git만 end-to-end 실행
analysis.target.path string local-git에 필수
analysis.target.github.{owner,repo,branch,token} strings github에 필수; token${ENV_VAR} 치환 지원
analysis.window.since / analysis.window.until ISO date 또는 analysis.window.days 사용
analysis.window.days integer ≥ 1 지금 기준 상대 윈도우
analysis.scope.granularity[] file | method 둘 다 선택 가능
analysis.scope.include[] glob[] 최소 한 개 필요
analysis.scope.exclude[] glob[] 선택
analysis.scoring.decayHalfLifeDays integer ≥ 1 최근성 감쇠 반감기; 기본 90일
analysis.scoring.excludeCoverage boolean true면 Composite = CC × Decay, 리포트는 배수 대신 원시 line-coverage 표시; 기본 false
analysis.apiAnalysis.enabled boolean REST API 엔드포인트 + 공유 컴포넌트 단위 활성화; 기본 false
analysis.apiAnalysis.sharedComponentMode CUMULATIVE | SEPARATE | BOTH 2개 이상 엔드포인트가 공유하는 메서드의 집계/리포트 방식; 기본 BOTH
analysis.apiAnalysis.classpathDirectories[] string[] 콜그래프 심볼 해석을 돕는 의존성 jar/클래스 디렉터리
analysis.jacocoReportPath string JaCoCo XML 리포트 경로; 커버리지 배수 1/(coverage+0.1) 활성화. 없으면 배수 1.0
output.formats[] CSV | YAML | MD | HTML 최소 한 개
output.apiLayout COMBINED | STANDALONE | BOTH API/공유 테이블 위치: hotspots.*에 통합, 독립 api_report.*, 또는 둘 다; 기본 BOTH
output.coverageBreakdown boolean true면(+JaCoCo 제공 시) 모든 커버리지 수치의 계산 근거 — 파일별 counted 라인, 엔드포인트별 메서드 기여(covered/실행가능 라인) — 를 coverage_breakdown.yml로 출력; 기본 false
output.path string 출력 디렉터리
output.topN integer ≥ 0 0은 "모든 행"

문자열 값의 환경변수는 ${VAR_NAME} 구문으로 치환됩니다. #로 시작하는 줄 (YAML 주석)은 그대로 두어 문서 예시가 오류를 일으키지 않습니다.


점수 계산 방식

모든 리포트는 이제 두 개의 점수네 개의 입력 요인을 다음 표준 순서로 나란히 담습니다:

의미
LOC 아티팩트의 현재 라인 수
Revisions 윈도우 내에서 해당 항목을 건드린 커밋 수
Simple Score Revisions × LOC — Adam Tornhill의 원래 신호
Recency Decay 같은 커밋들에 대한 Σ exp(-ln(2) × Δt / halfLife)
Cognitive Complexity SonarQube에서 영감을 받은 AST 순회 점수
Coverage Multiplier JaCoCo XML 기준 1 / (line_coverage + 0.1); 리포트 없으면 1.0
Composite Score Cognitive Complexity × Recency Decay × Coverage Multiplier

행은 Composite Score 내림차순으로 정렬됩니다(동점은 경로 / 표준 시그니처로 처리).

CSV는 단위별로 분리(파일 10열, 메서드 15열); YAML/MD/HTML은 모든 단위를 한 문서에 묶습니다. CSV는 위 표의 요인 앞에 simple_rank, composite_rank 두 열이 먼저 옵니다 — 즉 file_hotspots.csv 헤더는 simple_rank,composite_rank,path,loc,revisions,simple_score,recency_decay,cognitive_complexity,coverage_multiplier,composite_score이며, method_hotspots.csv는 그 사이에 fqcn,method,parameters,file,start_line,end_line을 더한 15열입니다. (excludeCoverage: true면 마지막 두 열이 composite_score,line_coverage로 바뀝니다.)

단위별 산출 방식 — 파일 / 메서드 / REST API 엔드포인트 / 공유 컴포넌트마다 Revisions / Recency Decay / Cognitive Complexity / Coverage를 어떻게 측정하고 결합하는지 — 는 docs/scoring/에 소스 근거와 워크드 예시까지 정리돼 있습니다.


저장소 구조

docs/
  phase1-design.md            ← 아키텍처 & 결정
  reports/T1…T11-*.md         ← 태스크별 완료 리포트
src/main/java/io/github/baekchangjoon/hotspotanalysis/
  HotspotApplication.java     ← Spring Boot 진입점
  cli/                        ← Picocli 서브커맨드
  config/                     ← YAML 스키마 record + 로더
  vcs/                        ← VcsProvider + LocalGit / GitHub 구현
  parser/                     ← JavaParser 기반 메서드 추출
  analysis/                   ← Revisions / LOC / Score / Orchestrator
  coverage/                   ← JaCoCo XML 파서
  output/                     ← CSV / YAML / MD / HTML 라이터
src/main/resources/
  application.yml             ← Spring Boot 로깅 설정
  templates/hotspot.example.yml ← `hotspot init`용 번들 샘플

트러블슈팅

첫 실행이 "비어" 보인다면 거의 확실히 아래 셋 중 하나입니다.

Files: 0scope.include에 매칭된 소스 파일 없음

Java NIO의 ** glob은 빈 경로 세그먼트를 매칭하지 않습니다. 그래서 단일 모듈과 멀티 모듈 레이아웃 사이에 미묘한 비대칭이 생깁니다:

저장소 형태 첫 Java 경로 예시 동작하는 패턴
단일 모듈 src/main/java/com/foo/Hot.java src/main/java/**/*.java
멀티 모듈 service-a/src/main/java/com/foo/Hot.java **/src/main/java/**/*.java

대상의 형태를 미리 모르면 두 패턴을 모두 나열하세요. 수집기가 매칭을 중복 제거합니다:

scope:
  include:
    - "src/main/java/**/*.java"        # 단일 모듈 저장소 포착
    - "**/src/main/java/**/*.java"     # 멀티 모듈 저장소 포착

빠른 점검(Bash):

git -C <repo> ls-files | grep -E 'src/main/java/.*\.java$' | head

Commits: 0 — 윈도우에 Java를 건드린 커밋 없음

윈도우는 지금 기준으로는 맞지만, 저장소가 수년간 비활성이었을 수 있습니다 (동결된 참조 구현, 아카이브된 데모 등). 실제 활동과 겹치는 절대 범위로 바꾸세요:

window:
  since: "2017-01-01"
  until: "2026-12-31"

커맨드라인에서 교차 확인:

git -C <repo> log --since=<since> --until=<until> --name-only \
    --pretty=format: -- '*.java' | sort -u | head

CI 팁: hotspot analyze 호출에 --strict를 추가하세요. 커밋이나 파일이 비어 돌아오면 종료 코드 3으로 끝나, 잘못 설정된 CI 파이프라인이 빈 리포트를 만드는 대신 큰 소리로 실패합니다.

java -jar hotspot.jar analyze --config hotspot.yml --strict

jar 실행 시 UnsupportedClassVersionError

번들 jar는 Java 21로 컴파일됩니다. Gradle 툴체인이 빌드에 21을 자동 프로비저닝했더라도, 실행에는 여전히 PATH에 JDK 21+가 필요합니다:

java -version            # 반드시 21 이상 보고
# macOS:
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
# Linux (sdkman):
sdk use java 21.0.4-tem

Could not parse GitHub repository / Authentication required

target.type: github에는 토큰이 필요합니다. 둘 중 하나:

target:
  type: github
  github:
    owner: my-org
    repo:  my-repo
    branch: main
    token: ${GITHUB_TOKEN}     # 로드 시 환경에서 해석

…그리고 실행 전에 export GITHUB_TOKEN=…. CLI는 local-git에만 end-to-end로 연결되어 있고, GitHub 프로바이더는 WireMock 계약 테스트로 검증됩니다. GitHub 저장소를 분석하려면 로컬에 클론하고 target.type: local-git을 작업 트리에 가리키세요.


한계

  1. GitHub 대상 end-to-end: API 클라이언트는 WireMock으로 검증되지만 CLI의 analyzelocal-git 대상을 요구합니다. GitHub 저장소는 로컬에 클론하고 target.type=local-git으로 실행하세요.
  2. 작업 트리 LOC: LOC는 각 역사적 커밋이 아니라 HEAD에서 읽습니다 (Tornhill이 책에서 하는 동일한 단순화).
  3. Merge 커밋: 변경 이력은 각 커밋의 첫 번째 부모와의 diff로 계산합니다. 따라서 fast-forward 없이 머지된 브랜치 쪽 변경은 revision/recency 집계에서 누락될 수 있습니다.
  4. 소스 파싱 실패: 스코프 내 .java 파일 하나라도 파싱에 실패하면 분석 전체가 중단됩니다(부분 결과를 내지 않음). 손상·비표준 소스는 scope.exclude로 제외하세요.

이 결정들과 대안의 태스크별 분석은 docs/reports/*를 참고하세요.


개발

./gradlew test            # 포괄적 테스트 스위트, ~10초
./gradlew build           # 전체 어셈블리
./gradlew check           # 테스트 + 정적 분석

CI는 매 푸시마다 그리고 매일 스케줄(09:00 KST)로 실행됩니다. 각 실행은 다음 아티팩트를 업로드합니다(Actions 탭 참고):

아티팩트 내용
hotspot-jar-<N> 어셈블된 fat jar (hotspot-*.jar)
test-results-<N> 모든 테스트 클래스의 JUnit XML
test-report-<N> Gradle의 전체 HTML 테스트 리포트
test-summary-<N> GitHub Step Summary 패널에 표시되는 Markdown 요약
hotspot-demo-report-<N> 자체 분석 출력file_hotspots.csv, method_hotspots.csv, hotspots.yml, hotspots.md, hotspots.html, 그리고 이를 만든 hotspot.yml. 다운로드해 hotspots.html을 브라우저에서 바로 여세요.

Privacy

hotspot-analysis는 전적으로 당신의 머신에서 실행됩니다. 텔레메트리도, 애널리틱스도, "phone home"도 없습니다.

  • local-git 대상 (기본값): git 히스토리와 Java 소스를 지정한 작업 트리에서 읽고, 점수를 로컬에서 계산해, 리포트를 로컬 output.path 디렉터리에 씁니다. 아무것도 컴퓨터를 떠나지 않습니다.
  • github 대상: 유일한 외부 네트워크 트래픽은 GitHub REST API로 향하며, 당신이 ${GITHUB_TOKEN}으로 제공한 토큰으로 인증합니다. 토큰은 로드 시 환경에서 읽히고 리포트에는 절대 기록되지 않습니다.
  • 리포트(hotspots.html 등)는 코드의 파일 경로·클래스/메서드 이름·라인 수를 담습니다. 소스와 동일하게 취급하세요 — HTML 리포트는 완전히 자체 완결형이고 (CDN/원격 호출 없음) 모든 값을 HTML 이스케이프하지만, 여전히 코드 구조를 담으므로 의도적으로만 공유하세요.
  • 스킬 / 플러그인은 이 로컬 CLI를 구동할 뿐입니다. 설치해도 어떤 코드나 분석도 제3자에게 전송하지 않습니다.

License

MIT © 2026 baekchangjoon

About

Rank Java files, methods, and REST API endpoints by a deterministic Composite Hotspot Score (recency-weighted churn × cognitive complexity × JaCoCo coverage gap) to prioritize test generation. Also a Claude Code skill/plugin.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors