projectops 4.40.0 → 4.41.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
@@ -9,7 +9,7 @@
9
9
  **Status: actively maintained.** npm `latest` is exactly what is on the `main` branch; work in progress on `develop` is never published. Versions can still ship several times a day. To stay on a version you have tested, run `npx projectops@<version>` ([releases](https://github.com/Cassiiopeia/projectops/releases)).
10
10
 
11
11
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
12
- ## Latest version : v4.39.0 (2026-10-10)
12
+ ## Latest version : v4.40.0 (2026-10-10)
13
13
 
14
14
  [View full version history](CHANGELOG.md)
15
15
 
@@ -327,6 +327,7 @@ Browse and search everything on the **[docs site](https://cassiiopeia.github.io/
327
327
  - [Issues](https://github.com/Cassiiopeia/projectops/issues): bug reports and feature requests
328
328
  - Finished issues are marked with the `status: done` label and closed automatically when a release is merged (`close_on_release`).
329
329
  - [CONTRIBUTING.md](CONTRIBUTING.md): contribution guide
330
+ - [ARCHITECTURE.md](ARCHITECTURE.md): how the code fits together and how to add a flag, option, provider, language or skill
330
331
  - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): how we expect everyone to behave, and where to report problems
331
332
  - [SECURITY.md](SECURITY.md): please report security vulnerabilities privately, not in a public issue
332
333
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.40.0",
3
+ "version": "4.41.0",
4
4
  "description": "ProjectOps: installer CLI for a fully automated GitHub project management template (versioning, CI/CD, release notes, agent skills)",
5
5
  "keywords": [
6
6
  "devops",
@@ -68,7 +68,10 @@ export function localChecks(cwd = ".") {
68
68
  }
69
69
 
70
70
  add({ name: t("doctor.integ.name"), purpose: t("doctor.integ.purpose"), status: "OK",
71
- value: t("doctor.integ.versions", { tpl: existing.templateVersion || "unknown", proj: existing.version }) });
71
+ // 템플릿 버전이 기록돼 있지 않으면 "vunknown" 이 아니라 사람이 읽는 문구로 — 옛 설치는 version.yml 에 이 값이 없다
72
+ value: existing.templateVersion
73
+ ? t("doctor.integ.versions", { tpl: existing.templateVersion, proj: existing.version })
74
+ : t("doctor.integ.versionsNoTemplate", { proj: existing.version }) });
72
75
 
73
76
  const wfDir = join(cwd, PATHS.workflowsDir);
74
77
  const files = existsSync(wfDir)
@@ -30,7 +30,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
30
30
  force = true, now, today, templateVersion = "unknown",
31
31
  deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, aiPrSummary = true,
32
32
  changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
33
- deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null } = context;
33
+ deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null, storeLocales = null } = context;
34
34
 
35
35
  // project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
36
36
  const pathMarkers = new Map();
@@ -53,7 +53,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
53
53
  deployValues,
54
54
  updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
55
55
  templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, aiPrSummary, optionsDate: today,
56
- changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
56
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
57
57
  })), { version, versionCode });
58
58
 
59
59
  // 2. README 버전 섹션
@@ -144,6 +144,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
144
144
  // 상태 라벨 표기(#776): 저장값 > (신규 en / 기존 ko). 기존 한글 레포에는 영문 전환을 한 번 제안한다(아래).
145
145
  const closeOnRelease = existing?.options?.closeOnRelease ?? (existing ? null : true); // #771 묻지 않는다
146
146
  const excludedWorkflows = existing?.options?.excludedWorkflows ?? null; // #810 저장값 보존
147
+ const storeLocales = existing?.options?.storeLocales ?? null; // #829 저장값 보존
147
148
  // Projects 보드 동기화(#716): 묻지 않는다. 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외 (index.js 와 같은 규칙).
148
149
  const projectsSync = existing?.options?.projectsSync
149
150
  ?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml"));
@@ -330,7 +331,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
330
331
  const { now, today } = clock || utcNow();
