alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__py3-none-any.whl

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 (278) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +156 -0
  6. alita_sdk/cli/agent_loader.py +245 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3113 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/testcases/__init__.py +94 -0
  23. alita_sdk/cli/testcases/data_generation.py +119 -0
  24. alita_sdk/cli/testcases/discovery.py +96 -0
  25. alita_sdk/cli/testcases/executor.py +84 -0
  26. alita_sdk/cli/testcases/logger.py +85 -0
  27. alita_sdk/cli/testcases/parser.py +172 -0
  28. alita_sdk/cli/testcases/prompts.py +91 -0
  29. alita_sdk/cli/testcases/reporting.py +125 -0
  30. alita_sdk/cli/testcases/setup.py +108 -0
  31. alita_sdk/cli/testcases/test_runner.py +282 -0
  32. alita_sdk/cli/testcases/utils.py +39 -0
  33. alita_sdk/cli/testcases/validation.py +90 -0
  34. alita_sdk/cli/testcases/workflow.py +196 -0
  35. alita_sdk/cli/toolkit.py +327 -0
  36. alita_sdk/cli/toolkit_loader.py +85 -0
  37. alita_sdk/cli/tools/__init__.py +43 -0
  38. alita_sdk/cli/tools/approval.py +224 -0
  39. alita_sdk/cli/tools/filesystem.py +1751 -0
  40. alita_sdk/cli/tools/planning.py +389 -0
  41. alita_sdk/cli/tools/terminal.py +414 -0
  42. alita_sdk/community/__init__.py +72 -12
  43. alita_sdk/community/inventory/__init__.py +236 -0
  44. alita_sdk/community/inventory/config.py +257 -0
  45. alita_sdk/community/inventory/enrichment.py +2137 -0
  46. alita_sdk/community/inventory/extractors.py +1469 -0
  47. alita_sdk/community/inventory/ingestion.py +3172 -0
  48. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  49. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  50. alita_sdk/community/inventory/parsers/base.py +295 -0
  51. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  52. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  53. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  54. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  55. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  56. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  57. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  58. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  59. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  60. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  61. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  62. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  63. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  64. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  65. alita_sdk/community/inventory/patterns/loader.py +348 -0
  66. alita_sdk/community/inventory/patterns/registry.py +198 -0
  67. alita_sdk/community/inventory/presets.py +535 -0
  68. alita_sdk/community/inventory/retrieval.py +1403 -0
  69. alita_sdk/community/inventory/toolkit.py +173 -0
  70. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  71. alita_sdk/community/inventory/visualize.py +1370 -0
  72. alita_sdk/configurations/__init__.py +1 -1
  73. alita_sdk/configurations/ado.py +141 -20
  74. alita_sdk/configurations/bitbucket.py +94 -2
  75. alita_sdk/configurations/confluence.py +130 -1
  76. alita_sdk/configurations/figma.py +76 -0
  77. alita_sdk/configurations/gitlab.py +91 -0
  78. alita_sdk/configurations/jira.py +103 -0
  79. alita_sdk/configurations/openapi.py +329 -0
  80. alita_sdk/configurations/qtest.py +72 -1
  81. alita_sdk/configurations/report_portal.py +96 -0
  82. alita_sdk/configurations/sharepoint.py +148 -0
  83. alita_sdk/configurations/testio.py +83 -0
  84. alita_sdk/configurations/testrail.py +88 -0
  85. alita_sdk/configurations/xray.py +93 -0
  86. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  87. alita_sdk/configurations/zephyr_essential.py +75 -0
  88. alita_sdk/runtime/clients/artifact.py +3 -3
  89. alita_sdk/runtime/clients/client.py +388 -46
  90. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  91. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  92. alita_sdk/runtime/clients/sandbox_client.py +8 -21
  93. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  94. alita_sdk/runtime/langchain/assistant.py +157 -39
  95. alita_sdk/runtime/langchain/constants.py +647 -1
  96. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  97. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
  100. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  102. alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
  103. alita_sdk/runtime/langchain/langraph_agent.py +405 -84
  104. alita_sdk/runtime/langchain/utils.py +106 -7
  105. alita_sdk/runtime/llms/preloaded.py +2 -6
  106. alita_sdk/runtime/models/mcp_models.py +61 -0
  107. alita_sdk/runtime/skills/__init__.py +91 -0
  108. alita_sdk/runtime/skills/callbacks.py +498 -0
  109. alita_sdk/runtime/skills/discovery.py +540 -0
  110. alita_sdk/runtime/skills/executor.py +610 -0
  111. alita_sdk/runtime/skills/input_builder.py +371 -0
  112. alita_sdk/runtime/skills/models.py +330 -0
  113. alita_sdk/runtime/skills/registry.py +355 -0
  114. alita_sdk/runtime/skills/skill_runner.py +330 -0
  115. alita_sdk/runtime/toolkits/__init__.py +31 -0
  116. alita_sdk/runtime/toolkits/application.py +29 -10
  117. alita_sdk/runtime/toolkits/artifact.py +20 -11
  118. alita_sdk/runtime/toolkits/datasource.py +13 -6
  119. alita_sdk/runtime/toolkits/mcp.py +783 -0
  120. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  121. alita_sdk/runtime/toolkits/planning.py +178 -0
  122. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  123. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  124. alita_sdk/runtime/toolkits/tools.py +356 -69
  125. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  126. alita_sdk/runtime/tools/__init__.py +10 -3
  127. alita_sdk/runtime/tools/application.py +27 -6
  128. alita_sdk/runtime/tools/artifact.py +511 -28
  129. alita_sdk/runtime/tools/data_analysis.py +183 -0
  130. alita_sdk/runtime/tools/function.py +67 -35
  131. alita_sdk/runtime/tools/graph.py +10 -4
  132. alita_sdk/runtime/tools/image_generation.py +148 -46
  133. alita_sdk/runtime/tools/llm.py +1003 -128
  134. alita_sdk/runtime/tools/loop.py +3 -1
  135. alita_sdk/runtime/tools/loop_output.py +3 -1
  136. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  137. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  138. alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
  139. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  140. alita_sdk/runtime/tools/planning/models.py +246 -0
  141. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  142. alita_sdk/runtime/tools/router.py +2 -4
  143. alita_sdk/runtime/tools/sandbox.py +65 -48
  144. alita_sdk/runtime/tools/skill_router.py +776 -0
  145. alita_sdk/runtime/tools/tool.py +3 -1
  146. alita_sdk/runtime/tools/vectorstore.py +9 -3
  147. alita_sdk/runtime/tools/vectorstore_base.py +70 -14
  148. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  149. alita_sdk/runtime/utils/constants.py +5 -1
  150. alita_sdk/runtime/utils/mcp_client.py +492 -0
  151. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  152. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  153. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  154. alita_sdk/runtime/utils/serialization.py +155 -0
  155. alita_sdk/runtime/utils/streamlit.py +40 -13
  156. alita_sdk/runtime/utils/toolkit_utils.py +30 -9
  157. alita_sdk/runtime/utils/utils.py +36 -0
  158. alita_sdk/tools/__init__.py +134 -35
  159. alita_sdk/tools/ado/repos/__init__.py +51 -32
  160. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  161. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  162. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  163. alita_sdk/tools/ado/utils.py +1 -18
  164. alita_sdk/tools/ado/wiki/__init__.py +25 -12
  165. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  166. alita_sdk/tools/ado/work_item/__init__.py +26 -13
  167. alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
  168. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  169. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  170. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  171. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  172. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  173. alita_sdk/tools/base/tool.py +5 -1
  174. alita_sdk/tools/base_indexer_toolkit.py +271 -84
  175. alita_sdk/tools/bitbucket/__init__.py +17 -11
  176. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  177. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  178. alita_sdk/tools/browser/__init__.py +5 -4
  179. alita_sdk/tools/carrier/__init__.py +5 -6
  180. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  181. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  182. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  183. alita_sdk/tools/chunkers/__init__.py +3 -1
  184. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  185. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  186. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  187. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  188. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  189. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  190. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  191. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  192. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  193. alita_sdk/tools/code/linter/__init__.py +10 -8
  194. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  195. alita_sdk/tools/code/sonar/__init__.py +11 -8
  196. alita_sdk/tools/code_indexer_toolkit.py +82 -22
  197. alita_sdk/tools/confluence/__init__.py +22 -16
  198. alita_sdk/tools/confluence/api_wrapper.py +107 -30
  199. alita_sdk/tools/confluence/loader.py +14 -2
  200. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  201. alita_sdk/tools/elastic/__init__.py +11 -8
  202. alita_sdk/tools/elitea_base.py +493 -30
  203. alita_sdk/tools/figma/__init__.py +58 -11
  204. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  205. alita_sdk/tools/figma/figma_client.py +73 -0
  206. alita_sdk/tools/figma/toon_tools.py +2748 -0
  207. alita_sdk/tools/github/__init__.py +14 -15
  208. alita_sdk/tools/github/github_client.py +224 -100
  209. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  210. alita_sdk/tools/github/schemas.py +14 -5
  211. alita_sdk/tools/github/tool.py +5 -1
  212. alita_sdk/tools/github/tool_prompts.py +9 -22
  213. alita_sdk/tools/gitlab/__init__.py +16 -11
  214. alita_sdk/tools/gitlab/api_wrapper.py +218 -48
  215. alita_sdk/tools/gitlab_org/__init__.py +10 -9
  216. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  217. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  218. alita_sdk/tools/google/bigquery/tool.py +5 -1
  219. alita_sdk/tools/google_places/__init__.py +11 -8
  220. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  221. alita_sdk/tools/jira/__init__.py +17 -10
  222. alita_sdk/tools/jira/api_wrapper.py +92 -41
  223. alita_sdk/tools/keycloak/__init__.py +11 -8
  224. alita_sdk/tools/localgit/__init__.py +9 -3
  225. alita_sdk/tools/localgit/local_git.py +62 -54
  226. alita_sdk/tools/localgit/tool.py +5 -1
  227. alita_sdk/tools/memory/__init__.py +12 -4
  228. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  229. alita_sdk/tools/ocr/__init__.py +11 -8
  230. alita_sdk/tools/openapi/__init__.py +491 -106
  231. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  232. alita_sdk/tools/openapi/tool.py +20 -0
  233. alita_sdk/tools/pandas/__init__.py +20 -12
  234. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  235. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  236. alita_sdk/tools/postman/__init__.py +10 -9
  237. alita_sdk/tools/pptx/__init__.py +11 -10
  238. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  239. alita_sdk/tools/qtest/__init__.py +31 -11
  240. alita_sdk/tools/qtest/api_wrapper.py +2135 -86
  241. alita_sdk/tools/rally/__init__.py +10 -9
  242. alita_sdk/tools/rally/api_wrapper.py +1 -1
  243. alita_sdk/tools/report_portal/__init__.py +12 -8
  244. alita_sdk/tools/salesforce/__init__.py +10 -8
  245. alita_sdk/tools/servicenow/__init__.py +17 -15
  246. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  247. alita_sdk/tools/sharepoint/__init__.py +10 -7
  248. alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
  249. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  250. alita_sdk/tools/sharepoint/utils.py +8 -2
  251. alita_sdk/tools/slack/__init__.py +10 -7
  252. alita_sdk/tools/slack/api_wrapper.py +2 -2
  253. alita_sdk/tools/sql/__init__.py +12 -9
  254. alita_sdk/tools/testio/__init__.py +10 -7
  255. alita_sdk/tools/testrail/__init__.py +11 -10
  256. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  257. alita_sdk/tools/utils/__init__.py +9 -4
  258. alita_sdk/tools/utils/content_parser.py +103 -18
  259. alita_sdk/tools/utils/text_operations.py +410 -0
  260. alita_sdk/tools/utils/tool_prompts.py +79 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
  262. alita_sdk/tools/xray/__init__.py +13 -9
  263. alita_sdk/tools/yagmail/__init__.py +9 -3
  264. alita_sdk/tools/zephyr/__init__.py +10 -7
  265. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
  266. alita_sdk/tools/zephyr_essential/__init__.py +10 -7
  267. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  268. alita_sdk/tools/zephyr_essential/client.py +2 -2
  269. alita_sdk/tools/zephyr_scale/__init__.py +11 -8
  270. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  271. alita_sdk/tools/zephyr_squad/__init__.py +10 -7
  272. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
  273. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  274. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  275. alita_sdk-0.3.379.dist-info/RECORD +0 -360
  276. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
