projectops 4.7.0 → 4.8.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
@@ -7,7 +7,7 @@
7
7
  > 이슈 등록부터 커밋, 보고서, 배포까지. 개발자는 코드만 작성하세요.
8
8
 
9
9
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
10
- ## 최신 버전 : v4.6.1 (2026-09-16)
10
+ ## 최신 버전 : v4.7.0 (2026-09-16)
11
11
 
12
12
  [전체 버전 기록 보기](CHANGELOG.md)
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "description": "ProjectOps — 완전 자동화 GitHub 프로젝트 관리 템플릿 통합 CLI",
5
5
  "keywords": [
6
6
  "devops",
@@ -100,6 +100,27 @@ export function localChecks(cwd = ".") {
100
100
  ] : null,
101
101
  });
102
102
 
103
+ // AI 요약 키 (#569) — "등록했는데 되는 건가?"를 확인할 수단이 없었다.
104
+ // 로컬에서는 저장소 Secret을 읽을 수 없으므로, 어떤 이름을 쓰면 되는지와
105
+ // 등록하지 않아도 무방하다는 사실을 알려준다. 실제 등록 여부는 원격 점검이 본다.
106
+ const AI_KEY_NAMES = ["GEMINI_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY"];
107
+ const hasSummaryWf = files.some((f) => /AI-PR-SUMMARY|RELEASE-CHANGELOG/.test(f));
108
+ if (hasSummaryWf) {
109
+ add({
110
+ name: "AI 요약 키", purpose: "릴리스 노트를 AI로 다듬을지 (선택)",
111
+ status: "INFO", value: "선택 사항",
112
+ detail: [
113
+ "등록하지 않아도 릴리스 노트는 나옵니다 — 커밋 내용을 분석해 만듭니다.",
114
+ "AI가 다듬은 문장을 원하면 아래 중 하나를 저장소 Secret에 등록하세요.",
115
+ " GEMINI_API_KEY 무료 · https://aistudio.google.com/apikey",
116
+ " GROQ_API_KEY 무료",
117
+ " MISTRAL_API_KEY 무료",
118
+ " OPENAI_API_KEY / ANTHROPIC_API_KEY 유료",
119
+ "등록한 것이 자동으로 쓰입니다. 별도 설정은 필요 없습니다.",
120
+ ],
121
+ });
122
+ }
123
+
103
124
  // 워크플로우 파일의 permissions 선언 점검 (#558).
104
125
  // 저장소 설정(Settings > Actions)은 "요청할 수 있는 최대 범위"이고, 워크플로우의
105
126
  // permissions 블록은 "실제로 요청한 범위"다. 둘은 다른 층이라 저장소 설정이 정상이어도
@@ -190,6 +211,20 @@ export async function remoteChecks(slug, token, requiredSecrets = []) {
190
211
  detail: ["토큰에 Secret 조회 권한이 없어 확인하지 못했습니다."] });