331
332
  const ctx = createContext({
332
333
  mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
333
- codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows,
334
+ codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales,
334
335
  repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
335
336
  // #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
336
337
  recordMode: existing?.templateMode === "full" ? "full" : "version",
@@ -425,7 +426,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
425
426
  migrationGuidePath = appendGuideEntry(cwd, {
426
427
  now, mode, types, repoName,
427
428
  templateFrom: existing?.templateVersion || "", templateTo: templateVersion,
428
- options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
429
+ options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
429
430
  branches: { defaultBranch: branch, deployBranch, ready: deployBranchReady, created: deployBranchCreated },
430
431
  breaking: breakingReport, migrations: migrationsResult, orphans: orphanReport,
431
432
  events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
@@ -17,7 +17,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
17
17
  const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
18
18
  now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
19
19
  changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
20
- deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = context;
20
+ deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null, storeLocales = null } = context;
21
21
 
22
22
  const pathMarkers = new Map();
23
23
  for (const [t] of paths) pathMarkers.set(t, markerForType(t));
@@ -34,7 +34,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
34
34
  // mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
35
35
  // 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
36
36
  templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
37
- changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
37
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
38
38
  }));
39
39
  addVersionSectionToReadme(version, targetRoot);
40
40
  copyScripts(tempDir, targetRoot);
package/src/context.js CHANGED
@@ -32,6 +32,7 @@ export function createContext(overrides = {}) {
32
32
  language: null, // 레포 문구 언어 (#769). null=미설정(신규 en / 기존 레포 ko)
33
33
  labelStyle: null, // 상태 라벨 표기 (#776). null=미설정(신규 en / 기존 레포 ko)
34
34
  excludedWorkflows: null, // 설치하지 않을 워크플로우 파일명 (#810). null=없음
35
+ storeLocales: null, // 스토어 릴리스 노트 언어 (#829). null=미설정 → 현행 동작 유지
35
36
  appRelease: null, // 앱 심사 배포 레포인가 (#553). null=미설정(키 기록 안 함)
36
37
  templateVersion: "",
37
38
  tempDir: "",
@@ -37,6 +37,8 @@ export function copyScripts(tempDir, targetRoot = ".") {
37
37
  "close_issues_on_release.py",
38
38
  // Play Store/TestFlight 배포 모드를 코드 설정(.github/config/store-deploy.json)에서 읽는다 (#767).
39
39
  "store_deploy_config.py",
40
+ // 스토어 릴리스 노트를 언어별로 준비 (#829). version.yml 의 store_locales 가 없으면 아무것도 하지 않는다.
41
+ "store_notes.py",
40
42
  "changelog_providers/_common.py", "changelog_providers/ladder.py",
41
43
  "changelog_providers/commit.py", "changelog_providers/copilot.py",
42
44
  "changelog_providers/openai_compatible.py",
@@ -1,7 +1,10 @@
1
- // 워크플로우 복사 엔진 (.sh copy_workflows + _copy_workflows_for_type 등가).
2
- // 실측: template_integrator.sh 3398~3815.
3
- // 대화형 3지선(기존 파일 충돌)은 copyWorkflowsInteractive(async)가 결정 Map을 만들어
4
- // 동기 엔진(copyWorkflows)에 hooks.decisions로 전달한다 — 기존 시그니처·force 동작 무변경.
1
+ // 워크플로우 복사 엔진. 구 template_integrator.sh(#458 로 삭제됨)의 copy_workflows 를 포팅한 것이다.
2
+ // 파일 안의 ".sh ... 등가" 표기는 그 삭제된 스크립트를 가리킨다 — 줄 번호는 더 이상 찾을 수 없다.
3
+ //
4
+ // 충돌(기존 파일과 다름)의 처리 결정은 hooks.decisions(Map<파일명, 'skip'|'backup'|'template'>)로 받는다.
5
+ // - 실제 마법사(commands/interactive.js)는 listWorkflowConflicts 로 충돌 목록을 얻어 직접 Map 을 만든다.
6
+ // - copyWorkflowsInteractive 는 onConflict 콜백으로 같은 Map 을 만들어 주는 보조 API 다. 프로덕션 경로는 쓰지 않고
7
+ // 테스트(copy-workflows-conflict.test.js)만 쓴다.
5
8
  import { join, basename } from "node:path";
6
9
  import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
7
10
  import { PATHS } from "../paths.js";
@@ -378,7 +381,7 @@ function applyDecision(decision, srcDir, workflowsDir, filename, counters, trace
378
381
  });
379
382
  }
