algomath-extract 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 (90) hide show
  1. package/README.md +260 -0
  2. package/bin/algo-extract.js +143 -0
  3. package/bin/algo-generate.js +102 -0
  4. package/bin/algo-help.js +136 -0
  5. package/bin/algo-list.js +56 -0
  6. package/bin/algo-run.js +141 -0
  7. package/bin/algo-status.js +88 -0
  8. package/bin/algo-verify.js +189 -0
  9. package/bin/install.js +349 -0
  10. package/package.json +57 -0
  11. package/requirements.txt +20 -0
  12. package/src/__pycache__/intent.cpython-313.pyc +0 -0
  13. package/src/cli/__pycache__/commands.cpython-313.pyc +0 -0
  14. package/src/cli/cli_entry.py +106 -0
  15. package/src/cli/commands.py +339 -0
  16. package/src/execution/__init__.py +74 -0
  17. package/src/execution/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/src/execution/__pycache__/display.cpython-313.pyc +0 -0
  19. package/src/execution/__pycache__/errors.cpython-313.pyc +0 -0
  20. package/src/execution/__pycache__/executor.cpython-313.pyc +0 -0
  21. package/src/execution/__pycache__/sandbox.cpython-313.pyc +0 -0
  22. package/src/execution/display.py +261 -0
  23. package/src/execution/errors.py +158 -0
  24. package/src/execution/executor.py +253 -0
  25. package/src/execution/sandbox.py +333 -0
  26. package/src/extraction/__init__.py +102 -0
  27. package/src/extraction/__pycache__/__init__.cpython-313.pyc +0 -0
  28. package/src/extraction/__pycache__/boundaries.cpython-313.pyc +0 -0
  29. package/src/extraction/__pycache__/errors.cpython-313.pyc +0 -0
  30. package/src/extraction/__pycache__/llm_extraction.cpython-313.pyc +0 -0
  31. package/src/extraction/__pycache__/notation.cpython-313.pyc +0 -0
  32. package/src/extraction/__pycache__/parser.cpython-313.pyc +0 -0
  33. package/src/extraction/__pycache__/pdf_processor.cpython-313.pyc +0 -0
  34. package/src/extraction/__pycache__/prompts.cpython-313.pyc +0 -0
  35. package/src/extraction/__pycache__/review.cpython-313.pyc +0 -0
  36. package/src/extraction/__pycache__/schema.cpython-313.pyc +0 -0
  37. package/src/extraction/__pycache__/validation.cpython-313.pyc +0 -0
  38. package/src/extraction/boundaries.py +281 -0
  39. package/src/extraction/errors.py +156 -0
  40. package/src/extraction/llm_extraction.py +225 -0
  41. package/src/extraction/notation.py +240 -0
  42. package/src/extraction/parser.py +402 -0
  43. package/src/extraction/pdf_processor.py +281 -0
  44. package/src/extraction/prompts.py +90 -0
  45. package/src/extraction/review.py +298 -0
  46. package/src/extraction/schema.py +173 -0
  47. package/src/extraction/validation.py +202 -0
  48. package/src/generation/__init__.py +79 -0
  49. package/src/generation/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/src/generation/__pycache__/code_generator.cpython-313.pyc +0 -0
  51. package/src/generation/__pycache__/errors.cpython-313.pyc +0 -0
  52. package/src/generation/__pycache__/hybrid.cpython-313.pyc +0 -0
  53. package/src/generation/__pycache__/llm_generator.cpython-313.pyc +0 -0
  54. package/src/generation/__pycache__/persistence.cpython-313.pyc +0 -0
  55. package/src/generation/__pycache__/prompts.cpython-313.pyc +0 -0
  56. package/src/generation/__pycache__/review.cpython-313.pyc +0 -0
  57. package/src/generation/__pycache__/templates.cpython-313.pyc +0 -0
  58. package/src/generation/__pycache__/types.cpython-313.pyc +0 -0
  59. package/src/generation/__pycache__/validation.cpython-313.pyc +0 -0
  60. package/src/generation/code_generator.py +375 -0
  61. package/src/generation/errors.py +84 -0
  62. package/src/generation/hybrid.py +210 -0
  63. package/src/generation/llm_generator.py +223 -0
  64. package/src/generation/persistence.py +221 -0
  65. package/src/generation/prompts.py +202 -0
  66. package/src/generation/review.py +254 -0
  67. package/src/generation/templates.py +208 -0
  68. package/src/generation/types.py +196 -0
  69. package/src/generation/validation.py +278 -0
  70. package/src/intent.py +323 -0
  71. package/src/verification/__init__.py +63 -0
  72. package/src/verification/__pycache__/__init__.cpython-313.pyc +0 -0
  73. package/src/verification/__pycache__/checker.cpython-313.pyc +0 -0
  74. package/src/verification/__pycache__/comparison.cpython-313.pyc +0 -0
  75. package/src/verification/__pycache__/explainer.cpython-313.pyc +0 -0
  76. package/src/verification/__pycache__/static_analysis.cpython-313.pyc +0 -0
  77. package/src/verification/checker.py +220 -0
  78. package/src/verification/comparison.py +492 -0
  79. package/src/verification/explainer.py +414 -0
  80. package/src/verification/static_analysis.py +540 -0
  81. package/src/workflows/__init__.py +21 -0
  82. package/src/workflows/__pycache__/__init__.cpython-313.pyc +0 -0
  83. package/src/workflows/__pycache__/extract.cpython-313.pyc +0 -0
  84. package/src/workflows/__pycache__/generate.cpython-313.pyc +0 -0
  85. package/src/workflows/__pycache__/run.cpython-313.pyc +0 -0
  86. package/src/workflows/__pycache__/verify.cpython-313.pyc +0 -0
  87. package/src/workflows/extract.py +181 -0
  88. package/src/workflows/generate.py +155 -0
  89. package/src/workflows/run.py +187 -0
  90. package/src/workflows/verify.py +334 -0
