tree-sitter-analyzer 1.1.3__py3-none-any.whl → 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

@@ -11,15 +11,14 @@ from pathlib import Path
11
11
  from typing import Any
12
12
 
13
13
  from ...file_handler import read_file_partial
14
- from ...security import SecurityValidator
15
14
  from ...utils import setup_logger
16
- from ..utils.path_resolver import PathResolver
15
+ from .base_tool import BaseMCPTool
17
16
 
18
17
  # Set up logging
19
18
  logger = setup_logger(__name__)
20
19
 
21
20
 
22
- class ReadPartialTool:
21
+ class ReadPartialTool(BaseMCPTool):
23
22
  """
24
23
  MCP Tool for reading partial content from code files.
25
24
 
@@ -29,9 +28,7 @@ class ReadPartialTool:
29
28
 
30
29
  def __init__(self, project_root: str = None) -> None:
31
30
  """Initialize the read partial tool."""
32
- self.security_validator = SecurityValidator(project_root)
33
- self.project_root = project_root
34
- self.path_resolver = PathResolver(project_root)
31
+ super().__init__(project_root)
35
32
  logger.info("ReadPartialTool initialized with security validation")
36
33
 
37
34
  def get_tool_schema(self) -> dict[str, Any]:
@@ -9,19 +9,26 @@ converting analysis results into structured table formats for better readability
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ from ...constants import (
13
+ ELEMENT_TYPE_CLASS,
14
+ ELEMENT_TYPE_FUNCTION,
15
+ ELEMENT_TYPE_IMPORT,
16
+ ELEMENT_TYPE_PACKAGE,
17
+ ELEMENT_TYPE_VARIABLE,
18
+ is_element_of_type,
19
+ )
12
20
  from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
13
21
  from ...language_detector import detect_language_from_file
14
- from ...security import SecurityValidator
15
22
  from ...table_formatter import TableFormatter
16
23
  from ...utils import setup_logger
17
24
  from ..utils import get_performance_monitor
18
- from ..utils.path_resolver import PathResolver
25
+ from .base_tool import BaseMCPTool
19
26
 
20
27
  # Set up logging
21
28
  logger = setup_logger(__name__)
22
29
 
23
30
 
24
- class TableFormatTool:
31
+ class TableFormatTool(BaseMCPTool):
25
32
  """
26
33
  MCP Tool for code structure analysis and table formatting.
27
34
 
@@ -31,12 +38,21 @@ class TableFormatTool:
31
38
 
32
39
  def __init__(self, project_root: str = None) -> None:
33
40
  """Initialize the table format tool."""
34
- self.project_root = project_root
41
+ super().__init__(project_root)
35
42
  self.analysis_engine = get_analysis_engine(project_root)
36
- self.security_validator = SecurityValidator(project_root)
37
- self.path_resolver = PathResolver(project_root)
38
43
  self.logger = logger
39
44
 
45
+ def set_project_path(self, project_path: str) -> None:
46
+ """
47
+ Update the project path for all components.
48
+
49
+ Args:
50
+ project_path: New project root directory
51
+ """
52
+ super().set_project_path(project_path)
53
+ self.analysis_engine = get_analysis_engine(project_path)
54
+ logger.info(f"TableFormatTool project path updated to: {project_path}")
55
+
40
56
  def get_tool_schema(self) -> dict[str, Any]:
41
57
  """
42
58
  Get the MCP tool schema for analyze_code_structure.
@@ -178,11 +194,21 @@ class TableFormatTool:
178
194
  def _convert_analysis_result_to_dict(self, result: Any) -> dict[str, Any]:
179
195
  """Convert AnalysisResult to dictionary format expected by TableFormatter"""
180
196
  # Extract elements by type
181
- classes = [e for e in result.elements if e.__class__.__name__ == "Class"]
182
- methods = [e for e in result.elements if e.__class__.__name__ == "Function"]
183
- fields = [e for e in result.elements if e.__class__.__name__ == "Variable"]
184
- imports = [e for e in result.elements if e.__class__.__name__ == "Import"]
185
- packages = [e for e in result.elements if e.__class__.__name__ == "Package"]
197
+ classes = [
198
+ e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)
199
+ ]
200
+ methods = [
201
+ e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
202
+ ]
203
+ fields = [
204
+ e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
205
+ ]
206
+ imports = [
207
+ e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
208
+ ]
209
+ packages = [
210
+ e for e in result.elements if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
211
+ ]
186
212
 
187
213
  # Convert package to expected format
188
214
  package_info = None
@@ -3,25 +3,32 @@
3
3
  Universal Analyze Tool for MCP
4
4
 
5
5
  This tool provides universal code analysis capabilities through the MCP protocol,
6
- supporting multiple languages with both basic and detailed analysis options.
6
+ supporting multiple programming languages with automatic language detection.
7
7
  """
