ara-cli 0.1.9.77__py3-none-any.whl → 0.1.10.8__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.

Potentially problematic release.


This version of ara-cli might be problematic. Click here for more details.

Files changed (122) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +245 -66
  3. ara_cli/ara_command_action.py +128 -63
  4. ara_cli/ara_config.py +201 -177
  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/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +214 -28
  28. ara_cli/artefact_creator.py +5 -8
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +13 -6
  31. ara_cli/artefact_lister.py +29 -55
  32. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  33. ara_cli/artefact_models/artefact_model.py +106 -25
  34. ara_cli/artefact_models/artefact_templates.py +23 -13
  35. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  36. ara_cli/artefact_models/feature_artefact_model.py +56 -1
  37. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  38. ara_cli/artefact_reader.py +4 -5
  39. ara_cli/artefact_renamer.py +6 -2
  40. ara_cli/artefact_scan.py +2 -2
  41. ara_cli/chat.py +594 -219
  42. ara_cli/chat_agent/__init__.py +0 -0
  43. ara_cli/chat_agent/agent_communicator.py +62 -0
  44. ara_cli/chat_agent/agent_process_manager.py +211 -0
  45. ara_cli/chat_agent/agent_status_manager.py +73 -0
  46. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  47. ara_cli/commands/__init__.py +0 -0
  48. ara_cli/commands/command.py +7 -0
  49. ara_cli/commands/extract_command.py +15 -0
  50. ara_cli/commands/load_command.py +65 -0
  51. ara_cli/commands/load_image_command.py +34 -0
  52. ara_cli/commands/read_command.py +117 -0
  53. ara_cli/completers.py +144 -0
  54. ara_cli/directory_navigator.py +37 -4
  55. ara_cli/error_handler.py +134 -0
  56. ara_cli/file_classifier.py +3 -2
  57. ara_cli/file_loaders/__init__.py +0 -0
  58. ara_cli/file_loaders/binary_file_loader.py +33 -0
  59. ara_cli/file_loaders/document_file_loader.py +34 -0
  60. ara_cli/file_loaders/document_reader.py +245 -0
  61. ara_cli/file_loaders/document_readers.py +233 -0
  62. ara_cli/file_loaders/file_loader.py +50 -0
  63. ara_cli/file_loaders/file_loaders.py +123 -0
  64. ara_cli/file_loaders/image_processor.py +89 -0
  65. ara_cli/file_loaders/markdown_reader.py +75 -0
  66. ara_cli/file_loaders/text_file_loader.py +187 -0
  67. ara_cli/global_file_lister.py +51 -0
  68. ara_cli/prompt_extractor.py +214 -87
  69. ara_cli/prompt_handler.py +508 -146
  70. ara_cli/tag_extractor.py +54 -24
  71. ara_cli/template_loader.py +245 -0
  72. ara_cli/template_manager.py +14 -4
  73. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  74. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  75. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  76. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  77. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  78. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  79. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  80. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  81. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  82. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  83. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  84. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  85. ara_cli/update_config_prompt.py +7 -1
  86. ara_cli/version.py +1 -1
  87. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  88. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/RECORD +104 -59
  89. tests/test_ara_command_action.py +66 -52
  90. tests/test_ara_config.py +200 -279
  91. tests/test_artefact_autofix.py +361 -5
  92. tests/test_artefact_lister.py +52 -132
  93. tests/test_artefact_scan.py +1 -1
  94. tests/test_chat.py +2009 -603
  95. tests/test_file_classifier.py +23 -0
  96. tests/test_file_creator.py +3 -5
  97. tests/test_global_file_lister.py +131 -0
  98. tests/test_prompt_handler.py +746 -0
  99. tests/test_tag_extractor.py +19 -13
  100. tests/test_template_loader.py +192 -0
  101. tests/test_template_manager.py +5 -4
  102. ara_cli/ara_command_parser.py +0 -536
  103. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  104. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  105. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  106. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  107. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  108. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  109. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  110. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  111. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  112. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  113. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  114. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  115. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  116. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  117. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  118. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  119. ara_cli-0.1.9.77.dist-info/METADATA +0 -18
  120. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  121. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  122. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
@@ -8,10 +8,11 @@ from ara_cli.list_filter import ListFilter
8
8
  def artefact():
