💬 배경

cursor AI와 함께 코드를 작성하는 연습을 하다보니, 알잘딱깔센으로 commit, push를 해주지 않아서 내가 신경써서 해달라고 요청하지 않는 이상 commit, push를 작은 단위 별로 꼼꼼하게 남겨놓는게 쉽지 않았다.

그래서 생각한게, 요즘 cursor ai + mcp 결합이 유행이던데, mcp를 조사해보면 자동화 구현에 도움이 되지 않을까 싶어서 시작하게 된 여정이다.

내가 원하는 기능은 딱 2가지였다.

  1. github issue를 내가 issue template 기반, 수기로 작성하면 업로드된 issue를 github projects TODO에 자동 업로드 시키기(이슈번호 포함)

1-1. 비슷한 원리로, issue에서 특정 이슈 deleted 됐을때, Todo에서도 삭제하기 (closed는 삭제 X. PR해서 closed된 이슈마저 삭제하면 안되기 때문)

  1. PR이 업로드되는 순간 projects의 해당 이슈번호들의 todo들은 in progress 단계로 자동 이동

📌 추가로, 원하는 기능이지만 github actions가 필요한게 아닌, cursor prompt에 명령하는 것 만으로도 구현이 가능했던 기능

👉🏻 특정 명령어 입력하면 PR 알아서 올려주기!


(gpt에게 질문했던 프롬프트)
커서와 기능 구현할때 커밋, 푸쉬는 내가 명령해서 수동으로 할거야. 그리고 PR template도 내가 알아서 .github에 넣어둘거야.
근데 내가 PR이 하고싶어지면 커서에게 '@pr closes #20, #30' 이런식으로 이슈번호가 포함된 특정 명령어를 입력하고, 커서가 알아서 해당 이슈들, 코드를 검토해서 PR template에 내용을 알아서 정리해서 PR올리도록 만들고싶어.


📌 추가 2. 내가 원하는 기능이었지만 기본 workflow에 있는 기능이라서 굳이 action을 만들 필요가 없었던 기능
👉🏻 merge 되는 순간 projects의 in progress에 있던 해당 이슈들은 DONE으로 이동

  • 전제 조건
    • default branch로의 merge일 것
    • pr discription 내부에 'closes #이슈번호' 적혀 있을 것

위의 3가지 기능을 알아보니 github actions + github api만으로도 구현이 가능한 기능이었지만, 구현하는 과정에서 mcp도 결합할 수 있다 하여 gpt, cursor ai와 함께하는 github 자동화 여정을 시작한다.


👩🏻‍💻 구현 과정

1️⃣ issue 업로드 시 projects Todo에 자동 업로드

✅ “Issue가 생성되면 GitHub Projects의 ‘To Do’ 열로 자동 이동”
이걸 GitHub Actions + GitHub API + MCP 주석을 함께 써서 직접 구현해보자!

1. 사전 확인이 필요한 GitHub Projects 정보

(projects v2 (beta)라는 가정 하에)

1) project_id (또는 project_number)
2) column_name 혹은 status name (예: "To Do")
3) 인증용 GITHUB_TOKEN (자동으로 제공됨)
(GITHUB_TOKEN ❌ secrets.PROJECT_ACCESS_TOKEN ✅ 로 문제 개선 됨)

✅ 1단계: GraphQL Explorer 접속
✅ 2단계: 쿼리문 복사해서 붙여넣기

query {
  user(login: "innes-k") {
    projectV2(number: 4) {
      id
      title
      fields(first: 20) {
        nodes {
          ... on ProjectV2Field {
            id
            name
          }
          ... on ProjectV2SingleSelectField {
            id
            name
            options {
              id
              name
            }
          }
        }
      }
    }
  }
}

✅ 3단계 : 실행하여 아래 3가지 추출
projectV2.id
"Status" 필드의 id
"Todo" 옵션의 id

📌 폴더 관리 계획

  • github actions 관련 : .github/workflows/[파일명.yml]
  • cursor, mcp 등 자동화 관련 : root경로/dev-helper/[파일명]

2. .github/workflows/[파일명.yml] 작성

❌ 1차 시도
-> 개선 필요 : query문 및 GITHUB_TOKEN을 PAT(Project Access Token)로 개선

name: Add Issue to Project Todo

on:
  issues:
    types: [opened]

jobs:
  add-to-project:
    runs-on: ubuntu-latest
    steps:
      - name: Add issue to project
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const query = `
              mutation {
                addProjectV2ItemById(input: {
                  projectId: "PVT_kwHOCNlLx84A3hwL",
                  contentId: "${{ github.event.issue.node_id }}"
                }) {
                  item {
                    id
                  }
                }
              }
            `;
            const result = await github.graphql(query);

            const itemId = result.addProjectV2ItemById.item.id;

            const setStatus = `
              mutation {
                updateProjectV2ItemFieldValue(input: {
                  projectId: "PVT_kwHOCNlLx84A3hwL",
                  itemId: "${itemId}",
                  fieldId: "PVTSSF_lAHOCNlLx84A3hwLzgspl6M",
                  value: {
                    singleSelectOptionId: "f75ad846"
                  }
                }) {
                  projectV2Item {
                    id
                  }
                }
              }
            `;
            await github.graphql(setStatus);

✅ 정상 동작

name: Add Issue to Project Todo

on:
  issues:
    types: [opened]

permissions:
  issues: write
  contents: read

jobs:
  add-to-project:
    runs-on: ubuntu-latest
    steps:
      - name: Add issue to project and set status to Todo
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.PROJECT_ACCESS_TOKEN }}
          script: |
            // 1. 이슈를 프로젝트에 추가
            const addToProjectQuery = `
              mutation {
                addProjectV2ItemById(input: {
                  projectId: "PVT_kwHOCNlLx84A3hwL",
                  contentId: "${{ github.event.issue.node_id }}"
                }) {
                  item {
                    id
                  }
                }
              }
            `;
            
            const addResult = await github.graphql(addToProjectQuery);
            const itemId = addResult.addProjectV2ItemById.item.id;
            
            // 2. 항목의 상태를 Todo로 설정
            const setStatusQuery = `
              mutation {
                updateProjectV2ItemFieldValue(input: {
                  projectId: "PVT_kwHOCNlLx84A3hwL",
                  itemId: "${itemId}",
                  fieldId: "PVTSSF_lAHOCNlLx84A3hwLzgspl6M",
                  value: {
                    singleSelectOptionId: "f75ad846"
                  }
                }) {
                  projectV2Item {
                    id
                  }
                }
              }
            `;
            
            await github.graphql(setStatusQuery);
            
            // 3. 이슈 제목에 이슈 번호 추가
            const issueNumber = context.issue.number;
            const issueTitle = context.payload.issue.title;
            
            if (!issueTitle.includes(`#${issueNumber}`)) {
              await github.rest.issues.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                title: `#${issueNumber} ${issueTitle}`
              });
            }

3. commit, push 필수!

변경사항이 있다면 push까지 마쳐야 .github를 인식해서 action workflow가 정상적으로 업로드된다.


🛠️ run 과정 중 트러블슈팅

🤔 문제

"Resource not accessible by integration" 오류는 GitHub Action이 GitHub Project에 접근할 수 있는 권한이 부족해서 발생하는 문제

🌈 해결

  1. settings > actions > general > workflow permissions 를 첫번째인 Read and write permissions 로 변경해주기

이후 한번 더 push해야 적용됨!

  • GitHub은 워크플로우 설정이 변경된 뒤에 새로 실행된 워크플로우에만 적용해줌
  • 따라서 .github/workflows/issue-to-project.yml 파일에 공백 추가해서 푸시라도 해야 됨
git commit --allow-empty -m "trigger workflow again after permission fix"
git push origin main

  1. action yml파일 중 github-token을 변경하기
  • 기존 : github-token: ${{ secrets.GITHUB_TOKEN }}
  • 변경 : github-token: ${{ secrets.PROJECT_ACCESS_TOKEN }}

  1. PAT(Project Access Token) 발급하기

📝 PROJECT_ACCESS_TOKEN 생성 및 등록 방법

특정 저장소의 GitHub Actions가 제대로 작동하려면 Project 접근 권한이 있는 Personal Access Token(PAT)이 필요합니다.

  1. GitHub 계정 설정으로 이동: 우측 상단 프로필 클릭 > Settings
  2. 좌측 메뉴에서 Developer settings > Personal access tokens > Tokens (classic) 클릭
  3. "Generate new token" > "Generate new token (classic)" 클릭
  4. 다음 설정으로 토큰 생성:
    • Note: cursor-practice-project-access
    • Expiration: 원하는 기간 선택 (최소 30일 이상 권장)
    • Scopes:
      • repo (전체 선택)
      • admin:org > write:org 선택 (Project 접근에 필요)
      • project (전체 선택)
  5. "Generate token" 클릭 후 생성된 토큰 복사 (이 페이지를 벗어나면 다시 확인 불가)
  6. 저장소로 돌아와서 Settings > Secrets and variables > Actions 클릭
  7. "New repository secret" 클릭 후 다음 정보 입력:
    • Name: PROJECT_ACCESS_TOKEN
    • Secret: 복사한 토큰 값 붙여넣기
  8. "Add secret" 클릭하여 저장

이제 GitHub Actions 워크플로우에서 ${{ secrets.PROJECT_ACCESS_TOKEN }}으로 해당 토큰에 접근할 수 있습니다.


2️⃣ PR 업로드 시 Todo -> in Progress 자동 이동

  1. cursor ai 프롬프트 작성

새로운 action을 만들거야.

일단 너가 PR을 알아서 올릴 수 있도록 PR방식을 먼저 설명해줄게.
내가 "@pr closes #20 #30" 형식으로 명령어를 작성하면, 지금까지 작성한 코드와 내가 #으로 남겨준 이슈번호를 토대로 검토 후, .github > pull_request_template 파일의 PR template 양식을 바탕으로 현재 브랜치의 작업 내용을 PR로 작성 후, dev 브랜치로 PR 업로드해줘.

그리고 PR이 github에 업로드되는 순간, PR closes 뒤에 적어둔 이슈 번호들이 적힌 projects Todo들을 in progress 단계로 자동으로 이동시키는 actions을 만들어줘.


📌 여기에 추가해야 할 내용
(이 프롬프트 이후에 개선한 내용)

  1. PR 제목을 브랜치 명이 아닌, 명령어에 포함되어 있는 이슈들의 제목들과 코드로 구현한 내용을 검토한 후, 모든 내용을 포괄할 수 있는 말로 PR 제목을 선정해줘.

  2. PR template 안에 관련 이슈번호 적을때 closes #1 #2 #3 이 형식이 아니라,
    closes #1
    closes #2
    closes #3
    이렇게 적도록 해줘.

  1. .github/workflows/move-issues-to-progress 문서 자동 작성
name: Move Issues to In Progress when PR Created

on:
  pull_request:
    types: [opened, reopened]

permissions:
  pull-requests: read
  contents: read

