project-auto-wizard 0.11.0 → 0.12.0

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 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
 
@@ -17,7 +17,7 @@ npx project-auto-wizard
17
17
  [![node](https://img.shields.io/badge/node-%3E%3D20.12-brightgreen)](package.json)
18
18
 
19
19
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
20
- ## 최신 버전 : v0.10.0 (2026-09-21)
20
+ ## 최신 버전 : v0.11.0 (2026-09-23)
21
21
 
22
22
  [전체 버전 기록 보기](CHANGELOG.md)
23
23
 
@@ -38,7 +38,7 @@ npx project-auto-wizard --mode full --force --type spring,react # CI에서 비
38
38
  | 축 | 내용 |
39
39
  |---|---|
40
40
  | ① **npx 마법사** | 마커 파일로 프로젝트 타입 자동 감지 — **10타입 + 멀티타입 + 모노레포 경로**까지. 질문은 최소한만 |
41
- | ② **GitHub-native AI Release Automation** | 릴리스 PR을 열면: 버전 확정 → **AI가 릴리스 노트 작성** → CHANGELOG 갱신 → automerge → tag + GitHub Release. **API 키 0개** (GitHub Models) |
41
+ | ② **GitHub-native Release Automation** | 릴리스 PR을 열면: 버전 확정 → **릴리스 노트 작성**(기본은 규칙 기반, GitHub Copilot AI는 선택) → CHANGELOG 갱신 → automerge → tag + GitHub Release. **API 키 0개** |
42
42
  | ③ **타입별 CI/CD 워크플로우** | Spring(무중단 배포 포함)·Flutter(스토어 배포)·React·Next·Python·Go 등 타입에 맞는 GitHub Actions 자동 배치 |
43
43
 
44
44
  ### 지원 프로젝트 타입
@@ -183,18 +183,23 @@ npx project-auto-wizard --mode uninstall --force # 워크플로우·스
183
183
  npx project-auto-wizard --mode uninstall --force --purge-readme --purge-gitignore --purge-version # 완전 삭제
184
184
  ```
185
185
 
186
- ## API 키 0개 AI — 요약 엔진 체인
186
+ ## 요약 엔진 체인
187
187
 
188
- 릴리스 노트는 3단 엔진 체인으로 생성됩니다. **상용 서드파티 서비스에 전혀 의존하지 않으며, 어떤 단계가 실패해도 릴리스는 절대 막히지 않습니다.**
188
+ 릴리스 노트는 3단 엔진 체인으로 생성됩니다. **어떤 단계가 실패해도 릴리스는 절대 막히지 않습니다.**
189
+
190
+ > GitHub Models는 2026-07-30에 종료되어 더 이상 사용하지 않습니다.
189
191
 
190
192
  ```mermaid
191
193
  flowchart LR
192
- B["사용자 지정 AI<br/>(AI_API_KEY)"] -->|"키 없음/실패"| C["GitHub Models<br/>(GITHUB_TOKEN만, 무료)"]
193
- C -->|"rate limit/실패"| D["규칙 기반 fallback<br/>(항상 성공)"]
194
+ B["사용자 지정 AI<br/>(AI_API_KEY + AI_API_BASE_URL + AI_MODEL)"] -->|"미설정/실패"| C["GitHub Copilot CLI<br/>(선택, GITHUB_TOKEN)"]
195
+ C -->|"꺼짐/사용 불가/실패"| D["규칙 기반 fallback<br/>(항상 성공)"]
194
196
  ```
195
197
 
196
- - 기본값은 **GitHub Models** — Actions의 `GITHUB_TOKEN` + `permissions: models: read`만으로 동작. **비용 0, 설정 0.**
197
- - `AI_API_KEY`/`AI_API_BASE_URL`/`AI_MODEL` secret으로 OpenAI-호환 엔드포인트(Groq, Gemini 호환 모드, Ollama 등) 교체 가능.
198
+ - 기본값은 **규칙 기반 요약**입니다. 설치 마법사에서 Copilot을 켜면(`--copilot`, `version.yml`의 `copilot_ai: true`) Actions의 `GITHUB_TOKEN` + `permissions: copilot-requests: write`로 Copilot CLI가 요약을 생성합니다 — 별도 API 키는 필요 없습니다.
199
+ - **Copilot은 GitHub Copilot AI Credits를 소비합니다.** 개인 저장소는 저장소 소유자의 Copilot 좌석에, 조직 저장소는 조직에 과금되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책을 켜야 합니다. 사용할 수 없으면 자동으로 규칙 기반 요약으로 전환됩니다. PR에 푸시할 때마다 요약이 새로 생성되므로 그만큼 크레딧이 소비됩니다.
200
+ - Copilot 모델은 저비용 소형 모델(`claude-haiku-4.5`)로 고정되어 있고, 저장소 변수 `COPILOT_MODEL`로 바꿀 수 있습니다.
201
+ - GitHub은 Copilot CLI를 `run` 스텝에서 직접 호출하기보다 Agentic Workflows를 쓰라고 권고하지만, 이 프로젝트는 직접 호출을 택했습니다. 프롬프트 입력이 PR 제목·커밋 메시지·`git diff --stat`뿐이고, 빈 임시 디렉터리에서 shell/write/url 도구와 내장 MCP를 모두 막은 텍스트 생성 전용으로 호출하며, 포크 PR은 기존 가드로 건너뛰어 프롬프트 인젝션 위험을 낮췄기 때문입니다.
202
+ - `AI_API_KEY`(**Secret**)와 `AI_API_BASE_URL`·`AI_MODEL`(**Variables**)을 **모두** 설정하면 OpenAI-호환 엔드포인트(Groq, Gemini 호환 모드, Ollama 등)를 최우선으로 사용합니다. 셋 중 하나라도 없으면 이 단계는 건너뜁니다.
198
203
  - 규칙 fallback 3단: 프로젝트 컨벤션 → Conventional Commits → 무형식 bullet. 커밋 컨벤션이 없어도 동작.
199
204
 
200
205
  ## 릴리스 흐름
@@ -204,7 +209,7 @@ flowchart LR
204
209
  subgraph pr-flow ["pr-flow (기본)"]
205
210
  D1[develop push] --> PR[develop→main 릴리스 PR]
206
211
  PR --> V["버전 확정 (patch+1)"]
207
- V --> AI[AI 릴리스 노트]
212
+ V --> AI[릴리스 노트]
208
213
  AI --> CL[CHANGELOG.json/md 갱신]
209
214
  CL --> AM[automerge]
210
215
  AM --> TAG["tag vX.Y.Z + GitHub Release"]
@@ -236,6 +241,7 @@ npx project-auto-wizard [옵션]
236
241
  --nexus 라이브러리 publish 워크플로우 포함 (Nexus + GitHub Packages)
237
242
  --secret-backup Secret 서버 백업 워크플로우 포함
238
243
  --semver-auto 커밋 타입 기반 자동 major/minor/patch 승격 (기본: 사용함, --no-semver-auto로 끔)
244
+ --copilot Copilot으로 AI 요약 생성 (기본: 사용 안 함, GitHub Copilot AI Credits 소비, --no-copilot으로 끔)
239
245
  --dry-run 실제 파일 변경 없이 무엇이 바뀔지만 미리 보여줌
240
246
  --purge-readme --mode uninstall --force 시 README.md 버전 섹션도 제거
241
247
  --purge-gitignore --mode uninstall --force 시 .gitignore 자동 추가 항목도 제거
@@ -270,9 +276,10 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
270
276
  secret이 없어도 폴백이 자동으로 이어받아 태그·Release까지 진행됩니다 — 실제 병합 후 최대 ~20초 정도 더 걸릴 뿐입니다.
271
277
  속도를 더 원한다면 PAT을 등록할 수 있습니다 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 (scopes: repo, workflow).
272
278
  등록: 레포 Settings → Secrets and variables → Actions → New repository secret · 이름은 WORKFLOW_PAT
273
- [i] GitHub Models — AI 릴리스 노트 생성
274
- 조직 정책으로 차단됐는지는 자동으로 확인할 수 없습니다 (Settings → Models).
275
- 차단돼 있어도 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
279
+ [i] Copilot AI 요약 — AI 릴리스 노트 생성(선택)
280
+ 기본은 꺼져 있습니다 (version.yml의 copilot_ai: false).
281
+ 켜면 GitHub Copilot AI Credits가 소비됩니다 — 조직은 'Allow use of Copilot CLI billed to the organization' 정책이 필요합니다.
282
+ 꺼져 있거나 사용할 수 없으면 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
276
283
 
277
284
  ✓ 문제를 찾지 못했습니다.
278
285
  ```
@@ -296,7 +303,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
296
303
 
297
304
  ### 자체 AI PR 요약봇
298
305
 
299
- 상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때 API 키 0개 AI 엔진 체인으로 요약 코멘트를 자동으로 답니다.
306
+ 상용 PR 리뷰 SaaS 없이 동작하는 자체 요약봇입니다. 릴리스 브랜치(`--main-branch`)를 대상으로 하는 PR이 열릴 때 요약 엔진 체인(기본은 규칙 기반, Copilot은 선택)으로 요약 코멘트를 자동으로 답니다.
300
307
 
301
308
  기본 설치(pr-flow) 기준으로 일상적인 기능 PR은 `develop`을 대상으로 열리므로, 이 봇은 develop→main 릴리스 PR에서만 실제로 동작합니다 — 해당 PR에서는 `AUTO-CHANGELOG-CONTROL`이 이미 같은 엔진으로 체인지로그 요약을 생성하므로, 이 봇은 그 요약을 PR 코멘트 형태로도 남겨주는 보조 역할입니다. 릴리스 브랜치 = 개발 브랜치인 trunk-based 모드에서는 모든 PR이 곧 릴리스 대상 브랜치를 향하므로 매 PR마다 동작합니다.
302
309
 
@@ -308,7 +315,7 @@ npx project-auto-wizard --no-semver-auto # 항상 patch+1 (레거시 동작)
308
315
  |---|---|
309
316
  | **`WORKFLOW_PAT` secret** (선택 — 속도 최적화용) | 없어도 `GITHUB_TOKEN` 폴백이 automerge부터 Release 발행까지 자동으로 이어갑니다(실제 병합 후 최대 ~20초 추가). 더 빠르게 하고 싶다면 Settings → Secrets → Actions에 `WORKFLOW_PAT` (scopes: `repo`, `workflow`) 등록 — 반드시 개인 계정이 아닌 조직 bot/machine 계정으로 발급하세요 |
310
317
  | **Workflow permissions** | Settings → Actions → Workflow permissions: **Read and write** |
311
- | **GitHub Models** | 기본 활성 — 별도 설정 불필요. 조직 정책으로 차단된 경우 자동으로 규칙 fallback |
318
+ | **Copilot AI 요약** (선택) | 기본 꺼짐. 켜려면 마법사에서 선택하거나 `version.yml`의 `copilot_ai`를 `true`로 — AI Credits가 소비되며 조직은 "Allow use of Copilot CLI billed to the organization" 정책이 필요합니다. 사용할 수 없으면 자동으로 규칙 fallback |
312
319
 
313
320
  ## 설계 원칙
314
321
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "project-auto-wizard",
3
- "version": "0.11.0",
4
- "description": "One command DevOps: npx wizard that installs GitHub-native AI Release Automation into any project",
3
+ "version": "0.12.0",
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": {
@@ -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 → GitHub Models)과 규칙 기반 폴백이
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
- ai_api_key = os.environ.get('AI_API_KEY')
393
- github_token = os.environ.get('GITHUB_TOKEN')
394
- candidates = [
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
- response = call_openai_compatible(base_url, token, model, prompt)
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
- continue
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
 
@@ -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
- _AI_DEFAULT_BASE_URL = "https://models.github.ai/inference"
698
- _AI_DEFAULT_MODEL = "openai/gpt-4o-mini"
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
- if ai_api_key:
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 github_token:
839
+ if summary_text is None and _copilot_enabled():
784
840
  try:
785
- # GitHub Models는 자체 모델 카탈로그만 서빙한다 — 사용자 API용으로
786
- # AI_MODEL이 오버라이드돼 있어도 여기서는 기본 모델을 쓴다
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 = "github-models"
844
+ engine = "copilot"
792
845
  else:
793
- print("[warn] github-models failed: empty content in response", file=sys.stderr)
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] github-models failed: {e}", file=sys.stderr)
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)
@@ -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
@@ -47,6 +50,7 @@ metadata:
47
50
  nexus: {{OPT_NEXUS}}
