@voodocs/cli 0.3.2 → 0.4.0

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.
@@ -0,0 +1,164 @@
1
+ """@darkarts
2
+ ⊢errors:context.helpful
3
+ ∂{darkarts.exceptions}
4
+ ⚠{users:unfamiliar,msg:actionable,emoji:readable}
5
+ ⊨{∀exc→inherit(VooDocsError),∀exc→msg:helpful,∀exc→suggest:recovery,msg:user-friendly∧¬jargon}
6
+ 🔒{no-implications}
7
+ ⚡{O(1)}
8
+
9
+ Context System Exceptions
10
+
11
+ Provides specific exception types for context system operations with
12
+ helpful error messages and recovery suggestions.
13
+ """
14
+
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+
19
+ # Import base exceptions
20
+ from darkarts.exceptions import VooDocsError, ConfigurationError, ValidationError
21
+
22
+
23
+ class ContextFileNotFoundError(ConfigurationError):
24
+ """Context file not found."""
25
+
26
+ def __init__(self, path: str):
27
+ self.path = path
28
+ super().__init__(
29
+ f"Context file not found: {path}\n\n"
30
+ f"💡 To create a new context file, run:\n"
31
+ f" voodocs context init"
32
+ )
33
+
34
+
35
+ class InvalidContextError(ConfigurationError):
36
+ """Invalid context file."""
37
+
38
+ def __init__(self, path: str, reason: str):
39
+ self.path = path
40
+ self.reason = reason
41
+ super().__init__(
42
+ f"Invalid context file: {path}\n"
43
+ f"Reason: {reason}\n\n"
44
+ f"💡 To validate your context file, run:\n"
45
+ f" voodocs context validate"
46
+ )
47
+
48
+
49
+ class GitNotAvailableError(ConfigurationError):
50
+ """Git is not available."""
51
+
52
+ def __init__(self):
53
+ super().__init__(
54
+ f"Git is not available\n\n"
55
+ f"💡 Some VooDocs features require Git. Please install Git:\n"
56
+ f" https://git-scm.com/downloads"
57
+ )
58
+
59
+
60
+ class InvalidProjectNameError(ValidationError):
61
+ """Invalid project name."""
62
+
63
+ def __init__(self, name: str, reason: str):
64
+ self.name = name
65
+ self.reason = reason
66
+ super().__init__(
67
+ f"Invalid project name: '{name}'\n"
68
+ f"Reason: {reason}\n\n"
69
+ f"💡 Project names must:\n"
70
+ f" - Not be empty\n"
71
+ f" - Be 100 characters or less\n"
72
+ f" - Contain only letters, numbers, hyphens, and underscores"
73
+ )
74
+
75
+
76
+ class InvalidVersionError(ValidationError):
77
+ """Invalid version number."""
78
+
79
+ def __init__(self, version: str):
80
+ self.version = version
81
+ super().__init__(
82
+ f"Invalid version: '{version}'\n\n"
83
+ f"💡 Version must follow semantic versioning (e.g., 1.0.0, 2.1.3)\n"
84
+ f" Format: MAJOR.MINOR.PATCH or MAJOR.MINOR"
85
+ )
86
+
87
+
88
+ class InvalidPathError(ValidationError):
89
+ """Invalid file path."""
90
+
91
+ def __init__(self, path: str, reason: str):
92
+ self.path = path
93
+ self.reason = reason
94
+ super().__init__(
95
+ f"Invalid path: '{path}'\n"
96
+ f"Reason: {reason}"
97
+ )
98
+
99
+
100
+ class InvariantViolationError(VooDocsError):
101
+ """Invariant violation detected."""
102
+
103
+ def __init__(self, file_path: str, invariant: str, line: int):
104
+ self.file_path = file_path
105
+ self.invariant = invariant
106
+ self.line = line
107
+ super().__init__(
108
+ f"Invariant violation in {file_path}:{line}\n"
109
+ f"Invariant: {invariant}\n\n"
110
+ f"💡 Review the code and update either:\n"
111
+ f" - The code to satisfy the invariant\n"
112
+ f" - The invariant if requirements changed"
113
+ )
114
+
115
+
116
+ class AssumptionViolationError(VooDocsError):
117
+ """Assumption violation detected."""
118
+
119
+ def __init__(self, file_path: str, assumption: str, line: int):
120
+ self.file_path = file_path
121
+ self.assumption = assumption
122
+ self.line = line
123
+ super().__init__(
124
+ f"Assumption violation in {file_path}:{line}\n"
125
+ f"Assumption: {assumption}\n\n"
126
+ f"💡 Review the code and update either:\n"
127
+ f" - The code to satisfy the assumption\n"
128
+ f" - The assumption if requirements changed"
129
+ )
130
+
131
+
132
+ class UserInterruptError(VooDocsError):
133
+ """User interrupted operation."""
134
+
135
+ def __init__(self, operation: str):
136
+ self.operation = operation
137
+ super().__init__(f"\nOperation cancelled by user: {operation}")
138
+
139
+
140
+ class OutputDirectoryError(VooDocsError):
141
+ """Output directory error."""
142
+
143
+ def __init__(self, path: str, reason: str):
144
+ self.path = path
145
+ self.reason = reason
146
+ super().__init__(
147
+ f"Output directory error: {path}\n"
148
+ f"Reason: {reason}"
149
+ )
150
+
151
+
152
+ class ContextVersionMismatchError(ConfigurationError):
153
+ """Context version mismatch."""
154
+
155
+ def __init__(self, current: str, required: str):
156
+ self.current = current
157
+ self.required = required
158
+ super().__init__(
159
+ f"Context version mismatch\n"
160
+ f"Current: {current}\n"
161
+ f"Required: {required}\n\n"
162
+ f"💡 Update your context file to the latest version:\n"
163
+ f" voodocs context update"
164
+ )
@@ -1,4 +1,26 @@
1
- """
1
+ """@voodocs
2
+ module_purpose: "Data structures for VooDocs context system (dataclasses for all context entities)"
3
+ dependencies: [
4
+ "dataclasses: Python dataclass decorator",
5
+ "typing: Type hints for optional and complex types",
6
+ "datetime: Date handling"
7
+ ]
8
+ assumptions: [
9
+ "Python 3.7+ with dataclasses support",
10
+ "All dates are ISO 8601 format (YYYY-MM-DD)",
11
+ "Version strings follow semver (major.minor)",
12
+ "All text fields are UTF-8 strings"
13
+ ]
14
+ invariants: [
15
+ "All dataclasses must be immutable (frozen=False but should not be mutated)",
16
+ "Optional fields must have None as default",
17
+ "List fields must use field(default_factory=list)",
18
+ "Dict fields must use field(default_factory=dict)",
19
+ "All models must be serializable to dict via asdict()"
20
+ ]
21
+ security_model: "Pure data structures, no I/O or side effects"
22
+ performance_model: "O(1) for all operations, lightweight dataclasses"
23
+
2
24
  Context System Data Models