191
212
  } else {
192
213
  const have = new Set((sec.data?.secrets || []).map((s) => s.name));
214
+
215
+ // AI 요약 키가 실제로 등록돼 있는지 (#569) — 있으면 어느 서비스인지까지 보여준다.
216
+ const AI_KEYS = { GEMINI_API_KEY: "Gemini", OPENAI_API_KEY: "OpenAI", ANTHROPIC_API_KEY: "Anthropic",
217
+ GROQ_API_KEY: "Groq", MISTRAL_API_KEY: "Mistral", MODEL_API_KEY: "구 이름(서비스 자동 추정)" };
218
+ const foundAi = Object.entries(AI_KEYS).filter(([n]) => have.has(n));
219
+ rows.push({
220
+ name: "AI 요약 키 등록", purpose: "릴리스 노트를 AI로 다듬을지 (선택)",
221
+ status: "INFO",
222
+ value: foundAi.length ? foundAi.map(([n, label]) => `${n} (${label})`).join(", ") : "없음 — 커밋 분석으로 동작",
223
+ detail: foundAi.length ? null : [
224
+ "등록하지 않아도 릴리스 노트는 정상적으로 나옵니다.",
225
+ "AI를 쓰려면 GEMINI_API_KEY(무료)를 등록하세요 — https://aistudio.google.com/apikey",
226
+ ],
227
+ });
193
228
  const missing = requiredSecrets.filter((n) => !have.has(n));
194
229
  add({
195
230
  name: "Secret 등록 여부", purpose: "배포에 필요한 값",
@@ -408,6 +408,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
408
408
  mode, types, version, deployBranch, deployBranchReady, migrationGuidePath,
409
409
  counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
410
410
  verification: result?.verification, // #549 설치 검증 결과
411
+ aiPrSummary, codeReviewCoderabbit, // #569 — 고른 것만 안내
411
412
  logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
412
413
  logFile: files?.logFile ?? null,
413
414
  traceFile: files?.traceFile ?? null,
@@ -332,18 +332,22 @@ export async function askAllOptionalWorkflows({
332
332
  // 저장소가 생겼다. 둘은 같은 자리(PR 댓글)를 놓고 경쟁하므로 한 번에 고르게 한다.
333
333
  if (ask("code-review") || codeReviewCoderabbit === null || aiPrSummary === null) {
334
334
  if (force || !tty || typeof io.select !== "function") {
335
- // 비대화형 기본값: CodeRabbit은 외부 앱 설치가 필요하므로 끄고, 자체 요약만 켠다.
335
+ // 비대화형 기본값: CodeRabbit은 별도 앱 설치가 있어야 실제로 동작하므로
336
+ // 자동화 환경에서 켜봐야 의미가 없다. 설정 없이 바로 도는 요약만 켠다.
336
337
  codeReviewCoderabbit = codeReviewCoderabbit ?? false;
337
338
  aiPrSummary = aiPrSummary ?? true;
338
339
  } else {
339
340
  say("");
340
- say("💬 PR에 리뷰·요약 댓글을 어떻게 받으시겠어요?");
341
+ say("💬 PR에 어떤 댓글을 받으시겠어요?");
342
+ say(" 두 가지는 하는 일이 다르고 한도도 따로라, 같이 켜도 서로 방해하지 않습니다.");
343
+ say(" · 변경 요약 — 무엇이 바뀌었는지 (추가 설정 없이 바로 동작)");
344
+ say(" · 코드 리뷰 — 버그·개선점 지적 (CodeRabbit 앱 설치 필요, 공개 저장소 무료)");
341
345
  const ans = await io.select({
342
346
  message: "PR 댓글 방식을 선택하세요",
343
347
  options: [
344
- { value: "summary", label: "AI 변경 요약 (추천 · 추가 설정 불필요)" },
345
- { value: "coderabbit", label: "CodeRabbit 코드 리뷰 (외부 설치 필요)" },
346
- { value: "both", label: " " },
348
+ { value: "both", label: " (추천 · 무료로 수 있습니다)" },
349
+ { value: "summary", label: "변경 요약만 (추가 설정 불필요)" },
350
+ { value: "coderabbit", label: "코드 리뷰만 (CodeRabbit)" },
347
351
  { value: "none", label: "사용 안 함" },
348
352
  ],
349
353
  });
@@ -68,7 +68,7 @@ const AUTO_SECRETS = new Set(["GITHUB_TOKEN"]);
68
68
 
69
69
  // 없어도 워크플로우가 도는 secret — 폴백이 문서화돼 있다. 필수와 섞어 "등록해야 동작합니다"라고
70
70
  // 하면 안내 자체를 못 믿게 되므로 분리한다.
71
- // MODEL_API_KEY → 없으면 GitHub Models(무료) commit 규칙 fallback (#455 provider 사다리)
71
+ // MODEL_API_KEY → 등록하면 외부 AI 요약, 없으면 Copilot커밋 분석 (#455·#566 사다리)
72
72
  // _GITHUB_PAT_TOKEN → 없으면 GITHUB_TOKEN으로 머지하고 후속 워크플로우를 직접 깨운다 (#551)
73
73
  export const OPTIONAL_SECRETS = new Set(["MODEL_API_KEY", "_GITHUB_PAT_TOKEN"]);
74
74
 
package/src/index.js CHANGED
@@ -322,6 +322,8 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
322
322
  mode: opts.mode, types, version, deployBranch: context.deployBranch, migrationGuidePath,
323
323
  counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
324
324
  verification: result?.verification, // #549 설치 후 검증 결과 (full/workflows 모드에서만 존재)
325
+ // #569 — 고른 것만 안내하려면 선택값이 필요하다
326
+ aiPrSummary: context.aiPrSummary, codeReviewCoderabbit: context.codeReviewCoderabbit,
325
327
  logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
326
328
  logFile: files?.logFile ?? null,
327
329
  traceFile: files?.traceFile ?? null,
package/src/ui/summary.js CHANGED
@@ -13,6 +13,7 @@ export function printSummary(ctx, targetRoot = ".") {
13
13
  const err = (s = "") => process.stderr.write(`${s}\n`);
14
14
  // 색상은 TTY일 때만 (.sh YELLOW/CYAN/NC 등가)
15
15
  const isTty = !!process.stderr.isTTY;
16
+ const BOLD = isTty ? "\u001b[1m" : "";
16
17
  const YELLOW = isTty ? "\x1b[1;33m" : "";
17
18
  const CYAN = isTty ? "\x1b[0;36m" : "";
18
19
  const NC = isTty ? "\x1b[0m" : "";
@@ -190,9 +191,32 @@ export function printSummary(ctx, targetRoot = ".") {
190
191
  err(` → git checkout -b ${deployBranchName} && git push -u origin ${deployBranchName}`);
191
192
  }
192
193
  err("");
193
- err(" 3️⃣ CodeRabbit 활성화 (코드 리뷰를 켰다면)");
194
- err(" → https://coderabbit.ai 로그인 → GitHub 앱 설치 → 이 저장소에 접근 권한(grant access) 부여");
195
- err("");
194
+ // #569 선택한 것만 안내한다. 끈 기능의 설정법을 보여주면 무엇을 해야 하는지 흐려진다.
195
+ let step = 3;
196
+ if (ctx?.aiPrSummary !== false) {
197
+ err(` ${step}️⃣ (선택) AI가 다듬은 릴리스 노트 받기`);
198
+ err(" → 지금 상태로도 릴리스 노트는 나옵니다. 커밋 내용을 분석해 만들기 때문에");
199
+ err(" 아무 설정을 하지 않아도 됩니다. 아래는 문장을 더 매끄럽게 하고 싶을 때만 하세요.");
200
+ err("");
201
+ err(" ① Google AI Studio에서 키 발급 (무료 · 신용카드 불필요 · 2분)");
202
+ err(" https://aistudio.google.com/apikey");
203
+ err(" ② 이 저장소에 등록");
204
+ err(" Settings > Secrets and variables > Actions > New repository secret");
205
+ err(` ${BOLD}Name: GEMINI_API_KEY${NC} Secret: 발급받은 키(AIza... 로 시작)`);
206
+ err("");
207
+ err(" 💡 다른 서비스를 쓴다면 이름만 바꿔 등록하면 됩니다 — 등록한 것이 자동으로 쓰입니다.");
208
+ err(" OPENAI_API_KEY · ANTHROPIC_API_KEY · GROQ_API_KEY · MISTRAL_API_KEY");
209
+ err("");
210
+ step++;
211
+ }
212
+ if (ctx?.codeReviewCoderabbit === true) {
213
+ err(` ${step}️⃣ CodeRabbit 활성화`);
214
+ err(" → https://coderabbit.ai 로그인 → GitHub 앱 설치 → 이 저장소에 접근 권한(grant access) 부여");
215
+ err(" → 이 단계를 안 하면 워크플로우는 켜져도 PR에 리뷰 댓글이 달리지 않습니다");
216
+ err(" → 공개 저장소는 무료입니다 (시간당 3회 리뷰 제한)");
217
+ err("");
218
+ step++;
219
+ }
196
220
  err(SEPARATOR);
197
221
  err("");
198
222
  err(`${CYAN}📖 자세한 설정 방법은 다음 파일을 참고하세요:${NC}`);