ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.0__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 (151) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +189 -101
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/common.py +2 -2
  6. ara_cli/ara_subcommands/config.py +221 -0
  7. ara_cli/ara_subcommands/convert.py +107 -0
  8. ara_cli/ara_subcommands/fetch.py +41 -0
  9. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  10. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  11. ara_cli/ara_subcommands/fetch_templates.py +15 -10
  12. ara_cli/ara_subcommands/list.py +97 -23
  13. ara_cli/ara_subcommands/prompt.py +266 -106
  14. ara_cli/artefact_autofix.py +117 -64
  15. ara_cli/artefact_converter.py +355 -0
  16. ara_cli/artefact_creator.py +41 -17
  17. ara_cli/artefact_lister.py +3 -3
  18. ara_cli/artefact_models/artefact_model.py +1 -1
  19. ara_cli/artefact_models/artefact_templates.py +0 -9
  20. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  21. ara_cli/artefact_reader.py +62 -43
  22. ara_cli/artefact_scan.py +39 -17
  23. ara_cli/chat.py +300 -71
  24. ara_cli/chat_agent/__init__.py +0 -0
  25. ara_cli/chat_agent/agent_process_manager.py +155 -0
  26. ara_cli/chat_script_runner/__init__.py +0 -0
  27. ara_cli/chat_script_runner/script_completer.py +23 -0
  28. ara_cli/chat_script_runner/script_finder.py +41 -0
  29. ara_cli/chat_script_runner/script_lister.py +36 -0
  30. ara_cli/chat_script_runner/script_runner.py +36 -0
  31. ara_cli/chat_web_search/__init__.py +0 -0
  32. ara_cli/chat_web_search/web_search.py +263 -0
  33. ara_cli/children_contribution_updater.py +737 -0
  34. ara_cli/classifier.py +34 -0
  35. ara_cli/commands/agent_run_command.py +98 -0
  36. ara_cli/commands/fetch_agents_command.py +106 -0
  37. ara_cli/commands/fetch_scripts_command.py +43 -0
  38. ara_cli/commands/fetch_templates_command.py +39 -0
  39. ara_cli/commands/fetch_templates_commands.py +39 -0
  40. ara_cli/commands/list_agents_command.py +39 -0
  41. ara_cli/commands/load_command.py +4 -3
  42. ara_cli/commands/load_image_command.py +1 -1
  43. ara_cli/commands/read_command.py +23 -27
  44. ara_cli/completers.py +95 -35
  45. ara_cli/constants.py +2 -0
  46. ara_cli/directory_navigator.py +37 -4
  47. ara_cli/error_handler.py +26 -11
  48. ara_cli/file_loaders/document_reader.py +0 -178
  49. ara_cli/file_loaders/factories/__init__.py +0 -0
  50. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  51. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  52. ara_cli/file_loaders/file_loader.py +1 -30
  53. ara_cli/file_loaders/loaders/__init__.py +0 -0
  54. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  55. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  56. ara_cli/file_loaders/readers/__init__.py +0 -0
  57. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  58. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  59. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  60. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  61. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  62. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  63. ara_cli/file_loaders/tools/__init__.py +0 -0
  64. ara_cli/llm_utils.py +58 -0
  65. ara_cli/output_suppressor.py +53 -0
  66. ara_cli/prompt_chat.py +20 -4
  67. ara_cli/prompt_extractor.py +47 -32
  68. ara_cli/prompt_handler.py +123 -17
  69. ara_cli/tag_extractor.py +8 -7
  70. ara_cli/template_loader.py +2 -1
  71. ara_cli/template_manager.py +52 -21
  72. ara_cli/templates/global-scripts/hello_global.py +1 -0
  73. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  79. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  82. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  83. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  84. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  92. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  93. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  94. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  95. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  96. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  97. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  98. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  99. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  100. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  101. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  102. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  103. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  104. ara_cli/version.py +1 -1
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
  106. ara_cli-0.1.14.0.dist-info/RECORD +253 -0
  107. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  108. tests/test_ara_command_action.py +31 -19
  109. tests/test_ara_config.py +177 -90
  110. tests/test_artefact_autofix.py +170 -97
  111. tests/test_artefact_autofix_integration.py +495 -0
  112. tests/test_artefact_converter.py +312 -0
  113. tests/test_artefact_extraction.py +564 -0
  114. tests/test_artefact_lister.py +11 -8
  115. tests/test_chat.py +166 -130
  116. tests/test_chat_givens_images.py +603 -0
  117. tests/test_chat_script_runner.py +454 -0
  118. tests/test_children_contribution_updater.py +98 -0
  119. tests/test_document_loader_office.py +267 -0
  120. tests/test_llm_utils.py +164 -0
  121. tests/test_prompt_chat.py +343 -0
  122. tests/test_prompt_extractor.py +683 -0
  123. tests/test_prompt_handler.py +416 -214
  124. tests/test_setup_default_chat_prompt_mode.py +198 -0
  125. tests/test_tag_extractor.py +95 -49
  126. tests/test_web_search.py +467 -0
  127. ara_cli/file_loaders/document_readers.py +0 -233
  128. ara_cli/file_loaders/file_loaders.py +0 -123
  129. ara_cli/file_loaders/text_file_loader.py +0 -187
  130. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  131. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  132. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  133. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  134. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  135. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  136. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  137. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  138. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  139. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  140. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  141. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  142. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  143. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  144. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  145. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  146. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  147. ara_cli-0.1.10.5.dist-info/RECORD +0 -194
  148. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  149. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  150. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  151. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,312 @@
