Community라이팅 & 에디팅github.com

buildinternet/releases

에이전트를 위한 제품 릴리스 노트 레지스트리입니다. MCP 및 CLI를 통해 최근 제품 변경 로그를 가져오세요.

releases란 무엇인가요?

이 스킬은 로컬 에이전트(특히 Claude Code)가 원격 서버의 AI 기반 추출 비용을 지불하지 않고도 제품 변경 로그를 인제스트할 수 있도록 설계되었습니다. '에이전트가 추출기'라는 핵심 원칙에 따라, 에이전트가 직접 웹 페이지를 가져오고 파싱하여 구조화된 릴리스 레코드를 만든 후, AI 개입 없이 멱등성 업서트를 수행하는 배치 엔드포인트에 POST 합니다. 이는 로컬 전용 스킬로, 원격 관리 에이전트 세션이 아닌 로컬 Claude Code 환경에서만 사용되며, `RELEASES_API_*` 환경 변수를 필요로 합니다. 주로 원격 추출 비용을 절약하거나, 원격 가져오기가 실패한 경우(예: 0개의 릴리스 작성), 또는 AI 입력을 거부하는 게시자를 존중해야 할 때 사용됩니다. 워크플로우는 사전 점검(opt-out 확인), 대상 소스 확인, 페이지 매핑, 인라인 추출(또는 하위 에이전트 분산), 배치 업서트, 검증, 플레이북 기록 단계로 구성됩니다. 각 단계는 명확한 비용 계약과 안전 장치(예: `ai-input=no` 게시자 차단)를 포함하여, AI 에이전트가 게시자 권리를 존중하면서 효율적으로 대규모 변경 로그를 온보딩할 수 있도록 합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add buildinternet/releases

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

로컬 인제스트

