@voodocs/cli 0.1.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 (52) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +153 -0
  3. package/USAGE.md +314 -0
  4. package/cli.py +1340 -0
  5. package/examples/.cursorrules +437 -0
  6. package/examples/instructions/.claude/instructions.md +372 -0
  7. package/examples/instructions/.cursorrules +437 -0
  8. package/examples/instructions/.windsurfrules +437 -0
  9. package/examples/instructions/VOODOCS_INSTRUCTIONS.md +437 -0
  10. package/examples/math_example.py +41 -0
  11. package/examples/phase2_test.py +24 -0
  12. package/examples/test_compound_conditions.py +40 -0
  13. package/examples/test_math_example.py +186 -0
  14. package/lib/darkarts/README.md +115 -0
  15. package/lib/darkarts/__init__.py +16 -0
  16. package/lib/darkarts/annotations/__init__.py +34 -0
  17. package/lib/darkarts/annotations/parser.py +618 -0
  18. package/lib/darkarts/annotations/types.py +181 -0
  19. package/lib/darkarts/cli.py +128 -0
  20. package/lib/darkarts/core/__init__.py +32 -0
  21. package/lib/darkarts/core/interface.py +256 -0
  22. package/lib/darkarts/core/loader.py +231 -0
  23. package/lib/darkarts/core/plugin.py +215 -0
  24. package/lib/darkarts/core/registry.py +146 -0
  25. package/lib/darkarts/exceptions.py +51 -0
  26. package/lib/darkarts/parsers/typescript/dist/cli.d.ts +9 -0
  27. package/lib/darkarts/parsers/typescript/dist/cli.d.ts.map +1 -0
  28. package/lib/darkarts/parsers/typescript/dist/cli.js +69 -0
  29. package/lib/darkarts/parsers/typescript/dist/cli.js.map +1 -0
  30. package/lib/darkarts/parsers/typescript/dist/parser.d.ts +111 -0
  31. package/lib/darkarts/parsers/typescript/dist/parser.d.ts.map +1 -0
  32. package/lib/darkarts/parsers/typescript/dist/parser.js +365 -0
  33. package/lib/darkarts/parsers/typescript/dist/parser.js.map +1 -0
  34. package/lib/darkarts/parsers/typescript/package-lock.json +51 -0
  35. package/lib/darkarts/parsers/typescript/package.json +19 -0
  36. package/lib/darkarts/parsers/typescript/src/cli.ts +41 -0
  37. package/lib/darkarts/parsers/typescript/src/parser.ts +408 -0
  38. package/lib/darkarts/parsers/typescript/tsconfig.json +19 -0
  39. package/lib/darkarts/plugins/voodocs/__init__.py +379 -0
  40. package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +151 -0
  41. package/lib/darkarts/plugins/voodocs/annotation_validator.py +280 -0
  42. package/lib/darkarts/plugins/voodocs/api_spec_generator.py +486 -0
  43. package/lib/darkarts/plugins/voodocs/documentation_generator.py +610 -0
  44. package/lib/darkarts/plugins/voodocs/html_exporter.py +260 -0
  45. package/lib/darkarts/plugins/voodocs/instruction_generator.py +706 -0
  46. package/lib/darkarts/plugins/voodocs/pdf_exporter.py +66 -0
  47. package/lib/darkarts/plugins/voodocs/test_generator.py +636 -0
  48. package/package.json +70 -0
  49. package/requirements.txt +13 -0
  50. package/templates/ci/github-actions.yml +73 -0
  51. package/templates/ci/gitlab-ci.yml +35 -0
  52. package/templates/ci/pre-commit-hook.sh +26 -0