8
8
 
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ from ...constants import (
13
+ ELEMENT_TYPE_CLASS,
14
+ ELEMENT_TYPE_FUNCTION,
15
+ ELEMENT_TYPE_IMPORT,
16
+ ELEMENT_TYPE_PACKAGE,
17
+ ELEMENT_TYPE_VARIABLE,
18
+ is_element_of_type,
19
+ )
12
20
  from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
13
21
  from ...language_detector import detect_language_from_file, is_language_supported
14
- from ...security import SecurityValidator
22
+ from ...mcp.utils import get_performance_monitor
15
23
  from ...utils import setup_logger
16
- from ..utils import get_performance_monitor
17
24
  from ..utils.error_handler import handle_mcp_errors
18
- from ..utils.path_resolver import PathResolver
25
+ from .base_tool import BaseMCPTool
19
26
 
20
27
  # Set up logging
21
28
  logger = setup_logger(__name__)
22
29
 
23
30
 
24
- class UniversalAnalyzeTool:
31
+ class UniversalAnalyzeTool(BaseMCPTool):
25
32
  """
26
33
  Universal MCP Tool for code analysis across multiple languages.
27
34
 
@@ -31,12 +38,21 @@ class UniversalAnalyzeTool:
31
38
 
32
39
  def __init__(self, project_root: str | None = None) -> None:
33
40
  """Initialize the universal analyze tool."""
34
- self.project_root = project_root
41
+ super().__init__(project_root)
35
42
  self.analysis_engine = get_analysis_engine(project_root)
36
- self.security_validator = SecurityValidator(project_root)
37
- self.path_resolver = PathResolver(project_root)
38
43
  logger.info("UniversalAnalyzeTool initialized with security validation")
39
44
 
45
+ def set_project_path(self, project_path: str) -> None:
46
+ """
47
+ Update the project path for all components.
48
+
49
+ Args:
50
+ project_path: New project root directory
51
+ """
52
+ super().set_project_path(project_path)
53
+ self.analysis_engine = get_analysis_engine(project_path)
54
+ logger.info(f"UniversalAnalyzeTool project path updated to: {project_path}")
55
+
40
56
  def get_tool_definition(self) -> dict[str, Any]:
41
57
  """
42
58
  Get MCP tool definition for universal code analysis
@@ -304,31 +320,75 @@ class UniversalAnalyzeTool:
304
320
  [
305
321
  e
306
322
  for e in analysis_result.elements
307
- if e.__class__.__name__ == "Class"
323
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
308
324
  ]
309
325
  ),
310
326
  "methods": len(
311
327
  [
312
328
  e
313
329
  for e in analysis_result.elements
314
- if e.__class__.__name__ == "Function"
330
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
315
331
  ]
316
332
  ),
317
333
  "fields": len(
318
334
  [
319
335
  e
320
336
  for e in analysis_result.elements
321
- if e.__class__.__name__ == "Variable"
337
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
322
338
  ]
323
339
  ),
324
340
  "imports": len(
325
341
  [
326
342
  e
327
343
  for e in analysis_result.elements
328
- if e.__class__.__name__ == "Import"
344
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
329
345
  ]
330
346
  ),
331
347
  "annotations": len(getattr(analysis_result, "annotations", [])),
