๐Ÿค– AI๋ฅผ ํ†ตํ•œ PR ๋‚ด์šฉ ์ƒ์„ฑ ์ž๋™ํ™”

JNETiiiยท2026๋…„ 1์›” 12์ผ

Backend

๋ชฉ๋ก ๋ณด๊ธฐ
4/4

๋ฐฐ๊ฒฝ

ํ˜„์žฌ Git-flow ์ „๋žต์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.
feature ๋ธŒ๋žœ์น˜์—์„œ ์ž‘์—…์„ ๋๋‚ด๊ณ  develop ๋ธŒ๋žœ์น˜์— ๋จธ์ง€๋ฅผ ํ•˜๊ณ ๋‚˜์„œ develop ๋ธŒ๋žœ์น˜๋ฅผ main ๋ธŒ๋žœ์น˜๋กœ PR์„ ์ƒ์„ฑํ•ด์•ผํ•˜๋Š”๋ฐ ์ด ๋ถ€๋ถ„์—์„œ ์ •ํ•ด์ง„ ๊ทœ์น™๋„ ์—†๊ณ , ๋งค๋ฒˆ develop PR๊ณผ ๋น„์Šทํ•œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•ด์„œ ๋ฒˆ๊ฑฐ๋กœ์›€๋งŒ ๋А๋ผ๊ณ  ์žˆ์—ˆ๋‹ค.
๊ทธ๋ž˜์„œ main์œผ๋กœ ์˜ฌ๋ฆฌ๋Š” PR์€ AI์—๊ฒŒ ๋งก๊ฒจ๋ณด๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค.

์ž‘์—… ์ ˆ์ฐจ

0. ์‚ฌ์ „ ์ž‘์—…

์ˆœ์„œ

AI๊ฐ€ PR์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ํ”Œ๋กœ์šฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
develop์— ํ‘ธ์‹œ๊ฐ€ ๋์„ ๋•Œ main์— ๋จธ์ง€ํ•˜๋Š” PR์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ,
์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ ํŠน์„ฑ์ƒ ์ด ๋ฐฉ๋ฒ•๋ณด๋‹จ ๊ฐœ๋ฐœ์ž๊ฐ€ PR์„ ์ƒ์„ฑํ–ˆ์„ ๋•Œ PR ๋‚ด์šฉ์„ ์ž๋™์œผ๋กœ ์ˆ˜์ •ํ•ด์ฃผ๋Š”๊ฒŒ ์ ์ ˆํ•˜๋‹ค๊ณ  ๋ณด์•„ ๊ทธ๋ ‡๊ฒŒ ์ง„ํ–‰ํ–ˆ๋‹ค.

1. main PR ์ƒ์„ฑ
2. AI๊ฐ€ PR ๋‚ด์šฉ ์ˆ˜์ •
	2-1. ์ปค๋ฐ‹์—์„œ Jira ํ‹ฐ์ผ“ ๋ฒˆํ˜ธ๋ฅผ ์ถ”์ถœํ•˜์—ฌ Jira API๋ฅผ ํ†ตํ•ด ์ด์Šˆ ๋‚ด์šฉ ๊ฐ€์ ธ์˜ค๊ธฐ
    2-2. Jira ์ด์Šˆ ๋‚ด์šฉ๊ณผ git diff ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ AI์—๊ฒŒ PR ๋‚ด์šฉ ์š”์ฒญํ•˜๊ธฐ

๋˜ํ•œ, git diff ๋งŒ ๋ณด์ง€ ์•Š๊ณ  Jira API ํ˜ธ์ถœ์„ ํ†ตํ•ด Jira ํ‹ฐ์ผ“ ์ด์Šˆ์˜ ๋‚ด์šฉ์„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์–ด ๋‚ด์šฉ์˜ ์ •ํ™•์„ฑ์„ ์˜ฌ๋ ธ๋‹ค.
ํ™•์‹คํžˆ diff ๋งŒ ๋ณด๋Š” ๊ฒƒ๋ณด๋‹จ ์ปค๋ฐ‹์˜ ๋ฐฐ๊ฒฝ์„ ์•Œ๊ฒŒ ๋˜๋‹ˆ ๋‚ด์šฉ์ด ๋” ์ข‹์•„์ง„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ค€๋น„ ์‚ฌํ•ญ

OpenAI API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” API Key๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
๋‚˜๋Š” ์ด๋ฏธ API Key๊ฐ€ ์žˆ์–ด์„œ ๋˜ ๋ฐœ๊ธ‰๋ฐ›์ง„ ์•Š์•˜๋‹ค.

