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.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +14 -15
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {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
|