@@ -42,6 +42,7 @@ def __init__(self, registry_path: str = "projects_registry.json"):
4242 self .registry_path = Path (registry_path )
4343 self .errors = []
4444 self .warnings = []
45+ self .unregistered_projects = []
4546 self .projects = []
4647 self .validation_status = "PENDING"
4748
@@ -156,9 +157,26 @@ def validate_project_paths(self):
156157
157158 if not project_file .exists ():
158159 self .errors .append (
159- f"Missing project file : { path } "
160+ f"Registered project not found in repository : { path } "
160161 )
161162
163+ def validate_repository_consistency (self ):
164+ """Validate repository and registry consistency."""
165+
166+ registry_dirs = self .get_registry_project_directories ()
167+ discovered_dirs = self .discover_repository_projects ()
168+
169+ unregistered_projects = sorted (
170+ discovered_dirs - registry_dirs
171+ )
172+
173+ self .unregistered_projects = unregistered_projects
174+
175+ for project in self .unregistered_projects :
176+ self .errors .append (
177+ f"Project exists in repository but is missing from registry: { project } "
178+ )
179+
162180 def validate_keywords (self ):
163181 """Validate keywords field."""
164182
@@ -177,6 +195,76 @@ def validate_keywords(self):
177195 f"{ project ['name' ]} has no keywords."
178196 )
179197
198+ def get_scan_directories (self ):
199+ """Return repository roots that contain projects."""
200+
201+ root = self .registry_path .parent
202+
203+ return {
204+ directory .name
205+ for directory in root .iterdir ()
206+ if directory .is_dir ()
207+ and directory .name != "__pycache__"
208+ and not directory .name .startswith ("." )
209+ }
210+
211+ def get_registry_project_directories (self ):
212+ """Return project directories defined in the registry."""
213+
214+ registry_dirs = set ()
215+
216+ for project in self .projects :
217+ path = project .get ("path" )
218+
219+ if not path :
220+ continue
221+
222+ registry_dirs .add (
223+ Path (path ).parent .as_posix ()
224+ )
225+
226+ return registry_dirs
227+
228+ def discover_repository_projects (self ):
229+ """Discover project directories from the repository."""
230+
231+ root = self .registry_path .parent
232+ discovered_projects = set ()
233+
234+ for directory_name in self .get_scan_directories ():
235+ directory = root / directory_name
236+
237+ if (
238+ not directory .exists ()
239+ or not directory .is_dir ()
240+ ):
241+ continue
242+
243+ # Standalone project (e.g. expression_parser)
244+ if directory_name == "expression_parser" :
245+ if any (directory .glob ("*.py" )):
246+ discovered_projects .add (
247+ directory .relative_to (root ).as_posix ()
248+ )
249+ continue
250+
251+ # Category containing project folders
252+ for project_dir in directory .iterdir ():
253+ if not project_dir .is_dir ():
254+ continue
255+
256+ if project_dir .name == "__pycache__" :
257+ continue
258+
259+ # Only consider directories that contain at least one
260+ # top-level Python file.
261+ if any (project_dir .glob ("*.py" )):
262+ discovered_projects .add (
263+ project_dir .relative_to (root ).as_posix ()
264+ )
265+
266+ return discovered_projects
267+
180268 def validate (self ):
181269 """Run all validation checks."""
182270
@@ -191,6 +279,7 @@ def validate(self):
191279 self .validate_duplicate_names ()
192280 self .validate_duplicate_paths ()
193281 self .validate_project_paths ()
282+ self .validate_repository_consistency ()
194283 self .validate_keywords ()
195284
196285 if self .errors :
@@ -207,6 +296,9 @@ def report(self, json_output=False):
207296 "errors" : len (self .errors ),
208297 "warnings" : len (self .warnings ),
209298 "status" : self .validation_status .lower (),
299+ "repository_consistency" : {
300+ "unregistered_projects" : self .unregistered_projects ,
301+ },
210302 },
211303 indent = 2 ,
212304 )
@@ -238,9 +330,23 @@ def report(self, json_output=False):
238330
239331 if self .errors :
240332 print ("\n Errors:" )
333+
241334 for error in self .errors :
335+ if error .startswith (
336+ "Project exists in repository but is missing from registry:"
337+ ):
338+ continue
339+
242340 print (f" - { error } " )
243341
342+ if self .unregistered_projects :
343+ print ("\n Repository Consistency:" )
344+
345+ print ("\n Unregistered projects:" )
346+
347+ for project in self .unregistered_projects :
348+ print (f" - { project } " )
349+
244350 if self .warnings :
245351 print ("\n Warnings:" )
246352 for warning in self .warnings :
0 commit comments