projectops 4.31.5 → 4.33.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.
Files changed (57) hide show
  1. package/README.md +144 -145
  2. package/package.json +2 -2
  3. package/src/cli/args.js +65 -29
  4. package/src/cli/errors.js +9 -7
  5. package/src/cli/help.js +51 -3
  6. package/src/commands/doctor.js +80 -78
  7. package/src/commands/full.js +11 -2
  8. package/src/commands/interactive.js +71 -49
  9. package/src/commands/issues.js +4 -0
  10. package/src/commands/skills.js +8 -7
  11. package/src/commands/version.js +4 -2
  12. package/src/commands/workflows.js +3 -1
  13. package/src/context.js +4 -0
  14. package/src/core/breaking-check.js +6 -5
  15. package/src/core/copy/simple.js +4 -0
  16. package/src/core/copy/workflows.js +10 -5
  17. package/src/core/exclusions.js +8 -0
  18. package/src/core/ide/adapters/claude.js +18 -17
  19. package/src/core/ide/adapters/codex.js +12 -11
  20. package/src/core/ide/adapters/cursor.js +11 -10
  21. package/src/core/ide/adapters/gemini.js +11 -10
  22. package/src/core/ide/adapters/pi-common.js +2 -1
  23. package/src/core/ide/adapters/pi-harness.js +13 -16
  24. package/src/core/ide/adapters/pi.js +12 -11
  25. package/src/core/label-style.js +78 -0
  26. package/src/core/migration-guide.js +38 -38
  27. package/src/core/migrations/index.js +19 -15
  28. package/src/core/migrations/registry.js +70 -55
  29. package/src/core/options-ask.js +65 -64
  30. package/src/core/orphan-workflows.js +2 -1
  31. package/src/core/paths-resolve.js +31 -30
  32. package/src/core/repo-language.js +47 -0
  33. package/src/core/run-trace.js +10 -0
  34. package/src/core/version-yml.js +68 -36
  35. package/src/i18n/core.en.js +31 -0
  36. package/src/i18n/core.ko.js +31 -0
  37. package/src/i18n/doctor.en.js +89 -0
  38. package/src/i18n/doctor.ko.js +89 -0
  39. package/src/i18n/en.js +141 -0
  40. package/src/i18n/ide.en.js +102 -0
  41. package/src/i18n/ide.ko.js +102 -0
  42. package/src/i18n/index.js +73 -0
  43. package/src/i18n/ko.js +134 -0
  44. package/src/i18n/migrate.en.js +66 -0
  45. package/src/i18n/migrate.ko.js +66 -0
  46. package/src/i18n/options.en.js +104 -0
  47. package/src/i18n/options.ko.js +104 -0
  48. package/src/i18n/summary.en.js +70 -0
  49. package/src/i18n/summary.ko.js +70 -0
  50. package/src/index.js +46 -28
  51. package/src/ui/banner.js +8 -6
  52. package/src/ui/env-plan.js +19 -18
  53. package/src/ui/prompts.js +25 -24
  54. package/src/ui/readline-engine.js +18 -10
  55. package/src/ui/skills-prompts.js +8 -7
  56. package/src/ui/status-cards.js +38 -38
  57. package/src/ui/summary.js +103 -111
package/src/ui/summary.js CHANGED
@@ -1,70 +1,71 @@
1
- // 완료 요약 출력 (.sh print_summary L5438~5626 등가). 전부 stderr.
2
- // ctx: { mode, types:[], version, counters:{ workflows, utilModules } }
1
+ // Completion summary, printed to stderr after an install.
2
+ // ctx: { mode, types:[], version, counters:{ workflows, workflowFiles, utilModules }, ... }
3
+ // All wording lives in src/i18n/summary.*.js so the screen follows the display language.
3
4
  import { existsSync, readdirSync } from "node:fs";
4
5
  import { join } from "node:path";
5
6
  import { WORKFLOW_COMMON_PREFIX } from "../core/paths.js";
7
+ import { t } from "../i18n/index.js";
6
8
 
7
9
  const SEPARATOR = "────────────────────────────────────────";
8
10
 
