Skip to content

Commit 96ca973

Browse files
snooclaude
andcommitted
feat: v3.1.0 - Template upgrade feature
- Add template version metadata (<!-- codesyncer-version: X.Y.Z -->) - Detect outdated templates during `codesyncer update` - Offer upgrade with automatic backup (.backup.YYYY-MM-DD) - Preview option before upgrading - Preserve project variables during upgrade New files: - src/utils/template-version.ts - src/utils/template-upgrader.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dbd968b commit 96ca973

24 files changed

+689
-1
lines changed

README.ko.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ codesyncer validate --verbose # 파일 경로 표시
237237
3. ✅ 언어 설정 자동 감지 (한국어/영어)
238238
4. ✅ 새 파일 생성 전 사용자 확인 요청
239239
5. ✅ 기존 커스터마이징 내용 보존
240+
6.**v3.1.0 신규**: 구버전 템플릿 감지 및 업그레이드 제안
240241

241242
**출력 예시:**
242243
```
@@ -264,6 +265,29 @@ This file allows Claude to automatically load context at session start.
264265
✅ Update complete!
265266
```
266267

268+
#### 템플릿 업그레이드 (v3.1.0 신규)
269+
270+
CodeSyncer를 새 버전으로 업데이트하면, 기존 템플릿 파일이 구버전일 수 있습니다. `update` 명령어가 이를 자동으로 감지합니다:
271+
272+
```
273+
📦 새 버전 감지: v3.1.0
274+
275+
📁 my-project/
276+
• CLAUDE.md (v3.0.0 → v3.1.0)
277+
• COMMENT_GUIDE.md (버전 없음 → v3.1.0)
278+
279+
? 2개 템플릿을 업그레이드할까요?
280+
> 예 - 업그레이드 (기존 파일 .backup으로 백업)
281+
아니오 - 건너뛰기
282+
미리보기 - 변경 파일만 확인
283+
```
284+
285+
**기능:**
286+
- 🔍 버전 메타데이터를 읽어 구버전 템플릿 자동 감지
287+
- 💾 업그레이드 전 `.backup` 파일 생성 (예: `CLAUDE.md.backup.2024-01-17`)
288+
- 📋 업그레이드 시 프로젝트 변수 (프로젝트 이름, 기술 스택) 보존
289+
- 👁️ 변경 사항을 미리 확인하는 미리보기 옵션
290+
267291
**`codesyncer update` 실행 후:**
268292

269293
다음 중 하나를 선택하여 변경사항을 적용하세요:

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ Sync your project with the latest templates and features:
237237
3. ✅ Auto-detects your language settings (English/Korean)
238238
4. ✅ Prompts before creating any new files
239239
5. ✅ Preserves your existing customizations
240+
6.**NEW in v3.1.0**: Detects outdated templates and offers upgrade
240241

241242
**Example output:**
242243
```
@@ -264,6 +265,29 @@ Option 2) Apply immediately in current session
264265
✅ Update complete!
265266
```
266267

268+
#### Template Upgrade (NEW in v3.1.0)
269+
270+
When you update CodeSyncer to a new version, your existing template files may be outdated. The `update` command now automatically detects this:
271+
272+
```
273+
📦 New Version Detected: v3.1.0
274+
275+
📁 my-project/
276+
• CLAUDE.md (v3.0.0 → v3.1.0)
277+
• COMMENT_GUIDE.md (no version → v3.1.0)
278+
279+
? Upgrade 2 template(s)?
280+
> Yes - Upgrade (backup existing files to .backup)
281+
No - Skip
282+
Preview - Show files only
283+
```
284+
285+
**Features:**
286+
- 🔍 Automatically detects outdated templates by reading version metadata
287+
- 💾 Creates `.backup` files before upgrading (e.g., `CLAUDE.md.backup.2024-01-17`)
288+
- 📋 Preserves project variables (project name, tech stack) during upgrade
289+
- 👁️ Preview option to see what would change
290+
267291
**After running `codesyncer update`:**
268292

269293
Choose one of these options to apply changes:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codesyncer",
3-
"version": "3.0.1",
3+
"version": "3.1.0",
44
"description": "Claude forgets everything when the session ends. CodeSyncer makes it remember.",
55
"keywords": [
66
"ai-collaboration",

src/commands/update.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ import * as path from 'path';
66
import { UpdateOptions, Language, RepositoryInfo } from '../types';
77
import { scanForRepositories, hasMasterSetup, detectMonorepo, scanMonorepoPackages } from '../utils/scanner';
88
import { getMonorepoToolName } from '../utils/monorepo-helpers';
9+
import {
10+
scanTemplateVersions,
11+
getOutdatedTemplates,
12+
TemplateStatus,
13+
getCurrentVersion,
14+
} from '../utils/template-version';
15+
import {
16+
upgradeTemplates,
17+
getTemplateVarsFromContext,
18+
formatUpgradeSummary,
19+
UpgradeResult,
20+
} from '../utils/template-upgrader';
921

1022
export async function updateCommand(options: UpdateOptions) {
1123
const isDryRun = options.dryRun || false;
@@ -777,10 +789,168 @@ rm .codesyncer/UPDATE_GUIDE.md
777789
}
778790
}
779791