48
51
  secret_backup: {{OPT_SECRET_BACKUP}}
49
52
  semver_auto: {{OPT_SEMVER_AUTO}}
53
+ copilot_ai: {{OPT_COPILOT_AI}}
50
54
  deploy_style: "{{OPT_DEPLOY_STYLE}}" # simple | nginx | traefik | none (서버 배포 워크플로우)
51
55
  {{FLUTTER_OPTIONS}}
52
56
  {{DEPLOY}}
@@ -4,9 +4,9 @@
4
4
  # Self-hosted AI PR summary bot — no third-party service required.
5
5
  # ===================================================================
6
6
  #
7
- # Posts an AI-generated summary comment on PRs targeting the default
8
- # branch, using the same zero-API-key engine chain as the release
9
- # changelog (user API key -> GitHub Models -> rule-based fallback).
7
+ # Posts a summary comment on PRs targeting the default branch, using the
8
+ # same engine chain as the release changelog (user API key -> Copilot CLI,
9
+ # opt-in via version.yml copilot_ai -> rule-based fallback).
10
10
  # The prompt includes commit subjects plus a capped `git diff --stat`.
11
11
  #
12
12
  # On release PRs (head branch == {{DEVELOP_BRANCH}}) this job is skipped.
@@ -30,7 +30,11 @@ on:
30
30
  permissions:
