Jason

2026-07-10

AI에게 프로젝트 70개를 통째로 읽히지 않았다 — 스크립트와 6개 에이전트로 포트폴리오 채우기

포트폴리오 카드 70장에 필요한 제목·요약·하이라이트·태그·스크린샷. AI 크롤링 대신 스크립트가 사실을 수집하고, 에이전트는 이해가 필요한 부분만 맡긴 파이프라인 기록.

빌드로그자동화AI에이전트

지난 글에서 맥북의 75GB짜리 ~/Developer.project.toml 70개로 인덱싱한 얘기를 했다. 그 글은 “이 포트폴리오 사이트가 그 파일들로 빌드된다”로 끝났는데, 사실 인덱스와 포트폴리오 사이에는 꽤 큰 간극이 있다. 인덱스는 “이 프로젝트가 존재하고, 이런 스택이고, 언제 마지막으로 커밋됐다”까지만 안다. 그런데 포트폴리오 카드에는 사람이 읽을 제목, 요약, 하이라이트, 태그, 그리고 스크린샷이 필요하다. 그것도 공개 프로젝트 70개 전부.

이번 글은 그 간극을 어떻게 채웠는지에 대한 기록이다. 결론부터: 기계가 판단할 수 있는 것은 스크립트가 수집하고, 문맥 이해가 필요한 설명만 AI 에이전트에게 맡겼다. 그리고 중간에 한 번 제대로 데였다. 그 얘기도 뒤에 나온다.

왜 AI에게 전부 읽히면 안 되는가

가장 쉬운 방법은 뻔하다. Claude Code를 열고 “내 프로젝트 폴더 읽고 포트폴리오 채워줘”라고 하는 것. 나도 처음엔 그 생각을 했고, 계산을 해보고 접었다.

  • 75GB. 코드를 다 읽히는 건 애초에 불가능하고, “적당히 골라 읽어”라고 하면 무엇을 골랐는지 내가 통제할 수 없다.
  • 토큰 비용. 프로젝트당 코드 정독을 시키면 70번 반복이다. 게다가 나중에 프로젝트 하나가 바뀌면? 또 읽어야 한다.
  • 속도. 코드 읽고 이해하는 데 프로젝트당 몇 분씩 쓰면 순수 대기 시간만 몇 시간이다.
  • 문맥 혼선. 이게 제일 무섭다. 비슷한 프로젝트 70개를 한 컨텍스트에서 처리하면 A 프로젝트 설명에 B 프로젝트 기능이 스며든다. 내 폴더에는 Django 백엔드가 열 개쯤 있다. 섞이면 눈치채기도 어렵다.

그래서 지난 글과 같은 원칙으로 역할을 갈랐다. 다른 점은, 이번엔 “기계가 알 수 있는 것”의 범위를 훨씬 공격적으로 넓혔다는 것이다.

1단계: 스크립트가 사실을 수집한다

discover.mjs라는 스크립트를 만들었다. 하는 일은 단순하다. 공개 프로젝트마다 실행 중인 화면을 확보하고, 스크린샷을 찍고, 화면의 텍스트를 추출해서 저장한다.

