@voodocs/cli 0.4.1 → 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 +322 -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 +385 -0
  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 +20 -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,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