31
31
  contents: read
32
32
  pull-requests: write
33
- models: read
33
+ copilot-requests: write
34
+
35
+ concurrency:
36
+ group: ai-pr-summary-${{ github.event.pull_request.number }}
37
+ cancel-in-progress: true
34
38
 
35
39
  jobs:
36
40
  ai-pr-summary:
@@ -46,6 +50,20 @@ jobs:
46
50
  fetch-depth: 0
47
51
  ref: ${{ github.event.pull_request.head.ref }}
48
52
 
53
+ - name: Read copilot_ai option from version.yml
54
+ id: copilot_options
55
+ run: |
56
+ # scoped to the metadata: -> template: -> options: chain so an
57
+ # unrelated "copilot_ai:" key can never hijack the value
58
+ 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")
59
+ echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
60
+ echo "copilot_ai option: $COPILOT_AI"
61
+
62
+ - name: Install Copilot CLI
63
+ if: steps.copilot_options.outputs.copilot_ai == 'true'
64
+ continue-on-error: true
65
+ run: npm install -g @github/copilot@1.0.88
66
+
49
67
  - name: Generate and post AI summary
50
68
  env:
51
69
  PR_TITLE: ${{ github.event.pull_request.title }}
@@ -53,6 +71,8 @@ jobs:
53
71
  AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
54
72
  AI_MODEL: ${{ vars.AI_MODEL }}
55
73
  GITHUB_TOKEN: ${{ github.token }}
74
+ COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
75
+ COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
56
76
  run: |
57
77
  PR_NUMBER=${{ github.event.pull_request.number }}
58
78
  VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
