@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,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DarkArts Annotation Types
|
|
3
|
+
|
|
4
|
+
Data structures for representing parsed DarkArts annotations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import List, Dict, Optional, Any
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AnnotationType(Enum):
|
|
13
|
+
"""Types of annotations supported."""
|
|
14
|
+
FUNCTION = "function"
|
|
15
|
+
METHOD = "method"
|
|
16
|
+
CLASS = "class"
|
|
17
|
+
MODULE = "module"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Language(Enum):
|
|
21
|
+
"""Supported programming languages."""
|
|
22
|
+
PYTHON = "python"
|
|
23
|
+
TYPESCRIPT = "typescript"
|
|
24
|
+
JAVASCRIPT = "javascript"
|
|
25
|
+
JAVA = "java"
|
|
26
|
+
CPP = "cpp"
|
|
27
|
+
CSHARP = "csharp"
|
|
28
|
+
GO = "go"
|
|
29
|
+
RUST = "rust"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ComplexityAnnotation:
|
|
34
|
+
"""Complexity annotations."""
|
|
35
|
+
time: str = "O(1)"
|
|
36
|
+
space: str = "O(1)"
|
|
37
|
+
best_case: Optional[str] = None
|
|
38
|
+
worst_case: Optional[str] = None
|
|
39
|
+
average_case: Optional[str] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class StateTransition:
|
|
44
|
+
"""State transition annotation."""
|
|
45
|
+
from_state: str
|
|
46
|
+
to_state: str
|
|
47
|
+
condition: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
if self.condition:
|
|
51
|
+
return f"{self.from_state} → {self.to_state}: {self.condition}"
|
|
52
|
+
return f"{self.from_state} → {self.to_state}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ErrorCase:
|
|
57
|
+
"""Error case annotation."""
|
|
58
|
+
condition: str
|
|
59
|
+
error_type: str
|
|
60
|
+
|
|
61
|
+
def __str__(self) -> str:
|
|
62
|
+
return f"{self.condition} → {self.error_type}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class FunctionAnnotation:
|
|
67
|
+
"""Annotations for a function or method."""
|
|
68
|
+
name: str
|
|
69
|
+
solve: Optional[str] = None
|
|
70
|
+
preconditions: List[str] = field(default_factory=list)
|
|
71
|
+
postconditions: List[str] = field(default_factory=list)
|
|
72
|
+
invariants: List[str] = field(default_factory=list)
|
|
73
|
+
optimize: Optional[str] = None
|
|
74
|
+
complexity: Optional[ComplexityAnnotation] = None
|
|
75
|
+
error_cases: List[ErrorCase] = field(default_factory=list)
|
|
76
|
+
side_effects: List[str] = field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
# Source location
|
|
79
|
+
line_number: int = 0
|
|
80
|
+
source_file: str = ""
|
|
81
|
+
|
|
82
|
+
# Raw annotation text
|
|
83
|
+
raw_annotation: str = ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class ClassAnnotation:
|
|
88
|
+
"""Annotations for a class."""
|
|
89
|
+
name: str
|
|
90
|
+
class_invariants: List[str] = field(default_factory=list)
|
|
91
|
+
state_transitions: List[StateTransition] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
# Methods in this class
|
|
94
|
+
methods: List[FunctionAnnotation] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
# Source location
|
|
97
|
+
line_number: int = 0
|
|
98
|
+
source_file: str = ""
|
|
99
|
+
|
|
100
|
+
# Raw annotation text
|
|
101
|
+
raw_annotation: str = ""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class ModuleAnnotation:
|
|
106
|
+
"""Annotations for a module/file."""
|
|
107
|
+
name: str
|
|
108
|
+
module_purpose: Optional[str] = None
|
|
109
|
+
dependencies: List[str] = field(default_factory=list)
|
|
110
|
+
assumptions: List[str] = field(default_factory=list)
|
|
111
|
+
|
|
112
|
+
# Classes and functions in this module
|
|
113
|
+
classes: List[ClassAnnotation] = field(default_factory=list)
|
|
114
|
+
functions: List[FunctionAnnotation] = field(default_factory=list)
|
|
115
|
+
|
|
116
|
+
# Source file
|
|
117
|
+
source_file: str = ""
|
|
118
|
+
language: Language = Language.PYTHON
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class ParsedAnnotations:
|
|
123
|
+
"""Complete set of parsed annotations from a source file."""
|
|
124
|
+
module: ModuleAnnotation
|
|
125
|
+
language: Language
|
|
126
|
+
source_file: str
|
|
127
|
+
|
|
128
|
+
def get_all_functions(self) -> List[FunctionAnnotation]:
|
|
129
|
+
"""Get all function annotations (module-level and class methods)."""
|
|
130
|
+
functions = list(self.module.functions)
|
|
131
|
+
for cls in self.module.classes:
|
|
132
|
+
functions.extend(cls.methods)
|
|
133
|
+
return functions
|
|
134
|
+
|
|
135
|
+
def get_all_classes(self) -> List[ClassAnnotation]:
|
|
136
|
+
"""Get all class annotations."""
|
|
137
|
+
return self.module.classes
|
|
138
|
+
|
|
139
|
+
def has_annotations(self) -> bool:
|
|
140
|
+
"""Check if any annotations were found."""
|
|
141
|
+
return (
|
|
142
|
+
bool(self.module.module_purpose) or
|
|
143
|
+
bool(self.module.dependencies) or
|
|
144
|
+
bool(self.module.assumptions) or
|
|
145
|
+
bool(self.module.classes) or
|
|
146
|
+
bool(self.module.functions)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class VerificationResult:
|
|
152
|
+
"""Result of annotation verification."""
|
|
153
|
+
valid: bool
|
|
154
|
+
errors: List[str] = field(default_factory=list)
|
|
155
|
+
warnings: List[str] = field(default_factory=list)
|
|
156
|
+
|
|
157
|
+
def add_error(self, error: str):
|
|
158
|
+
"""Add an error."""
|
|
159
|
+
self.errors.append(error)
|
|
160
|
+
self.valid = False
|
|
161
|
+
|
|
162
|
+
def add_warning(self, warning: str):
|
|
163
|
+
"""Add a warning."""
|
|
164
|
+
self.warnings.append(warning)
|
|
165
|
+
|
|
166
|
+
def __str__(self) -> str:
|
|
167
|
+
if self.valid and not self.warnings:
|
|
168
|
+
return "✓ All annotations are valid"
|
|
169
|
+
|
|
170
|
+
result = []
|
|
171
|
+
if self.errors:
|
|
172
|
+
result.append(f"❌ {len(self.errors)} error(s):")
|
|
173
|
+
for error in self.errors:
|
|
174
|
+
result.append(f" - {error}")
|
|
175
|
+
|
|
176
|
+
if self.warnings:
|
|
177
|
+
result.append(f"⚠️ {len(self.warnings)} warning(s):")
|
|
178
|
+
for warning in self.warnings:
|
|
179
|
+
result.append(f" - {warning}")
|
|
180
|
+
|
|
181
|
+
return "\n".join(result)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for DarkArts.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import argparse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from darkarts.core import DarkArts
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
"""Main CLI entry point."""
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
description="DarkArts - General-purpose AI reasoning platform",
|
|
15
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
16
|
+
epilog="""
|
|
17
|
+
Examples:
|
|
18
|
+
# Solve a math problem
|
|
19
|
+
darkarts solve "Solve x^2 + 5x + 6 = 0" --plugin math
|
|
20
|
+
|
|
21
|
+
# Generate code documentation
|
|
22
|
+
darkarts solve myfile.py --plugin voodocs --file
|
|
23
|
+
|
|
24
|
+
# Auto-detect plugin
|
|
25
|
+
darkarts solve "Find the largest n where n^2 < 100"
|
|
26
|
+
|
|
27
|
+
# List available plugins
|
|
28
|
+
darkarts list
|
|
29
|
+
"""
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
33
|
+
|
|
34
|
+
# Solve command
|
|
35
|
+
solve_parser = subparsers.add_parser("solve", help="Solve a problem")
|
|
36
|
+
solve_parser.add_argument("input", help="Problem text or file path")
|
|
37
|
+
solve_parser.add_argument("--plugin", "-p", help="Plugin to use (auto-detect if not specified)")
|
|
38
|
+
solve_parser.add_argument("--file", "-f", action="store_true", help="Treat input as file path")
|
|
39
|
+
solve_parser.add_argument("--explain", "-e", action="store_true", help="Generate explanation")
|
|
40
|
+
solve_parser.add_argument("--level", "-l", default="student",
|
|
41
|
+
choices=["student", "teacher", "expert"],
|
|
42
|
+
help="Explanation level")
|
|
43
|
+
solve_parser.add_argument("--output", "-o", help="Output file path")
|
|
44
|
+
|
|
45
|
+
# List command
|
|
46
|
+
list_parser = subparsers.add_parser("list", help="List available plugins")
|
|
47
|
+
|
|
48
|
+
# Parse arguments
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
if not args.command:
|
|
52
|
+
parser.print_help()
|
|
53
|
+
return 1
|
|
54
|
+
|
|
55
|
+
# Initialize DarkArts
|
|
56
|
+
da = DarkArts()
|
|
57
|
+
|
|
58
|
+
if args.command == "list":
|
|
59
|
+
return list_plugins(da)
|
|
60
|
+
|
|
61
|
+
elif args.command == "solve":
|
|
62
|
+
return solve_problem(da, args)
|
|
63
|
+
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def list_plugins(da: DarkArts) -> int:
|
|
68
|
+
"""List available plugins."""
|
|
69
|
+
print("Available Plugins:")
|
|
70
|
+
print("=" * 60)
|
|
71
|
+
|
|
72
|
+
for plugin in da.list_plugins():
|
|
73
|
+
print(f"\n{plugin.name} v{plugin.version}")
|
|
74
|
+
print(f" {plugin.description}")
|
|
75
|
+
print(f" Capabilities: {', '.join(plugin.capabilities)}")
|
|
76
|
+
if plugin.dependencies:
|
|
77
|
+
print(f" Dependencies: {', '.join(plugin.dependencies)}")
|
|
78
|
+
|
|
79
|
+
print()
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def solve_problem(da: DarkArts, args) -> int:
|
|
84
|
+
"""Solve a problem."""
|
|
85
|
+
# Get input
|
|
86
|
+
if args.file:
|
|
87
|
+
try:
|
|
88
|
+
input_text = Path(args.input).read_text()
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
91
|
+
return 1
|
|
92
|
+
else:
|
|
93
|
+
input_text = args.input
|
|
94
|
+
|
|
95
|
+
# Solve
|
|
96
|
+
try:
|
|
97
|
+
result = da.solve(input_text, plugin=args.plugin)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
if not result.success:
|
|
103
|
+
print(f"Failed: {result.error}", file=sys.stderr)
|
|
104
|
+
return 1
|
|
105
|
+
|
|
106
|
+
# Output result
|
|
107
|
+
output_text = str(result.result)
|
|
108
|
+
|
|
109
|
+
if args.explain:
|
|
110
|
+
explanation = da.explain(result, level=args.level, plugin=args.plugin)
|
|
111
|
+
output_text = f"{output_text}\n\n{'=' * 60}\nExplanation:\n{'=' * 60}\n\n{explanation}"
|
|
112
|
+
|
|
113
|
+
# Write output
|
|
114
|
+
if args.output:
|
|
115
|
+
try:
|
|
116
|
+
Path(args.output).write_text(output_text)
|
|
117
|
+
print(f"Output written to: {args.output}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Error writing output: {e}", file=sys.stderr)
|
|
120
|
+
return 1
|
|
121
|
+
else:
|
|
122
|
+
print(output_text)
|
|
123
|
+
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
sys.exit(main())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DarkArts Core - Domain-agnostic reasoning engine.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .plugin import (
|
|
6
|
+
DarkArtsPlugin,
|
|
7
|
+
PluginMetadata,
|
|
8
|
+
PluginInput,
|
|
9
|
+
PluginOutput,
|
|
10
|
+
PluginCapability,
|
|
11
|
+
ParseError,
|
|
12
|
+
ExecutionError,
|
|
13
|
+
PluginError,
|
|
14
|
+
)
|
|
15
|
+
from .registry import PluginRegistry, get_global_registry
|
|
16
|
+
from .loader import PluginLoader
|
|
17
|
+
from .interface import DarkArts
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DarkArtsPlugin",
|
|
21
|
+
"PluginMetadata",
|
|
22
|
+
"PluginInput",
|
|
23
|
+
"PluginOutput",
|
|
24
|
+
"PluginCapability",
|
|
25
|
+
"ParseError",
|
|
26
|
+
"ExecutionError",
|
|
27
|
+
"PluginError",
|
|
28
|
+
"PluginRegistry",
|
|
29
|
+
"get_global_registry",
|
|
30
|
+
"PluginLoader",
|
|
31
|
+
"DarkArts",
|
|
32
|
+
]
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified interface to the DarkArts platform.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
from .plugin import DarkArtsPlugin, PluginInput, PluginOutput, PluginMetadata, PluginError
|
|
7
|
+
from .registry import PluginRegistry, get_global_registry
|
|
8
|
+
from .loader import PluginLoader
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DarkArts:
|
|
12
|
+
"""Unified interface to DarkArts platform."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, plugin: Optional[str] = None, registry: Optional[PluginRegistry] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize DarkArts interface.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
plugin: Optional default plugin name
|
|
20
|
+
registry: Optional custom registry (uses global if not provided)
|
|
21
|
+
"""
|
|
22
|
+
self.registry = registry or get_global_registry()
|
|
23
|
+
self.loader = PluginLoader(self.registry)
|
|
24
|
+
self.default_plugin = plugin
|
|
25
|
+
|
|
26
|
+
# Try to load built-in plugins if registry is empty
|
|
27
|
+
if len(self.registry) == 0:
|
|
28
|
+
try:
|
|
29
|
+
self.loader.load_and_register_builtin()
|
|
30
|
+
except Exception:
|
|
31
|
+
pass # Silently fail if no built-in plugins available
|
|
32
|
+
|
|
33
|
+
def solve(self, problem: str, plugin: Optional[str] = None, **options) -> PluginOutput:
|
|
34
|
+
"""
|
|
35
|
+
Solve a problem using specified or default plugin.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
problem: The problem to solve
|
|
39
|
+
plugin: Optional plugin name (uses default if not provided)
|
|
40
|
+
**options: Additional options to pass to the plugin
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Plugin output with results
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
PluginError: If plugin not found or execution fails
|
|
47
|
+
"""
|
|
48
|
+
plugin_name = plugin or self.default_plugin
|
|
49
|
+
|
|
50
|
+
if not plugin_name:
|
|
51
|
+
# Try to auto-detect plugin
|
|
52
|
+
plugin_name = self._auto_detect_plugin(problem)
|
|
53
|
+
|
|
54
|
+
if not plugin_name:
|
|
55
|
+
raise PluginError("No plugin specified and auto-detection failed")
|
|
56
|
+
|
|
57
|
+
plugin_obj = self.registry.get(plugin_name)
|
|
58
|
+
|
|
59
|
+
if not plugin_obj:
|
|
60
|
+
raise PluginError(f"Plugin '{plugin_name}' not found")
|
|
61
|
+
|
|
62
|
+
# Create input
|
|
63
|
+
input_obj = PluginInput(content=problem, options=options)
|
|
64
|
+
|
|
65
|
+
# Validate input
|
|
66
|
+
if not plugin_obj.validate_input(input_obj):
|
|
67
|
+
return PluginOutput(
|
|
68
|
+
success=False,
|
|
69
|
+
result=None,
|
|
70
|
+
error="Invalid input",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Preprocess
|
|
74
|
+
input_obj = plugin_obj.preprocess(input_obj)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# Execute pipeline
|
|
78
|
+
parsed = plugin_obj.parse(input_obj)
|
|
79
|
+
analysis = plugin_obj.analyze(parsed)
|
|
80
|
+
output = plugin_obj.execute(parsed, analysis)
|
|
81
|
+
|
|
82
|
+
# Postprocess
|
|
83
|
+
output = plugin_obj.postprocess(output)
|
|
84
|
+
|
|
85
|
+
# Learn from execution
|
|
86
|
+
if plugin_obj.is_learning_enabled():
|
|
87
|
+
plugin_obj.learn(input_obj, output)
|
|
88
|
+
|
|
89
|
+
return output
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return PluginOutput(
|
|
93
|
+
success=False,
|
|
94
|
+
result=None,
|
|
95
|
+
error=str(e),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def explain(self, result: PluginOutput, level: str = "student",
|
|
99
|
+
plugin: Optional[str] = None) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Generate explanation for a result.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
result: The result to explain
|
|
105
|
+
level: Explanation level (student, teacher, expert)
|
|
106
|
+
plugin: Optional plugin name (uses default if not provided)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Natural language explanation
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
PluginError: If plugin not found
|
|
113
|
+
"""
|
|
114
|
+
plugin_name = plugin or self.default_plugin
|
|
115
|
+
|
|
116
|
+
if not plugin_name:
|
|
117
|
+
raise PluginError("No plugin specified")
|
|
118
|
+
|
|
119
|
+
plugin_obj = self.registry.get(plugin_name)
|
|
120
|
+
|
|
121
|
+
if not plugin_obj:
|
|
122
|
+
raise PluginError(f"Plugin '{plugin_name}' not found")
|
|
123
|
+
|
|
124
|
+
return plugin_obj.explain(result, level)
|
|
125
|
+
|
|
126
|
+
def list_plugins(self) -> List[PluginMetadata]:
|
|
127
|
+
"""
|
|
128
|
+
List all available plugins.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of plugin metadata
|
|
132
|
+
"""
|
|
133
|
+
return self.registry.list()
|
|
134
|
+
|
|
135
|
+
def get_plugin(self, name: str) -> Optional[DarkArtsPlugin]:
|
|
136
|
+
"""
|
|
137
|
+
Get a plugin by name.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
name: Plugin name
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The plugin, or None if not found
|
|
144
|
+
"""
|
|
145
|
+
return self.registry.get(name)
|
|
146
|
+
|
|
147
|
+
def has_plugin(self, name: str) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Check if a plugin is available.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
name: Plugin name
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if available, False otherwise
|
|
156
|
+
"""
|
|
157
|
+
return self.registry.has_plugin(name)
|
|
158
|
+
|
|
159
|
+
def register_plugin(self, plugin: DarkArtsPlugin) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Register a new plugin.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
plugin: The plugin to register
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
PluginError: If registration fails
|
|
168
|
+
"""
|
|
169
|
+
self.registry.register(plugin)
|
|
170
|
+
|
|
171
|
+
def load_plugin(self, source: str, source_type: str = "module") -> DarkArtsPlugin:
|
|
172
|
+
"""
|
|
173
|
+
Load and register a plugin.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
source: Module path, file path, or directory path
|
|
177
|
+
source_type: Type of source ('module', 'file', or 'directory')
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
The loaded plugin (or list of plugins for directory)
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
PluginError: If loading fails
|
|
184
|
+
"""
|
|
185
|
+
return self.loader.load_and_register(source, source_type)
|
|
186
|
+
|
|
187
|
+
def set_default_plugin(self, name: str) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Set the default plugin.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
name: Plugin name
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
PluginError: If plugin not found
|
|
196
|
+
"""
|
|
197
|
+
if not self.registry.has_plugin(name):
|
|
198
|
+
raise PluginError(f"Plugin '{name}' not found")
|
|
199
|
+
|
|
200
|
+
self.default_plugin = name
|
|
201
|
+
|
|
202
|
+
def enable_learning(self, plugin: Optional[str] = None, enabled: bool = True) -> None:
|
|
203
|
+
"""
|
|
204
|
+
Enable or disable learning for a plugin.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
plugin: Optional plugin name (applies to default if not provided)
|
|
208
|
+
enabled: Whether to enable learning
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
PluginError: If plugin not found
|
|
212
|
+
"""
|
|
213
|
+
plugin_name = plugin or self.default_plugin
|
|
214
|
+
|
|
215
|
+
if not plugin_name:
|
|
216
|
+
raise PluginError("No plugin specified")
|
|
217
|
+
|
|
218
|
+
plugin_obj = self.registry.get(plugin_name)
|
|
219
|
+
|
|
220
|
+
if not plugin_obj:
|
|
221
|
+
raise PluginError(f"Plugin '{plugin_name}' not found")
|
|
222
|
+
|
|
223
|
+
plugin_obj.enable_learning(enabled)
|
|
224
|
+
|
|
225
|
+
def _auto_detect_plugin(self, problem: str) -> Optional[str]:
|
|
226
|
+
"""
|
|
227
|
+
Auto-detect which plugin to use based on problem content.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
problem: The problem text
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Plugin name, or None if detection fails
|
|
234
|
+
"""
|
|
235
|
+
# Simple heuristics for now
|
|
236
|
+
problem_lower = problem.lower()
|
|
237
|
+
|
|
238
|
+
# Check for math keywords
|
|
239
|
+
math_keywords = ["solve", "maximize", "minimize", "find", "calculate", "compute"]
|
|
240
|
+
if any(keyword in problem_lower for keyword in math_keywords):
|
|
241
|
+
if self.registry.has_plugin("math"):
|
|
242
|
+
return "math"
|
|
243
|
+
|
|
244
|
+
# Check for code keywords
|
|
245
|
+
code_keywords = ["def ", "class ", "import ", "function", "method"]
|
|
246
|
+
if any(keyword in problem_lower for keyword in code_keywords):
|
|
247
|
+
if self.registry.has_plugin("voodocs"):
|
|
248
|
+
return "voodocs"
|
|
249
|
+
|
|
250
|
+
# Default to math if available
|
|
251
|
+
if self.registry.has_plugin("math"):
|
|
252
|
+
return "math"
|
|
253
|
+
|
|
254
|
+
# Return first available plugin
|
|
255
|
+
plugins = self.registry.list_names()
|
|
256
|
+
return plugins[0] if plugins else None
|