jobs:
  move-issues-to-in-progress:
    runs-on: ubuntu-latest
    steps:
      - name: Move related issues to In Progress
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.PROJECT_ACCESS_TOKEN }}
          script: |
            // PR 내용 가져오기
            const prNumber = context.payload.pull_request.number;
            const prBody = context.payload.pull_request.body;
            
            // PR 설명에서 "closes #숫자" 패턴 찾기 (여러 이슈 번호 지원)
            // 1. 이슈 키워드(closes, fixes 등) 뒤에 오는 첫 번째 이슈 번호 찾기
            const closesPattern = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)/gi;
            const issueNumbers = [];
            let match;
            
            while ((match = closesPattern.exec(prBody)) !== null) {
              issueNumbers.push(parseInt(match[1]));
            }
            
            // 2. PR 설명에서 모든 #숫자 형식 찾기
            const hashNumberPattern = /#(\d+)/g;
            const allMatches = [...prBody.matchAll(hashNumberPattern)];
            
            // 이미 찾은 이슈 번호 외의 새로운 이슈 번호만 추가
            for (const match of allMatches) {
              const num = parseInt(match[1]);
              if (!issueNumbers.includes(num)) {
                issueNumbers.push(num);
              }
            }
            
            console.log(`Found issue numbers: ${issueNumbers.join(', ')}`);
            
            if (issueNumbers.length === 0) {
              console.log('No related issues found in PR description');
              return;
            }
            
            // 1. 프로젝트 아이템 조회
            const findItemsQuery = `
              query {
                user(login: "${{ github.repository_owner }}") {
                  projectV2(number: 4) {
                    items(first: 100) {
                      nodes {
                        id
                        content {
                          ... on Issue {
                            id
                            number
                          }
                        }
                        fieldValues(first: 10) {
                          nodes {
                            ... on ProjectV2ItemFieldSingleSelectValue {
                              name
                              field {
                                ... on ProjectV2SingleSelectField {
                                  id
                                  name
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            `;
            
            const result = await github.graphql(findItemsQuery);
            const projectItems = result.user.projectV2.items.nodes;
            
            // 2. 각 이슈에 대해 처리
            for (const issueNumber of issueNumbers) {
              // 2.1 프로젝트에서 해당 이슈를 찾기
              const projectItem = projectItems.find(item => 
                item.content && 
                item.content.number === issueNumber
              );
              
              if (!projectItem) {
                console.log(`Issue #${issueNumber} not found in project`);
                continue;
              }
              
              // 2.2 현재 상태가 Todo인 경우에만 In Progress로 변경
              const statusField = projectItem.fieldValues.nodes.find(
                node => node.field && node.field.name === 'Status'
              );
              
              if (!statusField || statusField.name !== 'Todo') {
                console.log(`Issue #${issueNumber} is not in Todo status`);
                continue;
              }
              
              // 2.3 상태를 In Progress로 변경
              const updateStatusQuery = `
                mutation {
                  updateProjectV2ItemFieldValue(input: {
                    projectId: "PVT_kwHOCNlLx84A3hwL",
                    itemId: "${projectItem.id}",
                    fieldId: "PVTSSF_lAHOCNlLx84A3hwLzgspl6M",
                    value: {
                      singleSelectOptionId: "47fc9ee4"
                    }
                  }) {
                    projectV2Item {
                      id
                    }
                  }
                }
              `;
              
              await github.graphql(updateStatusQuery);
              console.log(`Successfully moved issue #${issueNumber} to In Progress`);
            } 
  • dev-helper/README.md 에 구체적인 내용도 함께 작성해줌
  • dev-helper/create-pr.sh 에 PR 자동 생성 해주는 코드 들어 있음

  1. Github CLI 설치 및 gh auth login

1) GitHub CLI 설치

# macOS (Homebrew 사용)
brew install gh

# Windows (Scoop 사용)
scoop install gh

# Linux (Debian/Ubuntu)
sudo apt install gh

2) GitHub CLI에 로그인

gh auth login
  • 프롬프트에 따라 브라우저에서 인증을 완료하거나 토큰을 입력합니다.

주의 : default branch 를 dev로 바꿔두어야 dev에 PR 올림과 동시에 해당 이슈들이 닫힘.


📌 주의사항!

  • PR template에 closes #이슈번호 적을때는
    ❌ closes #1 #2 #3

    closes #1
    closes #2
    closes #3

이렇게 적어야 PR merge시 이슈들이 전부 제대로 닫힌다.

  • merge 시 이슈 닫히도록 만드는건
    default branch로 merge할 때만 가능하다.
profile
무서운 속도로 흡수하는 스펀지 개발자 🧽

0개의 댓글

Powered by GraphCDN, the GraphQL CDN