@@ -68,17 +88,20 @@ jobs:
68
88
  fi
69
89
  rm -f _full_diff_stat.txt
70
90
 
71
- python3 .github/scripts/changelog_manager.py ai-summary \
91
+ SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
72
92
  --commits-file commits.txt \
73
93
  --version "$VERSION" \
74
94
  --output summary.md \
75
95
  --pr-title "$PR_TITLE" \
76
- --diff-stat-file diff_stat.txt
96
+ --diff-stat-file diff_stat.txt | tail -n 1)
97
+ ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
77
98
 
78
99
  {
79
- echo "🤖 **AI Summary (project-auto-wizard)**"
100
+ echo "📋 **PR Summary (project-auto-wizard)**"
80
101
  echo ""
81
102
  cat summary.md
103
+ echo ""
104
+ echo "<sub>engine: ${ENGINE}</sub>"
82
105
  } > comment_body.md
83
106
 
84
107
  python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
@@ -12,8 +12,8 @@
12
12
  # commits) and bumps accordingly.
13
13
  # - Otherwise: always patch+1 (legacy behavior).
14
14
  # 2. Generates the release summary locally with changelog_manager.py
15
- # ai-summary (engine chain: user API key -> GitHub Models -> rule-based
16
- # fallback; always succeeds, no third-party service involved)
15
+ # ai-summary (engine chain: user API key -> Copilot CLI, opt-in via
16
+ # version.yml copilot_ai -> rule-based fallback; always succeeds)
17
17
  # - The AI prompt includes commit subjects plus a capped
18
18
  # `git diff --stat` (file-level change summary, no diff body)
19
19
  # for richer context without token-cost blowup.
@@ -34,8 +34,10 @@
34
34
  # - secrets.WORKFLOW_PAT: PAT used for the merge. Without it the merge is
35
35
  # performed with GITHUB_TOKEN, and GitHub will NOT trigger downstream
36
36
  # {{MAIN_BRANCH}}-push workflows (e.g. RELEASE-PUBLISH) for that merge.
37
- # - secrets.AI_API_KEY (+ vars.AI_API_BASE_URL / vars.AI_MODEL): preferred
38
- # summary engine. Falls back to GitHub Models, then to rule-based.
37
+ # - secrets.AI_API_KEY (+ vars.AI_API_BASE_URL / vars.AI_MODEL, all three
38
+ # required together): preferred summary engine. Falls back to Copilot CLI
39
+ # (only when version.yml copilot_ai is true; optional vars.COPILOT_MODEL),
40
+ # then to rule-based.
39
41
  #
40
42
  # Issue #90: without WORKFLOW_PAT, a wait-for-merge-and-trigger-release job
41
43
  # polls until the automerge actually lands, then wakes RELEASE-PUBLISH via
@@ -60,7 +62,7 @@ on:
60
62
  permissions:
61
63
  contents: write
62
64
  pull-requests: write
63
- models: read
65
+ copilot-requests: write
64
66
 
65
67
  jobs:
66
68
  changelog-and-merge:
@@ -94,6 +96,20 @@ jobs:
94
96
  echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
95
97
  echo "semver_auto option: $SEMVER_AUTO"
96
98
 
99
+ - name: Read copilot_ai option from version.yml
100
+ id: copilot_options
101
+ run: |
102
+ # scoped to the metadata: -> template: -> options: chain so an
103
+ # unrelated "copilot_ai:" key can never hijack the value
104
+ 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")
105
+ echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
106
+ echo "copilot_ai option: $COPILOT_AI"
107
+
108
+ - name: Install Copilot CLI
109
+ if: steps.copilot_options.outputs.copilot_ai == 'true'
110
+ continue-on-error: true
111
+ run: npm install -g @github/copilot@1.0.88
112
+
97
113
  - name: Collect commits since last release
98
114
  id: collect
99
115
  run: |
@@ -149,6 +165,8 @@ jobs:
149
165
  AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
150
166
  AI_MODEL: ${{ vars.AI_MODEL }}
151
167
  GITHUB_TOKEN: ${{ github.token }}
168
+ COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
169
+ COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
152
170
  run: |
153
171
  # Idempotency: if the PR head is already this workflow's own
154
172
  # version-confirm commit (re-run / synchronize retrigger), do not
@@ -175,6 +193,7 @@ jobs:
175
193
  echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
176
194
 
177
195
  - name: Generate summary with the AI engine chain
196
+ id: summary
178
197
  env:
179
198
  # PR title goes through the env block — never inline-interpolate
180
199
  # untrusted text into the shell string (quote injection).
@@ -183,28 +202,37 @@ jobs:
183
202
  AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
184
203
  AI_MODEL: ${{ vars.AI_MODEL }}
185
204
  GITHUB_TOKEN: ${{ github.token }}
205
+ COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
206
+ COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
186
207
  run: |
187
208
  VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
188
209
 
189
- python3 .github/scripts/changelog_manager.py ai-summary \
210
+ SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
190
211
  --commits-file commits.txt \
191
212
  --version "$VERSION" \
192
213
  --output summary.md \
193
214
  --pr-title "$PR_TITLE" \