@@ -0,0 +1,379 @@
1
+ """
2
+ Code Documentation Plugin for DarkArts - Automated documentation generation.
3
+ """
4
+
5
+ import ast
6
+ import re
7
+ from typing import Any, Dict, List, Optional
8
+ from dataclasses import dataclass, field
9
+ from darkarts.core import (
10
+ DarkArtsPlugin,
11
+ PluginMetadata,
12
+ PluginInput,
13
+ PluginOutput,
14
+ ParseError,
15
+ )
16
+
17
+
18
+ @dataclass
19
+ class FunctionInfo:
20
+ """Information about a function."""
21
+ name: str
22
+ args: List[str]
23
+ returns: Optional[str]
24
+ docstring: Optional[str]
25
+ decorators: List[str] = field(default_factory=list)
26
+ lineno: int = 0
27
+ complexity: int = 0
28
+
29
+
30
+ @dataclass
31
+ class ClassInfo:
32
+ """Information about a class."""
33
+ name: str
34
+ bases: List[str]
35
+ docstring: Optional[str]
36
+ methods: List[FunctionInfo] = field(default_factory=list)
37
+ attributes: List[str] = field(default_factory=list)
38
+ lineno: int = 0
39
+
40
+
41
+ @dataclass
42
+ class CodeStructure:
43
+ """Parsed code structure."""
44
+ module_docstring: Optional[str]
45
+ imports: List[str]
46
+ functions: List[FunctionInfo]
47
+ classes: List[ClassInfo]
48
+ constants: List[str]
49
+ complexity_score: int = 0
50
+
51
+
52
+ class VooDocsPlugin(DarkArtsPlugin):
53
+ """Plugin for automated code documentation generation."""
54
+
55
+ def __init__(self):
56
+ """Initialize the code docs plugin."""
57
+ super().__init__()
58
+ self._metadata = PluginMetadata(
59
+ name="voodocs",
60
+ version="1.0.0",
61
+ description="Automated code documentation generation from Python source",
62
+ author="DarkArts Team",
63
+ dependencies=[],
64
+ capabilities=["parse", "analyze", "execute", "explain"],
65
+ )
66
+
67
+ @property
68
+ def metadata(self) -> PluginMetadata:
69
+ """Return plugin metadata."""
70
+ return self._metadata
71
+
72
+ def parse(self, input: PluginInput) -> CodeStructure:
73
+ """
74
+ Parse Python source code.
75
+
76
+ Args:
77
+ input: The input containing Python source code
78
+
79
+ Returns:
80
+ Parsed code structure
81
+
82
+ Raises:
83
+ ParseError: If parsing fails
84
+ """
85
+ try:
86
+ tree = ast.parse(input.content)
87
+ return self._extract_structure(tree, input.content)
88
+ except SyntaxError as e:
89
+ raise ParseError(f"Syntax error in code: {e}")
90
+ except Exception as e:
91
+ raise ParseError(f"Failed to parse code: {e}")
92
+
93
+ def analyze(self, parsed: CodeStructure) -> Dict[str, Any]:
94
+ """
95
+ Analyze code complexity and patterns.
96
+
97
+ Args:
98
+ parsed: The parsed code structure
99
+
100
+ Returns:
101
+ Analysis results
102
+ """
103
+ return {
104
+ "functions_count": len(parsed.functions),
105
+ "classes_count": len(parsed.classes),
106
+ "imports_count": len(parsed.imports),
107
+ "complexity": parsed.complexity_score,
108
+ "has_docstrings": self._check_docstrings(parsed),
109
+ "documentation_coverage": self._calculate_coverage(parsed),
110
+ }
111
+
112
+ def execute(self, parsed: CodeStructure, analysis: Dict[str, Any]) -> PluginOutput:
113
+ """
114
+ Generate documentation.
115
+
116
+ Args:
117
+ parsed: The parsed code structure
118
+ analysis: Analysis results
119
+
120
+ Returns:
121
+ Plugin output with generated documentation
122
+ """
123
+ try:
124
+ docs = self._generate_markdown(parsed, analysis)
125
+
126
+ return PluginOutput(
127
+ success=True,
128
+ result=docs,
129
+ explanation=self._generate_summary(parsed, analysis),
130
+ metadata=analysis,
131
+ )
132
+ except Exception as e:
133
+ return PluginOutput(
134
+ success=False,
135
+ result=None,
136
+ error=str(e),
137
+ )
138
+
139
+ def explain(self, output: PluginOutput, level: str = "student") -> str:
140
+ """
141
+ Generate explanation of the documentation.
142
+
143
+ Args:
144
+ output: The output to explain
145
+ level: Explanation level
146
+
147
+ Returns:
148
+ Natural language explanation
149
+ """
150
+ if not output.success:
151
+ return f"Failed to generate documentation: {output.error}"
152
+
153
+ if output.explanation:
154
+ return output.explanation
155
+
156
+ return "Documentation generated successfully."
157
+
158
+ def _extract_structure(self, tree: ast.AST, source: str) -> CodeStructure:
159
+ """Extract structure from AST."""
160
+ module_docstring = ast.get_docstring(tree)
161
+ imports = []
162
+ functions = []
163
+ classes = []
164
+ constants = []
165
+
166
+ # Only iterate over top-level nodes
167
+ for node in tree.body:
168
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
169
+ imports.append(ast.unparse(node))
170
+
171
+ elif isinstance(node, ast.FunctionDef):
172
+ functions.append(self._extract_function(node))
173
+
174
+ elif isinstance(node, ast.ClassDef):
175
+ classes.append(self._extract_class(node))
176
+
177
+ elif isinstance(node, ast.Assign):
178
+ # Top-level constants (uppercase names)
179
+ for target in node.targets:
180
+ if isinstance(target, ast.Name) and target.id.isupper():
181
+ constants.append(target.id)
182
+
183
+ # Calculate complexity
184
+ complexity = self._calculate_complexity(tree)
185
+
186
+ return CodeStructure(
187
+ module_docstring=module_docstring,
188
+ imports=imports,
189
+ functions=functions,
190
+ classes=classes,
191
+ constants=constants,
192
+ complexity_score=complexity,
193
+ )
194
+
195
+ def _extract_function(self, node: ast.FunctionDef) -> FunctionInfo:
196
+ """Extract function information."""
197
+ args = [arg.arg for arg in node.args.args]
198
+ returns = ast.unparse(node.returns) if node.returns else None
199
+ docstring = ast.get_docstring(node)
200
+ decorators = [ast.unparse(d) for d in node.decorator_list]
201
+
202
+ # Calculate function complexity (simple metric)
203
+ complexity = sum(1 for _ in ast.walk(node) if isinstance(_, (ast.If, ast.For, ast.While, ast.Try)))
204
+
205
+ return FunctionInfo(
206
+ name=node.name,
207
+ args=args,
208
+ returns=returns,
209
+ docstring=docstring,
210
+ decorators=decorators,
211
+ lineno=node.lineno,
212
+ complexity=complexity,
213
+ )
214
+
215
+ def _extract_class(self, node: ast.ClassDef) -> ClassInfo:
216
+ """Extract class information."""
217
+ bases = [ast.unparse(base) for base in node.bases]
218
+ docstring = ast.get_docstring(node)
219
+ methods = []
220
+ attributes = []
221
+
222
+ for item in node.body:
223
+ if isinstance(item, ast.FunctionDef):
224
+ methods.append(self._extract_function(item))
225
+ elif isinstance(item, ast.Assign):
226
+ for target in item.targets:
227
+ if isinstance(target, ast.Name):
228
+ attributes.append(target.id)
229
+
230
+ return ClassInfo(
231
+ name=node.name,
232
+ bases=bases,
233
+ docstring=docstring,
234
+ methods=methods,
235
+ attributes=attributes,
236
+ lineno=node.lineno,
237
+ )
238
+
239
+ def _calculate_complexity(self, tree: ast.AST) -> int:
240
+ """Calculate cyclomatic complexity."""
241
+ complexity = 1 # Base complexity
242
+
243
+ for node in ast.walk(tree):
244
+ if isinstance(node, (ast.If, ast.While, ast.For, ast.ExceptHandler)):
245
+ complexity += 1
246
+ elif isinstance(node, ast.BoolOp):
247
+ complexity += len(node.values) - 1
248
+
249
+ return complexity
250
+
251
+ def _check_docstrings(self, structure: CodeStructure) -> bool:
252
+ """Check if code has docstrings."""
253
+ has_module_doc = structure.module_docstring is not None
254
+ has_function_docs = any(f.docstring for f in structure.functions)
255
+ has_class_docs = any(c.docstring for c in structure.classes)
256
+
257
+ return has_module_doc or has_function_docs or has_class_docs
258
+
259
+ def _calculate_coverage(self, structure: CodeStructure) -> float:
260
+ """Calculate documentation coverage percentage."""
261
+ total_items = 1 + len(structure.functions) + len(structure.classes) # +1 for module
262
+ documented_items = 0
263
+
264
+ if structure.module_docstring:
265
+ documented_items += 1
266
+
267
+ documented_items += sum(1 for f in structure.functions if f.docstring)
268
+ documented_items += sum(1 for c in structure.classes if c.docstring)
269
+
270
+ return (documented_items / total_items * 100) if total_items > 0 else 0
271
+
272
+ def _generate_summary(self, structure: CodeStructure, analysis: Dict[str, Any]) -> str:
273
+ """Generate a summary of the documentation."""
274
+ summary = f"Generated documentation for:\n"
275
+ summary += f"- {analysis['functions_count']} functions\n"
276
+ summary += f"- {analysis['classes_count']} classes\n"
277
+ summary += f"- {analysis['imports_count']} imports\n"
278
+ summary += f"- Complexity score: {analysis['complexity']}\n"
279
+ summary += f"- Documentation coverage: {analysis['documentation_coverage']:.1f}%\n"
280
+
281
+ return summary
282
+
283
+ def _generate_markdown(self, structure: CodeStructure, analysis: Dict[str, Any]) -> str:
284
+ """Generate Markdown documentation."""
285
+ md = []
286
+
287
+ # Module header
288
+ md.append("# Module Documentation\n")
289
+
290
+ if structure.module_docstring:
291
+ md.append(f"{structure.module_docstring}\n")
292
+
293
+ # Metrics
294
+ md.append("## Metrics\n")
295
+ md.append(f"- **Functions**: {analysis['functions_count']}")
296
+ md.append(f"- **Classes**: {analysis['classes_count']}")
297
+ md.append(f"- **Complexity**: {analysis['complexity']}")
298
+ md.append(f"- **Documentation Coverage**: {analysis['documentation_coverage']:.1f}%\n")
299
+
300
+ # Imports
301
+ if structure.imports:
302
+ md.append("## Imports\n")
303
+ md.append("```python")
304
+ md.extend(structure.imports)
305
+ md.append("```\n")
306
+
307
+ # Constants
308
+ if structure.constants:
309
+ md.append("## Constants\n")
310
+ for const in structure.constants:
311
+ md.append(f"- `{const}`")
312
+ md.append("")
313
+
314
+ # Functions
315
+ if structure.functions:
316
+ md.append("## Functions\n")
317
+ for func in structure.functions:
318
+ md.append(f"### `{func.name}({', '.join(func.args)})`\n")
319
+
320
+ if func.decorators:
321
+ md.append(f"**Decorators**: {', '.join(f'`{d}`' for d in func.decorators)}\n")
322
+
323
+ if func.returns:
324
+ md.append(f"**Returns**: `{func.returns}`\n")
325
+
326
+ if func.docstring:
327
+ md.append(func.docstring)
328
+ else:
329
+ md.append("*No documentation available.*")
330
+
331
+ md.append(f"\n**Complexity**: {func.complexity}\n")
332
+
333
+ # Classes
334
+ if structure.classes:
335
+ md.append("## Classes\n")
336
+ for cls in structure.classes:
337
+ md.append(f"### `class {cls.name}`\n")
338
+
339
+ if cls.bases:
340
+ md.append(f"**Inherits from**: {', '.join(f'`{b}`' for b in cls.bases)}\n")
341
+
342
+ if cls.docstring:
343
+ md.append(cls.docstring)
344
+ else:
345
+ md.append("*No documentation available.*")
346
+
347
+ md.append("")
348
+
349
+ if cls.attributes:
350
+ md.append("**Attributes**:")
351
+ for attr in cls.attributes:
352
+ md.append(f"- `{attr}`")
353
+ md.append("")
354
+
355
+ if cls.methods:
356
+ md.append("**Methods**:\n")
357
+ for method in cls.methods:
358
+ md.append(f"#### `{method.name}({', '.join(method.args)})`\n")
359
+ if method.docstring:
360
+ md.append(method.docstring)
361
+ else:
362
+ md.append("*No documentation available.*")
363
+ md.append("")
364
+
365
+ return "\n".join(md)
366
+
367
+
368
+ # Register plugin on import
369
+ from darkarts.core import get_global_registry
370
+
371
+ try:
372
+ registry = get_global_registry()
373
+ if not registry.has_plugin("voodocs"):
374
+ registry.register(VooDocsPlugin())
375
+ except Exception:
376
+ pass # Silently fail if registration fails
377
+
378
+
379
+ __all__ = ["VooDocsPlugin"]
@@ -0,0 +1,151 @@
1
+ """
2
+ AI-Native VooDocs Plugin for DarkArts
3
+
4
+ Enhanced version that supports both:
5
+ 1. Traditional code documentation (AST-based)
6
+ 2. AI-native documentation (@voodocs annotations)
7
+ """
8
+
9
+ from darkarts.core import (
10
+ DarkArtsPlugin,
11
+ PluginMetadata,
12
+ PluginInput,
13
+ PluginOutput,
14
+ )
15
+ from darkarts.annotations import AnnotationParser
16
+ from .documentation_generator import DocumentationGenerator
17
+ from .test_generator import TestGenerator
18
+ from .api_spec_generator import APISpecGenerator
19
+
20
+
21
+ class AIVooDocsPlugin(DarkArtsPlugin):
22
+ """Enhanced plugin for AI-native code documentation."""
23
+
24
+ def __init__(self):
25
+ super().__init__()
26
+ self._metadata = PluginMetadata(
27
+ name="ai_voodocs",
28
+ version="2.0.0",
29
+ description="AI-native documentation system with automatic test and API generation from @voodocs annotations",
30
+ author="DarkArts Team",
31
+ dependencies=[],
32
+ capabilities=["parse", "analyze", "execute", "explain"],
33
+ )
34
+
35
+ # Initialize generators
36
+ self.parser = AnnotationParser()
37
+ self.doc_generator = DocumentationGenerator()
38
+ self.test_generator = TestGenerator()
39
+ self.api_generator = APISpecGenerator()
40
+
41
+ @property
42
+ def metadata(self) -> PluginMetadata:
43
+ return self._metadata
44
+
45
+ def parse(self, input: PluginInput) -> dict:
46
+ """Parse source code and extract @voodocs annotations."""
47
+ source_code = input.content
48
+ source_file = input.metadata.get('source_file', '<string>')
49
+
50
+ # Parse annotations
51
+ parsed = self.parser.parse_source(source_code, source_file)
52
+
53
+ return {
54
+ 'parsed_annotations': parsed,
55
+ 'has_annotations': parsed.has_annotations(),
56
+ 'source_code': source_code,
57
+ 'source_file': source_file,
58
+ }
59
+
60
+ def analyze(self, parsed: dict) -> dict:
61
+ """Analyze the parsed annotations."""
62
+ annotations = parsed['parsed_annotations']
63
+
64
+ return {
65
+ 'module_name': annotations.module.name,
66
+ 'has_module_annotations': bool(
67
+ annotations.module.module_purpose or
68
+ annotations.module.dependencies or
69
+ annotations.module.assumptions
70
+ ),
71
+ 'classes_count': len(annotations.module.classes),
72
+ 'functions_count': len(annotations.get_all_functions()),
73
+ 'has_annotations': parsed['has_annotations'],
74
+ }
75
+
76
+ def execute(self, parsed: dict, analysis: dict) -> PluginOutput:
77
+ """Generate documentation, tests, and API specs."""
78
+ try:
79
+ annotations = parsed['parsed_annotations']
80
+
81
+ # Generate all outputs
82
+ documentation = self.doc_generator.generate(annotations)
83
+ tests = self.test_generator.generate(annotations)
84
+ api_spec = self.api_generator.generate(annotations)
85
+
86
+ result = {
87
+ 'documentation': documentation,
88
+ 'tests': tests,
89
+ 'api_spec': api_spec,
90
+ 'module': analysis['module_name'],
91
+ }
92
+
93
+ return PluginOutput(
94
+ success=True,
95
+ result=result,
96
+ explanation=self._generate_explanation(analysis, result),
97
+ metadata=analysis,
98
+ )
99
+
100
+ except Exception as e:
101
+ return PluginOutput(
102
+ success=False,
103
+ result=None,
104
+ error=str(e),
105
+ )
106
+
107
+ def explain(self, output: PluginOutput, level: str = "student") -> str:
108
+ """Explain the AI-native documentation generation."""
109
+ if not output.success:
110
+ return f"Failed to generate documentation: {output.error}"
111
+
112
+ if output.explanation:
113
+ return output.explanation
114
+
115
+ return "AI-native documentation generated successfully."
116
+
117
+ def _generate_explanation(self, analysis: dict, result: dict) -> str:
118
+ """Generate explanation of the process."""
119
+ lines = []
120
+
121
+ lines.append("# AI-Native Documentation System")
122
+ lines.append("")
123
+ lines.append(f"**Module**: {analysis['module_name']}")
124
+ lines.append(f"**Has DarkArts Annotations**: {analysis['has_annotations']}")
125
+ lines.append("")
126
+
127
+ lines.append("## Statistics")
128
+ lines.append(f"- Classes: {analysis['classes_count']}")
129
+ lines.append(f"- Functions: {analysis['functions_count']}")
130
+ lines.append(f"- Module Annotations: {'Yes' if analysis['has_module_annotations'] else 'No'}")
131
+ lines.append("")
132
+
133
+ lines.append("## Generated Outputs")
134
+ lines.append("✅ **Documentation**: Human-readable Markdown from DarkArts annotations")
135
+ lines.append("✅ **Tests**: Automated test cases from preconditions/postconditions")
136
+ lines.append("✅ **API Spec**: OpenAPI specification from function contracts")
137
+ lines.append("")
138
+
139
+ lines.append("## How It Works")
140
+ lines.append("1. **AI writes** precise mathematical/logical specifications in @voodocs annotations")
141
+ lines.append("2. **Parser extracts** annotations from source code")
142
+ lines.append("3. **Generators translate** annotations into:")
143
+ lines.append(" - Human-readable documentation (Markdown)")
144
+ lines.append(" - Automated test cases (pytest/unittest/jest)")
145
+ lines.append(" - API specifications (OpenAPI/GraphQL)")
146
+
147
+ return "\n".join(lines)
148
+
149
+
150
+ # Export
151
+ __all__ = ['AIVooDocsPlugin']