Jira API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋„ Jira์—์„œ API Token์„ ๋ฐœ๊ธ‰๋ฐ›์•„์•ผ ํ•œ๋‹ค.
๋จผ์ €, ์šฐ์ธก ์ƒ๋‹จ์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ๋ˆŒ๋Ÿฌ Account settings๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

๊ทธ ํ›„, ์ƒ๋‹จ ํƒญ ์ค‘ ๋ณด์•ˆ์— ๋“ค์–ด๊ฐ„๋‹ค.
ํ•˜๋‹จ์— ๋ณด๋ฉด API ํ† ํฐ ์„น์…˜์ด ์žˆ๋‹ค. API ํ† ํฐ ๋งŒ๋“ค๊ธฐ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ƒ์„ฑํ•œ API ํ† ํฐ ๋ชฉ๋ก์ด ๋‚˜์˜จ๋‹ค. ๋‚˜๋Š” ์ด๋ฏธ PR ์—…๋ฐ์ดํŠธ์šฉ ํ† ํฐ์„ ๋งŒ๋“ค์–ด๋†“์•˜๋‹ค.
์—†๋‹ค๋ฉด API ํ† ํฐ ๋งŒ๋“ค๊ธฐ๋กœ ํ† ํฐ์„ ์ƒ์„ฑํ•œ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฆ„๊ณผ ๋งŒ๋ฃŒ ๋‚ ์งœ๋งŒ ์„ ํƒํ•˜๋ฉด ๋œ๋‹ค.


์ด๋ ‡๊ฒŒ ๋ฐœ๊ธ‰๋ฐ›์€ OpenAI API Key์™€ Jira API Token์„ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ๋„ฃ์–ด์ค€๋‹ค.
๋ ˆํฌ์ง€ํ† ๋ฆฌ Settings > Secrets and variables > Actions ์—์„œ ์„ค์ • ๊ฐ€๋Šฅํ•˜๋‹ค.



1. workflow yml ํŒŒ์ผ

๋‹ค์Œ์€ ์‹ค์ œ๋กœ ํŠน์ • ํ–‰๋™์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋™์ž‘ํ•  workflow๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

workflow name๊ณผ ์–ธ์ œ ์ด workflow๋ฅผ ์‹คํ–‰ํ• ๊ฑด์ง€ ์„ค์ •ํ•ด์ค€๋‹ค.
PR์ด ์ƒ์„ฑ๋  ๋•Œ, ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ ์‹คํ–‰ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.

name: AI PR Updater

# main PR์ด ์ƒ์„ฑ๋  ๋•Œ(opend), ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ(synchronize) ์‹คํ–‰
on:
  pull_request:
    types: [opened, synchronize]
    branches:
      - main

์ปค๋ฐ‹์˜ ๋ชจ๋“  ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ git diff ๋ช…๋ น์–ด๋กœ ํ˜„์žฌ ๋ฒ„์ „๊ณผ ๊ณผ๊ฑฐ ๋ฒ„์ „์˜ Diff๋ฅผ ์ถ”์ถœํ•˜์—ฌ pr_diff.txt ํŒŒ์ผ๋กœ ์ €์žฅํ•œ๋‹ค.

jobs:
  update-pr:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get Diff
        id: get_diff
        run: |
          git diff origin/${{ github.base_ref }}...origin/${{ github.head_ref }} > pr_diff.txt

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ Poetry๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—(openai ํฌํ•จ) poetry ์„ค์น˜ ํ›„ install ๋ฐ›๋Š”๋‹ค.

      - name: Install Poetry
        run: pipx install poetry

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.12.1'
          cache: 'poetry'

      - name: Install dependencies
        run: poetry install --no-interaction --no-root

AI ์‚ฌ์šฉ์‹œ ํ•„์š”ํ•œ ์ •๋ณด๋“ค์„ env๋กœ ์„ค์ • ํ›„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. (์Šคํฌ๋ฆฝํŠธ ๋‚ด์šฉ์€ ์•„๋ž˜ ๋‚˜์˜จ๋‹ค.)

๊ทธ ํ›„, AI ์‘๋‹ต๊ฐ’์œผ๋กœ PR ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•ด์ค€๋‹ค.
PR ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•œ ๊ถŒํ•œ๋„ ๋ถ€์—ฌํ•ด์ค€๋‹ค.

	  # **AI๋ฅผ ํ†ตํ•ด PR ๋‚ด์šฉ ์ƒ์„ฑ (์Šคํฌ๋ฆฝํŠธ์—์„œ ์‚ฌ์šฉํ•  ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ env๋กœ ์„ค์ •)**
      - name: Generate AI Summary
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
          JIRA_EMAIL_ADDRESS: ${{ secrets.JIRA_EMAIL_ADDRESS }}
        run: |
          poetry run python .github/scripts/ai_summary.py

	  # PR ๋‚ด์šฉ ์ˆ˜์ • (gh pr edit ...)
      - name: Update PR Description
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh pr edit ${{ github.event.pull_request.number }} --body-file ai_summary.txt