348
+ "packages": len(
349
+ [
350
+ e
351
+ for e in analysis_result.elements
352
+ if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
353
+ ]
354
+ ),
355
+ "total": (
356
+ len(
357
+ [
358
+ e
359
+ for e in analysis_result.elements
360
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
361
+ ]
362
+ )
363
+ + len(
364
+ [
365
+ e
366
+ for e in analysis_result.elements
367
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
368
+ ]
369
+ )
370
+ + len(
371
+ [
372
+ e
373
+ for e in analysis_result.elements
374
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
375
+ ]
376
+ )
377
+ + len(
378
+ [
379
+ e
380
+ for e in analysis_result.elements
381
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
382
+ ]
383
+ )
384
+ + len(
385
+ [
386
+ e
387
+ for e in analysis_result.elements
388
+ if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)
389
+ ]
390
+ )
391
+ ),
332
392
  },
333
393
  }
334
394
  }
@@ -339,7 +399,9 @@ class UniversalAnalyzeTool:
339
399
 
340
400
  # Add complexity metrics
341
401
  methods = [
342
- e for e in analysis_result.elements if e.__class__.__name__ == "Function"
402
+ e
403
+ for e in analysis_result.elements
404
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
343
405
  ]
344
406
  total_complexity = sum(
345
407
  getattr(method, "complexity_score", 0) or 0 for method in methods
@@ -347,7 +409,7 @@ class UniversalAnalyzeTool:
347
409
 
348
410
  basic["metrics"]["complexity"] = {
349
411
  "total": total_complexity,
350
- "average": (total_complexity / len(methods) if methods else 0),
412
+ "average": round(total_complexity / len(methods) if methods else 0, 2),
351
413
  "max": max(
352
414
  (getattr(method, "complexity_score", 0) or 0 for method in methods),
353
415
  default=0,
@@ -372,7 +434,7 @@ class UniversalAnalyzeTool:
372
434
  for cls in [
373
435
  e
374
436
  for e in analysis_result.elements
375
- if e.__class__.__name__ == "Class"
437
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
376
438
  ]
377
439
  ],
378
440
  "methods": [
@@ -384,7 +446,7 @@ class UniversalAnalyzeTool:
384
446
  for method in [
385
447
  e
386
448
  for e in analysis_result.elements
387
- if e.__class__.__name__ == "Function"
449
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
388
450
  ]
389
451
  ],
390
452
  "fields": [
@@ -396,7 +458,7 @@ class UniversalAnalyzeTool:
396
458
  for field in [
397
459
  e
398
460
  for e in analysis_result.elements
399
- if e.__class__.__name__ == "Variable"
461
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
400
462
  ]
401
463
  ],
402
464
  "imports": [
@@ -408,7 +470,7 @@ class UniversalAnalyzeTool:
408
470
  for imp in [
409
471
  e
410
472
  for e in analysis_result.elements
411
- if e.__class__.__name__ == "Import"
473
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
412
474
  ]
413
475
  ],
414
476
  "annotations": [
@@ -446,16 +508,34 @@ class UniversalAnalyzeTool:
446
508
  "lines_blank": 0, # Not available in universal analyzer
447
509
  "elements": {
448
510
  "classes": len(
449
- [e for e in elements if e.get("__class__", "") == "Class"]
511
+ [
512
+ e
513
+ for e in elements
514
+ if hasattr(e, "element_type") and e.element_type == "class"
515
+ ]
450
516
  ),
451
517
  "methods": len(
452
- [e for e in elements if e.get("__class__", "") == "Function"]
518
+ [
519
+ e
520
+ for e in elements
521
+ if hasattr(e, "element_type")
522
+ and e.element_type == "function"
523
+ ]
453
524
  ),
454
525
  "fields": len(
455
- [e for e in elements if e.get("__class__", "") == "Variable"]
526
+ [
527
+ e
528
+ for e in elements
529
+ if hasattr(e, "element_type")
530
+ and e.element_type == "variable"
531
+ ]
456
532
  ),
457
533
  "imports": len(
458
- [e for e in elements if e.get("__class__", "") == "Import"]
534
+ [
535
+ e
536
+ for e in elements
537
+ if hasattr(e, "element_type") and e.element_type == "import"
538
+ ]
459
539
  ),
460
540
  "annotations": 0, # Not available in universal analyzer
461
541
  },
@@ -11,6 +11,16 @@ from collections.abc import Callable
11
11
  from dataclasses import dataclass, field
12
12
  from typing import TYPE_CHECKING, Any
13
13
 
14
+ from .constants import (
15
+ ELEMENT_TYPE_ANNOTATION,
16
+ ELEMENT_TYPE_CLASS,
17
+ ELEMENT_TYPE_FUNCTION,
18
+ ELEMENT_TYPE_IMPORT,
19
+ ELEMENT_TYPE_PACKAGE,
20
+ ELEMENT_TYPE_VARIABLE,
21
+ is_element_of_type,
22
+ )
23
+
14
24
  if TYPE_CHECKING:
15
25
  pass
16
26
 
@@ -276,45 +286,64 @@ class AnalysisResult:
276
286
  ) # Query results for new architecture
277
287
  source_code: str = "" # Source code for new architecture
278
288
  package: JavaPackage | None = None
279
- imports: list[JavaImport] = field(default_factory=list)
280
- classes: list[JavaClass] = field(default_factory=list)
281
- methods: list[JavaMethod] = field(default_factory=list)
282
- fields: list[JavaField] = field(default_factory=list)
283
- annotations: list[JavaAnnotation] = field(default_factory=list)
289
+ # Legacy fields removed - use elements list instead
290
+ # imports: list[JavaImport] = field(default_factory=list)
291
+ # classes: list[JavaClass] = field(default_factory=list)
292
+ # methods: list[JavaMethod] = field(default_factory=list)
293
+ # fields: list[JavaField] = field(default_factory=list)
294
+ # annotations: list[JavaAnnotation] = field(default_factory=list)
284
295
  analysis_time: float = 0.0
285
296
  success: bool = True
286
297
  error_message: str | None = None
287
298
 
288
299
  def to_dict(self) -> dict[str, Any]:
289
- """Convert analysis result to dictionary for serialization"""
300
+ """Convert analysis result to dictionary for serialization using unified elements"""
301
+ # Use unified elements list for consistent data structure
302
+ elements = self.elements or []
303
+
304
+ # Extract elements by type from unified list using constants
305
+ classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
306
+ methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
307
+ fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
308
+ imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
309
+ packages = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_PACKAGE)]
310
+
290
311
  return {
291
312
  "file_path": self.file_path,
292
313
  "line_count": self.line_count,
293
- "package": {"name": self.package.name} if self.package else None,
314
+ "package": {"name": packages[0].name} if packages else None,
294
315
  "imports": [
295
316
  {
296
317
  "name": imp.name,
297
- "is_static": imp.is_static,
298
- "is_wildcard": imp.is_wildcard,
318
+ "is_static": getattr(imp, "is_static", False),
319
+ "is_wildcard": getattr(imp, "is_wildcard", False),
299
320
  }
300
- for imp in self.imports
321
+ for imp in imports
301
322
  ],
302
323
  "classes": [
303
- {"name": cls.name, "type": cls.class_type, "package": cls.package_name}
304
- for cls in self.classes
324
+ {
325
+ "name": cls.name,
326
+ "type": getattr(cls, "class_type", "class"),
327
+ "package": getattr(cls, "package_name", None),
328
+ }
329
+ for cls in classes
305
330
  ],
306
331
  "methods": [
307
332
  {
308
333
  "name": method.name,
309
- "return_type": method.return_type,
310
- "parameters": method.parameters,
334
+ "return_type": getattr(method, "return_type", None),
335
+ "parameters": getattr(method, "parameters", []),
311
336
  }
312
- for method in self.methods
337
+ for method in methods
313
338
  ],
314
339
  "fields": [
315
- {"name": field.name, "type": field.field_type} for field in self.fields
340
+ {"name": field.name, "type": getattr(field, "field_type", None)}
341
+ for field in fields
342
+ ],
343
+ "annotations": [
344
+ {"name": getattr(ann, "name", str(ann))}
345
+ for ann in getattr(self, "annotations", [])
316
346
  ],
317
- "annotations": [{"name": ann.name} for ann in self.annotations],
318
347
  "analysis_time": self.analysis_time,
319
348
  "success": self.success,
320
349
  "error_message": self.error_message,
@@ -322,40 +351,56 @@ class AnalysisResult:
322
351
 
323
352
  def to_summary_dict(self, types: list[str] | None = None) -> dict[str, Any]:
324
353
  """
325
- Return analysis summary as a dictionary.
354
+ Return analysis summary as a dictionary using unified elements.
326
355
  Only include specified element types (e.g., 'classes', 'methods', 'fields').
327
356
  """
328
357
  if types is None:
329
358
  types = ["classes", "methods"] # default
330
359
 
331
360
  summary: dict[str, Any] = {"file_path": self.file_path, "summary_elements": []}
332
-
333
- all_elements: dict[str, list[Any]] = {
334
- "imports": self.imports,
335
- "classes": self.classes,
336
- "methods": self.methods,
337
- "fields": self.fields,
338
- "annotations": self.annotations,
361
+ elements = self.elements or []
362
+
363
+ # Map type names to element_type constants
364
+ type_mapping = {
365
+ "imports": ELEMENT_TYPE_IMPORT,
366
+ "classes": ELEMENT_TYPE_CLASS,
367
+ "methods": ELEMENT_TYPE_FUNCTION,
368
+ "fields": ELEMENT_TYPE_VARIABLE,
369
+ "annotations": ELEMENT_TYPE_ANNOTATION,
339
370
  }
340
371
 
341
- for type_name, elements in all_elements.items():
372
+ for type_name, element_type in type_mapping.items():
342
373
  if "all" in types or type_name in types:
343
- for element in elements:
374
+ type_elements = [
375
+ e for e in elements if is_element_of_type(e, element_type)
376
+ ]
377
+ for element in type_elements:
344
378
  # Call each element model's to_summary_item()
345
379
  summary["summary_elements"].append(element.to_summary_item())
346
380
 
347
381
  return summary
348
382
 
349
383
  def get_summary(self) -> dict[str, Any]:
350
- """Get analysis summary statistics"""
384
+ """Get analysis summary statistics using unified elements"""
385
+ elements = self.elements or []
386
+
387
+ # Count elements by type from unified list using constants
388
+ classes = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_CLASS)]
389
+ methods = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)]
390
+ fields = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)]
391
+ imports = [e for e in elements if is_element_of_type(e, ELEMENT_TYPE_IMPORT)]
392
+ annotations = [
393
+ e for e in elements if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
394
+ ]
395
+
351
396
  return {
352
397
  "file_path": self.file_path,
353
398
  "line_count": self.line_count,
354
- "class_count": len(self.classes),
355
- "method_count": len(self.methods),
356
- "field_count": len(self.fields),
357
- "import_count": len(self.imports),
358
- "annotation_count": len(self.annotations),
399
+ "class_count": len(classes),
400
+ "method_count": len(methods),
401
+ "field_count": len(fields),
402
+ "import_count": len(imports),
403
+ "annotation_count": len(annotations),
359
404
  "success": self.success,
360
405
  "analysis_time": self.analysis_time,
361
406
  }