380
383
 
381
- // 대상 워크플로우 디렉토리에서 changed(충돌) 파일 목록만 뽑는다 — copyWorkflowsInteractive의 사전 조사용.
384
+ // 대상 워크플로우 디렉토리에서 changed(충돌) 파일 목록만 뽑는다 — 마법사(commands/interactive.js)와 copyWorkflowsInteractive 가 쓴다.
382
385
  // copyWorkflows 본체와 동일한 classify 기준을 써야 결정 Map이 실제 처리 대상과 1:1로 맞는다.
383
386
  export function listWorkflowConflicts(context, tempDir, targetRoot = ".") {
384
387
  const { types = [], paths = new Map(), deployTarget = "docker-ssh", repoName = "", resolvers = {}, branch = "", deployBranch = "" } = context;
@@ -1,6 +1,6 @@
1
1
  // 템플릿 다운로드 후 제거하는 항목 (복사 제외 목록).
2
2
  // ⚠️ CLAUDE.md "템플릿 전용 파일 추가" 규칙의 동기화 지점:
3
- // template_initializer.sh(삭제) / 이 파일(복사 제외)
3
+ // template_initializer.py(삭제) / 이 파일(복사 제외)
4
4
  // (구 template_integrator.sh/.ps1의 배열은 #458 EOF로 소멸 — 여기가 유일한 복사 제외 지점)
5
5
  export const DOCS_TO_REMOVE = [
6
6
  "CONTRIBUTING.md",
@@ -11,7 +11,8 @@
11
11
  // tier - "safe" : 신형이 같은 기능을 대체(순수 리네임). 공존 시 이중 트리거 실해
12
12
  // → 확인 1회 후 자동 무해화(.bak) / 삭제
13
13
  // "confirm" : 배포 파이프라인일 수 있음(그 레포의 유일한 현역 배포 가능성)
14
- // → 자동으로 건드리지 않고 안내만. --force에서도 불변
14
+ // → 기본은 건드리지 않고 완료 요약·doctor 에 목록만 안내한다.
15
+ // --remove-legacy(비대화형) 또는 대화형 확인(기본 아니오)이 있을 때만 .bak 으로 무해화 (#809)
15
16
  // "ask" : 사용자 콘텐츠가 담긴 폴더 등 — 손실 없는 이동이지만 사용자 소유물 (#476)
16
17
  // → 대화형: 확인 후 이동 / 비대화형: 자동 조치 없이 안내만
17
18
  // file - 정확한 파일명 (글롭 금지 — 사용자 커스텀 보호의 핵심)
@@ -105,6 +105,9 @@ export const VERSION_YML = [
105
105
  { path: "metadata.template.options.excluded_workflows", parserKey: "excludedWorkflows", type: "string[]",
106
106
  description: "Workflow file names the update must never install. Deleted files are also not restored; a copy is kept in .github/.projectops/incoming/.",
107
107
  edit: "by hand" },
108
+ { path: "metadata.template.options.store_locales", parserKey: "storeLocales", type: "string[]",
109
+ description: "Store release-note languages, first is the default (for example [\"ko-KR\", \"en-US\", \"ja-JP\", \"zh-CN\"]). Missing = one text for every store language, as before. Per-language text comes from store_notes in CHANGELOG.json; a language without text gets the default-language text (the stores reject an empty What's New).",
110
+ edit: "by hand" },
108
111
  { path: "metadata.template.options.code_review.coderabbit", parserKey: "codeReviewCoderabbit", type: "boolean", description: "CodeRabbit config installed.", edit: "wizard" },
109
112
  { path: "metadata.template.options.code_review.ai_summary", parserKey: "aiPrSummary", type: "boolean", default: true, description: "AI PR summary workflow installed.", edit: "wizard (--ai-summary)" },
110
113
  { path: "metadata.template.options.changelog.provider", parserKey: "changelogProvider", type: "enum",
@@ -115,6 +115,7 @@ const COMMENTS = {
115
115
  appRelease: "whether releases go through App Store / Play Store review",
116
116
  deploy: "deploy settings remembered by the wizard (non-sensitive, safe to edit by hand)",
117
117
  excluded: "workflow files the update must never install (safe to edit by hand)",
118
+ storeLocales: "store release-note languages, first is the default (missing = one text for every store language)",
118
119
  },
119
120
  ko: {
120
121
  versionCode: "app build number",
@@ -130,6 +131,7 @@ const COMMENTS = {
130
131
  appRelease: "앱스토어·플레이스토어 심사로 이어지는 배포인가",
131
132
  deploy: "마법사가 기억하는 배포 설정 (비민감 / 직접 수정 가능)",
132
133
  excluded: "업데이트가 절대 설치하지 않을 워크플로우 파일 (직접 수정 가능)",
134
+ storeLocales: "스토어 릴리스 노트 언어 목록, 첫 항목이 기본 언어 (미기재면 모든 스토어 언어에 같은 문구)",
133
135
  },
134
136
  };
135
137
  const commentsFor = (lang) => COMMENTS[lang === "ko" ? "ko" : "en"];
@@ -158,7 +160,7 @@ export function resolveUpdatedBy(existingContent = "", cwd = ".") {
158
160
  export function parseTemplateOptions(content) {
159
161
  const out = { deploy: null, publish: null, secretBackup: null,
160
162
  changelogProvider: null, changelogBaseUrl: null, codeReviewCoderabbit: null, aiPrSummary: null, projectsSync: null,
161
- deployBranch: null, intent: null, semverAuto: null, appRelease: null, labelStyle: null, closeOnRelease: null, language: null, excludedWorkflows: null };
163
+ deployBranch: null, intent: null, semverAuto: null, appRelease: null, labelStyle: null, closeOnRelease: null, language: null, excludedWorkflows: null, storeLocales: null };
162
164
  // deploy_branch는 metadata 직속(#456) — template.options 밖이라 별도로 스캔한다.
163
165
  for (const line of String(content || "").split("\n")) {
164
166
  if (line.startsWith("#")) continue;
@@ -266,6 +268,12 @@ export function parseTemplateOptions(content) {
266
268
  out.excludedWorkflows = m[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
267
269
  continue;
268
270
  }
271
+ // 스토어 릴리스 노트 언어 (#829) — 인라인 배열, 첫 항목이 기본 언어. 미기재면 현행 동작(한 문구를 모든 언어에).
272
+ m = line.match(/^\s+store_locales:\s*\[([^\]]*)\]/);
273
+ if (m) {
274
+ out.storeLocales = m[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
275
+ continue;
276
+ }
269
277
  // 프로젝트 성격(#553) — 앱 심사로 이어지는 레포인가. 워크플로우와 스킬이 같은 값을 본다.
270
278
  m = line.match(/^\s+app_release:\s*["']?(true|false)["']?/);
271
279
  if (m) {
@@ -470,7 +478,7 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
470
478
  if (templateOptions) {
471
479
  const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today,
472
480
  changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true, aiPrSummary = true, intent = null, mode = null,
473
- semverAuto = true, appRelease = null, labelStyle = null, closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = templateOptions;
481
+ semverAuto = true, appRelease = null, labelStyle = null, closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null, storeLocales = null } = templateOptions;
474
482
  const publishJson = `[${publishTargets.map((t) => `"${t}"`).join(",")}]`;
475
483
  // intent(프로젝트 성격, #485) — 미지정이면 deploy/publish에서 역추론해 기록 (재통합 시 진입 질문 생략용)
476
484
  const intentVal = intent || inferIntent(deployTarget, publishTargets) || "manual";
@@ -496,6 +504,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
496
504
  // 레포 문구 언어(#769) — 미지정이면 키를 쓰지 않는다(기존 레포 무변화).
497
505
  // 설치 제외 목록(#810) — 전체 재생성이라 다시 쓰지 않으면 사용자가 적은 목록이 사라진다.
498
506
  if (excludedWorkflows && excludedWorkflows.length) out += ` excluded_workflows: [${excludedWorkflows.map((f) => `"${f}"`).join(", ")}] # ${C.excluded}\n`;
507
+ // 스토어 릴리스 노트 언어(#829) — 미지정이면 키를 쓰지 않는다. 전체 재생성이라 다시 쓰지 않으면 사용자가 적은 값이 사라진다.
508
+ if (storeLocales && storeLocales.length) out += ` store_locales: [${storeLocales.map((l) => `"${l}"`).join(", ")}] # ${C.storeLocales}\n`;
499
509
  if (language) out += ` language: ${language} # ${C.language}\n`;
500
510
  if (labelStyle) out += ` label_style: ${labelStyle} # ${C.labelStyle}\n`;
501
511
  // 앱 심사 배포 레포 여부(#553) — 미지정이면 키를 쓰지 않는다(기존 레포 무변화).
@@ -7,6 +7,7 @@ export default {
7
7
  "doctor.integ.notIntegrated": "The template is not integrated in this folder.",
8
8
  "doctor.integ.howTo": "To integrate: npx projectops",
9
9
  "doctor.integ.versions": "Template v{tpl} / Project v{proj}",
10
+ "doctor.integ.versionsNoTemplate": "Template version not recorded / Project v{proj}",
10
11
  "doctor.wf.name": "Installed workflows",
11
12
  "doctor.wf.purpose": "Automation running in this repository",
12
13
  "doctor.count": "{n}",
@@ -7,6 +7,7 @@ export default {
7
7
  "doctor.integ.notIntegrated": "이 폴더에는 템플릿이 통합되어 있지 않습니다.",
8
8
  "doctor.integ.howTo": "통합하려면: npx projectops",
9
9
  "doctor.integ.versions": "템플릿 v{tpl} / 프로젝트 v{proj}",
10
+ "doctor.integ.versionsNoTemplate": "템플릿 버전 기록 없음 / 프로젝트 v{proj}",
10
11
  "doctor.wf.name": "설치된 워크플로우",
11
12
  "doctor.wf.purpose": "이 저장소에서 도는 자동화",
12
13
  "doctor.count": "{n}개",
package/src/index.js CHANGED
@@ -260,6 +260,7 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
260
260
  // (사용자가 version.yml에 직접 쓰거나 스킬이 기록한 값을 그대로 유지).
261
261
  appRelease: existing?.options?.appRelease ?? null,
262
262
  excludedWorkflows: existing?.options?.excludedWorkflows ?? null, // #810 저장값 보존
263
+ storeLocales: existing?.options?.storeLocales ?? null, // #829 저장값 보존 (전체 재생성이라 안 넘기면 사라진다)
263
264
  // 상태 라벨 표기(#776): 플래그 > 저장값 > (신규 en / 기존 ko). 기존 레포의 라벨 이름은 업데이트만으로 바뀌지 않는다.
264
265
  // 릴리스 시 완료 이슈 닫기(#771): 저장값 보존, 없으면 신규만 true. 기존 레포는 키를 만들지 않아 현행 유지.
265
266
  closeOnRelease: existing?.options?.closeOnRelease ?? (existing ? null : true),
@@ -361,7 +362,7 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
361
362
  migrationGuidePath = appendGuideEntry(cwd, {
362
363
  now, mode: opts.mode, types, repoName,
363
364
  templateFrom: existing?.templateVersion || "", templateTo: context.templateVersion,
364
- options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease, language: context.language, labelStyle: context.labelStyle, closeOnRelease: context.closeOnRelease, projectsSync: context.projectsSync, excludedWorkflows: context.excludedWorkflows },
365
+ options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease, language: context.language, labelStyle: context.labelStyle, closeOnRelease: context.closeOnRelease, projectsSync: context.projectsSync, excludedWorkflows: context.excludedWorkflows, storeLocales: context.storeLocales },
365
366
  branches: { defaultBranch: branch, deployBranch: context.deployBranch || "develop", ready: null, created: null },
366
367
  breaking: breakingReport, migrations: migrationsResult, orphans: { cleaned: [], pending: orphanPending },
367
368
  events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },