@voodocs/cli 0.3.1 → 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.
- package/CHANGELOG.md +454 -0
- package/cli.py +32 -3
- package/lib/darkarts/annotations/DARKARTS_SYMBOLS.md +529 -0
- package/lib/darkarts/annotations/TRANSFORMATION_EXAMPLES.md +478 -0
- package/lib/darkarts/annotations/__init__.py +42 -0
- package/lib/darkarts/annotations/darkarts_parser.py +238 -0
- package/lib/darkarts/annotations/parser.py +186 -5
- package/lib/darkarts/annotations/symbols.py +244 -0
- package/lib/darkarts/annotations/translator.py +386 -0
- package/lib/darkarts/context/ai_instructions.py +8 -1
- package/lib/darkarts/context/ai_integrations.py +22 -1
- package/lib/darkarts/context/checker.py +291 -40
- package/lib/darkarts/context/commands.py +375 -267
- package/lib/darkarts/context/diagram.py +22 -1
- package/lib/darkarts/context/errors.py +164 -0
- package/lib/darkarts/context/models.py +23 -1
- package/lib/darkarts/context/module_utils.py +198 -0
- package/lib/darkarts/context/ui.py +337 -0
- package/lib/darkarts/context/validation.py +311 -0
- package/lib/darkarts/context/yaml_utils.py +130 -16
- package/lib/darkarts/exceptions.py +5 -0
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +8 -1
- package/package.json +1 -1
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""@voodocs
|
|
2
|
+
module_purpose: "Architecture diagram generation from context (Mermaid and D2 formats)"
|
|
3
|
+
dependencies: [
|
|
4
|
+
"subprocess: External diagram rendering (manus-render-diagram)",
|
|
5
|
+
"pathlib: File path handling",
|
|
6
|
+
"typing: Type hints"
|
|
7
|
+
]
|
|
8
|
+
assumptions: [
|
|
9
|
+
"Context file contains architecture.modules section",
|
|
10
|
+
"manus-render-diagram utility is available for PNG rendering",
|
|
11
|
+
"Output directory is writable",
|
|
12
|
+
"Mermaid/D2 syntax is valid"
|
|
13
|
+
]
|
|
14
|
+
invariants: [
|
|
15
|
+
"Generated diagrams must be valid Mermaid or D2 syntax",
|
|
16
|
+
"Module names must be sanitized for diagram syntax",
|
|
17
|
+
"Diagram generation must not modify context file",
|
|
18
|
+
"PNG rendering is optional and fails gracefully if utility unavailable"
|
|
19
|
+
]
|
|
20
|
+
security_model: "Read context file, write diagram files to user-specified paths"
|
|
21
|
+
performance_model: "O(n) where n=number of modules, O(n^2) for dependency graphs"
|
|
22
|
+
|
|
2
23
|
Architecture Diagram Generator
|
|
3
24
|
|
|
4
25
|
Generates visual diagrams from context files.
|
|
@@ -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
|