792+
// Template Upgrade Check
793+
await checkAndOfferTemplateUpgrade(currentDir, foundRepos, lang, isDryRun);
794+
780795
if (isDryRun) {
781796
console.log(chalk.bold.yellow('\n✅ Dry run complete! No files were modified.\n'));
782797
console.log(chalk.gray('Run without --dry-run to apply changes.\n'));
783798
} else {
784799
console.log(chalk.bold.green('\n✅ Update complete!\n'));
785800
}
786801
}
802+
803+
/**
804+
* Check for outdated templates and offer upgrade option
805+
*
806+
* @codesyncer-context Template upgrade feature - checks version metadata in files
807+
* @codesyncer-decision [2026-01-17] Backup files before upgrade for user safety
808+
*/
809+
async function checkAndOfferTemplateUpgrade(
810+
currentDir: string,
811+
repos: RepositoryInfo[],
812+
lang: Language,
813+
isDryRun: boolean
814+
): Promise<void> {
815+
const currentVersion = getCurrentVersion();
816+
817+
// Collect all outdated templates across all repos
818+
const allOutdated: { repo: string; claudeDir: string; templates: TemplateStatus[] }[] = [];
819+
820+
for (const repo of repos) {
821+
const claudeDir = path.join(repo.path, '.claude');
822+
if (await fs.pathExists(claudeDir)) {
823+
const outdated = await getOutdatedTemplates(claudeDir);
824+
if (outdated.length > 0) {
825+
allOutdated.push({
826+
repo: repo.name,
827+
claudeDir,
828+
templates: outdated,
829+
});
830+
}
831+
}
832+
}
833+
834+
// Also check root .claude if it exists (for single repo setup)
835+
const rootClaudeDir = path.join(currentDir, '.claude');
836+
if (await fs.pathExists(rootClaudeDir)) {
837+
const rootOutdated = await getOutdatedTemplates(rootClaudeDir);
838+
if (rootOutdated.length > 0) {
839+
// Avoid duplicates
840+
const alreadyIncluded = allOutdated.some((o) => o.claudeDir === rootClaudeDir);
841+
if (!alreadyIncluded) {
842+
allOutdated.push({
843+
repo: path.basename(currentDir),
844+
claudeDir: rootClaudeDir,
845+
templates: rootOutdated,
846+
});
847+
}
848+
}
849+
}
850+
851+
if (allOutdated.length === 0) {
852+
return; // No outdated templates found
853+
}
854+
855+
// Count total outdated files
856+
const totalOutdated = allOutdated.reduce((sum, o) => sum + o.templates.length, 0);
857+
858+
// Display upgrade notification
859+
console.log(chalk.bold.blue('\n📦 ' + (lang === 'ko' ? '새 버전 감지' : 'New Version Detected') + `: v${currentVersion}\n`));
860+
861+
// Show outdated files grouped by repo
862+
allOutdated.forEach(({ repo, templates }) => {
863+
console.log(chalk.yellow(` 📁 ${repo}/`));
864+
templates.forEach((t) => {
865+
const fileName = path.basename(t.file);
866+
const versionInfo = t.currentVersion
867+
? `v${t.currentVersion} → v${t.latestVersion}`
868+
: `(${lang === 'ko' ? '버전 없음' : 'no version'}) → v${t.latestVersion}`;
869+
console.log(chalk.gray(` • ${fileName} (${versionInfo})`));
870+
});
871+
});
872+
console.log();
873+
874+
// Prompt for upgrade
875+
const { upgradeChoice } = await inquirer.prompt([
876+
{
877+
type: 'list',
878+
name: 'upgradeChoice',
879+
message: lang === 'ko'
880+
? `${totalOutdated}개 템플릿을 업그레이드할까요?`
881+
: `Upgrade ${totalOutdated} template(s)?`,
882+
choices: [
883+
{
884+
name: lang === 'ko'
885+
? '예 - 업그레이드 (기존 파일 .backup으로 백업)'
886+
: 'Yes - Upgrade (backup existing files to .backup)',
887+
value: 'yes',
888+
},
889+
{
890+
name: lang === 'ko' ? '아니오 - 건너뛰기' : 'No - Skip',
891+
value: 'no',
892+
},
893+
{
894+
name: lang === 'ko' ? '미리보기 - 변경 파일만 확인' : 'Preview - Show files only',
895+
value: 'preview',
896+
},
897+
],
898+
default: 'yes',
899+
},
900+
]);
901+
902+
if (upgradeChoice === 'preview') {
903+
console.log(chalk.bold('\n' + (lang === 'ko' ? '📋 업그레이드 대상 파일:' : '📋 Files to upgrade:')));
904+
allOutdated.forEach(({ repo, templates }) => {
905+
templates.forEach((t) => {
906+
console.log(chalk.gray(` • ${repo}/.claude/${path.basename(t.file)}`));
907+
});
908+
});
909+
console.log();
910+
return;
911+
}
912+
913+
if (upgradeChoice === 'no') {
914+
console.log(chalk.gray(lang === 'ko' ? ' 템플릿 업그레이드 건너뜀\n' : ' Template upgrade skipped\n'));
915+
return;
916+
}
917+
918+
// Perform upgrade
919+
if (isDryRun) {
920+
console.log(chalk.yellow(lang === 'ko'
921+
? '\n [DRY RUN] 업그레이드 수행됨 (실제 파일 변경 없음)\n'
922+
: '\n [DRY RUN] Upgrade would be performed (no actual changes)\n'));
923+
allOutdated.forEach(({ repo, templates }) => {
924+
templates.forEach((t) => {
925+
console.log(chalk.gray(` • ${repo}/.claude/${path.basename(t.file)}`));
926+
});
927+
});
928+
return;
929+
}
930+
931+
const spinner = ora(lang === 'ko' ? '템플릿 업그레이드 중...' : 'Upgrading templates...').start();
932+
933+
const allResults: UpgradeResult[] = [];
934+
935+
for (const { claudeDir, templates } of allOutdated) {
936+
const vars = await getTemplateVarsFromContext(claudeDir, lang);
937+
const results = await upgradeTemplates(templates, { lang, vars, dryRun: isDryRun });
938+
allResults.push(...results);
939+
}
940+
941+
spinner.succeed(lang === 'ko' ? '템플릿 업그레이드 완료!' : 'Templates upgraded!');
942+
943+
// Show summary
944+
console.log();
945+
console.log(formatUpgradeSummary(allResults, lang));
946+
console.log();
947+
948+
// Show backup notice
949+
const successfulWithBackup = allResults.filter((r) => r.success && r.backupPath);
950+
if (successfulWithBackup.length > 0) {
951+
console.log(chalk.cyan(lang === 'ko'
952+
? '💡 백업 파일에서 커스터마이징 내용을 복사할 수 있습니다.'
953+
: '💡 You can copy customizations from backup files.'));
954+
console.log();
955+
}
956+
}

