Reference

03. 점수 모델

메트릭 → 점수 → 등급 변환 체계, 가중치, 정규화 방식

Source: docs/03-scoring-model.md

03. 점수 모델

1. 설계 원칙

  1. 설명 가능성 우선: 모든 점수는 detail.csv의 finding 합산으로 정확히 재현 가능해야 한다. 블랙박스 가중치 없음.
  2. 가산·추적 가능한 부채: finding별 remediation cost를 더하며, 위치가 한 파일에 집중됐다는 이유로 비용을 사후 할인하지 않는다. 중복 신호는 메트릭 정의와 비용에서 통제한다.
  3. 범위가 명시된 밀도 정규화: 절대 건수가 아니라 해당 카테고리가 실제로 평가 가능한 production 코드 규모 대비 밀도로 평가한다. SQALE(Letouzey 2012)의 "수정 비용 / 개발 비용" 비율 개념을 차용한다.
  4. 점수는 진단 보조: 기본 등급 컷은 보수적으로 두고, 현재 사용자 설정은 메트릭 임계값 오버라이드부터 지원한다.

2. 구조

jam-lite:

finding (개별 위반, severity 포함)
   │  remediation cost 부여 (분 단위, SQALE 방식)
   ▼
metric debt  =  Σ finding cost          (메트릭별 기술부채)
   ▼
category score = 100 × (1 − category debt / eligible production dev cost)   하한 0
   ▼
JAM Lite Score = Σ (category score × weight)
   ▼
Grade (A ~ F)

jam-full:

jam-lite score + test-cli score + security-cli score
   ▼
JAM Full Score = lite×0.50 + test×0.25 + security×0.25
   ▼
Grade (A ~ F)

2.1 Severity와 기본 비용

SQALE 방법론(Letouzey 2012)과 SonarQube의 technical debt ratio 방식을 따른다. 각 finding에 "고치는 데 드는 추정 시간(분)"을 부여한다.

Severity의미기본 비용(분)
info알림. 점수 영향 없음0
warning주의. 소액 감점10
violation명백한 문제30
critical즉시 수정 필요(시크릿 노출 등)120

메트릭별로 비용을 오버라이드할 수 있다(예: ARCH-01 순환은 SCC 크기 × 60분, ARCH-09 레이어 규칙 위반은 위반 edge당 고정 45분; SIZE-01은 심각도 기본 비용을 사용한다).

2.2 측정 모집단·개발 비용(분모)·coverage (v13.0.0)

SQALE/SonarQube와 같은 구조로 개발 비용을 CLOC에 비례해 추정하되, v13부터 분모의 측정 모집단을 명시한다.

  1. 분석 입력 파일을 production, test, documentation, configuration, generated 역할로 상호 배타적으로 분류한다.
  2. 점수 모집단은 production 파일만이다. 나머지 역할의 finding은 삭제하지 않고 score_exclusion과 함께 감사 출력에 남긴다.
  3. 카테고리마다 구현된 rule set이 지원하는 언어만 eligible이다. 예를 들어 RES는 Go/Python/Rust/C/C++ production CLOC만 분모에 들어가고, 그 외 언어 CLOC는 RES 점수를 희석하지 않는다.
production CLOC = Σ CLOC(file.role == production)
eligible CLOC(category) = Σ CLOC(production file whose language is supported by category)
coverage(category) = eligible CLOC(category) / production CLOC
dev cost(category) = eligible CLOC(category) × 3.6분
TDR(category) = Σ eligible finding debt_counted / dev cost(category)
category score = clamp(round(100 × (1 − TDR / 0.10)), 0, 100)

eligible CLOC=0이면 그 카테고리는 not_assessed로 가중 합에서 제외되고 남은 가중치를 1.0으로 재정규화한다. 모든 카테고리가 미측정이면 점수 상태는 not_assessed, 등급은 N/A다. 0/F는 “측정했으며 매우 나쁨”이므로 측정 불가와 혼용하지 않는다.

이 범위 규칙의 근거는 (a) SonarQube가 main/test 소스를 별도 범위로 두고 test를 source 지표·LOC에서 제외하는 산업 관행, (b) 언어별 Quality Profile로 적용 rule set을 구분하는 관행, (c) ISO/IEC/IEEE 15939:2017의 명시적 측정 범위·정보 요구·타당성 관리다. El Emam et al.(2001)은 크기가 메트릭 타당성의 강한 교란변수임을 보였으므로, 측정하지 못하는 코드 크기를 분모에 넣는 것은 정규화가 아니라 편향이다. Letouzey(2012)의 SQALE debt ratio도 remediation cost와 해당 개발 비용의 대응을 전제로 한다(§8).

