@voodocs/cli 0.4.2 → 1.0.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/lib/cli/__init__.py +53 -0
  3. package/lib/cli/benchmark.py +311 -0
  4. package/lib/cli/fix.py +244 -0
  5. package/lib/cli/generate.py +310 -0
  6. package/lib/cli/test_cli.py +215 -0
  7. package/lib/cli/validate.py +364 -0
  8. package/lib/darkarts/__init__.py +11 -5
  9. package/lib/darkarts/annotations/__init__.py +11 -3
  10. package/lib/darkarts/annotations/darkarts_parser.py +1 -1
  11. package/lib/darkarts/annotations/types.py +16 -3
  12. package/lib/darkarts/cli_darkarts.py +1 -1
  13. package/lib/darkarts/context/__init__.py +11 -3
  14. package/lib/darkarts/context/ai_integrations.py +7 -21
  15. package/lib/darkarts/context/commands.py +1 -1
  16. package/lib/darkarts/context/diagram.py +8 -22
  17. package/lib/darkarts/context/models.py +7 -22
  18. package/lib/darkarts/context/module_utils.py +1 -1
  19. package/lib/darkarts/context/ui.py +1 -1
  20. package/lib/darkarts/context/validation.py +1 -1
  21. package/lib/darkarts/context/yaml_utils.py +8 -23
  22. package/lib/darkarts/core/__init__.py +12 -2
  23. package/lib/darkarts/core/interface.py +16 -2
  24. package/lib/darkarts/core/loader.py +17 -2
  25. package/lib/darkarts/core/plugin.py +16 -3
  26. package/lib/darkarts/core/registry.py +17 -2
  27. package/lib/darkarts/exceptions.py +17 -3
  28. package/lib/darkarts/plugins/voodocs/__init__.py +12 -2
  29. package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +16 -5
  30. package/lib/darkarts/plugins/voodocs/annotation_validator.py +16 -3
  31. package/lib/darkarts/plugins/voodocs/api_spec_generator.py +16 -3
  32. package/lib/darkarts/plugins/voodocs/documentation_generator.py +16 -3
  33. package/lib/darkarts/plugins/voodocs/html_exporter.py +16 -3
  34. package/lib/darkarts/plugins/voodocs/instruction_generator.py +1 -1
  35. package/lib/darkarts/plugins/voodocs/pdf_exporter.py +16 -3
  36. package/lib/darkarts/plugins/voodocs/test_generator.py +16 -3
  37. package/lib/darkarts/telemetry.py +16 -3
  38. package/lib/darkarts/validation/README.md +147 -0
  39. package/lib/darkarts/validation/__init__.py +91 -0
  40. package/lib/darkarts/validation/autofix.py +297 -0
  41. package/lib/darkarts/validation/benchmark.py +426 -0
  42. package/lib/darkarts/validation/benchmark_wrapper.py +22 -0
  43. package/lib/darkarts/validation/config.py +257 -0
  44. package/lib/darkarts/validation/performance.py +412 -0
  45. package/lib/darkarts/validation/performance_wrapper.py +37 -0
  46. package/lib/darkarts/validation/semantic.py +461 -0
  47. package/lib/darkarts/validation/semantic_wrapper.py +77 -0
  48. package/lib/darkarts/validation/test_validation.py +160 -0
  49. package/lib/darkarts/validation/types.py +97 -0
  50. package/lib/darkarts/validation/watch.py +239 -0
  51. package/package.json +19 -6
  52. package/voodocs_cli.py +28 -0
  53. package/cli.py +0 -1646
  54. package/lib/darkarts/cli.py +0 -128