discover.mjs ──▶ site-snapshots/*.json ──▶ 에이전트 6개 (병렬)
 (Playwright)      (화면 텍스트·메타)          │
     │                                        ▼
     └──▶ shots/*.png ──▶ .project.toml [portfolio] ──▶ portfolio.json ──▶ Astro 빌드
          (스크린샷)         (검증 스크립트가 기록)

실행 URL은 순서대로 시도한다. 수동으로 확정한 live_url이 있으면 그것, 없으면 배포 URL 추정값(단, HTTP GET으로 살아있는지 확인한 경우만 — 죽은 도메인의 파킹 페이지를 찍으면 안 되니까), 그것도 없으면 로컬에서 직접 부팅한다. package.json과 설정 파일로 Next.js·Astro·Vite·Django·FastAPI를 감지해서 프레임워크별 기본 커맨드로 8600번대 포트에 띄운다.

떠 있는 페이지에서는 Playwright가 일을 한다. 리다이렉트가 다 끝난 최종 URL을 메인으로 판정하고, 메인의 내비게이션 링크 → sitemap → 파일시스템 라우트 순서로 중요 서브페이지를 최대 4개 고른다. 로그인·어드민·약관 같은 경로는 제외 패턴으로 거른다. 그리고 페이지마다 제목, 메타 설명, h1, 본문 텍스트(메인 2,000자, 서브 800자)를 JSON으로 저장한다. 이 JSON이 다음 단계 에이전트의 입력이 된다.

중요한 설계 결정 하나: 실패는 정상 경로다. DB나 환경변수가 필요한 백엔드는 로컬 부팅이 당연히 실패한다. 실패한 프로젝트는 전체 실행을 멈추지 않고 사유와 함께 실패 목록에 격리된다. 전체 실행 결과가 이랬다.

결과 내용
성공 28 로컬 자동 부팅 21, 배포 URL 5, 수동 설정 2
실패 — 부팅 계획 없음 31 CLI 도구, 봇, 라이브러리 등 화면이 없는 프로젝트
실패 — 부팅 타임아웃 10 DB·env 필요한 백엔드
실패 — 빈 페이지 1 부팅은 됐지만 내용 없음

절반 이상이 실패인데, 이건 문제가 아니라 사실의 기록이다. 화면이 없는 프로젝트는 카드에 스크린샷이 없는 게 맞다.

2단계: 에이전트 6개로 나눈다

이제 이해가 필요한 부분. 각 프로젝트의 title(서비스명), summary_en, highlights 3개, tags를 만드는 일은 Claude Code 서브에이전트에게 맡겼다. 단, 통제 없이 풀어놓지 않았다.

작업 분할. 카테고리 묶음으로 나눠 에이전트 6개를 병렬로 돌렸다. 프로젝트당 딱 1회. 에이전트끼리는 서로의 작업을 모른다 — 문맥 혼선을 구조적으로 차단하는 가장 값싼 방법이다.

입력 제한. 에이전트가 읽을 수 있는 자료를 우선순위와 함께 못박았다.

  1. site-snapshots/<slug>.json — 실제 서비스 화면의 텍스트. 있으면 최우선. 서비스가 스스로를 설명하는 문구보다 정확한 소스는 없다.
  2. README 상위 100줄
  3. 라우트 디렉토리 목록 — ls 수준의 기능 힌트. 코드 정독 금지.
  4. 인덱스에 이미 있는 스택·기술 메타데이터

출력 고정. 결과는 자유 서술이 아니라 JSON 초안 파일이고, 적용은 별도 검증 스크립트가 한다. 이 스크립트는 70개 slug가 전부 있는지 대조하고, 필수 필드를 검사하고, 태그를 화이트리스트로 거른 다음에야 .project.toml[portfolio] 섹션만 치환한다. 섹션 밖은 어떤 경우에도 건드리지 않는다.

환각을 막은 규칙

생성형 AI로 70개 분량의 “사실”을 만들 때 제일 무서운 건 그럴듯한 거짓말이다. 규칙 네 개를 걸었다.

근거 우선순위. 실제 화면 텍스트 > README > 라우트 목록. README는 계획을 적어둔 문서라 실제와 다를 수 있지만, 배포된 화면의 문구는 거짓말을 안 한다.

추측 금지, 모자라면 줄여라. 하이라이트는 “무엇을/어떻게/결과” 구조로 3개를 요청하되, 근거가 없으면 개수를 줄이라고 했다. 실제 결과물은 70개 프로젝트에 하이라이트 201개, 평균 2.9개다. 저 0.1의 결손이 규칙이 작동했다는 증거다. 근거가 안 되는 프로젝트에서 에이전트가 실제로 3개를 채우지 않았다.

통제 태그. 태그는 자유 생성이 아니라 15개 어휘(SaaS, 랜딩, 커머스, 자동화, 크롤링, SEO, AI, 챗봇, 대시보드, B2B, 모바일, 게임, 도구, 콘텐츠, 금융) 안에서만 고르게 했다. 자유 태그를 허용하면 “웹앱”, “웹 애플리케이션”, “web-app”이 다 따로 생겨서 필터가 쓰레기가 된다.

기계 검증 후 기록. 위에서 말한 적용 스크립트가 마지막 관문이다. 화이트리스트에 없는 태그는 조용히 버려지고, 필수 필드가 비면 전체 적용이 중단된다.

그런데도 뚫렸다

여기까지 하고 “잘 됐네” 하고 넘어갔는데, 사이트를 보다가 이상한 걸 발견했다. 어떤 카드의 썸네일이 노란 바탕의 Django “Page not found (404)” 디버그 페이지였다. 확인해보니 성공 28건 중 6건이 가짜 성공이었다. API 전용 백엔드 4개는 /가 404인 게 정상이라 디버그 페이지가 찍혔고, 하나는 /login으로 리다이렉트된 로그인 폼이, 하나는 “Authentication required” 에러 화면이 썸네일이 됐다.

스크립트에는 “빈 페이지” 가드가 있었다 — 제목이 없고 본문이 200자 미만이면 버린다. 그런데 Django 디버그 404 페이지는 제목도 있고 본문도 아주 풍부하다(친절하게 URL 패턴 목록까지 보여준다). 가드를 정확히 통과했다.

수정은 두 줄짜리 교훈이다. HTTP 상태 코드를 확인하라(404 화면이 아무리 그럴듯해도 404다), 그리고 리다이렉트가 끝난 최종 경로가 로그인이면 메인이 아니다. 이 두 가드를 추가하고 가짜 6건을 지웠다. 실제 성공은 22건.

실제 결과

  • 공개 프로젝트 70개 전부 제목·영문 요약·하이라이트·태그 완성, 누락 0
  • 하이라이트 201개(평균 2.9), 태그는 통제 어휘 15종만 사용
  • 스크린샷은 검증 후 22개 프로젝트, 프로젝트당 최대 5장 + 상세 페이지용 풀스크롤 캡처
  • 캡처 실행은 순차(동시 부팅 금지)로 파일 타임스탬프 기준 30분 남짓. 그동안 나는 딴 일 했다
  • 토큰 비용은 정확히 측정하지 못했다. 다만 구조적으로 상한이 있다 — 에이전트 입력이 프로젝트당 화면 텍스트 몇천 자 + README 100줄로 캡되니, 75GB를 읽는 것과는 비교 자체가 안 된다
  • 사람이 직접 개입한 것: 로컬 전용 앱 하나의 부팅 커맨드, 도메인 하나의 live_url 확정, 그리고 가짜 썸네일 6건 정리. 70개 분량에서 이 정도면 만족한다

배운 점

AI는 크롤러가 아니다. 수집은 스크립트가 하는 게 싸고, 빠르고, 재현 가능하다. AI의 몫은 수집된 사실을 읽고 사람 언어로 정리하는 딱 그 지점이다.

코드보다 입력 계약이 중요하다. 에이전트 품질을 좌우한 건 프롬프트 문장력이 아니라 “무엇을 읽을 수 있고, 무엇을 읽으면 안 되고, 근거가 없으면 어떻게 하라”는 계약이었다. 하이라이트 평균이 3.0이 아니라 2.9라는 게 그 계약의 성적표다.

병렬화보다 검증 규칙이 먼저다. 에이전트 6개 병렬은 금방 만든다. 그런데 404 디버그 페이지를 썸네일로 걸러내는 가드가 없었으면, 나는 지금 방문자에게 노란 에러 화면을 대표 이미지로 보여주고 있었을 것이다. 자동화 파이프라인의 가치는 처리량이 아니라 가짜 성공을 얼마나 잡아내느냐에서 갈린다.

다음 단계는 3단계다. 70개 중 대표작 몇 개를 골라, 이번엔 진짜로 코드까지 읽혀서 케이스 스터디를 쓰게 하는 것. 그건 또 다른 글에서.