194
- --diff-stat-file diff_stat.txt
215
+ --diff-stat-file diff_stat.txt | tail -n 1)
216
+ ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
217
+ echo "summary engine: $ENGINE"
218
+ echo "engine=$ENGINE" >> $GITHUB_OUTPUT
195
219
 
196
220
  # update-from-summary reads ./pr_body.md
197
221
  cp summary.md pr_body.md
198
222
 
199
223
  - name: Post AI summary comment
200
224
  continue-on-error: true
225
+ env:
226
+ ENGINE: ${{ steps.summary.outputs.engine }}
201
227
  run: |
202
228
  PR_NUMBER=${{ github.event.pull_request.number }}
203
229
 
204
230
  {
205
- echo "🤖 **AI Summary (project-auto-wizard)**"
231
+ echo "📋 **PR Summary (project-auto-wizard)**"
206
232
  echo ""
207
233
  cat summary.md
234
+ echo ""
235
+ echo "<sub>engine: ${ENGINE:-unknown}</sub>"
208
236
  } > comment_body.md
209
237
 
210
238
  python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
@@ -59,7 +59,7 @@ on:
59
59
 
60
60
  permissions:
61
61
  contents: write
62
- models: read
62
+ copilot-requests: write
63
63
 
64
64
  jobs:
65
65
  release:
@@ -160,6 +160,28 @@ jobs:
160
160
  echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
161
161
  echo "semver_auto option: $SEMVER_AUTO"
162
162
 
163
+ - name: Read copilot_ai option from version.yml
164
+ id: copilot_options
165
+ if: >-
166
+ steps.gate.outputs.proceed == 'true' &&
167
+ steps.mode.outputs.mode == 'trunk-based' &&
168
+ github.event_name == 'push'
169
+ run: |
170
+ # scoped to the metadata: -> template: -> options: chain so an
171
+ # unrelated "copilot_ai:" key can never hijack the value
172
+ 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")
173
+ echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
174
+ echo "copilot_ai option: $COPILOT_AI"
175
+
176
+ - name: Install Copilot CLI
177
+ if: >-
178
+ steps.gate.outputs.proceed == 'true' &&
179
+ steps.mode.outputs.mode == 'trunk-based' &&
180
+ github.event_name == 'push' &&
181
+ steps.copilot_options.outputs.copilot_ai == 'true'
182
+ continue-on-error: true
183
+ run: npm install -g @github/copilot@1.0.88
184
+
163
185
  - name: Trunk-based version bump + changelog
164
186
  if: >-
165
187
  steps.gate.outputs.proceed == 'true' &&
@@ -170,6 +192,8 @@ jobs:
170
192
  AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
171
193
  AI_MODEL: ${{ vars.AI_MODEL }}
172
194
  GITHUB_TOKEN: ${{ github.token }}
195
+ COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
196
+ COPILOT_MODEL: ${{ vars.COPILOT_MODEL }}
173
197
  run: |
174
198
  # Idempotency: on a re-run of a failed job the checked-out HEAD may
175
199
  # already be this workflow's own docs commit — never bump twice.