1
+ """
2
+ Executor cache management for test execution.
3
+
4
+ Handles creating, caching, and cleaning up agent executors.
5
+ """
6
+
7
+ import logging
8
+ import sqlite3
9
+ from typing import Dict, Optional, Any, Tuple
10
+ from rich.console import Console
11
+
12
+ logger = logging.getLogger(__name__)
13
+ console = Console()
14
+
15
+
16
+ def cleanup_executor_cache(cache: Dict[str, tuple], cache_name: str = "executor") -> None:
17
+ """Clean up executor cache resources.
18
+
19
+ Args:
20
+ cache: Dictionary of cached executors
21
+ cache_name: Name of cache for logging
22
+ """
23
+ console.print(f"[dim]Cleaning up {cache_name} cache...[/dim]")
24
+ for cache_key, cached_items in cache.items():
25
+ try:
26
+ # Extract memory from tuple (second element)
27
+ memory = cached_items[1] if len(cached_items) > 1 else None
28
+
29
+ # Close SQLite memory connection
30
+ if memory and hasattr(memory, 'conn') and memory.conn:
31
+ memory.conn.close()
32
+ except Exception as e:
33
+ logger.debug(f"Error cleaning up {cache_name} cache for {cache_key}: {e}")
34
+
35
+
36
+ def create_executor_from_cache(
37
+ cache: Dict[str, tuple],
38
+ cache_key: str,
39
+ client,
40
+ agent_def: Dict[str, Any],
41
+ toolkit_config_path: Optional[str],
42
+ config,
43
+ model: Optional[str],
44
+ temperature: Optional[float],
45
+ max_tokens: Optional[int],
46
+ work_dir: Optional[str],
47
+ setup_executor_func
48
+ ) -> Tuple:
49
+ """Get or create executor from cache.
50
+
51
+ Args:
52
+ cache: Executor cache dictionary
53
+ cache_key: Key for caching
54
+ client: API client
55
+ agent_def: Agent definition
56
+ toolkit_config_path: Path to toolkit config
57
+ config: CLI configuration
58
+ model: Model override
59
+ temperature: Temperature override
60
+ max_tokens: Max tokens override
61
+ work_dir: Working directory
62
+ setup_executor_func: Function to setup local agent executor
63
+
64
+ Returns:
65
+ Tuple of (agent_executor, memory, mcp_session_manager)
66
+ """
67
+ if cache_key in cache:
68
+ return cache[cache_key]
69
+
70
+ # Create new executor
71
+ from langgraph.checkpoint.sqlite import SqliteSaver
72
+
73
+ memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
74
+ toolkit_config_tuple = (toolkit_config_path,) if toolkit_config_path else ()
75
+
76
+ agent_executor, mcp_session_manager, _, _, _, _, _ = setup_executor_func(
77
+ client, agent_def, toolkit_config_tuple, config, model, temperature,
78
+ max_tokens, memory, work_dir
79
+ )
80
+
81
+ # Cache the executor
82
+ cached_tuple = (agent_executor, memory, mcp_session_manager)
83
+ cache[cache_key] = cached_tuple
84
+ return cached_tuple
@@ -0,0 +1,85 @@
1
+ """
2
+ Test execution logging utilities.
3
+
4
+ Provides the TestLogCapture context manager for capturing console output to log files.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from datetime import datetime
9
+ from typing import Optional
10
+
11
+ from rich.console import Console
12
+
13
+
14
+ class TestLogCapture:
15
+ """
16
+ Context manager to capture console output to a log file.
17
+ Creates a single log file for the entire test execution session.
18
+ Strips Rich markup for plain text output with UTF-8 encoding.
19
+ """
20
+
21
+ def __init__(self, results_dir: Path, session_name: str, console: Optional[Console] = None):
22
+ """
23
+ Initialize log capture.
24
+
25
+ Args:
26
+ results_dir: Base results directory
27
+ session_name: Name for this test session
28
+ """
29
+ self.results_dir = results_dir
30
+ self.session_name = session_name
31
+ self.log_file_path = None
32
+ self.was_recording = False
33
+ # Use injected console if provided; otherwise create a dedicated console.
34
+ # Injecting allows other components (e.g., tool-call callbacks) to print to the
35
+ # same console and be captured in the session log.
36
+ self.console: Console = console or Console()
37
+
38
+ def __enter__(self):
39
+ """Start capturing console output."""
40
+ # Extract toolkit name from session name (e.g., "test-execution-confluence" -> "confluence")
41
+ toolkit_name = self.session_name.replace('test-execution-', '')
42
+
43
+ # Create toolkit-specific directory
44
+ toolkit_dir = self.results_dir / toolkit_name
45
+ toolkit_dir.mkdir(parents=True, exist_ok=True)
46
+
47
+ # Generate timestamped filename
48
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
49
+ log_filename = f"{timestamp}.{self.session_name}.txt"
50
+ self.log_file_path = toolkit_dir / log_filename
51
+
52
+ # Enable recording on this console
53
+ self.was_recording = self.console.record
54
+ self.console.record = True
55
+
56
+ return self
57
+
58
+ def print(self, *args, **kwargs):
59
+ """Print to console (both display and recording)."""
60
+ self.console.print(*args, **kwargs)
61
+
62
+ def status(self, message, **kwargs):
63
+ """Create status spinner and log the message."""
64
+ # Manually record the status message (spinners are transient)
65
+ self.console.print(message)
66
+ # Show spinner (this won't be recorded due to transient nature)
67
+ return self.console.status(message, **kwargs)
68
+
69
+ def __exit__(self, exc_type, exc_val, exc_tb):
70
+ """Stop capturing and save log file."""
71
+ if self.log_file_path:
72
+ # Export recorded text (strips Rich markup)
73
+ plain_text = self.console.export_text()
74
+
75
+ # Save to file with UTF-8 encoding
76
+ with open(self.log_file_path, 'w', encoding='utf-8') as f:
77
+ f.write(f"Test Execution Session: {self.session_name}\n")
78
+ f.write(f"Timestamp: {datetime.now().isoformat()}\n")
79
+ f.write("=" * 80 + "\n\n")
80
+ f.write(plain_text)
81
+
82
+ # Restore previous recording state
83
+ self.console.record = self.was_recording
84
+
85
+ return False
@@ -0,0 +1,172 @@
1
+ """
2
+ Test case parsing utilities.
3
+
4
+ Handles parsing of test case markdown files and resolving configuration paths.
5
+ """
6
+
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Optional, Dict, Any
10
+
11
+
12
+ def resolve_toolkit_config_path(config_path_str: str, test_file: Path, test_cases_dir: Path) -> Optional[str]:
13
+ """
14
+ Resolve toolkit configuration file path from test case.
15
+
16
+ Tries multiple locations in order:
17
+ 1. Absolute path
18
+ 2. Relative to test case file directory
19
+ 3. Relative to test cases directory
20
+ 4. Relative to workspace root
21
+
22
+ Args:
23
+ config_path_str: Config path from test case
24
+ test_file: Path to the test case file
25
+ test_cases_dir: Path to test cases directory
26
+
27
+ Returns:
28
+ Absolute path to config file if found, None otherwise
29
+ """
30
+ if not config_path_str:
31
+ return None
32
+
33
+ # Normalize path separators
34
+ config_path_str = config_path_str.replace('\\', '/')
35
+
36
+ # Try absolute path first
37
+ config_path = Path(config_path_str)
38
+ if config_path.is_absolute() and config_path.exists():
39
+ return str(config_path)
40
+
41
+ # Try relative to test case file directory
42
+ config_path = test_file.parent / config_path_str
43
+ if config_path.exists():
44
+ return str(config_path)
45
+
46
+ # Try relative to test_cases_dir
47
+ config_path = test_cases_dir / config_path_str
48
+ if config_path.exists():
49
+ return str(config_path)
50
+
51
+ # Try relative to workspace root
52
+ workspace_root = Path.cwd()
53
+ config_path = workspace_root / config_path_str
54
+ if config_path.exists():
55
+ return str(config_path)
56
+
57
+ return None
58
+
59
+
60
+ def parse_test_case(test_case_path: str) -> Dict[str, Any]:
61
+ """
62
+ Parse a test case markdown file to extract configuration, steps, and expectations.
63
+
64
+ Args:
65
+ test_case_path: Path to the test case markdown file
66
+
67
+ Returns:
68
+ Dictionary containing:
69
+ - name: Test case name
70
+ - objective: Test objective
71
+ - config_path: Path to toolkit config file
72
+ - generate_test_data: Boolean flag indicating if test data generation is needed (default: True)
73
+ - test_data_config: Dictionary of test data configuration from table
74
+ - prerequisites: Pre-requisites section text
75
+ - variables: List of variable placeholders found (e.g., {{TEST_PR_NUMBER}})
76
+ - steps: List of test steps with their descriptions
77
+ - expectations: List of expectations/assertions
78
+ """
79
+ path = Path(test_case_path)
80
+ if not path.exists():
81
+ raise FileNotFoundError(f"Test case not found: {test_case_path}")
82
+
83
+ content = path.read_text(encoding='utf-8')
84
+
85
+ # Extract test case name from the first heading
86
+ name_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
87
+ name = name_match.group(1) if name_match else path.stem
88
+
89
+ # Extract objective
90
+ objective_match = re.search(r'##\s+Objective\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
91
+ objective = objective_match.group(1).strip() if objective_match else ""
92
+
93
+ # Extract config path and generateTestData flag
94
+ config_section_match = re.search(r'##\s+Config\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
95
+ config_path = None
96
+ generate_test_data = True # Default to True if not specified
97
+
98
+ if config_section_match:
99
+ config_section = config_section_match.group(1)
100
+ # Extract path
101
+ path_match = re.search(r'path:\s*(.+?)(?=\n|$)', config_section, re.MULTILINE)
102
+ if path_match:
103
+ config_path = path_match.group(1).strip()
104
+
105
+ # Extract generateTestData flag
106
+ gen_data_match = re.search(r'generateTestData\s*:\s*(true|false)', config_section, re.IGNORECASE)
107
+ if gen_data_match:
108
+ generate_test_data = gen_data_match.group(1).lower() == 'true'
109
+
110
+ # Extract Test Data Configuration section as a raw fenced code block string
111
+ # NOTE: We intentionally store the entire section as a single string rather than parsing
112
+ # individual table rows. This preserves the original formatting for downstream tools
113
+ # which may prefer the raw markdown block.
114
+ test_data_config = None
115
+ config_section_match = re.search(r'##\s+Test Data Configuration\s*\n(.+?)(?=\n##|\Z)', content, re.DOTALL)
116
+ if config_section_match:
117
+ config_section = config_section_match.group(1).strip()
118
+ # Store as a fenced code block to make it clear this is a raw block of text
119
+ test_data_config = f"\n{config_section}\n"
120
+
121
+ # Extract Pre-requisites section
122
+ prerequisites = ""
123
+ prereq_match = re.search(r'##\s+Pre-requisites\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
124
+ if prereq_match:
125
+ prerequisites = prereq_match.group(1).strip()
126
+
127
+ # Find all variable placeholders ({{VARIABLE_NAME}})
128
+ variables = list(set(re.findall(r'\{\{([A-Z_]+)\}\}', content)))
129
+
130
+ # Extract test steps and expectations
131
+ steps = []
132
+ expectations = []
133
+
134
+ # Find all Step sections
135
+ step_pattern = r'###\s+Step\s+(\d+):\s+(.+?)\n\n(.+?)(?=\n\n###|\n\n##|\Z)'
136
+ for step_match in re.finditer(step_pattern, content, re.DOTALL):
137
+ step_num = step_match.group(1)
138
+ step_title = step_match.group(2).strip()
139
+ step_content = step_match.group(3).strip()
140
+
141
+ # Extract the actual instruction (first paragraph before "Expectation:")
142
+ instruction_match = re.search(r'(.+?)(?=\n\n\*\*Expectation:\*\*|\Z)', step_content, re.DOTALL)
143
+ instruction = instruction_match.group(1).strip() if instruction_match else step_content
144
+
145
+ # Extract expectation if present
146
+ expectation_match = re.search(r'\*\*Expectation:\*\*\s+(.+)', step_content, re.DOTALL)
147
+ expectation = expectation_match.group(1).strip() if expectation_match else None
148
+
149
+ steps.append({
150
+ 'number': int(step_num),
151
+ 'title': step_title,
152
+ 'instruction': instruction,
153
+ 'expectation': expectation
154
+ })
155
+
156
+ if expectation:
157
+ expectations.append({
158
+ 'step': int(step_num),
159
+ 'description': expectation
160
+ })
161
+
162
+ return {
163
+ 'name': name,
164
+ 'objective': objective,
165
+ 'config_path': config_path,
166
+ 'generate_test_data': generate_test_data,
167
+ 'test_data_config': test_data_config,
168
+ 'prerequisites': prerequisites,
169
+ 'variables': variables,
170
+ 'steps': steps,
171
+ 'expectations': expectations
172
+ }
@@ -0,0 +1,91 @@
1
+ """
2
+ Prompt building utilities for test execution.
3
+
4
+ Builds prompts for data generation, test execution, and validation.
5
+ """
6
+
7
+ from typing import Dict, Any
8
+
9
+
10
+ def build_bulk_data_gen_prompt(parsed_test_cases: list) -> str:
11
+ """Build consolidated requirements text for bulk test data generation."""
12
+ requirements = []
13
+ for idx, tc in enumerate(parsed_test_cases, 1):
14
+ test_case = tc['data']
15
+ test_file = tc['file']
16
+ # Build parts for this test case (do not include separator lines here;
17
+ # the entire block is wrapped with separators at the top-level)
18
+ parts = [f"Test Case #{idx}: {test_case['name']}", f"File: {test_file.name}", ""]
19
+
20
+ if test_case.get('test_data_config'):
21
+ parts.append("Test Data Configuration:")
22
+ td = test_case['test_data_config']
23
+ raw_lines = str(td).splitlines()
24
+ for line in raw_lines:
25
+ parts.append(f"{line}")
26
+
27
+ if test_case.get('prerequisites'):
28
+ parts.append(f"\nPre-requisites:\n{test_case['prerequisites']}")
29
+
30
+ requirements.append("\n".join(parts))
31
+
32
+ # If no requirements were collected, return an empty string to avoid
33
+ # producing a prompt with only separator lines.
34
+ if not requirements:
35
+ return ""
36
+
37
+ # Use a visible divider between test cases so each entry is clearly separated
38
+ divider = '-' * 40
39
+ body = f"\n\n{divider}\n\n".join(requirements)
40
+ return f"{('='*60)}\n\n{body}\n\n{('='*60)}"
41
+
42
+
43
+ def build_single_test_execution_prompt(test_case_info: Dict[str, Any], test_number: int) -> str:
44
+ """Build execution prompt for a single test case."""
45
+ test_case = test_case_info['data']
46
+ test_file = test_case_info['file']
47
+
48
+ parts = [
49
+ f"\n{'='*80}",
50
+ f"TEST CASE #{test_number}: {test_case['name']}",
51
+ f"File: {test_file.name}",
52
+ f"{'='*80}"
53
+ ]
54
+
55
+ if test_case['steps']:
56
+ for step in test_case['steps']:
57
+ parts.append(f"\nStep {step['number']}: {step['title']}")
58
+ parts.append(step['instruction'])
59
+ else:
60
+ parts.append("\n(No steps defined)")
61
+
62
+ return "\n".join(parts)
63
+
64
+
65
+ def build_single_test_validation_prompt(test_case_info: Dict[str, Any], test_number: int, execution_output: str) -> str:
66
+ """Build validation prompt for a single test case."""
67
+ test_case = test_case_info['data']
68
+
69
+ parts = [
70
+ f"\nTest Case #{test_number}: {test_case['name']}"
71
+ ]
72
+
73
+ if test_case['steps']:
74
+ for step in test_case['steps']:
75
+ parts.append(f" Step {step['number']}: {step['title']}")
76
+ if step['expectation']:
77
+ parts.append(f" Expected: {step['expectation']}")
78
+
79
+ parts.append(f"\n\nActual Execution Results:\n{execution_output}\n")
80
+
81
+ # Escape quotes in test name for valid JSON in prompt
82
+ escaped_test_name = test_case['name'].replace('"', '\\"')
83
+
84
+ parts.append(f"""\nBased on the execution results above, validate this test case.
85
+ {{
86
+ "test_number": {test_number},
87
+ "test_name": "{escaped_test_name}"
88
+ }}
89
+ """)
90
+
91
+ return "\n".join(parts)
@@ -0,0 +1,125 @@
1
+ """
2
+ Test result reporting and summary generation.
3
+
4
+ Handles generating test reports and displaying summaries.
5
+ """
6
+
7
+ import json
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import List, Dict, Any
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+ from rich import box
14
+
15
+ console = Console()
16
+
17
+
18
+ def generate_summary_report(test_results: List[Dict[str, Any]]) -> Table:
19
+ """Generate a summary table for test results.
20
+
21
+ Args:
22
+ test_results: List of test result dicts
23
+
24
+ Returns:
25
+ Rich Table with summary statistics
26
+ """
27
+ total_tests = len(test_results)
28
+ passed_tests = sum(1 for r in test_results if r['passed'])
29
+ failed_tests = total_tests - passed_tests
30
+ pass_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
31
+
32
+ summary_table = Table(box=box.ROUNDED, border_style="cyan")
33
+ summary_table.add_column("Metric", style="bold")
34
+ summary_table.add_column("Value", justify="right")
35
+
36
+ summary_table.add_row("Total Tests", str(total_tests))
37
+ summary_table.add_row("Passed", f"[green]{passed_tests}[/green]")
38
+ summary_table.add_row("Failed", f"[red]{failed_tests}[/red]")
39
+ summary_table.add_row("Pass Rate", f"{pass_rate:.1f}%")
40
+
41
+ return summary_table
42
+
43
+
44
+ def save_structured_report(
45
+ test_results: List[Dict[str, Any]],
46
+ results_dir: str,
47
+ log_file: Path = None
48
+ ) -> Path:
49
+ """Save structured JSON report of test results.
50
+
51
+ Args:
52
+ test_results: List of test result dicts
53
+ results_dir: Directory to save report
54
+ log_file: Optional path to log file
55
+
56
+ Returns:
57
+ Path to saved report file
58
+ """
59
+ results_path = Path(results_dir)
60
+ results_path.mkdir(parents=True, exist_ok=True)
61
+
62
+ total_tests = len(test_results)
63
+ passed_tests = sum(1 for r in test_results if r['passed'])
64
+ failed_tests = total_tests - passed_tests
65
+ pass_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
66
+ overall_result = "pass" if failed_tests == 0 else "fail"
67
+
68
+ structured_report = {
69
+ "test_cases": [
70
+ {
71
+ "title": r['title'],
72
+ "passed": r['passed'],
73
+ "steps": r.get('step_results', [])
74
+ }
75
+ for r in test_results
76
+ ],
77
+ "overall_result": overall_result,
78
+ "summary": {
79
+ "total_tests": total_tests,
80
+ "passed": passed_tests,
81
+ "failed": failed_tests,
82
+ "pass_rate": f"{pass_rate:.1f}%"
83
+ },
84
+ "timestamp": datetime.now().isoformat(),
85
+ "log_file": str(log_file) if log_file else None
86
+ }
87
+
88
+ summary_file = results_path / "test_execution_summary.json"
89
+
90
+ console.print(f"\n[bold yellow]💾 Saving test execution summary...[/bold yellow]")
91
+ with open(summary_file, 'w') as f:
92
+ json.dump(structured_report, f, indent=2)
93
+ console.print(f"[green]✓ Summary saved to {summary_file}[/green]\n")
94
+
95
+ return summary_file
96
+
97
+
98
+ def print_test_execution_summary(
99
+ test_results: List[Dict[str, Any]],
100
+ results_dir: str,
101
+ session_name: str
102
+ ) -> None:
103
+ """Print test execution summary to console.
104
+
105
+ Args:
106
+ test_results: List of test result dicts
107
+ results_dir: Directory where results are saved
108
+ session_name: Session name for finding log file
109
+ """
110
+ console.print(f"\n[bold]{'='*60}[/bold]")
111
+ console.print(f"[bold cyan]📊 Test Execution Summary[/bold cyan]")
112
+ console.print(f"[bold]{'='*60}[/bold]\n")
113
+
114
+ summary_table = generate_summary_report(test_results)
115
+ console.print(summary_table)
116
+
117
+ # Show log file location
118
+ results_path = Path(results_dir)
119
+ toolkit_name = session_name.replace('test-execution-', '')
120
+ toolkit_dir = results_path / toolkit_name
121
+ log_files = sorted(toolkit_dir.glob(f"*{session_name}.txt")) if toolkit_dir.exists() else []
122
+
123
+ console.print(f"\n[bold cyan]📁 Log File[/bold cyan]")
124
+ if log_files:
125
+ console.print(f" [dim]{log_files[0]}[/dim]")
@@ -0,0 +1,108 @@
1
+ """
2
+ Agent setup utilities for test execution.
3
+
4
+ Handles loading and validating test runner, data generator, and validator agents.
5
+ """
6
+
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import Optional, Dict, Any, Tuple
10
+ from rich.console import Console
11
+
12
+ logger = logging.getLogger(__name__)
13
+ console = Console()
14
+
15
+
16
+ def load_test_runner_agent(agent_source: str) -> Tuple[Dict[str, Any], str]:
17
+ """Load test runner agent definition.
18
+
19
+ Args:
20
+ agent_source: Path to agent definition file
21
+
22
+ Returns:
23
+ Tuple of (agent_def, agent_name)
24
+
25
+ Raises:
26
+ FileNotFoundError: If agent file doesn't exist
27
+ """
28
+ from ..agent_loader import load_agent_definition
29
+
30
+ agent_source_path = Path(agent_source)
31
+ if not agent_source_path.exists():
32
+ raise FileNotFoundError(
33
+ f"Agent definition not found: {agent_source}. "
34
+ f"Make sure you are running from the repository root, "
35
+ f"or pass --agent_source explicitly."
36
+ )
37
+
38
+ agent_def = load_agent_definition(agent_source)
39
+ agent_name = agent_def.get('name', agent_source_path.stem)
40
+
41
+ return agent_def, agent_name
42
+
43
+
44
+ def load_data_generator_agent(data_generator: str, skip_data_generation: bool) -> Optional[Dict[str, Any]]:
45
+ """Load data generator agent definition if needed.
46
+
47
+ Args:
48
+ data_generator: Path to data generator agent file
49
+ skip_data_generation: Whether data generation is skipped
50
+
51
+ Returns:
52
+ Agent definition dict or None if skipped/failed
53
+ """
54
+ from ..agent_loader import load_agent_definition
55
+
56
+ if skip_data_generation:
57
+ return None
58
+
59
+ if not data_generator:
60
+ return None
61
+
62
+ try:
63
+ data_gen_def = load_agent_definition(data_generator)
64
+ data_gen_name = data_gen_def.get('name', Path(data_generator).stem)
65
+ console.print(f"Data Generator Agent: [bold]{data_gen_name}[/bold]\n")
66
+ return data_gen_def
67
+ except Exception as e:
68
+ console.print(f"[yellow]⚠ Warning: Failed to setup data generator: {e}[/yellow]")
69
+ console.print("[yellow]Continuing with test execution...[/yellow]\n")
70
+ logger.debug(f"Data generator setup error: {e}", exc_info=True)
71
+ return None
72
+
73
+
74
+ def load_validator_agent(validator: Optional[str]) -> Tuple[Optional[Dict[str, Any]], str, Optional[str]]:
75
+ """Load validator agent definition.
76
+
77
+ Args:
78
+ validator: Path to validator agent file (optional)
79
+
80
+ Returns:
81
+ Tuple of (validator_def, validator_name, validator_path)
82
+ """
83
+ from ..agent_loader import load_agent_definition
84
+
85
+ validator_def = None
86
+ validator_agent_name = "Default Validator"
87
+ validator_path = validator
88
+
89
+ # Try to load validator from specified path or default location
90
+ if not validator_path:
91
+ default_validator = Path.cwd() / '.alita' / 'agents' / 'test-validator.agent.md'
92
+ if default_validator.exists():
93
+ validator_path = str(default_validator)
94
+
95
+ if validator_path and Path(validator_path).exists():
96
+ try:
97
+ validator_def = load_agent_definition(validator_path)
98
+ validator_agent_name = validator_def.get('name', Path(validator_path).stem)
99
+ console.print(f"Validator Agent: [bold]{validator_agent_name}[/bold]")
100
+ console.print(f"[dim]Using: {validator_path}[/dim]\n")
101
+ except Exception as e:
102
+ console.print(f"[yellow]⚠ Warning: Failed to load validator agent: {e}[/yellow]")
103
+ console.print(f"[yellow]Will use test runner agent for validation[/yellow]\n")
104
+ logger.debug(f"Validator load error: {e}", exc_info=True)
105
+ else:
106
+ console.print(f"[dim]No validator agent specified, using test runner agent for validation[/dim]\n")
107
+
108
+ return validator_def, validator_agent_name, validator_path