Skip to content

Commit 8fb9fa6

Browse files
snooclaude
andcommitted
feat: v3.1.1 - Smart merge for template upgrades
- Add section markers to claude.md templates (en/ko) - Implement smart merge: only update marked sections, preserve user content - Add extractSections(), supportsSmartMerge(), smartMergeContent() functions - Add upgradeTemplateWithSmartMerge() for section-aware upgrades - Add 8 tests for smart merge functionality (79 total tests pass) Users can now add custom content to CLAUDE.md and it won't be overwritten when upgrading templates. Only CodeSyncer-managed sections are updated. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 96ca973 commit 8fb9fa6

23 files changed

Lines changed: 432 additions & 23 deletions

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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.1.0",
3+
"version": "3.1.1",
44
"description": "Claude forgets everything when the session ends. CodeSyncer makes it remember.",
55
"keywords": [
66
"ai-collaboration",
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { extractSections, supportsSmartMerge, smartMergeContent } from '../utils/template-upgrader';
2+
3+
describe('template-upgrader', () => {
4+
describe('extractSections', () => {
5+
it('should extract marked sections', () => {
6+
const content = `
7+
Some content before
8+
9+
<!-- codesyncer-section-start:header -->
10+
# Header
11+
Some header content
12+
<!-- codesyncer-section-end:header -->
13+
14+
User added content here
15+
16+
<!-- codesyncer-section-start:footer -->
17+
Footer content
18+
<!-- codesyncer-section-end:footer -->
19+
20+
More user content
21+
`;
22+
23+
const sections = extractSections(content);
24+
expect(sections).toHaveLength(2);
25+
expect(sections[0].name).toBe('header');
26+
expect(sections[0].content).toContain('# Header');
27+
expect(sections[1].name).toBe('footer');
28+
expect(sections[1].content).toContain('Footer content');
29+
});
30+
31+
it('should return empty array for content without sections', () => {
32+
const content = `
33+
# Regular Markdown
34+
No sections here
35+
`;
36+
const sections = extractSections(content);
37+
expect(sections).toHaveLength(0);
38+
});
39+
});
40+
41+
describe('supportsSmartMerge', () => {
42+
it('should return true for content with section markers', () => {
43+
const content = '<!-- codesyncer-section-start:test -->content<!-- codesyncer-section-end:test -->';
44+
expect(supportsSmartMerge(content)).toBe(true);
45+
});
46+
47+
it('should return false for content without section markers', () => {
48+
const content = '# Just regular markdown';
49+
expect(supportsSmartMerge(content)).toBe(false);
50+
});
51+
});
52+
53+
describe('smartMergeContent', () => {
54+
it('should update marked sections while preserving user content', () => {
55+
const existingContent = `
56+
<!-- codesyncer-section-start:header -->
57+
# Old Header
58+
Old content
59+
<!-- codesyncer-section-end:header -->
60+
61+
## User Added Section
62+
This is content the user added themselves
63+
- Custom item 1
64+
- Custom item 2
65+
66+
<!-- codesyncer-section-start:footer -->
67+
Old footer
68+
<!-- codesyncer-section-end:footer -->
69+
70+
## Another User Section
71+
More user content here
72+
73+
<!-- codesyncer-version: 3.0.0 -->
74+
`;
75+
76+
const newTemplateContent = `
77+
<!-- codesyncer-section-start:header -->
78+
# New Header v3.1
79+
Updated header content with new features
80+
<!-- codesyncer-section-end:header -->
81+
82+
Some template content
83+
84+
<!-- codesyncer-section-start:footer -->
85+
New footer with improvements
86+
<!-- codesyncer-section-end:footer -->
87+
88+
<!-- codesyncer-version: 3.1.0 -->
89+
`;
90+
91+
const result = smartMergeContent(existingContent, newTemplateContent);
92+
93+
// Should update the header section
94+
expect(result).toContain('# New Header v3.1');
95+
expect(result).toContain('Updated header content with new features');
96+
expect(result).not.toContain('# Old Header');
97+
98+
// Should preserve user content between sections
99+
expect(result).toContain('## User Added Section');
100+
expect(result).toContain('This is content the user added themselves');
101+
expect(result).toContain('- Custom item 1');
102+
expect(result).toContain('- Custom item 2');
103+
104+
// Should update the footer section
105+
expect(result).toContain('New footer with improvements');
106+
expect(result).not.toContain('Old footer');
107+
108+
// Should preserve user content after footer
109+
expect(result).toContain('## Another User Section');
110+
expect(result).toContain('More user content here');
111+
112+
// Should update version
113+
expect(result).toContain('codesyncer-version: 3.1.0');
114+
expect(result).not.toContain('codesyncer-version: 3.0.0');
115+
});
116+
117+
it('should return new template if existing has no sections', () => {
118+
const existingContent = '# No sections here';
119+
const newTemplateContent = `
120+
<!-- codesyncer-section-start:header -->
121+
# Header
122+
<!-- codesyncer-section-end:header -->
123+
`;
124+
125+
const result = smartMergeContent(existingContent, newTemplateContent);
126+
expect(result).toBe(newTemplateContent);
127+
});
128+
129+
it('should return new template if new template has no sections', () => {
130+
const existingContent = `
131+
<!-- codesyncer-section-start:header -->
132+
# Header
133+
<!-- codesyncer-section-end:header -->
134+
`;
135+
const newTemplateContent = '# No sections here';
136+
137+
const result = smartMergeContent(existingContent, newTemplateContent);
138+
expect(result).toBe(newTemplateContent);
139+
});
140+
141+
it('should handle sections with different sizes', () => {
142+
const existingContent = `
143+
<!-- codesyncer-section-start:short -->
144+
Short
145+
<!-- codesyncer-section-end:short -->
146+
147+
User content
148+
149+
<!-- codesyncer-section-start:long -->
150+
Long content here
151+
With multiple lines
152+
And more lines
153+
<!-- codesyncer-section-end:long -->
154+
`;
155+
156+
const newTemplateContent = `
157+
<!-- codesyncer-section-start:short -->
158+
Now this is a much longer section
159+
With many more lines
160+
Line 3
161+
Line 4
162+
Line 5
163+
<!-- codesyncer-section-end:short -->
164+
165+
Template content
166+
167+
<!-- codesyncer-section-start:long -->
168+
Now short
169+
<!-- codesyncer-section-end:long -->
170+
`;
171+
172+
const result = smartMergeContent(existingContent, newTemplateContent);
173+
174+
// Short section should now be long
175+
expect(result).toContain('Now this is a much longer section');
176+
expect(result).toContain('Line 5');
177+
178+
// Long section should now be short
179+
expect(result).toContain('Now short');
180+
expect(result).not.toContain('With multiple lines');
181+
182+
// User content should be preserved
183+
expect(result).toContain('User content');
184+
});
185+
});
186+
});

src/commands/update.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
getCurrentVersion,
1414
} from '../utils/template-version';
1515
import {
16-
upgradeTemplates,
16+
upgradeTemplatesWithSmartMerge,
1717
getTemplateVarsFromContext,
1818
formatUpgradeSummary,
1919
UpgradeResult,
@@ -934,7 +934,8 @@ async function checkAndOfferTemplateUpgrade(
934934

935935
for (const { claudeDir, templates } of allOutdated) {
936936
const vars = await getTemplateVarsFromContext(claudeDir, lang);
937-
const results = await upgradeTemplates(templates, { lang, vars, dryRun: isDryRun });
937+
// Use smart merge to preserve user content outside marked sections
938+
const results = await upgradeTemplatesWithSmartMerge(templates, { lang, vars, dryRun: isDryRun });
938939
allResults.push(...results);
939940
}
940941

src/templates/en/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,4 @@ No API endpoints discovered.
146146

147147
*This document is auto-generated and managed by CodeSyncer.*
148148

149-
<!-- codesyncer-version: 3.1.0 -->
149+
<!-- codesyncer-version: 3.1.1 -->

src/templates/en/claude.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- codesyncer-section-start:header -->
12
# CLAUDE.md - [PROJECT_NAME] Coding Guide v3.0
23

34
> **Powered by CodeSyncer** - AI Collaboration System
@@ -39,6 +40,7 @@ CodeSyncer helps maintain context between AI sessions through @codesyncer-* tags
3940
```
4041

4142
---
43+
<!-- codesyncer-section-end:header -->
4244

4345
## 🏗️ Project Information
4446
- **Project Name**: [PROJECT_NAME]
@@ -88,6 +90,7 @@ See `.claude/ARCHITECTURE.md` for detailed structure
8890

8991
---
9092

93+
<!-- codesyncer-section-start:comment-rules -->
9194
## 📝 Comment Writing Rules
9295

9396
### Comment Tags (Both formats supported)
@@ -190,6 +193,7 @@ async function deleteUser(id: string) {
190193
"Add template" → Convert repeated pattern to template
191194
"Update stats" → Refresh comment tag statistics
192195
```
196+
<!-- codesyncer-section-end:comment-rules -->
193197

194198
---
195199

@@ -199,6 +203,7 @@ async function deleteUser(id: string) {
199203

200204
---
201205

206+
<!-- codesyncer-section-start:work-process -->
202207
## 🔄 Work Process
203208

204209
### Typical Work Flow
@@ -227,9 +232,11 @@ async function deleteUser(id: string) {
227232
- ✅ Auto-generate comments
228233
- ✅ Add error handling
229234
- ✅ Record inferences with tags
235+
<!-- codesyncer-section-end:work-process -->
230236

231237
---
232238

239+
<!-- codesyncer-section-start:session-checklist -->
233240
## 💡 Session Start Checklist
234241

235242
When AI reads this file, it automatically:
@@ -238,18 +245,22 @@ When AI reads this file, it automatically:
238245
2.**Understand project structure** - Check ARCHITECTURE.md
239246
3.**Check recent discussions** - Review DECISIONS.md
240247
4.**Ready message** - "Ready to work!"
248+
<!-- codesyncer-section-end:session-checklist -->
241249

242250
---
243251

252+
<!-- codesyncer-section-start:related-docs -->
244253
## 📚 Related Documents
245254

246255
- **Comment Guide**: `.claude/COMMENT_GUIDE.md` - Detailed comment writing guide
247256
- **Project Structure**: `.claude/ARCHITECTURE.md` - Folder structure, statistics
248257
- **Discussion Records**: `.claude/DECISIONS.md` - All discussion decisions
249258
- **Master Document**: `../.codesyncer/MASTER_CODESYNCER.md` - Multi-repo switching
259+
<!-- codesyncer-section-end:related-docs -->
250260

251261
---
252262

263+
<!-- codesyncer-section-start:footer -->
253264
## 🔍 Comment Search
254265

255266
All comment tags are searchable:
@@ -279,5 +290,6 @@ grep -r "@codesyncer-rule" ./
279290
---
280291

281292
*This collaboration system is open source. Suggest improvements at [CodeSyncer GitHub](https://github.com/bitjaru/codesyncer)!*
293+
<!-- codesyncer-section-end:footer -->
282294

283-
<!-- codesyncer-version: 3.1.0 -->
295+
<!-- codesyncer-version: 3.1.1 -->

src/templates/en/comment_guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,4 +581,4 @@ After writing code:
581581

582582
*Comments are the documentation. Record all context in code.*
583583

584-
<!-- codesyncer-version: 3.1.0 -->
584+
<!-- codesyncer-version: 3.1.1 -->

src/templates/en/decisions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,4 @@ grep "@codesyncer-decision" src/services/PaymentService.ts
226226

227227
*All important decisions are permanently recorded. This is your team's knowledge asset.*
228228

229-
<!-- codesyncer-version: 3.1.0 -->
229+
<!-- codesyncer-version: 3.1.1 -->

src/templates/en/master.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,4 @@ Both formats work identically, so use whichever you prefer!
178178
*CodeSyncer - Persistent context, controlled inference, live architecture sync for Claude Code*
179179
*Currently Supported: Claude Code | Coming Soon: Cursor, GitHub Copilot, Continue.dev*
180180

181-
<!-- codesyncer-version: 3.1.0 -->
181+
<!-- codesyncer-version: 3.1.1 -->

src/templates/en/root_claude.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,4 @@ grep -r "payment" ./*/
240240

241241
*CodeSyncer is open source: https://github.com/bitjaru/codesyncer*
242242

243-
<!-- codesyncer-version: 3.1.0 -->
243+
<!-- codesyncer-version: 3.1.1 -->

0 commit comments

Comments
 (0)