원격 가져오기 경로를 사용하지 않고 변경 로그를 온보딩합니다. 원격 경로(releases admin source fetch → POST /v1/workflows/update → API 워커의 DeterministicUpdateWorkflow, #1946)는 본문→레코드 추출을 서버 측에서 실행하고(증분 Haiku 또는 도구 루프), 0개의 릴리스를 작성하더라도 해당 추론 비용을 청구합니다. 로컬 Claude Code 에이전트는 이미 웹 가져오기 + 파일 도구를 갖춘 유능한 모델이므로, 페이지별 추출을 인라인으로 수행하고(이미 진행 중인 세션에 통합) 결정론적 쓰기 경로만 필요로 합니다.

핵심 원칙: 에이전트가 추출기입니다. 가져오고, 파싱하고, 레코드를 구조화한 후 배치 엔드포인트에 POST합니다. 엔드포인트는 AI 없이 일반 멱등성 업서트를 수행합니다. 원격 추출 루프는 실행되지 않습니다.

로컬 Claude Code 전용입니다. 이 스킬은 하위 에이전트 분산을 위해 Agent 도구를 사용하며, 영구 로컬 파일 시스템과 CLI의 RELEASES_API_* 환경 변수를 가정합니다. seeding-playbooks와 함께 로컬 전용 운영자 스킬로 사용됩니다. 관리형 에이전트 팜에는 배포되지 않습니다.

비용 계약

대규모 실행 전에 운영자에게 이 내용을 명시적으로 알리고 준수해야 합니다:

  • 지출: 가져오기 + 추출을 위한 에이전트 자체 토큰, 그리고 분산된 모든 하위 에이전트(모델 선택 가능 — sonnet은 품질, haiku는 대량 작업).
  • 지출하지 않음: 서버 측 추출 없음(업데이트 워크플로우의 증분 Haiku / 도구 루프 패스), 원격 web_fetch 추출 없음. POST /v1/workflows/update는 절대 호출되지 않습니다.
  • 배치 엔드포인트는 삽입 시 AI를 실행하지 않습니다 — 요약, 마케팅 분류기, 피드 보강 없음. insert 후 fire-and-forget 벡터 임베드(waitUntil)를 수행합니다. AI 필드(title_generated / title_short / summary)는 의도적으로 비워 둡니다. 아래 수동 보강 섹션에서 의도적인 후속 경로를 참조하세요.

원격 실행이 발생하지 않았음을 확인하는 부정 신호: Axiom releases-cloudflare-logs에 POST .../fetch?sessionId=det-…가 없고 해당 소스에 대한 extract-deps-worker 이벤트가 없습니다.

사용 시기

  • Claude Code에서 새 소스를 대화형으로 온보딩하거나 기존 소스를 백필링할 때 원격 추출 비용을 건너뛰고 싶은 경우.
  • 원격 가져오기가 전체 추출 루프를 소모하고 0개의 릴리스를 작성한 경우(Conductor / 대형 본문 / max_tokens 실패 모드) — 인라인 추출은 루프를 완전히 우회합니다.
  • 페이지가 자체 웹 도구로 가져올 수 있는 경우(아래 거부는 정책 게이트이며 가져오기 제한이 아닙니다).

사용하지 말아야 할 경우:

  • 관리형 에이전트 세션에 있는 경우. 이것은 로컬 전용입니다(Agent-도구 분산 + 로컬 파일 시스템 + CLI 환경). 관리형 에이전트 세션은 일반 가져오기 경로를 계속 사용해야 합니다.
  • 소스에 깔끔하고 콘텐츠가 풍부한 피드가 있는 경우. 그냥 추가하고(managing-sources) 크론 파이프라인이 가져오도록 두세요. 로컬 인제스트는 인라인 추출을 수행하는 에이전트가 올바른 도구인 경우를 위한 것이지, 피드/GitHub 어댑터를 대체하기 위한 것이 아닙니다.
  • 게시자가 거부하는 경우(사전 점검 실패 — 1단계 참조).

워크플로우

1단계 — 사전 점검 (필수 안전 게이트)

무엇이든 가져오거나 쓰기 전에 옵트아웃 검사를 실행하세요:

bun .claude/skills/local-ingest/preflight.ts <url> --json

robots.txt를 가져오고(apex→www 리디렉션 따름), Cloudflare Content Signals 정책을 파싱한 후, 종료 코드로 게이트합니다:

종료판결조치
0proceed허용적이거나 없음 — 계속 진행합니다.
1refuseai-input=no 선언됨. 중단합니다. 사용자에게 알립니다. 가져오거나 쓰지 마세요.
2unknownrobots.txt를 가져오거나 파싱할 수 없음. 한 번 재시도; 그래도 알 수 없으면 사용자에게 알립니다 — 진행하는 것으로 가정하지 마세요.

게이트는 ai-input에만 적용됩니다. 로컬 인제스트는 검색/입력 인제스트 — 검색 인덱스에 입력 — 이며, 이는 정확히 ai-input이 규제하는 것입니다. ai-train=no는 모델 학습을 거부하며, 이 경로는 이를 수행하지 않으므로 ai-train=no만 있는 경우(예: vercel.com) 올바르게 진행됩니다.

이것이 Conductor 교훈입니다. conductor.build는 Content-Signal: ai-train=no, search=yes, ai-input=no를 제공합니다 — 해당 조직/소스는 정확히 이 이유로 레지스트리에서 이미 일시 중지되었습니다(releases admin playbook conductor). 로컬 온보드는 게시자 옵트아웃에 반하여 자동으로 토큰을 소비하거나 콘텐츠를 인제스트해서는 안 됩니다. conductor.build는 거부되어야 합니다(해당 ai-input=no를 통해) — 이 게이트의 회귀 대상입니다.

거부는 정책 선택이며 기술적 한계가 아닙니다: web_fetch와 CF Browser Rendering은 여전히 이러한 페이지를 검색할 수 있습니다(Cloudflare의 /crawl 엔드포인트만 Conductor에 대해 신호를 강제로 적용했습니다). 어쨌든 옵트아웃을 존중하세요. 운영자가 문서화한 명시적인 게시자 허가가 있는 경우에만 재정의하세요 — 자동 우회는 없습니다.

사전 점검은 robots.txt에서 Sitemap: URL을 출력합니다 — 3단계를 위해 보관하세요.

2단계 — 대상 확인

쓰기 전에 조직과 소스가 존재하는지 확인하세요 — 릴리스는 source_id에 연결됩니다. 생성/이름 지정/기본 규칙은 managing-sources 를 참조하고, 구조화된 피드가 존재하는지 여부는 finding-changelogs 를 참조하세요(존재하면 일반 파이프라인을 선호합니다). 그런 다음:

  • 파싱 힌트를 위해 조직 플레이북을 읽으세요: releases admin playbook <org> (managing-sources → 플레이북 참조).
  • 생성/확인된 행에서 sourceSlug 와 src_… 아이디를 캡처하세요 — 배치 URL에 둘 중 하나가 필요합니다.

소스가 완전히 새로운 경우 생성하세요(예: releases admin source create "<name>" --url <url> --org <org> [--primary]) 그러나 가져오기를 트리거하지 마세요 — 그러면 피하려는 원격 업데이트 워크플로우가 실행됩니다.

3단계 — 페이지 매핑

페이지 모양 분류 (releases admin discovery evaluate <url> --json은 pageStructure: single-page | index | unknown 보고):

  • 단일 페이지 변경 로그 → 4단계에서 한 번 가져오기.
  • 인덱스 → 상세 → 릴리스별 URL을 열거합니다. /sitemap.xml(사전 점검에서 찾은)을 변경 로그 경로로 필터링하여 선호하고, 그렇지 않으면 인덱스 HTML을 파싱하여 릴리스별 링크를 찾습니다.
  • 클라이언트 렌더링 인덱스 → 인덱스는 JS 셸로, 카드가 클라이언트 측 가져오기를 통해 로드되므로 curl/비JS 스크랩은 0개의 항목을 반환합니다. 렌더링을 통해 열거: Chrome에서 Claude로 인덱스를 구동하거나(또는 렌더링 하위 에이전트 분산), 목록이 윈도우 처리된 경우 페이지를 매기고 릴리스별 링크를 수집합니다. 그런 다음 상세 페이지가 정적으로 렌더링되는지 확인하세요 — 인덱스가 그렇지 않더라도 종종 그렇습니다. 정적인 경우 해당 URL을 직접 벌크 curl하세요(HTML→마크다운 로컬, 렌더링 없음, AI 비용 없음). Harvey의 help.harvey.ai/release-notes가 정확히 이것입니다: 클라이언트 렌더링 인덱스, 정적 상세 페이지. 인덱스의 비JS 스크랩이 비어 있다고 "릴리스 없음"으로 결론 내리지 마세요.

최근 윈도우(기본 ~25–50개의 가장 최근 릴리스)로 제한하고 건너뛴 것을 기록하세요 — 자동으로 잘라내지 마세요. 재실행은 멱등성이므로, 나중에 더 깊은 백필은 단순히 행을 추가합니다.

4단계 — 가져오기 + 추출

parsing-changelogs 의 type/version/date/media 규칙을 재사용하세요(아래 일치성 참조).

  • 몇 페이지: 이 에이전트에서 인라인으로 가져오고 추출합니다.
  • 많은 페이지: 병렬 하위 에이전트로 분산 — 각각 하나의 URL 조각을 담당하며, 각각 배치 스키마의 레코드를 반환합니다. seeding-playbooks (그리고 분산 규율에 대해서는 superpowers:dispatching-parallel-agents)의 Agent-도구 디스패치 패턴을 사용하세요. 부모 에이전트에서 릴리스 URL을 가져오지 마세요 — 부모의 컨텍스트를 깨끗하게 유지하기 위해 위임하세요.
  • 부모-저장 주의 사항: 하위 에이전트는 배치 쓰기(권한 제한)가 차단될 수 있습니다. 계획하세요 — 각 하위 에이전트가 결과에 레코드를 반환하게 하고, 부모(당신)가 5단계에서 쓰기를 수행하게 하세요. seeding-playbooks가 노트 저장에 사용하는 것과 동일한 패턴입니다.
  • 미디어 추출 — 텍스트 전용으로 보내지 마세요. 각 릴리스 본문에서 이미지/비디오를 추출하여 media를 채우고, _next/image / _vercel/image 최적화 래퍼를 기본 CDN URL로 풀어내세요(packages/rendering/src/media-url.ts의 normalizeMediaUrl). 텍스트 전용 첫 번째 패스는 운영자 검토에서만 발견되는 일반적인 실수입니다(Harvey는 텍스트 전용으로 보낸 후 _next/image → cdn.sanity.io를 다시 추출하고 재시드해야 했습니다). 보강 재-POST는 mode: "upsert-content"입니다(5단계).

각 레코드는 배치 스키마(5단계 참조)와 일치성 규칙과 일치해야 합니다.

선택 사항 — 결정론적 추출 (접근법 A, 모노레포 전용): 수동으로 추출하는 대신 packages/adapters/src/extract/의 프로덕션 추출 라이브러리(extractFromBody, extractWithTools, runDirectFetchExtraction)를 호출할 수 있습니다. ExtractDeps.cloudflare는 null 가능하고 repo는 noop이 될 수 있으므로 Anthropic 키만 있으면 실행됩니다(증명: scripts/smoke-toolloop.ts). 이는 에이전트 토큰을 직접 Anthropic-API 토큰으로 교환하지만 프로덕션 도구 루프를 정확히 재현합니다. 이 패키지는 private: true입니다 — 모노레포에서만 접근 가능하며, 얇은 CLI에서는 절대 접근할 수 없습니다. 기본값은 접근법 B(에이전트가 추출기)로 유지됩니다.

5단계 — 쓰기 (배치 업서트)

레코드를 배치 엔드포인트에 청크로 POST합니다(~50/요청으로 요청을 제한; 핸들러는 서버 측에서 D1 바인딩 제한 청킹을 수행합니다):

# 조직 범위 경로 사용. 기본 /v1/sources/:slug 형식은 형식화된 `src_…` 아이디만 허용합니다.
# 사람이 읽을 수 있는 슬러그는 400 bare_slug_rejected를 반환합니다(슬러그는 조직 범위, #690).
# 형식화된 아이디를 사용하면 기본 형식도 작동합니다:
#   POST $RELEASES_API_URL/v1/orgs/<orgSlug>/sources/<sourceSlug>/releases/batch
#   POST $RELEASES_API_URL/v1/sources/<src_…>/releases/batch
curl -sS -X POST "$RELEASES_API_URL/v1/orgs/<orgSlug>/sources/<sourceSlug>/releases/batch" \
  -H "Authorization: Bearer $RELEASES_API_KEY" \
  -H "Content-Type: application/json" \
  -d @chunk.json   # { "releases": [ … ] }

릴리스별 필드 (실제 허용되는 형태 — workers/api/src/routes/sources.ts, postReleasesBatchHandler):

{
  "version": "1.4.0", // 선택 사항; 날짜 기반 또는 블로그 소스의 경우 생략/null
  "title": "다크 모드", // 필수
  "content": "마크다운 본문…", // 필수
  "url": "https://…/changelog/dark-mode", // 선택 사항이지만 **항상 채울 것** — 중복 제거 키입니다
  "contentHash": "…", // 선택 사항
  "publishedAt": "2026-05-30", // 선택 사항; ISO-8601 (일치성 참조)
  "media": "https://…/shot.png", // 선택 사항
  "type": "feature", // 선택 사항; "feature" | "rollup"
  "prerelease": false, // 선택 사항
}
  • 멱등성. UNIQUE(source_id, url)에서 업서트, 채우고 덮어쓰지 않음: 충돌 시 저장된 행이 비어 있을 때만 content/media를 백필하므로 재실행이 좋은 행을 덮어쓰지 않습니다. 재실행 및 페이지 매김에 안전합니다 — 동일한 url이 중복되지 않고 업데이트됩니다.
  • 보강 재-POST (mode: "upsert-content"). 기본값이 채우기 전용이므로, 이미 콘텐츠가 있는 URL에 더 풍부한 콘텐츠를 재-POST하는 것은 no-op입니다 — 스텁이 그대로 유지됩니다. 의도적인 두 번째 패스(먼저 인덱스 요약 시드, 그 다음 전체 상세 페이지 본문 + 미디어)를 위해 배치 본문의 최상위 레벨에 "mode": "upsert-content"를 추가하세요: 동일 URL 충돌 시 content/media를 덮어쓰고 스크랩 제목 중복 제거 사전 필터(#1526)를 건너뜁니다. 의도적인 보강에만 사용하고, 기본 경로는 채우기 전용으로 유지하여 일상적인 재가져오기가 덮어쓰지 못하게 하세요. url은 시드된 행과 동일해야 합니다 — 보강은 URL을 키로 하므로, URL 체계가 변경되면 대신 중복이 삽입됩니다. URL 체계가 변경된 경우 대신 재시드하세요: releases release delete --source <src_…> --hard 후 새 /batch(기본 모드).
  • 인증. write 범위 필요; 정적 RELEASES_API_KEY(루트)가 이를 충족합니다. 이 리포지토리에서 .env가 RELEASES_API_URL + RELEASES_API_KEY를 자동 로드합니다.
  • 삽입 시 AI 없음 (비용 계약 참조).

청킹 도우미(records.json 배열을 읽고 50개씩 배치로 POST):

bun -e '
const orgSlug = process.argv[1], sourceSlug = process.argv[2], file = process.argv[3];
const all = JSON.parse(await Bun.file(file).text());
const base = process.env.RELEASES_API_URL, key = process.env.RELEASES_API_KEY;
for (let i = 0; i < all.length; i += 50) {
  const releases = all.slice(i, i + 50);
  const res = await fetch(`${base}/v1/orgs/${orgSlug}/sources/${sourceSlug}/releases/batch`, {
    method: "POST",
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    body: JSON.stringify({ releases }),
  });
  console.log(`chunk ${i / 50}: ${res.status} ${(await res.text()).slice(0, 200)}`);
}
' <orgSlug> <sourceSlug> records.json

6단계 — 검증

releases tail <slug> --json   # 또는: get_latest_releases (형식화된 도구)

릴리스에 제목, 날짜, 콘텐츠 및 미디어가 비어 있지 않은지 확인합니다. 행이 비어 있으면 추출을 수정하고 재실행하세요 — 기본 업서트는 빈 행을 안전하게 백필합니다. 행에 이미 얇지만 비어 있지 않은 콘텐츠가 있는 경우(예: 전체 본문으로 대체하려는 인덱스 요약 스텁), 일반 재실행은 no-op입니다 — 5단계의 mode: "upsert-content" 보강 재-POST를 사용하세요.

소스를 온보딩하는 것이 아니라 복구하는 중인가요? 이 단계는 작성된 내용만 확인합니다 — 작성되지 않은 내용에 대해서는 아무 것도 알려주지 않습니다. 소스가 "복구"되었다거나 "누락된 것이 없다"고 누군가에게 말하기 전에, 필수 라이브 페이지 현장 확인을 위해 backfilling-sources → 소스를 "깨끗하다" 또는 "복구되었다"고 선언하기 전에 를 참조하세요.

7단계 — 플레이북 노트

향후 에이전트가 이를 다시 유도하지 않도록 이 소스가 어떻게 인제스트되었는지 기록하세요. manage_playbook(액션 update_notes) 또는 releases admin playbook <org> --notes-file -을 통해. 소스가 로컬로 인제스트되었고(아직 크론 경로에 있지 않음), 관찰된 주기, 추출 문제점을 기록하세요. managing-sources 플레이북 작성 규칙(명령형 어조, keep-test, ### Fetch instructions / ### Traps / ### Coverage)을 따르세요.

관련 스킬