Skip to content

Commit 8d8a90d

Browse files
bitjaruclaude
andcommitted
fix: validate command now supports single-repo mode
- Added single-repo mode detection in validate command - Single-repo mode validates .claude/ directory instead of .codesyncer/ - Shows Mode info (Single Repository vs Multi Repository) - Added demo.tape for VHS terminal recording - Version bump to 2.7.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 35174cd commit 8d8a90d

File tree

5 files changed

+195
-51
lines changed

5 files changed

+195
-51
lines changed

demo.gif

801 KB
Loading

demo.mp4

455 KB
Binary file not shown.

demo.tape

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# VHS Demo Script for CodeSyncer
2+
# https://github.com/charmbracelet/vhs
3+
4+
Output demo.gif
5+
Output demo.mp4
6+
7+
Set FontSize 16
8+
Set Width 1000
9+
Set Height 600
10+
Set Theme "Dracula"
11+
Set Padding 20
12+
Set Shell "bash"
13+
14+
# Setup: Create and enter test directory
15+
Hide
16+
Type "cd /tmp && rm -rf codesyncer-demo && mkdir codesyncer-demo && cd codesyncer-demo && git init -q"
17+
Enter
18+
Sleep 500ms
19+
Type "clear"
20+
Enter
21+
Show
22+
Sleep 500ms
23+
24+
# Title
25+
Type "# CodeSyncer - AI Coding Assistant Memory System"
26+
Enter
27+
Sleep 1s
28+
29+
# Show version
30+
Type "codesyncer -v"
31+
Enter
32+
Sleep 2s
33+
34+
# Init command
35+
Type "codesyncer init"
36+
Enter
37+
Sleep 2s
38+
39+
# Select English (default is already English, just press Enter)
40+
Enter
41+
Sleep 1s
42+
43+
# Project name (Enter for default)
44+
Enter
45+
Sleep 500ms
46+
47+
# GitHub username (type demo username)
48+
Type "demo-user"
49+
Enter
50+
Sleep 2s
51+
52+
# Show result
53+
Type "# ✅ Setup complete! Let's check the files:"
54+
Enter
55+
Sleep 1s
56+
57+
Type "ls -la .claude/"
58+
Enter
59+
Sleep 2s
60+
61+
Type "head -30 CLAUDE.md"
62+
Enter
63+
Sleep 3s
64+
65+
# Final step - show what to do next
66+
Type "# Next step: Run 'claude' and type:"
67+
Enter
68+
Sleep 500ms
69+
Type "# Read .claude/SETUP_GUIDE.md and follow the instructions"
70+
Enter
71+
Sleep 3s

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": "2.7.2",
3+
"version": "2.7.3",
44
"description": "AI-powered multi-repository collaboration system - Works with Claude Code, Cursor, GitHub Copilot, and more",
55
"keywords": [
66
"ai-collaboration",

src/commands/validate.ts

Lines changed: 123 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as fs from 'fs-extra';
1010
import * as path from 'path';
1111
import { Language } from '../types';
1212
import { detectLanguage } from '../utils/language';
13-
import { hasMasterSetup, detectMonorepo, scanMonorepoPackages, scanForRepositories } from '../utils/scanner';
13+
import { hasMasterSetup, hasSingleRepoSetup, detectMonorepo, scanMonorepoPackages, scanForRepositories } from '../utils/scanner';
1414

1515
export interface ValidateOptions {
1616
verbose?: boolean;
@@ -76,9 +76,12 @@ export async function validateCommand(options: ValidateOptions = {}): Promise<vo
7676
// Check if CodeSyncer is set up
7777
const spinner = ora(isKo ? '설정 검증 중...' : 'Validating setup...').start();
7878

79-
// 1. Check master setup
79+
// 1. Detect setup mode (single-repo vs multi-repo)
8080
const hasMaster = await hasMasterSetup(currentDir);
81-
if (!hasMaster) {
81+
const hasSingleRepo = await hasSingleRepoSetup(currentDir);
82+
const isSingleRepoMode = !hasMaster && hasSingleRepo;
83+
84+
if (!hasMaster && !hasSingleRepo) {
8285
result.valid = false;
8386
result.errors.push({
8487
code: 'NO_SETUP',
@@ -90,63 +93,133 @@ export async function validateCommand(options: ValidateOptions = {}): Promise<vo
9093
return;
9194
}
9295

93-
// 2. Check .codesyncer directory
94-
const codesyncerDir = path.join(currentDir, '.codesyncer');
95-
if (!(await fs.pathExists(codesyncerDir))) {
96-
result.warnings.push({
97-
code: 'NO_CODESYNCER_DIR',
98-
message: isKo ? '.codesyncer 폴더가 없습니다' : 'No .codesyncer directory',
99-
suggestion: isKo ? '.codesyncer 폴더를 생성하세요' : 'Create .codesyncer directory',
100-
});
101-
}
96+
// Add mode info
97+
result.info.push({
98+
label: isKo ? '모드' : 'Mode',
99+
value: isSingleRepoMode
100+
? (isKo ? '단일 레포지토리' : 'Single Repository')
101+
: (isKo ? '멀티 레포지토리' : 'Multi Repository'),
102+
});
102103

103-
// 3. Check MASTER_CODESYNCER.md
104-
const masterPath = path.join(codesyncerDir, 'MASTER_CODESYNCER.md');
105-
if (await fs.pathExists(masterPath)) {
106-
result.info.push({
107-
label: 'MASTER_CODESYNCER.md',
108-
value: '✓',
109-
});
104+
// 2. Mode-specific validation
105+
if (isSingleRepoMode) {
106+
// === SINGLE-REPO MODE VALIDATION ===
107+
const claudeDir = path.join(currentDir, '.claude');
110108

111-
// Validate master file content
112-
try {
113-
const masterContent = await fs.readFile(masterPath, 'utf-8');
114-
if (masterContent.includes('[PROJECT_NAME]') || masterContent.includes('[GITHUB_USERNAME]')) {
109+
// Check .claude directory exists
110+
if (await fs.pathExists(claudeDir)) {
111+
result.info.push({
112+
label: '.claude/',
113+
value: '✓',
114+
});
115+
116+
// Check required files in .claude
117+
for (const file of REQUIRED_FILES) {
118+
const filePath = path.join(claudeDir, file);
119+
if (await fs.pathExists(filePath)) {
120+
// Check for unfilled placeholders
121+
try {
122+
const content = await fs.readFile(filePath, 'utf-8');
123+
const placeholders = content.match(/\[([A-Z_]+)\]/g);
124+
if (placeholders && placeholders.length > 0) {
125+
result.warnings.push({
126+
code: 'UNFILLED_PLACEHOLDER',
127+
message: isKo
128+
? `.claude/${file}: 미완성 플레이스홀더 (${placeholders.slice(0, 3).join(', ')})`
129+
: `.claude/${file}: Unfilled placeholders (${placeholders.slice(0, 3).join(', ')})`,
130+
path: filePath,
131+
});
132+
}
133+
} catch {
134+
// Ignore read errors
135+
}
136+
} else {
137+
result.warnings.push({
138+
code: 'MISSING_FILE',
139+
message: isKo ? `.claude/${file} 누락` : `Missing .claude/${file}`,
140+
path: filePath,
141+
suggestion: 'codesyncer update',
142+
});
143+
}
144+
}
145+
}
146+
147+
// Check root CLAUDE.md for single-repo
148+
const rootClaudePath = path.join(currentDir, 'CLAUDE.md');
149+
if (await fs.pathExists(rootClaudePath)) {
150+
result.info.push({
151+
label: 'Root CLAUDE.md',
152+
value: '✓',
153+
});
154+
} else {
155+
result.warnings.push({
156+
code: 'NO_ROOT_CLAUDE',
157+
message: isKo ? '루트 CLAUDE.md가 없습니다 (AI 자동 로드 불가)' : 'No root CLAUDE.md (AI auto-load disabled)',
158+
suggestion: 'codesyncer update',
159+
});
160+
}
161+
162+
} else {
163+
// === MULTI-REPO MODE VALIDATION ===
164+
165+
// 2. Check .codesyncer directory
166+
const codesyncerDir = path.join(currentDir, '.codesyncer');
167+
if (!(await fs.pathExists(codesyncerDir))) {
168+
result.warnings.push({
169+
code: 'NO_CODESYNCER_DIR',
170+
message: isKo ? '.codesyncer 폴더가 없습니다' : 'No .codesyncer directory',
171+
suggestion: isKo ? '.codesyncer 폴더를 생성하세요' : 'Create .codesyncer directory',
172+
});
173+
}
174+
175+
// 3. Check MASTER_CODESYNCER.md
176+
const masterPath = path.join(codesyncerDir, 'MASTER_CODESYNCER.md');
177+
if (await fs.pathExists(masterPath)) {
178+
result.info.push({
179+
label: 'MASTER_CODESYNCER.md',
180+
value: '✓',
181+
});
182+
183+
// Validate master file content
184+
try {
185+
const masterContent = await fs.readFile(masterPath, 'utf-8');
186+
if (masterContent.includes('[PROJECT_NAME]') || masterContent.includes('[GITHUB_USERNAME]')) {
187+
result.warnings.push({
188+
code: 'UNFILLED_PLACEHOLDER',
189+
message: isKo ? 'MASTER_CODESYNCER.md에 미완성 플레이스홀더가 있습니다' : 'MASTER_CODESYNCER.md has unfilled placeholders',
190+
path: masterPath,
191+
});
192+
}
193+
} catch {
115194
result.warnings.push({
116-
code: 'UNFILLED_PLACEHOLDER',
117-
message: isKo ? 'MASTER_CODESYNCER.md에 미완성 플레이스홀더가 있습니다' : 'MASTER_CODESYNCER.md has unfilled placeholders',
195+
code: 'READ_ERROR',
196+
message: isKo ? 'MASTER_CODESYNCER.md를 읽을 수 없습니다' : 'Cannot read MASTER_CODESYNCER.md',
118197
path: masterPath,
119198
});
120199
}
121-
} catch {
122-
result.warnings.push({
123-
code: 'READ_ERROR',
124-
message: isKo ? 'MASTER_CODESYNCER.md를 읽을 수 없습니다' : 'Cannot read MASTER_CODESYNCER.md',
200+
} else {
201+
result.errors.push({
202+
code: 'NO_MASTER',
203+
message: isKo ? 'MASTER_CODESYNCER.md 파일이 없습니다' : 'No MASTER_CODESYNCER.md file',
125204
path: masterPath,
126205
});
206+
result.valid = false;
127207
}
128-
} else {
129-
result.errors.push({
130-
code: 'NO_MASTER',
131-
message: isKo ? 'MASTER_CODESYNCER.md 파일이 없습니다' : 'No MASTER_CODESYNCER.md file',
132-
path: masterPath,
133-
});
134-
result.valid = false;
135-
}
136208

137-
// 4. Check root CLAUDE.md
138-
const rootClaudePath = path.join(currentDir, 'CLAUDE.md');
139-
if (await fs.pathExists(rootClaudePath)) {
140-
result.info.push({
141-
label: 'Root CLAUDE.md',
142-
value: '✓',
143-
});
144-
} else {
145-
result.warnings.push({
146-
code: 'NO_ROOT_CLAUDE',
147-
message: isKo ? '루트 CLAUDE.md가 없습니다 (AI 자동 로드 불가)' : 'No root CLAUDE.md (AI auto-load disabled)',
148-
suggestion: 'codesyncer update',
149-
});
209+
// 4. Check root CLAUDE.md
210+
const rootClaudePath = path.join(currentDir, 'CLAUDE.md');
211+
if (await fs.pathExists(rootClaudePath)) {
212+
result.info.push({
213+
label: 'Root CLAUDE.md',
214+
value: '✓',
215+
});
216+
} else {
217+
result.warnings.push({
218+
code: 'NO_ROOT_CLAUDE',
219+
message: isKo ? '루트 CLAUDE.md가 없습니다 (AI 자동 로드 불가)' : 'No root CLAUDE.md (AI auto-load disabled)',
220+
suggestion: 'codesyncer update',
221+
});
222+
}
150223
}
151224

152225
// 5. Scan repositories

0 commit comments

Comments
 (0)