ara-cli 0.1.10.5__py3-none-any.whl → 0.1.13.3__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.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +87 -75
- ara_cli/ara_command_action.py +95 -57
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/common.py +2 -2
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +15 -10
- ara_cli/ara_subcommands/list.py +97 -23
- ara_cli/artefact_autofix.py +115 -62
- ara_cli/artefact_converter.py +256 -0
- ara_cli/chat.py +283 -62
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/completers.py +71 -35
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +47 -32
- ara_cli/template_loader.py +2 -1
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +33 -1
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +89 -43
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +357 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_web_search.py +467 -0
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
tests/test_web_search.py
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for chat_web_search/web_search.py
|
|
3
|
+
|
|
4
|
+
Provides full test coverage for web search functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from unittest.mock import patch, MagicMock
|
|
9
|
+
from ara_cli.chat_web_search.web_search import (
|
|
10
|
+
is_web_search_supported,
|
|
11
|
+
get_supported_models_message,
|
|
12
|
+
_get_raw_model_name,
|
|
13
|
+
_deduplicate_citations,
|
|
14
|
+
_format_citations,
|
|
15
|
+
_create_chunk,
|
|
16
|
+
_extract_openai_citations,
|
|
17
|
+
_extract_anthropic_text_citations,
|
|
18
|
+
_extract_anthropic_search_results,
|
|
19
|
+
perform_openai_web_search,
|
|
20
|
+
perform_anthropic_web_search,
|
|
21
|
+
perform_web_search_completion,
|
|
22
|
+
OPENAI_WEB_SEARCH_MODELS,
|
|
23
|
+
ANTHROPIC_WEB_SEARCH_MODELS,
|
|
24
|
+
)
|
|
25
|
+
from ara_cli.error_handler import AraError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# Tests for is_web_search_supported
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestIsWebSearchSupported:
|
|
34
|
+
"""Tests for is_web_search_supported function."""
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"model",
|
|
38
|
+
[
|
|
39
|
+
"gpt-5",
|
|
40
|
+
"gpt-5.1",
|
|
41
|
+
"o3",
|
|
42
|
+
"o4-mini",
|
|
43
|
+
"openai/gpt-5",
|
|
44
|
+
"openai/o4-mini",
|
|
45
|
+
"gpt-5-search-api",
|
|
46
|
+
"gpt-4o-search-preview",
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_openai_models_supported(self, model):
|
|
50
|
+
"""OpenAI web search models are supported."""
|
|
51
|
+
supported, provider = is_web_search_supported(model)
|
|
52
|
+
assert supported is True
|
|
53
|
+
assert provider == "openai"
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
"model",
|
|
57
|
+
[
|
|
58
|
+
"claude-sonnet-4-5-20250929",
|
|
59
|
+
"claude-sonnet-4-20250514",
|
|
60
|
+
"anthropic/claude-sonnet-4-5-20250929",
|
|
61
|
+
"claude-haiku-4-5-20251001",
|
|
62
|
+
"claude-opus-4-20250514",
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
def test_anthropic_models_supported(self, model):
|
|
66
|
+
"""Anthropic web search models are supported."""
|
|
67
|
+
supported, provider = is_web_search_supported(model)
|
|
68
|
+
assert supported is True
|
|
69
|
+
assert provider == "anthropic"
|
|
70
|
+
|
|
71
|
+
def test_unsupported_model_returns_false(self):
|
|
72
|
+
"""Unsupported models return False."""
|
|
73
|
+
supported, provider = is_web_search_supported("gpt-4o")
|
|
74
|
+
assert supported is False
|
|
75
|
+
assert provider is None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# =============================================================================
|
|
79
|
+
# Tests for get_supported_models_message
|
|
80
|
+
# =============================================================================
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestGetSupportedModelsMessage:
|
|
84
|
+
"""Tests for get_supported_models_message function."""
|
|
85
|
+
|
|
86
|
+
def test_includes_model_name(self):
|
|
87
|
+
"""Message includes the unsupported model name."""
|
|
88
|
+
result = get_supported_models_message("unsupported-model")
|
|
89
|
+
assert "unsupported-model" in result
|
|
90
|
+
|
|
91
|
+
def test_lists_openai_models(self):
|
|
92
|
+
"""Message lists OpenAI models."""
|
|
93
|
+
result = get_supported_models_message("test")
|
|
94
|
+
assert "gpt-5" in result
|
|
95
|
+
assert "OpenAI" in result
|
|
96
|
+
|
|
97
|
+
def test_lists_anthropic_models(self):
|
|
98
|
+
"""Message lists Anthropic models."""
|
|
99
|
+
result = get_supported_models_message("test")
|
|
100
|
+
assert "claude" in result
|
|
101
|
+
assert "Anthropic" in result
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# =============================================================================
|
|
105
|
+
# Tests for _get_raw_model_name
|
|
106
|
+
# =============================================================================
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestGetRawModelName:
|
|
110
|
+
"""Tests for _get_raw_model_name function."""
|
|
111
|
+
|
|
112
|
+
def test_strips_openai_prefix(self):
|
|
113
|
+
"""Strips openai/ prefix."""
|
|
114
|
+
result = _get_raw_model_name("openai/gpt-5")
|
|
115
|
+
assert result == "gpt-5"
|
|
116
|
+
|
|
117
|
+
def test_strips_anthropic_prefix(self):
|
|
118
|
+
"""Strips anthropic/ prefix."""
|
|
119
|
+
result = _get_raw_model_name("anthropic/claude-sonnet-4-20250514")
|
|
120
|
+
assert result == "claude-sonnet-4-20250514"
|
|
121
|
+
|
|
122
|
+
def test_returns_unchanged_without_prefix(self):
|
|
123
|
+
"""Returns model unchanged when no prefix."""
|
|
124
|
+
result = _get_raw_model_name("gpt-5")
|
|
125
|
+
assert result == "gpt-5"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# =============================================================================
|
|
129
|
+
# Tests for _deduplicate_citations
|
|
130
|
+
# =============================================================================
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TestDeduplicateCitations:
|
|
134
|
+
"""Tests for _deduplicate_citations function."""
|
|
135
|
+
|
|
136
|
+
def test_removes_duplicate_urls(self):
|
|
137
|
+
"""Removes citations with duplicate URLs."""
|
|
138
|
+
citations = [
|
|
139
|
+
{"title": "First", "url": "https://example.com"},
|
|
140
|
+
{"title": "Second", "url": "https://example.com"},
|
|
141
|
+
{"title": "Third", "url": "https://other.com"},
|
|
142
|
+
]
|
|
143
|
+
result = _deduplicate_citations(citations)
|
|
144
|
+
assert len(result) == 2
|
|
145
|
+
assert result[0]["title"] == "First"
|
|
146
|
+
assert result[1]["title"] == "Third"
|
|
147
|
+
|
|
148
|
+
def test_preserves_order(self):
|
|
149
|
+
"""Preserves order of first occurrences."""
|
|
150
|
+
citations = [
|
|
151
|
+
{"title": "A", "url": "https://a.com"},
|
|
152
|
+
{"title": "B", "url": "https://b.com"},
|
|
153
|
+
{"title": "C", "url": "https://c.com"},
|
|
154
|
+
]
|
|
155
|
+
result = _deduplicate_citations(citations)
|
|
156
|
+
assert [c["title"] for c in result] == ["A", "B", "C"]
|
|
157
|
+
|
|
158
|
+
def test_handles_empty_list(self):
|
|
159
|
+
"""Handles empty citation list."""
|
|
160
|
+
result = _deduplicate_citations([])
|
|
161
|
+
assert result == []
|
|
162
|
+
|
|
163
|
+
def test_skips_empty_urls(self):
|
|
164
|
+
"""Skips citations with empty URLs."""
|
|
165
|
+
citations = [
|
|
166
|
+
{"title": "First", "url": ""},
|
|
167
|
+
{"title": "Second", "url": "https://example.com"},
|
|
168
|
+
]
|
|
169
|
+
result = _deduplicate_citations(citations)
|
|
170
|
+
assert len(result) == 1
|
|
171
|
+
assert result[0]["title"] == "Second"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# =============================================================================
|
|
175
|
+
# Tests for _format_citations
|
|
176
|
+
# =============================================================================
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestFormatCitations:
|
|
180
|
+
"""Tests for _format_citations function."""
|
|
181
|
+
|
|
182
|
+
def test_formats_markdown_links(self):
|
|
183
|
+
"""Formats citations as markdown links."""
|
|
184
|
+
citations = [
|
|
185
|
+
{"title": "Example Site", "url": "https://example.com"},
|
|
186
|
+
]
|
|
187
|
+
result = _format_citations(citations)
|
|
188
|
+
assert "[Example Site](https://example.com)" in result
|
|
189
|
+
|
|
190
|
+
def test_includes_sources_header(self):
|
|
191
|
+
"""Includes Sources header."""
|
|
192
|
+
citations = [{"title": "Test", "url": "https://test.com"}]
|
|
193
|
+
result = _format_citations(citations)
|
|
194
|
+
assert "**Sources:**" in result
|
|
195
|
+
|
|
196
|
+
def test_numbers_citations(self):
|
|
197
|
+
"""Numbers each citation."""
|
|
198
|
+
citations = [
|
|
199
|
+
{"title": "First", "url": "https://first.com"},
|
|
200
|
+
{"title": "Second", "url": "https://second.com"},
|
|
201
|
+
]
|
|
202
|
+
result = _format_citations(citations)
|
|
203
|
+
assert "1. [First]" in result
|
|
204
|
+
assert "2. [Second]" in result
|
|
205
|
+
|
|
206
|
+
def test_returns_empty_for_no_citations(self):
|
|
207
|
+
"""Returns empty string when no citations."""
|
|
208
|
+
result = _format_citations([])
|
|
209
|
+
assert result == ""
|
|
210
|
+
|
|
211
|
+
def test_handles_missing_url(self):
|
|
212
|
+
"""Handles citations without URL."""
|
|
213
|
+
citations = [{"title": "No URL", "url": ""}]
|
|
214
|
+
result = _format_citations(citations)
|
|
215
|
+
assert result == "" # Empty URL is filtered by deduplicate
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# =============================================================================
|
|
219
|
+
# Tests for _create_chunk
|
|
220
|
+
# =============================================================================
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestCreateChunk:
|
|
224
|
+
"""Tests for _create_chunk function."""
|
|
225
|
+
|
|
226
|
+
def test_creates_mock_chunk_with_content(self):
|
|
227
|
+
"""Creates mock chunk with content accessible via choices."""
|
|
228
|
+
chunk = _create_chunk("test content")
|
|
229
|
+
assert chunk.choices[0].delta.content == "test content"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# =============================================================================
|
|
233
|
+
# Tests for _extract_openai_citations
|
|
234
|
+
# =============================================================================
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TestExtractOpenaiCitations:
|
|
238
|
+
"""Tests for _extract_openai_citations function."""
|
|
239
|
+
|
|
240
|
+
def test_extracts_url_citations(self):
|
|
241
|
+
"""Extracts URL citations from response."""
|
|
242
|
+
mock_annotation = MagicMock()
|
|
243
|
+
mock_annotation.type = "url_citation"
|
|
244
|
+
mock_annotation.title = "Test Title"
|
|
245
|
+
mock_annotation.url = "https://test.com"
|
|
246
|
+
|
|
247
|
+
mock_content_item = MagicMock()
|
|
248
|
+
mock_content_item.annotations = [mock_annotation]
|
|
249
|
+
|
|
250
|
+
mock_output_item = MagicMock()
|
|
251
|
+
mock_output_item.type = "message"
|
|
252
|
+
mock_output_item.content = [mock_content_item]
|
|
253
|
+
|
|
254
|
+
mock_response = MagicMock()
|
|
255
|
+
mock_response.output = [mock_output_item]
|
|
256
|
+
|
|
257
|
+
result = _extract_openai_citations(mock_response)
|
|
258
|
+
|
|
259
|
+
assert len(result) == 1
|
|
260
|
+
assert result[0]["title"] == "Test Title"
|
|
261
|
+
assert result[0]["url"] == "https://test.com"
|
|
262
|
+
|
|
263
|
+
def test_returns_empty_for_no_output(self):
|
|
264
|
+
"""Returns empty list when no output."""
|
|
265
|
+
mock_response = MagicMock()
|
|
266
|
+
mock_response.output = None
|
|
267
|
+
|
|
268
|
+
result = _extract_openai_citations(mock_response)
|
|
269
|
+
|
|
270
|
+
assert result == []
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# =============================================================================
|
|
274
|
+
# Tests for _extract_anthropic_text_citations
|
|
275
|
+
# =============================================================================
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class TestExtractAnthropicTextCitations:
|
|
279
|
+
"""Tests for _extract_anthropic_text_citations function."""
|
|
280
|
+
|
|
281
|
+
def test_extracts_citations_with_url(self):
|
|
282
|
+
"""Extracts citations that have URL attribute."""
|
|
283
|
+
mock_citation = MagicMock()
|
|
284
|
+
mock_citation.url = "https://example.com"
|
|
285
|
+
mock_citation.title = "Example"
|
|
286
|
+
|
|
287
|
+
mock_content_block = MagicMock()
|
|
288
|
+
mock_content_block.citations = [mock_citation]
|
|
289
|
+
|
|
290
|
+
result = _extract_anthropic_text_citations(mock_content_block)
|
|
291
|
+
|
|
292
|
+
assert len(result) == 1
|
|
293
|
+
assert result[0]["url"] == "https://example.com"
|
|
294
|
+
|
|
295
|
+
def test_returns_empty_for_no_citations(self):
|
|
296
|
+
"""Returns empty list when no citations."""
|
|
297
|
+
mock_content_block = MagicMock()
|
|
298
|
+
mock_content_block.citations = None
|
|
299
|
+
|
|
300
|
+
result = _extract_anthropic_text_citations(mock_content_block)
|
|
301
|
+
|
|
302
|
+
assert result == []
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# =============================================================================
|
|
306
|
+
# Tests for _extract_anthropic_search_results
|
|
307
|
+
# =============================================================================
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class TestExtractAnthropicSearchResults:
|
|
311
|
+
"""Tests for _extract_anthropic_search_results function."""
|
|
312
|
+
|
|
313
|
+
def test_extracts_web_search_results(self):
|
|
314
|
+
"""Extracts web search result citations."""
|
|
315
|
+
mock_result = MagicMock()
|
|
316
|
+
mock_result.type = "web_search_result"
|
|
317
|
+
mock_result.title = "Search Result"
|
|
318
|
+
mock_result.url = "https://search.com"
|
|
319
|
+
|
|
320
|
+
mock_content_block = MagicMock()
|
|
321
|
+
mock_content_block.content = [mock_result]
|
|
322
|
+
|
|
323
|
+
result = _extract_anthropic_search_results(mock_content_block)
|
|
324
|
+
|
|
325
|
+
assert len(result) == 1
|
|
326
|
+
assert result[0]["title"] == "Search Result"
|
|
327
|
+
assert result[0]["url"] == "https://search.com"
|
|
328
|
+
|
|
329
|
+
def test_skips_non_search_results(self):
|
|
330
|
+
"""Skips items that aren't web_search_result type."""
|
|
331
|
+
mock_result = MagicMock()
|
|
332
|
+
mock_result.type = "other_type"
|
|
333
|
+
|
|
334
|
+
mock_content_block = MagicMock()
|
|
335
|
+
mock_content_block.content = [mock_result]
|
|
336
|
+
|
|
337
|
+
result = _extract_anthropic_search_results(mock_content_block)
|
|
338
|
+
|
|
339
|
+
assert result == []
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# =============================================================================
|
|
343
|
+
# Tests for perform_openai_web_search
|
|
344
|
+
# =============================================================================
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class TestPerformOpenaiWebSearch:
|
|
348
|
+
"""Tests for perform_openai_web_search function."""
|
|
349
|
+
|
|
350
|
+
@patch("openai.OpenAI")
|
|
351
|
+
@patch("os.getenv", return_value="test-api-key")
|
|
352
|
+
def test_uses_chat_completions_for_search_models(
|
|
353
|
+
self, mock_getenv, mock_openai_class
|
|
354
|
+
):
|
|
355
|
+
"""Uses Chat Completions API for search models."""
|
|
356
|
+
mock_client = MagicMock()
|
|
357
|
+
mock_openai_class.return_value = mock_client
|
|
358
|
+
|
|
359
|
+
mock_chunk = MagicMock()
|
|
360
|
+
mock_chunk.choices = [MagicMock(delta=MagicMock(content="response"))]
|
|
361
|
+
mock_client.chat.completions.create.return_value = [mock_chunk]
|
|
362
|
+
|
|
363
|
+
results = list(perform_openai_web_search("test query", "gpt-5-search-api"))
|
|
364
|
+
|
|
365
|
+
mock_client.chat.completions.create.assert_called_once()
|
|
366
|
+
|
|
367
|
+
@patch("openai.OpenAI")
|
|
368
|
+
@patch("os.getenv", return_value="test-api-key")
|
|
369
|
+
def test_uses_responses_api_for_other_models(self, mock_getenv, mock_openai_class):
|
|
370
|
+
"""Uses Responses API for non-search models."""
|
|
371
|
+
mock_client = MagicMock()
|
|
372
|
+
mock_openai_class.return_value = mock_client
|
|
373
|
+
|
|
374
|
+
mock_response = MagicMock()
|
|
375
|
+
mock_response.output_text = "response text"
|
|
376
|
+
mock_response.output = []
|
|
377
|
+
mock_client.responses.create.return_value = mock_response
|
|
378
|
+
|
|
379
|
+
results = list(perform_openai_web_search("test query", "gpt-5"))
|
|
380
|
+
|
|
381
|
+
mock_client.responses.create.assert_called_once()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# =============================================================================
|
|
385
|
+
# Tests for perform_anthropic_web_search
|
|
386
|
+
# =============================================================================
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class TestPerformAnthropicWebSearch:
|
|
390
|
+
"""Tests for perform_anthropic_web_search function."""
|
|
391
|
+
|
|
392
|
+
@patch("anthropic.Anthropic")
|
|
393
|
+
@patch("os.getenv", return_value="test-api-key")
|
|
394
|
+
def test_creates_message_with_web_search_tool(
|
|
395
|
+
self, mock_getenv, mock_anthropic_class
|
|
396
|
+
):
|
|
397
|
+
"""Creates message with web_search tool."""
|
|
398
|
+
mock_client = MagicMock()
|
|
399
|
+
mock_anthropic_class.return_value = mock_client
|
|
400
|
+
|
|
401
|
+
mock_text_block = MagicMock()
|
|
402
|
+
mock_text_block.type = "text"
|
|
403
|
+
mock_text_block.text = "response text"
|
|
404
|
+
mock_text_block.citations = None
|
|
405
|
+
|
|
406
|
+
mock_response = MagicMock()
|
|
407
|
+
mock_response.content = [mock_text_block]
|
|
408
|
+
mock_client.messages.create.return_value = mock_response
|
|
409
|
+
|
|
410
|
+
results = list(
|
|
411
|
+
perform_anthropic_web_search("test query", "claude-sonnet-4-20250514")
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
mock_client.messages.create.assert_called_once()
|
|
415
|
+
call_args = mock_client.messages.create.call_args
|
|
416
|
+
assert any("web_search" in str(arg) for arg in call_args)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# =============================================================================
|
|
420
|
+
# Tests for perform_web_search_completion
|
|
421
|
+
# =============================================================================
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class TestPerformWebSearchCompletion:
|
|
425
|
+
"""Tests for perform_web_search_completion function."""
|
|
426
|
+
|
|
427
|
+
@patch("ara_cli.chat_web_search.web_search.LLMSingleton")
|
|
428
|
+
def test_raises_for_unsupported_model(self, mock_singleton):
|
|
429
|
+
"""Raises AraError for unsupported model."""
|
|
430
|
+
mock_instance = MagicMock()
|
|
431
|
+
mock_instance.get_config_by_purpose.return_value = {"model": "gpt-4o"}
|
|
432
|
+
mock_singleton.get_instance.return_value = mock_instance
|
|
433
|
+
|
|
434
|
+
with pytest.raises(AraError) as exc_info:
|
|
435
|
+
list(perform_web_search_completion("test query"))
|
|
436
|
+
|
|
437
|
+
assert "not supported" in str(exc_info.value)
|
|
438
|
+
|
|
439
|
+
@patch("ara_cli.chat_web_search.web_search.perform_openai_web_search")
|
|
440
|
+
@patch("ara_cli.chat_web_search.web_search.LLMSingleton")
|
|
441
|
+
def test_uses_openai_for_openai_models(self, mock_singleton, mock_openai_search):
|
|
442
|
+
"""Uses OpenAI search for OpenAI models."""
|
|
443
|
+
mock_instance = MagicMock()
|
|
444
|
+
mock_instance.get_config_by_purpose.return_value = {"model": "gpt-5"}
|
|
445
|
+
mock_singleton.get_instance.return_value = mock_instance
|
|
446
|
+
mock_openai_search.return_value = iter([])
|
|
447
|
+
|
|
448
|
+
list(perform_web_search_completion("test query"))
|
|
449
|
+
|
|
450
|
+
mock_openai_search.assert_called_once()
|
|
451
|
+
|
|
452
|
+
@patch("ara_cli.chat_web_search.web_search.perform_anthropic_web_search")
|
|
453
|
+
@patch("ara_cli.chat_web_search.web_search.LLMSingleton")
|
|
454
|
+
def test_uses_anthropic_for_anthropic_models(
|
|
455
|
+
self, mock_singleton, mock_anthropic_search
|
|
456
|
+
):
|
|
457
|
+
"""Uses Anthropic search for Anthropic models."""
|
|
458
|
+
mock_instance = MagicMock()
|
|
459
|
+
mock_instance.get_config_by_purpose.return_value = {
|
|
460
|
+
"model": "claude-sonnet-4-20250514"
|
|
461
|
+
}
|
|
462
|
+
mock_singleton.get_instance.return_value = mock_instance
|
|
463
|
+
mock_anthropic_search.return_value = iter([])
|
|
464
|
+
|
|
465
|
+
list(perform_web_search_completion("test query"))
|
|
466
|
+
|
|
467
|
+
mock_anthropic_search.assert_called_once()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Usage:
|
|
2
|
-
# necessary input and adaption:
|
|
3
|
-
# replace text snippets in <> with specific context
|
|
4
|
-
# ...
|
|
5
|
-
# expected output:
|
|
6
|
-
# ...
|
|
7
|
-
Do not use usage information as prompt instructions
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Given source code
|
|
11
|
-
```python
|
|
12
|
-
<source code for context, skip irrelevant for current task>
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Given existing unit tests
|
|
16
|
-
```python
|
|
17
|
-
<existing unit tests>
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Given pytest is available
|
|
21
|
-
|
|
22
|
-
Modify and/or create unit tests so this is fully covered:
|
|
23
|
-
```python
|
|
24
|
-
<snippet you want to cover in the next step>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Give me only what is relevant to testing this snippet. Use parametrization where applicable. Split into multiple tests instead of using if-else blocks. Mock all dependencies of tested code.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
MODE: STRICT_GENERATION
|
|
2
|
-
TASK: Produce one complete pytest file for a given Python module.
|
|
3
|
-
|
|
4
|
-
REQUIREMENTS:
|
|
5
|
-
- Output only valid Python code (no text, no markdown).
|
|
6
|
-
- Target: full behavioral + branch coverage (100%).
|
|
7
|
-
- Tests follow AAA pattern (# Arrange, # Act, # Assert).
|
|
8
|
-
- Exactly ONE assert or ONE pytest.raises() per test.
|
|
9
|
-
- Use tmp_path fixture for filesystem isolation.
|
|
10
|
-
- Include an autouse=True fixture for global patching if needed.
|
|
11
|
-
- No external I/O or network calls.
|
|
12
|
-
- All tests independent, self-contained.
|
|
13
|
-
|
|
14
|
-
STYLE:
|
|
15
|
-
- File starts with module docstring describing AAA, single assert rule, autouse fixture.
|
|
16
|
-
- Group tests with comment headers:
|
|
17
|
-
# --- Success paths --- / # --- Error paths --- / # --- Edge cases ---
|
|
18
|
-
- Function names: test_<function>_<expected_behavior>_<condition>
|
|
19
|
-
- Variables descriptive (rel, path, new_content, etc.).
|
|
20
|
-
- Use direct asserts (assert result is True, assert path.read_text() == "x").
|
|
21
|
-
- For errors:
|
|
22
|
-
with pytest.raises(ExceptionType, match=r"text"): function_call()
|
|
23
|
-
|
|
24
|
-
COVERAGE:
|
|
25
|
-
- Include success, failure, and rare edge branches (e.g., conditional exceptions).
|
|
26
|
-
- Ensure 100% of conditional branches executed.
|
|
27
|
-
|
|
28
|
-
OUTPUT:
|
|
29
|
-
- Single .py file, runnable via: pytest -v --disable-warnings --maxfail=1 --cov=<target_module>
|
|
30
|
-
- No explanations or prose, only the test code.
|
|
31
|
-
|
|
32
|
-
END
|
ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
### INTENTION and CONTEXT
|
|
2
|
-
My intention is to setup a todo list in my given task that helps me to implement a feature in a BDD way
|
|
3
|
-
|
|
4
|
-
Now do the following:
|
|
5
|
-
Search for a line starting with `Task: ` defined in the `### GIVENS` section. Just repeat the task_name you have found as confirmation
|
|
6
|
-
* Do not proceed if no task is defined. Return immediatly with the message: "No task defined as prompt control"
|
|
7
|
-
|
|
8
|
-
* Focus on the description in the `Description` section of the defined task. Ignore all other sections.
|
|
9
|
-
* Analyze the content of the task description section and adapt your default recipe accordingly. You can add new "[@to-do]s ...", you can delete "[@to-do]s" that are not necessary anymore according to the existing task description content
|
|
10
|
-
|
|
11
|
-
* the format and formulation of your default recipe implementing a feature in BDD style is
|
|
12
|
-
```
|
|
13
|
-
[@to-do] analyze and understand the given `user story`
|
|
14
|
-
[@to-do] generate an example contributing to the rule `rule of userstory to implement` which should be turned into a scenario or feature description
|
|
15
|
-
[@to-do] use the example and aditional relevant context `{context list}` to formulate the feature file
|
|
16
|
-
[@to-do] use the formulated feature file, relevant existing step implementations and relevant existing production code to implement the new step implementations that will fail
|
|
17
|
-
[@to-do] use the created step definitions, the relevant existing production code to modify existing code and to create new code implementing the requested behavior so that the step implementations will pass
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
* append your recipe at the end of task
|
|
21
|
-
* return the extended task in the following format
|
|
22
|
-
```artefact
|
|
23
|
-
# [ ] extract
|
|
24
|
-
# filename: ara/tasks/{task_name}.task
|
|
25
|
-
{initial task content}
|
|
26
|
-
{recipe}
|
|
27
|
-
```
|
|
28
|
-
* the extract and filename statements are only allowed once per code block
|
|
29
|
-
|
|
30
|
-
* in case you think information is missing in order to generate a suffiently precise formulation, return a warning "WARNING: information is missing to formulate the new artefacts" and then explain what kind of information you think is missing and how I could easily retrieve it
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
### COMMANDS FOR THE CORRECT CONTEXTUAL CLASSIFICATION OF ARTIFACTS
|
|
2
|
-
Your job is now:
|
|
3
|
-
* analyze the given files with respect to content and relationship
|
|
4
|
-
* analyze the new artefact with respect to content and the relationship to the given files
|
|
5
|
-
* give me a list of the top 5 artefacts to which the new artefact contributes the most
|
|
6
|
-
* the output format must be a table with the columns
|
|
7
|
-
| artefact name | contribution rating from 0 (very low) - 1 (very high) | arguments for rating | path to artefact |
|
|
8
|
-
|
|
9
|
-
* in case you think the relationship of the new artefact is to weak to any given files, return a warning "WARNING: new artefact is not directly related to already existing aretefacts" and then make a proposal with regard of the ara "work orchestration contribution hierarchy" and/or the "specification contribution hierarchy"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
### COMMANDS FOR EXTENDING EXISTING AGILE REQUIREMENTS ARTIFACTS
|
|
2
|
-
Your job is now:
|
|
3
|
-
* analyze the given files with respect to content and relationship
|
|
4
|
-
* analyze the new or changed behavior with respect to content and the relationship to the existing artefacts
|
|
5
|
-
* Develop formulation strategies that minimize formulation changes of existing changes.
|
|
6
|
-
* generate a formulation proposal that adds the new context and or behavior for the specified documents
|
|
7
|
-
* wrap and return the formulated in the following format
|
|
8
|
-
```artefact
|
|
9
|
-
# [ ] extract
|
|
10
|
-
# filename: {path/filename.filextension}
|
|
11
|
-
{formulation}
|
|
12
|
-
```
|
|
13
|
-
* the extract and filename statements are only allowed once per code block
|
|
14
|
-
|
|
15
|
-
* Adhere strictly to established rules for AGILE PRODUCT OWNERS for high-quality specification artefacts.
|
|
16
|
-
|
|
17
|
-
* in case you think information is missing in order to generate a suffiently precise formulation, return a warning "WARNING: information is missing to formulate the new aretefacts" and then explain what kind of information you think is missing and how I could easily retrieve it
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
### COMMANDS FOR FORMULATING AGILE REQUIREMENTS ARTIFACTS
|
|
2
|
-
Your job is now:
|
|
3
|
-
* analyze the given files with respect to content and relationship
|
|
4
|
-
* analyze the new artefact with respect to content and the relationship to the given files
|
|
5
|
-
* generate a formulation proposal for the specified documents
|
|
6
|
-
* wrap and return the formulated in the following format
|
|
7
|
-
```artefact
|
|
8
|
-
# [ ] extract
|
|
9
|
-
# filename: {path/filename.filextension}
|
|
10
|
-
{formulation}
|
|
11
|
-
```
|
|
12
|
-
* the extract and filename statements are only allowed once per code block
|
|
13
|
-
|
|
14
|
-
* in case you think information is missing in order to generate a suffiently precise formulation, return a warning "WARNING: information is missing to formulate the new artefacts" and then explain what kind of information you think is missing and how I could easily retrieve it
|