project-auto-wizard 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -21
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +1 -1
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/scripts/changelog_manager.py +84 -31
- package/payload/scripts/version_manager.py +2 -2
- package/payload/version.yml.template +4 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +30 -10
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +37 -12
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +3 -6
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +0 -12
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +27 -6
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +3 -7
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +7 -34
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +2 -25
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +2 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +5 -30
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +0 -4
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +7 -51
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +5 -29
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +9 -29
- package/payload/workflows/go/PROJECT-GO-CI.yaml +5 -28
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +19 -99
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +18 -89
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +11 -41
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +9 -45
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +1 -27
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +14 -97
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +14 -92
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +11 -40
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +9 -43
- package/payload/workflows/spring/{nexus/PROJECT-SPRING-NEXUS-CI.yml → PROJECT-SPRING-CI.yml} +2 -25
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +19 -63
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +19 -59
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +23 -86
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +20 -80
- package/src/cli/args.js +8 -15
- package/src/cli/help.js +2 -3
- package/src/commands/doctor.js +9 -8
- package/src/commands/dry-run.js +1 -1
- package/src/commands/full.js +9 -10
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +34 -46
- package/src/commands/purge.js +2 -2
- package/src/commands/status.js +5 -6
- package/src/commands/uninstall.js +3 -4
- package/src/context.js +5 -6
- package/src/core/assets.js +2 -2
- package/src/core/baseline.js +1 -1
- package/src/core/branches.js +3 -3
- package/src/core/breaking-check.js +1 -1
- package/src/core/breaking.js +1 -1
- package/src/core/copy/flutter-app.js +2 -2
- package/src/core/copy/gitignore.js +8 -8
- package/src/core/copy/workflows.js +24 -69
- package/src/core/deploy-style.js +2 -2
- package/src/core/detect-fs.js +11 -11
- package/src/core/detect.js +7 -7
- package/src/core/flutter-options.js +1 -1
- package/src/core/installed-stores.js +1 -1
- package/src/core/logger.js +1 -1
- package/src/core/paths-resolve.js +1 -1
- package/src/core/removal-plan.js +4 -4
- package/src/core/verify.js +4 -4
- package/src/core/version-yml.js +27 -34
- package/src/core/wizard-env.js +4 -4
- package/src/index.js +14 -14
- package/src/ui/banner.js +2 -2
- package/src/ui/env-plan.js +10 -16
- package/src/ui/prompts.js +9 -14
- package/src/ui/status-cards.js +10 -12
- package/src/ui/summary.js +13 -13
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +0 -210
- package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +0 -92
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +0 -70
- package/src/core/options-ask.js +0 -102
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# project-auto-wizard
|
|
2
2
|
|
|
3
|
-
> **One command DevOps** — `npx` 한 줄로 어떤 프로젝트든 GitHub-native AI
|
|
3
|
+
> **One command DevOps** — `npx` 한 줄로 어떤 프로젝트든 GitHub-native 릴리스 자동화(GitHub Copilot AI 요약은 선택)를 설치하는 마법사
|
|
4
4
|
|
|
5
5
|
- 새 프로젝트를 시작하면 코드를 작성하기 전부터 버전 관리, 배포 자동화, 변경 기록 작성처럼 먼저 정해야 할 일이 많다. project-auto-wizard는 이 준비 과정을 명령 한 번으로 구성하고, 프로젝트 상태 관리가 GitHub 안에서 자동으로 동작하도록 해 개발자가 기능 구현에 집중하도록 돕는 오픈소스 도구
|
|
6
6
|
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
npx project-auto-wizard
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
<!-- TODO: 30초 데모 GIF (docs/assets/demo.gif) -->
|
|
12
|
-
<!-- TODO: 3분 데모 YouTube 링크 -->
|
|
13
11
|
|
|
14
12
|
[](https://github.com/Twin-Fang/project-auto-wizard/actions/workflows/CI.yaml)
|
|
15
13
|
[](https://www.npmjs.com/package/project-auto-wizard)
|
|
@@ -17,7 +15,7 @@ npx project-auto-wizard
|
|
|
17
15
|
[](package.json)
|
|
18
16
|
|
|
19
17
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
20
|
-
## 최신 버전 : v0.
|
|
18
|
+
## 최신 버전 : v0.12.0 (2026-09-24)
|
|
21
19
|
|
|
22
20
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
23
21
|
|
|
@@ -38,7 +36,7 @@ npx project-auto-wizard --mode full --force --type spring,react # CI에서 비
|
|
|
38
36
|
| 축 | 내용 |
|
|
39
37
|
|---|---|
|
|
40
38
|
| ① **npx 마법사** | 마커 파일로 프로젝트 타입 자동 감지 — **10타입 + 멀티타입 + 모노레포 경로**까지. 질문은 최소한만 |
|
|
41
|
-
| ② **GitHub-native
|
|
39
|
+
| ② **GitHub-native Release Automation** | 릴리스 PR을 열면: 버전 확정 → **릴리스 노트 작성**(기본은 규칙 기반, GitHub Copilot AI는 선택) → CHANGELOG 갱신 → automerge → tag + GitHub Release. **API 키 0개** |
|
|
42
40
|
| ③ **타입별 CI/CD 워크플로우** | Spring(무중단 배포 포함)·Flutter(스토어 배포)·React·Next·Python·Go 등 타입에 맞는 GitHub Actions 자동 배치 |
|
|
43
41
|
|
|
44
42
|
### 지원 프로젝트 타입
|
|
@@ -67,7 +65,7 @@ flutter.APP_ARTIFACT_NAME:
|
|
|
67
65
|
`spring`/`flutter`는 아래처럼 단일 CI 이상으로 깊게 구성되어 있습니다:
|
|
68
66
|
|
|
69
67
|
- **flutter**: Android(Firebase/Playstore/Selfhosted/TestAPK 배포), iOS(TestFlight/Test-TestFlight), CI, Lab 트리거까지 8종 — 스토어 배포(Play Store·TestFlight)는 고른 플랫폼만 설치되고 fastlane 파일도 함께 생성됩니다. 환경변수 방식·배포 모드 등 자세한 내용은 아래 "Flutter 워크플로우 상세"를 참고하세요.
|
|
70
|
-
- **spring**: 서버 배포 1종(단일 서버 / 무중단 Nginx / 무중단 Traefik / 배포 안 함 중 택1) + PR 프리뷰 +
|
|
68
|
+
- **spring**: 서버 배포 1종(단일 서버 / 무중단 Nginx / 무중단 Traefik / 배포 안 함 중 택1) + PR 프리뷰 + CI
|
|
71
69
|
- 서버 배포 워크플로우는 **서로 대체재**라 하나만 설치합니다. 대화형에서 고르면 그것만 깔리고 **`push` 트리거까지 켜진 채로** 설치됩니다. 비대화형은 `--deploy-style simple|nginx|traefik|none` (기본: `simple`).
|
|
72
70
|
- 고른 방식은 `version.yml`에 기록되므로 다시 실행해도 묻지 않습니다. 방식을 바꾸면 **이전 워크플로우를 마법사가 정리합니다** — 손대지 않은 파일은 삭제하고, 수정한 파일은 `.bak`으로 옮겨 내용을 보존합니다. 남겨두면 배포가 두 번 돕니다.
|
|
73
71
|
- PR 프리뷰는 배포 방식과 무관한 별개 축이라 선택과 관계없이 함께 설치됩니다 (단, `none`을 고르면 PR 프리뷰도 함께 제외됩니다 — 서버 배포 자체를 하지 않는 프로젝트를 위한 선택지입니다).
|
|
@@ -144,7 +142,7 @@ flutter.APP_ARTIFACT_NAME:
|
|
|
144
142
|
**모노레포와 `ci-gate`**
|
|
145
143
|
|
|
146
144
|
- `--paths flutter=app`처럼 Flutter 하위 폴더를 지정하면 모든 Flutter 워크플로우가 그 폴더(`FLUTTER_PROJECT_DIR`)를 기준으로 동작하고, main push로 도는 배포 워크플로우(`PLAYSTORE`·`IOS-TESTFLIGHT`·`SELFHOSTED`·`FIREBASE`)는 `app/**`가 바뀔 때만 실행됩니다(Spring 등 다른 타입과 같은 `paths` 필터).
|
|
147
|
-
- CI 워크플로우(Flutter·Go·Next·Python·React·Spring
|
|
145
|
+
- CI 워크플로우(Flutter·Go·Next·Python·React·Spring CI)는 `push`·`pull_request`에서 항상 실행되고, 첫 job `changes`가 변경 파일을 판별해 나머지 job을 건너뜁니다(건너뛴 job은 Success). 마지막 job `ci-gate`는 항상 실행되어 필수 job이 모두 success 또는 skipped이면 통과하고 failure·cancelled가 하나라도 있으면 실패합니다.
|
|
148
146
|
- 브랜치 보호 규칙의 required status check에는 개별 job이 아니라 **`CI Gate`(`ci-gate`) 하나만** 등록하세요. 경로 필터로 워크플로우 자체를 건너뛰면 required check가 Pending에 머물러 머지가 막히지만, job을 건너뛰는 방식은 그렇지 않기 때문입니다.
|
|
149
147
|
- 워크플로우 `paths` 필터는 태그 push에 적용되지 않습니다(GitHub 문서).
|
|
150
148
|
|
|
@@ -183,18 +181,23 @@ npx project-auto-wizard --mode uninstall --force # 워크플로우·스
|
|
|
183
181
|
npx project-auto-wizard --mode uninstall --force --purge-readme --purge-gitignore --purge-version # 완전 삭제
|
|
184
182
|
```
|
|
185
183
|
|
|
186
|
-
##
|
|
184
|
+
## 요약 엔진 체인
|
|
187
185
|
|
|
188
|
-
릴리스 노트는 3단 엔진 체인으로 생성됩니다.
|
|
186
|
+
릴리스 노트는 3단 엔진 체인으로 생성됩니다. **어떤 단계가 실패해도 릴리스는 절대 막히지 않습니다.**
|
|
187
|
+
|
|
188
|
+
> GitHub Models는 2026-07-30에 종료되어 더 이상 사용하지 않습니다.
|
|
189
189
|
|
|
190
190
|
```mermaid
|
|
191
191
|
flowchart LR
|
|
192
|
-
B["사용자 지정 AI<br/>(AI_API_KEY)"] -->|"
|
|
193
|
-
C -->|"
|
|
192
|
+
B["사용자 지정 AI<br/>(AI_API_KEY + AI_API_BASE_URL + AI_MODEL)"] -->|"미설정/실패"| C["GitHub Copilot CLI<br/>(선택, GITHUB_TOKEN)"]
|
|
193
|
+
C -->|"꺼짐/사용 불가/실패"| D["규칙 기반 fallback<br/>(항상 성공)"]
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
- 기본값은
|
|
197
|
-
-
|
|
196
|
+
- 기본값은 **규칙 기반 요약**입니다. 설치 마법사에서 Copilot을 켜면(`--copilot`, `version.yml`의 `copilot_ai: true`) Actions의 `GITHUB_TOKEN` + `permissions: copilot-requests: write`로 Copilot CLI가 요약을 생성합니다 — 별도 API 키는 필요 없습니다.
|
|
197
|
+
- **Copilot은 GitHub Copilot AI Credits를 소비합니다.** 개인 저장소는 저장소 소유자의 Copilot 좌석에, 조직 저장소는 조직에 과금되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책을 켜야 합니다. 사용할 수 없으면 자동으로 규칙 기반 요약으로 전환됩니다. PR에 푸시할 때마다 요약이 새로 생성되므로 그만큼 크레딧이 소비됩니다.
|
|
198
|
+
- Copilot 모델은 저비용 소형 모델(`claude-haiku-4.5`)로 고정되어 있고, 저장소 변수 `COPILOT_MODEL`로 바꿀 수 있습니다.
|
|
199
|
+
- GitHub은 Copilot CLI를 `run` 스텝에서 직접 호출하기보다 Agentic Workflows를 쓰라고 권고하지만, 이 프로젝트는 직접 호출을 택했습니다. 프롬프트 입력이 PR 제목·커밋 메시지·`git diff --stat`뿐이고, 빈 임시 디렉터리에서 shell/write/url 도구와 내장 MCP를 모두 막은 텍스트 생성 전용으로 호출하며, 포크 PR은 기존 가드로 건너뛰어 프롬프트 인젝션 위험을 낮췄기 때문입니다.
|
|
200
|
+
- `AI_API_KEY`(**Secret**)와 `AI_API_BASE_URL`·`AI_MODEL`(**Variables**)을 **모두** 설정하면 OpenAI-호환 엔드포인트(Groq, Gemini 호환 모드, Ollama 등)를 최우선으로 사용합니다. 셋 중 하나라도 없으면 이 단계는 건너뜁니다.
|
|
198
201
|
- 규칙 fallback 3단: 프로젝트 컨벤션 → Conventional Commits → 무형식 bullet. 커밋 컨벤션이 없어도 동작.
|
|
199
202
|
|
|
200
203
|
## 릴리스 흐름
|
|
@@ -204,7 +207,7 @@ flowchart LR
|
|
|
204
207
|
subgraph pr-flow ["pr-flow (기본)"]
|
|
205
208
|
D1[develop push] --> PR[develop→main 릴리스 PR]
|
|
206
209
|
PR --> V["버전 확정 (patch+1)"]
|
|
207
|
-
V --> AI[
|
|
210
|
+
V --> AI[릴리스 노트]
|
|
208
211
|
AI --> CL[CHANGELOG.json/md 갱신]
|
|
209
212
|
CL --> AM[automerge]
|
|
210
213
|
AM --> TAG["tag vX.Y.Z + GitHub Release"]
|
|
@@ -233,9 +236,8 @@ npx project-auto-wizard [옵션]
|
|
|
233
236
|
--flutter-store CSV Flutter 스토어 배포 대상: android,ios,none (미지정 시 둘 다 설치)
|
|
234
237
|
--android-deploy-mode M Play Store 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
235
238
|
--ios-deploy-mode M iOS 배포 모드: store_only | store_prepare | store_submit (기본: store_only)
|
|
236
|
-
--nexus 라이브러리 publish 워크플로우 포함 (Nexus + GitHub Packages)
|
|
237
|
-
--secret-backup Secret 서버 백업 워크플로우 포함
|
|
238
239
|
--semver-auto 커밋 타입 기반 자동 major/minor/patch 승격 (기본: 사용함, --no-semver-auto로 끔)
|
|
240
|
+
--copilot Copilot으로 AI 요약 생성 (기본: 사용 안 함, GitHub Copilot AI Credits 소비, --no-copilot으로 끔)
|
|
239
241
|
--dry-run 실제 파일 변경 없이 무엇이 바뀔지만 미리 보여줌
|
|
240
242
|
--purge-readme --mode uninstall --force 시 README.md 버전 섹션도 제거
|
|
241
243
|
--purge-gitignore --mode uninstall --force 시 .gitignore 자동 추가 항목도 제거
|
|
@@ -270,9 +272,10 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
|
|
|
270
272
|
secret이 없어도 폴백이 자동으로 이어받아 태그·Release까지 진행됩니다 — 실제 병합 후 최대 ~20초 정도 더 걸릴 뿐입니다.
|
|
271
273
|
속도를 더 원한다면 PAT을 등록할 수 있습니다 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 (scopes: repo, workflow).
|
|
272
274
|
등록: 레포 Settings → Secrets and variables → Actions → New repository secret · 이름은 WORKFLOW_PAT
|
|
273
|
-
[i]
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
[i] Copilot AI 요약 — AI 릴리스 노트 생성(선택)
|
|
276
|
+
기본은 꺼져 있습니다 (version.yml의 copilot_ai: false).
|
|
277
|
+
켜면 GitHub Copilot AI Credits가 소비됩니다 — 조직은 'Allow use of Copilot CLI billed to the organization' 정책이 필요합니다.
|
|
278
|
+
꺼져 있거나 사용할 수 없으면 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
|
|
276
279
|
|
|
277
280
|
✓ 문제를 찾지 못했습니다.
|
|
278
281
|
```
|
|
@@ -296,7 +299,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
|
|
|
296
299
|
|
|
297
300
|
### 자체 AI PR 요약봇
|
|
298
301
|
|
|
299
|
-
상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때
|
|
302
|
+
상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때 요약 엔진 체인(기본은 규칙 기반, Copilot은 선택)으로 요약 코멘트를 자동으로 답니다.
|
|
300
303
|
|
|
301
304
|
기본 설치(pr-flow) 기준으로 일상적인 기능 PR은 `develop`을 대상으로 열리므로, 이 봇은 develop→main 릴리스 PR에서만 실제로 동작합니다 — 해당 PR에서는 `AUTO-CHANGELOG-CONTROL`이 이미 같은 엔진으로 체인지로그 요약을 생성하므로, 이 봇은 그 요약을 PR 코멘트 형태로도 남겨주는 보조 역할입니다. 릴리스 브랜치 = 개발 브랜치인 trunk-based 모드에서는 모든 PR이 곧 릴리스 대상 브랜치를 향하므로 매 PR마다 동작합니다.
|
|
302
305
|
|
|
@@ -308,7 +311,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
|
|
|
308
311
|
|---|---|
|
|
309
312
|
| **`WORKFLOW_PAT` secret** (선택 — 속도 최적화용) | 없어도 `GITHUB_TOKEN` 폴백이 automerge부터 Release 발행까지 자동으로 이어갑니다(실제 병합 후 최대 ~20초 추가). 더 빠르게 하고 싶다면 Settings → Secrets → Actions에 `WORKFLOW_PAT` (scopes: `repo`, `workflow`) 등록 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 |
|
|
310
313
|
| **Workflow permissions** | Settings → Actions → Workflow permissions: **Read and write** |
|
|
311
|
-
| **
|
|
314
|
+
| **Copilot AI 요약** (선택) | 기본 꺼짐. 켜려면 마법사에서 선택하거나 `version.yml`의 `copilot_ai`를 `true`로 — AI Credits가 소비되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책이 필요합니다. 사용할 수 없으면 자동으로 규칙 fallback |
|
|
312
315
|
|
|
313
316
|
## 설계 원칙
|
|
314
317
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-auto-wizard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One command DevOps: npx wizard that installs GitHub-native
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "One command DevOps: npx wizard that installs GitHub-native Release Automation (optional GitHub Copilot AI summaries) into any project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{
|
|
19
19
|
"severity": "warning",
|
|
20
20
|
"title": "모노레포 CI required check는 ci-gate 하나만 등록",
|
|
21
|
-
"message": "CI 워크플로우(Flutter·Go·Next·Python·React·Spring
|
|
21
|
+
"message": "CI 워크플로우(Flutter·Go·Next·Python·React·Spring CI)는 항상 실행되고 변경이 없으면 job을 건너뜁니다. required check로 쓰려면 개별 job이 아닌 CI Gate(ci-gate) 하나만 등록하세요."
|
|
22
22
|
}
|
|
23
23
|
]
|
|
24
24
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -27,7 +27,9 @@ import html
|
|
|
27
27
|
import json
|
|
28
28
|
import os
|
|
29
29
|
import re
|
|
30
|
+
import subprocess
|
|
30
31
|
import sys
|
|
32
|
+
import tempfile
|
|
31
33
|
import traceback
|
|
32
34
|
import urllib.error
|
|
33
35
|
import urllib.request
|
|
@@ -123,7 +125,7 @@ def _parse_markdown_sections(md_content: str) -> dict:
|
|
|
123
125
|
"""
|
|
124
126
|
섹션 파서: `### 카테고리` 헤딩 + `- 항목` 불릿 형식.
|
|
125
127
|
|
|
126
|
-
AI 엔진 체인(사용자 지정 API →
|
|
128
|
+
AI 엔진 체인(사용자 지정 API → Copilot CLI)과 규칙 기반 폴백이
|
|
127
129
|
동일하게 생성하는 형식이므로 1순위로 시도한다.
|
|
128
130
|
"""
|
|
129
131
|
detected: dict[str, dict] = {}
|
|
@@ -389,21 +391,19 @@ def _ai_assisted_minor_upgrade(unclassified_lines: list[str]) -> bool:
|
|
|
389
391
|
return False
|
|
390
392
|
prompt = _BUMP_AI_PROMPT_PREFIX + "\n".join(f"- {line}" for line in unclassified_lines)
|
|
391
393
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
(ai_api_key, os.environ.get('AI_API_BASE_URL') or _AI_DEFAULT_BASE_URL, os.environ.get('AI_MODEL') or _AI_DEFAULT_MODEL),
|
|
396
|
-
(github_token, _AI_DEFAULT_BASE_URL, _AI_DEFAULT_MODEL),
|
|
397
|
-
]
|
|
398
|
-
for token, base_url, model in candidates:
|
|
399
|
-
if not token:
|
|
400
|
-
continue
|
|
394
|
+
settings = _user_api_settings()
|
|
395
|
+
if settings:
|
|
396
|
+
api_key, base_url, model = settings
|
|
401
397
|
try:
|
|
402
|
-
|
|
403
|
-
return response.strip() == 'MINOR'
|
|
398
|
+
return call_openai_compatible(base_url, api_key, model, prompt).strip() == 'MINOR'
|
|
404
399
|
except Exception as e:
|
|
405
400
|
print(f"[warn] bump AI assist failed: {e}", file=sys.stderr)
|
|
406
|
-
|
|
401
|
+
|
|
402
|
+
if _copilot_enabled():
|
|
403
|
+
try:
|
|
404
|
+
return call_copilot_cli(prompt).strip() == 'MINOR'
|
|
405
|
+
except Exception as e:
|
|
406
|
+
print(f"[warn] bump AI assist (copilot) failed: {e}", file=sys.stderr)
|
|
407
407
|
return False
|
|
408
408
|
|
|
409
409
|
|
|
@@ -464,7 +464,7 @@ def cmd_classify_bump(commits_file: str) -> int:
|
|
|
464
464
|
def cmd_update_from_summary() -> int:
|
|
465
465
|
"""pr_body.md에서 Markdown을 파싱하여 CHANGELOG.json 갱신."""
|
|
466
466
|
version = os.environ.get('VERSION')
|
|
467
|
-
# PROJECT_TYPES(csv)가 유일한 입력 — 단수 PROJECT_TYPE 폴백은 제거됐다
|
|
467
|
+
# PROJECT_TYPES(csv)가 유일한 입력 — 단수 PROJECT_TYPE 폴백은 제거됐다
|
|
468
468
|
project_types_csv = os.environ.get('PROJECT_TYPES', '')
|
|
469
469
|
project_types = [t.strip() for t in project_types_csv.split(',') if t.strip()]
|
|
470
470
|
today = os.environ.get('TODAY')
|
|
@@ -694,8 +694,67 @@ def cmd_export_release_notes(version: str, output_path: str | None) -> int:
|
|
|
694
694
|
|
|
695
695
|
# ------------------------ ai-summary 엔진 체인 ------------------------
|
|
696
696
|
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
_COPILOT_DEFAULT_MODEL = "claude-haiku-4.5"
|
|
698
|
+
_COPILOT_TIMEOUT_SECONDS = 90
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _user_api_settings() -> tuple[str, str, str] | None:
|
|
702
|
+
"""사용자 지정 AI 티어 설정. AI_API_KEY와 AI_API_BASE_URL, AI_MODEL이 모두 있어야 한다.
|
|
703
|
+
|
|
704
|
+
키만 있고 URL·모델이 비어 있으면 그 키를 어디로도 보내지 않고 경고 후 건너뛴다
|
|
705
|
+
(종료된 기본 엔드포인트으로 사용자 키가 흘러가던 문제 방지)."""
|
|
706
|
+
api_key = os.environ.get('AI_API_KEY')
|
|
707
|
+
if not api_key:
|
|
708
|
+
return None
|
|
709
|
+
base_url = os.environ.get('AI_API_BASE_URL')
|
|
710
|
+
model = os.environ.get('AI_MODEL')
|
|
711
|
+
if not base_url or not model:
|
|
712
|
+
print(
|
|
713
|
+
"::warning::AI_API_KEY가 설정됐지만 AI_API_BASE_URL/AI_MODEL이 없어 사용자 API 티어를 건너뜁니다",
|
|
714
|
+
file=sys.stderr,
|
|
715
|
+
)
|
|
716
|
+
return None
|
|
717
|
+
return api_key, base_url, model
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _copilot_enabled() -> bool:
|
|
721
|
+
"""version.yml의 copilot_ai가 켜져 있고(워크플로우가 COPILOT_AI로 전달) 토큰이 있을 때만 True."""
|
|
722
|
+
return os.environ.get('COPILOT_AI', '').strip().lower() == 'true' and bool(os.environ.get('GITHUB_TOKEN'))
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def call_copilot_cli(prompt: str) -> str:
|
|
726
|
+
"""Copilot CLI를 텍스트 생성 전용으로 호출해 응답 텍스트를 반환.
|
|
727
|
+
|
|
728
|
+
프롬프트에 필요한 정보가 이미 다 들어 있으므로 에이전트 기능은 전부 막는다:
|
|
729
|
+
빈 임시 디렉터리에서 실행하고, shell/write/url 도구를 거부하며, 내장 MCP와
|
|
730
|
+
커스텀 지침 로딩을 끈다.
|
|
731
|
+
실패(비정상 종료·타임아웃·CLI 없음)는 예외로 올려 호출부가 fallback한다."""
|
|
732
|
+
model = os.environ.get('COPILOT_MODEL') or _COPILOT_DEFAULT_MODEL
|
|
733
|
+
with tempfile.TemporaryDirectory() as workdir:
|
|
734
|
+
result = subprocess.run(
|
|
735
|
+
[
|
|
736
|
+
'copilot', '-p', prompt, '-s',
|
|
737
|
+
'--no-ask-user', '--no-color', '--no-custom-instructions', '--disable-builtin-mcps',
|
|
738
|
+
'--deny-tool=shell', '--deny-tool=write', '--deny-tool=url',
|
|
739
|
+
'--model', model,
|
|
740
|
+
],
|
|
741
|
+
cwd=workdir, stdin=subprocess.DEVNULL,
|
|
742
|
+
capture_output=True, text=True, timeout=_COPILOT_TIMEOUT_SECONDS,
|
|
743
|
+
)
|
|
744
|
+
if result.returncode != 0:
|
|
745
|
+
raise RuntimeError(f"copilot exited with {result.returncode}: {result.stderr.strip()[:200]}")
|
|
746
|
+
return result.stdout
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _is_valid_copilot_summary(text: str) -> bool:
|
|
750
|
+
"""프롬프트가 요구한 Markdown 형식인지 최소한만 검사한다.
|
|
751
|
+
|
|
752
|
+
섹션 헤딩('### ')이 하나도 없거나 코드펜스로 시작하는(통째로 감싼) 응답은 릴리스 노트로
|
|
753
|
+
쓰지 않는다."""
|
|
754
|
+
stripped = text.strip()
|
|
755
|
+
if not stripped or stripped.startswith('```'):
|
|
756
|
+
return False
|
|
757
|
+
return any(line.startswith('### ') for line in stripped.splitlines())
|
|
699
758
|
|
|
700
759
|
|
|
701
760
|
def _build_ai_prompt(commit_lines: list[str], pr_title: str | None, version: str, diff_stat: str | None = None) -> str:
|
|
@@ -760,16 +819,13 @@ def cmd_ai_summary(commits_file: str, version: str, output_path: str, pr_title:
|
|
|
760
819
|
except Exception:
|
|
761
820
|
diff_stat = None
|
|
762
821
|
|
|
763
|
-
ai_api_key = os.environ.get('AI_API_KEY')
|
|
764
|
-
ai_base_url = os.environ.get('AI_API_BASE_URL') or _AI_DEFAULT_BASE_URL
|
|
765
|
-
ai_model = os.environ.get('AI_MODEL') or _AI_DEFAULT_MODEL
|
|
766
|
-
github_token = os.environ.get('GITHUB_TOKEN')
|
|
767
|
-
|
|
768
822
|
engine = None
|
|
769
823
|
summary_text = None
|
|
770
824
|
prompt = _build_ai_prompt(commit_lines, pr_title, version, diff_stat)
|
|
771
825
|
|
|
772
|
-
|
|
826
|
+
settings = _user_api_settings()
|
|
827
|
+
if settings:
|
|
828
|
+
ai_api_key, ai_base_url, ai_model = settings
|
|
773
829
|
try:
|
|
774
830
|
candidate = call_openai_compatible(ai_base_url, ai_api_key, ai_model, prompt)
|
|
775
831
|
if candidate and candidate.strip():
|
|
@@ -780,19 +836,16 @@ def cmd_ai_summary(commits_file: str, version: str, output_path: str, pr_title:
|
|
|
780
836
|
except Exception as e:
|
|
781
837
|
print(f"[warn] user-api failed: {e}", file=sys.stderr)
|
|
782
838
|
|
|
783
|
-
if summary_text is None and
|
|
839
|
+
if summary_text is None and _copilot_enabled():
|
|
784
840
|
try:
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
# (커스텀 모델명은 models.github.ai에서 404).
|
|
788
|
-
candidate = call_openai_compatible(_AI_DEFAULT_BASE_URL, github_token, _AI_DEFAULT_MODEL, prompt)
|
|
789
|
-
if candidate and candidate.strip():
|
|
841
|
+
candidate = call_copilot_cli(prompt)
|
|
842
|
+
if _is_valid_copilot_summary(candidate):
|
|
790
843
|
summary_text = candidate
|
|
791
|
-
engine = "
|
|
844
|
+
engine = "copilot"
|
|
792
845
|
else:
|
|
793
|
-
print("[warn]
|
|
846
|
+
print("[warn] copilot failed: empty or not in the requested Markdown format", file=sys.stderr)
|
|
794
847
|
except Exception as e:
|
|
795
|
-
print(f"[warn]
|
|
848
|
+
print(f"[warn] copilot failed: {e}", file=sys.stderr)
|
|
796
849
|
|
|
797
850
|
if summary_text is None:
|
|
798
851
|
classified = classify_commits(commit_lines)
|
|
@@ -160,7 +160,7 @@ def get_project_types_csv():
|
|
|
160
160
|
# Inline array form: project_types: ["a", "b"] # trailing comment allowed
|
|
161
161
|
# The template always appends "# first entry is primary", so anchoring at
|
|
162
162
|
# end-of-line made this branch never match — every install silently fell
|
|
163
|
-
# through to the singular key instead
|
|
163
|
+
# through to the singular key instead.
|
|
164
164
|
m = re.search(r'^project_types:[ \t]*\[(.*?)\][ \t]*(?:#.*)?$', text, re.MULTILINE)
|
|
165
165
|
if m:
|
|
166
166
|
inner = m.group(1)
|
|
@@ -435,7 +435,7 @@ def sync_all_project_files(new_version):
|
|
|
435
435
|
types = get_project_types_csv()
|
|
436
436
|
if not types:
|
|
437
437
|
# No silent fallback: an unreadable project_types used to degrade to
|
|
438
|
-
# "basic" and skip every sync without a word
|
|
438
|
+
# "basic" and skip every sync without a word.
|
|
439
439
|
raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync project files")
|
|
440
440
|
for t in types:
|
|
441
441
|
sync_for_type(t, new_version, get_version_code)
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# (default) — feat: commits bump minor, a `!` breaking marker on any commit
|
|
7
7
|
# type bumps major. Set semver_auto: false to always patch-bump instead
|
|
8
8
|
# (then edit major/minor manually, e.g., 1.0.5 -> 1.1.0 or 2.0.0).
|
|
9
|
+
# - copilot_ai: opt-in (default false) — true lets the release/PR summary
|
|
10
|
+
# workflows call GitHub Copilot CLI, which consumes GitHub Copilot AI Credits.
|
|
11
|
+
# Falls back to rule-based summaries whenever Copilot is off or unavailable.
|
|
9
12
|
# ===
|
|
10
13
|
# Supported project types: spring, flutter, next, react, react-native,
|
|
11
14
|
# react-native-expo, node, python, basic, go
|
|
@@ -44,9 +47,8 @@ metadata:
|
|
|
44
47
|
develop: "{{DEVELOP_BRANCH}}"
|
|
45
48
|
mode: "{{BRANCH_MODE}}" # pr-flow | trunk-based
|
|
46
49
|
options:
|
|
47
|
-
nexus: {{OPT_NEXUS}}
|
|
48
|
-
secret_backup: {{OPT_SECRET_BACKUP}}
|
|
49
50
|
semver_auto: {{OPT_SEMVER_AUTO}}
|
|
51
|
+
copilot_ai: {{OPT_COPILOT_AI}}
|
|
50
52
|
deploy_style: "{{OPT_DEPLOY_STYLE}}" # simple | nginx | traefik | none (서버 배포 워크플로우)
|
|
51
53
|
{{FLUTTER_OPTIONS}}
|
|
52
54
|
{{DEPLOY}}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# ===================================================================
|
|
3
2
|
# PROJECT-COMMON-AI-PR-SUMMARY.yaml
|
|
4
3
|
# Self-hosted AI PR summary bot — no third-party service required.
|
|
5
|
-
# ===================================================================
|
|
6
4
|
#
|
|
7
|
-
# Posts
|
|
8
|
-
#
|
|
9
|
-
#
|
|
5
|
+
# Posts a summary comment on PRs targeting the default branch, using the
|
|
6
|
+
# same engine chain as the release changelog (user API key -> Copilot CLI,
|
|
7
|
+
# opt-in via version.yml copilot_ai -> rule-based fallback).
|
|
10
8
|
# The prompt includes commit subjects plus a capped `git diff --stat`.
|
|
11
9
|
#
|
|
12
10
|
# On release PRs (head branch == {{DEVELOP_BRANCH}}) this job is skipped.
|
|
@@ -18,7 +16,6 @@
|
|
|
18
16
|
# that commit is pushed with GITHUB_TOKEN, which GitHub does not use
|
|
19
17
|
# to retrigger pull_request events, so there would be no later run to
|
|
20
18
|
# correct it.
|
|
21
|
-
# ===================================================================
|
|
22
19
|
|
|
23
20
|
name: PROJECT-AI-PR-SUMMARY
|
|
24
21
|
|
|
@@ -30,7 +27,11 @@ on:
|
|
|
30
27
|
permissions:
|
|
31
28
|
contents: read
|
|
32
29
|
pull-requests: write
|
|
33
|
-
|
|
30
|
+
copilot-requests: write
|
|
31
|
+
|
|
32
|
+
concurrency:
|
|
33
|
+
group: ai-pr-summary-${{ github.event.pull_request.number }}
|
|
34
|
+
cancel-in-progress: true
|
|
34
35
|
|
|
35
36
|
jobs:
|
|
36
37
|
ai-pr-summary:
|
|
@@ -46,6 +47,20 @@ jobs:
|
|
|
46
47
|
fetch-depth: 0
|
|
47
48
|
ref: ${{ github.event.pull_request.head.ref }}
|
|
48
49
|
|
|
50
|
+
- name: Read copilot_ai option from version.yml
|
|
51
|
+
id: copilot_options
|
|
52
|
+
run: |
|
|
53
|
+
# scoped to the metadata: -> template: -> options: chain so an
|
|
54
|
+
# unrelated "copilot_ai:" key can never hijack the value
|
|
55
|
+
COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
|
|
56
|
+
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
57
|
+
echo "copilot_ai option: $COPILOT_AI"
|
|
58
|
+
|
|
59
|
+
- name: Install Copilot CLI
|
|
60
|
+
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
61
|
+
continue-on-error: true
|
|
62
|
+
run: npm install -g @github/copilot@1.0.88
|
|
63
|
+
|
|
49
64
|
- name: Generate and post AI summary
|
|
50
65
|
env:
|
|
51
66
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
@@ -53,6 +68,8 @@ jobs:
|
|
|
53
68
|
AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
|
|
54
69
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
55
70
|
GITHUB_TOKEN: ${{ github.token }}
|
|
71
|
+
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
72
|
+
COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
|
|
56
73
|
run: |
|
|
57
74
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
58
75
|
VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
@@ -68,17 +85,20 @@ jobs:
|
|
|
68
85
|
fi
|
|
69
86
|
rm -f _full_diff_stat.txt
|
|
70
87
|
|
|
71
|
-
python3 .github/scripts/changelog_manager.py ai-summary \
|
|
88
|
+
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
72
89
|
--commits-file commits.txt \
|
|
73
90
|
--version "$VERSION" \
|
|
74
91
|
--output summary.md \
|
|
75
92
|
--pr-title "$PR_TITLE" \
|
|
76
|
-
--diff-stat-file diff_stat.txt
|
|
93
|
+
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
94
|
+
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
77
95
|
|
|
78
96
|
{
|
|
79
|
-
echo "
|
|
97
|
+
echo "📋 **PR Summary (project-auto-wizard)**"
|
|
80
98
|
echo ""
|
|
81
99
|
cat summary.md
|
|
100
|
+
echo ""
|
|
101
|
+
echo "<sub>engine: ${ENGINE}</sub>"
|
|
82
102
|
} > comment_body.md
|
|
83
103
|
|
|
84
104
|
python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# ===================================================================
|
|
3
2
|
# PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml
|
|
4
3
|
# Release PR pipeline: version confirm + AI changelog + automerge
|
|
5
|
-
# ===================================================================
|
|
6
4
|
#
|
|
7
5
|
# Runs on release PRs ({{DEVELOP_BRANCH}} -> {{MAIN_BRANCH}}) and:
|
|
8
6
|
# 1. Confirms the release version and syncs every version file:
|
|
@@ -12,8 +10,8 @@
|
|
|
12
10
|
# commits) and bumps accordingly.
|
|
13
11
|
# - Otherwise: always patch+1 (legacy behavior).
|
|
14
12
|
# 2. Generates the release summary locally with changelog_manager.py
|
|
15
|
-
# ai-summary (engine chain: user API key ->
|
|
16
|
-
# fallback; always succeeds
|
|
13
|
+
# ai-summary (engine chain: user API key -> Copilot CLI, opt-in via
|
|
14
|
+
# version.yml copilot_ai -> rule-based fallback; always succeeds)
|
|
17
15
|
# - The AI prompt includes commit subjects plus a capped
|
|
18
16
|
# `git diff --stat` (file-level change summary, no diff body)
|
|
19
17
|
# for richer context without token-cost blowup.
|
|
@@ -34,14 +32,15 @@
|
|
|
34
32
|
# - secrets.WORKFLOW_PAT: PAT used for the merge. Without it the merge is
|
|
35
33
|
# performed with GITHUB_TOKEN, and GitHub will NOT trigger downstream
|
|
36
34
|
# {{MAIN_BRANCH}}-push workflows (e.g. RELEASE-PUBLISH) for that merge.
|
|
37
|
-
# - secrets.AI_API_KEY (+ vars.AI_API_BASE_URL / vars.AI_MODEL
|
|
38
|
-
# summary engine. Falls back to
|
|
35
|
+
# - secrets.AI_API_KEY (+ vars.AI_API_BASE_URL / vars.AI_MODEL, all three
|
|
36
|
+
# required together): preferred summary engine. Falls back to Copilot CLI
|
|
37
|
+
# (only when version.yml copilot_ai is true; optional vars.COPILOT_MODEL),
|
|
38
|
+
# then to rule-based.
|
|
39
39
|
#
|
|
40
|
-
#
|
|
40
|
+
# Without WORKFLOW_PAT, a wait-for-merge-and-trigger-release job
|
|
41
41
|
# polls until the automerge actually lands, then wakes RELEASE-PUBLISH via
|
|
42
42
|
# workflow_dispatch — GITHUB_TOKEN can always trigger that event, unlike
|
|
43
43
|
# the push/release events blocked by GitHub's anti-recursion policy.
|
|
44
|
-
# ===================================================================
|
|
45
44
|
|
|
46
45
|
name: PROJECT-AUTO-CHANGELOG-CONTROL
|
|
47
46
|
|
|
@@ -60,7 +59,7 @@ on:
|
|
|
60
59
|
permissions:
|
|
61
60
|
contents: write
|
|
62
61
|
pull-requests: write
|
|
63
|
-
|
|
62
|
+
copilot-requests: write
|
|
64
63
|
|
|
65
64
|
jobs:
|
|
66
65
|
changelog-and-merge:
|
|
@@ -94,6 +93,20 @@ jobs:
|
|
|
94
93
|
echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
|
|
95
94
|
echo "semver_auto option: $SEMVER_AUTO"
|
|
96
95
|
|
|
96
|
+
- name: Read copilot_ai option from version.yml
|
|
97
|
+
id: copilot_options
|
|
98
|
+
run: |
|
|
99
|
+
# scoped to the metadata: -> template: -> options: chain so an
|
|
100
|
+
# unrelated "copilot_ai:" key can never hijack the value
|
|
101
|
+
COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
|
|
102
|
+
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
103
|
+
echo "copilot_ai option: $COPILOT_AI"
|
|
104
|
+
|
|
105
|
+
- name: Install Copilot CLI
|
|
106
|
+
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
107
|
+
continue-on-error: true
|
|
108
|
+
run: npm install -g @github/copilot@1.0.88
|
|
109
|
+
|
|
97
110
|
- name: Collect commits since last release
|
|
98
111
|
id: collect
|
|
99
112
|
run: |
|
|
@@ -149,6 +162,8 @@ jobs:
|
|
|
149
162
|
AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
|
|
150
163
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
151
164
|
GITHUB_TOKEN: ${{ github.token }}
|
|
165
|
+
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
166
|
+
COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
|
|
152
167
|
run: |
|
|
153
168
|
# Idempotency: if the PR head is already this workflow's own
|
|
154
169
|
# version-confirm commit (re-run / synchronize retrigger), do not
|
|
@@ -175,6 +190,7 @@ jobs:
|
|
|
175
190
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
176
191
|
|
|
177
192
|
- name: Generate summary with the AI engine chain
|
|
193
|
+
id: summary
|
|
178
194
|
env:
|
|
179
195
|
# PR title goes through the env block — never inline-interpolate
|
|
180
196
|
# untrusted text into the shell string (quote injection).
|
|
@@ -183,28 +199,37 @@ jobs:
|
|
|
183
199
|
AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
|
|
184
200
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
185
201
|
GITHUB_TOKEN: ${{ github.token }}
|
|
202
|
+
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
203
|
+
COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
|
|
186
204
|
run: |
|
|
187
205
|
VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
188
206
|
|
|
189
|
-
python3 .github/scripts/changelog_manager.py ai-summary \
|
|
207
|
+
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
190
208
|
--commits-file commits.txt \
|
|
191
209
|
--version "$VERSION" \
|
|
192
210
|
--output summary.md \
|
|
193
211
|
--pr-title "$PR_TITLE" \
|
|
194
|
-
--diff-stat-file diff_stat.txt
|
|
212
|
+
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
213
|
+
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
214
|
+
echo "summary engine: $ENGINE"
|
|
215
|
+
echo "engine=$ENGINE" >> $GITHUB_OUTPUT
|
|
195
216
|
|
|
196
217
|
# update-from-summary reads ./pr_body.md
|
|
197
218
|
cp summary.md pr_body.md
|
|
198
219
|
|
|
199
220
|
- name: Post AI summary comment
|
|
200
221
|
continue-on-error: true
|
|
222
|
+
env:
|
|
223
|
+
ENGINE: ${{ steps.summary.outputs.engine }}
|
|
201
224
|
run: |
|
|
202
225
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
203
226
|
|
|
204
227
|
{
|
|
205
|
-
echo "
|
|
228
|
+
echo "📋 **PR Summary (project-auto-wizard)**"
|
|
206
229
|
echo ""
|
|
207
230
|
cat summary.md
|
|
231
|
+
echo ""
|
|
232
|
+
echo "<sub>engine: ${ENGINE:-unknown}</sub>"
|
|
208
233
|
} > comment_body.md
|
|
209
234
|
|
|
210
235
|
python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# ===================================================================
|
|
3
2
|
# PROJECT-COMMON-ISSUE-HELPER.yaml
|
|
4
3
|
# 이슈 생성/제목 수정 시 브랜치명·커밋 메시지를 댓글로 안내
|
|
5
|
-
# ===================================================================
|
|
6
4
|
#
|
|
7
5
|
# 이슈가 열리거나(opened) 제목이 수정되면(edited + 제목 변경),
|
|
8
6
|
# 정규화된 브랜치명과 커밋 메시지를 이슈 댓글로 생성/갱신한다.
|
|
9
7
|
# ISSUE_HELPER_CREATE_BRANCH=true로 바꾸면 브랜치도 함께 생성한다
|
|
10
8
|
# (기본값은 false — 이슈를 열 때마다 브랜치가 자동 생성되는 것을
|
|
11
|
-
# 막기 위해 옵트인으로
|
|
9
|
+
# 막기 위해 옵트인으로 전환됨).
|
|
12
10
|
#
|
|
13
11
|
# 로직 본체는 .github/scripts/issue_helper.py (Chuseok22/github-issue-helper의
|
|
14
|
-
# Python 재작성, 동일 작성자
|
|
15
|
-
# ===================================================================
|
|
12
|
+
# Python 재작성, 동일 작성자).
|
|
16
13
|
|
|
17
14
|
name: PROJECT-ISSUE-HELPER
|
|
18
15
|
|
|
@@ -23,7 +20,7 @@ on:
|
|
|
23
20
|
# contents는 항상 write로 고정한다. ISSUE_HELPER_CREATE_BRANCH="true"일 때
|
|
24
21
|
# git/refs API로 브랜치를 생성하려면 write가 필요한데, 이 값은 @wizard ask로
|
|
25
22
|
# 설치 후에도 사용자가 직접 켤 수 있어(마법사 재실행 없이) 그 시점에 맞춰
|
|
26
|
-
# permissions를 조건부로 올릴 방법이
|
|
23
|
+
# permissions를 조건부로 올릴 방법이 없다.
|
|
27
24
|
permissions:
|
|
28
25
|
issues: write
|
|
29
26
|
contents: write
|