1
+ """
2
+ Unit tests for artefact_converter.py
3
+
4
+ Provides full test coverage for the AraArtefactConverter class.
5
+ """
6
+
7
+ import pytest
8
+ import os
9
+ import tempfile
10
+ from unittest.mock import patch, MagicMock, mock_open
11
+ from ara_cli.artefact_converter import AraArtefactConverter
12
+ from ara_cli.error_handler import AraError
13
+
14
+
15
+ @pytest.fixture
16
+ def mock_file_system():
17
+ """Mock file system for testing."""
18
+ mock_fs = MagicMock()
19
+ mock_fs.path = MagicMock()
20
+ mock_fs.path.join = os.path.join
21
+ mock_fs.path.exists = MagicMock(return_value=False)
22
+ return mock_fs
23
+
24
+
25
+ @pytest.fixture
26
+ def converter(mock_file_system):
27
+ """Create converter instance with mocked dependencies."""
28
+ with patch("ara_cli.artefact_converter.ArtefactReader") as mock_reader, patch(
29
+ "ara_cli.artefact_converter.ArtefactCreator"
30
+ ):
31
+ converter = AraArtefactConverter(file_system=mock_file_system)
32
+ converter.reader = mock_reader.return_value
33
+ return converter
34
+
35
+
36
+ class TestAraArtefactConverterInit:
37
+ """Tests for AraArtefactConverter initialization."""
38
+
39
+ def test_uses_provided_file_system(self, mock_file_system):
40
+ """Uses provided file system when given."""
41
+ with patch("ara_cli.artefact_converter.ArtefactReader"), patch(
42
+ "ara_cli.artefact_converter.ArtefactCreator"
43
+ ):
44
+ converter = AraArtefactConverter(file_system=mock_file_system)
45
+ assert converter.file_system == mock_file_system
46
+
47
+ def test_uses_os_as_default_file_system(self):
48
+ """Uses os module as default file system."""
49
+ with patch("ara_cli.artefact_converter.ArtefactReader"), patch(
50
+ "ara_cli.artefact_converter.ArtefactCreator"
51
+ ):
52
+ converter = AraArtefactConverter()
53
+ assert converter.file_system == os
54
+
55
+
56
+ class TestValidateClassifiers:
57
+ """Tests for classifier validation."""
58
+
59
+ @patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
60
+ def test_raises_for_invalid_old_classifier(self, mock_is_valid, converter):
61
+ """Raises ValueError for invalid old classifier."""
62
+ mock_is_valid.side_effect = lambda x: x != "invalid"
63
+
64
+ with pytest.raises(ValueError) as exc_info:
65
+ converter._validate_classifiers("invalid", "feature")
66
+
67
+ assert "Invalid classifier: invalid" in str(exc_info.value)
68
+
69
+ @patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
70
+ def test_raises_for_invalid_new_classifier(self, mock_is_valid, converter):
71
+ """Raises ValueError for invalid new classifier."""
72
+ mock_is_valid.side_effect = lambda x: x != "invalid"
73
+
74
+ with pytest.raises(ValueError) as exc_info:
75
+ converter._validate_classifiers("feature", "invalid")
76
+
77
+ assert "Invalid classifier: invalid" in str(exc_info.value)
78
+
79
+ @patch(
80
+ "ara_cli.artefact_converter.Classifier.is_valid_classifier", return_value=True
81
+ )
82
+ def test_passes_for_valid_classifiers(self, mock_is_valid, converter):
83
+ """Passes validation for valid classifiers."""
84
+ converter._validate_classifiers("feature", "userstory")
85
+ assert mock_is_valid.call_count == 2
86
+
87
+
88
+ class TestResolveTargetContent:
89
+ """Tests for target content resolution."""
90
+
91
+ def test_raises_when_target_exists_without_flags(self, converter):
92
+ """Raises ValueError when target exists and no override/merge flags."""
93
+ converter.reader.read_artefact_data.return_value = (None, {"exists": True})
94
+
95
+ with pytest.raises(ValueError) as exc_info:
96
+ converter._resolve_target_content(
97
+ "test", "feature", merge=False, override=False
98
+ )
99
+
100
+ assert "already existing" in str(exc_info.value)
101
+
102
+ def test_returns_content_when_merge_flag_set(self, converter):
103
+ """Returns existing content when merge flag is set."""
104
+ converter.reader.read_artefact_data.return_value = (
105
+ "existing content",
106
+ {"exists": True},
107
+ )
108
+
109
+ result = converter._resolve_target_content(
110
+ "test", "feature", merge=True, override=False
111
+ )
112
+
113
+ assert result == "existing content"
114
+
115
+ def test_returns_none_when_override_flag_set(self, converter):
116
+ """Returns None when override flag is set (different path)."""
117
+ converter.reader.read_artefact_data.return_value = (None, {"exists": True})
118
+
119
+ # Override skips the existence check entirely
120
+ result = converter._resolve_target_content(
121
+ "test", "feature", merge=False, override=True
122
+ )
123
+
124
+ # This won't raise because override=True skips the check
125
+ assert result is None
126
+
127
+ def test_returns_none_when_no_existing_target(self, converter):
128
+ """Returns None when no existing target artefact."""
129
+ converter.reader.read_artefact_data.return_value = (None, None)
130
+
131
+ result = converter._resolve_target_content(
132
+ "test", "feature", merge=False, override=False
133
+ )
134
+
135
+ assert result is None
136
+
137
+
138
+ class TestGetTargetClass:
139
+ """Tests for getting target artefact class."""
140
+
141
+ def test_returns_class_for_valid_classifier(self, converter):
142
+ """Returns artefact class for valid classifier."""
143
+ # Using a known classifier
144
+ result = converter._get_target_class("feature")
145
+ assert result is not None
146
+
147
+ def test_raises_for_invalid_classifier(self, converter):
148
+ """Raises ValueError for invalid classifier string."""
149
+ with pytest.raises(ValueError):
150
+ converter._get_target_class("definitely_not_a_type")
151
+
152
+
153
+ class TestGetPrompt:
154
+ """Tests for prompt generation."""
155
+
156
+ @patch("ara_cli.artefact_converter.LLMSingleton")
157
+ def test_uses_fallback_when_langfuse_unavailable(self, mock_singleton, converter):
158
+ """Uses fallback prompt when Langfuse is unavailable."""
159
+ mock_singleton.get_instance.return_value.langfuse = None
160
+
161
+ result = converter._get_prompt(
162
+ old_classifier="feature",
163
+ new_classifier="userstory",
164
+ artefact_name="test",
165
+ content="content",
166
+ target_content_existing=None,
167
+ merge=False,
168
+ )
169
+
170
+ assert "Convert the following feature artefact" in result
171
+ assert "content" in result
172
+
173
+ @patch("ara_cli.artefact_converter.LLMSingleton")
174
+ def test_merge_prompt_includes_both_contents(self, mock_singleton, converter):
175
+ """Merge prompt includes source and target content."""
176
+ mock_singleton.get_instance.return_value.langfuse = None
177
+
178
+ result = converter._get_prompt(
179
+ old_classifier="feature",
180
+ new_classifier="userstory",
181
+ artefact_name="test",
182
+ content="source content",
183
+ target_content_existing="target content",
184
+ merge=True,
185
+ )
186
+
187
+ assert "Merge" in result
188
+ assert "source content" in result
189
+ assert "target content" in result
190
+
191
+ @patch("ara_cli.artefact_converter.LLMSingleton")
192
+ def test_uses_langfuse_prompt_when_available(self, mock_singleton, converter):
193
+ """Uses Langfuse prompt when available."""
194
+ mock_langfuse = MagicMock()
195
+ mock_prompt = MagicMock()
196
+ mock_prompt.compile.return_value = "langfuse prompt"
197
+ mock_langfuse.get_prompt.return_value = mock_prompt
198
+ mock_singleton.get_instance.return_value.langfuse = mock_langfuse
199
+
200
+ result = converter._get_prompt(
201
+ old_classifier="feature",
202
+ new_classifier="userstory",
203
+ artefact_name="test",
204
+ content="content",
205
+ target_content_existing=None,
206
+ merge=False,
207
+ )
208
+
209
+ assert result == "langfuse prompt"
210
+
211
+
212
+ class TestRunConversionAgent:
213
+ """Tests for running the conversion agent."""
214
+
215
+ @patch("ara_cli.llm_utils.create_pydantic_ai_agent")
216
+ def test_returns_converted_artefact(self, mock_create_agent, converter):
217
+ """Returns converted artefact from agent."""
218
+ mock_result = MagicMock()
219
+ mock_result.output = "converted artefact"
220
+ mock_agent = MagicMock()
221
+ mock_agent.run_sync.return_value = mock_result
222
+ mock_create_agent.return_value = mock_agent
223
+
224
+ result = converter._run_conversion_agent("prompt", MagicMock)
225
+
226
+ assert result == "converted artefact"
227
+
228
+ @patch("ara_cli.llm_utils.create_pydantic_ai_agent")
229
+ def test_raises_ara_error_on_failure(self, mock_create_agent, converter):
230
+ """Raises AraError when agent fails."""
231
+ mock_agent = MagicMock()
232
+ mock_agent.run_sync.side_effect = Exception("LLM error")
233
+ mock_create_agent.return_value = mock_agent
234
+
235
+ with pytest.raises(AraError) as exc_info:
236
+ converter._run_conversion_agent("prompt", MagicMock)
237
+
238
+ assert "LLM conversion failed" in str(exc_info.value)
239
+
240
+
241
+ class TestWriteArtefact:
242
+ """Tests for writing converted artefacts."""
243
+
244
+ @patch("ara_cli.artefact_converter.DirectoryNavigator")
245
+ @patch(
246
+ "ara_cli.artefact_converter.Classifier.get_sub_directory",
247
+ return_value="features",
248
+ )
249
+ @patch("builtins.open", new_callable=mock_open)
250
+ @patch("os.makedirs")
251
+ @patch("shutil.rmtree")
252
+ def test_writes_artefact_file(
253
+ self,
254
+ mock_rmtree,
255
+ mock_makedirs,
256
+ mock_file,
257
+ mock_subdir,
258
+ mock_navigator,
259
+ converter,
260
+ mock_file_system,
261
+ ):
262
+ """Writes artefact content to file."""
263
+ mock_file_system.path.exists.return_value = False
264
+
265
+ converter._write_artefact(
266
+ "feature", "test", "content", merge=False, override=False
267
+ )
268
+
269
+ mock_file.assert_called()
270
+ mock_file().write.assert_called_with("content")
271
+
272
+ @patch("ara_cli.artefact_converter.DirectoryNavigator")
273
+ @patch(
274
+ "ara_cli.artefact_converter.Classifier.get_sub_directory",
275
+ return_value="features",
276
+ )
277
+ def test_raises_when_file_exists_without_flags(
278
+ self, mock_subdir, mock_navigator, converter, mock_file_system
279
+ ):
280
+ """Raises ValueError when target file exists without override/merge."""
281
+ mock_file_system.path.exists.return_value = True
282
+
283
+ with pytest.raises(ValueError) as exc_info:
284
+ converter._write_artefact(
285
+ "feature", "test", "content", merge=False, override=False
286
+ )
287
+
288
+ assert "already exists" in str(exc_info.value)
289
+
290
+
291
+ class TestConvert:
292
+ """Integration tests for the convert method."""
293
+
294
+ @patch(
295
+ "ara_cli.artefact_converter.Classifier.is_valid_classifier", return_value=True
296
+ )
297
+ def test_raises_when_source_not_found(self, mock_is_valid, converter):
298
+ """Raises AraError when source artefact not found."""
299
+ converter.reader.read_artefact_data.return_value = (None, None)
300
+
301
+ with pytest.raises(AraError) as exc_info:
302
+ converter.convert("feature", "test", "userstory")
303
+
304
+ assert "not found" in str(exc_info.value)
305
+
306
+ @patch("ara_cli.artefact_converter.Classifier.is_valid_classifier")
307
+ def test_raises_for_invalid_classifier(self, mock_is_valid, converter):
308
+ """Raises ValueError for invalid classifier."""
309
+ mock_is_valid.return_value = False
310
+
311
+ with pytest.raises(ValueError):
312
+ converter.convert("invalid", "test", "userstory")