@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.
- package/LICENSE +37 -0
- package/README.md +153 -0
- package/USAGE.md +314 -0
- package/cli.py +1340 -0
- package/examples/.cursorrules +437 -0
- package/examples/instructions/.claude/instructions.md +372 -0
- package/examples/instructions/.cursorrules +437 -0
- package/examples/instructions/.windsurfrules +437 -0
- package/examples/instructions/VOODOCS_INSTRUCTIONS.md +437 -0
- package/examples/math_example.py +41 -0
- package/examples/phase2_test.py +24 -0
- package/examples/test_compound_conditions.py +40 -0
- package/examples/test_math_example.py +186 -0
- package/lib/darkarts/README.md +115 -0
- package/lib/darkarts/__init__.py +16 -0
- package/lib/darkarts/annotations/__init__.py +34 -0
- package/lib/darkarts/annotations/parser.py +618 -0
- package/lib/darkarts/annotations/types.py +181 -0
- package/lib/darkarts/cli.py +128 -0
- package/lib/darkarts/core/__init__.py +32 -0
- package/lib/darkarts/core/interface.py +256 -0
- package/lib/darkarts/core/loader.py +231 -0
- package/lib/darkarts/core/plugin.py +215 -0
- package/lib/darkarts/core/registry.py +146 -0
- package/lib/darkarts/exceptions.py +51 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts +9 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js +69 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts +111 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js +365 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js.map +1 -0
- package/lib/darkarts/parsers/typescript/package-lock.json +51 -0
- package/lib/darkarts/parsers/typescript/package.json +19 -0
- package/lib/darkarts/parsers/typescript/src/cli.ts +41 -0
- package/lib/darkarts/parsers/typescript/src/parser.ts +408 -0
- package/lib/darkarts/parsers/typescript/tsconfig.json +19 -0
- package/lib/darkarts/plugins/voodocs/__init__.py +379 -0
- package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +151 -0
- package/lib/darkarts/plugins/voodocs/annotation_validator.py +280 -0
- package/lib/darkarts/plugins/voodocs/api_spec_generator.py +486 -0
- package/lib/darkarts/plugins/voodocs/documentation_generator.py +610 -0
- package/lib/darkarts/plugins/voodocs/html_exporter.py +260 -0
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +706 -0
- package/lib/darkarts/plugins/voodocs/pdf_exporter.py +66 -0
- package/lib/darkarts/plugins/voodocs/test_generator.py +636 -0
- package/package.json +70 -0
- package/requirements.txt +13 -0
- package/templates/ci/github-actions.yml +73 -0
- package/templates/ci/gitlab-ci.yml +35 -0
- package/templates/ci/pre-commit-hook.sh +26 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VooDocs Annotation Validator
|
|
3
|
+
|
|
4
|
+
Validates @voodocs annotations for correctness and quality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
from darkarts.annotations.types import ParsedAnnotations, FunctionAnnotation, ClassAnnotation
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ValidationIssue:
|
|
13
|
+
"""Represents a validation issue."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, severity: str, message: str, location: str, suggestion: Optional[str] = None):
|
|
16
|
+
self.severity = severity # "error", "warning", "info"
|
|
17
|
+
self.message = message
|
|
18
|
+
self.location = location
|
|
19
|
+
self.suggestion = suggestion
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
icon = "❌" if self.severity == "error" else "⚠️" if self.severity == "warning" else "ℹ️"
|
|
23
|
+
result = f"{icon} [{self.severity.upper()}] {self.location}: {self.message}"
|
|
24
|
+
if self.suggestion:
|
|
25
|
+
result += f"\n 💡 Suggestion: {self.suggestion}"
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnnotationValidator:
|
|
30
|
+
"""Validates VooDocs annotations."""
|
|
31
|
+
|
|
32
|
+
VALID_FIELDS = {
|
|
33
|
+
"module_purpose", "dependencies", "assumptions", "security_model",
|
|
34
|
+
"preconditions", "postconditions", "invariants", "complexity",
|
|
35
|
+
"error_cases", "security_implications", "solve", "optimize",
|
|
36
|
+
"class_invariants", "state_transitions", "thread_safety"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
REQUIRED_FUNCTION_FIELDS = {"preconditions", "postconditions"}
|
|
40
|
+
REQUIRED_CLASS_FIELDS = {"class_invariants"}
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.issues = []
|
|
44
|
+
|
|
45
|
+
def validate(self, parsed: ParsedAnnotations) -> List[ValidationIssue]:
|
|
46
|
+
"""Validate all annotations in a parsed file."""
|
|
47
|
+
self.issues = []
|
|
48
|
+
|
|
49
|
+
# Validate module annotations
|
|
50
|
+
self._validate_module(parsed.module)
|
|
51
|
+
|
|
52
|
+
# Validate function annotations
|
|
53
|
+
for func in parsed.get_all_functions():
|
|
54
|
+
self._validate_function(func)
|
|
55
|
+
|
|
56
|
+
# Validate class annotations
|
|
57
|
+
for cls in parsed.module.classes:
|
|
58
|
+
self._validate_class(cls)
|
|
59
|
+
|
|
60
|
+
return self.issues
|
|
61
|
+
|
|
62
|
+
def _validate_module(self, module):
|
|
63
|
+
"""Validate module-level annotations."""
|
|
64
|
+
location = f"Module '{module.name}'"
|
|
65
|
+
|
|
66
|
+
# Check for module_purpose
|
|
67
|
+
if not module.module_purpose:
|
|
68
|
+
self.issues.append(ValidationIssue(
|
|
69
|
+
"info",
|
|
70
|
+
"No module_purpose defined",
|
|
71
|
+
location,
|
|
72
|
+
"Add a module_purpose to describe what this module does"
|
|
73
|
+
))
|
|
74
|
+
|
|
75
|
+
# Check dependencies format
|
|
76
|
+
if module.dependencies:
|
|
77
|
+
for dep in module.dependencies:
|
|
78
|
+
if not isinstance(dep, str):
|
|
79
|
+
self.issues.append(ValidationIssue(
|
|
80
|
+
"error",
|
|
81
|
+
f"Invalid dependency format: {dep}",
|
|
82
|
+
location,
|
|
83
|
+
"Dependencies should be strings"
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
def _validate_function(self, func: FunctionAnnotation):
|
|
87
|
+
"""Validate function annotations."""
|
|
88
|
+
location = f"Function '{func.name}'"
|
|
89
|
+
|
|
90
|
+
# Check for required fields
|
|
91
|
+
if not func.preconditions:
|
|
92
|
+
self.issues.append(ValidationIssue(
|
|
93
|
+
"warning",
|
|
94
|
+
"No preconditions defined",
|
|
95
|
+
location,
|
|
96
|
+
"Add preconditions to specify input requirements"
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
if not func.postconditions:
|
|
100
|
+
self.issues.append(ValidationIssue(
|
|
101
|
+
"warning",
|
|
102
|
+
"No postconditions defined",
|
|
103
|
+
location,
|
|
104
|
+
"Add postconditions to specify output guarantees"
|
|
105
|
+
))
|
|
106
|
+
|
|
107
|
+
# Validate preconditions
|
|
108
|
+
if func.preconditions:
|
|
109
|
+
for pre in func.preconditions:
|
|
110
|
+
self._validate_condition(pre, location, "precondition")
|
|
111
|
+
|
|
112
|
+
# Validate postconditions
|
|
113
|
+
if func.postconditions:
|
|
114
|
+
for post in func.postconditions:
|
|
115
|
+
self._validate_condition(post, location, "postcondition")
|
|
116
|
+
|
|
117
|
+
# Check for complexity annotation
|
|
118
|
+
if not func.complexity:
|
|
119
|
+
self.issues.append(ValidationIssue(
|
|
120
|
+
"info",
|
|
121
|
+
"No complexity annotation",
|
|
122
|
+
location,
|
|
123
|
+
"Add complexity annotation for performance-critical functions"
|
|
124
|
+
))
|
|
125
|
+
else:
|
|
126
|
+
# Validate complexity format
|
|
127
|
+
if func.complexity.time:
|
|
128
|
+
if not self._is_valid_complexity(func.complexity.time):
|
|
129
|
+
self.issues.append(ValidationIssue(
|
|
130
|
+
"warning",
|
|
131
|
+
f"Invalid time complexity format: {func.complexity.time}",
|
|
132
|
+
location,
|
|
133
|
+
"Use Big-O notation like O(n), O(log n), O(1)"
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
if func.complexity.space:
|
|
137
|
+
if not self._is_valid_complexity(func.complexity.space):
|
|
138
|
+
self.issues.append(ValidationIssue(
|
|
139
|
+
"warning",
|
|
140
|
+
f"Invalid space complexity format: {func.complexity.space}",
|
|
141
|
+
location,
|
|
142
|
+
"Use Big-O notation like O(n), O(log n), O(1)"
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
# Check for error_cases
|
|
146
|
+
if not func.error_cases:
|
|
147
|
+
self.issues.append(ValidationIssue(
|
|
148
|
+
"info",
|
|
149
|
+
"No error_cases defined",
|
|
150
|
+
location,
|
|
151
|
+
"Add error_cases to document failure modes"
|
|
152
|
+
))
|
|
153
|
+
|
|
154
|
+
# Validate consistency between pre and post conditions
|
|
155
|
+
self._validate_consistency(func, location)
|
|
156
|
+
|
|
157
|
+
def _validate_class(self, cls: ClassAnnotation):
|
|
158
|
+
"""Validate class annotations."""
|
|
159
|
+
location = f"Class '{cls.name}'"
|
|
160
|
+
|
|
161
|
+
# Check for class invariants
|
|
162
|
+
if not cls.class_invariants:
|
|
163
|
+
self.issues.append(ValidationIssue(
|
|
164
|
+
"warning",
|
|
165
|
+
"No class_invariants defined",
|
|
166
|
+
location,
|
|
167
|
+
"Add class_invariants to specify object state constraints"
|
|
168
|
+
))
|
|
169
|
+
|
|
170
|
+
# Check for state transitions
|
|
171
|
+
if not cls.state_transitions:
|
|
172
|
+
self.issues.append(ValidationIssue(
|
|
173
|
+
"info",
|
|
174
|
+
"No state_transitions defined",
|
|
175
|
+
location,
|
|
176
|
+
"Add state_transitions if this class has state changes"
|
|
177
|
+
))
|
|
178
|
+
|
|
179
|
+
# Validate methods
|
|
180
|
+
if cls.methods:
|
|
181
|
+
for method in cls.methods:
|
|
182
|
+
self._validate_function(method)
|
|
183
|
+
|
|
184
|
+
def _validate_condition(self, condition: str, location: str, cond_type: str):
|
|
185
|
+
"""Validate a single condition (precondition or postcondition)."""
|
|
186
|
+
# Check for empty conditions
|
|
187
|
+
if not condition or not condition.strip():
|
|
188
|
+
self.issues.append(ValidationIssue(
|
|
189
|
+
"error",
|
|
190
|
+
f"Empty {cond_type}",
|
|
191
|
+
location,
|
|
192
|
+
f"Remove empty {cond_type} or add meaningful constraint"
|
|
193
|
+
))
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Check for common typos in mathematical notation
|
|
197
|
+
if '∈' in condition:
|
|
198
|
+
# Valid: x ∈ ℝ, x ∈ ℤ, x ∈ ℕ
|
|
199
|
+
if not re.search(r'\w+\s*∈\s*[ℝℤℕ]', condition):
|
|
200
|
+
self.issues.append(ValidationIssue(
|
|
201
|
+
"warning",
|
|
202
|
+
f"Possibly malformed set membership: {condition}",
|
|
203
|
+
location,
|
|
204
|
+
"Use ∈ with standard sets: ℝ (reals), ℤ (integers), ℕ (naturals)"
|
|
205
|
+
))
|
|
206
|
+
|
|
207
|
+
# Check for inconsistent operators
|
|
208
|
+
if '⇒' in condition or '⇔' in condition:
|
|
209
|
+
# These should be used correctly
|
|
210
|
+
if condition.count('⇒') > 1 and '∧' not in condition and '∨' not in condition:
|
|
211
|
+
self.issues.append(ValidationIssue(
|
|
212
|
+
"info",
|
|
213
|
+
f"Complex implication: {condition}",
|
|
214
|
+
location,
|
|
215
|
+
"Consider breaking into multiple conditions for clarity"
|
|
216
|
+
))
|
|
217
|
+
|
|
218
|
+
# Check for undefined variables
|
|
219
|
+
# This is a simple heuristic - in practice, you'd need proper parsing
|
|
220
|
+
if cond_type == "postcondition" and "result" not in condition.lower():
|
|
221
|
+
# Postconditions should usually reference the result
|
|
222
|
+
self.issues.append(ValidationIssue(
|
|
223
|
+
"info",
|
|
224
|
+
f"Postcondition doesn't reference 'result': {condition}",
|
|
225
|
+
location,
|
|
226
|
+
"Postconditions typically specify properties of the result"
|
|
227
|
+
))
|
|
228
|
+
|
|
229
|
+
def _is_valid_complexity(self, complexity: str) -> bool:
|
|
230
|
+
"""Check if complexity notation is valid."""
|
|
231
|
+
# Valid patterns: O(1), O(n), O(log n), O(n log n), O(n^2), etc.
|
|
232
|
+
pattern = r'^O\([^)]+\)$'
|
|
233
|
+
return bool(re.match(pattern, complexity))
|
|
234
|
+
|
|
235
|
+
def _validate_consistency(self, func: FunctionAnnotation, location: str):
|
|
236
|
+
"""Validate consistency between preconditions and postconditions."""
|
|
237
|
+
if not func.preconditions or not func.postconditions:
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# Extract variable names from preconditions
|
|
241
|
+
pre_vars = set()
|
|
242
|
+
for pre in func.preconditions:
|
|
243
|
+
# Simple heuristic: extract words that look like variable names
|
|
244
|
+
matches = re.findall(r'\b([a-z_][a-z0-9_]*)\b', pre.lower())
|
|
245
|
+
pre_vars.update(matches)
|
|
246
|
+
|
|
247
|
+
# Extract variable names from postconditions
|
|
248
|
+
post_vars = set()
|
|
249
|
+
for post in func.postconditions:
|
|
250
|
+
matches = re.findall(r'\b([a-z_][a-z0-9_]*)\b', post.lower())
|
|
251
|
+
post_vars.update(matches)
|
|
252
|
+
|
|
253
|
+
# Remove common keywords
|
|
254
|
+
keywords = {'and', 'or', 'not', 'in', 'is', 'must', 'be', 'the', 'a', 'an', 'for', 'all', 'exists'}
|
|
255
|
+
pre_vars -= keywords
|
|
256
|
+
post_vars -= keywords
|
|
257
|
+
|
|
258
|
+
# Check if postconditions reference precondition variables
|
|
259
|
+
# (This is a weak check but can catch some issues)
|
|
260
|
+
if pre_vars and not (pre_vars & post_vars or 'result' in post_vars):
|
|
261
|
+
self.issues.append(ValidationIssue(
|
|
262
|
+
"info",
|
|
263
|
+
"Postconditions don't reference precondition variables",
|
|
264
|
+
location,
|
|
265
|
+
"Consider relating output to input in postconditions"
|
|
266
|
+
))
|
|
267
|
+
|
|
268
|
+
def get_summary(self) -> Dict[str, int]:
|
|
269
|
+
"""Get summary of validation issues."""
|
|
270
|
+
summary = {
|
|
271
|
+
"errors": sum(1 for issue in self.issues if issue.severity == "error"),
|
|
272
|
+
"warnings": sum(1 for issue in self.issues if issue.severity == "warning"),
|
|
273
|
+
"info": sum(1 for issue in self.issues if issue.severity == "info"),
|
|
274
|
+
"total": len(self.issues)
|
|
275
|
+
}
|
|
276
|
+
return summary
|
|
277
|
+
|
|
278
|
+
def has_errors(self) -> bool:
|
|
279
|
+
"""Check if there are any errors."""
|
|
280
|
+
return any(issue.severity == "error" for issue in self.issues)
|