# ์“ฐ๊ธฐ ๊ถŒํ•œ ๋ถ€์—ฌ
permissions:
  contents: read
  pull-requests: write

์ตœ์ข… ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

[ ai-pr.yml ]

name: AI PR Updater

# main PR์ด ์ƒ์„ฑ๋  ๋•Œ(opend), ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ(synchronize) ์‹คํ–‰
on:
  pull_request:
    types: [opened, synchronize]
    branches:
      - main

jobs:
  update-pr:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          
      # ํ˜„์žฌ ๋ฒ„์ „๊ณผ ๊ณผ๊ฑฐ ๋ฒ„์ „์˜ Diff๋ฅผ ์ถ”์ถœํ•˜์—ฌ *pr_diff.txt* ํŒŒ์ผ๋กœ ์ €์žฅ
      - name: Get Diff
        id: get_diff
        run: |
          git diff origin/${{ github.base_ref }}...origin/${{ github.head_ref }} > pr_diff.txt
          
      # openai ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ poetry๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— poetry ์„ค์น˜
      - name: Install Poetry
        run: pipx install poetry

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.12.1'
          cache: 'poetry'

      - name: Install dependencies
        run: poetry install --no-interaction --no-root

	  # **AI๋ฅผ ํ†ตํ•ด PR ๋‚ด์šฉ ์ƒ์„ฑ (์Šคํฌ๋ฆฝํŠธ์—์„œ ์‚ฌ์šฉํ•  ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ env๋กœ ์„ค์ •)**
      - name: Generate AI Summary
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
          JIRA_EMAIL_ADDRESS: ${{ secrets.JIRA_EMAIL_ADDRESS }}
        run: |
          poetry run python .github/scripts/ai_summary.py

	  # PR ๋‚ด์šฉ ์ˆ˜์ • (gh pr edit ...)
      - name: Update PR Description
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh pr edit ${{ github.event.pull_request.number }} --body-file ai_summary.txt

# ์“ฐ๊ธฐ ๊ถŒํ•œ ๋ถ€์—ฌ
permissions:
  contents: read
  pull-requests: write


2. AI ํ˜ธ์ถœ ์Šคํฌ๋ฆฝํŠธ

Generate AI Summary ๋‹จ๊ณ„์—์„œ ์–ธ๊ธ‰ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ฐจ๋ก€๋‹ค.

์šฐ๋ฆฌ๋Š” ์ปค๋ฐ‹๋ฉ”์‹œ์ง€ ๋งจ ์•ž์— prefix๋กœ Jira ํ‹ฐ์ผ“ ๋ฒˆํ˜ธ๋ฅผ ๋ถ™์ด๋Š” ๊ทœ์น™์ด ์žˆ๋‹ค.
์ด๋ฅผ ์ด์šฉํ•ด์„œ ์ปค๋ฐ‹๋ฉ”์‹œ์ง€์˜ Jira ํ‹ฐ์ผ“ ๋ฒˆํ˜ธ๋ฅผ ์ถ”์ถœํ•˜๊ณ , ํ•ด๋‹น ๋ฒˆํ˜ธ๋กœ Jira API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ด์Šˆ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€, ํ•ด๋‹น ์ด์Šˆ ๋‚ด์šฉ๊ณผ Diff ๊ธฐ๋ฐ˜์œผ๋กœ AI๊ฐ€ PR ๋‚ด์šฉ์„ ์ž‘์„ฑํ•ด์ฃผ๋„๋ก ์ž‘์„ฑํ–ˆ๋‹ค.

์ตœ์ข… ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

[ ai_summary.yml ]

main ํ•จ์ˆ˜

import os
import re
import subprocess
from openai import OpenAI
import sys
import requests
from requests.auth import HTTPBasicAuth

DIFF_FILE = 'pr_diff.txt'
MODEL_NAME = 'gpt-4-turbo'
SUMMARY_FILE = 'ai_summary.txt'
JIRA_BASE_URL = 'base_url'
DEFAULT_MESSAGE = "No text"