@@ -400,7 +445,11 @@ class AnalysisResult:
400
445
  "end": safe_get_attr(imp, "end_line", 0),
401
446
  },
402
447
  }
403
- for imp in self.imports
448
+ for imp in [
449
+ e
450
+ for e in (self.elements or [])
451
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
452
+ ]
404
453
  ],
405
454
  "classes": [
406
455
  {
@@ -412,7 +461,11 @@ class AnalysisResult:
412
461
  "end": safe_get_attr(cls, "end_line", 0),
413
462
  },
414
463
  }
415
- for cls in self.classes
464
+ for cls in [
465
+ e
466
+ for e in (self.elements or [])
467
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
468
+ ]
416
469
  ],
417
470
  "methods": [
418
471
  {
@@ -424,7 +477,11 @@ class AnalysisResult:
424
477
  "end": safe_get_attr(method, "end_line", 0),
425
478
  },
426
479
  }
427
- for method in self.methods
480
+ for method in [
481
+ e
482
+ for e in (self.elements or [])
483
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
484
+ ]
428
485
  ],
429
486
  "fields": [
430
487
  {
@@ -435,7 +492,11 @@ class AnalysisResult:
435
492
  "end": safe_get_attr(field, "end_line", 0),
436
493
  },
437
494
  }
438
- for field in self.fields
495
+ for field in [
496
+ e
497
+ for e in (self.elements or [])
498
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
499
+ ]
439
500
  ],