@@ -0,0 +1,333 @@
1
+ """Sandboxed subprocess execution module for AlgoMath.
2
+
3
+ Per D-01 through D-06: Provides safe, isolated code execution with resource
4
+ limits, timeout protection, output capture, and file system restrictions.
5
+
6
+ This module implements a subprocess-based sandbox that:
7
+ - Runs code in a separate Python process for isolation
8
+ - Applies CPU and memory limits to prevent resource abuse
9
+ - Captures stdout/stderr and returns execution results
10
+ - Restricts dangerous imports (os, sys, subprocess, etc.)
11
+ - Uses temporary directories that auto-clean after execution
12
+ """
13
+
14
+ import subprocess
15
+ import tempfile
16
+ import os
17
+ import sys
18
+ import signal
19
+ import json
20
+ import time
21
+ from pathlib import Path
22
+ from dataclasses import dataclass, field
23
+ from enum import Enum
24
+ from typing import Optional, Any, Dict, List
25
+
26
+
27
+ class ExecutionStatus(Enum):
28
+ """Enumeration of possible execution statuses."""
29
+ SUCCESS = "success"
30
+ TIMEOUT = "timeout"
31
+ MEMORY_ERROR = "memory_error"
32
+ RUNTIME_ERROR = "runtime_error"
33
+ SYNTAX_ERROR = "syntax_error"
34
+ IMPORT_ERROR = "import_error"
35
+
36
+
37
+ @dataclass
38
+ class ExecutionResult:
39
+ """Result of code execution.
40
+
41
+ Attributes:
42
+ status: Execution status (success, timeout, error, etc.)
43
+ stdout: Captured standard output
44
+ stderr: Captured standard error
45
+ runtime_seconds: Execution time in seconds
46
+ return_value: Serialized return value if code defines main()
47
+ error_type: Type of error if execution failed
48
+ error_message: Human-readable error message
49
+ """
50
+ status: ExecutionStatus
51
+ stdout: str = ""
52
+ stderr: str = ""
53
+ runtime_seconds: float = 0.0
54
+ return_value: Any = None
55
+ error_type: Optional[str] = None
56
+ error_message: Optional[str] = None
57
+
58
+
59
+ # Modules that are blocked for security
60
+ # Note: 'sys' is removed from blocklist since wrapper needs it,
61
+ # but dangerous sys functions like sys.exit are blocked separately
62
+ BLOCKED_MODULES = [
63
+ 'os', 'subprocess', 'socket', 'urllib', 'http', 'ftplib',
64
+ 'smtplib', 'ssl', 'ctypes', 'mmap', 'resource', 'pty', 'tty',
65
+ 'multiprocessing', 'concurrent.futures.process'
66
+ ]
67
+
68
+ # Functions/methods that are dangerous even within allowed modules
69
+ BLOCKED_CALLS = [
70
+ ('sys', 'exit'), # sys.exit() would terminate process
71
+ ]
72
+
73
+
74
+ def _create_import_restrictor_code() -> str:
75
+ """Create Python code that restricts dangerous imports.
76
+
77
+ Per D-28: Import restrictions block os, sys, subprocess, network modules.
78
+ This code overrides __import__ to check against a blocklist.
79
+ """
80
+ blocked = json.dumps(BLOCKED_MODULES)
81
+ return f'''
82
+ import builtins
83
+ import json
84
+
85
+ __BLOCKED_MODULES = set(json.loads({repr(blocked)}))
86
+ _original_import = builtins.__import__
87
+
88
+ def _restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
89
+ """Import hook that blocks dangerous modules."""
90
+ # Check if the module or any of its parents is blocked
91
+ module_parts = name.split('.')
92
+ for i in range(len(module_parts)):
93
+ partial_name = '.'.join(module_parts[:i+1])
94
+ if partial_name in __BLOCKED_MODULES:
95
+ raise ImportError(
96
+ f"Import of '{{name}}' is not allowed. "
97
+ f"Module '{{partial_name}}' is restricted for security."
98
+ )
99
+ return _original_import(name, globals, locals, fromlist, level)
100
+
101
+ builtins.__import__ = _restricted_import
102
+ '''
103
+
104
+
105
+ def _create_return_capture_wrapper(code: str) -> str:
106
+ """Wrap code to capture return values from main() function.
107
+
108
+ Per D-30: Capture function return values for algorithms that return data.
109
+ """
110
+ return f'''
111
+ import json
112
+
113
+ __algo_return_value = None
114
+
115
+ def _capture_main_result():
116
+ """Check if main() was defined and capture its return value."""
117
+ global __algo_return_value
118
+ if 'main' in globals() and callable(globals()['main']):
119
+ __algo_return_value = main()
120
+
121
+ # User code starts here
122
+ {code}
123
+
124
+ # After code runs, try to capture main() result
125
+ _capture_main_result()
126
+
127
+ # Print return value for parent process to capture
128
+ if __algo_return_value is not None:
129
+ try:
130
+ # Try to serialize; if not JSON serializable, use repr
131
+ json.dumps(__algo_return_value)
132
+ print("__ALGO_RETURN__" + json.dumps(__algo_return_value))
133
+ except (TypeError, ValueError):
134
+ print("__ALGO_RETURN__" + repr(__algo_return_value))
135
+ '''
136
+
137
+
138
+ class SandboxExecutor:
139
+ """Executor that runs Python code in a sandboxed subprocess.
140
+
141
+ Per D-01, D-02: Uses subprocess-based isolation with resource limits.
142
+ Per D-05: Default 30-second timeout.
143
+ Per D-09, D-10, D-11: Uses tempfile.TemporaryDirectory for isolation and cleanup.
144
+
145
+ Attributes:
146
+ timeout: Maximum execution time in seconds (default: 30)
147
+ max_memory_mb: Maximum memory in MB (default: 512)
148
+ allowed_imports: Optional list of allowed imports (None = use blocklist)
149
+ """
150
+
151
+ def __init__(
152
+ self,
153
+ timeout: int = 30,
154
+ max_memory_mb: int = 512,
155
+ allowed_imports: Optional[List[str]] = None
156
+ ):
157
+ """Initialize sandbox executor.
158
+
159
+ Args:
160
+ timeout: Maximum execution time in seconds
161
+ max_memory_mb: Maximum memory allowed in megabytes
162
+ allowed_imports: Optional whitelist of allowed imports
163
+ """
164
+ self.timeout = timeout
165
+ self.max_memory_mb = max_memory_mb
166
+ self.allowed_imports = allowed_imports
167
+
168
+ def _set_resource_limits(self):
169
+ """Set resource limits for the child process.
170
+
171
+ Per D-02: Apply CPU and memory limits to subprocess.
172
+ Note: This only works on Unix-like systems.
173
+ """
174
+ try:
175
+ import resource as res
176
+
177
+ # Set memory limit (soft limit, hard limit = unlimited)
178
+ max_bytes = self.max_memory_mb * 1024 * 1024
179
+ res.setrlimit(res.RLIMIT_AS, (max_bytes, res.RLIM_INFINITY))
180
+
181
+ # Set CPU time limit
182
+ res.setrlimit(res.RLIMIT_CPU, (self.timeout, self.timeout + 5))
183
+ except (ImportError, ValueError, OSError):
184
+ # resource module not available on Windows or failed
185
+ pass
186
+
187
+ def execute(
188
+ self,
189
+ code: str,
190
+ working_dir: Optional[Path] = None
191
+ ) -> ExecutionResult:
192
+ """Execute Python code in a sandboxed subprocess.
193
+
194
+ Args:
195
+ code: Python code to execute
196
+ working_dir: Optional working directory (uses temp dir if None)
197
+
198
+ Returns:
199
+ ExecutionResult with status, output, and metadata
200
+ """
201
+ start_time = time.time()
202
+
203
+ # Create temporary directory for execution
204
+ # Per D-09, D-10: Auto-cleanup via TemporaryDirectory
205
+ with tempfile.TemporaryDirectory() as tmpdir:
206
+ code_file = Path(tmpdir) / "script.py"
207
+
208
+ # Prepare code with import restrictions and return value capture
209
+ full_code = _create_import_restrictor_code() + "\n"
210
+ full_code += _create_return_capture_wrapper(code)
211
+
212
+ # Write code to file
213
+ code_file.write_text(full_code, encoding='utf-8')
214
+
215
+ # Per D-11: Working directory isolation via cwd
216
+ cwd = working_dir if working_dir else Path(tmpdir)
217
+
218
+ try:
219
+ # Per D-01: Run in subprocess for isolation
220
+ # Per D-05, D-06: Apply timeout for hard termination
221
+ result = subprocess.run(
222
+ [sys.executable, str(code_file)],
223
+ capture_output=True,
224
+ text=True,
225
+ timeout=self.timeout,
226
+ cwd=str(cwd),
227
+ preexec_fn=self._set_resource_limits if os.name != 'nt' else None,
228
+ # Kill process group on timeout for complete cleanup
229
+ start_new_session=True
230
+ )
231
+
232
+ runtime = time.time() - start_time
233
+
234
+ # Parse return value from output
235
+ return_value = None
236
+ stdout_lines = result.stdout.split('\n')
237
+ filtered_stdout = []
238
+ for line in stdout_lines:
239
+ if line.startswith('__ALGO_RETURN__'):
240
+ json_str = line[len('__ALGO_RETURN__'):]
241
+ try:
242
+ return_value = json.loads(json_str)
243
+ except json.JSONDecodeError:
244
+ # Fall back to repr
245
+ return_value = json_str
246
+ else:
247
+ filtered_stdout.append(line)
248
+
249
+ stdout = '\n'.join(filtered_stdout).rstrip()
250
+
251
+ # Determine status based on return code
252
+ if result.returncode == 0:
253
+ status = ExecutionStatus.SUCCESS
254
+ else:
255
+ status = ExecutionStatus.RUNTIME_ERROR
256
+
257
+ return ExecutionResult(
258
+ status=status,
259
+ stdout=stdout,
260
+ stderr=result.stderr.rstrip(),
261
+ runtime_seconds=runtime,
262
+ return_value=return_value
263
+ )
264
+
265
+ except subprocess.TimeoutExpired as e:
266
+ # Per D-05, D-06, D-07: Timeout handling with clear status
267
+ runtime = time.time() - start_time
268
+ return ExecutionResult(
269
+ status=ExecutionStatus.TIMEOUT,
270
+ stdout=e.stdout.decode('utf-8', errors='replace') if e.stdout else "",
271
+ stderr=e.stderr.decode('utf-8', errors='replace') if e.stderr else "",
272
+ runtime_seconds=runtime,
273
+ error_type="TimeoutExpired",
274
+ error_message=f"Execution timed out after {self.timeout} seconds. Check for infinite loops."
275
+ )
276
+
277
+ except MemoryError:
278
+ # Per D-17, D-18: Memory error handling
279
+ runtime = time.time() - start_time
280
+ return ExecutionResult(
281
+ status=ExecutionStatus.MEMORY_ERROR,
282
+ runtime_seconds=runtime,
283
+ error_type="MemoryError",
284
+ error_message=f"Algorithm used too much memory (limit: {self.max_memory_mb}MB)"
285
+ )
286
+
287
+ except Exception as e:
288
+ # Per D-17: Error categorization
289
+ runtime = time.time() - start_time
290
+ error_type = type(e).__name__
291
+
292
+ # Check for syntax error indicators
293
+ if "SyntaxError" in str(e) or "syntax error" in str(e).lower():
294
+ status = ExecutionStatus.SYNTAX_ERROR
295
+ error_message = f"Generated code has a syntax issue: {e}"
296
+ elif "ImportError" in error_type or "ModuleNotFound" in error_type:
297
+ status = ExecutionStatus.IMPORT_ERROR
298
+ error_message = str(e)
299
+ else:
300
+ status = ExecutionStatus.RUNTIME_ERROR
301
+ error_message = f"Runtime error: {e}"
302
+
303
+ return ExecutionResult(
304
+ status=status,
305
+ runtime_seconds=runtime,
306
+ error_type=error_type,
307
+ error_message=error_message
308
+ )
309
+
310
+
311
+ # Convenience function for simple use cases
312
+ def execute_sandboxed(
313
+ code: str,
314
+ timeout: int = 30,
315
+ max_memory_mb: int = 512,
316
+ working_dir: Optional[Path] = None
317
+ ) -> ExecutionResult:
318
+ """Execute code with default sandbox settings.
319
+
320
+ Args:
321
+ code: Python code to execute
322
+ timeout: Maximum execution time in seconds
323
+ max_memory_mb: Maximum memory in megabytes
324
+ working_dir: Optional working directory
325
+
326
+ Returns:
327
+ ExecutionResult with status and output
328
+ """
329
+ executor = SandboxExecutor(
330
+ timeout=timeout,
331
+ max_memory_mb=max_memory_mb
332
+ )
333
+ return executor.execute(code, working_dir=working_dir)
@@ -0,0 +1,102 @@
1
+ """AlgoMath Extraction Module.
2
+
3
+ Provides functionality for extracting structured algorithms from
4
+ mathematical text descriptions.
5
+
6
+ Usage:
7
+ from src.extraction import HybridExtractor, Algorithm
8
+
9
+ extractor = HybridExtractor()
10
+ result = extractor.extract(text)
11
+
12
+ if result.success:
13
+ print(f"Extracted: {result.algorithm.name}")
14
+ for step in result.algorithm.steps:
15
+ print(f" {step.id}. {step.description}")
16
+ """
17
+
18
+ # Core types
19
+ from .schema import (
20
+ Algorithm,
21
+ Step,
22
+ StepType,
23
+ algorithm_to_json,
24
+ algorithm_from_json,
25
+ )
26
+
27
+ # Parsing
28
+ from .parser import (
29
+ RuleBasedParser,
30
+ parse_algorithm,
31
+ )
32
+
33
+ # LLM Extraction
34
+ from .llm_extraction import (
35
+ HybridExtractor,
36
+ extract_algorithm_llm,
37
+ ExtractionResult,
38
+ )
39
+
40
+ # Review
41
+ from .review import (
42
+ ReviewInterface,
43
+ validate_step_edit,
44
+ apply_edits,
45
+ )
46
+
47
+ # Errors
48
+ from .errors import (
49
+ ExtractionError,
50
+ ParseError,
51
+ AmbiguityError,
52
+ IncompleteError,
53
+ categorize_error,
54
+ format_errors_for_user,
55
+ )
56
+
57
+ # Validation
58
+ from .validation import (
59
+ validate_algorithm,
60
+ check_step_connectivity,
61
+ check_variable_flow,
62
+ ValidationResult,
63
+ )
64
+
65
+ __version__ = "0.1.0"
66
+
67
+ __all__ = [
68
+ # Core types
69
+ "Algorithm",
70
+ "Step",
71
+ "StepType",
72
+ "algorithm_to_json",
73
+ "algorithm_from_json",
74
+
75
+ # Parsing
76
+ "RuleBasedParser",
77
+ "parse_algorithm",
78
+
79
+ # LLM Extraction
80
+ "HybridExtractor",
81
+ "extract_algorithm_llm",
82
+ "ExtractionResult",
83
+
84
+ # Review
85
+ "ReviewInterface",
86
+ "validate_step_edit",
87
+ "apply_edits",
88
+
89
+ # Errors
90
+ "ExtractionError",
91
+ "ParseError",
92
+ "AmbiguityError",
93
+ "IncompleteError",
94
+ "categorize_error",
95
+ "format_errors_for_user",
96
+
97
+ # Validation
98
+ "validate_algorithm",
99
+ "check_step_connectivity",
100
+ "check_variable_flow",
101
+ "ValidationResult",
102
+ ]