03. 점수 모델
1. 설계 원칙
- 설명 가능성 우선: 모든 점수는
detail.csv의 finding 합산으로 정확히 재현 가능해야 한다. 블랙박스 가중치 없음. - 가산·추적 가능한 부채: finding별 remediation cost를 더하며, 위치가 한 파일에 집중됐다는 이유로 비용을 사후 할인하지 않는다. 중복 신호는 메트릭 정의와 비용에서 통제한다.
- 범위가 명시된 밀도 정규화: 절대 건수가 아니라 해당 카테고리가 실제로 평가 가능한 production 코드 규모 대비 밀도로 평가한다. SQALE(Letouzey 2012)의 "수정 비용 / 개발 비용" 비율 개념을 차용한다.
- 점수는 진단 보조: 기본 등급 컷은 보수적으로 두고, 현재 사용자 설정은 메트릭 임계값 오버라이드부터 지원한다.
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부터 분모의 측정 모집단을 명시한다.
- 분석 입력 파일을
production,test,documentation,configuration,generated역할로 상호 배타적으로 분류한다. - 점수 모집단은 production 파일만이다. 나머지 역할의 finding은 삭제하지 않고
score_exclusion과 함께 감사 출력에 남긴다. - 카테고리마다 구현된 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.20 | AI 산출물의 실증된 약점 (Pearce 2022) |
| DUP (중복) | 0.15 | AI 산출물에서 증가 추세 실증 (GitClear 2024) |
| SIZE (크기) | 0.10 | CPLX와 신호 중복이 있어 낮게 |
| RES (자원) | 0.08 | 휴리스틱 정밀도 한계 반영 |
| HYG (위생) | 0.05 | 보조 신호 |
가중치 합 = 1.00. 가중치 재정의는 후속 설정 확장 항목이며, 현재 jam.yaml은 메트릭별 임계값 오버라이드를 지원한다.
2.4 등급 컷
| 점수 | 등급 | 해석 |
|---|---|---|
| 90–100 | A | 프로덕션 수준 구조 |
| 80–89 | B | 양호. 소규모 정리 권장 |
| 65–79 | C | 리팩토링 계획 필요 |
| 50–64 | D | 구조적 부채 상당. 기능 추가 전 정리 권장 |
| 0–49 | F | 재설계 검토 |
2.5 하드 룰 (등급 캡)
점수와 무관하게 다음은 jam-lite 등급 상한을 강제한다. 현재 설정으로 해제할 수 없으며, 설정 확장은 후속 항목이다.
- SEC critical(시크릿 노출·인젝션 등) ≥ 1건 → 최대 C
- ARCH-01 순환 SCC 크기 ≥ 5 → 최대 B
- HYG-07 파스 실패 ≥ 1건 → 최대 C
근거: 가중 평균은 치명적 단일 결함을 희석시키는 약점이 있다(메트릭 합성의 고전적 함정 — 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이 된다.
근거(문헌):
- 가중 평균 합성이 단일 치명 결함을 희석/은폐하는 것은 메트릭 합성의 고전적 함정 — Fenton & Pfleeger, *Software Metrics: A Rigorous and Practical Approach*; severity 는 순서척도라 산술 평균이 부적절.
- 크기가 최대 교란변수이므로 누적형 차원은 정규화가 필수 — El Emam et al. 2001, *The Confounding Effect of Class Size on the Validity of OO Metrics*, IEEE TSE. 밀도(SQALE TDR)는 그 표준 구현 — Letouzey 2012, *The SQALE Method*.
- 보안 결함을 worst-case 로 다루는 것이 업계 표준 — SonarQube Security/Reliability Rating 은 가장 심각한 결함 1건이 등급을 결정(Blocker 1건 → 최저 등급), 평균하지 않음. CVSS base score 도 "reasonable worst-case impact" 전제. JAM 의 SEC 절대 상한은 이 패턴을 카테고리 점수에 적용한 것. (단, OWASP Risk Rating 은 요인을 *평균*하므로 worst-case 가 보안의 *보편* 규칙은 아님 — §8.)
- 크기가 클수록 점수를 깎는 sub-linear 분모(규모 보상)는 채택하지 않음 — Boehm, COCOMO II 의 규모의 비경제(effort 지수 > 1).
따라서 SEC 카테고리 점수에 밀도와 무관한 절대 상한을 둔다. 카테고리에 존재하는 가장 심각한 SEC finding이:
| 최고 severity | SEC 카테고리 점수 상한 |
|---|---|
| critical | 25 (+ §2.5 등급 캡 C) |
| violation | 60 |
| warning/info | 상한 없음(밀도만) |
category score = min(밀도 점수, worst-case 상한). SEC만 이렇게 다룬다:
- 누적형 차원(CPLX·SIZE·DUP·HYG)은 밀도 정규화를 유지한다 — 실증상 크기 일관적(대형 OSS 코퍼스에서 CPLX vs log(CLOC) 상관 ≈ 0)이라 굳이 상한이 필요 없다.
- ARCH 결합도는 밀도를 기본으로 하되, 심각 결합/허브 violation이 개수로 집중되면 별도 상한을 둔다(§2.8) — god-module 여럿은 대형 repo에서 비율로 희석되기 때문.
- 순환(ARCH-01)·파스 실패(HYG-07)는 기존 등급 캡(§2.5)으로 충분하다. 소형 순환(SCC 2~3)은 흔하고 양성이라 카테고리 상한을 걸면 헤더 전용 라이브러리 등을 오탐 강등시킨다(그래서 §2.8 상한에서도 ARCH-01은 제외).
- RES는 밀도 유지 — RES-01은 정밀도 한계로 가중치를 0.08로 낮춘 휴리스틱이라(§2.3) 상한으로 증폭하면 테스트 파일 등의 오탐을 처벌하게 된다. (RES-01은 v11.0.0부터 테스트 파일 자체를 건너뛴다.)
- 테스트 코드 SEC는 상한을 발동시키지 않는다: v13에서는 test/spec/fixture/mock finding을 원 심각도 그대로 감사 출력에 보존하되 production 점수에서 제외한다(§4.2). 테스트의
eval/new Function/약한 암호는 제품 공격면이 아니므로 worst-case 상한·등급 캡을 발동시키지 않는다.
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 밴드) |
- 대상: ARCH-03(Ce > 위반 임계), ARCH-05(허브/god component), ARCH-06(변경 파급) violation. 이들은 *불연속 아키텍처 스멜*로, 유지보수 해악이 주변 깨끗한 코드 양과 무관하다(Arcan/Fontana 2017; SIG risk-profile은 평균이 아니라 집중으로 집계 — Heitlager 2007/Alves 2010/Baggen 2012, §8).
- 제외: 순환(ARCH-01)은 BigCycle 등급 캡(SCC≥5, §2.5)으로 따로 처리하고, 소형 순환 1건은 양성이라(잘 관리된 fmt·hyperfine도 보유) 상한 대상에서 뺀다 — 안 그러면 캘리브레이션이 깨진다.
- 캘리브레이션: 16종 코퍼스 best/stress 중 ARCH-03/05/06 violation을 가진 샘플은 0개(있는 violation은 전부 ARCH-01 순환) → 코퍼스 점수 전부 불변. 과결합 실프로젝트만 발화(sepilotd ARCH 100→79, 총점 87→82).
- 리포트는 밀도 점수와 별개로 원시 신호(순환·결합과다·허브·변경파급 개수)를 ARCH 섹션에 항상 노출해, 점수가 희석돼도 신호 자체는 보이게 한다.
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 밴드) |
- 30~100 구간 god type은 상한 없이 밀도 점수만 받는다(리포트엔 계속 노출). god class의 정식 정의(Lanza & Marinescu 2006 = WMC 高 + ATFD 高 + TCC 低)는 응집도·복잡도를 요구하지만 JAM의 SIZE-04는 메서드 수만 보므로, count-only 신호는 극단에서만 점수에 반영하는 게 정직하다.
- god file(최상위 선언 ≥100)은 god type과 구분되며 이 상한을 발동하지 않는다(메시지의 "God Type" 토큰으로 식별).
- 캘리브레이션: 16종 코퍼스 + 실프로젝트 중 ≥100메서드 타입을 가진 건 sepilotd뿐(DaemonClient 320, SqliteSemanticIndex 107) → SIZE 99→79, 총점 82→80/B. cs-stateless(최대 85)·전 코퍼스 불변.
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 밴드) |
- 근거: McCabe(1976) — CCN>10 위험 권고, >50은 사실상 테스트 불가, ≥100은 어떤 기준으로도 god function/component다(정당한 단일 함수는 도달하지 않음). CCN 30~100 구간은 기존처럼 밀도 점수만 받는다(이미 critical로 보고됨).
- 캘리브레이션: 16종 코퍼스 best/stress 전부 CCN≥100 함수 0개(최대 fmt CCN 77) → 코퍼스 점수 전부 불변. god component 가진 실 프로젝트만 발화 — gitops 91/A→88/B, sepilotd 80/B→77/C, jpad-web 84/B→80/B, jam(max 51) 불변.
- 한계: JSX 조건부 렌더(
&&/?:)가 React 컴포넌트 CCN을 부풀리는 경향이 있어 임계를 100으로 높게 잡았다(소형 컴포넌트는 도달 불가). 4000줄급 god component만 잡힌다.
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_category는 score_exclusion에 사유를 기록하고 debt_counted=0으로 둔다. finding의 원래 severity·confidence·message·raw debt는 보존한다. 이 행들은 카테고리 debt, risk profile, worst-case/등급 cap, diff --fail-on-added 품질 게이트에 영향을 주지 않는다. “문제가 없음”과 “제품 점수 모집단이 아님”을 구분하기 위한 감사 계약이다.
5. 재현성 규칙
- 동일 입력 → 동일 출력(결정적). 파일 순회는 경로 정렬 순서 고정, 해시 시드 고정.
- 점수 계산 전 과정이 CSV와 manifest/report에서 역산 가능:
Σ(score-eligible cost × confidence_factor) / (eligible production CLOC × 3.6)→ 카테고리 점수 → 유효 가중 합. report.md 부록에 카테고리별 production/eligible CLOC, coverage, debt, dev cost를 출력한다. jam-run.json에 CLI/Metrics Spec, 정책/유효 설정 hash, 범위, 실제 분석 입력의 source hash, Git commit/tree/dirty, source 스킵/파스/analyzer/억제 무결성을 각인한다. 이 계약이 같지 않은 수치는 기본 diff에서 비교하지 않음으로써 "재현 가능한 오답"을 "정답"처럼 보이지 않게 한다.
6. 보정(캘리브레이션) 계획
기본 임계값/가중치는 문헌값에서 출발하되, 다음 Metrics MAJOR에서 데이터 재사용으로 인한 과적합을 막도록 검증한다.
- 사용자 포트폴리를 제품 종류·언어·규모·무결성 수준으로 층화한 뒤 calibration / validation / holdout으로 프로젝트 단위 분리한다.
- 규칙별 precision, recall, F1, FP/KLOC, 언어 커버리지와 점수/순위 안정성을 독립 validation·holdout에서 보고한다. 평균 등급을 목표로 임계값을 맞추지 않음으로써 정답 누수를 막는다.
- metamorphic 불변성 테스트를 게이트한다: 문서만 추가해도 production 점수 불변, 테스트 코드가 production debt를 희석하지 않음, 관계없는 라인 이동에 finding ID 불변, 미지원 언어가 점수를 올리지 않음.
- 보안 규칙은 OWASP Benchmark와 NIST SARD의 라벨된 케이스로 외부 교차 검증하고, 커버리지·제외 클래스를 함께 공개한다.
7. jam-full 합성 점수
jam-full은 jam-lite를 대체하지 않고 상위 점수로 합성한다. 상세 정의는 12-jam-lite-full.md에 둔다.
| 컴포넌트 | 가중치 | 입력 | |
|---|---|---|---|
| jam-lite | 0.50 | 이 문서의 SQALE 기반 정적 코드 건강도 | |
| test | 0.25 | `test-cli/report@1 | @2`의 pass/coverage/evidence/density/source-mix/skip/duration + report@2 quality 계약 |
| security | 0.25 | security-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-GATE는 spec-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 판정 근거
TEST-QUALITY의 weight 0 + 상한은 report@2가 이미 pass/coverage/hygiene/distribution을 합성한 canonical measurement이기 때문이다. 같은 관측을 JAM의 기존 양의 가중치에 다시 넣으면 이중 계상된다. Fenton & Pfleeger(1997)는 서로 다른 측정 척도의 무비판적 합성과 평균에 의한 고위험 은폐를 경고하며, ISO/IEC 25023:2016은 정의된 product-quality measure의 해석 경계를 요구한다. 따라서 새로운 임의 평균 대신 더 풍부한 upstream 측정치를 보수적 상한으로 사용한다.SEC-COVERAGE의 weight 0 + fail-closed error gate는 vulnerability severity가 아니라 측정 타당성이다. ISO/IEC/IEEE 15939:2017은 결과의 타당성과 측정 과정 증적을 요구하고, NIST SP 800-218 SSDF v1.1 PW.7/PW.8은 코드 분석·검토·테스트 수행과 결과 기록을 요구한다. security-cli v0.2가 선언한complete/scanner status 계약을 만족하지 않은 0 findings는 clean evidence가 아니므로 종합에 0점으로 넣지 않고 미측정으로 제외하며 exit 4로 실패한다.SPEC-TRACE-GATE의 weight 0 + binary upstream gate는 ISO/IEC/IEEE 29148:2018의 요구사항 추적성·verification information을 보존하되, warning/error마다 JAM 고유 감점 계수를 새로 만들지 않기 위함이다. 통과/실패 경계는 실행 시 선택한 spec-clitrace_fail_on이 단일 원장이다.- 거부된 대안: 세 신규 지표에 양의 기본 가중치를 부여하거나 diagnostic/scanner 건수에 임의 penalty를 주는 안은 채택하지 않았다. 앞의 두 경우는 동일 evidence의 중복 합성이고, 마지막은 표준이나 독립 calibration에 없는 숫자를 만드는 일이기 때문이다. 신규 수치는 upstream canonical score 또는 binary validity/gate에서만 온다.
8. 참고문헌 (점수 모델 근거)
점수 모델의 모든 설계 결정은 아래 문헌/표준에 근거한다(AGENTS.md "Evidence & Citations" 규약). ✅ = deep-research 적대적 검증 통과(2026-06-13), ⚠️ = 출처는 확인됐으나 정밀 청구 검증은 후속 과제.
밀도/정규화 (누적형 차원, §2.2):
- ✅ El Emam, Benlarbi, Goel & Rai (2001). *The Confounding Effect of Class Size on the Validity of Object-Oriented Metrics*. IEEE TSE 27(7):630–650. — 크기 통제 후 24개 OO 메트릭 중 4개만 결함과 연관 → 규모 정규화 필수. (방법론 논쟁 Evanco 2003은 "어떻게"이지 "크기가 지배하는가"가 아님.)
- ✅ Letouzey (2012). *The SQALE Method for Evaluating Technical Debt*. MTD@ICSE. DOI 10.1109/MTD.2012.6225997. — TDR = 수정비용 / 개발비용. 부채 은유: Cunningham (1992), OOPSLA.
- ✅ SonarQube Maintainability(SQALE) Rating = 밀도 밴드 A≤0.05·B≤0.10·C≤0.20·D≤0.50·E>0.50 ("부채 밀도/코드 크기"). JAM은 이 *구조*를 차용하되 파라미터(3.6분/LOC, TDR_max 0.10)는 SonarQube 기본(30분/LOC, A≤0.05)과 다른 의도적 보정값. (SonarQube docs 9.9 LTA, 안정적.)
측정 범위·언어 coverage (§2.2, v13):
- ✅ ISO/IEC/IEEE 15939:2017, *Systems and software engineering — Measurement process*. — 정보 요구에 맞는 측정 범위·측정치·평가 기준을 정의하고 측정 결과의 타당성을 관리하는 표준. JAM은 파일 role, eligible CLOC, coverage, integrity를 manifest에 명시한다.
- ✅ SonarSource, *Setting the initial scope*. — main/source와 test 범위는 상호 배타적이어야 하며 test 파일은 source 관련 metrics와 LOC에서 제외된다. JAM의 production/test 분리의 직접적 산업 선례.
- ✅ SonarSource, *Introduction to quality profiles*. — rule set은 언어별 quality profile로 관리된다. JAM이 카테고리×언어 지원표에 없는 CLOC를 해당 category denominator에서 제외하는 산업적 근거.
- ✅ ISO/IEC 25023:2016, *Measurement of system and software product quality*. — 품질 모델의 정량 측정 정의와 해석 경계를 명시하는 상위 표준. JAM은 적합성 인증을 주장하지 않고 적용 가능한 source measure 범위만 기록한다.
외부 증적·추적성 계약 (§7.1, v14):
- ✅ NIST SP 800-218 SSDF v1.1 (2022), *Secure Software Development Framework*, PW.7/PW.8. — 사람/자동 도구를 통한 코드 검토·분석·테스트와 결과 기록. JAM은 scanner 수행 여부를 finding 수와 분리한다.
- ✅ ISO/IEC/IEEE 29148:2018, *Requirements engineering*. — 요구사항 간·설계/구현/검증 산출물 간 추적성과 verification/validation 정보의 표준 앵커. JAM은 spec-cli의 trace diagnostic·relation·gate를 외부 증적으로 보존한다.
- ✅ test-cli/security-cli/spec-cli v0.2 machine contracts (2026-07-14). —
test-cli/report@2, security schema 1 coverage/scanners/security_status, spec schema 1 trace/summary의 정확한 필드·binary gate 정의. 표준이 정하지 않는 구체 JSON 및 상태 전이는 각 producer 계약을 단일 원장으로 삼는다.
worst-case 합성 (SEC 절대 상한, §2.5–2.6):
- ✅ SonarQube Security & Reliability Rating = worst-case: A=0건, B=경미 1건↑, C=주요 1건↑, D=치명 1건↑, E=blocker 1건↑ — 건수·코드 크기 무관, 최악 1건이 등급 결정. JAM의 SEC 카테고리 절대 상한의 직접적 산업 선례. (SonarQube docs 9.9 LTA; MQR 모드 라벨만 바뀌고 원리 동일.)
- ✅ Fenton & Pfleeger (1997). *Software Metrics: A Rigorous and Practical Approach*, 2nd ed. — severity는 순서척도(ordinal)라 산술평균이 측정이론상 부적격(중앙값/box-plot 권장) → 치명 결함은 평균이 아니라 worst-case(max)로 집계해야 함. (정통 입장이며 일부 이견 존재.)
- ✅ FIRST, *CVSS v3.1/v4.0 Specification* — base score는 "reasonable worst-case impact across deployed environments" 전제, Scope 변경 시 "whichever component suffers the most severe outcome"로 평가. 즉 보안 *심각도*는 worst-case가 표준. (단 *절대* 최악이 아니라 현실적 최악.)
- ⚠️→정정 OWASP *Risk Rating Methodology* — worst-case가 아님: 8개 likelihood 요인과 impact 요인을 평균해 likelihood×impact 2D 행렬(Note~Critical)로 산출. worst-case는 "어느 위협 행위자를 모델링할지" 선택에만 쓰임. → 보안 집계가 *보편적으로* worst-case라고 일반화하면 안 됨. JAM이 차용하는 근거는 CVSS(심각도)와 SonarQube(다중 finding→카테고리 등급)의 worst-case이지 OWASP가 아니다.
거부된 대안의 근거:
- ✅ Boehm et al. (2000). *Software Cost Estimation with COCOMO II* (USC Model Definition Manual v2.1). — 노력 PM = A×Size^E, post-architecture 모델 E = 0.91 + 0.01·Σ(스케일팩터)로 통상 E>1(규모의 비경제, ~0.91–1.226; 간이형 1.01–1.26). 큰 시스템일수록 더 어려움 → 크기를 보상하는 sub-linear 분모(CLOC^0.9 등) 거부.
critical일괄 cap/가중 거부: JAM은critical을 누적형 임계(CPLX-01 CCN≥50, SIZE-01 file≥2000, DUP-01 ≥20%)에도 사용하므로, 측정이론상 이질 척도를 한 손잡이로 묶는 오류. 시뮬레이션에서 일괄 적용 시 우수 라이브러리(fmt 95/A→77/C) 오탐 강등 확인 → worst-case 차원(SEC)에만 절대 처리.- 단일 파일 30% debt 할인 거부(v13): SQALE remediation cost는 위반별 가산 값이고, 결함 위치 집중은 수정 비용을 줄이지 않는다. 평균이 고위험 부분을 가리는 문제는 SIG risk profile(Heitlager 2007; Baggen 2012)과 근거 있는 category ceiling으로 처리한다. 출처 없는 cap을 재도입하려면 독립 calibration/holdout 근거와 Metrics MAJOR가 필요하다.
구조/순환 차원 (ARCH, 누적 희석 잔존 — 후속 검토용):
- ⚠️ Lakos (1996). *Large-Scale C++ Software Design* — 순환 의존은 1급 설계 결함.
- ⚠️ MacCormack, Rusnak & Baldwin (2006). *Exploring the Structure of Complex Software Designs*. Management Science. — 변경 전파(propagation cost).
- ⚠️ Arcelli Fontana et al. (2017). *Arcan*. ICSA. — 아키텍처 스멜.
- ✅ 표준화 대안(밀도 평균의 희석 한계 정면 돌파): SIG/TÜViT 모델 — Heitlager, Kuipers & Visser (2007, QUATIC, DOI 10.1109/QUATIC.2007.7); Alves, Ypma & Visser (2010, *Deriving Metric Thresholds from Benchmark Data*, ICSM, DOI 10.1109/ICSM.2010.5609747); Baggen, Correia, Schill & Visser (2012, *Standardized code quality benchmarking*, Software Quality Journal 20(2):287–307, DOI 10.1007/s11219-011-9144-9). 검증된 핵심 명제(verbatim): "averaging to aggregate measures on individual system parts tends to mask the presence of high-risk parts" — 복잡도는 멱법칙 분포라 문제는 소수 outlier에 있음 → 해법은 가중치 강화가 아니라 벤치마크 도출 임계 + risk profile(low/moderate/high/very-high 위험 버킷별 코드 비율; 예 McCabe 6/8/15 = 벤치마크 70/80/90%). 거대 시스템에서도 소량 고위험을 보존 → JAM의 ARCH 잔존 희석을 정말 고치려면 이 경로(가중치 X). Quamoco — Wagner et al. (2012, ICSE; IST 62:101–123, 2015).
검증 메모: "큰 모듈이 오히려 결함 밀도가 낮다(size paradox)"를 Fenton & Neil (1999)에 귀속하지 말 것 — 해당 논문은 실재하나(IEEE TSE 25(5):675–689) 그 현상을 문서화하지 않음(적대적 검증 0-3 기각).