src/templates/en/architecture.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,5 @@ No API endpoints discovered.
145145
---
146146

147147
*This document is auto-generated and managed by CodeSyncer.*
148+
149+
<!-- codesyncer-version: 3.1.0 -->

src/templates/en/claude.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,5 @@ grep -r "@codesyncer-rule" ./
279279
---
280280

281281
*This collaboration system is open source. Suggest improvements at [CodeSyncer GitHub](https://github.com/bitjaru/codesyncer)!*
282+
283+
<!-- codesyncer-version: 3.1.0 -->

src/templates/en/comment_guide.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,3 +580,5 @@ After writing code:
580580
**Last Updated**: [TODAY]
581581

582582
*Comments are the documentation. Record all context in code.*
583+
584+
<!-- codesyncer-version: 3.1.0 -->

src/templates/en/decisions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,5 @@ grep "@codesyncer-decision" src/services/PaymentService.ts
225225
**Last Updated**: [TODAY]
226226

227227
*All important decisions are permanently recorded. This is your team's knowledge asset.*
228+
229+
<!-- codesyncer-version: 3.1.0 -->

src/templates/en/master.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,5 @@ Both formats work identically, so use whichever you prefer!
177177

178178
*CodeSyncer - Persistent context, controlled inference, live architecture sync for Claude Code*
179179
*Currently Supported: Claude Code | Coming Soon: Cursor, GitHub Copilot, Continue.dev*
180+
181+
<!-- codesyncer-version: 3.1.0 -->

src/templates/en/root_claude.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,5 @@ grep -r "payment" ./*/
239239
---
240240

241241
*CodeSyncer is open source: https://github.com/bitjaru/codesyncer*
242+
243+
<!-- codesyncer-version: 3.1.0 -->

0 commit comments

Comments
 (0)