심각도(Severity)는 체크(Check)가 아니라 체크 종류(Kind)에 속한다: 512개의 잘못된 "로드되지 않음" 판정을 수정한 사례
요약
Claude Code 아티팩트를 스크래핑하여 품질을 측정하는 인덱스 운영 중 발생한 오탐 사례를 분석합니다. 데이터 구조 불일치와 잘못된 심각도 판정 로직으로 인해 발생한 512개의 오류 수정 과정을 다룹니다.
핵심 포인트
- 널 병합 연산자(??) 사용 시 데이터 형태 불일치로 인한 잘못된 값 할당 주의
- 옵셔널 체이닝이 예외를 발생시키지 않아 잘못된 데이터가 유효한 값으로 처리될 위험성
- 체크 항목의 종류(kind)에 따라 심각도(severity)를 다르게 정의해야 함
- 잘못된 오탐(False Positive)은 사용자 리포지토리에 대한 공개적 비난이 될 수 있음
저는 공개된 Claude Code 아티팩트(artifacts) — 스킬(skills), 서브에이전트(subagents), 플러그인(plugins), 마켓플레이스(marketplaces) — 를 스크래핑하여 4가지 측정 구성 요소로 점수를 매기고, h1 태그가 말 그대로 **"이것들은 작동하지 않을 것입니다."**라고 적힌 페이지를 발행하는 인덱스를 운영하고 있습니다.
그 페이지가 곧 제품의 전부입니다. 또한 버그로 인해 타인에게 손실을 입히는 유일한 장소이기도 합니다. 검사기(checker)의 두 가지 오류 방향은 대칭적이지 않습니다. 결함을 놓치는 것은 공백(gap)이지만, 오탐(false positive)은 타인의 리포지토리(repo)에 대한 공개적인 비난입니다. 지난 주말, 저는 동일한 스코어러(scorer)에서 세 가지 오류를 발견했는데, 그것들은 모두 다른 옷을 입고 있을 뿐 동일한 실수였습니다.
1. ?? 0이 형태 불일치(shape mismatch)를 판정(verdict)으로 바꾼 사례
마켓플레이스 체크(marketplace check)는 단 한 줄입니다: 플러그인이 나열되지 않은 마켓플레이스 매니페스트(manifest)는 아무것도 설치할 수 없습니다.
const n = a.extra?.plugin_count ?? 0;
extra는 스코어러가 실행된 후, 데이터베이스로 들어가는 과정에서 조립되는 형태(shape)입니다. 스크래퍼가 스코어러에게 전달하는 아티팩트(artifact)는 최상위 레벨에 plugin_count를 가지고 있습니다. 따라서 읽기 작업은 undefined였고, ?? 0은 이를 확신에 찬 0으로 만들었습니다. 그 결과 인덱스의 모든 마켓플레이스 — 총 55개 모두 — 가 "빈 마켓플레이스, 로드되지 않음"이라는 치명적인 판정을 발행했습니다. 276개의 플러그인을 나열하고 있는 anthropics/claude-plugins-official을 포함해서 말이죠.
옵셔널 체이닝(Optional chaining)과 널 병합 연산자(nullish default)의 조합은 누락된 필드가 어떻게 측정값으로 변하는지를 보여줍니다. 속성 접근(property access)이 실패하지 않으므로 아무것도 예외(throw)를 발생시키지 않으며, 잘못된 답이 그럴듯한 답이 되어버립니다.
2. 동일한 체크라도 종류(kind)에 따라 심각도(severity)는 달라야 한다
더 큰 문제입니다. 스코어러는 프론트매터(frontmatter)가 있는 모든 항목에 대해 no-name과 no-description을 치명적(fatal)인 것으로 표시했습니다. Anthropic의 자체 참조 문서(skills and sub-agents, 2026-08-02)를 읽어보면 다음과 같습니다:
name | description | |
|---|---|---|
| subagent | 필수(Required): 예 | 필수(Required): 예 |
| skill | 필수(Required): 아니오 — "디렉토리 이름으로 기본 설정됨" | 권장(Recommended) — "생략 시 마크다운 콘텐츠의 첫 번째 단락 사용" |
두 필드(field)가 모두 없는 스킬도 여전히 등록됩니다. 해당 스킬은 디렉토리로부터 명령(command)을 가져오고, 첫 번째 단락이 무엇이든 그 내용을 라우팅 텍스트(routing text)로 가져옵니다. 이는 작성된 설명이 없는 것보다 더 나쁜 상황이며, 로딩 실패(loading failure)가 아닙니다.
저는 서브에이전트(subagent) 규칙을 두 종류(kinds) 모두에 일반화하여 적용했기 때문에, 494개의 스킬, 6개의 플러그인(plugin), 2개의 마켓플레이스(marketplace), 그리고 10개의 서브에이전트가 "작동하지 않음" 범주에 분류되어 있었습니다. 수정 사항은 치명적 오류(fatality)가 플래그(flag)의 속성(property)이 아니게 된 것입니다:
const FATAL_ALWAYS = new Set(['broken-frontmatter', 'broken-manifest', 'empty-marketplace']);
/** 서브에이전트에는 필수적인 프론트매터(frontmatter)이지만, 스킬에서는 선택 사항으로 문서화됨. */
...
플래그 자체는 유지됩니다. 설명이 없는 스킬은 여전히 라우팅하기가 진정으로 더 어렵고, 여전히 감점(30점 대신 8점)을 받습니다. 바뀐 점은 감점이 판결(verdict)이 아니게 되었다는 것입니다.
archived 역시 같은 과정에서 치명적 목록(fatal list)에서 제외되었습니다. 아카이브된 GitHub 리포지토리(repo)는 읽기 전용(read-only)일 뿐, 로드할 수 없는(unloadable) 것이 아닙니다. 파일은 존재하며 스킬은 여전히 설치됩니다. 또한 이는 이미 유지 관리됨(Maintained) 컴포넌트에 가장 큰 영향을 미치는 입력값이었기에, 중복으로 비용이 청구되고 있었습니다.
3. 읽는 대신 기억하게 되는 임계값(Thresholds)
long-description은 1,024자를 초과할 때 발생했으며, 사유 문자열은 "실질적인 상한선은 ~1,024자입니다"였습니다. 하지만 그 숫자를 어디에도 기록해 둔 사람은 없었습니다. 문서화된 제한은 description과 when_to_use를 합쳐 총 1,536자이며, 컨텍스트(context) 사용량을 줄이기 위해 리스팅 텍스트에 적용됩니다. 기억에 의존한 숫자가 문서에서 명시적으로 허용하는 설명을 처벌하고 있었던 것입니다.
이제 그 내용은 FACTS.json 레지스터에 포함되었습니다. 제품이 외부 세계에 대해 내리는 모든 주장과 함께, 기본 소스 URL, 읽은 날짜, 그리고 어떤 파일이 해당 주장에 의존하는지를 기록합니다. 내부 산술(Internal arithmetic) — 점수 가중치(score weights), 등급 경계(grade boundaries) — 등은 의도적으로 제외됩니다. 그것들은 방법론(method)이지, 세계에 대한 주장(claims about the world)이 아니기 때문입니다.
세 가지 모두를 잡아낼 수 있었던 체크
이들 모두는 동일한 징후를 가지고 있었으며, 이를 찾아내는 비용은 매우 저렴합니다. 즉, 특정 카테고리의 100%가 검사기(checker)를 통과하지 못한다면, 문제가 있는 것은 검사기 자체입니다. 55개 중 55개의 마켓플레이스가 비어 있었습니다. 사실상 지구상의 모든 기술(skill)에서 필수 필드가 누락된 상태였습니다. 이는 결코 코퍼스(corpus)에 대한 사실일 수 없습니다.
타인의 작업물을 평가하는 모든 대상에 적용할 수 있는 일반화된 버전은 다음과 같습니다. 기준(bar)을 리스트 옆의 코드 내에 문장으로 인코딩하고(제 경우에는 "Claude Code는 아티팩트(artifact)를 등록할 수 없다 — 이보다 더 완화된 기준은 없다"입니다), 심각도(severity)를 체크(check)가 아닌 쌍(pair)에 키(key)를 지정하며, 의심스러울 정도로 딱 떨어지는 실패율은 스스로에 대한 버그 리포트(bug report)로 취급하십시오.
이것이 우리가 SkillWorks를 구축한 방식입니다. SkillWorks는 게시된 모든 Claude Code 기술(skill), 서브에이전트(subagent) 및 플러그인(plugin)에 대한 점수가 매겨진 인덱스(index)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기