AgriciDaniel/claude-seo) 딥다이브/seo audit example.com 한 줄이면 15개 전문 에이전트가 동시에 사이트를 뜯어보고, 0–100 점수와 우선순위 액션 플랜을 내놓는다. 다만 이 저장소가 별 15,000개를 받은 이유는 "SEO를 자동화해서"가 아니다 — 모든 권고안에 "이게 틀렸다면 어떻게 알 수 있는가"라는 반증 조건을 강제로 붙였다는 점이 이 프로젝트를 다른 AI SEO 도구와 갈라놓는다.
/seo 명령 32개 · MCP 확장 8개 · 스킬 마크다운 132개 17,586줄 + 파이썬 53개 20,941줄 · 테스트 410개)
SEO 감사는 원래 기술·콘텐츠·구조화 데이터·속도·로컬·해외·이커머스를 각각 다른 사람이 며칠씩 들여다보는 일이다. 이 플러그인은 그 각 영역을 독립된 서브에이전트 한 명으로 만들어 동시에 풀어놓고, 결과를 하나의 리포트로 합친다. 사람 팀의 병목이 "동시에 몇 명을 붙일 수 있나"였다면, 여기선 그게 사라진다.
구조는 지시(마크다운) + 실행(파이썬) 하이브리드다 — 판단은 LLM이, 측정은 결정적 스크립트가 맡는다.
| 구성 요소 | 수 | 정체 |
|---|---|---|
| 서브스킬 | 25개 | 마크다운 SKILL.md. 오케스트레이터 1 + 핵심 21 + 프레임워크 1 + 확장 미러 2 |
| 서브에이전트 | 18개 | 독립 컨텍스트를 가진 작업자. agents/seo-*.md |
/seo 명령 | 32개 | 사용자가 직접 부르는 진입점 |
| 파이썬 스크립트 | 53개 | 실제 측정·API 호출·렌더링. 20,941줄 |
| MCP 확장 | 8개 | Firecrawl · DataForSEO · Ahrefs · SE Ranking · Profound · Bing WMT · Unlighthouse · Banana(이미지 생성) |
| 테스트 | 410개 | 보안·이식성·매니페스트 정합성 위주 35개 파일 |
대부분의 AI SEO 출력은 "제목 태그를 개선하세요" 같은 일반론으로 끝난다. 이 플러그인은 권고안 하나에 네 개의 필드를 반드시 채우게 만든다.
| 필드 | 답해야 하는 질문 |
|---|---|
| 1차원리 관찰 | 이 권고가 기대고 있는 관찰된 사실은 무엇인가 |
| 의존 관계 | 다른 권고 중 무엇이 먼저 끝나야 이게 의미가 있나 |
| 반증 체크 | 이게 틀렸다면 우리는 어떻게 알게 되는가 |
| 선행 지표 | 효과가 나오기 전에 먼저 움직일 숫자는 무엇인가 |
LLM은 그럴듯한 일반론을 무한히 생성할 수 있다. SEO는 특히 그 함정이 깊은 분야다 — "콘텐츠를 개선하라"는 항상 맞는 말이고, 그래서 아무 정보도 없다. 반증 조건을 요구하면 확인할 수 없는 권고는 애초에 쓸 수 없게 된다. 도메인이 SEO가 아니어도 그대로 가져다 쓸 수 있는 프롬프트 설계 원칙이다.
SEO 업계의 통념 세 가지를 1차 출처 근거를 들어 직접 부정한다. AI SEO 도구가 보통 이걸 팔아먹는다는 점을 생각하면 눈에 띄는 선택이다.
| 업계 통념 | 이 저장소의 입장 |
|---|---|
"llms.txt를 두면 AI가 인용해 준다" | 현재 인용 레버가 아니다. skills/seo-geo/references/llmstxt-evidence.md에 1차 출처 근거를 모아 둠 |
| "AI용으로 콘텐츠를 청킹해야 한다" | 필요 없다 |
| "AI 검색용 키워드로 다시 써야 한다" | 불필요. 동의어 이해로 충분 |
| "GEO/AEO는 SEO와 다른 새 분야다" | 구글 AI 최적화 가이드 인용 — "AEO·GEO는 SEO의 리브랜딩 라벨". AI Overviews도 같은 랭킹 시스템에 기반 |
SEO 지식의 절반은 유통기한이 있다. 이 저장소는 폐기된 기능을 날짜로 못 박아 관리한다 — LLM이 훈련 데이터에 남은 옛 조언을 되풀이하는 걸 막는 장치다.
| 항목 | 상태 | 날짜 |
|---|---|---|
| FID → INP 교체 | INP가 코어 웹 바이탈 지표 | 2024-03-12 (FID는 2024-09-09에 CrUX·PSI에서 제거) |
| FAQPage 리치 결과 | 전 사이트 중단 — 구글 이득 없음 | 2026-05-07 |
| HowTo 리치 결과 | 제거 | 2023-09 |
| SpecialAnnouncement | 제거 | 2025-07 |
| ClaimReview · VehicleListing · EstimatedSalary 등 | 2025-06 일괄 폐기 | 대체 안내 문서 별도 제공 |
| 검색품질평가 가이드라인 | YMYL에 정치·사회 주제 확장 | 2025-09 개정 반영 |
README 상단의 서치 콘솔 성장 그래프는 사이트 한 곳의 3개월 기록이다. 신생 도메인은 원래 그 구간에서 가파르게 오르므로, 도구의 효과를 분리해 낸 데이터가 아니다. 또한 저자는 유료 커뮤니티(Skool)를 운영하고 공개판 / 커뮤니티 전용 프라이빗 미러 두 갈래를 두고 있다 — 공개판이 MIT로 완전히 열려 있긴 하지만, 신기능이 프라이빗에 먼저 간다는 뜻이기도 하다. 도구의 구조는 배울 게 많고, 성과 주장은 별도로 검증할 일이다.
| 계층 | 구성 | 역할 |
|---|---|---|
| 지시(Directive) | YAML frontmatter가 붙은 마크다운 132개 | "언제 이 스킬이 발동하나"를 description에 쓴다. Claude가 이걸 읽고 스스로 고른다 |
| 오케스트레이션 | skills/seo/SKILL.md | 업종 감지 → 병렬 디스패치 → 집계 → 종합. 15단계 절차가 마크다운 산문으로 적혀 있다 |
| 실행(Execution) | 파이썬 53개 · 20,941줄 | 실제 fetch·렌더·측정·API 호출. LLM이 추측하면 안 되는 부분 전부 |
| 참조 데이터 | references/*.md (오케스트레이터만 13개) | 스키마 타입 목록·CWV 임계값·E-E-A-T 프레임워크. 필요할 때만 로드해 컨텍스트를 아낀다 |
| 렌더링 | Playwright Chromium | SPA 감지 시 자동 전환(--render auto). 스크린샷도 여기서 |
| 런타임 | bin/claude-seo + scripts/runtime.py (표준 라이브러리만) | 격리 venv 생성·관리. 전역 pip 설치를 하지 않는다 |
| 외부 데이터(선택) | Google API(GSC·PSI·CrUX·GA4·Indexing) · Moz · Bing WMT · Common Crawl | 무료 티어에서도 쓸 수 있는 축 |
| MCP 확장(선택) | Ahrefs · DataForSEO · SE Ranking · Profound · Firecrawl · Unlighthouse · Bing · Banana | 각자 계정 필요. 없으면 그 축만 빠지고 나머지는 정상 동작 |
SKILL.md의 frontmatter에 name·description을 쓰면 Claude가 대화 맥락을 보고 알아서 발동한다. 이 저장소는 skills/seo-*/와 agents/seo-*.md 이름 규칙만으로 자동 발견되게 설계해, 새 스킬 추가 시 등록 코드를 고칠 필요가 없다.기본 7개는 항상 돌지만, 나머지 8개는 신호가 있을 때만 뜬다. 이커머스가 아닌 사이트에 이커머스 감사를 붙이면 토큰만 쓰고 잡음만 나오기 때문이다. 판정 근거도 추측이 아니라 스크립트 실행 결과다 — 예: 구글 에이전트는 claude-seo run google_auth.py --check가 자격증명을 실제로 찾았을 때만 뜬다.
병원 종합검진과 같다. 기본 검사 7종은 누구나 받고, 혈액 수치에 이상이 보일 때만 추가 정밀검사가 붙는다. 모두에게 모든 검사를 하면 비용도 시간도 결과지 두께도 감당이 안 되고, 정작 중요한 이상 소견이 묻힌다.
| LLM이 하는 일 | 파이썬 스크립트가 하는 일 |
|---|---|
| 업종 판정, 우선순위 매김, 맥락 해석 | 페이지 fetch·디코딩(fetch_page.py) |
| 발견 사항의 인과 연결 | SPA 렌더링(render_page.py), 스크린샷 |
| 액션 플랜 문장화 | CWV 측정(pagespeed_check.py·crux_history.py·lcp_subparts.py) |
| 반증 조건 도출 | GSC 질의·페이지네이션(gsc_query.py), GA4 리포트 |
| 업계 통념 반박 시 근거 인용 | 스키마 검증, 사이트맵 파싱, 백링크 조회, SQLite 기준선 비교 |
경계가 분명하다 — 숫자가 나와야 하는 것은 전부 스크립트다. "이 페이지 LCP가 몇 초쯤 될 것 같다"를 LLM이 말하는 순간 도구는 무의미해지기 때문. 반대로 스크립트가 판단을 하려 들면 규칙이 폭발한다.
| 단계 | 원칙 | 하는 일 |
|---|---|---|
| PERCEIVE | OBSERVE(외부) · OBSERVE(내부) · LISTEN | 원신호 수집 + 자기 가정 점검 + SERP·브랜드 보이스·커뮤니티가 실제로 하는 말 읽기 |
| ANALYZE | THINK · CONNECT(측면) · CONNECT(시스템) | 1차원리로 환원 → 스킬 간 비자명한 연결 찾기 → 의존성 그래프로 순서화 |
| VALIDATE | FEEL · ACCEPT | UX·브랜드 보이스·실행 주체의 실제 역량에 대고 압박 테스트 → 반증 가능성 노출 |
| ACT | CREATE · GROW | 산출물 배포 + 다음 감사를 위한 피드백 루프 설치 |
주목할 두 가지: OBSERVE(내부)는 자기 가정을 감사하는 단계로, 에이전트가 스스로의 전제를 점검하게 만든다. ACCEPT는 "운영자가 실제로 이걸 실행할 수 있나"를 묻는다 — 1인 블로거에게 12명짜리 콘텐츠 팀 계획을 주는 것이 AI SEO 리포트의 전형적 실패인데, 그걸 명시적 단계로 잡아 둔 것이다.
scripts/url_safety.py는 "이 저장소에서 사용자 URL을 받는 모든 스크립트는 네트워크 요청 전에 여기를 통과해야 한다"고 못 박은 단일 관문이다.
| 함수 | 막는 것 |
|---|---|
validate_url() | http(s) 아닌 스킴, 호스트명 없음, localhost·클라우드 메타데이터 엔드포인트, 사설/루프백/예약 IP 리터럴. DNS 해석은 안 함 |
validate_url_strict() | getaddrinfo로 해석한 모든 A 레코드를 검사하고 IP를 고정(pin). 검사와 접속 사이에 공인 IP가 사설 IP로 바뀌는 DNS 리바인딩 차단 |
safe_requests_get() | 요청 동안 OS 리졸버가 사전 검증된 IP만 보게 하는 컨텍스트 매니저. Host 헤더의 원래 호스트명은 보존 |
사용자가 준 주소를 서버가 대신 열어 주는 기능(미리보기·웹훅·이미지 프록시·크롤러)은 전부 같은 위험을 안는다. http://169.254.169.254/ 같은 클라우드 메타데이터 주소를 넣으면 서버가 자기 자격증명을 대신 읽어다 준다. 게다가 "검사할 땐 공인 IP였다가 접속할 땐 사설 IP"로 바꾸는 DNS 리바인딩까지 있어서, 단순 문자열 검사만으로는 부족하다. 이 파일은 그 방어를 100줄 남짓으로 보여 주는 실물 교재다 — SEO에 관심이 없어도 읽을 값어치가 있는 부분.
| 규칙 | 구현 |
|---|---|
| 전역 오염 금지 | Claude 플러그인 데이터 영역에 전용 venv 생성. 전역 패키지·PATH shim 안 만듦 |
| 암묵적 설치 금지 | /seo setup을 사용자가 명시적으로 부를 때만. 스킬 문서가 "pip install을 즉흥으로 하지 말라"고 지시 |
| 진단과 변경 분리 | /seo doctor는 상태만 보고 시스템을 안 바꾼다. 출력에서 절대경로·환경값을 의도적으로 생략 |
| 실행 대상 제한 | ALLOWED_CORE_SCRIPTS 허용목록 + 파일명 정규식 검증. 임의 파이썬 실행 경로가 되지 않게 |
| 설치 방식 | README가 irm | iex 원라이너 대신 git clone 후 스크립트 확인을 권한다 — 공급망 위험을 스스로 인정 |
| 순서 | 파일 | 얻는 것 |
|---|---|---|
| 1 | skills/seo/SKILL.md "Orchestration Logic" | 15단계 절차 전체. 이 문서 4장의 다이어그램이 여기서 나왔다 |
| 2 | skills/seo/references/thinking-framework.md | 10원칙 정본 — 도메인 무관하게 재사용 가능한 부분 |
| 3 | 아무 agents/seo-*.md 하나 | 에이전트 정의가 얼마나 짧은지 확인. frontmatter + 지시문이 전부 |
| 4 | scripts/url_safety.py | SSRF/DNS 리바인딩 방어 실물 |
| 5 | scripts/runtime.py | 전역 오염 없는 플러그인 런타임 설계 |
| 6 | tests/test_manifest_consistency.py | "스킬 25개·에이전트 18개"가 실제로 맞는지 테스트가 검증한다 |
파일명만 봐도 드러난다 — API 키 유출 검사, 확장 설치기 인젝션, 크로스플랫폼 훅, 매니페스트 정합성, 이식성. 이 프로젝트의 "코드"는 대부분 프롬프트라 단위 테스트를 걸 대상이 적고, 대신 깨지면 사용자 시스템에 피해가 가는 지점에 테스트를 몰았다. 프롬프트 기반 도구를 배포할 때 무엇을 테스트해야 하는지의 답 하나.
| 배울 것 | 어디서 | 왜 일반적으로 유용한가 |
|---|---|---|
| 반증 조건 강제 | thinking-framework.md | LLM 출력에 "틀렸다면 어떻게 아나"를 요구하면 확인 불가능한 일반론이 제거된다. 어떤 도메인에서든 통하는 프롬프트 설계 |
| 판단/측정 분리 | 스킬(md) 대 스크립트(py) | LLM에 숫자를 추측시키지 않는 경계선. 에이전트 도구를 만들 때의 1번 원칙 |
| 조건부 팬아웃 | skills/seo/SKILL.md 3~11단계 | 모두에게 모든 검사를 돌리지 않는다. 스폰 조건 자체를 스크립트 실행 결과로 두는 게 핵심 |
| 온디맨드 참조 로딩 | references/*.md | 스킬 본문을 얇게 유지하고 무거운 데이터는 필요할 때만. 컨텍스트 예산 관리의 표준 패턴 |
| 이름 규칙 기반 자동 발견 | skills/seo-*/, agents/seo-*.md | 새 기능 추가에 등록 코드 수정이 필요 없다. 플러그인 아키텍처의 기본기 |
| SSRF·DNS 리바인딩 방어 | scripts/url_safety.py | 사용자 URL을 받는 모든 서비스의 필수 방어. 100줄 남짓 실물 예제 |
| 격리 런타임 | scripts/runtime.py | 표준 라이브러리만으로 venv 관리 + 허용목록 실행. 남의 환경을 안 건드리는 CLI 배포 |
| 지식 만료 관리 | 폐기 타입 참조 문서 | 날짜와 함께 "이건 이제 아니다"를 명시해 LLM이 옛 훈련 지식을 되풀이하는 걸 막는다 |
| 항목 | 필요도 | 비고 |
|---|---|---|
| Claude Code CLI | 필수 | 플러그인 설치는 1.0.33+ |
| Python 3.10+ | 필수 | /seo setup이 격리 venv를 만든다 |
| Playwright Chromium | 선택 | SPA 렌더링·스크린샷 전용. 없으면 정적 HTML 감사는 그대로 되고 그 축만 빠짐 |
| Google API 자격증명 | 선택 | 없으면 CWV는 랩(lab) 추정치만, 색인 상태는 페이지 신호로 추론. 있으면 CrUX 실사용자 데이터 + GSC 실제 색인 상태 |
| MCP 확장 계정 | 선택 | Ahrefs·DataForSEO·SE Ranking·Profound 등. 경쟁사·AI 인용 데이터가 여기서 온다 |
| GPU | 불필요 | 무거운 계산이 없다. 비용은 토큰과 외부 API 쿼터 |
① 헤드리스 렌더러가 대부분의 SPA는 처리하지만, 스크롤 위치에 묶인 하이드레이션·사용자 조작(모달·탭 클릭) 후에야 오는 핵심 콘텐츠·서드파티 위젯의 경쟁 상태에서는 잡음 섞인 결과가 나온다. 대응은 seo-visual 에이전트를 수동으로 띄워 Playwright 스냅샷과 원시 HTML 결과를 대조하는 것.
② 무료 티어는 기본적으로 서드파티 API를 안 부른다(대상 URL fetch는 함). 즉 코어 웹 바이탈이 실사용자 데이터가 아니라 랩 추정치이고, 색인 여부도 추론이다. 이 차이를 모르고 리포트를 클라이언트에 그대로 넘기면 곤란해진다 — 실사용자 수치가 필요하면 Google API 연결이 사실상 필수다.
skills/seo-geo/SKILL.md를 열어 본다. 프로그래밍 언어가 한 줄도 없는데 동작이 완전히 규정돼 있다는 점을 확인하는 게 목적. 그다음 agents/seo-geo.md와 비교해 스킬(무엇을 하나)과 에이전트(누가 하나)가 어떻게 갈리는지 본다.
/seo page 돌리고 "반증 조건"만 따로 뽑기결과 리포트에서 권고안 본문은 무시하고 반증 체크만 모아 읽는다. 확인 가능한 게 몇 개고 사실상 확인 불가능한 게 몇 개인지 세어 보면, 이 프레임워크가 실제로 작동하는지 스스로 검증하게 된다.
(같은 사이트를 그냥 "SEO 봐 줘"로 물었을 때와 비교하면 차이가 더 뚜렷하다.)
scripts/url_safety.py를 임포트해 http://localhost:8080, http://169.254.169.254/, http://127.0.0.1, 그리고 사설 IP로 해석되는 자기 소유 도메인을 각각 넣어 본다. 앞의 셋은 validate_url()에서 막히지만 넷째는 문자열만 봐서는 못 막는다 — validate_url_strict()가 왜 필요한지 실물로 확인하는 과제.
이어서 자기 프로젝트에서 사용자 URL을 받는 지점을 찾아 같은 방어가 있는지 점검.
skills/seo-schema/를 골격으로 삼아 SEO가 아닌 내 분야의 스킬을 하나 만든다(코드 리뷰 체크리스트, 계약서 검토, 데이터 품질 감사 등). 반드시 지킬 것 둘:
① 참조 데이터는 references/로 분리해 본문을 얇게 유지 ② 모든 산출 항목에 반증 조건 필드를 강제. 두 번째가 이 저장소에서 가져갈 진짜 값어치다.
scripts/drift_baseline.py·drift_compare.py를 읽고, SQLite에 스냅샷을 쌓고 회귀를 탐지하는 구조를 자기 도메인에 옮긴다. 어려운 부분은 저장이 아니라 "무엇을 변화로 볼 것인가"다 — 매번 바뀌는 값(타임스탬프·광고 슬롯)을 걸러내지 않으면 전부 회귀로 뜬다.
얻는 것: 스냅샷 비교형 감시 도구의 일반 패턴. 설정 드리프트·API 스키마 변경·문서 변조 탐지에 그대로 쓰인다.
| 주차 | 주제 | 구체적으로 |
|---|---|---|
| 1주차 | Agent Skills 규약 | Claude Code 스킬 문서 → SKILL.md frontmatter의 description이 발동 조건인 이유 → 서브에이전트의 독립 컨텍스트가 왜 필요한가(컨텍스트 오염 방지) → 이 저장소의 자동 발견 규칙 따라 만들어 보기 |
| 2주차 | SEO 실물 지식 | 코어 웹 바이탈(LCP 2.5초·INP 200ms·CLS 0.1) → LCP 하위 구간 분해(TTFB·로드 지연·로드 시간·렌더 지연) → 구조화 데이터 JSON-LD → E-E-A-T와 Who/How/Why 휴리스틱. references/cwv-thresholds.md·schema-types.md가 교재 |
| 3주차 | AI 검색(GEO)의 실제 | 구글 AI 최적화 가이드 원문 → 인용 가능성: 134–167단어 자기완결 답변 블록·질문형 헤딩·귀속 밀도 → 엔티티 존재(위키·레딧·유튜브·링크드인) → llmstxt-evidence.md로 통념 반박의 근거 구성법 익히기 |
| 4주차 | 안전한 자동화 도구 만들기 | url_safety.py로 SSRF·DNS 리바인딩 → runtime.py로 격리 venv·허용목록 실행 → Playwright 헤드리스 렌더링과 하이드레이션 타이밍 함정 → Google API(GSC·PSI·CrUX·GA4) OAuth와 페이지네이션 |
SEO가 목적이면 2~3주차가 본체지만, 엔지니어에게 가장 오래 남는 건 1주차와 4주차다. 스킬·에이전트 구조와 안전한 도구 배포는 SEO가 아닌 어떤 분야에 옮겨도 그대로 쓰이는 반면, SEO 지식은 1~2년이면 절반이 갈린다 — 이 저장소가 폐기 날짜를 관리하는 이유가 바로 그것이다.
| 용어 | 어디에 | 뜻 |
|---|---|---|
| Agent Skill | skills/*/SKILL.md | Claude가 맥락을 보고 스스로 고르는 마크다운 지시서. frontmatter의 description이 발동 조건이 된다 |
| 서브에이전트 | agents/*.md | 독립 컨텍스트를 가진 작업자. 여러 명을 동시에 띄워도 서로의 맥락이 섞이지 않는다 |
| E-E-A-T | seo-content | 경험·전문성·권위·신뢰. 검색품질평가 가이드라인의 축이며 신뢰가 가장 무겁다 |
| YMYL | 같은 곳 | Your Money or Your Life — 돈·건강처럼 잘못되면 피해가 큰 주제. 2025-09부터 정치·사회까지 확장 |
| 코어 웹 바이탈 | seo-performance | LCP(최대 콘텐츠 표시, 2.5초) · INP(상호작용 응답, 200ms) · CLS(레이아웃 이동, 0.1) |
| INP | 같은 곳 | Interaction to Next Paint. 2024-03-12에 FID를 대체했고 FID는 2024-09-09에 도구에서 제거됨 |
| CrUX | crux_history.py | 크롬 실사용자 경험 리포트. 필드 데이터(실제 방문자) — Lighthouse의 랩 데이터(모의 측정)와 구분 |
| GEO / AEO | seo-geo | 생성형 엔진 최적화. 구글 공식 입장은 "SEO의 리브랜딩 라벨"이며 별개 랭킹 시스템이 아니다 |
| 인용 가능성(citability) | 같은 곳 | AI가 인용하기 좋은 형태인가. 핵심은 134–167단어의 자기완결 답변 블록 |
| JSON-LD | seo-schema | 구조화 데이터 표기 방식. 구글이 공식 선호한다고 밝힌 형식 |
| SXO | seo-sxo | 검색 경험 최적화. 순위가 아니라 도착 후 사용자가 실제로 목적을 이루는가 |
| 드리프트 모니터링 | seo-drift | 기준선(baseline)을 SQLite에 저장해 두고 시간이 지나며 생긴 회귀를 탐지 |
| SSRF | url_safety.py | 서버가 사용자 지정 주소를 대신 열게 만들어 내부망·클라우드 메타데이터에 접근하는 공격 |
| DNS 리바인딩 | 같은 곳 | 검사할 땐 공인 IP, 접속할 땐 사설 IP로 도중에 바꿔치기하는 우회. IP 고정(pinning)으로 막는다 |
| MCP | extensions/ | Model Context Protocol. 외부 서비스를 Claude의 도구로 붙이는 규약 |