def generate_pr_summary():
    # ์ง€๋ผ ํ‹ฐ์ผ“ ๋ฒˆํ˜ธ ์ถ”์ถœ ํ›„ ์ด์Šˆ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜จ๋‹ค
    ticket_id = get_jira_ticket_id()
    jira_description, jira_summary = DEFAULT_MESSAGE, DEFAULT_MESSAGE
    if ticket_id:
        jira_description, jira_summary = get_jira_issue_details(ticket_id=ticket_id)

	# yml ํŒŒ์ผ์—์„œ ์„ค์ •ํ•ด๋‘” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        print("Error: OPENAI_API_KEY is not set.")
        sys.exit(1)

    client = OpenAI(api_key=api_key)

    # diff ํŒŒ์ผ ์ฝ๊ธฐ
    try:
        with open(DIFF_FILE, "r", encoding="utf-8") as f:
            diff_text = f.read()
    except FileNotFoundError:
        print("Error: pr_diff.txt not found.")
        sys.exit(1)

    # ํ”„๋กฌํ”„ํŠธ๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค.
    # ์˜์–ด๋กœ๋งŒ ์ž‘์„ฑํ–ˆ๋”๋‹ˆ ๊ฐ€๋”์”ฉ ์˜์–ด๋กœ PR ๋‚ด์šฉ์„ ์ ์–ด์ฃผ๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์–ด์„œ ์˜์–ด์™€ ํ•œ๊ตญ์–ด๋ฅผ ๊ฐ™์ด ์‚ฌ์šฉํ–ˆ๋‹ค.
    system_prompt = ("You are a professional Senior Software Engineer."
                     "You must communicate only in Korean and use Markdown for formatting.")
    user_prompt = f"""
    Analyze the provided Jira ticket information and Git Diff to write a concise GitHub Pull Request (PR) description.

    [Context Data]
    - Jira Ticket ID: {ticket_id}
    - Jira Summary: {jira_summary}
    - Jira Description: {jira_description}
    - Git Diff:
    {diff_text}

    [Response Guidelines - ๋ฐ˜๋“œ์‹œ ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๋ฉฐ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์„ ์‚ฌ์šฉํ•˜์„ธ์š”]
    1. **Jira ์ž‘์—… ์š”์•ฝ**: ์ œ๊ณต๋œ Jira ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ด ํ‹ฐ์ผ“์˜ ์ž‘์—… ๋‚ด์šฉ์„ ํ•ต์‹ฌ ์œ„์ฃผ๋กœ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.
       - ์ง€๋ผ ์ •๋ณด๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด Git Diff๋ฅผ ๋ถ„์„ํ•˜์—ฌ ํ•ด๋‹น ์ž‘์—…์ด ์™œ ํ•„์š”ํ•œ์ง€ ์œ ์ถ”ํ•ด์„œ ์ž‘์„ฑํ•˜์„ธ์š”.
    2. **PR Overview**: ๋ณ€๊ฒฝ ์‚ฌํ•ญ์˜ ํ•ต์‹ฌ(What)์„ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.
    3. **Detailed Changes**: ๊ฐ ํŒŒ์ผ๋ณ„ ๋ณ€๊ฒฝ์ ์„ **๋งˆํฌ๋‹ค์šด ๋ชฉ๋กํ˜•(Bullet points)**์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
       - ์ค‘์š”ํ•œ ๋กœ์ง ๋ณ€ํ™”๋ฅผ ์šฐ์„  ์„ค๋ช…ํ•˜์„ธ์š”.
       - **GROUP MINOR CHANGES**: ์‚ฌ์†Œํ•œ ์„ค์ •์ด๋‚˜ ์˜์กด์„ฑ ๋ณ€๊ฒฝ์€ ํ•œ ์ค„์˜ ๋ชฉ๋ก์œผ๋กœ ๋ฌถ์–ด์„œ ์š”์•ฝํ•˜์„ธ์š”.
       - ํ˜•์‹: `1. ํŒŒ์ผ๋ช…: ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์„ค๋ช…`
    4. **Reviewer's Notes**: ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ฃผ์˜ ๊นŠ๊ฒŒ ํ™•์ธํ•ด์•ผ ํ•  ์‚ฌํ•ญ์„ ๊ธฐ์ˆ ํ•ฉ๋‹ˆ๋‹ค.

    [Output Style & Rules]
    - **Language**: MUST be Korean (ํ•œ๊ตญ์–ด).
    - **Format**: Use clear Markdown (Headers, Bullets, Bold text, `Code Backticks`).
    - **Visual**: **๊ฐ ์„น์…˜ ์ œ๋ชฉ ์•ž์— ์ ์ ˆํ•œ ์ด๋ชจํ‹ฐ์ฝ˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์ด์„ธ์š”.**
    - **Tone**: Professional and formal.
    - No introductory phrases. Start with the content immediately.
    """

    # Create summary
    try:
        response = client.chat.completions.create(
            model=MODEL_NAME,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.2,
        )

        summary = response.choices[0].message.content

		# response๋กœ ๋ฐ›์€ ๊ฒฐ๊ณผ๋ฅผ ai_summary.txtํŒŒ์ผ๋กœ ์ €์žฅํ•œ๋‹ค.
        with open(SUMMARY_FILE, "w", encoding="utf-8") as f:
            f.write("## ๐Ÿค– AI PR ๋ถ„์„ ๊ฒฐ๊ณผ\n\n")
            f.write(summary)
            f.write("\n\n---\n*์ด ์š”์•ฝ์€ ChatGPT์— ์˜ํ•ด ์ž๋™ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.*")
    except Exception as e:
        print(f"Error during AI generation: {e}")
        sys.exit(1)
        
 if __name__ == "__main__":
    generate_pr_summary()
 