@@ -0,0 +1,147 @@
1
+ # DarkArts Validation Module
2
+
3
+ **Phase 1: Foundation - Complete** ✅
4
+
5
+ This module provides validation tools for @darkarts annotations.
6
+
7
+ ## Overview
8
+
9
+ The validation module ensures that @darkarts annotations are accurate and up-to-date by:
10
+ - **Semantic Validation**: Verifying dependencies match actual imports
11
+ - **Performance Validation**: Checking complexity claims match code structure
12
+ - **Benchmarking**: Measuring actual performance to validate claims
13
+ - **Auto-fix**: Automatically correcting validation issues
14
+
15
+ ## Installation
16
+
17
+ The validation module is part of the VooDocs package:
18
+
19
+ ```bash
20
+ pip install voodocs
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Semantic Validation
26
+
27
+ ```python
28
+ from darkarts.validation import SemanticValidator
29
+
30
+ validator = SemanticValidator()
31
+
32
+ # Validate a single file
33
+ result = validator.validate_file("myfile.py")
34
+ if not result.is_valid:
35
+ print(f"Missing dependencies: {result.missing_deps}")
36
+ print(f"Extra dependencies: {result.extra_deps}")
37
+
38
+ # Validate a directory
39
+ results = validator.validate_directory("lib/", recursive=True)
40
+ valid_count = sum(1 for r in results if r.is_valid)
41
+ print(f"Valid: {valid_count}/{len(results)}")
42
+ ```
43
+
44
+ ### Performance Validation
45
+
46
+ ```python
47
+ from darkarts.validation import PerformanceTracker
48
+
49
+ tracker = PerformanceTracker()
50
+
51
+ # Analyze a file
52
+ result = tracker.analyze_file("myfile.py")
53
+ print(f"Claimed: {result.claimed_complexity}")
54
+ print(f"Actual: {result.actual_complexity}")
55
+ print(f"Valid: {result.is_valid}")
56
+ ```
57
+
58
+ ### Benchmarking
59
+
60
+ ```python
61
+ from darkarts.validation import BenchmarkSuite
62
+
63
+ suite = BenchmarkSuite()
64
+
65
+ # Run benchmarks
66
+ config = {
67
+ "critical_paths": ["mymodule.core.process"],
68
+ "sizes": [10, 100, 1000],
69
+ }
70
+ results = suite.run_benchmarks(config)
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### SemanticValidator
76
+
77
+ - `validate_file(file_path)` - Validate a single file
78
+ - `validate_directory(directory, recursive=True)` - Validate all files in directory
79
+ - `validate_path(path, recursive=False)` - Validate file or directory
80
+
81
+ ### PerformanceTracker
82
+
83
+ - `analyze_file(file_path)` - Analyze a single file
84
+ - `analyze_directory(directory, recursive=True)` - Analyze all files in directory
85
+
86
+ ### BenchmarkSuite
87
+
88
+ - `run_benchmarks(config)` - Run benchmarks based on configuration
89
+
90
+ ## Module Structure
91
+
92
+ ```
93
+ validation/
94
+ ├── __init__.py # Public API
95
+ ├── semantic.py # Semantic validation (functional)
96
+ ├── semantic_wrapper.py # SemanticValidator class
97
+ ├── performance.py # Performance tracking (functional)
98
+ ├── performance_wrapper.py # PerformanceTracker class
99
+ ├── benchmark.py # Benchmarking (functional)
100
+ ├── benchmark_wrapper.py # BenchmarkSuite class
101
+ ├── autofix.py # Auto-fix functionality
102
+ ├── config.py # Configuration management
103
+ ├── watch.py # Watch mode
104
+ ├── types.py # Shared type definitions
105
+ ├── test_validation.py # Unit tests
106
+ └── README.md # This file
107
+ ```
108
+
109
+ ## Design Principles
110
+
111
+ 1. **Functional Core**: Core validation logic is functional (pure functions)
112
+ 2. **OOP Wrapper**: Object-oriented API for ease of use
113
+ 3. **No CLI**: CLI removed - use `voodocs` CLI instead
114
+ 4. **Lazy Loading**: Modules loaded on-demand for fast startup
115
+ 5. **Type Safety**: Full type hints for better IDE support
116
+
117
+ ## Next Steps
118
+
119
+ **Phase 2**: CLI Integration (Week 2)
120
+ - Integrate into `voodocs` CLI
121
+ - Add `voodocs validate` command
122
+ - Add `--validate` flag to `voodocs generate`
123
+
124
+ **Phase 3**: Advanced Features (Week 3)
125
+ - Watch mode integration
126
+ - Configuration file support
127
+ - HTML/JSON reports
128
+
129
+ ## Testing
130
+
131
+ Run tests:
132
+
133
+ ```bash
134
+ # Unit tests
135
+ python3 -m pytest lib/darkarts/validation/test_validation.py
136
+
137
+ # With coverage
138
+ python3 -m pytest lib/darkarts/validation/test_validation.py --cov=darkarts.validation
139
+ ```
140
+
141
+ ## Contributing
142
+
143
+ See `../../docs/darkarts/CODE_REVIEW_PROCESS.md` for contribution guidelines.
144
+
145
+ ## License
146
+
147
+ Part of VooDocs - see repository root for license.
@@ -0,0 +1,91 @@
1
+ """@darkarts
2
+ ⊢ validation:api
3
+ ∂{typing}
4
+ ⚠{
5
+ - Python ≥3.7
6
+ - Validation modules available
7
+ }
8
+ ⊨{
9
+ - Public API is stable
10
+ - All validators are importable
11
+ - Version compatibility maintained
12
+ }
13
+ 🔒{
14
+ - Read-only validation operations
15
+ - No side effects except file writes in fix mode
16
+ - Safe for concurrent use
17
+ }
18
+ ⚡{O(n):import-time|n=lazy-loaded-modules}
19
+ """
20
+
21
+ """
22
+ DarkArts Validation Module
23
+
24
+ This module provides validation tools for @darkarts annotations:
25
+ - SemanticValidator: Validates dependencies match imports
26
+ - PerformanceTracker: Validates complexity claims
27
+ - BenchmarkSuite: Benchmarks performance with real data
28
+ - AutoFix: Automatically fixes validation issues
29
+ - Config: Configuration management
30
+
31
+ Public API:
32
+ from darkarts.validation import (
33
+ SemanticValidator,
34
+ PerformanceTracker,
35
+ BenchmarkSuite,
36
+ AutoFix,
37
+ Config,
38
+ )
39
+ """
40
+
41
+ from typing import TYPE_CHECKING
42
+
43
+ __version__ = "1.0.0"
44
+ __all__ = [
45
+ "SemanticValidator",
46
+ "PerformanceTracker",
47
+ "BenchmarkSuite",
48
+ "AutoFix",
49
+ "Config",
50
+ "ValidationResult",
51
+ "PerformanceResult",
52
+ "BenchmarkResult",
53
+ ]
54
+
55
+ # Lazy imports to avoid circular dependencies and improve startup time
56
+ if TYPE_CHECKING:
57
+ from .semantic import SemanticValidator
58
+ from .performance import PerformanceTracker
59
+ from .benchmark import BenchmarkSuite
60
+ from .autofix import AutoFix
61
+ from .config import Config
62
+ from .types import ValidationResult, PerformanceResult, BenchmarkResult
63
+
64
+
65
+ def __getattr__(name: str):
66
+ """Lazy import for better startup performance."""
67
+ if name == "SemanticValidator":
68
+ from .semantic_wrapper import SemanticValidator
69
+ return SemanticValidator
70
+ elif name == "PerformanceTracker":
71
+ from .performance_wrapper import PerformanceTracker
72
+ return PerformanceTracker
73
+ elif name == "BenchmarkSuite":
74
+ from .benchmark_wrapper import BenchmarkSuite
75
+ return BenchmarkSuite
76
+ elif name == "AutoFix":
77
+ from .autofix import AutoFix
78
+ return AutoFix
79
+ elif name == "Config":
80
+ from .config import Config
81
+ return Config
82
+ elif name == "ValidationResult":
83
+ from .types import ValidationResult
84
+ return ValidationResult
85
+ elif name == "PerformanceResult":
86
+ from .types import PerformanceResult
87
+ return PerformanceResult
88
+ elif name == "BenchmarkResult":
89
+ from .types import BenchmarkResult
90
+ return BenchmarkResult
91
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,297 @@
1
+ """@darkarts
2
+ ⊢autofix:validation.automatic-repair
3
+ ∂{re,pathlib,typing,semantic_validator}
4
+ ⚠{python≥3.7,file:writable,backup:recommended}
5
+ ⊨{∀fix→annotation-updated,∀backup→created,¬data-loss,idempotent:safe-to-rerun}
6
+ 🔒{read-write:files,write:backups,¬network,¬exec}
7
+ ⚡{O(n)|n=file-size,file-io:linear}
8
+
9
+ Auto-fix Mode for @darkarts Annotations
10
+
11
+ Automatically updates ∂{} sections to match actual imports with:
12
+ - Safe updates (creates backups before modifying)
13
+ - Dry-run mode (preview changes without applying)
14
+ - Selective fixing (fix only specific issues)
15
+ - Rollback support (restore from backups)
16
+ - Batch operations (fix entire directories)
17
+ - Validation after fix (ensure correctness)
18
+ """
19
+
20
+ import re
21
+ from pathlib import Path
22
+ from typing import Optional, List, Set, Tuple
23
+ from dataclasses import dataclass
24
+ from datetime import datetime
25
+
26
+ from . import semantic
27
+ from .types import ValidationResult
28
+
29
+
30
+ @dataclass
31
+ class FixResult:
32
+ """Result of an auto-fix operation."""
33
+ file_path: str
34
+ success: bool
35
+ original_deps: Set[str]
36
+ fixed_deps: Set[str]
37
+ backup_path: Optional[str]
38
+ error: Optional[str] = None
39
+
40
+ def __str__(self) -> str:
41
+ if not self.success:
42
+ return f"❌ {self.file_path}: {self.error}"
43
+
44
+ added = self.fixed_deps - self.original_deps
45
+ removed = self.original_deps - self.fixed_deps
46
+
47
+ lines = [f"✅ {self.file_path}: Fixed"]
48
+ if added:
49
+ lines.append(f" Added: {', '.join(sorted(added))}")
50
+ if removed:
51
+ lines.append(f" Removed: {', '.join(sorted(removed))}")
52
+ if self.backup_path:
53
+ lines.append(f" Backup: {self.backup_path}")
54
+
55
+ return "\n".join(lines)
56
+
57
+
58
+ class AutoFixer:
59
+ """Automatically fixes @darkarts annotation dependencies."""
60
+
61
+ def __init__(self, backup_dir: Optional[Path] = None, dry_run: bool = False):
62
+ """
63
+ Initialize the auto-fixer.
64
+
65
+ Args:
66
+ backup_dir: Directory to store backups (default: .backups/)
67
+ dry_run: If True, preview changes without applying them
68
+ """
69
+ self.backup_dir = backup_dir or Path(".backups")
70
+ self.dry_run = dry_run
71
+
72
+ if not dry_run:
73
+ self.backup_dir.mkdir(parents=True, exist_ok=True)
74
+
75
+ def create_backup(self, file_path: Path) -> Path:
76
+ """
77
+ Create a backup of the file before modifying.
78
+
79
+ Args:
80
+ file_path: Path to the file to backup
81
+
82
+ Returns:
83
+ Path to the backup file
84
+ """
85
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
86
+ backup_name = f"{file_path.name}.{timestamp}.bak"
87
+ backup_path = self.backup_dir / backup_name
88
+
89
+ content = file_path.read_text(encoding='utf-8')
90
+ backup_path.write_text(content, encoding='utf-8')
91
+
92
+ return backup_path
93
+
94
+ def extract_annotation_section(self, content: str) -> Tuple[str, int, int]:
95
+ """
96
+ Extract the @darkarts annotation section from file content.
97
+
98
+ Args:
99
+ content: File content
100
+
101
+ Returns:
102
+ Tuple of (annotation_text, start_index, end_index)
103
+
104
+ Raises:
105
+ ValueError: If no annotation found
106
+ """
107
+ if not content.startswith('"""@darkarts'):
108
+ raise ValueError("No @darkarts annotation found")
109
+
110
+ # Find the end of the docstring
111
+ end_idx = content.find('"""', 3)
112
+ if end_idx == -1:
113
+ raise ValueError("Malformed annotation: no closing quotes")
114
+
115
+ annotation = content[3:end_idx] # Skip opening """
116
+ return annotation, 3, end_idx
117
+
118
+ def update_dependencies_section(self, annotation: str, new_deps: Set[str]) -> str:
119
+ """
120
+ Update the ∂{} section in an annotation.
121
+
122
+ Args:
123
+ annotation: The annotation text
124
+ new_deps: New set of dependencies
125
+
126
+ Returns:
127
+ Updated annotation text
128
+ """
129
+ # Format dependencies (sorted for consistency)
130
+ deps_str = ','.join(sorted(new_deps)) if new_deps else ''
131
+ new_deps_section = f"∂{{{deps_str}}}"
132
+
133
+ # Replace existing ∂{} section
134
+ pattern = r'∂\{[^}]*\}'
135
+
136
+ if re.search(pattern, annotation):
137
+ # Replace existing section
138
+ updated = re.sub(pattern, new_deps_section, annotation)
139
+ else:
140
+ # Add new section after ⊢ line
141
+ lines = annotation.split('\n')
142
+ for i, line in enumerate(lines):
143
+ if line.startswith('⊢'):
144
+ lines.insert(i + 1, new_deps_section)
145
+ break
146
+ updated = '\n'.join(lines)
147
+
148
+ return updated
149
+
150
+ def fix_file(self, file_path: Path) -> FixResult:
151
+ """
152
+ Fix the dependencies in a single file.
153
+
154
+ Args:
155
+ file_path: Path to the file to fix
156
+
157
+ Returns:
158
+ FixResult with details of the fix
159
+ """
160
+ try:
161
+ # Validate first to see what needs fixing
162
+ validation = semantic.validate_file(file_path)
163
+
164
+ if validation.is_valid:
165
+ return FixResult(
166
+ file_path=str(file_path),
167
+ success=True,
168
+ original_deps=set(),
169
+ fixed_deps=set(),
170
+ backup_path=None,
171
+ error="Already valid (no fix needed)"
172
+ )
173
+
174
+ # Read file content
175
+ content = file_path.read_text(encoding='utf-8')
176
+
177
+ # Extract annotation
178
+ annotation, start_idx, end_idx = self.extract_annotation_section(content)
179
+
180
+ # Get current and correct dependencies
181
+ original_deps = semantic.parse_dependencies(annotation)
182
+ correct_deps = semantic.extract_imports(file_path)
183
+
184
+ # Update annotation
185
+ updated_annotation = self.update_dependencies_section(annotation, correct_deps)
186
+
187
+ # Reconstruct file content
188
+ updated_content = (
189
+ content[:start_idx] +
190
+ updated_annotation +
191
+ content[end_idx:]
192
+ )
193
+
194
+ # Create backup and write (unless dry-run)
195
+ backup_path = None
196
+ if not self.dry_run:
197
+ backup_path = self.create_backup(file_path)
198
+ file_path.write_text(updated_content, encoding='utf-8')
199
+
200
+ return FixResult(
201
+ file_path=str(file_path),
202
+ success=True,
203
+ original_deps=original_deps,
204
+ fixed_deps=correct_deps,
205
+ backup_path=str(backup_path) if backup_path else None
206
+ )
207
+
208
+ except Exception as e:
209
+ return FixResult(
210
+ file_path=str(file_path),
211
+ success=False,
212
+ original_deps=set(),
213
+ fixed_deps=set(),
214
+ backup_path=None,
215
+ error=str(e)
216
+ )
217
+
218
+ def fix_directory(self, directory: Path, recursive: bool = True) -> List[FixResult]:
219
+ """
220
+ Fix all Python files in a directory.
221
+
222
+ Args:
223
+ directory: Path to the directory
224
+ recursive: Whether to search recursively
225
+
226
+ Returns:
227
+ List of FixResult objects
228
+ """
229
+ results = []
230
+
231
+ pattern = "**/*.py" if recursive else "*.py"
232
+
233
+ for py_file in directory.glob(pattern):
234
+ if py_file.name.startswith('.'):
235
+ continue # Skip hidden files
236
+
237
+ result = self.fix_file(py_file)
238
+ results.append(result)
239
+
240
+ return results
241
+
242
+ def rollback(self, backup_path: Path, original_path: Path) -> bool:
243
+ """
244
+ Restore a file from backup.
245
+
246
+ Args:
247
+ backup_path: Path to the backup file
248
+ original_path: Path to restore to
249
+
250
+ Returns:
251
+ True if successful, False otherwise
252
+ """
253
+ try:
254
+ content = backup_path.read_text(encoding='utf-8')
255
+ original_path.write_text(content, encoding='utf-8')
256
+ return True
257
+ except Exception as e:
258
+ print(f"Rollback failed: {e}")
259
+ return False
260
+
261
+
262
+ def preview_fix(file_path: Path) -> str:
263
+ """
264
+ Preview what would be changed without actually modifying the file.
265
+
266
+ Args:
267
+ file_path: Path to the file
268
+
269
+ Returns:
270
+ Human-readable preview of changes
271
+ """
272
+ fixer = AutoFixer(dry_run=True)
273
+ result = fixer.fix_file(file_path)
274
+
275
+ if not result.success:
276
+ return f"❌ Cannot fix: {result.error}"
277
+
278
+ if not result.original_deps and not result.fixed_deps:
279
+ return "✅ No changes needed (already valid)"
280
+
281
+ lines = [f"Preview for {file_path}:"]
282
+ lines.append(f" Current: ∂{{{','.join(sorted(result.original_deps))}}}")
283
+ lines.append(f" Fixed: ∂{{{','.join(sorted(result.fixed_deps))}}}")
284
+
285
+ added = result.fixed_deps - result.original_deps
286
+ removed = result.original_deps - result.fixed_deps
287
+
288
+ if added:
289
+ lines.append(f" + Add: {', '.join(sorted(added))}")
290
+ if removed:
291
+ lines.append(f" - Remove: {', '.join(sorted(removed))}")
292
+
293
+ return "\n".join(lines)
294
+
295
+
296
+
297
+ # CLI interface removed - use darkarts.validation API or voodocs CLI instead