06. 출력 명세
jam-lite 스캔 1회 = 기본 출력 3개 (출력 디렉터리 기본값 ./, --out으로 변경):
jam-report.md— 사람이 읽는 진단 리포트jam-detail.csv— 점수 산출 근거 전수 데이터 (기계 처리·감사용)jam-run.json— 도구·Metrics Spec·정책·실행 범위·Git 상태·측정 완전성을 각인한 항상 생성되는 런 매니페스트
추가로 --json 옵션 시 jam-result.json(report와 동일 정보의 구조화 버전, CI 연동용)을 생성하고, --sarif 옵션 시 jam.sarif(SARIF 2.1.0, Code Scanning 연동용)을 생성한다.
--set full 또는 jam full은 위 jam-lite 산출물에 더해 다음을 생성한다.
jam-full-report.md— jam-lite/test/security 컴포넌트 합성 리포트jam-full.json— jam-full 합성 점수 JSONtest/report.json— test-cli 표준 JSONsecurity/security-report.json— security-cli 표준 JSON
1. jam-detail.csv
1.1 원칙
- 모든 finding 1건 = 1행. 점수에 산입되지 않은 info·억제·비production·미지원 언어 finding과 confidence 보정 전후가 모두 드러나야 한다. CSV와 run/report 메타데이터로 최종 점수를 역산할 수 있어야 한다(03-scoring-model.md §5).
- RFC 4180 준수, UTF-8(BOM 없음), 헤더 1행. Excel 호환 BOM 옵션(
--csv-bom)은 후속 후보. - 정렬:
category, metric_id, file, line— 결정적 출력.
1.2 컬럼 정의
| 컬럼 | 타입 | 설명 | 예 |
|---|---|---|---|
finding_id | string | 안정적 ID = metric_id + 파일 + 심볼/원본 메시지/소스 앵커 지문 8자리. 관계없는 라인 이동은 ID를 바꾸지 않음 | CPLX-01-a3f9c2d1 |
category | enum | SIZE/CPLX/ARCH/DUP/SEC/RES/HYG | CPLX |
metric_id | string | 메트릭 카탈로그의 ID | CPLX-01 |
metric_name | string | 사람용 이름 | Cyclomatic Complexity |
metric_version | int | 해당 메트릭의 산출식 버전 (11-metrics-versioning.md) | 1 |
severity | enum | info/warning/violation/critical | violation |
confidence | enum | high/medium/low | high |
file | string | 루트 기준 상대 경로, / 구분 | src/api/handler.ts |
line | int | 1-based 시작 라인 | 142 |
end_line | int | 끝 라인(단일 라인이면 동일) | 198 |
column | int | 1-based, 불명확하면 1 | 1 |
symbol | string | 함수/타입/모듈명 (해당 시) | processOrder |
value | float | 측정값 | 23 |
threshold | float | 적용된 임계값 | 15 |
message | string | 설명 (한/영 설정 따름) | 함수 'processOrder'의 순환 복잡도가 23으로 임계값 15를 초과 |
related | string | 관련 위치 목록 path:line 세미콜론 구분 (클론 상대편, 사이클 경로) | src/util/order.ts:31;src/api/handler.ts:142 |
debt_minutes | float | 부여된 수정 비용(분) | 30 |
debt_counted | float | 점수 범위·억제·confidence 보정 후 실제 산입된 비용 | 30 |
rule_ref | string | 근거 참조 (CWE, 문헌 키) | CWE-89 / McCabe1976 |
suppressed | bool | 점수에서 제외된 억제 finding 여부. 억제돼도 severity와 message는 원본을 보존 | true |
suppression_source | enum | jam.yaml / inline; 비억제면 빈 값 | jam.yaml |
suppression_reason | string | 억제 사유 | 테스트 fixture |
suppression_owner | string | 면제 책임자(선택) | security-team |
suppression_ticket | string | 추적 ticket/결정 ID(선택) | SEC-42 |
suppression_expires | date | 만료일 YYYY-MM-DD(선택). 만료된 규칙은 적용되지 않음 | 2026-12-31 |
score_exclusion | enum/string | 비억제 finding이 점수 밖인 이유. 빈 값이면 eligible; test/documentation/configuration/generated/unsupported_language/unselected_category | test |
1.3 예시
finding_id,category,metric_id,metric_name,metric_version,severity,confidence,file,line,end_line,column,symbol,value,threshold,message,related,debt_minutes,debt_counted,rule_ref,suppressed,suppression_source,suppression_reason,suppression_owner,suppression_ticket,suppression_expires,score_exclusion
CPLX-01-a3f9c2d1,CPLX,CPLX-01,Cyclomatic Complexity,1,violation,high,src/api/handler.ts,142,198,1,processOrder,23,15,"함수 'processOrder'의 순환 복잡도 23 (임계값 15)",,30,30,McCabe1976,false,,,,,,
SEC-01-f01c44aa,SEC,SEC-01,Hardcoded Secret,2,critical,high,config/db.go,17,17,22,,4.9,4.5,"고엔트로피 문자열이 'password' 변수에 할당됨",,120,0,CWE-798,true,jam.yaml,테스트 fixture,security-team,SEC-42,2026-12-31,
SEC-01-2d81c614,SEC,SEC-01,Hardcoded Secret,2,critical,high,tests/fixture.ts,9,9,1,,4.8,4.5,"테스트 fixture 시크릿",,120,0,CWE-798,false,,,,,,test
1.4 점수 역산용 메타데이터
CSV는 순수 finding 데이터만 유지한다. 실행 메타데이터와 비교 가능성은 jam-run.json, 카테고리별 합계(debt, dev cost, TDR, score)는 jam-report.md 부록 A에 기록한다.
1.5 jam-run.json 런 매니페스트
jam-run.json은 옵션이 아니며 모든 scan에서 생성된다. JSON Schema는 docs/schemas/jam-run.schema.json이다.
tool.version,metricsSpec.version: 산출물을 만든 정확한 도구/스펙.policy:jam.yamlpolicy.id/version과 정규화된 정책 SHA-256.configuration.hash: 정책 hash에 CLI--only/--exclude/--include-generated/metric set을 합친 유효 실행 설정 hash.scope: 선택 카테고리, 생성 파일 포함 여부, 적용 exclude.target: 전체 파일/CLOC와productionFiles/productionCloc, role별 CLOC, 카테고리별eligibleCloc/eligibleFiles/ratio, 언어별 CLOC, 실제 분석 입력(path+bytes)의sourceHash, Git 저장소인 경우 commit/tree/dirty 상태.sourceHash는 dirty worktree도 정확히 구분하지만 소스가 달라지는 정상 diff를 막는 호환성 키에는 포함하지 않는다.integrity.status:complete또는incomplete. production source 스킵·파스 실패, analyzer 오류, config 경고, 만료·사유 미기재 억제가 하나라도 있으면incomplete. test/documentation/configuration에서만 발생한 스킵·파스 실패는 감사 배열에 남지만 production 측정 완전성을 낮추지 않는다.comparisonKey: 도구/스펙/정책/유효 설정의 SHA-256 비교 키.jam diff는 이 키와 완전성을 검증한다.
2. jam-report.md
2.1 구조
# JAM Lite Report — <프로젝트명>
> 스캔: 2026-06-10 14:02 · jam v0.46.0 · metrics v14.5.0 · 대상: /path/to/project
> 파일 1,243개 · 전체 98,402 CLOC · production 1,102개/92,000 CLOC · 언어: TypeScript 61%, Python 30%, 기타 9%
## JAM Lite Score: 62 / 100 (C)
[등급 캡 적용 시: "SEC critical 1건으로 등급 상한 C 적용" 명시]
| 카테고리 | 점수 | 가중치 | 핵심 문제 |
|---|---|---|---|
| Architecture | 55 | 0.22 | 순환 참조 3건 (최대 SCC 크기 4) |
| Complexity | 70 | 0.20 | CCN>15 함수 12개 |
| ...
## Top Findings (수정 효과 큰 순)
1. **[ARCH-01]** `src/order/` ↔ `src/billing/` 순환 — service.py:3 → core.py:7 → models.py:12 → (back)
2. **[SEC-01]** 하드코딩 시크릿 — config/db.go:17
3. ...상위 10건
## 카테고리별 상세
### Architecture (55/100)
- 순환 의존 3건: (각 사이클 경로를 화살표 체인으로 표기, 파일:라인 링크)
- Fan-out 과다 모듈: ...
[카테고리마다: 무엇이/어디서/왜 문제인지 + 권장 조치 1줄]
## 파일 핫스팟 (debt 상위 10개 파일)
| 파일 | debt(분) | 주요 메트릭 |
|---|---|---|
## AX Readiness: N/4 (점수 미산입)
| 검사 | 항목 | 결과 | 상세 |
|---|---|---|---|
[AX-01 에이전트 지침 / AX-02 기계 검증 게이트 / AX-03 재현 가능 환경 / AX-04 검증 루프 — docs/18 축 C. 코드 품질 점수와 무관한 환경 준비도라 점수·등급·diff에 산입하지 않는다]
## 부록 A. 점수 계산 근거
| 카테고리 | coverage | eligible/production CLOC | findings | dev cost(분) | debt_counted(분) | TDR | score |
[가중 합산식 명시 → 최종 점수 재현 가능]
## 부록 B. 스캔 범위
제외된 파일/디렉터리와 사유(gitignore, 생성 파일, 비UTF-8 등), 파스 실패 파일 목록
## 부록 C. 메트릭 설명과 임계값
[적용된 임계값 표 + 문헌 참조 — 점수의 정당성을 리포트 자체가 설명]
2.2 작성 규칙
- 모든 위치 언급은
path:line형식 — 터미널/에디터에서 클릭 가능. - Top Findings는
debt_counted내림차순, 동률이면 severity 순. - 현재 메시지는 한국어로 출력한다.
--lang ko|en은 후속 후보. - 리포트는 5천 라인을 넘지 않게: 카테고리별 상세는 상위 20건 + "나머지 N건은 detail.csv 참조".
3. jam-result.json (옵션)
CI 연동용. 스키마(요약):
{
"version": "3",
"metricSet": "jam-lite",
"tool": {"name": "jam", "version": "v0.46.0"},
"metricsSpec": {"version": "14.5.0"},
"scannedAt": "2026-06-10T14:02:11+09:00",
"target": {"path": "...", "files": 1243, "cloc": 98402, "productionFiles": 1102, "productionCloc": 92000, "languages": {"typescript": 0.61}},
"score": {"status": "assessed", "total": 62, "grade": "C", "capNote": "SEC critical finding 존재", "notAssessed": [], "devCostMinutes": 331200,
"categories": {"ARCH": {"score": 55, "debtMinutes": 4320, "tdr": 0.013, "highRiskFiles": 4, "highRiskShare": 0.0036, "productionCloc": 92000, "eligibleCloc": 92000, "productionFiles": 1102, "eligibleFiles": 1102, "coverage": 1.0, "devCostMinutes": 331200}}},
"findingCounts": {"critical": 1, "violation": 47, "warning": 102, "info": 215, "suppressed": 2, "excluded": 18},
"policy": {"id": "engineering/portfolio", "version": "3", "hash": "<sha256>"},
"configuration": {"hash": "<sha256>"},
"scope": {"metricSet": "jam-lite", "categories": ["SIZE", "CPLX", "ARCH", "DUP", "SEC", "RES", "HYG"], "includeGenerated": false, "excludes": []},
"integrity": {"status": "complete", "reasons": [], "skippedFiles": [], "parseFailures": [], "analyzerErrors": [], "configWarnings": [], "suppressionIssues": [], "notAssessed": []},
"comparisonKey": "<sha256>",
"ax": {"score": 3, "total": 4, "checks": [{"id": "AX-01", "name": "Agent Instructions", "name_ko": "에이전트 지침", "pass": true, "detail": "AGENTS.md — 검증 방법 포함."}]}
}
ax는 v0.46.0에 추가된 선택적 additive 필드다(docs/18 축 C의 Agent-Experience readiness). 점수·등급·comparisonKey·jam diff 어디에도 산입되지 않는 순수 보고 데이터이며, 구버전 소비자는 무시해도 안전하다.
finding 전체는 JSON에 중복 포함하지 않는다(CSV가 원본). findingCounts의 네 severity 건수는 score-eligible finding만 세며, suppressed와 excluded는 이들과 겹치지 않는 별도 건수다. 따라서 여섯 값의 합이 CSV 데이터 행 수와 같다. production code가 없어 평가할 수 없으면 score.status="not_assessed", grade="N/A", categories={}다. JSON Schema는 docs/schemas/jam-result.schema.json이다.
3.5 jam-full.json
full 모드의 합성 결과는 jam-full.json에 기록한다. jam-lite의 finding 원본은 계속 jam-detail.csv이며, test/security/spec의 원본은 각 외부 CLI JSON이다. JSON Schema는 docs/schemas/jam-full.schema.json에 둔다.
{
"version": "2",
"metricSet": "jam-full",
"tool": {"name": "jam", "version": "v0.46.0"},
"score": {
"total": 88,
"grade": "B",
"status": "final",
"usable": true,
"weights": {"jam-lite": 0.5, "test": 0.25, "security": 0.25}
},
"decision": {"status": "pass", "reasons": []},
"components": [
{"id": "jam-lite", "score": 82, "status": "ok", "reportPath": "jam-report.md"},
{
"id": "test",
"score": 94,
"status": "ok",
"reportPath": "test/report.json",
"metrics": [
{"id": "TEST-PASS", "score": 100, "weight": 0.25, "status": "ok"},
{"id": "TEST-LINE-COV", "score": 90, "weight": 0.20, "status": "ok"},
{"id": "TEST-BRANCH-COV", "score": 0, "weight": 0.12, "status": "not_assessed"},
{"id": "TEST-EVIDENCE", "score": 85, "weight": 0.10, "status": "ok"},
{"id": "TEST-COV-TAIL", "score": 88, "weight": 0.08, "status": "ok"},
{"id": "TEST-DENSITY", "score": 92, "weight": 0.12, "status": "ok"},
{"id": "TEST-SOURCE-COV", "score": 100, "weight": 0.10, "status": "ok"},
{"id": "TEST-SKIP-RISK", "score": 100, "weight": 0.02, "status": "ok"},
{"id": "TEST-DURATION-TAIL", "score": 100, "weight": 0.01, "status": "ok"},
{"id": "TEST-QUALITY", "score": 94, "weight": 0.00, "status": "ok"}
]
},
{
"id": "security",
"score": 95,
"status": "ok",
"reportPath": "security/security-report.json",
"metrics": [
{"id": "SEC-SEVERITY", "score": 100, "weight": 0.25, "status": "ok"},
{"id": "SEC-POLICY", "score": 100, "weight": 0.18, "status": "ok"},
{"id": "SEC-CATEGORY", "score": 100, "weight": 0.12, "status": "ok"},
{"id": "SEC-FIXABILITY", "score": 100, "weight": 0.10, "status": "ok"},
{"id": "SEC-SUPPRESSION", "score": 100, "weight": 0.05, "status": "ok"},
{"id": "SEC-TOOLCHAIN", "score": 0, "weight": 0.12, "status": "not_assessed"},
{"id": "SEC-CVSS", "score": 0, "weight": 0.10, "status": "not_assessed"},
{"id": "SEC-CWE", "score": 0, "weight": 0.08, "status": "not_assessed"},
{"id": "SEC-COVERAGE", "score": 100, "weight": 0.00, "status": "ok"}
]
}
]
}
metrics는 jam-full이 외부 CLI JSON을 어떻게 점수화·검증했는지 보여주는 하위 지표 배열이다. status=not_assessed인 하위 지표는 해당 외부 도구가 값을 제공하지 않은 경우이며, 컴포넌트 내부 가중치 합산에서 제외한다. TEST-QUALITY, SEC-COVERAGE, SPEC-TRACE-GATE는 weight=0인 계약 신호다. 첫째는 test 점수 상한, 나머지는 measurement/trace gate에 쓰이며 기존 evidence를 양의 가중치로 이중 계상하지 않는다.
schema v2의 decision은 pass|fail|incomplete 3상태다. 모든 요청 컴포넌트와 jam-lite 무결성이 완전한 경우에만 score.status=final, score.usable=true다. 측정은 완전하지만 품질/보안 게이트가 실패하면 decision=fail이며 점수는 여전히 비교 가능한 최종값이다. jam-lite 무결성 불완전, 외부 컴포넌트 error, 또는 요청 컴포넌트 not_assessed가 하나라도 있으면 decision=incomplete, score.status=provisional, score.usable=false이고 reasons가 원인을 기록한다. 이 잠정 숫자는 일부 성공한 측정의 진단에는 쓸 수 있지만 포트폴리오 순위·릴리스 승인에는 사용할 수 없다.
컴포넌트가 실패하면 reason이 추가될 수 있다.
{"id": "test", "score": 0, "status": "failed", "reason": "no_test_evidence"}
부분 full(--component security, --skip-test)에서는 선택된 컴포넌트만 components와 artifacts에 포함하고, score.weights는 effective weight를 기록한다.
실제로 생성된 companion 산출물만 artifacts에 기록된다. spec v0.2 컴포넌트를 선택하면 생성 여부에 따라 specReport, specTrace, specSummary가 기록된다. security v0.2 coverage가 불완전하면 component는 status=error, reason=coverage_incomplete이며 full 실행은 exit 4다.
4. jam.sarif (옵션)
--sarif 사용 시 SARIF 2.1.0 JSON을 jam.sarif로 생성한다. SARIF는 GitHub Code Scanning 같은 도구에 위치 정보를 전달하기 위한 투영이며, 점수 산출과 감사의 원본은 계속 jam-detail.csv다.
runs[0].tool.driver:name=jam, CLI 버전, 사용된 metric rule 목록.rules: 해당 스캔에서 실제 등장한metric_id만 포함. rule properties에는category,metricVersion,metricsSpec,ruleRef, 적용 임계값을 기록한다.results: finding 1건이 SARIF result 1건.ruleId는metric_id,message.text는 CSV message와 동일하다.- level 매핑:
critical/violation→error,warning→warning,info→note. locations[0]: CSV의file,line,end_line,column을 SARIF region으로 기록한다.relatedLocations: CSVrelated위치를 중복 블록/순환 경로 추적용으로 기록한다.partialFingerprints.jamFindingId:finding_id와 같은 안정 ID를 기록해 code-scanning 재발견을 돕는다.- result properties에는
severity,confidence,symbol,value,threshold,debtMinutes,debtCounted,scoreExclusion,metricsSpec을 포함한다.
4.5 assurance 출력
jam assurance verdict는 security-verdict.json/.md, jam assurance evidence는 control-evidence.json/.md를 생성한다. 이 출력은 jam-lite/jam-full 점수와 독립이다.
- security verdict는 입력 파일 SHA-256, JAM Git commit/source hash, security-cli target·생성 시각·scanner coverage/toolchain, 정책/리뷰 hash와 후보별 상태를 보존한다.
- control evidence는 조직이 선언한 framework/version/scope와 통제별 evidence ref, 원본 파일 SHA-256, 예외 만료, 누락/실패 상태를 보존한다.
complianceClaim=false는 고정 계약이다. control evidence가evidence_ready여도 인증서·감사 의견·법률상 준수 판정은 아니다.- 입력/출력 JSON Schema와 운영 절차는 17-security-assurance.md에 정의한다.
5. jam diff 출력
jam diff OLD.csv NEW.csv는 두 jam-detail.csv를 finding_id 기준으로 비교한다. 각 CSV와 같은 디렉터리의 jam-run.json을 먼저 읽어 두 측정이 모두 complete이고 comparisonKey가 같을 때만 기본 비교를 허용한다.
jam diff: added 2 · fixed 1 · unchanged 31 · old 32 · new 33 · debt +40 min
Added findings:
- [SEC-01 critical] config/db.go:17 — 고엔트로피 문자열이 'password' 변수에 할당됨 (debt 120)
Fixed findings:
- [CPLX-01 violation] api/handler.go:142 — 함수 'processOrder'의 순환 복잡도 23 (임계값 15) (debt 30)
added: 새 CSV에만 있는 finding.fixed: 이전 CSV에만 있는 finding.unchanged: 양쪽 CSV에 모두 있는 finding.- debt는
debt_counted합계의new - old증감이다. --fail-on-added는 added finding 중--min-severity이상이 있으면 exit 1을 반환한다.- 억제되거나
score_exclusion이 있는 added finding은 원 심각도를 보존하지만--fail-on-added게이트에서 제외된다. - 구버전 baseline 등을 수동 조사할 때만
--allow-incompatible로 검증을 우회한다. 이 경우 호환성 경고를 stderr에 남긴다.
6. 종료 코드
| code | 의미 |
|---|---|
| 0 | 스캔 성공 (게이트 통과 또는 게이트 미설정) |
| 1 | 품질 게이트 또는 assurance 보안/통제 게이트 실패 |
| 2 | 명령 자체 실패 (스캔 대상 없음, 권한 오류, CSV 파싱 오류 등) |
| 3 | scan --strict 불완전 측정 또는 assurance 불완전 판정/증거. 리포트는 생성 |
| 4 | full 모드 외부 컴포넌트 측정 불가 |
scan --strict와 scan --fail-under가 동시에 실패하면 측정 불완전성 신호를 우선해 exit 3을 반환한다. 리포트/CSV/런 매니페스트/JSON/SARIF는 가능한 범위에서 모두 작성한 뒤 종료한다.