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