product-update-logger
이 스킬에 제품 출시 내용을 알려주세요. docs/changelog.md(최신 항목이 가장 위에 오는 지속적으로 업데이트되는 로그)에 깔끔한 변경 로그 항목을 작성하고, 동시에 트윗 스레드, LinkedIn 게시물, 이메일 스니펫, 한 줄 요약으로 구성된 콘텐츠 패키지를 생성합니다.
입력 소스: 메시지의 자유 텍스트, 로컬 리포지토리에서 자동으로 읽어온 Git 커밋, 또는 리포지토리를 제공한 경우 GitHub PR. 어떤 조합이든 가능합니다.
참조 파일
각 실행 전에 다음 파일을 읽으십시오:
cat references/changelog-format.md
cat references/content-rules.md
cat references/noise-filter.md
1단계: 설정 확인
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-설정 안 됨 -- GitHub PR 가져오기 비활성화됨}"
echo "Git: $(git rev-parse --is-inside-work-tree 2>/dev/null && echo '리포지토리 감지됨' || echo 'Git 리포지토리 아님')"
echo "변경 로그: $(ls docs/changelog.md 2>/dev/null && echo '존재함' || echo '생성 예정')"
Git 사용 가능 여부와 변경 로그가 이미 존재하는지 확인합니다. 이는 버전 레이블 형식을 결정합니다.
2단계: 입력 파싱
대화에서 다음을 수집합니다:
items-- 출시된 내용에 대한 자유 텍스트 설명(여러 개인 경우 파이프로 구분). Git을 사용할 수 있는 경우 선택 사항입니다.since-- 몇 일 전까지를 조회할지. 기본값: 7. 사용자가 "지난 2주"(14) 또는 "마지막 릴리스 이후"라고 말할 수 있습니다.repo-- PR 가져오기를 위한 GitHub "owner/repo". 선택 사항입니다.version_label-- "v2.1.0" 또는 "The Speed Update"와 같은 사용자 정의 레이블. 선택 사항이며, 기본값은 날짜 기반입니다.
사용자가 items에 대해 아무 말도 하지 않았고 Git 리포지토리도 없는 경우: "무엇을 출시했나요? 기능, 수정 사항, 또는 개선 사항을 한 줄에 하나씩 나열해 주세요."라고 물어보십시오.
Git을 사용할 수 있고 사용자가 특별히 언급한 것이 없는 경우: 3단계에서 Git 자동 읽기를 진행합니다. 변환하기 전에 찾은 내용을 사용자에게 보여주고 확인을 받으십시오.
파싱된 입력을 작성합니다:
python3 << 'PYEOF'
import json, os, re
inp = {
"items": "", # 채우기: 파이프로 구분된 자유 텍스트, 또는 없는 경우 ""
"since": 7, # 채우기: 정수 일수
"repo": "", # 채우기: "owner/repo" 또는 ""
"version_label": "" # 채우기: ""은 자동(날짜 기반), 또는 사용자 정의 문자열
}
with open("/tmp/pul-input.json", "w") as f:
json.dump(inp, f, indent=2)
print(f"조회 기간: {inp['since']}일")
print(f"자유 텍스트 항목: {inp['items'] or '없음 (Git/GitHub 사용 예정)'}")
print(f"GitHub 리포지토리: {inp['repo'] or '없음'}")
print(f"버전 레이블: {inp['version_label'] or '자동 (날짜 기반)'}")
PYEOF
3단계: 수집 스크립트 실행
ls scripts/gather.py 2>/dev/null && echo "스크립트 찾음" || echo "오류: scripts/gather.py를 찾을 수 없음"
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/gather.py \
--since "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['since'])")" \
--repo "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['repo'])")" \
--items "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['items'])")" \
--output /tmp/pul-raw.json
출력 확인:
python3 -c "
import json
with open('/tmp/pul-raw.json') as f:
d = json.load(f)
print(f'찾은 항목 수: {d[\"total_items\"]}')
print(f'노이즈 필터링됨: {d[\"noise_filtered\"]}')
print(f'Git 사용 가능: {d[\"git_available\"]}')
print(f'GitHub 사용 가능: {d[\"github_available\"]}')
print(f'출처: git={sum(1 for i in d[\"items\"] if i[\"source\"]==\"git_commit\")}, '
f'prs={sum(1 for i in d[\"items\"] if i[\"source\"]==\"github_pr\")}, '
f'text={sum(1 for i in d[\"items\"] if i[\"source\"]==\"free_text\")}')
print()
print('항목:')
for item in d['items']:
print(f' [{item[\"source\"]}] {item[\"subject\"]}')
"
total_items == 0인 경우: 중단하고 사용자에게 알립니다: "출시된 항목을 찾을 수 없습니다. 출시한 내용을 설명하거나, 최근 커밋이 있는 Git 리포지토리를 알려주거나, repo: owner/repo와 GITHUB_TOKEN을 사용하여 GitHub 리포지토리를 추가해 주세요."
항목 목록을 사용자에게 보여주고 다음과 같이 묻습니다: "이것이 제가 찾은 항목들입니다. 변경 로그를 작성하기 전에 추가하거나 제거할 항목이 있나요?"
확인 또는 수정을 기다리십시오. 사용자가 "좋아요", "진행"이라고 말하거나 변경하지 않으면 계속 진행합니다. 사용자가 항목을 추가하거나 제거하면 4단계 전에 /tmp/pul-raw.json을 그에 따라 업데이트하십시오.
4단계: 변경 로그 항목 생성
컨텍스트를 위해 항목 출력:
python3 -c "
import json
with open('/tmp/pul-raw.json') as f:
d = json.load(f)
print(json.dumps(d['items'], indent=2))
print()
print(f'기존 변경 로그 형식: {d[\"existing_changelog\"][\"format\"]}')
print(f'마지막 레이블: {d[\"existing_changelog\"][\"last_label\"]}')
print(f'오늘 날짜: {d[\"date\"]}')
"
AI 지침: 각 원시 항목을 기술적 언어에서 사용자 혜택 중심 언어로 변환하십시오. 변환 규칙과 예시는 references/changelog-format.md를 따르십시오.
규칙:
- 결과나 지표를 임의로 만들어내지 마십시오. "40% 더 빠름"은 소스 데이터에서 비롯되어야 합니다. 커밋이나 PR에 숫자가 없으면 추가하지 마십시오.
- 과거형 사용: "Added", "Fixed", "Improved" -- "Adds", "Fixes"가 아님
- 각 항목에 정확히 하나의 카테고리 할당: New, Improved, Fixed, 또는 Under the hood
- Under the hood: 개발자와 관련된 경우(API 변경, 호환성 깨는 변경)에만 포함. 빈 섹션은 생략합니다.
- 테스트 변경, CI 변경, 문서 전용 커밋에 해당하는 항목은 생략합니다.
버전 레이블 결정:
- 사용자가 지정한 경우: 정확히 사용
existing_changelog.format == "semver"인 경우: 변경 사항에 따라 증가(수정 사항만 있으면 패치, 새 기능이 있으면 마이너)- 기본값: 오늘 날짜를 사용하여
Week of [Month Day, Year]
/tmp/pul-entry.json에 항목 작성:
{
"label": "2026년 4월 23일 주",
"date": "2026-04-23",
"new": [
{"title": "다크 모드", "description": "설정 > 외관에서 토글 가능. 모든 보기에서 작동."}
],
"improved": [
{"title": "API 응답 시간", "description": "평균 40% 향상. 대시보드가 1초 미만으로 로드됩니다."}
],
"fixed": [
{"title": "CSV 내보내기", "description": "내보내기 시 마지막 행이 누락되지 않습니다."}
],
"under_the_hood": []
}
항목 확인:
python3 -c "
import json
with open('/tmp/pul-entry.json') as f:
e = json.load(f)
print(f'레이블: {e[\"label\"]}')
total = 0
for cat in ['new', 'improved', 'fixed', 'under_the_hood']:
items = e.get(cat, [])
if items:
print(f'{cat.replace(\"_\", \" \").title()} ({len(items)}건):')
for item in items:
print(f' - {item[\"title\"]}: {item[\"description\"]}')
total += len(items)
print(f'총계: {total}개 항목')
"
5단계: 콘텐츠 패키지 생성
4단계의 변경 로그 항목을 사용하여 네 가지 콘텐츠를 모두 생성합니다. references/content-rules.md를 엄격히 따르십시오.
한 줄 요약 (최대 20단어): 가장 큰 1-2개 항목을 다루는 한 문장. 평이한 언어, 전문 용어 사용 금지.
트윗 스레드 (3-5개 트윗):
- 트윗 1: 후크 -- "이번 주에 [N]가지를 출시했습니다." 또는 가장 큰 기능으로 시작
- 트윗 2-N: 트윗당 하나의 항목, 최대 1-2문장
- 마지막 트윗: "변경 로그: [링크]" 또는 "다음 주에 더 많은 소식이 있습니다." (선택 사항)
- 각 트윗은 280자 미만
- 해시태그 없음. 줄표(em dash) 없음. 능동태 사용.
LinkedIn 게시물:
- 마크다운 없음(별표는 LinkedIn에서 문자 그대로 렌더링됨)
- 해시태그 없음
- 창업자 톤: "We shipped", "We are excited to announce" 사용 금지
- 짧은 단락 (각 1-2문장), 단락 사이에 빈 줄
- CTA 대신 질문이나 관찰로 마무리
- 총 150-400단어
이메일 스니펫:
- 제목: "이번 주 출시: [가장 큰 항목] + [그 외 1개]"
- 본문: 50-100단어. "이번 주에 출시한 내용입니다:" 그 다음 불릿 목록.
/tmp/pul-content.json에 작성:
{
"one_liner": "다크 모드, 더 빨라진 API, 수정된 내보내기 버그.",
"tweet_thread": [
"이번 주에 3가지를 출시했습니다.",
"다크 모드가 출시되었습니다. 설정 > 외관에서 토글하세요. 모든 곳에서 작동합니다.",
"API 응답 시간이 40% 빨라졌습니다. 대시보드가 1초 안에 로드됩니다.",
"수정됨: CSV 내보내기 시 마지막 행이 누락되던 문제가 해결되었습니다.",
"변경 로그: [링크]"
],
"linkedin_post": "이번 주에 3가지 업데이트를 출시했습니다.\\n\\n다크 모드가 출시되었습니다. 설정의 외관에서 토글할 수 있으며 모든 보기에서 작동합니다.\\n\\nAPI 응답 시간이 평균 40% 빨라졌습니다. 대부분의 사용자에게 대시보드가 1초 안에 로드됩니다.\\n\\n또한 CSV 내보내기 시 마지막 행이 조용히 누락되던 버그를 수정했습니다. 이 문제를 겪고 내보내기를 중단했다면 다시 시도해 볼 가치가 있습니다.\\n\\n어떤 기능을 기다리고 계셨나요?",
"email_snippet": {
"subject": "이번 주 출시: 다크 모드 + 더 빨라진 API",
"body": "이번 주에 출시한 내용입니다:\\n\\n- 다크 모드: 설정 > 외관에서 토글 가능\\n- API 응답 시간: 40% 향상, 대시보드 1초 미만 로드\\n- 수정됨: CSV 내보내기에서 마지막 행 누락 문제 해결\\n\\n전체 변경 로그는 아래에 있습니다."
}
}
6단계: 자체 QA
python3 -c "
import json, re
with open('/tmp/pul-raw.json') as f:
raw = json.load(f)
with open('/tmp/pul-entry.json') as f:
entry = json.load(f)
with open('/tmp/pul-content.json') as f:
content = json.load(f)
full_text = json.dumps(entry) + json.dumps(content)
fails = 0
# 검사 1: 줄표(em dash) 없음
if chr(8212) in full_text:
print('실패: 줄표(em dash) 발견 -- 하이픈으로 대체하세요')
fails += 1
else:
print('통과: 줄표 없음')
# 검사 2: 금지 단어
banned = ['powerful', 'robust', 'seamless', 'innovative', 'game-changing',
'streamline', 'leverage', 'transform', 'revolutionize', 'excited to announce',
'pleased to announce', 'we are thrilled', 'cutting-edge', 'best-in-class',
'world-class', 'unlock', 'delightful']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
print(f'실패: 금지 단어 발견됨: {found}')
fails += 1
else:
print('통과: 금지 단어 없음')
# 검사 3: 트윗 길이
thread = content.get('tweet_thread', [])
long_tweets = [(i+1, len(t)) for i, t in enumerate(thread) if len(t) > 280]
if long_tweets:
print(f'실패: 280자 초과 트윗: {long_tweets}')
fails += 1
else:
print(f'통과: 모든 {len(thread)}개 트윗 280자 미만')
# 검사 4: LinkedIn에 해시태그 없음
li = content.get('linkedin_post', '')
if re.search(r'#[A-Za-z]', li):
print('실패: LinkedIn 게시물에 해시태그 발견됨')
fails += 1
else:
print('통과: LinkedIn에 해시태그 없음')
# 검사 5: LinkedIn에 마크다운 없음
if '**' in li or '__' in li:
print('실패: LinkedIn에 마크다운 형식이 있음 (별표가 문자 그대로 렌더링됨)')
fails += 1
else:
print('통과: LinkedIn에 마크다운 없음')
# 검사 6: 한 줄 요약 단어 수
one_liner = content.get('one_liner', '')
word_count = len(one_liner.split())
if word_count > 20:
print(f'실패: 한 줄 요약이 {word_count}단어입니다 (최대 20단어)')
fails += 1
else:
print(f'통과: 한 줄 요약이 {word_count}단어입니다')
# 검사 7: 항목 수
entry_items = (len(entry.get('new', [])) + len(entry.get('improved', [])) +
len(entry.get('fixed', [])) + len(entry.get('under_the_hood', [])))
raw_total = raw['total_items']
print(f'정보: {raw_total}개의 원시 항목에서 {entry_items}개의 변경 로그 항목 생성')
print()
print(f'결과: {\"통과\" if fails == 0 else f\"실패 ({fails}개 문제)\"}')
"
검사에 실패한 경우: 7단계로 진행하기 전에 관련 임시 파일에서 문제를 수정하십시오. 수정 후 검사를 다시 실행하십시오.
7단계: 변경 로그에 추가 및 콘텐츠 저장
python3 << 'PYEOF'
import json, os, re
with open('/tmp/pul-entry.json') as f:
entry = json.load(f)
with open('/tmp/pul-content.json') as f:
content = json.load(f)
# 새 변경 로그 섹션 빌드
lines = [f"## {entry['label']}", ""]
CAT_HEADERS = {
"new": "### 새로운 기능",
"improved": "### 개선 사항",
"fixed": "### 수정 사항",
"under_the_hood": "### 내부 변경 사항",
}
for cat, header in CAT_HEADERS.items():
items = entry.get(cat, [])
if items:
lines.append(header)
for item in items:
lines.append(f"- **{item['title']}** -- {item['description']}")
lines.append("")
lines.append("---")
lines.append("")
new_section = "\n".join(lines)
# docs/changelog.md 앞에 추가
os.makedirs("docs", exist_ok=True)
changelog_path = "docs/changelog.md"
if os.path.exists(changelog_path):
existing = open(changelog_path).read()
# 최상위 제목(있는 경우) 뒤 또는 맨 위에 삽입
if existing.startswith("# "):
end_of_heading = existing.index("\n") + 1
updated = existing[:end_of_heading] + "\n" + new_section + existing[end_of_heading:]
else:
updated = new_section + existing
else:
updated = "# 변경 로그\n\n" + new_section
with open(changelog_path, "w") as f:
f.write(updated)
print(f"변경 로그 업데이트됨: {changelog_path}")
# 콘텐츠 패키지 저장
date = entry['date']
content_dir = "docs/product-updates"
os.makedirs(content_dir, exist_ok=True)
content_path = f"{content_dir}/{date}-content.md"
content_lines = [
f"# 콘텐츠 패키지: {entry['label']}",
"",
"## 한 줄 요약",
content.get('one_liner', ''),
"",
"## 트윗 스레드",
"",
]
thread = content.get('tweet_thread', [])
for i, tweet in enumerate(thread, 1):
content_lines.append(f"[{i}/{len(thread)}] {tweet}")
content_lines.append("")
content_lines += [
"## LinkedIn 게시물",
"",
content.get('linkedin_post', ''),
"",
"## 이메일 스니펫",
"",
f"제목: {content.get('email_snippet', {}).get('subject', '')}",
"",
content.get('email_snippet', {}).get('body', ''),
"",
]
with open(content_path, "w") as f:
f.write("\n".join(content_lines))
print(f"콘텐츠 패키지: {content_path}")
PYEOF
8단계: 정리 및 결과 표시
rm -f /tmp/pul-input.json /tmp/pul-raw.json /tmp/pul-entry.json /tmp/pul-content.json
echo "완료."
다음 순서로 사용자에게 표시합니다:
1. 변경 로그 항목 (원시 JSON이 아닌 마크다운 형식):
## 2026년 4월 23일 주
### 새로운 기능
- **다크 모드** -- 설정 > 외관에서 토글 가능. 모든 보기에서 작동.
### 개선 사항
- **API 응답 시간** -- 평균 40% 향상. 대시보드가 이제 1초 안에 로드됩니다.
### 수정 사항
- **CSV 내보내기** -- 내보내기 시 마지막 행이 더 이상 누락되지 않습니다.
2. 한 줄 요약
다크 모드, 더 빨라진 API, 수정된 내보내기 버그.
3. 콘텐츠 패키지 파일 경로
docs/product-updates/2026-04-23-content.md
"다음에 이 작업을 다시 실행하면 새 항목이 docs/changelog.md 상단에 추가됩니다."