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,339 @@
1
+ """
2
+ CLI Commands for AlgoMath - User-facing command implementations.
3
+
4
+ This module provides command functions that can be called directly
5
+ or through the intent routing system. Commands follow a consistent
6
+ return format with status, progress, message, and next_steps.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ # Add project root to Python path
16
+ project_root = Path(__file__).parent.parent.parent
17
+ if str(project_root) not in sys.path:
18
+ sys.path.insert(0, str(project_root))
19
+
20
+ from algomath.context import ContextManager
21
+ from algomath.state import WorkflowState
22
+ from src.workflows.run import run_execution
23
+
24
+
25
+ def extract_command(text: str, name: Optional[str] = None) -> Dict[str, Any]:
26
+ """
27
+ Extract algorithm from mathematical text.
28
+
29
+ Args:
30
+ text: Mathematical text describing an algorithm
31
+ name: Optional name for the algorithm
32
+
33
+ Returns:
34
+ Dict with extraction status and results
35
+ """
36
+ from src.workflows.extract import extract_algorithm
37
+
38
+ ctx = ContextManager()
39
+ ctx.start_session()
40
+
41
+ if name:
42
+ ctx.create_algorithm(name)
43
+
44
+ result = extract_algorithm(ctx, text)
45
+ return result
46
+
47
+
48
+ def generate_command() -> Dict[str, Any]:
49
+ """
50
+ Generate code from extracted algorithm steps.
51
+
52
+ Returns:
53
+ Dict with generation status and results
54
+ """
55
+ from src.workflows.generate import generate_code
56
+
57
+ ctx = ContextManager()
58
+ ctx.start_session()
59
+
60
+ result = generate_code(ctx)
61
+ return result
62
+
63
+
64
+ def run_command(
65
+ skip: bool = False,
66
+ inputs: Optional[Dict[str, Any]] = None
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Execute generated code with /algo-run.
70
+
71
+ Per D-21: Auto-triggered after code approval
72
+ Per D-25: Can skip with skip=True to proceed directly to verification
73
+
74
+ Args:
75
+ skip: If True, skip execution and proceed to verification
76
+ inputs: Optional input data for the algorithm
77
+
78
+ Returns:
79
+ Dict with execution status and results
80
+ """
81
+ ctx = ContextManager()
82
+ ctx.start_session()
83
+
84
+ current = ctx.get_current()
85
+
86
+ # Check current state
87
+ if current.current_state == WorkflowState.EXECUTION_COMPLETE:
88
+ # Already executed - show results
89
+ results = current.data.get('results', {})
90
+ return {
91
+ 'status': 'already_executed',
92
+ 'message': 'Algorithm already executed. Run again to re-execute.',
93
+ 'results': results,
94
+ 'next_steps': [
95
+ 'Verify results with /algo-verify',
96
+ 'Re-run with /algo-run',
97
+ 'Regenerate with /algo-generate'
98
+ ]
99
+ }
100
+
101
+ if current.current_state != WorkflowState.CODE_GENERATED:
102
+ return {
103
+ 'status': 'invalid_state',
104
+ 'message': f"Cannot execute: current state is {current.current_state.value}",
105
+ 'required_state': 'code_generated',
106
+ 'next_steps': [
107
+ 'Generate code first with /algo-generate',
108
+ 'Check status with /algo-status'
109
+ ]
110
+ }
111
+
112
+ # Check if skip requested per D-25
113
+ if skip:
114
+ # Skip execution, proceed to verification
115
+ ctx.save_results({
116
+ 'status': 'skipped',
117
+ 'message': 'Execution skipped by user',
118
+ 'timestamp': datetime.now().isoformat()
119
+ })
120
+
121
+ return {
122
+ 'status': 'skipped',
123
+ 'message': 'Execution skipped. Proceeding to verification.',
124
+ 'next_steps': [
125
+ 'Verify with /algo-verify',
126
+ 'Run later with /algo-run'
127
+ ]
128
+ }
129
+
130
+ # Execute
131
+ print(f"Executing algorithm: {current.current_algorithm or '(unnamed)'}...")
132
+ result = run_execution(ctx, inputs=inputs)
133
+
134
+ return result
135
+
136
+
137
+ def verify_command(
138
+ expected: Optional[Any] = None,
139
+ step: Optional[int] = None,
140
+ detailed: bool = False,
141
+ diagnostic: bool = False
142
+ ) -> Dict[str, Any]:
143
+ """
144
+ Verify execution results with /algo-verify.
145
+
146
+ Per D-01: Quick inline summary shown automatically
147
+ Per D-02: Full verification via this command
148
+ Per D-03: Diagnostic mode for failed executions
149
+ Per D-06: Detailed explanation with --detailed
150
+ Per D-09: Interactive expected results prompt
151
+ Per D-22: Diagnostic mode with --diagnostic
152
+
153
+ Args:
154
+ expected: Optional expected output for comparison
155
+ step: Optional step ID for detailed explanation (VER-05)
156
+ detailed: If True, generate detailed step-by-step explanation
157
+ diagnostic: If True, run diagnostic mode for failed executions
158
+
159
+ Returns:
160
+ Dict with verification status and results
161
+ """
162
+ from src.workflows.verify import run_verification, verify_step
163
+
164
+ ctx = ContextManager()
165
+ ctx.start_session()
166
+
167
+ current = ctx.get_current()
168
+
169
+ # Check current state per D-02, D-25
170
+ if current.current_state == WorkflowState.VERIFIED:
171
+ # Already verified - show cached report
172
+ algorithm_data = ctx.store.load_session()
173
+ return {
174
+ 'status': 'already_verified',
175
+ 'message': 'Algorithm already verified. Run again for fresh verification.',
176
+ 'last_verification': algorithm_data.get('verification', {}),
177
+ 'next_steps': [
178
+ 'Re-verify with /algo-verify',
179
+ 'Extract new with /algo-extract',
180
+ 'Check status with /algo-status'
181
+ ]
182
+ }
183
+
184
+ if current.current_state not in [WorkflowState.EXECUTION_COMPLETE, WorkflowState.CODE_GENERATED]:
185
+ return {
186
+ 'status': 'not_ready',
187
+ 'message': f"Cannot verify: current state is {current.current_state.value}",
188
+ 'required_state': 'execution_complete',
189
+ 'next_steps': [
190
+ 'Run code first with /algo-run',
191
+ 'Check status with /algo-status'
192
+ ]
193
+ }
194
+
195
+ # Handle step-specific explanation per VER-05
196
+ if step is not None:
197
+ result = verify_step(ctx, step)
198
+ return result
199
+
200
+ # Interactive prompt for expected results per D-09
201
+ if expected is None and not diagnostic:
202
+ # Note: In actual CLI, this would prompt user
203
+ # For now, proceed without comparison
204
+ pass
205
+
206
+ # Run full verification per D-02
207
+ print(f"Verifying algorithm: {current.current_algorithm or '(unnamed)'}...")
208
+ result = run_verification(
209
+ ctx,
210
+ expected=expected,
211
+ detailed=detailed,
212
+ diagnostic=diagnostic
213
+ )
214
+
215
+ return result
216
+
217
+
218
+ def status_command() -> Dict[str, Any]:
219
+ """
220
+ Show current algorithm status and progress.
221
+
222
+ Returns:
223
+ Dict with current state information
224
+ """
225
+ ctx = ContextManager()
226
+ ctx.start_session()
227
+
228
+ current = ctx.get_current()
229
+ progress = ctx.get_progress()
230
+ progress_bar = ctx.get_progress_bar()
231
+
232
+ return {
233
+ 'status': 'success',
234
+ 'algorithm': progress['algorithm_name'],
235
+ 'state': current.current_state.value,
236
+ 'progress_bar': progress_bar,
237
+ 'steps_completed': progress['steps_completed'],
238
+ 'steps_total': progress['steps_total'],
239
+ 'data_status': progress['data_status'],
240
+ 'has_text': 'text' in current.data,
241
+ 'has_steps': 'steps' in current.data,
242
+ 'has_code': 'code' in current.data,
243
+ 'has_results': 'results' in current.data,
244
+ 'next_steps': [
245
+ f"Current: {current.current_state.value}",
246
+ "Continue with workflow commands",
247
+ "Use /algo-help for command list"
248
+ ]
249
+ }
250
+
251
+
252
+ def list_command() -> Dict[str, Any]:
253
+ """
254
+ List all saved algorithms.
255
+
256
+ Returns:
257
+ Dict with list of algorithms
258
+ """
259
+ ctx = ContextManager()
260
+ algorithms = ctx.list_algorithms()
261
+
262
+ return {
263
+ 'status': 'success',
264
+ 'count': len(algorithms),
265
+ 'algorithms': [
266
+ {'name': name, 'updated': updated}
267
+ for name, updated in algorithms
268
+ ],
269
+ 'next_steps': [
270
+ 'Load algorithm with /algo-extract [name]',
271
+ 'Start new with /algo-extract',
272
+ 'Check status with /algo-status'
273
+ ]
274
+ }
275
+
276
+
277
+ def help_command() -> Dict[str, Any]:
278
+ """
279
+ Show help and available commands.
280
+
281
+ Returns:
282
+ Dict with command reference
283
+ """
284
+ commands = [
285
+ ('/algo-extract "text" [name]', 'Extract algorithm from text', 'Initial'),
286
+ ('/algo-generate', 'Generate code from steps', 'STEPS_STRUCTURED'),
287
+ ('/algo-run', 'Execute generated code', 'CODE_GENERATED'),
288
+ ('/algo-run --skip', 'Skip execution (proceed to verify)', 'CODE_GENERATED'),
289
+ ('/algo-verify', 'Verify execution results', 'EXECUTION_COMPLETE'),
290
+ ('/algo-verify --step N', 'Explain step N in detail', 'EXECUTION_COMPLETE'),
291
+ ('/algo-verify --detailed', 'Show detailed explanation', 'EXECUTION_COMPLETE'),
292
+ ('/algo-verify --diagnostic', 'Diagnose failed execution', 'EXECUTION_COMPLETE'),
293
+ ('/algo-status', 'Show current state and progress', 'Any'),
294
+ ('/algo-list', 'List saved algorithms', 'Any'),
295
+ ('/algo-help', 'Show this help', 'Any'),
296
+ ]
297
+
298
+ return {
299
+ 'status': 'success',
300
+ 'commands': [
301
+ {
302
+ 'command': cmd,
303
+ 'description': desc,
304
+ 'requires': req
305
+ }
306
+ for cmd, desc, req in commands
307
+ ],
308
+ 'tip': 'Commands can be used naturally, e.g., "extract algorithm from this text"',
309
+ 'next_steps': [
310
+ 'Try /algo-extract to start',
311
+ 'Check /algo-status any time',
312
+ 'Ask for /algo-help when needed'
313
+ ]
314
+ }
315
+
316
+
317
+ # Command map for routing
318
+ COMMAND_MAP = {
319
+ 'extract': extract_command,
320
+ 'generate': generate_command,
321
+ 'run': run_command,
322
+ 'verify': verify_command,
323
+ 'status': status_command,
324
+ 'list': list_command,
325
+ 'help': help_command,
326
+ }
327
+
328
+
329
+ # Exports
330
+ __all__ = [
331
+ 'COMMAND_MAP',
332
+ 'extract_command',
333
+ 'generate_command',
334
+ 'run_command',
335
+ 'verify_command',
336
+ 'status_command',
337
+ 'list_command',
338
+ 'help_command',
339
+ ]
@@ -0,0 +1,74 @@
1
+ """Execution module for AlgoMath.
2
+
3
+ Provides safe, sandboxed code execution with resource limits,
4
+ timeout protection, and comprehensive output capture.
5
+
6
+ Per Phase 4 decisions D-01 through D-30:
7
+ - D-01: Subprocess-based isolation
8
+ - D-02: Resource limits (CPU, memory)
9
+ - D-05: 30-second default timeout
10
+ - D-09, D-10: Temp directory sandbox with auto-cleanup
11
+ - D-16: Execution metadata capture
12
+ - D-17: Error categorization
13
+ - D-28: Import restrictions
14
+ - D-29: Input handling
15
+ - D-30: Return value capture
16
+
17
+ Example:
18
+ >>> from src.execution import execute_code, ExecutionConfig
19
+ >>> config = ExecutionConfig(timeout=60, max_memory_mb=1024)
20
+ >>> result = execute_code('print("Hello")', config=config)
21
+ >>> print(result.stdout)
22
+ "Hello"
23
+ >>> print(result.status)
24
+ "success"
25
+ """
26
+
27
+ # Core sandbox and executor components
28
+ from .sandbox import SandboxExecutor, ExecutionResult, ExecutionStatus, execute_sandboxed
29
+ from .executor import execute_code, ExecutionConfig, format_results_for_context, build_execution_response
30
+
31
+ # Error handling and display (from existing modules)
32
+ from .errors import (
33
+ ExecutionError,
34
+ ErrorTranslator,
35
+ ErrorDetails,
36
+ categorize_error,
37
+ extract_line_number,
38
+ )
39
+ from .display import (
40
+ ExecutionFormatter,
41
+ FormattedResult,
42
+ truncate_output,
43
+ show_progress,
44
+ show_execution_summary,
45
+ format_execution_log,
46
+ )
47
+
48
+ __version__ = "0.1.0"
49
+
50
+ __all__ = [
51
+ # Sandbox components
52
+ 'SandboxExecutor',
53
+ 'ExecutionResult',
54
+ 'ExecutionStatus',
55
+ 'execute_sandboxed',
56
+ # High-level interface
57
+ 'execute_code',
58
+ 'ExecutionConfig',
59
+ 'format_results_for_context',
60
+ 'build_execution_response',
61
+ # Error handling
62
+ 'ExecutionError',
63
+ 'ErrorTranslator',
64
+ 'ErrorDetails',
65
+ 'categorize_error',
66
+ 'extract_line_number',
67
+ # Display formatting
68
+ 'ExecutionFormatter',
69
+ 'FormattedResult',
70
+ 'truncate_output',
71
+ 'show_progress',
72
+ 'show_execution_summary',
73
+ 'format_execution_log',
74
+ ]
@@ -0,0 +1,261 @@
1
+ """Output formatting and display for execution results.
2
+
3
+ Covers EXE-05 (status reporting) and output formatting per D-13 through D-16.
4
+ Reuses progress indicator pattern from Phase 1 (D-09).
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Optional, Any
10
+
11
+ from .errors import ErrorDetails, ExecutionError
12
+
13
+
14
+ # Constants per D-15
15
+ MAX_INLINE_LINES = 50
16
+ TRUNCATION_MESSAGE = "\n... ({} more lines - see full log for details)\n"
17
+
18
+
19
+ @dataclass
20
+ class FormattedResult:
21
+ """Formatted execution result per D-14, D-16.
22
+
23
+ Attributes:
24
+ display_text: Formatted text for display
25
+ status_emoji: Visual status indicator
26
+ status_text: Text status (Success/Failed/Timed Out)
27
+ execution_time: Human-readable execution time
28
+ output_truncated: Whether output was truncated
29
+ full_log_path: Path to full log file (if exists)
30
+ """
31
+ display_text: str
32
+ status_emoji: str
33
+ status_text: str
34
+ execution_time: str
35
+ output_truncated: bool
36
+ full_log_path: Optional[str]
37
+
38
+
39
+ class ExecutionFormatter:
40
+ """Format execution results for display per D-14, D-15, D-16.
41
+
42
+ Takes raw execution results and formats them for user presentation
43
+ with status indicators, truncated output, and log references.
44
+ """
45
+
46
+ def __init__(self, algorithm_name: Optional[str] = None):
47
+ """Initialize formatter with optional algorithm name.
48
+
49
+ Args:
50
+ algorithm_name: Name of the algorithm (for log path)
51
+ """
52
+ self.algorithm_name = algorithm_name
53
+ self.log_dir = Path(".algomath/algorithms") / algorithm_name if algorithm_name else None
54
+
55
+ def format_results(self, result: Any) -> FormattedResult:
56
+ """Format execution result for display per D-14, D-15, D-16.
57
+
58
+ Args:
59
+ result: Execution result object with status, stdout, stderr, runtime_seconds
60
+
61
+ Returns:
62
+ FormattedResult with display-ready text
63
+ """
64
+ # Status indicators per EXE-05
65
+ if result.status == 'success':
66
+ emoji = '✓'
67
+ status = 'Success'
68
+ elif result.status == 'timeout':
69
+ emoji = '⏱'
70
+ status = 'Timed Out'
71
+ else:
72
+ emoji = '✗'
73
+ status = 'Failed'
74
+
75
+ # Build display text
76
+ lines = []
77
+ lines.append(f"{emoji} Execution: {status}")
78
+
79
+ # Execution time per D-16
80
+ runtime = getattr(result, 'runtime_seconds', 0)
81
+ lines.append(f"Time: {runtime:.3f}s")
82
+
83
+ # Output per D-13 (dual capture) and D-15 (truncation)
84
+ stdout = getattr(result, 'stdout', '') or ''
85
+ stderr = getattr(result, 'stderr', '') or ''
86
+
87
+ if stdout:
88
+ lines.append("\n--- Output ---")
89
+ output = truncate_output(stdout, MAX_INLINE_LINES)
90
+ lines.append(output)
91
+
92
+ # Errors (fewer lines shown)
93
+ if stderr:
94
+ lines.append("\n--- Errors ---")
95
+ errors = truncate_output(stderr, 20) # Fewer error lines
96
+ lines.append(errors)
97
+
98
+ # Full log path per D-14
99
+ log_path = str(self.log_dir / "execution.log") if self.log_dir else None
100
+ output_truncated = len(stdout.split('\n')) > MAX_INLINE_LINES if stdout else False
101
+
102
+ if log_path and output_truncated:
103
+ lines.append(f"\nFull log: {log_path}")
104
+
105
+ return FormattedResult(
106
+ display_text='\n'.join(lines),
107
+ status_emoji=emoji,
108
+ status_text=status,
109
+ execution_time=f"{runtime:.3f}s",
110
+ output_truncated=output_truncated,
111
+ full_log_path=log_path
112
+ )
113
+
114
+
115
+ def truncate_output(text: str, max_lines: int = MAX_INLINE_LINES) -> str:
116
+ """Truncate output to max_lines with summary per D-15.
117
+
118
+ Args:
119
+ text: Output text to truncate
120
+ max_lines: Maximum number of lines to show
121
+
122
+ Returns:
123
+ Truncated text with summary message if needed
124
+ """
125
+ if not text:
126
+ return text
127
+
128
+ lines = text.split('\n')
129
+ if len(lines) <= max_lines:
130
+ return text
131
+
132
+ truncated = '\n'.join(lines[:max_lines])
133
+ remaining = len(lines) - max_lines
134
+ return truncated + TRUNCATION_MESSAGE.format(remaining)
135
+
136
+
137
+ def show_progress(phase: str, current: int, total: int) -> str:
138
+ """Generate progress bar string per Phase 1 D-09.
139
+
140
+ Args:
141
+ phase: Name of the current phase
142
+ current: Current step number
143
+ total: Total number of steps
144
+
145
+ Returns:
146
+ Formatted progress bar string like "Execute: █████░░░░░ 50%"
147
+ """
148
+ if total <= 0:
149
+ return f"{phase}: ░░░░░░░░░░ 0%"
150
+
151
+ filled = int(10 * current / total)
152
+ filled = max(0, min(filled, 10)) # Clamp to 0-10 range
153
+ bar = '█' * filled + '░' * (10 - filled)
154
+ pct = int(100 * current / total)
155
+ return f"{phase}: {bar} {pct}%"
156
+
157
+
158
+ def show_execution_summary(
159
+ result: Any,
160
+ error_details: Optional[ErrorDetails] = None
161
+ ) -> str:
162
+ """Show execution summary with error translation if needed per EXE-05, EXE-06.
163
+
164
+ Args:
165
+ result: Execution result object
166
+ error_details: Optional error translation details
167
+
168
+ Returns:
169
+ Formatted summary string
170
+ """
171
+ lines = []
172
+
173
+ # Status line per EXE-05
174
+ runtime = getattr(result, 'runtime_seconds', 0)
175
+ if result.status == 'success':
176
+ lines.append(f"✓ Algorithm executed successfully in {runtime:.3f}s")
177
+ else:
178
+ lines.append(f"✗ Execution {result.status}")
179
+
180
+ # Error translation per EXE-06
181
+ if error_details:
182
+ lines.append(f"\n{error_details.user_message}")
183
+ lines.append(f"\n💡 {error_details.hint}")
184
+
185
+ # Technical details in collapsed section per D-19
186
+ if error_details.technical_details:
187
+ lines.append(f"\n<details>")
188
+ lines.append(f"<summary>Technical Details (for debugging)</summary>")
189
+ lines.append(f"\n```\n{error_details.technical_details}\n```")
190
+ lines.append(f"</details>")
191
+
192
+ return '\n'.join(lines)
193
+
194
+
195
+ def format_execution_log(
196
+ algorithm_name: str,
197
+ status: str,
198
+ runtime_seconds: float,
199
+ stdout: str,
200
+ stderr: str,
201
+ error_type: Optional[str] = None,
202
+ error_message: Optional[str] = None
203
+ ) -> str:
204
+ """Format complete execution log for file persistence per D-14.
205
+
206
+ Args:
207
+ algorithm_name: Name of the algorithm
208
+ status: Execution status
209
+ runtime_seconds: Execution time in seconds
210
+ stdout: Standard output
211
+ stderr: Standard error
212
+ error_type: Type of error if failed
213
+ error_message: Error message if failed
214
+
215
+ Returns:
216
+ Formatted log content for saving to file
217
+ """
218
+ from datetime import datetime
219
+
220
+ lines = [
221
+ f"Algorithm: {algorithm_name}",
222
+ f"Status: {status}",
223
+ f"Timestamp: {datetime.now().isoformat()}",
224
+ f"Runtime: {runtime_seconds:.3f}s",
225
+ "",
226
+ "=" * 50,
227
+ "OUTPUT",
228
+ "=" * 50,
229
+ stdout if stdout else "(no output)",
230
+ ]
231
+
232
+ if stderr:
233
+ lines.extend([
234
+ "",
235
+ "=" * 50,
236
+ "ERRORS",
237
+ "=" * 50,
238
+ stderr
239
+ ])
240
+
241
+ if error_type:
242
+ lines.extend([
243
+ "",
244
+ "=" * 50,
245
+ "ERROR DETAILS",
246
+ "=" * 50,
247
+ f"Type: {error_type}",
248
+ f"Message: {error_message or '(no message)'}"
249
+ ])
250
+
251
+ return '\n'.join(lines)
252
+
253
+
254
+ __all__ = [
255
+ 'ExecutionFormatter',
256
+ 'FormattedResult',
257
+ 'truncate_output',
258
+ 'show_progress',
259
+ 'show_execution_summary',
260
+ 'format_execution_log',
261
+ ]