Jira ํ‹ฐ์ผ“ ๋ฒˆํ˜ธ ์ถ”์ถœ ํ›„ ์ด์Šˆ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜

def get_jira_issue_details(ticket_id):
    api_token = os.getenv("JIRA_API_TOKEN")
    email_address = os.getenv("JIRA_EMAIL_ADDRESS")

    if not api_token or not email_address:
        print("Error: JIRA_API_TOKEN or JIRA_EMAIL_ADDRESS is not set.")
        return DEFAULT_MESSAGE, DEFAULT_MESSAGE

    try:
        url = f"{JIRA_BASE_URL}/rest/api/2/issue/{ticket_id}"
        auth = HTTPBasicAuth(email_address, api_token)
        response = requests.get(url, auth=auth)

        if response.status_code != 200:
            print(f"Error: Failed to get Jira issue {ticket_id}. Status: {response.status_code}")
            return DEFAULT_MESSAGE, DEFAULT_MESSAGE

        data = response.json()
        description = str(data['fields'].get('description', DEFAULT_MESSAGE))[:1000]
        summary = data['fields'].get('summary', DEFAULT_MESSAGE)

        return description, summary
    except Exception:
        return DEFAULT_MESSAGE, DEFAULT_MESSAGE

def get_jira_ticket_id():
    try:
    	# develop ๋ธŒ๋žœ์น˜์˜ ๋งˆ์ง€๋ง‰ ์ปค๋ฐ‹์€ PR ๋จธ์ง€ํ•œ ์ปค๋ฐ‹์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‚˜ ์Šคํ‚ตํ•˜๊ณ  ๊ทธ ๋‹ค์Œ ์ปค๋ฐ‹์„ ๊ฐ€์ ธ์˜จ๋‹ค
        commit_msg = subprocess.check_output(["git", "log", "-1", "--skip=1", "--pretty=%B"]).decode()
        match = re.search(r"\[([A-Z]+-\d+)\]", commit_msg)

        return match.group(1) if match else None
    except Exception:
        return None


3. ํ…Œ์ŠคํŠธ

main์— PR์„ ์ƒ์„ฑํ•ด๋ณธ ๊ฒฐ๊ณผ, ๊ฐ€๋…์„ฑ ์žˆ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ PR ๋‚ด์šฉ์„ ์š”์•ฝํ•ด์ฃผ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
(ํ”„๋กฌํ”„ํŠธ ๊ฐœ์„  ๋ฐ ์ปค๋ฐ‹ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง ์ˆ˜์ •์„ ํ•œ ์ž‘์—…์— ๋Œ€ํ•ด ChatGPT๊ฐ€ ์จ์ค€ PR ๋‚ด์šฉ์ด๋‹ค.)

๊ฒฐ๋ก 

AI๋ฅผ ํ†ตํ•ด ๋ฒˆ๊ฑฐ๋กœ์šด ์ž‘์—…์„ ์ž๋™ํ™”๋กœ ์ „ํ™˜ํ•˜๋‹ˆ ์ข‹์€ ๊ฒฝํ—˜์ด์—ˆ๋‹ค.
์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ๋˜ ์žˆ์„์ง€ ์ƒ๊ฐํ•ด๋ณด์•„์•ผ๊ฒ ๋‹ค.

profile
๋„์ „์ž | ๊ฐœ๋ฐœ์ž

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2026๋…„ 1์›” 22์ผ

ํด๋กœ๋“œ๋กœ ๋ญ˜ ๋” ํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผ์ด ๋œ๋‹ค๋ฉด claude-code-achievements๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์•„์š”.

1๊ฐœ์˜ ๋‹ต๊ธ€