cost_per_line은 JAM의 의도적 보정값 3.6분/라인이며 SonarQube의 기본값(30분/라인 = 0.06일/라인)이 아니다 — "SQALE 관례값"으로 오인하지 말 것. 분모가 작을수록 같은 debt의 TDR이 커지므로, 3.6분/라인과 TDR_max=0.10은 함께 보정된 JAM 고유 파라미터다(§8 참고문헌).

카테고리별 TDR이 10%를 넘으면 그 카테고리는 0점. (설정 가능) v4.0.0에서 0.20 → 0.10으로 낮춰 희석 민감도를 2배로 했다(메트릭 changelog 참조).

2.3 카테고리 가중치 (기본값)

바이브코딩 진단이라는 목적에 맞춰 구조·중복·보안에 무게를 둔다.

카테고리가중치사유
ARCH (아키텍처)0.22순환 참조·결합은 사후 수정 비용이 가장 큼 (Lakos 1996)
CPLX (복잡도)0.20이해 불가능한 코드는 모든 후속 작업을 막음
SEC (보안)0.20AI 산출물의 실증된 약점 (Pearce 2022)
DUP (중복)0.15AI 산출물에서 증가 추세 실증 (GitClear 2024)
SIZE (크기)0.10CPLX와 신호 중복이 있어 낮게
RES (자원)0.08휴리스틱 정밀도 한계 반영
HYG (위생)0.05보조 신호

가중치 합 = 1.00. 가중치 재정의는 후속 설정 확장 항목이며, 현재 jam.yaml은 메트릭별 임계값 오버라이드를 지원한다.

2.4 등급 컷

점수등급해석
90–100A프로덕션 수준 구조
80–89B양호. 소규모 정리 권장
65–79C리팩토링 계획 필요
50–64D구조적 부채 상당. 기능 추가 전 정리 권장
0–49F재설계 검토

2.5 하드 룰 (등급 캡)

점수와 무관하게 다음은 jam-lite 등급 상한을 강제한다. 현재 설정으로 해제할 수 없으며, 설정 확장은 후속 항목이다.

근거: 가중 평균은 치명적 단일 결함을 희석시키는 약점이 있다(메트릭 합성의 고전적 함정 — Fenton & Pfleeger, *Software Metrics: A Rigorous and Practical Approach*).

SEC-02 인젝션 에스컬레이션 (v5.0.0): SQL/명령 인젝션 (SEC-02) 은 모든 언어에서 critical 로 에스컬레이션되었다. Java/C# 도 탐지되므로, 인젝션이 있는 앱은 클린 코드라도 Grade C 를 초과할 수 없다.

SEC-04 동적 실행 에스컬레이션 (v6.0.0): 위험 역직렬화/동적 실행 (SEC-04 — eval, pickle.loads, yaml.load, child_process.exec, new Function) 은 싱크 인자가 비리터럴 (변수·호출, 예: eval(req.body.x)) 이면 critical, 리터럴만 (예: new Function('return 1')) 이면 violation. 사용자 입력을 직접 실행하는 SSJI 앱은 Grade C 를 초과할 수 없다.

총점 밴드 클램프 (v7.0.0): 하드 등급 캡이 등급을 낮추면 표시 총점도 해당 등급 밴드 상한으로 함께 내린다(A 100 · B 89 · C 79 · D 64 · F 49). 이전에는 캡이 글자만 바꿔 "총점 98인데 등급 C" 같은 모순이 났다 — 단일 치명 결함이 가중 평균에서 희석돼 숫자가 높게 남았기 때문. 자연 등급이 이미 캡 이하면 총점은 건드리지 않는다.

2.6 worst-case 카테고리 상한 (v7.0.0)

§2.2의 밀도(debt/dev-cost) 정규화는 누적형 결함(긴 함수·중복 등 — 해악이 점진적)에는 옳지만, 단일 인스턴스가 곧 치명인 worst-case 차원에는 맞지 않는다. 70만 LOC 프로젝트의 시크릿 노출 1건은 debt 밀도로는 반올림 오차로 희석돼 SEC 카테고리가 ~100이 된다.

근거(문헌):

따라서 SEC 카테고리 점수에 밀도와 무관한 절대 상한을 둔다. 카테고리에 존재하는 가장 심각한 SEC finding이:

최고 severitySEC 카테고리 점수 상한
critical25 (+ §2.5 등급 캡 C)
violation60
warning/info상한 없음(밀도만)

category score = min(밀도 점수, worst-case 상한). SEC만 이렇게 다룬다:

2.7 risk profile 진단 + 집중 가드 (v8.0.0)

SIG/TÜViT 유지보수성 모델(Heitlager 2007; Alves 2010; Baggen 2012, §8)은 단일 평균/밀도 집계가 "고위험 부분의 존재를 가린다(masks the presence of high-risk parts)"고 실증하고, 평균 대신 risk profile(코드를 위험 버킷별 비율로 집계) + 벤치마크 도출 임계를 처방한다. JAM은 SQALE 밀도 점수(SonarQube 정렬·크기 일관성 검증됨)를 유지하되, 이 통찰을 두 방식으로 도입한다:

(1) 진단 — risk profile (점수 불변, 리포트 부록 A): 카테고리별로 심각도 분포(info/warning/violation/critical)와 size-independent 고위험 집중도 = violation·critical finding을 가진 고유 eligible production 파일 수 / eligible production 파일 수를 보고한다. 이로써 미지원 언어·테스트 파일이 비율을 희석하지 않는다. 단 이 비율 집계는 대형 repo에서 분자가 작은 절대 결함을 또 희석할 수 있어 ARCH 결합도는 절대 개수 상한(§2.8)으로 따로 잡는다.

(2) 가드 — 집중 상한 (점수 영향, 벤치마크 도출): 한 카테고리의 고위험 파일 비율이 임계(30%) 이상이면, 밀도 점수가 아무리 size로 희석돼도 그 카테고리는 깨끗한 A를 제시할 수 없다(점수 상한 79 = C 밴드). 임계는 벤치마크 도출값이다 — 16종 코퍼스에서 밀도상 ≥90점인 카테고리의 고위험 비율은 (소형 프로젝트 아티팩트 제외) ~12% 미만이고, 30%를 넘는 경우는 이미 밀도가 낮게 매긴다(예 33% DUP 프로젝트 = 59점). 30%는 정상 범위의 ~2.5배 너머라 현 코퍼스에선 무발화(dormant) — 밀도가 이미 포섭하기 때문 — 이고, 밀도가 어떤 이유로 놓치는 *미래의 병적 집중*만 잡는 안전망이다. 소형 프로젝트의 % 노이즈를 피하려 파일 20개 미만 프로젝트엔 적용하지 않는다.

설계 노트: SIG식 risk profile *전면 점수화*(밀도 점수를 risk-profile 집계로 교체 + 메트릭·언어별 벤치마크 임계 도출)는 더 큰 작업이며 상대 점수화·설명가능성(§1) 재정의를 수반한다. JAM은 검증된 SQALE 밀도를 유지하고 risk profile은 진단+가드로만 도입한다(SonarQube와 동일한 절충: 밀도 점수 + 보조 신호).

2.8 ARCH 결합도 집중 상한 (v9.0.0)

§2.2 밀도와 §2.7 비율 가드 둘 다 대형 코드베이스에서 *결합 결함의 집중*을 희석한다. sepilotd가 그 예다: 심각한 원심 결합(Ce 최대 44) god-module 5개 + 순환 1개가 있는데, debt 밀도로는 457분/70만 LOC ≈ TDR 0.0002 → ARCH 100, 고위험 비율도 0.2%(<30%)라 §2.7 가드도 무발화. 결국 실재하는 결합 집중이 "ARCH 100"으로 가려진다.

해결: 심각 결합 violation의 절대 개수로 ARCH 카테고리에 상한을 둔다. 비율(분모=전체 파일)이 아니라 개수여야 대형 repo에서 안 희석된다.

심각 결합 violation 개수ARCH 카테고리 점수 상한
0상한 없음(밀도만)
1–2 (국소)89
≥ 3 (구조적)79 (C 밴드)

2.9 god-type 극단 상한 (v10.0.0)

SIZE-04 god type은 클래스를 메서드 수(≥30)로 잡는데, 이는 30~100 구간에서 약한 proxy다 — 정당한 fluent/builder API가 이 구간에 산다(예: 잘 만든 dotnet-state-machine/stateless의 StateConfiguration.Permit() 오버로드 85개). 메서드 수만으론 정당한 빌더(85)와 진짜 god class(sepilotd ChannelMessagePipeline 79)를 못 가른다 — 숫자가 겹친다. 따라서 단순 개수 cap은 best 라이브러리(cs-stateless)를 오탐 강등시킨다.