440
501
  "annotations": [
441
502
  {
@@ -445,16 +506,50 @@ class AnalysisResult:
445
506
  "end": safe_get_attr(ann, "end_line", 0),
446
507
  },
447
508
  }
448
- for ann in self.annotations
509
+ for ann in [
510
+ e
511
+ for e in (self.elements or [])
512
+ if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
513
+ ]
449
514
  ],
450
515
  },
451
516
  "metadata": {
452
517
  "line_count": self.line_count,
453
- "class_count": len(self.classes),
454
- "method_count": len(self.methods),
455
- "field_count": len(self.fields),
456
- "import_count": len(self.imports),
457
- "annotation_count": len(self.annotations),
518
+ "class_count": len(
519
+ [
520
+ e
521
+ for e in (self.elements or [])
522
+ if is_element_of_type(e, ELEMENT_TYPE_CLASS)
523
+ ]
524
+ ),
525
+ "method_count": len(
526
+ [
527
+ e
528
+ for e in (self.elements or [])
529
+ if is_element_of_type(e, ELEMENT_TYPE_FUNCTION)
530
+ ]
531
+ ),
532
+ "field_count": len(
533
+ [
534
+ e
535
+ for e in (self.elements or [])
536
+ if is_element_of_type(e, ELEMENT_TYPE_VARIABLE)
537
+ ]
538
+ ),
539
+ "import_count": len(
540
+ [
541
+ e
542
+ for e in (self.elements or [])
543
+ if is_element_of_type(e, ELEMENT_TYPE_IMPORT)
544
+ ]
545
+ ),
546
+ "annotation_count": len(
547
+ [
548
+ e
549
+ for e in (self.elements or [])
550
+ if is_element_of_type(e, ELEMENT_TYPE_ANNOTATION)
551
+ ]
552
+ ),
458
553
  "analysis_time": self.analysis_time,
459
554
  "success": self.success,
460
555
  "error_message": self.error_message,
@@ -39,18 +39,38 @@ class ProjectBoundaryManager:
39
39
  if not project_root:
40
40
  raise SecurityError("Project root cannot be empty")
41
41
 
42
- project_path = Path(project_root)
43
- if not project_path.exists():
44
- raise SecurityError(f"Project root does not exist: {project_root}")
42
+ try:
43
+ project_path = Path(project_root)
44
+
45
+ # Handle both string and Path objects
46
+ if isinstance(project_root, str):
47
+ project_path = Path(project_root)
48
+ elif isinstance(project_root, Path):
49
+ project_path = project_root
50
+ else:
51
+ raise SecurityError(f"Invalid project root type: {type(project_root)}")
45
52
 
46
- if not project_path.is_dir():
47
- raise SecurityError(f"Project root is not a directory: {project_root}")
53
+ # Ensure the path exists and is a directory
54
+ if not project_path.exists():
55
+ raise SecurityError(f"Project root does not exist: {project_root}")
48
56
 
49
- # Store real path to prevent symlink attacks
50
- self.project_root = str(project_path.resolve())
51
- self.allowed_directories: set[str] = {self.project_root}
57
+ if not project_path.is_dir():
58
+ raise SecurityError(f"Project root is not a directory: {project_root}")
52
59
 
53
- log_debug(f"ProjectBoundaryManager initialized with root: {self.project_root}")
60
+ # Store real path to prevent symlink attacks
61
+ self.project_root = str(project_path.resolve())
62
+ self.allowed_directories: set[str] = {self.project_root}
63
+
64
+ log_debug(
65
+ f"ProjectBoundaryManager initialized with root: {self.project_root}"
66
+ )
67
+
68
+ except Exception as e:
69
+ if isinstance(e, SecurityError):
70
+ raise
71
+ raise SecurityError(
72
+ f"Failed to initialize ProjectBoundaryManager: {e}"
73
+ ) from e
54
74
 
55
75
  def add_allowed_directory(self, directory: str) -> None:
56
76
  """