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,540 @@
1
+ """Static analysis and edge case detection module for AlgoMath.
2
+
3
+ Provides detection of potential edge cases through static code analysis
4
+ and execution-based testing.
5
+
6
+ Per D-13 through D-16: Pattern detection, execution testing, severity levels,
7
+ and comprehensive edge case coverage.
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Optional, Set
13
+ import re
14
+ import sys
15
+
16
+ from src.extraction.schema import Algorithm, Step, StepType
17
+
18
+
19
+ class EdgeCaseSeverity(Enum):
20
+ """Severity levels for edge cases."""
21
+ INFO = "info"
22
+ WARNING = "warning"
23
+ CRITICAL = "critical"
24
+
25
+
26
+ @dataclass
27
+ class EdgeCase:
28
+ """Represents a detected edge case.
29
+
30
+ Per D-15: Includes severity, description, location, suggestion.
31
+ Per D-16: Covers empty, single, max, negative, zero values.
32
+ Per D-141: Suggests specific test values.
33
+ """
34
+ type: str
35
+ severity: EdgeCaseSeverity
36
+ description: str
37
+ location: Optional[str] = None
38
+ suggestion: str = ""
39
+ test_input: Optional[Dict[str, Any]] = None
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Convert to dictionary representation."""
43
+ return {
44
+ "type": self.type,
45
+ "severity": self.severity.value,
46
+ "description": self.description,
47
+ "location": self.location,
48
+ "suggestion": self.suggestion,
49
+ "test_input": self.test_input,
50
+ }
51
+
52
+
53
+ class EdgeCaseDetector:
54
+ """Detects edge cases through static and execution analysis.
55
+
56
+ Per D-13: Static analysis for patterns.
57
+ Per D-14: Execution-based testing.
58
+ Per D-15: Report detected and potential edge cases.
59
+ Per D-16: Test empty, single, max, negative, zero.
60
+ """
61
+
62
+ def __init__(self, code: str, algorithm: Optional[Algorithm] = None):
63
+ """Initialize detector with code and optional algorithm.
64
+
65
+ Args:
66
+ code: Python code to analyze
67
+ algorithm: Optional algorithm structure for context
68
+ """
69
+ self.code = code
70
+ self.algorithm = algorithm
71
+ self._detected: List[EdgeCase] = []
72
+
73
+ def analyze_static(self) -> List[EdgeCase]:
74
+ """Perform static analysis to detect edge cases.
75
+
76
+ Returns:
77
+ List of detected edge cases
78
+ """
79
+ edge_cases = []
80
+
81
+ # Run all static analysis checks
82
+ edge_cases.extend(self._check_empty_loops())
83
+ edge_cases.extend(self._check_division_by_zero())
84
+ edge_cases.extend(self._check_recursion_depth())
85
+ edge_cases.extend(self._check_uninitialized_variables())
86
+ edge_cases.extend(self._check_off_by_one())
87
+ edge_cases.extend(self._check_infinite_loops())
88
+ edge_cases.extend(self._check_empty_collections())
89
+
90
+ self._detected.extend(edge_cases)
91
+ return edge_cases
92
+
93
+ def analyze_execution(self, sandbox) -> List[EdgeCase]:
94
+ """Perform execution-based edge case detection.
95
+
96
+ Args:
97
+ sandbox: SandboxExecutor instance for running code
98
+
99
+ Returns:
100
+ List of detected edge cases from execution
101
+ """
102
+ edge_cases = []
103
+
104
+ # Run execution-based tests
105
+ edge_cases.extend(self._test_empty_input(sandbox))
106
+ edge_cases.extend(self._test_single_element(sandbox))
107
+ edge_cases.extend(self._test_boundary_values(sandbox))
108
+ edge_cases.extend(self._test_negative_values(sandbox))
109
+ edge_cases.extend(self._test_zero_values(sandbox))
110
+
111
+ self._detected.extend(edge_cases)
112
+ return edge_cases
113
+
114
+ def detect_edge_cases(self) -> List[EdgeCase]:
115
+ """Run all detection methods and return combined results.
116
+
117
+ Returns:
118
+ List of all detected edge cases
119
+ """
120
+ # Run static analysis
121
+ static_cases = self.analyze_static()
122
+
123
+ # Note: Execution analysis requires a sandbox instance
124
+ # Return static cases for now
125
+ return static_cases
126
+
127
+ def _check_empty_loops(self) -> List[EdgeCase]:
128
+ """Detect loops with no body or trivial body."""
129
+ edge_cases = []
130
+
131
+ # Pattern: for/while loop with only pass/continue/break
132
+ patterns = [
133
+ (r'for\s+\w+\s+in\s+[^:]+:\s*\n\s*(?:pass|continue|break|\.\.\.)\s*\n',
134
+ "Loop with empty body"),
135
+ (r'while\s+[^:]+:\s*\n\s*(?:pass|continue|break|\.\.\.)\s*\n',
136
+ "While loop with empty body"),
137
+ ]
138
+
139
+ for pattern, desc in patterns:
140
+ matches = list(re.finditer(pattern, self.code, re.MULTILINE))
141
+ for i, match in enumerate(matches):
142
+ line_num = self._get_line_number(match.start())
143
+ edge_cases.append(EdgeCase(
144
+ type="empty_loop",
145
+ severity=EdgeCaseSeverity.WARNING,
146
+ description=desc,
147
+ location=f"line {line_num}",
148
+ suggestion="Ensure loop has meaningful work or remove it"
149
+ ))
150
+
151
+ return edge_cases
152
+
153
+ def _check_division_by_zero(self) -> List[EdgeCase]:
154
+ """Detect potential division by zero."""
155
+ edge_cases = []
156
+
157
+ # Pattern: division without zero check
158
+ div_pattern = r'(?:/|//|%|\bdiv\b)\s*\(?\s*\w+'
159
+ matches = list(re.finditer(div_pattern, self.code))
160
+
161
+ for match in matches:
162
+ line_num = self._get_line_number(match.start())
163
+ context = self._get_line_context(line_num)
164
+
165
+ # Check if there's a zero guard
166
+ if not self._has_zero_guard(context):
167
+ edge_cases.append(EdgeCase(
168
+ type="division_by_zero",
169
+ severity=EdgeCaseSeverity.CRITICAL,
170
+ description="Potential division by zero",
171
+ location=f"line {line_num}",
172
+ suggestion="Add check: if divisor != 0: before dividing",
173
+ test_input={"divisor": 0}
174
+ ))
175
+
176
+ # Check for modulo operations
177
+ mod_pattern = r'\w+\s*%\s*\w+'
178
+ mod_matches = list(re.finditer(mod_pattern, self.code))
179
+
180
+ for match in mod_matches:
181
+ line_num = self._get_line_number(match.start())
182
+ context = self._get_line_context(line_num)
183
+
184
+ if not self._has_zero_guard(context):
185
+ edge_cases.append(EdgeCase(
186
+ type="modulo_by_zero",
187
+ severity=EdgeCaseSeverity.CRITICAL,
188
+ description="Potential modulo by zero",
189
+ location=f"line {line_num}",
190
+ suggestion="Add check: if divisor != 0: before modulo",
191
+ test_input={"divisor": 0}
192
+ ))
193
+
194
+ return edge_cases
195
+
196
+ def _check_recursion_depth(self) -> List[EdgeCase]:
197
+ """Detect recursive functions and depth concerns."""
198
+ edge_cases = []
199
+
200
+ # Pattern: function that calls itself
201
+ func_pattern = r'def\s+(\w+)\s*\([^)]*\):'
202
+ func_matches = list(re.finditer(func_pattern, self.code))
203
+
204
+ for match in func_matches:
205
+ func_name = match.group(1)
206
+ func_start = match.end()
207
+
208
+ # Look for recursive call within function scope
209
+ func_body = self._get_function_body(func_start)
210
+ if func_name in func_body:
211
+ line_num = self._get_line_number(match.start())
212
+ edge_cases.append(EdgeCase(
213
+ type="recursion_depth",
214
+ severity=EdgeCaseSeverity.WARNING,
215
+ description=f"Recursive function '{func_name}' may hit recursion limit",
216
+ location=f"line {line_num}",
217
+ suggestion="Consider iterative version or add recursion depth limit"
218
+ ))
219
+
220
+ return edge_cases
221
+
222
+ def _check_uninitialized_variables(self) -> List[EdgeCase]:
223
+ """Detect use of potentially uninitialized variables."""
224
+ edge_cases = []
225
+
226
+ # Simple check: variable used before assignment in a branch
227
+ # This is a heuristic - full analysis requires AST
228
+ var_pattern = r'(?:return|if|while|for)\s+\w+'
229
+ matches = list(re.finditer(var_pattern, self.code))
230
+
231
+ # Note: Full uninitialized variable detection requires AST parsing
232
+ # This is a simplified version
233
+
234
+ return edge_cases
235
+
236
+ def _check_off_by_one(self) -> List[EdgeCase]:
237
+ """Detect potential off-by-one errors."""
238
+ edge_cases = []
239
+
240
+ # Pattern: indexing with len() without -1
241
+ patterns = [
242
+ (r'\[\s*len\s*\(\s*\w+\s*\)\s*\]', "Array index at len() may be out of bounds"),
243
+ (r'range\s*\(\s*len\s*\(\s*\w+\s*\)\s*\+\s*1\s*\)', "Extra iteration in range"),
244
+ ]
245
+
246
+ for pattern, desc in patterns:
247
+ matches = list(re.finditer(pattern, self.code))
248
+ for match in matches:
249
+ line_num = self._get_line_number(match.start())
250
+ edge_cases.append(EdgeCase(
251
+ type="off_by_one",
252
+ severity=EdgeCaseSeverity.WARNING,
253
+ description=desc,
254
+ location=f"line {line_num}",
255
+ suggestion="Verify indices and ranges are correct"
256
+ ))
257
+
258
+ return edge_cases
259
+
260
+ def _check_infinite_loops(self) -> List[EdgeCase]:
261
+ """Detect potential infinite loops."""
262
+ edge_cases = []
263
+
264
+ # Pattern: while True without break
265
+ loop_pattern = r'while\s+True\s*:'
266
+ matches = list(re.finditer(loop_pattern, self.code))
267
+
268
+ for match in matches:
269
+ # Check if there's a break in the loop body
270
+ body = self._get_loop_body(match.end())
271
+ if 'break' not in body:
272
+ line_num = self._get_line_number(match.start())
273
+ edge_cases.append(EdgeCase(
274
+ type="infinite_loop",
275
+ severity=EdgeCaseSeverity.CRITICAL,
276
+ description="Potential infinite loop (while True without break)",
277
+ location=f"line {line_num}",
278
+ suggestion="Add a break condition or use a for loop"
279
+ ))
280
+
281
+ return edge_cases
282
+
283
+ def _check_empty_collections(self) -> List[EdgeCase]:
284
+ """Detect operations on empty collections."""
285
+ edge_cases = []
286
+
287
+ # Pattern: direct access to list elements without check
288
+ patterns = [
289
+ (r'\[\s*0\s*\]', "First element access without empty check"),
290
+ (r'\.pop\s*\(\s*\)', "Pop operation without empty check"),
291
+ ]
292
+
293
+ for pattern, desc in patterns:
294
+ if re.search(pattern, self.code):
295
+ edge_cases.append(EdgeCase(
296
+ type="empty_collection",
297
+ severity=EdgeCaseSeverity.WARNING,
298
+ description=desc,
299
+ suggestion="Add check: if len(collection) > 0: before accessing"
300
+ ))
301
+
302
+ return edge_cases
303
+
304
+ def _test_empty_input(self, sandbox) -> List[EdgeCase]:
305
+ """Test with empty inputs per D-16."""
306
+ edge_cases = []
307
+
308
+ if self.algorithm:
309
+ test_inputs = self._generate_test_inputs("empty_input", self.algorithm.inputs)
310
+ # Execution would happen here with sandbox
311
+ # For now, add potential edge case
312
+ if test_inputs:
313
+ edge_cases.append(EdgeCase(
314
+ type="empty_input",
315
+ severity=EdgeCaseSeverity.INFO,
316
+ description="Algorithm behavior with empty input",
317
+ suggestion="Try n=0, empty list, or empty string"
318
+ ))
319
+
320
+ return edge_cases
321
+
322
+ def _test_single_element(self, sandbox) -> List[EdgeCase]:
323
+ """Test with single element inputs per D-16."""
324
+ edge_cases = []
325
+
326
+ if self.algorithm:
327
+ test_inputs = self._generate_test_inputs("single_element", self.algorithm.inputs)
328
+ if test_inputs:
329
+ edge_cases.append(EdgeCase(
330
+ type="single_element",
331
+ severity=EdgeCaseSeverity.INFO,
332
+ description="Algorithm behavior with single element",
333
+ suggestion="Try n=1, single item list"
334
+ ))
335
+
336
+ return edge_cases
337
+
338
+ def _test_boundary_values(self, sandbox) -> List[EdgeCase]:
339
+ """Test with boundary values per D-16."""
340
+ edge_cases = []
341
+
342
+ edge_cases.append(EdgeCase(
343
+ type="max_value",
344
+ severity=EdgeCaseSeverity.WARNING,
345
+ description="Algorithm behavior with maximum values",
346
+ suggestion="Try sys.maxsize, float('inf'), very large numbers"
347
+ ))
348
+
349
+ edge_cases.append(EdgeCase(
350
+ type="min_value",
351
+ severity=EdgeCaseSeverity.WARNING,
352
+ description="Algorithm behavior with minimum values",
353
+ suggestion="Try -sys.maxsize, float('-inf'), very small numbers"
354
+ ))
355
+
356
+ return edge_cases
357
+
358
+ def _test_negative_values(self, sandbox) -> List[EdgeCase]:
359
+ """Test with negative values per D-16."""
360
+ edge_cases = []
361
+
362
+ edge_cases.append(EdgeCase(
363
+ type="negative",
364
+ severity=EdgeCaseSeverity.INFO,
365
+ description="Algorithm behavior with negative inputs",
366
+ suggestion="Try -1, negative integers, negative floats"
367
+ ))
368
+
369
+ return edge_cases
370
+
371
+ def _test_zero_values(self, sandbox) -> List[EdgeCase]:
372
+ """Test with zero values per D-16."""
373
+ edge_cases = []
374
+
375
+ edge_cases.append(EdgeCase(
376
+ type="zero",
377
+ severity=EdgeCaseSeverity.INFO,
378
+ description="Algorithm behavior with zero input",
379
+ suggestion="Try n=0, empty values, zero floats"
380
+ ))
381
+
382
+ return edge_cases
383
+
384
+ def _generate_test_inputs(
385
+ self,
386
+ edge_case_type: str,
387
+ algorithm_inputs: List[Dict]
388
+ ) -> List[Dict]:
389
+ """Generate test inputs for edge case type.
390
+
391
+ Per D-14, D-16: Generate varied test inputs.
392
+
393
+ Args:
394
+ edge_case_type: Type of edge case
395
+ algorithm_inputs: Algorithm input definitions
396
+
397
+ Returns:
398
+ List of test input dictionaries
399
+ """
400
+ test_inputs = []
401
+
402
+ if edge_case_type == "empty_input":
403
+ for input_def in algorithm_inputs:
404
+ name = input_def.get("name", "x")
405
+ input_type = input_def.get("type", "any")
406
+
407
+ if input_type == "list" or input_type == "array":
408
+ test_inputs.append({name: []})
409
+ elif input_type == "str":
410
+ test_inputs.append({name: ""})
411
+ elif input_type == "dict":
412
+ test_inputs.append({name: {}})
413
+ else:
414
+ test_inputs.append({name: 0})
415
+
416
+ elif edge_case_type == "single_element":
417
+ for input_def in algorithm_inputs:
418
+ name = input_def.get("name", "x")
419
+ input_type = input_def.get("type", "any")
420
+
421
+ if input_type == "list" or input_type == "array":
422
+ test_inputs.append({name: [1]})
423
+ elif input_type == "str":
424
+ test_inputs.append({name: "a"})
425
+ else:
426
+ test_inputs.append({name: 1})
427
+
428
+ elif edge_case_type == "max_value":
429
+ for input_def in algorithm_inputs:
430
+ name = input_def.get("name", "x")
431
+ input_type = input_def.get("type", "any")
432
+
433
+ if input_type == "int":
434
+ test_inputs.append({name: sys.maxsize})
435
+ elif input_type == "float":
436
+ test_inputs.append({name: float('inf')})
437
+ else:
438
+ test_inputs.append({name: 10**9})
439
+
440
+ elif edge_case_type == "negative":
441
+ for input_def in algorithm_inputs:
442
+ name = input_def.get("name", "x")
443
+ input_type = input_def.get("type", "any")
444
+
445
+ if input_type in ("int", "float"):
446
+ test_inputs.append({name: -1})
447
+ test_inputs.append({name: -0.001})
448
+
449
+ elif edge_case_type == "zero":
450
+ for input_def in algorithm_inputs:
451
+ name = input_def.get("name", "x")
452
+ input_type = input_def.get("type", "any")
453
+
454
+ if input_type in ("int", "float"):
455
+ test_inputs.append({name: 0})
456
+ test_inputs.append({name: 0.0})
457
+
458
+ return test_inputs
459
+
460
+ def _get_line_number(self, pos: int) -> int:
461
+ """Get line number for a position in code."""
462
+ return self.code[:pos].count('\n') + 1
463
+
464
+ def _get_line_context(self, line_num: int) -> str:
465
+ """Get context around a line number."""
466
+ lines = self.code.split('\n')
467
+ start = max(0, line_num - 3)
468
+ end = min(len(lines), line_num + 2)
469
+ return '\n'.join(lines[start:end])
470
+
471
+ def _has_zero_guard(self, context: str) -> bool:
472
+ """Check if context has a zero guard."""
473
+ guard_patterns = [
474
+ r'if\s+\w+\s*!=\s*0',
475
+ r'if\s+\w+\s*>\s*0',
476
+ r'if\s+\w+',
477
+ r'try\s*:',
478
+ r'except\s+ZeroDivisionError',
479
+ ]
480
+ return any(re.search(p, context) for p in guard_patterns)
481
+
482
+ def _get_function_body(self, start_pos: int) -> str:
483
+ """Extract function body starting from position."""
484
+ # Simple extraction - find next def at same indentation or EOF
485
+ remaining = self.code[start_pos:]
486
+ lines = remaining.split('\n')
487
+ body_lines = []
488
+
489
+ for line in lines[1:]: # Skip first line (def line)
490
+ if line.strip() and not line.startswith(' ') and not line.startswith('\t'):
491
+ break
492
+ body_lines.append(line)
493
+
494
+ return '\n'.join(body_lines)
495
+
496
+ def _get_loop_body(self, start_pos: int) -> str:
497
+ """Extract loop body starting from position."""
498
+ remaining = self.code[start_pos:]
499
+ lines = remaining.split('\n')
500
+ body_lines = []
501
+ indent = None
502
+
503
+ for line in lines:
504
+ if not line.strip():
505
+ continue
506
+ stripped = line.lstrip()
507
+ current_indent = len(line) - len(stripped)
508
+
509
+ if indent is None and stripped:
510
+ indent = current_indent
511
+ body_lines.append(line)
512
+ elif stripped and current_indent > indent:
513
+ body_lines.append(line)
514
+ elif stripped and current_indent <= indent:
515
+ break
516
+
517
+ return '\n'.join(body_lines)
518
+
519
+
520
+ def detect_edge_cases(
521
+ code: str,
522
+ algorithm: Optional[Algorithm] = None,
523
+ sandbox=None
524
+ ) -> List[EdgeCase]:
525
+ """Convenience function to detect edge cases.
526
+
527
+ Args:
528
+ code: Python code to analyze
529
+ algorithm: Optional algorithm structure
530
+ sandbox: Optional SandboxExecutor for execution testing
531
+
532
+ Returns:
533
+ List of detected edge cases
534
+ """
535
+ detector = EdgeCaseDetector(code, algorithm)
536
+
537
+ if sandbox:
538
+ return detector.analyze_execution(sandbox)
539
+ else:
540
+ return detector.analyze_static()
@@ -0,0 +1,21 @@
1
+ """
2
+ Workflow modules for AlgoMath.
3
+
4
+ This package contains workflow implementations for the four core phases:
5
+ - extract: Extract algorithm steps from text
6
+ - generate: Generate executable code from steps
7
+ - run: Execute generated code
8
+ - verify: Verify execution results
9
+ """
10
+
11
+ from .extract import run_extraction
12
+ from .generate import run_generation
13
+ from .run import run_execution
14
+ from .verify import run_verification
15
+
16
+ __all__ = [
17
+ 'run_extraction',
18
+ 'run_generation',
19
+ 'run_execution',
20
+ 'run_verification',
21
+ ]