@@ -10,7 +10,7 @@ import * as fs from 'fs-extra';
1010import * as path from 'path' ;
1111import { Language } from '../types' ;
1212import { detectLanguage } from '../utils/language' ;
13- import { hasMasterSetup , detectMonorepo , scanMonorepoPackages , scanForRepositories } from '../utils/scanner' ;
13+ import { hasMasterSetup , hasSingleRepoSetup , detectMonorepo , scanMonorepoPackages , scanForRepositories } from '../utils/scanner' ;
1414
1515export 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