3
25
 
4
26
  This module defines the data structures for the VooDocs context system.
@@ -0,0 +1,198 @@
1
+ """@darkarts
2
+ ⊢module_utils:context.extraction
3
+ ∂{pathlib,typing}
4
+ ⚠{annotations:parsed,paths:relative,extensions:standard}
5
+ ⊨{∀extract→complete-module,language:detected,api:extracted}
6
+ 🔒{read-only,¬modify-annotations}
7
+ ⚡{O(1)}
8
+
9
+ Module Extraction Utilities
10
+
11
+ Helper functions for extracting module information from parsed annotations.
12
+ """
13
+
14
+ from pathlib import Path
15
+ from typing import Dict, Any, Optional, List
16
+
17
+
18
+ def detect_language(file_path: str) -> Optional[str]:
19
+ """
20
+ Detect programming language from file extension.
21
+
22
+ Args:
23
+ file_path: Path to the source file
24
+
25
+ Returns:
26
+ Language name or None if unknown
27
+
28
+ Examples:
29
+ >>> detect_language("main.py")
30
+ 'Python'
31
+ >>> detect_language("app.js")
32
+ 'JavaScript'
33
+ >>> detect_language("unknown.xyz")
34
+ None
35
+ """
36
+ ext_map = {
37
+ '.py': 'Python',
38
+ '.js': 'JavaScript',
39
+ '.jsx': 'JavaScript',
40
+ '.ts': 'TypeScript',
41
+ '.tsx': 'TypeScript',
42
+ '.java': 'Java',
43
+ '.go': 'Go',
44
+ '.rs': 'Rust',
45
+ '.cpp': 'C++',
46
+ '.cc': 'C++',
47
+ '.cxx': 'C++',
48
+ '.c': 'C',
49
+ '.h': 'C/C++',
50
+ '.hpp': 'C++',
51
+ '.cs': 'C#',
52
+ '.rb': 'Ruby',
53
+ '.php': 'PHP',
54
+ '.swift': 'Swift',
55
+ '.kt': 'Kotlin',
56
+ '.scala': 'Scala',
57
+ '.r': 'R',
58
+ '.m': 'Objective-C',
59
+ '.sh': 'Shell',
60
+ '.bash': 'Bash',
61
+ '.zsh': 'Zsh',
62
+ }
63
+
64
+ ext = Path(file_path).suffix.lower()
65
+ return ext_map.get(ext)
66
+
67
+
68
+ def extract_public_api(parsed) -> List[str]:
69
+ """
70
+ Extract public API from parsed annotations.
71
+
72
+ Collects:
73
+ - Public functions (not starting with _)
74
+ - Public classes
75
+ - Explicitly marked public APIs
76
+
77
+ Args:
78
+ parsed: ParsedAnnotations object
79
+
80
+ Returns:
81
+ List of public API names
82
+
83
+ Examples:
84
+ >>> # parsed.module.functions = [func1, func2, _private]
85
+ >>> extract_public_api(parsed)
86
+ ['func1', 'func2']
87
+ """
88
+ public_api = []
89
+
90
+ # From module annotation
91
+ module_ann = parsed.module
92
+ if hasattr(module_ann, 'public_api') and module_ann.public_api:
93
+ public_api.extend(module_ann.public_api)
94
+
95
+ # From function annotations (if not already in public_api)
96
+ if hasattr(module_ann, 'functions'):
97
+ for func in module_ann.functions:
98
+ func_name = getattr(func, 'name', None)
99
+ if func_name and not func_name.startswith('_') and func_name not in public_api:
100
+ public_api.append(func_name)
101
+
102
+ # From class annotations
103
+ if hasattr(module_ann, 'classes'):
104
+ for cls in module_ann.classes:
105
+ cls_name = getattr(cls, 'name', None)
106
+ if cls_name and not cls_name.startswith('_') and cls_name not in public_api:
107
+ public_api.append(cls_name)
108
+
109
+ return public_api
110
+
111
+
112
+ def extract_module_info(parsed, scan_path: Path) -> Optional[Dict[str, Any]]:
113
+ """
114
+ Extract complete module information from parsed annotations.
115
+
116
+ Creates a dict compatible with the Module dataclass:
117
+ - description: Module purpose/description
118
+ - path: Relative path to source file
119
+ - language: Detected programming language
120
+ - dependencies: List of dependencies
121
+ - public_api: List of public API names
122
+
123
+ Args:
124
+ parsed: ParsedAnnotations object
125
+ scan_path: Base path for calculating relative paths
126
+
127
+ Returns:
128
+ Module info dict or None if no module_purpose
129
+
130
+ Examples:
131
+ >>> module_info = extract_module_info(parsed, Path('/project'))
132
+ >>> module_info['description']
133
+ 'User authentication module'
134
+ >>> module_info['language']
135
+ 'Python'
136
+ """
137
+ module_ann = parsed.module
138
+
139
+ # Check if module has a purpose/description
140
+ if not hasattr(module_ann, 'module_purpose') or not module_ann.module_purpose:
141
+ return None
142
+
143
+ # Calculate relative path
144
+ try:
145
+ rel_path = Path(parsed.source_file).relative_to(scan_path).as_posix()
146
+ except ValueError:
147
+ # If file is not under scan_path, use absolute path
148
+ rel_path = Path(parsed.source_file).as_posix()
149
+
150
+ # Detect language from file extension
151
+ language = detect_language(parsed.source_file)
152
+
153
+ # Extract dependencies
154
+ dependencies = []
155
+ if hasattr(module_ann, 'dependencies') and module_ann.dependencies:
156
+ dependencies = list(module_ann.dependencies)
157
+
158
+ # Extract public API
159
+ public_api = extract_public_api(parsed)
160
+
161
+ # Build module info dict (matches Module dataclass fields)
162
+ return {
163
+ 'description': module_ann.module_purpose,
164
+ 'path': rel_path,
165
+ 'language': language,
166
+ 'dependencies': dependencies,
167
+ 'public_api': public_api
168
+ }
169
+
170
+
171
+ def extract_modules_from_results(results: List, scan_path: Path) -> Dict[str, Dict[str, Any]]:
172
+ """
173
+ Extract module information from all parsed annotation results.
174
+
175
+ Args:
176
+ results: List of ParsedAnnotations objects
177
+ scan_path: Base path for calculating relative paths
178
+
179
+ Returns:
180
+ Dict mapping file paths to module info dicts
181
+
182
+ Examples:
183
+ >>> results = parser.parse_directory(Path('/project'))
184
+ >>> modules = extract_modules_from_results(results, Path('/project'))
185
+ >>> len(modules)
186
+ 5
187
+ >>> modules['lib/auth.py']['language']
188
+ 'Python'
189
+ """
190
+ modules_info = {}
191
+
192
+ for parsed in results:
193
+ module_info = extract_module_info(parsed, scan_path)
194
+ if module_info:
195
+ # Use path as key
196
+ modules_info[module_info['path']] = module_info
197
+
198
+ return modules_info