package/src/cli/args.js CHANGED
@@ -15,6 +15,7 @@ export function parseArgs(argv) {
15
15
  includeNexus: null, // null=미설정
16
16
  includeSecretBackup: null,
17
17
  includeSemverAuto: null, // --semver-auto / --no-semver-auto (기본 true — 미지정 시 다운스트림에서 해석)
18
+ includeCopilotAi: null, // --copilot / --no-copilot (기본 false — AI Credits를 소비하는 opt-in)
18
19
  pathsCsv: "", // "flutter=app,react=client" 원문 (정규화는 resolve 단계)
19
20
  mainBranch: "", // 릴리스 브랜치 (--main-branch). 빈값=감지된 default branch
20
21
  developBranch: "", // 개발 브랜치 (--develop-branch). 빈값=develop
@@ -139,6 +140,12 @@ export function parseArgs(argv) {
139
140
  case "--no-semver-auto":
140
141
  if (seenFlags.has("--semver-auto")) throw new CliError("--semver-auto와 --no-semver-auto는 동시에 지정할 수 없습니다");
141
142
  seenFlags.add("--no-semver-auto"); result.includeSemverAuto = false; break;
143
+ case "--copilot":
144
+ if (seenFlags.has("--no-copilot")) throw new CliError("--copilot과 --no-copilot은 동시에 지정할 수 없습니다");
145
+ seenFlags.add("--copilot"); result.includeCopilotAi = true; break;
146
+ case "--no-copilot":
147
+ if (seenFlags.has("--copilot")) throw new CliError("--copilot과 --no-copilot은 동시에 지정할 수 없습니다");
148
+ seenFlags.add("--no-copilot"); result.includeCopilotAi = false; break;
142
149
  case "--paths": result.pathsCsv = args.shift() ?? ""; break;
143
150
  case "--main-branch": {
144
151
  const v = args.shift();
package/src/cli/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // --help 텍스트.
2
- export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-native AI 릴리스 자동화 설치 마법사
2
+ export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-native 릴리스 자동화 설치 마법사
3
3
 
4
4
  사용법:
5
5
  npx project-auto-wizard [옵션]
@@ -24,6 +24,7 @@ export const HELP_TEXT = `project-auto-wizard — One command DevOps: GitHub-nat
24
24
  --nexus / --no-nexus 라이브러리 publish 워크플로우 포함/제외 (Nexus + GitHub Packages)
25
25
  --secret-backup / --no-secret-backup Secret 백업 워크플로우 포함/제외
26
26
  --semver-auto / --no-semver-auto 커밋 타입 기반 자동 major/minor/patch 승격 (기본: 사용함)
27
+ --copilot / --no-copilot Copilot으로 AI 요약 생성 (기본: 사용 안 함, GitHub Copilot AI Credits 소비)
27
28
  --force full 실행에 필수 (모든 확인 생략, 비대화형 기본값 사용)
28
29
  --dry-run 실제 파일 변경 없이 무엇이 바뀔지만 미리 보여줌 (full/uninstall 지원)
29
30
  --purge-readme --mode uninstall --force 시 README.md 버전 섹션도 제거
@@ -161,10 +161,11 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
161
161
  }
162
162
 
163
163
  add({
164
- name: "GitHub Models 활성화", label: "GitHub Models", purpose: "AI 릴리스 노트 생성", status: "INFO",
164
+ name: "Copilot AI 요약", label: "Copilot AI 요약", purpose: "AI 릴리스 노트 생성(선택)", status: "INFO",
165
165
  note: [
166
- "조직 정책으로 차단됐는지는 자동으로 확인할 수 없습니다 (Settings → Models).",
167
- "차단돼 있어도 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.",
166
+ "기본은 꺼져 있습니다 (version.yml의 copilot_ai: false).",
167
+ "켜면 GitHub Copilot AI Credits가 소비됩니다 — 조직은 'Allow use of Copilot CLI billed to the organization' 정책이 필요합니다.",
168
+ "꺼져 있거나 사용할 수 없으면 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.",
168
169
  ],
169
170
  });
170
171
 
@@ -88,6 +88,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
88
88
  let includeNexus = existing?.options?.nexus ?? false;
89
89
  let includeSecretBackup = existing?.options?.secretBackup ?? false;
90
90
  let includeSemverAuto = existing?.options?.semverAuto ?? null;
91
+ let includeCopilotAi = existing?.options?.copilotAi ?? null;
91
92
  // 서버 배포 방식 — 저장값(version.yml)이 있으면 재질문하지 않는다 (nexus/secret_backup과 같은 규약).
92
93
  let deployStyle = isDeployStyle(existing?.options?.deployStyle) ? existing.options.deployStyle : "";
93
94
  const showOptional = mode === "full";
@@ -153,11 +154,18 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
153
154
  const y2 = await io.askYesNo("자동 버전 승격을 사용하시겠습니까? (커밋 타입에 따라 major/minor/patch 자동 결정)", true);
154
155
  includeSemverAuto = y2 === true;
155
156
  }
157
+
158
+ // Copilot AI 요약 — AI Credits를 소비하므로 opt-in(기본 No). 저장값 있으면 재질문 생략.
159
+ if (mode === "full" && includeCopilotAi === null) {
160
+ const y3 = await io.askYesNo("Copilot으로 AI 요약을 생성하시겠습니까? (GitHub Copilot AI Credits가 소비되며, 사용할 수 없으면 자동으로 규칙 기반 요약으로 전환됩니다)", false);
161
+ includeCopilotAi = y3 === true;
162
+ }
156
163
  }
157
164
  // 질문이 실제로 나온 경우(위 full 모드 질문) 답변을 그대로 존중.
158
165
  // 질문이 안 나온 경우(version/workflows 모드) — 기존 설치는 안전하게 false로 폴백,
159
166
  // 완전 신규 설치만 true(기존 설계) 유지 — CLI 경로(index.js)와 동일한 안전 정책.
160
167
  includeSemverAuto = includeSemverAuto === null ? (existing ? false : true) : includeSemverAuto !== false;
168
+ includeCopilotAi = includeCopilotAi === true;
161
169
 
162
170
  // 확인/수정 루프 — ESC는 '머무르기' (.sh L1877~1881: 명시적 '아니오'만 종료)
163
171
  let paths = new Map();
@@ -278,6 +286,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), payloadRoot
278
286
  mode, force: true, types, version, versionCode, branch, branches, paths,
279
287
  includeNexus, includeSecretBackup,
280
288
  includeSemverAuto,
289
+ includeCopilotAi,
281
290
  repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
282
291
  // 설치 로그(#79)·완료 요약(#80)이 쓰는 부가 문맥 — 설치 동작 자체는 바꾸지 않는다.
283
292
  markers, envAnswers, detectWarnings,
@@ -69,8 +69,9 @@ export function printStatus(status) {
69
69
  }
70
70
  const boolLabel = (v) => (v === null ? "미설정(기본 false)" : v);
71
71
  const semverAutoLabel = status.options.semverAuto === null ? "미설정(기본 false)" : status.options.semverAuto;
72
+ const copilotAiLabel = boolLabel(status.options.copilotAi ?? null);
72
73
  const flutterLabels = status.types.includes("flutter") ? flutterOptionLabels(status.options) : "";
73
- lines.push(`옵션 : nexus=${boolLabel(status.options.nexus)} secret_backup=${boolLabel(status.options.secretBackup)} semver_auto=${semverAutoLabel}${flutterLabels}`);
74
+ lines.push(`옵션 : nexus=${boolLabel(status.options.nexus)} secret_backup=${boolLabel(status.options.secretBackup)} semver_auto=${semverAutoLabel} copilot_ai=${copilotAiLabel}${flutterLabels}`);
74
75
  if (status.modifiedFiles.length) {
75
76
  lines.push("", `사용자가 수정한 워크플로우 파일 (${status.modifiedFiles.length}개):`);
76
77
  for (const f of status.modifiedFiles) lines.push(` - ${f}`);
package/src/context.js CHANGED
@@ -27,6 +27,7 @@ export function createContext(overrides = {}) {
27
27
  includeNexus: null, // null=미설정, true/false=명시
28
28
  includeSecretBackup: null,
29
29
  includeSemverAuto: null, // null=미설정(다운스트림에서 true로 해석), true/false=명시
30
+ includeCopilotAi: null, // null=미설정(다운스트림에서 false로 해석), true/false=명시 — Copilot AI 요약 opt-in
30
31
  // Flutter 옵션 (이슈 #131) — Flutter 타입이 없는 프로젝트에서는 전부 무시된다.
31
32
  // 결정은 src/core/flutter-options.js의 resolveFlutterOptions가 한다.
32
33
  envMode: "", // "dart-define" | "dotenv". ""=미결정 → 템플릿 기본값(dart-define)
@@ -43,7 +43,7 @@ export function scanUnsubstituted(workflowsDir, filenames = []) {
43
43
  const AUTO_SECRETS = new Set(["GITHUB_TOKEN"]);
44
44
  // 없어도 워크플로우가 도는 secret — 폴백이 문서화돼 있다. 필수와 섞어 "등록해야 동작합니다"라고
45
45
  // 하면 안내 자체를 못 믿게 되므로 분리한다.
46
- // AI_API_KEY → 없으면 GitHub Models(무료) → 규칙 fallback
46
+ // AI_API_KEY → 없으면 Copilot(opt-in, copilot_ai) → 규칙 fallback
47
47
  // WORKFLOW_PAT → 없으면 GITHUB_TOKEN
48
48
  export const OPTIONAL_SECRETS = new Set(["AI_API_KEY", "WORKFLOW_PAT"]);
49
49
  const SECRET_RE = /secrets\.([A-Z][A-Z0-9_]*)/g;
@@ -44,13 +44,13 @@ const FLUTTER_OPTION_KEYS = {
44
44
  };
45
45
 
46
46
  // metadata.template.options 상태머신 파싱 (.sh read_template_options L2361~2416 등가).
47
- // 반환: { nexus: bool|null, secretBackup: bool|null, semverAuto: bool|null, deployStyle: string|null,
47
+ // 반환: { nexus: bool|null, secretBackup: bool|null, semverAuto: bool|null, copilotAi: bool|null, deployStyle: string|null,
48
48
  // envMode/flutterStore/androidDeployMode/iosDeployMode: string|null } — null=미기재.
49
49
  // 구 synology·coderabbit 키 등 다른 키는 어느 분기에도 안 걸려 자연히 무시된다(파싱 에러 없음).
50
50
  // (options-ask.js가 이 함수를 import한다 — 순환 방지 위해 여기(version-yml)에 정의.)
51
51
  export function parseTemplateOptions(content) {
52
52
  const out = {
53
- nexus: null, secretBackup: null, semverAuto: null, deployStyle: null,
53
+ nexus: null, secretBackup: null, semverAuto: null, copilotAi: null, deployStyle: null,
54
54
  envMode: null, flutterStore: null, androidDeployMode: null, iosDeployMode: null,
55
55
  };
56
56
  // 값 정규화: 따옴표 제거 + 트림 (.sh tr -d '"' | tr -d "'" | xargs 등가)
@@ -88,6 +88,13 @@ export function parseTemplateOptions(content) {
88
88
  if (v === "false") out.semverAuto = false;
89
89
  continue;
90
90
  }
91
+ m = line.match(/^\s+copilot_ai:\s*(.+)/);
92
+ if (m) {
93
+ const v = strip(m[1]);
94
+ if (v === "true") out.copilotAi = true;
95
+ if (v === "false") out.copilotAi = false;
96
+ continue;
97
+ }
91
98
  // 들여쓰기 0~4칸의 다른 키 → options 섹션 종료 (.sh L2404~2408)
92
99
  if (/^\s{0,4}[a-z_]+:/.test(line)) { inOptions = false; inTemplate = false; }
93
100
  }
@@ -204,7 +211,7 @@ export function buildVersionYml({
204
211
  const b = branches || { main: branch || "main", develop: "develop", mode: "pr-flow" };
205
212
  const {
206
213
  templateVersion = "unknown", includeNexus = false, includeSecretBackup = false,
207
- includeSemverAuto = true, deployStyle = "", optionsDate = today,
214
+ includeSemverAuto = true, includeCopilotAi = false, deployStyle = "", optionsDate = today,
208
215
  } = templateOptions || {};
209
216
 
210
217
  // project_paths 블록 (full-line 토큰 {{PROJECT_PATHS}} — 없으면 라인 제거)
@@ -244,6 +251,7 @@ export function buildVersionYml({
244
251
  MAIN_BRANCH: b.main, DEVELOP_BRANCH: b.develop, BRANCH_MODE: b.mode,
245
252
  OPT_NEXUS: String(includeNexus), OPT_SECRET_BACKUP: String(includeSecretBackup),
246
253
  OPT_SEMVER_AUTO: String(includeSemverAuto),
254
+ OPT_COPILOT_AI: String(includeCopilotAi),
247
255
  OPT_DEPLOY_STYLE: String(deployStyle || ""),
248
256
  };
249
257
 
@@ -273,7 +281,7 @@ export function buildVersionYml({
273
281
  export function renderVersionYml(context, templateText, { pathMarkers, deployValues = new Map(), extraTopLevel = [] }) {
274
282
  const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
275
283
  now, today, templateVersion = "unknown", branches = null,
276
- includeNexus = false, includeSecretBackup = false, includeSemverAuto, deployStyle,
284
+ includeNexus = false, includeSecretBackup = false, includeSemverAuto, includeCopilotAi, deployStyle,
277
285
  envMode, flutterStore, androidDeployMode, iosDeployMode } = context;
278
286
  return buildVersionYml({
279
287
  templateText, version, types, paths, pathMarkers, branch, branches, versionCode, now, today,
@@ -282,6 +290,7 @@ export function renderVersionYml(context, templateText, { pathMarkers, deployVal
282
290
  templateOptions: {
283
291
  templateVersion, includeNexus, includeSecretBackup,
284
292
  includeSemverAuto: includeSemverAuto !== false,
293
+ includeCopilotAi: includeCopilotAi === true,
285
294
  deployStyle: deployStyle || DEFAULT_DEPLOY_STYLE,
286
295
  optionsDate: today,
287
296
  },
package/src/index.js CHANGED
@@ -290,6 +290,8 @@ async function runInner(argv, {
290
290
  // workflows-only 재실행) 조용히 true로 켜지면 애매한 커밋 하나로 major가 승격될 위험이 있다 —
291
291
  // 기존 설치는 false로 안전하게 폴백, 완전 신규 설치만 true(기존 설계) 유지.
292
292
  includeSemverAuto: opts.includeSemverAuto ?? existing?.options?.semverAuto ?? (existing ? false : true),
293
+ // Copilot AI 요약은 AI Credits를 소비하는 opt-in — 신규·기존 설치 모두 명시하지 않으면 false다.
294
+ includeCopilotAi: opts.includeCopilotAi ?? existing?.options?.copilotAi ?? false,
293
295
  repoName,
294
296
  // 실 resolver 4종 (.sh resolve_token 등가)
295
297
  resolvers: makeResolvers(cwd, repoName, paths, flutterOptions),
package/src/ui/summary.js CHANGED
@@ -29,7 +29,7 @@ export function printSummary(ctx) {
29
29
  case "full":
30
30
  err(" ✅ 버전 관리 시스템 (version.yml)");
31
31
  err(" ✅ README.md 자동 버전 업데이트");
32
- err(" ✅ GitHub Actions 워크플로우 (AI 릴리스 자동화 포함)");
32
+ err(" ✅ GitHub Actions 워크플로우 (릴리스 자동화 포함)");
33
33
  if (gitignoreUpdated) err(" ✅ .gitignore 백업 파일 제외 항목 (*.bak/*.template.yaml)");
34
34
  break;
35
35
  case "version":
@@ -37,7 +37,7 @@ export function printSummary(ctx) {
37
37
  err(" ✅ README.md 자동 버전 업데이트");
38
38
  break;
39
39
  case "workflows":
40
- err(" ✅ GitHub Actions 워크플로우 (AI 릴리스 자동화 포함)");
40
+ err(" ✅ GitHub Actions 워크플로우 (릴리스 자동화 포함)");
41
41
  break;
42
42
  }
43
43
 
@@ -48,13 +48,13 @@ export function printSummary(ctx) {
48
48
  if (branches.mode === "trunk-based") {
49
49
  err(` 🌿 ${branches.main} 단일 브랜치 (trunk-based) — RELEASE-PUBLISH 하나가 버전확정→체인지로그→tag→Release를 순차 처리`);
50
50
  } else {
51
- err(` 🌿 개발 ${branches.develop} → 릴리스 ${branches.main} (pr-flow) — 릴리스 PR에서 버전확정·AI 체인지로그·automerge`);
51
+ err(` 🌿 개발 ${branches.develop} → 릴리스 ${branches.main} (pr-flow) — 릴리스 PR에서 버전확정·체인지로그·automerge`);
52
52
  }
53
53
  }
54
54
  if (mode === "full" || mode === "workflows") {
55
55
  err("");
56
56
  err("릴리스 노트 요약 엔진:");
57
- err(" 🤖 AI_API_KEY(선택) → GitHub Models(기본·무료·API 키 불필요) → 규칙 fallback — 릴리스는 절대 막히지 않음");
57
+ err(" 🤖 AI_API_KEY(선택) → Copilot(선택·AI Credits 소비, 기본 꺼짐) → 규칙 fallback — 릴리스는 절대 막히지 않음");
58
58
  }
59
59
 
60
60
  err("");