메서드 수가 명백한 god-class 신호가 되는 건 극단뿐이다: 어떤 정당한 단일 클래스도 ~100메서드를 넘지 않는다. 그래서 ≥100메서드 god type(극단)만 SIZE 카테고리에 상한을 둔다.

극단(≥100메서드) god type 개수SIZE 카테고리 점수 상한
0상한 없음(밀도만)
1 (국소)89
≥ 2 (구조적)79 (C 밴드)

2.10 CPLX 극단 복잡도 상한 (v12.0.0)

CPLX도 밀도 정규화라, 대형 코드베이스의 병적으로 복잡한 소수 함수가 희석된다. 예: gitops-console의 4709줄 React 컴포넌트(CCN 445)가 CPLX 94, sepilotd의 CCN 1046 함수(+ CCN≥100 함수 28개)가 CPLX 95 — god function이 크기 분모에 가려진다.

해결: CCN≥100 함수의 절대 개수로 CPLX 카테고리에 상한.

CCN≥100 함수 개수CPLX 카테고리 점수 상한
0상한 없음(밀도만)
1 (국소)89
≥ 2 (구조적)79 (C 밴드)

3. 카테고리 내 부채 집계 — 가산 원칙 (v13.0.0)

category debt = Σ finding.debt_counted. SQALE은 요구사항 위반별 remediation cost를 합산해 technical debt를 구성하므로, JAM도 score-eligible finding의 비용을 가산한다(Letouzey 2012). 같은 함수에서 CPLX-01/02처럼 상관된 신호가 겹치는 문제는 각 메트릭의 낮은 비용과 analyzer의 중복 억제 규칙에서 처리한다.

v12까지 문서에 있던 “단일 파일은 카테고리 debt의 30%까지만 기여” 규칙은 제거했다. 이 규칙은 peer-reviewed 근거나 공인 표준이 없었고, 최종 category debt를 알아야 개별 파일 cap을 계산하는 순환 정의였으며, 한 파일에 결함이 집중됐다는 이유로 실제 수정 비용을 할인했다. 집중 결함이 평균에 가려지는 문제는 debt 할인이 아니라 §2.6~2.10의 근거 있는 worst-case/집중 상한과 risk profile로 다룬다(Heitlager et al. 2007; Baggen et al. 2012). 이는 재도입하지 않기로 한 근거 기반의 음성 결정이다.

4. 신뢰도 표기

휴리스틱 메트릭(SEC-02, RES-*, ARCH-02, HYG-02)은 finding에 confidence: high|medium|low를 기록한다. low confidence finding은 기본적으로 debt의 50%만 산입한다. 리포트에는 "확신 낮음" 마크를 단다 — 오탐을 숨기지 않고 드러내는 것이 신뢰를 만든다.

4.1 억제 finding

억제는 측정값을 삭제하는 것이 아니라 승인된 예외을 구조화하는 것이다. finding의 원본 severity, message, 기본 debt_minutes는 보존하고 debt_counted=0만 적용한다. 이 finding은 SEC worst-case, 고위험 파일 비율, 카테고리/등급 cap 조건에서도 제외된다. source/reason/owner/ticket/expires를 CSV/SARIF에 남기며, 만료된 규칙은 적용하지 않고 측정 무결성을 incomplete로 만든다.

4.2 점수 범위 제외 finding

억제가 없어도 test, documentation, configuration, generated, unsupported_language, unselected_categoryscore_exclusion에 사유를 기록하고 debt_counted=0으로 둔다. finding의 원래 severity·confidence·message·raw debt는 보존한다. 이 행들은 카테고리 debt, risk profile, worst-case/등급 cap, diff --fail-on-added 품질 게이트에 영향을 주지 않는다. “문제가 없음”과 “제품 점수 모집단이 아님”을 구분하기 위한 감사 계약이다.

5. 재현성 규칙

6. 보정(캘리브레이션) 계획