9
9
  """Fixture to create a mock artefact object."""
10
10
  class Artefact:
11
- def __init__(self, tags, status, users, path="dummy.md", content=""):
11
+ def __init__(self, tags, status, users, author="creator_unknown", path="dummy.md", content=""):
12
12
  self.tags = tags
13
13
  self.status = status
14
14
  self.users = users
15
+ self.author = author
15
16
  self.path = path
16
17
  self.content = content
17
18
  return Artefact
@@ -21,33 +22,33 @@ def artefact():
21
22
  (
22
23
  False, False, None,
23
24
  {'artefacts': [
24
- (['tag1', 'tag2'], 'in-progress', ['user1']),
25
- (['tag3'], 'done', ['user2'])
25
+ (['tag1', 'tag2'], 'in-progress', ['user1'], "creator_unknown"),
26
+ (['tag3'], 'done', ['user2'], "creator_unknown")
26
27
  ]},
27
- ['done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
28
+ ['creator_unknown', 'done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
28
29
  ),
29
30
  (
30
31
  False, True, None,
31
32
  {'artefacts': [
32
- (['project_a', 'priority_high'], None, ['user1']),
33
- (['feature_x'], 'done', ['user2'])
33
+ (['project_a', 'priority_high'], None, ['user1'], "creator_unknown"),
34
+ (['feature_x'], 'done', ['user2'], "creator_unknown")
34
35
  ]},
35
36
  ['project_a']
36
37
  ),
37
38
  (
38
- False, False, ListFilter(include_tags=['@kritik']),
39
+ False, False, ListFilter(include_tags=['kritik']),
39
40
  {'artefacts': [
40
- (['release', 'kritik'], 'review', ['dev1']),
41
- (['bugfix'], 'to-do', ['dev2'])
41
+ (['release', 'kritik'], 'review', ['dev1'], "creator_unknown"),
42
+ (['bugfix'], 'to-do', ['dev2'], "creator_unknown")
42
43
  ]},
43
- ['kritik', 'release', 'review', 'user_dev1']
44
+ ['creator_unknown', 'kritik', 'release', 'review', 'user_dev1']
44
45
  ),
45
46
  (
46
47
  True, False, None,
47
48
  {'artefacts': [
48
- (['tag3'], 'status2', ['user3'])
49
+ (['tag3'], 'status2', ['user3'], "creator_unknown")
49
50
  ]},
50
- ['status2', 'tag3', 'user_user3']
51
+ ['creator_unknown', 'status2', 'tag3', 'user_user3']
51
52
  ),
52
53
  (
53
54
  False, False, None,
@@ -80,4 +81,9 @@ def test_extract_tags(mock_directory_navigator, mock_artefact_reader, artefact,
80
81
 
81
82
  mock_artefact_reader.read_artefacts.assert_called_once()
82
83
 
83
- assert sorted(result) == sorted(expected_tags)
84
+ # Convert dictionary result to flat list for comparison
85
+ actual_tags = []
86
+ for group in result.values():
87
+ actual_tags.extend(group)
88
+
89
+ assert sorted(actual_tags) == sorted(expected_tags)
@@ -0,0 +1,192 @@
1
+ import os
2
+ import pytest
3
+ from unittest.mock import MagicMock, patch, mock_open
4
+ from ara_cli.template_loader import TemplateLoader
5
+
6
+
7
+ @pytest.fixture
8
+ def mock_chat_instance():
9
+ """Fixture for a mocked chat instance."""
10
+ mock = MagicMock()
11
+ mock.choose_file_to_load.return_value = "chosen_file.md"
12
+ mock.load_file.return_value = True
13
+ return mock
14
+
15
+
16
+ @pytest.fixture
17
+ def template_loader_cli():
18
+ """Fixture for a TemplateLoader in CLI mode."""
19
+ return TemplateLoader()
20
+
21
+
22
+ @pytest.fixture
23
+ def template_loader_chat(mock_chat_instance):
24
+ """Fixture for a TemplateLoader in chat mode."""
25
+ return TemplateLoader(chat_instance=mock_chat_instance)
26
+
27
+
28
+ def test_init(mock_chat_instance):
29
+ """Test the constructor."""
30
+ loader_cli = TemplateLoader()
31
+ assert loader_cli.chat_instance is None
32
+
33
+ loader_chat = TemplateLoader(chat_instance=mock_chat_instance)
34
+ assert loader_chat.chat_instance == mock_chat_instance
35
+
36
+
37
+ @pytest.mark.parametrize("template_name, default_pattern, expected_method_to_call", [
38
+ ("", "*.rules.md", "load_template_from_prompt_data"),
39
+ ("my_rule", "*.rules.md", "load_template_from_global_or_local"),
40
+ ])
41
+ def test_load_template_routing(template_loader_cli, template_name, default_pattern, expected_method_to_call):
42
+ """Test that load_template calls the correct downstream method based on inputs."""
43
+ with patch.object(TemplateLoader, 'load_template_from_prompt_data') as mock_from_prompt, \
44
+ patch.object(TemplateLoader, 'load_template_from_global_or_local') as mock_from_global_local:
45
+
46
+ template_loader_cli.load_template(
47
+ template_name, "rules", "chat.md", default_pattern)
48
+
49
+ if expected_method_to_call == "load_template_from_prompt_data":
50
+ mock_from_prompt.assert_called_once()
51
+ mock_from_global_local.assert_not_called()
52
+ else:
53
+ mock_from_prompt.assert_not_called()
54
+ mock_from_global_local.assert_called_once()
55
+
56
+
57
+ def test_load_template_no_name_no_pattern(template_loader_cli, capsys):
58
+ """Test load_template fails gracefully when no name or pattern is given."""
59
+ result = template_loader_cli.load_template("", "blueprint", "chat.md", None)
60
+ assert result is False
61
+ captured = capsys.readouterr()
62
+ assert "A template name is required for template type 'blueprint'" in captured.out
63
+
64
+
65
+ @pytest.mark.parametrize("template_type, expected_plural", [
66
+ ("rules", "rules"),
67
+ ("commands", "commands"),
68
+ ("intention", "intentions"),
69
+ ("blueprint", "blueprints"),
70
+ ("custom", "customs"),
71
+ ])
72
+ def test_get_plural_template_type(template_loader_cli, template_type, expected_plural):
73
+ """Test the pluralization of template types."""
74
+ assert template_loader_cli.get_plural_template_type(
75
+ template_type) == expected_plural
76
+
77
+
78
+ @pytest.mark.parametrize("template_name, expected_method_to_call", [
79
+ ("global/my_rule", "_load_global_template"),
80
+ ("my_rule", "_load_local_template"),
81
+ ])
82
+ def test_load_template_from_global_or_local_routing(template_loader_cli, template_name, expected_method_to_call):
83
+ """Test routing between global and local template loading."""
84
+ with patch.object(TemplateLoader, '_load_global_template') as mock_global, \
85
+ patch.object(TemplateLoader, '_load_local_template') as mock_local:
86
+
87
+ template_loader_cli.load_template_from_global_or_local(
88
+ template_name, "rules", "chat.md")
89
+
90
+ if expected_method_to_call == "_load_global_template":
91
+ mock_global.assert_called_once()
92
+ mock_local.assert_not_called()
93
+ else:
94
+ mock_global.assert_not_called()
95
+ mock_local.assert_called_once()
96
+
97
+
98
+ @pytest.mark.parametrize("files, pattern, user_input, expected_return", [
99
+ (["one.md"], "*.md", "", "one.md"),
100
+ ([], "*.md", "", None),
101
+ (["a.md", "b.md"], "*", "1", "a.md"),
102
+ (["a.md", "b.md"], "*", "2", "b.md"),
103
+ (["a.md", "b.md"], "*", "3", None),
104
+ (["a.md", "b.md"], "*", "invalid", None),
105
+ ])
106
+ def test_choose_file_for_cli(template_loader_cli, files, pattern, user_input, expected_return):
107
+ """Test the interactive file selection for the CLI."""
108
+ with patch('builtins.input', return_value=user_input):
109
+ result = template_loader_cli._choose_file_for_cli(files, pattern)
110
+ assert result == expected_return
111
+
112
+
113
+ def test_load_file_to_chat_cli_context(tmp_path):
114
+ """Test writing template content to a chat file in a CLI context."""
115
+ chat_file = tmp_path / "chat.md"
116
+ chat_file.write_text("# ara prompt:\n")
117
+ template_file = tmp_path / "template.md"
118
+ template_content = "This is the template content."
119
+ template_file.write_text(template_content)
120
+
121
+ loader = TemplateLoader()
122
+ result = loader._load_file_to_chat(
123
+ str(template_file), "rules", str(chat_file))
124
+
125
+ assert result is True
126
+ final_content = chat_file.read_text()
127
+ expected_content = f"# ara prompt:\n\n{template_content}\n"
128
+ assert final_content == expected_content
129
+
130
+
131
+ def test_load_file_to_chat_chat_context(template_loader_chat, mock_chat_instance):
132
+ """Test delegating file loading to the chat instance."""
133
+ result = template_loader_chat._load_file_to_chat(
134
+ "file.md", "rules", "chat.md")
135
+
136
+ assert result is True
137
+ mock_chat_instance.add_prompt_tag_if_needed.assert_called_once_with(
138
+ "chat.md")
139
+ mock_chat_instance.load_file.assert_called_once_with("file.md")
140
+
141
+
142
+ def test_find_project_root(tmp_path):
143
+ """Test finding the project root directory."""
144
+ project_root = tmp_path / "project"
145
+ ara_dir = project_root / "ara"
146
+ nested_dir = project_root / "src" / "component"
147
+ ara_dir.mkdir(parents=True)
148
+ nested_dir.mkdir(parents=True)
149
+
150
+ no_ara_dir = tmp_path / "other"
151
+ no_ara_dir.mkdir()
152
+
153
+ loader = TemplateLoader()
154
+
155
+ # Test finding the root from a nested directory
156
+ assert loader._find_project_root(str(nested_dir)) == str(project_root)
157
+ # Test finding the root from the root itself
158
+ assert loader._find_project_root(str(project_root)) == str(project_root)
159
+ # Test not finding the root
160
+ assert loader._find_project_root(str(no_ara_dir)) is None
161
+
162
+
163
+ @patch('ara_cli.template_loader.TemplatePathManager')
164
+ @patch('ara_cli.template_loader.ConfigManager')
165
+ def test_get_available_templates(MockConfigManager, MockTemplatePathManager, tmp_path):
166
+ """Test the discovery of global and local templates."""
167
+ # Setup mock paths and config
168
+ project_root = tmp_path / "project"
169
+ ara_dir = project_root / "ara"
170
+ araconfig_dir = project_root / ".araconfig"
171
+ custom_modules_dir = araconfig_dir / "custom-prompt-modules" / "rules"
172
+ global_modules_dir = tmp_path / "global_templates" / "prompt-modules" / "rules"
173
+
174
+ for d in [ara_dir, custom_modules_dir, global_modules_dir]:
175
+ d.mkdir(parents=True)
176
+
177
+ (custom_modules_dir / "local_rule.md").touch()
178
+ (global_modules_dir / "global_rule.md").touch()
179
+
180
+ mock_config = MagicMock()
181
+ mock_config.local_prompt_templates_dir = ".araconfig"
182
+ mock_config.custom_prompt_templates_subdir = "custom-prompt-modules"
183
+ MockConfigManager.get_config.return_value = mock_config
184
+ MockTemplatePathManager.get_template_base_path.return_value = str(
185
+ tmp_path / "global_templates")
186
+
187
+ loader = TemplateLoader()
188
+ templates = loader.get_available_templates(
189
+ "rules", context_path=str(project_root))
190
+
191
+ assert sorted(templates) == sorted(
192
+ ["global/global_rule.md", "local_rule.md"])
@@ -9,10 +9,11 @@ import os
9
9
 
10
10
  @pytest.fixture(autouse=True)
11
11
  def navigate_to_ara_directory():
12
- navigator = DirectoryNavigator("ara")
13
- original_directory = navigator.navigate_to_target()
14
- yield
15
- os.chdir(original_directory)
12
+ with patch('builtins.input', return_value='y'):
13
+ navigator = DirectoryNavigator("ara")
14
+ original_directory = navigator.navigate_to_target()
15
+ yield
16
+ os.chdir(original_directory)
16
17
 
17
18
 
18
19
  @pytest.mark.parametrize(