| name | contribution-check |
| description | 로컬 git 레포의 커밋/브랜치를 분석해서, 팀원별로 "무엇을 만들고 무엇을 고쳤는지"를 기능 단위 서술형 보고서로 정리한다. 커밋 수·라인 수 같은 정량 지표나 퍼센트 대신, 대/중/소분류 기능 트리를 기준으로 실제 기여를 추적한다. 사용자가 "이 레포 기여도 분석해줘", "우리 팀 프로젝트 회고 리포트 만들어줘", "누가 뭘 만들었는지 정리해줘", "이 레포 커밋 히스토리로 기여도 좀 봐줘" 같은 요청을 하거나, 로컬 git 레포 경로를 주며 팀원별 기여를 물어볼 때 사용한다. 단일 파일 diff 리뷰나 코드 리뷰, PR 리뷰에는 사용하지 않는다 (그건 /code-review 같은 다른 도구의 영역). |
Contribution Check
깃 레포 하나를 분석해서 "팀원별로 무엇을 만들고 고쳤는지"를 기능 단위 서술형으로 정리한다.
규칙 근거와 설계 배경은 references/design-notes.md를 참고한다.
이 스킬 폴더의 구성:
scripts/survey_commits.py — 1단계 정량 조사 스크립트 (표준 라이브러리만 사용).
scripts/render_report.py — 6단계에서 analysis.json을 HTML + Markdown으로 렌더링한다.
references/analysis-example.json — analysis.json 스키마 예시.
references/report-template.html — 렌더러가 스타일/스크립트를 읽어가는 원본. 디자인을 바꿀 때만 건드린다.
references/design-notes.md — 각 규칙의 근거와 설계 배경.
절대 바꾸지 않는 원칙
- 서술형, 퍼센트 없음. "A 30% / B 70%" 같은 정량 판정은 절대 내지 않는다. "이 중분류 아래 소분류 5개 중 3개를 A가 만들었다"처럼 개수로 열거하는 건 된다.
- 문서 커밋도 기여다. 코드를 안 짜는 팀원(기획, 문서화, 테스트 시나리오 설계 등)의 기여도 기능 트리 위에서 똑같이 서술한다.
- git author 병합은 신뢰도를 구분한다. 이메일 완전 일치·GitHub noreply 고유 ID 일치는 자동으로 합친다. 이름 유사성·로컬 로그인 계정 이메일 같은 약한 신호만 있으면 근거를 보여주고 사용자에게 확인받는다. 확정 후 "인물별 커밋 수 합 = 전체 커밋 수"로 검산한다.
- AI 사용 여부는 판별하지 않는다.
Co-Authored-By 등 커밋 메시지 트레일러는 무시하고 git author만 귀속 기준으로 삼는다. 트레일러에 나온 이름은 people/identities에 절대 넣지 않는다.
- diff 전수 확인은 하지 않되, 스킵/샘플링 기준을 수치로 남긴다. "N개 중 M개를 어떤 기준으로 읽었다"를 최종 결과물에 명시한다.
- 결과물에 군더더기 설명을 넣지 않는다. 보면 아는 것을 글로 다시 쓰지 않는다 (6-5 참고).
절차
0. 준비
- 사용자가 브랜치를 지정했으면 그걸 쓴다.
- 안 지정했으면
git branch -a에서 remotes/origin/HEAD -> origin/<branch> 줄을 본다.
- 이 줄이 있고 후보가 하나면 그걸 기본 브랜치로 쓴다. 브랜치가 여러 개라도
origin/HEAD가 하나를 명확히 가리키면 물어보지 않는다.
- 이 줄이 없거나, 동등하게 활발해 보이는 브랜치가 여럿이면
AskUserQuestion으로 후보(브랜치명·최근 커밋 시각·커밋 수)를 보여주고 물어본다.
survey_commits.py의 --branch 기본값(HEAD)에 기대지 않는다. 위에서 정한 브랜치를 항상 --branch로 명시한다.
<repo>/.temp_contribution_check/progress.json이 이미 있으면 거기 적힌 지점부터 이어간다 (3~4단계 참고).
1. 정량 조사 스크립트 실행
python scripts/survey_commits.py <repo> --branch <branch> > <repo>/.temp_contribution_check/survey.json
스크립트가 커밋마다 아래를 계산해서 JSON으로 낸다:
- 변경 파일, 추가/삭제 줄 수, 병합 커밋 여부
- 사전 필터 대상 여부(
prefilter_skip), whitespace-only 여부(whitespace_only)
- 메시지 위험 신호(
message_risk_reason): "no_change_claim" / "vague_filler" / null
- 레포 내 "진짜 작업" 커밋 기준 diff 크기 robust z-score(
size_zscore)
message_reliability_flag: 위험 신호가 있거나 |z| >= 3.5면 true
- author를 이메일 완전 일치 / GitHub noreply 고유 ID 일치로 묶은
identity_groups
레포가 한국어/영어 위주가 아니면 스크립트의 NO_CHANGE_CLAIM_KEYWORDS/VAGUE_FILLER_WORDS를 그 자리에서 늘려쓴다. 스크립트 한계는 references/design-notes.md 참고.
2. 신원 병합
survey.json의 identity_groups를 본다.
- 신원 병합의 입력은 git author(이름/이메일)뿐이다. 커밋 메시지 트레일러(
Co-Authored-By, Signed-off-by 등)에 나온 이름은 병합 후보로도 새 인물로도 다루지 않는다.
- 강한 신호로 이미 묶인 그룹은 그대로 확정한다.
- 그룹끼리 이름 집합이 겹치면 강한 병합 후보다 — 근거를 남기고 병합한다.
- 이름도 이메일도 안 겹치는 그룹은
AskUserQuestion으로 후보와 근거(각 그룹의 커밋 수·시기·겹치는 작업 영역)를 보여주고 확인받는다.
- 병합 후 "인물별 커밋 수 합 == 전체 커밋 수"를 검산한다. 팀 인원수를 안다면 인물 수도 맞는지 확인한다.
- 판단 과정(어떤 신호로 합쳤는지)을 기록해서 "신원 병합 근거" 절에 그대로 쓴다.
3. 레포 구조 판단 + 기능 트리
- README/문서/이슈에서 컨셉과 기능을 뽑고(하향식), 코드 디렉터리/모듈 구조를 훑어 기능 경계를 추론한다(상향식). 두 결과를 합쳐 기능 트리 초안을 만든다.
- 레포가 계층형(코어→확장→운영이 층으로 쌓임)인지 병렬형(장면/화면/기능이 나란함)인지 판단한다.
- 계층형 → 대분류(시스템) → 중분류(통합 컴포넌트) → 소분류(컴포넌트) 3단계.
- 병렬형 → 중분류(=최상위 기능 단위) → 소분류 2단계.
- 병합 커밋(
is_merge)은 기능 트리 노드로 넣지 않는다 (6단계 "브랜치 통합 역할" 참고).
- 모든 중분류는 소분류를 최소 1개 가져야 한다. 못 채우면 그 중분류를 한 단계 내려 소분류로 바꾼다.
- 트리를
<repo>/.temp_contribution_check/taxonomy.md에 저장한다.
4. 커밋별 분류 + 커버리지 결정 (체크포인트/재개)
survey.json의 커밋을 순서대로 처리한다. <repo>/.temp_contribution_check/commits.jsonl에 이미 기록된 sha는 건너뛴다.
각 커밋에 대해:
is_merge가 true면 diff를 읽지 않고 "병합 — 통합 역할"로 기록한다 (충돌 해결로 실제 코드가 바뀐 게 의심되면 git show -m <sha>로 확인).
prefilter_skip 또는 whitespace_only가 true면 diff를 열지 않고 "스킵"으로 기록한다.
- 나머지는 커밋 메시지 + 변경 파일 경로만 보고 기능 트리의 소분류 노드에 매핑한다.
- 아래 중 하나라도 해당하면 diff를 연다: 매핑된 소분류 노드의 커밋이 아직 3개 이하, 여러 소분류에 걸쳐 애매함,
message_reliability_flag가 true. 나머지는 메시지+경로 분류로 확정한다.
- diff를 연 경우 분류를 확정/수정한다. 메시지와 다르면 ("메시지는 X지만 실제로는 Y") 그대로 적어둔다.
- 결과(
sha, 매핑 노드, diff를 열었는지 + 이유, 한 줄 요약)를 commits.jsonl에 append하고 progress.json을 갱신한다. diff_read 여부와 그 집계(coverage)를 빠뜨리지 않는다.
5. 서술 작성
commits.jsonl이 다 채워지면 중분류 단위로 서술을 쓴다.
- 헤더는 중분류 단위로만 만든다. 소분류마다 헤더를 새로 만들지 않는다.
- 문서 커밋도 똑같이 서술한다.
- many-to-many(한 커밋이 여러 소분류에 걸침)는 "이 커밋은 A.1과 A.2 양쪽에 걸친다"처럼 그대로 드러낸다.
- 근거 커밋(
evidence) 목록은 이름을 SHA보다 왼쪽에 둔다: <span class="by" style="--pc: var(--p{n})">이름</span> <code class="sha">sha</code> 설명 순서로 쓴다.
6. 최종 리포트 조립 (고정 스키마)
HTML을 직접 쓰지 않는다. analysis.json에는 판단(기능 트리·귀속·서술·근거 SHA)만 담고, 렌더러를 돌린다:
python scripts/render_report.py <출력경로>/analysis.json --out-dir <출력경로> --basename {repo}-{branch}_result
파생 수치는 전부 렌더러가 계산한다 (직접 세거나 계산해서 넣지 않는다):
- 소분류/중분류/대분류 개수, 사람별 관여 기능 수, 역할(주도/참여/수정) 집계, 2인 이상이 손댄 소분류 수
- 통합표의
rowspan, 사람 색(--p1~--p15, 커밋 수 내림차순), SVG 차트 좌표·눈금·날짜 라벨·끝점 라벨 충돌 회피
- 커버리지 검산 문장
스키마는 references/analysis-example.json을 보고 쓴다. 렌더러는 검증 실패 시 결과물을 만들지 않고 exit 1로 죽는다. 막히는 조건:
- 소분류가 하나도 없는 중분류 / 기여자가 없는 소분류 / 근거 SHA가 없는 기여
people에 없는 사람에게 귀속, 주도·참여·수정이 아닌 역할값
total ≠ merge + prefilter + message_only + diff_read, 인물별 커밋 수 합 ≠ 전체 커밋 수
timeline 마지막 누적값 ≠ people.commits
인원수 상한은 없다. 렌더러가 stderr로 경고하면 결과물에 그 한계를 한 줄 적는다:
- 15명 초과 — 색이 앞에서부터 다시 쓰인다. 이름은 항상 색 옆에 나오므로 구분은 가능하다.
- 10명 초과 — 누적 커밋 추이 차트의 선이 서로를 가린다. "차트만 보고 판단하지 말라"고 적거나 팀을 나눠 돌릴지 사용자에게 묻는다.
references/report-template.html은 리포트를 만들 때 복사하지 않는다 — 렌더러가 스타일·스크립트만 읽어간다.
아래 6-1~6-4는 analysis.json을 채우기 위한 결과물 형태 설명이다. 직접 만들지 않는다.
6-1. HTML 대시보드
자기완결 단일 파일(인라인 <style>/<script>, 차트는 인라인 SVG). 탭 4개:
- 대시보드 — 6-2 참고.
- 기능 분류 × 기여 — 기능 트리와 귀속을 합친 통합표 (6-3).
- 기능별 서술 — 중분류 단위 서술을
<details>로 접는다.
- 분석 근거 — 브랜치 통합 역할 / 신원 병합 근거 / 커버리지.
사람 필터(팀원 카드 클릭 → 그 사람 기여만 강조)는 탭을 넘어 유지한다.
6-2. 대시보드 탭 구성
- 팀원 카드 — 사람마다
커밋 수 / 관여 소분류 n·전체 / 주도 개수 + 한 줄 요약.
- 누적 커밋 추이 — 가로축은 실제 날짜 간격(등간격 아님), 세로축은 사람별 누적 커밋.
- 기능 커버리지 — 전체 소분류 중 관여 개수를 가로 막대로.
21 / 28처럼 개수로 쓴다.
- 역할 구성 — 주도/참여/수정을 사람별 스택 막대로 표시한다.
- KPI 타일 — 소분류 총 개수 / 2인 이상이 손댄 소분류 수 / 메시지 ≠ 실제 diff 건수 / diff를 직접 읽은 커밋 수.
6-3. 기능 분류 × 기여 통합표
기능 트리와 기여 표를 하나로 rowspan 합친다:
- 행 = 소분류 하나.
- 왼쪽 열(들) = 대분류/중분류. 각 셀의
rowspan은 그 아래 소분류 개수 (병렬형은 중분류 열 하나, 계층형은 대분류+중분류 두 열).
- 마지막 열 = 기여. 관여자마다 한 줄: 역할 배지(주도/참여/수정) · 이름 · 근거 SHA · 메시지≠diff 표시 · 귀속 근거(diff 확인 / 메시지 추정).
- 사람을 열로 만들지 않는다. 기여자는 행 안에 줄로 넣는다.
6-4. Markdown
렌더러가 HTML과 같은 내용을 아래 스키마로 낸다:
# {repo} 기여도 분석
## 개요
## 기능 트리
## 중분류별 기여 서술
## 팀원별 종합 요약
## 브랜치 통합 역할 (병합 커밋이 있을 때만 포함)
## 신원 병합 근거
## 커버리지 / 분석의 한계
수치는 analysis.json의 coverage에서 나온다. 4단계를 처리하면서 아래를 채운다:
total 전체 커밋 수 / merge 병합 커밋 수
prefilter 사전 필터·공백으로 스킵한 수
message_only 메시지 + 파일 경로만으로 분류한 수
diff_read diff를 직접 읽은 수 / read_reason_coverage·read_reason_reliability 이유별 내역
total = merge + prefilter + message_only + diff_read가 안 맞으면 렌더러가 죽는다. 계층형/병렬형 판단 근거는 notes에 한 줄 넣는다.
6-5. 군더더기 금지 (원칙 6의 구체 규칙)
analysis.json에 직접 쓰는 문장(paras, callout, blurb, notes, identities.basis)에서 아래를 넣지 않는다:
- 섹션 도입부 설명 문단. 표를 보면 아는 내용. 꼭 필요하면 제목 옆 작은 회색 글씨 한 줄로 압축한다 (
중분류 10 · 소분류 28).
- 도구 자기소개 / 푸터 면책 문구.
- 차트 주석. 그래프를 보면 보이는 설명, 보조선.
- 자명한 UI 안내. "펼쳐 볼 수 있습니다" 류.
남기는 것: 기호 범례(●주도/◐참여/≠), 커버리지 수치와 한계 서술, 신원 병합 근거, 특정 커밋에 대한 판단 메모.
7. 마무리
- 파일명은 렌더러가
{repo-name}-{branch}_result.html / .md로 붙인다.
- 저장 위치(
--out-dir): 사용자가 출력 경로를 지정했으면 그곳에, 아니면 분석 대상 레포 루트에 contribution-check-result/ 폴더를 만들고(있으면 그대로 쓰고) 그 안에 저장한다. 대상 레포의 .gitignore에 없으면 추가할지 사용자에게 물어본다.
- 같은 이름의 파일이 이미 있으면 덮어쓰기 전에 사용자에게 확인한다.
analysis.json도 결과물 옆에 남긴다.
.temp_contribution_check/는 삭제한다. 지우기 전에 대상 레포의 .gitignore에 없으면 추가한다.