기본 임계값/가중치는 문헌값에서 출발하되, 다음 Metrics MAJOR에서 데이터 재사용으로 인한 과적합을 막도록 검증한다.

  1. 사용자 포트폴리를 제품 종류·언어·규모·무결성 수준으로 층화한 뒤 calibration / validation / holdout으로 프로젝트 단위 분리한다.
  2. 규칙별 precision, recall, F1, FP/KLOC, 언어 커버리지와 점수/순위 안정성을 독립 validation·holdout에서 보고한다. 평균 등급을 목표로 임계값을 맞추지 않음으로써 정답 누수를 막는다.
  3. metamorphic 불변성 테스트를 게이트한다: 문서만 추가해도 production 점수 불변, 테스트 코드가 production debt를 희석하지 않음, 관계없는 라인 이동에 finding ID 불변, 미지원 언어가 점수를 올리지 않음.
  4. 보안 규칙은 OWASP Benchmark와 NIST SARD의 라벨된 케이스로 외부 교차 검증하고, 커버리지·제외 클래스를 함께 공개한다.

7. jam-full 합성 점수

jam-full은 jam-lite를 대체하지 않고 상위 점수로 합성한다. 상세 정의는 12-jam-lite-full.md에 둔다.

컴포넌트가중치입력
jam-lite0.50이 문서의 SQALE 기반 정적 코드 건강도
test0.25`test-cli/report@1@2`의 pass/coverage/evidence/density/source-mix/skip/duration + report@2 quality 계약
security0.25security-cli의 severity/policy/category/fixability/toolchain/CVSS/CWE + scanner coverage 계약
lint (opt-in)0.15외부 린터 finding, 코드베이스 언어 비중에 비례 재조정
spec (opt-in)0.15외부 spec-cli의 요구사항 검증 결과(verified/failed/unverified)

테스트 컴포넌트는 통과율, 라인/분기 커버리지, evidence strength, coverage tail, test density, source coverage mix, skip risk, duration tail을 내부 가중 합성한다. 실패/에러 테스트가 있으면 최대 50점, 실행 테스트가 없으면 최대 40점, 큰 coverable surface에서 test density가 매우 낮으면 최대 70점으로 제한한다. 큰 coverable surface가 문서/설정 파일 coverage로만 구성되면 source coverage mix가 최대 50점 cap을 적용한다. report@2이면 zero-weight TEST-QUALITY를 기록하고 최종 test 점수를 min(기존 합성, quality.score)로 제한한다. 보안 컴포넌트는 severity baseline을 유지하면서 policy/category/fixability/toolchain/CVSS/CWE를 합성하고, critical/high/policy failure/secret/unfixed package/stale DB에 hard cap을 적용한다. schema 1의 zero-weight SEC-COVERAGE는 점수 항목이 아니라 측정 타당성 게이트이며, 요청 scanner가 하나라도 failed/skipped/missing이면 security 컴포넌트는 저점이 아니라 미측정 error다.

spec 컴포넌트(opt-in, --component spec)는 jam이 기능 적합성을 직접 측정하는 것이 아니라, 외부 spec-cli가 산출한 spec-report.json(요구사항별 verified/failed/unverified)을 합성만 한다. 점수 = round(100 × verified / requirements_total). failed > 0이거나 spec-cli exit code가 1이면 컴포넌트는 StatusFailed(품질 게이트)로 표시되고, 리포트 파싱 실패나 requirements_total <= 0이면 StatusError(미측정, docs/12 §3.2d 재정규화 대상)로 표시된다. v0.2의 zero-weight SPEC-TRACE-GATEspec-trace.json/spec-summary.json에 기록된 실제 trace gate(통과 100, 실패 0)를 보존하되 기능 점수에 다시 가중하지 않는다. 컴포넌트 이름은 "Functional (external)"로 고정해 jam 자체 측정과 구분한다. 세부 정의와 jam.yaml override는 12-jam-lite-full.md에 둔다.

7.1 companion v0.2 판정 근거

8. 참고문헌 (점수 모델 근거)

점수 모델의 모든 설계 결정은 아래 문헌/표준에 근거한다(AGENTS.md "Evidence & Citations" 규약). ✅ = deep-research 적대적 검증 통과(2026-06-13), ⚠️ = 출처는 확인됐으나 정밀 청구 검증은 후속 과제.

밀도/정규화 (누적형 차원, §2.2):

측정 범위·언어 coverage (§2.2, v13):

외부 증적·추적성 계약 (§7.1, v14):

worst-case 합성 (SEC 절대 상한, §2.5–2.6):

거부된 대안의 근거:

구조/순환 차원 (ARCH, 누적 희석 잔존 — 후속 검토용):

검증 메모: "큰 모듈이 오히려 결함 밀도가 낮다(size paradox)"를 Fenton & Neil (1999)에 귀속하지 말 것 — 해당 논문은 실재하나(IEEE TSE 25(5):675–689) 그 현상을 문서화하지 않음(적대적 검증 0-3 기각).