9
- export function printSummary(ctx, targetRoot = ".") {
11
+ export function printSummary(ctx, targetRoot = ".", write = null) {
10
12
  const { mode, types = [], version = "", counters = {} } = ctx || {};
11
- const deployBranchName = ctx?.deployBranch || "develop"; // #477 — 설정된 배포 브랜치명으로 안내
12
- const deployBranchReady = ctx?.deployBranchReady === true; // #490 — 마법사가 이번 실행에서 존재/생성 확인함
13
- const err = (s = "") => process.stderr.write(`${s}\n`);
14
- // 색상은 TTY일 때만 (.sh YELLOW/CYAN/NC 등가)
15
- const isTty = !!process.stderr.isTTY;
13
+ const deployBranchName = ctx?.deployBranch || "develop";
14
+ const deployBranchReady = ctx?.deployBranchReady === true; // the wizard confirmed or created it in this run
15
+ const err = (s = "") => (write ? write(`${s}\n`) : process.stderr.write(`${s}\n`));
16
+ // Colors only on a TTY.
17
+ const isTty = write ? false : !!process.stderr.isTTY;
16
18
  const BOLD = isTty ? "\u001b[1m" : "";
17
19
  const YELLOW = isTty ? "\x1b[1;33m" : "";
18
20
  const CYAN = isTty ? "\x1b[0;36m" : "";
19
21
  const NC = isTty ? "\x1b[0m" : "";
20
22
  const utilModulesCopied = counters.utilModules ?? 0;
21
- // README.md 가 없으면 버전 섹션 추가를 건너뛰므로 화면에도 추가·자동 갱신으로 표시하지 않는다 (#677)
23
+ // Without a README the version section is skipped, so do not claim it was added or auto-updated.
22
24
  const hasReadme = existsSync(join(targetRoot, "README.md"));
23
25
 
24
26
  err("");
25
27
  err(SEPARATOR);
26
28
  err("");
27
- err("✨ projectops Setup Complete!");
29
+ err(t("done.title"));
28
30
  err("");
29
31
  err(SEPARATOR);
30
32
  err("");
31
- err("통합된 기능:");
33
+ err(t("done.integrated"));
32
34
 
33
- // 모드별 체크리스트 (.sh L5449~5481)
34
35
  switch (mode) {
35
36
  case "full":
36
- err(" ✅ 버전 관리 시스템 (version.yml)");
37
- if (hasReadme) err(" ✅ README.md 자동 버전 업데이트");
38
- err(" ✅ GitHub Actions 워크플로우");
39
- if (utilModulesCopied > 0) err(` ✅ 유틸리티 모듈 (${utilModulesCopied} 개)`);
40
- err(" ✅ 이슈/PR/Discussion 템플릿");
41
- err(" ✅ CodeRabbit AI 리뷰 설정");
42
- err(" ✅ .gitignore 필수 항목");
43
- err(" ✅ 템플릿 설정 가이드 (SETUP-GUIDE.md)");
37
+ err(` ✅ ${t("done.feat.version")}`);
38
+ if (hasReadme) err(` ✅ ${t("done.feat.readme")}`);
39
+ err(` ✅ ${t("done.feat.workflows")}`);
40
+ if (utilModulesCopied > 0) err(` ✅ ${t("done.feat.util", { count: utilModulesCopied })}`);
41
+ err(` ✅ ${t("done.feat.templates")}`);
42
+ err(` ✅ ${t("done.feat.coderabbit")}`);
43
+ err(` ✅ ${t("done.feat.gitignore")}`);
44
+ err(` ✅ ${t("done.feat.guide")}`);
44
45
  break;
45
46
  case "version":
46
- err(" ✅ 버전 관리 시스템 (version.yml)");
47
- if (hasReadme) err(" ✅ README.md 자동 버전 업데이트");
48
- err(" ✅ .gitignore 필수 항목");
49
- err(" ✅ 템플릿 설정 가이드 (SETUP-GUIDE.md)");
47
+ err(` ✅ ${t("done.feat.version")}`);
48
+ if (hasReadme) err(` ✅ ${t("done.feat.readme")}`);
49
+ err(` ✅ ${t("done.feat.gitignore")}`);
50
+ err(` ✅ ${t("done.feat.guide")}`);
50
51
  break;
51
52
  case "workflows":
52
- err(" ✅ GitHub Actions 워크플로우");
53
- if (utilModulesCopied > 0) err(` ✅ 유틸리티 모듈 (${utilModulesCopied} 개)`);
54
- err(" ✅ 템플릿 설정 가이드 (SETUP-GUIDE.md)");
53
+ err(` ✅ ${t("done.feat.workflows")}`);
54
+ if (utilModulesCopied > 0) err(` ✅ ${t("done.feat.util", { count: utilModulesCopied })}`);
55
+ err(` ✅ ${t("done.feat.guide")}`);
55
56
  break;
56
57
  case "issues":
57
- err(" ✅ 이슈/PR/Discussion 템플릿");
58
+ err(` ✅ ${t("done.feat.templates")}`);
58
59
  break;
59
60
  case "skills":
60
- err(" ✅ Agent Skill 설치 (Claude, Cursor, Gemini, Codex, PI)");
61
+ err(` ✅ ${t("done.feat.skills")}`);
61
62
  break;
62
63
  }
63
64
 
64
- // skills 모드: 파일/워크플로우 추가 없으므로 간결하게 종료 (.sh L5484~5491)
65
+ // Skills mode adds no files or workflows, so the screen ends here.
65
66
  if (mode === "skills") {
66
67
  err("");
67
- err(" 📖 TEMPLATE REPO: https://github.com/Cassiiopeia/projectops");
68
+ err(` 📖 TEMPLATE REPO: https://github.com/Cassiiopeia/projectops`);
68
69
  err("");
69
70
  err(SEPARATOR);
70
71
  err("");
@@ -72,34 +73,32 @@ export function printSummary(ctx, targetRoot = ".") {
72
73
  }
73
74
 
74
75
  err("");
75
- err("추가된 파일:");
76
- err(` 📄 version.yml (버전: ${version}, 타입: ${types.join(",")})`);
77
- err(hasReadme ? " 📝 README.md (버전 섹션 추가)" : " 📝 README.md 없음 — 버전 섹션 추가를 건너뜀");
76
+ err(t("done.addedFiles"));
77
+ err(` 📄 ${t("done.versionYml", { version, types: types.join(",") })}`);
78
+ err(` 📝 ${hasReadme ? t("done.readmeAdded") : t("done.readmeMissing")}`);
78
79
  err("");
79
- err("추가된 워크플로우:");
80
+ err(t("done.addedWorkflows"));
80
81
 
81
- // 실제 복사·교체된 파일 목록만 출력 (#473 — 디렉토리 스캔은 존재 파일 전부를 "새로 설치됨"으로
82
- // 오표기했고 카운터와 소스가 달라 (0개) 불일치가 났다. 복사 엔진의 copiedFiles가 유일한 소스.)
82
+ // Only files the copy engine actually wrote are listed (copiedFiles is the single source).
83
83
  const copiedFiles = counters.workflowFiles ?? [];
84
84
  const commonWorkflows = copiedFiles.filter((f) => f.startsWith(`${WORKFLOW_COMMON_PREFIX}-`));
85
85
  const typeWorkflows = copiedFiles.filter((f) => !f.startsWith(`${WORKFLOW_COMMON_PREFIX}-`));
86
86
 
87
87
  if (copiedFiles.length > 0) {
88
- err(` 📦 새로 설치·갱신됨 (${copiedFiles.length}개):`);
88
+ err(` 📦 ${t("done.installedCount", { count: copiedFiles.length })}`);
89
89
  for (const wf of commonWorkflows) err(` 📌 ${wf}`);
90
90
  for (const wf of typeWorkflows) err(` 🎯 ${wf}`);
91
91
  } else {
92
- err(" 📦 새로 설치·갱신된 워크플로우 없음 — 모두 최신 상태");
92
+ err(` 📦 ${t("done.noneInstalled")}`);
93
93
  }
94
94
 
95
- // #654 — 사용자 수정본을 유지한 워크플로: 무엇이 얼마나 다른지, 어떻게 병합하는지 안내한다.
96
- // 건너뛴 것이 없으면 아무것도 출력하지 않는다 (기존 출력 유지).
95
+ // Workflows the user modified are kept as-is: say what differs and how to merge.
97
96
  const kept = ctx?.skippedConflicts ?? [];
98
97
  if (kept.length > 0) {
99
98
  err("");
100
- err(` ${YELLOW}✋ 수정한 워크플로 ${kept.length}개는 덮어쓰지 않고 그대로 두었습니다${NC}`);
99
+ err(` ${YELLOW}✋ ${t("done.keptTitle", { count: kept.length })}${NC}`);
101
100
  for (const k of kept) {
102
- const cnt = k.added != null && k.removed != null ? ` (새 템플릿 기준 추가 ${k.added}줄, 삭제 ${k.removed}줄)` : "";
101
+ const cnt = k.added != null && k.removed != null ? ` (${t("done.keptDiff", { added: k.added, removed: k.removed })})` : "";
103
102
  err(` 📝 ${k.filename}${cnt}`);
104
103
  if (k.incoming) {
105
104
  err(` diff -u .github/workflows/${k.filename} ${k.incoming}`);
@@ -107,19 +106,18 @@ export function printSummary(ctx, targetRoot = ".") {
107
106
  }
108
107
  }
109
108
  if (kept.some((k) => k.incoming)) {
110
- err(" 💡 새 템플릿 사본은 저장소에 추적되지 않는 .github/.projectops/incoming/ 에 있습니다.");
111
- err(" 필요한 변경만 워크플로에 직접 옮겨 반영하세요.");
109
+ err(` 💡 ${t("done.keptHint1")}`);
110
+ err(` ${t("done.keptHint2")}`);
112
111
  }
113
112
  }
114
113
 
115
- // #673 — 업데이트 기준점이 없어 수정 여부를 구분하지 못하고 교체한 파일: 원본이 .bak 으로만 남으므로
116
- // 사용자가 자기 수정이 사라졌는지 git diff 없이도 알 수 있어야 한다.
114
+ // No baseline to tell whether the file was edited: it was replaced and the original kept as .bak.
117
115
  const replaced = ctx?.replacedBak ?? [];
118
116
  if (replaced.length > 0) {
119
117
  err("");
120
- err(` ${YELLOW}⚠️ 수정 여부를 확인할 수 없어 ${replaced.length}개 워크플로를 새 템플릿으로 교체하고 원본을 .bak 으로 백업했습니다${NC}`);
118
+ err(` ${YELLOW}⚠️ ${t("done.replacedTitle", { count: replaced.length })}${NC}`);
121
119
  for (const f of replaced) err(` 💾 .github/workflows/${f}.bak`);
122
- err(" 직접 수정한 내용이 있었다면 .bak 에서 옮겨 오세요. (업데이트 기준점은 이번 실행으로 새로 기록됩니다)");
120
+ err(` ${t("done.replacedHint")}`);
123
121
  }
124
122
 
125
123
  err("");
@@ -128,150 +126,144 @@ export function printSummary(ctx, targetRoot = ".") {
128
126
  err(" └─ changelog_manager.py");
129
127
  err("");
130
128
 
131
- // util 모듈 목록 (.sh L5566~5581) — 타입별 .github/util/{type}/*/ 스캔
132
129
  if (utilModulesCopied > 0) {
133
- err(" 🧙 유틸리티 모듈:");
134
- for (const t of types) {
135
- const utilDir = join(targetRoot, ".github/util", t);
130
+ err(` 🧙 ${t("done.utilTitle")}`);
131
+ for (const type of types) {
132
+ const utilDir = join(targetRoot, ".github/util", type);
136
133
  if (!existsSync(utilDir)) continue;
137
134
  let entries = [];
138
- try { entries = readdirSync(utilDir, { withFileTypes: true }); } catch { /* 무시 */ }
135
+ try { entries = readdirSync(utilDir, { withFileTypes: true }); } catch { /* ignore */ }
139
136
  for (const e of entries) {
140
- if (e.isDirectory()) err(` ├─ ${e.name} (${t})`);
137
+ if (e.isDirectory()) err(` ├─ ${e.name} (${type})`);
141
138
  }
142
139
  }
143
140
  err("");
144
141
  }
145
142
 
146
- // 프로젝트 타입별 안내 (.sh L5583~5599)
147
143
  if (types.includes("spring")) {
148
- err(" 💡 Spring 프로젝트 추가 설정:");
149
- err(" • build.gradle의 버전 정보가 자동 동기화됩니다");
150
- err(" • CI/CD 워크플로우에서 GitHub Secrets 설정이 필요합니다");
151
- err(" • 자세한 설정 방법: .github/workflows/project-types/spring/README.md");
144
+ err(` 💡 ${t("done.spring.title")}`);
145
+ err(` • ${t("done.spring.1")}`);
146
+ err(` • ${t("done.spring.2")}`);
147
+ err(` • ${t("done.spring.3")}`);
152
148
  err("");
153
149
  }
154
150
  if (types.includes("flutter") && utilModulesCopied > 0) {
155
- err(" 💡 Flutter 배포 마법사 사용법:");
151
+ err(` 💡 ${t("done.flutter.title")}`);
156
152
  err(" • iOS TestFlight: .github/util/flutter/testflight-wizard/testflight-wizard.html");
157
153
  err(" • Android Play Store: .github/util/flutter/playstore-wizard/playstore-wizard.html");
158
154
  err(" • Firebase App Distribution: .github/util/flutter/firebase-wizard/firebase-wizard.html");
159
- err(" • 브라우저에서 열어 필요한 정보 입력 후 파일 생성");
155
+ err(` • ${t("done.flutter.open")}`);
160
156
  err("");
161
157
  }
162
158
 
163
159
  err(" 📖 TEMPLATE REPO: https://github.com/Cassiiopeia/projectops");
164
- err(" 📚 워크플로우 가이드: .github/workflows/project-types/README.md");
165
- // #493 — 이번 실행의 마이그레이션 기록. "뭐가 남았고 AI에게 어떻게 시키는지"가 바로 보이게 행동 유도형으로 안내.
166
- // 실행 기록 위치 (#561) — 무슨 일이 있었는지 나중에 확인할 자리를 알린다.
160
+ err(` 📚 ${t("done.workflowGuide")}: .github/workflows/project-types/README.md`);
167
161
 
168
- // 설치 후 검증 결과 (#549) — 문제가 있을 때만 펼치고, 정상이면 한 줄로 압축한다.
162
+ // Post-install verification: expand only when something is wrong, one line otherwise.
169
163
  const verification = ctx?.verification;
170
164
  if (verification) {
171
165
  const unresolved = verification.unresolved || [];
172
166
  err(SEPARATOR);
173
167
  err("");
174
168
  if (unresolved.length === 0) {
175
- err("✅ 설치 검증: 치환되지 않은 값 없음");
169
+ err(`✅ ${t("done.verify.ok")}`);
176
170
  } else {
177
- err(`${YELLOW}⚠️ 설치 검증: 치환되지 않은 값 ${unresolved.length}건${NC}`);
171
+ err(`${YELLOW}⚠️ ${t("done.verify.bad", { count: unresolved.length })}${NC}`);
178
172
  err("");
179
- err(" 아래 위치에 템플릿 값이 그대로 남아 있습니다. 그대로 두면 배포 시점에 실패합니다.");
180
- err(" (프로젝트 구조를 자동으로 찾지 못한 경우입니다 — 직접 값을 채워주세요)");
173
+ err(` ${t("done.verify.explain1")}`);
174
+ err(` ${t("done.verify.explain2")}`);
181
175
  err("");
182
176
  for (const u of unresolved.slice(0, 10)) {
183
177
  err(` ${u.filename}:${u.line} ${u.token}`);
184
178
  }
185
- if (unresolved.length > 10) err(` … 외 ${unresolved.length - 10}건`);
179
+ if (unresolved.length > 10) err(` ${t("done.verify.more", { count: unresolved.length - 10 })}`);
186
180
  }
187
181
  err("");
188
182
 
189
- // 필요한 Secret 목록 — 설치된 워크플로우 기준이라 "이 레포에 실제로 필요한 것"만 나온다.
183
+ // Secrets needed by the installed workflows only.
190
184
  const secrets = verification.secrets;
191
185
  if (secrets && secrets.size > 0) {
192
- err(`${CYAN}🔑 등록이 필요한 GitHub Secret (${secrets.size}개)${NC}`);
186
+ err(`${CYAN}🔑 ${t("done.secrets.title", { count: secrets.size })}${NC}`);
193
187
  err(" → Repository Settings > Secrets and variables > Actions");
194
188
  err("");
195
189
  for (const [name, users] of secrets) {
196
- const where = users.length > 2 ? `${users.slice(0, 2).join(", ")} 외 ${users.length - 2}개` : users.join(", ");
190
+ const where = users.length > 2 ? t("done.secrets.more", { list: users.slice(0, 2).join(", "), count: users.length - 2 }) : users.join(", ");
197
191
  err(` ${name}`);
198
192
  err(` └ ${where}`);
199
193
  }
200
194
  err("");
201
- err(" 💡 등록 전까지 해당 워크플로우는 실패합니다. 쓰지 않는 워크플로우라면 무시해도 됩니다.");
195
+ err(` 💡 ${t("done.secrets.hint")}`);
202
196
  err("");
203
197
  }
204
198
  }
205
199
 
206
- // 필수 3가지 작업 안내 (.sh L5605~5625 — 원문 유지)
207
200
  err(SEPARATOR);
208
201
  err("");
209
- err(`${YELLOW}⚠️ 다음 작업을 확인해주세요:${NC}`);
202
+ err(`${YELLOW}⚠️ ${t("done.todo.title")}${NC}`);
210
203
  err("");
211
- // #551 — PAT는 없어도 릴리스가 완주한다. 있으면 후속 자동화가 더 매끄러워질 뿐이다.
212
- err(" 1️⃣ (선택) GitHub Personal Access Token 설정");
213
- err(" → 없어도 릴리스는 정상 동작합니다. 등록하면 릴리스 후 후속 워크플로우가");
214
- err(" 더 확실하게 이어지고, 브랜치 보호 규칙이 있어도 자동 머지가 가능합니다.");
204
+ // A PAT is optional: releases complete without it, it only makes follow-up automation smoother.
205
+ err(` 1️⃣ ${t("done.todo.pat")}`);
206
+ err(` → ${t("done.todo.pat1")}`);
207
+ err(` ${t("done.todo.pat2")}`);
215
208
  err(" → Repository Settings > Secrets > Actions");
216
209
  err(" → Secret Name: _GITHUB_PAT_TOKEN / Scopes: repo, workflow");
217
210
  err("");
218
- // #490 — 마법사가 브랜치를 직접 생성(또는 존재 확인)했으면 같은 작업을 재지시하지 않는다
211
+ // Do not re-instruct a task the wizard already did.
219
212
  if (deployBranchReady) {
220
- err(` 2️⃣ ✅ ${deployBranchName} 브랜치 준비 완료: 마법사가 확인 및 생성했습니다 (추가 작업 불필요)`);
213
+ err(` 2️⃣ ✅ ${t("done.todo.branchReady", { branch: deployBranchName })}`);
221
214
  } else {
222
- err(` 2️⃣ ${deployBranchName} 브랜치 생성 (아직 없다면)`);
215
+ err(` 2️⃣ ${t("done.todo.branch", { branch: deployBranchName })}`);
223
216
  err(` → git checkout -b ${deployBranchName} && git push -u origin ${deployBranchName}`);
224
217
  }
225
218
  err("");
226
- // #569 — 선택한 것만 안내한다. 끈 기능의 설정법을 보여주면 무엇을 해야 하는지 흐려진다.
219
+ // Only explain what the user selected: instructions for a disabled feature blur what to do.
227
220
  let step = 3;
228
221
  if (ctx?.aiPrSummary !== false) {
229
- err(` ${step}️⃣ (선택) AI가 다듬은 릴리스 노트 받기`);
230
- err(" → 지금 상태로도 릴리스 노트는 나옵니다. 커밋 내용을 분석해 만들기 때문에");
231
- err(" 아무 설정을 하지 않아도 됩니다. 아래는 문장을 더 매끄럽게 하고 싶을 때만 하세요.");
222
+ err(` ${step}️⃣ ${t("done.todo.ai")}`);
223
+ err(` → ${t("done.todo.ai1")}`);
224
+ err(` ${t("done.todo.ai2")}`);
232
225
  err("");
233
- err(" ① Google AI Studio에서 키 발급 (무료 · 신용카드 불필요 · 2분)");
226
+ err(` ① ${t("done.todo.aiKey")}`);
234
227
  err(" https://aistudio.google.com/apikey");
235
- err(" ② 이 저장소에 등록");
228
+ err(` ② ${t("done.todo.aiRegister")}`);
236
229
  err(" Settings > Secrets and variables > Actions > New repository secret");
237
- err(` ${BOLD}Name: GEMINI_API_KEY${NC} Secret: 발급받은 키(AIza... 로 시작)`);
230
+ err(` ${BOLD}Name: GEMINI_API_KEY${NC} Secret: ${t("done.todo.aiSecret")}`);
238
231
  err("");
239
- err(" 💡 다른 서비스를 쓴다면 이름만 바꿔 등록하면 됩니다 — 등록한 것이 자동으로 쓰입니다.");
240
- err(" OPENAI_API_KEY · ANTHROPIC_API_KEY · GROQ_API_KEY · MISTRAL_API_KEY");
232
+ err(` 💡 ${t("done.todo.aiOthers")}`);
233
+ err(" OPENAI_API_KEY, ANTHROPIC_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY");
241
234
  err("");
242
235
  step++;
243
236
  }
244
237
  if (ctx?.codeReviewCoderabbit === true) {
245
- err(` ${step}️⃣ CodeRabbit 활성화`);
246
- err(" → https://coderabbit.ai 로그인 → GitHub 앱 설치 → 이 저장소에 접근 권한(grant access) 부여");
247
- err(" → 이 단계를 안 하면 워크플로우는 켜져도 PR에 리뷰 댓글이 달리지 않습니다");
248
- err(" → 공개 저장소는 무료입니다 (시간당 3회 리뷰 제한)");
238
+ err(` ${step}️⃣ ${t("done.todo.coderabbit")}`);
239
+ err(` → ${t("done.todo.coderabbit1")}`);
240
+ err(` → ${t("done.todo.coderabbit2")}`);
241
+ err(` → ${t("done.todo.coderabbit3")}`);
249
242
  err("");
250
243
  step++;
251
244
  }
252
- // #668 — 라벨 동기화는 issue-labels.yml 이 바뀐 push 에서만 도는데, 설치 직후 첫 push 에서
253
- // 실행 기록이 없는 경우가 실측됐다. 라벨이 없으면 상태 라벨 자동화가 조용히 무시되므로 1회 수동 실행을 안내한다.
254
- err(` ${step}️⃣ (권장) 템플릿 라벨 만들기`);
255
- err(" → Actions 탭 > PROJECT-SYNC-GITHUB-LABELS > Run workflow 를 한 번 실행하세요.");
256
- err(" → 안 하면 작업전·작업중 같은 상태 라벨이 레포에 없어 라벨 자동화가 무시됩니다.");
245
+ // Label sync only runs on pushes that change issue-labels.yml, and the first push after install can miss it.
246
+ // Status labels missing means label automation is silently ignored, so ask for one manual run.
247
+ err(` ${step}️⃣ ${t("done.todo.labels")}`);
248
+ err(` → ${t("done.todo.labels1")}`);
249
+ err(` → ${t("done.todo.labels2")}`);
257
250
  err("");
258
251
  step++;
259
252
  err(SEPARATOR);
260
253
  err("");
261
- err(`${CYAN}📖 자세한 설정 방법은 다음 파일을 참고하세요:${NC}`);
254
+ err(`${CYAN}📖 ${t("done.guide")}${NC}`);
262
255
  err(" → PROJECTOPS-SETUP-GUIDE.md");
263
256
  err("");
264
- // 기록 안내는 완료 화면의 맨 끝에 둔다 (#561) — Secret 목록이 길면 위쪽은 스크롤에 묻혀
265
- // 사용자가 "로그 얘기가 없다"고 느낀다. 문제가 생겼을 때 가장 먼저 찾는 것이므로 마지막 자리다.
257
+ // The run record goes last: with a long secret list the top scrolls away, and this is the first thing people need when something breaks.
266
258
  if (ctx?.logFile || ctx?.logDir || ctx?.migrationGuidePath) {
267
- err(`${CYAN}📁 이번 실행 기록${NC}`);
259
+ err(`${CYAN}📁 ${t("done.log.title")}${NC}`);
268
260
  if (ctx?.logFile || ctx?.logDir) {
269
- err(` 로그: ${ctx.logFile || `${ctx.logDir}/`}`);
270
- if (ctx.traceFile) err(` 이벤트: ${ctx.traceFile}`);
261
+ err(` ${t("done.log.log")}: ${ctx.logFile || `${ctx.logDir}/`}`);
262
+ if (ctx.traceFile) err(` ${t("done.log.events")}: ${ctx.traceFile}`);
271
263
  }
272
- if (ctx?.migrationGuidePath) err(` 가이드: ${ctx.migrationGuidePath}`);
273
- err(" 무엇을 어떤 근거로 정했는지 전부 남아 있습니다 (저장소에 추적되지 않음).");
274
- err(" 💡 문제가 생기면 AI Agent에게 \"실행 로그 확인해줘\"라고 요청하세요.");
264
+ if (ctx?.migrationGuidePath) err(` ${t("done.log.guide")}: ${ctx.migrationGuidePath}`);
265
+ err(` ${t("done.log.note")}`);
266
+ err(` 💡 ${t("done.log.ask")}`);
275
267
  err("");
276
268
  }
277
269
  }