ara-cli 0.1.9.69__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 (150) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +248 -62
  3. ara_cli/ara_command_action.py +155 -86
  4. ara_cli/ara_config.py +226 -80
  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 +649 -68
  28. ara_cli/artefact_creator.py +8 -11
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +22 -10
  31. ara_cli/artefact_link_updater.py +4 -4
  32. ara_cli/artefact_lister.py +29 -55
  33. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  34. ara_cli/artefact_models/artefact_load.py +11 -3
  35. ara_cli/artefact_models/artefact_model.py +146 -39
  36. ara_cli/artefact_models/artefact_templates.py +70 -44
  37. ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
  38. ara_cli/artefact_models/epic_artefact_model.py +34 -26
  39. ara_cli/artefact_models/feature_artefact_model.py +203 -64
  40. ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
  41. ara_cli/artefact_models/serialize_helper.py +1 -1
  42. ara_cli/artefact_models/task_artefact_model.py +83 -15
  43. ara_cli/artefact_models/userstory_artefact_model.py +37 -27
  44. ara_cli/artefact_models/vision_artefact_model.py +23 -42
  45. ara_cli/artefact_reader.py +92 -91
  46. ara_cli/artefact_renamer.py +8 -4
  47. ara_cli/artefact_scan.py +66 -3
  48. ara_cli/chat.py +622 -162
  49. ara_cli/chat_agent/__init__.py +0 -0
  50. ara_cli/chat_agent/agent_communicator.py +62 -0
  51. ara_cli/chat_agent/agent_process_manager.py +211 -0
  52. ara_cli/chat_agent/agent_status_manager.py +73 -0
  53. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  54. ara_cli/commands/__init__.py +0 -0
  55. ara_cli/commands/command.py +7 -0
  56. ara_cli/commands/extract_command.py +15 -0
  57. ara_cli/commands/load_command.py +65 -0
  58. ara_cli/commands/load_image_command.py +34 -0
  59. ara_cli/commands/read_command.py +117 -0
  60. ara_cli/completers.py +144 -0
  61. ara_cli/directory_navigator.py +37 -4
  62. ara_cli/error_handler.py +134 -0
  63. ara_cli/file_classifier.py +6 -5
  64. ara_cli/file_lister.py +1 -1
  65. ara_cli/file_loaders/__init__.py +0 -0
  66. ara_cli/file_loaders/binary_file_loader.py +33 -0
  67. ara_cli/file_loaders/document_file_loader.py +34 -0
  68. ara_cli/file_loaders/document_reader.py +245 -0
  69. ara_cli/file_loaders/document_readers.py +233 -0
  70. ara_cli/file_loaders/file_loader.py +50 -0
  71. ara_cli/file_loaders/file_loaders.py +123 -0
  72. ara_cli/file_loaders/image_processor.py +89 -0
  73. ara_cli/file_loaders/markdown_reader.py +75 -0
  74. ara_cli/file_loaders/text_file_loader.py +187 -0
  75. ara_cli/global_file_lister.py +51 -0
  76. ara_cli/list_filter.py +1 -1
  77. ara_cli/output_suppressor.py +1 -1
  78. ara_cli/prompt_extractor.py +215 -88
  79. ara_cli/prompt_handler.py +521 -134
  80. ara_cli/prompt_rag.py +2 -2
  81. ara_cli/tag_extractor.py +83 -38
  82. ara_cli/template_loader.py +245 -0
  83. ara_cli/template_manager.py +18 -13
  84. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  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/prompt_template_tech_stack_transformer.commands.md +95 -0
  91. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  92. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  93. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  94. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  95. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  96. ara_cli/update_config_prompt.py +9 -3
  97. ara_cli/version.py +1 -1
  98. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  99. ara_cli-0.1.10.8.dist-info/RECORD +193 -0
  100. tests/test_ara_command_action.py +73 -59
  101. tests/test_ara_config.py +341 -36
  102. tests/test_artefact_autofix.py +1060 -0
  103. tests/test_artefact_link_updater.py +3 -3
  104. tests/test_artefact_lister.py +52 -132
  105. tests/test_artefact_renamer.py +2 -2
  106. tests/test_artefact_scan.py +327 -33
  107. tests/test_chat.py +2063 -498
  108. tests/test_file_classifier.py +24 -1
  109. tests/test_file_creator.py +3 -5
  110. tests/test_file_lister.py +1 -1
  111. tests/test_global_file_lister.py +131 -0
  112. tests/test_list_filter.py +2 -2
  113. tests/test_prompt_handler.py +746 -0
  114. tests/test_tag_extractor.py +19 -13
  115. tests/test_template_loader.py +192 -0
  116. tests/test_template_manager.py +5 -4
  117. tests/test_update_config_prompt.py +2 -2
  118. ara_cli/ara_command_parser.py +0 -327
  119. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  120. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  121. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  122. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  123. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  124. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  125. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  126. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  127. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  128. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  129. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  130. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  131. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  132. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  133. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  134. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  135. ara_cli/templates/template.businessgoal +0 -10
  136. ara_cli/templates/template.capability +0 -10
  137. ara_cli/templates/template.epic +0 -15
  138. ara_cli/templates/template.example +0 -6
  139. ara_cli/templates/template.feature +0 -26
  140. ara_cli/templates/template.issue +0 -14
  141. ara_cli/templates/template.keyfeature +0 -15
  142. ara_cli/templates/template.task +0 -6
  143. ara_cli/templates/template.userstory +0 -17
  144. ara_cli/templates/template.vision +0 -14
  145. ara_cli-0.1.9.69.dist-info/METADATA +0 -16
  146. ara_cli-0.1.9.69.dist-info/RECORD +0 -158
  147. tests/test_ara_autofix.py +0 -219
  148. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  149. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  150. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
ara_cli/prompt_rag.py CHANGED
@@ -16,7 +16,7 @@ def find_files_in_prompt_config_givens(search_file, prompt_givens_file_path):
16
16
  header_stack = []
17
17
  modified_lines = [] # To store the modified file content
18
18
 
19
- with open(prompt_givens_file_path, 'r') as file:
19
+ with open(prompt_givens_file_path, 'r', encoding='utf-8') as file:
20
20
  for line in file:
21
21
  if line.strip().startswith('#'):
22
22
  level = line.count('#')
@@ -37,7 +37,7 @@ def find_files_in_prompt_config_givens(search_file, prompt_givens_file_path):
37
37
 
38
38
  if file_found:
39
39
  # Rewrite the file with the modified content if any line was changed
40
- with open(prompt_givens_file_path, 'w') as file:
40
+ with open(prompt_givens_file_path, 'w', encoding='utf-8') as file:
41
41
  file.writelines(modified_lines)
42
42
 
43
43
  return file_found
ara_cli/tag_extractor.py CHANGED
@@ -1,17 +1,89 @@
1
1
  import os
2
2
  from ara_cli.list_filter import ListFilter, filter_list
3
- from ara_cli.artefact_lister import ArtefactLister
4
-
3
+ from ara_cli.artefact_models.artefact_data_retrieval import (
4
+ artefact_content_retrieval,
5
+ artefact_path_retrieval,
6
+ artefact_tags_retrieval,
7
+ )
5
8
 
6
9
  class TagExtractor:
7
10
  def __init__(self, file_system=None):
8
11
  self.file_system = file_system or os
9
12
 
13
+ def filter_column(self, tag_groups, filtered_artefacts):
14
+ status_tags = {"to-do", "in-progress", "review", "done", "closed"}
15
+
16
+ artefacts_to_process = self._get_artefacts_without_status_tags(
17
+ filtered_artefacts, status_tags
18
+ )
19
+ self._add_non_status_tags_to_set(tag_groups, artefacts_to_process, status_tags)
20
+
21
+ def _get_artefacts_without_status_tags(self, filtered_artefacts, status_tags):
22
+ artefacts_to_process = []
23
+ for artefact_list in filtered_artefacts.values():
24
+ for artefact in artefact_list:
25
+ tag_set = self._get_tag_set(artefact)
26
+ if not tag_set & status_tags:
27
+ artefacts_to_process.append(artefact)
28
+ return artefacts_to_process
29
+
30
+ def _get_tag_set(self, artefact):
31
+ tags = artefact.tags + [artefact.status] if artefact.status else artefact.tags
32
+ return set(tag for tag in tags if tag is not None)
33
+
34
+ def _add_non_status_tags_to_set(self, tag_groups, artefacts, status_tags):
35
+ for artefact in artefacts:
36
+ tags = [
37
+ tag for tag in (artefact.tags + [artefact.status]) if tag is not None
38
+ ]
39
+ for tag in tags:
40
+ if self._is_skipped_tag(tag, status_tags):
41
+ continue
42
+ key = tag.lower()
43
+ if key not in tag_groups:
44
+ tag_groups[key] = set()
45
+ tag_groups[key].add(tag)
46
+
47
+ def _is_skipped_tag(self, tag, status_tags):
48
+ return (
49
+ tag in status_tags or tag.startswith("priority_") or tag.startswith("user_")
50
+ )
51
+
52
+ def _collect_all_tags(self, artefact):
53
+ """Collect all tags from an artefact including user tags and author."""
54
+ all_tags = []
55
+ all_tags.extend(artefact.tags)
56
+
57
+ if artefact.status:
58
+ all_tags.append(artefact.status)
59
+
60
+ user_tags = [f"user_{tag}" for tag in artefact.users]
61
+ all_tags.extend(user_tags)
62
+
63
+ if hasattr(artefact, 'author') and artefact.author:
64
+ all_tags.append(artefact.author)
65
+
66
+ return [tag for tag in all_tags if tag is not None]
67
+
68
+ def _add_tags_to_groups(self, tag_groups, tags):
69
+ """Add tags to tag groups."""
70
+ for tag in tags:
71
+ key = tag.lower()
72
+ if key not in tag_groups:
73
+ tag_groups[key] = set()
74
+ tag_groups[key].add(tag)
75
+
76
+ def add_to_tags_set(self, tag_groups, filtered_artefacts):
77
+ for artefact_list in filtered_artefacts.values():
78
+ for artefact in artefact_list:
79
+ all_tags = self._collect_all_tags(artefact)
80
+ self._add_tags_to_groups(tag_groups, all_tags)
81
+
10
82
  def extract_tags(
11
83
  self,
12
84
  navigate_to_target=False,
13
85
  filtered_extra_column=False,
14
- list_filter: ListFilter | None = None
86
+ list_filter: ListFilter | None = None,
15
87
  ):
16
88
  from ara_cli.template_manager import DirectoryNavigator
17
89
  from ara_cli.artefact_reader import ArtefactReader
@@ -25,43 +97,16 @@ class TagExtractor:
25
97
  filtered_artefacts = filter_list(
26
98
  list_to_filter=artefacts,
27
99
  list_filter=list_filter,
28
- content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
29
- file_path_retrieval=ArtefactLister.artefact_path_retrieval,
30
- tag_retrieval=ArtefactLister.artefact_tags_retrieval
100
+ content_retrieval_strategy=artefact_content_retrieval,
101
+ file_path_retrieval=artefact_path_retrieval,
102
+ tag_retrieval=artefact_tags_retrieval,
31
103
  )
32
104
 
33
- unique_tags = set()
105
+ tag_groups = {}
34
106
 
35
107
  if filtered_extra_column:
36
- status_tags = {"to-do", "in-progress", "review", "done", "closed"}
37
- artefacts_to_process = []
38
-
39
- for artefact_list in filtered_artefacts.values():
40
- for artefact in artefact_list:
41
- tags = artefact.tags + \
42
- [artefact.status] if artefact.status else artefact.tags
43
- tag_set = set(tag for tag in tags if tag is not None)
44
- if not tag_set & status_tags:
45
- artefacts_to_process.append(artefact)
46
-
47
- for artefact in artefacts_to_process:
48
- tags = [tag for tag in (
49
- artefact.tags + [artefact.status]) if tag is not None]
50
- for tag in tags:
51
- if (
52
- tag in status_tags
53
- or tag.startswith("priority_")
54
- or tag.startswith("user_")
55
- ):
56
- continue
57
- unique_tags.add(tag)
58
-
108
+ self.filter_column(tag_groups, filtered_artefacts)
59
109
  else:
60
- for artefact_list in filtered_artefacts.values():
61
- for artefact in artefact_list:
62
- user_tags = [f"user_{tag}" for tag in artefact.users]
63
- tags = [tag for tag in (artefact.tags + [artefact.status] + user_tags) if tag is not None]
64
- unique_tags.update(tags)
65
-
66
- sorted_tags = sorted(unique_tags)
67
- return sorted_tags
110
+ self.add_to_tags_set(tag_groups, filtered_artefacts)
111
+
112
+ return tag_groups
@@ -0,0 +1,245 @@
1
+
2
+ import os
3
+ import glob
4
+ from ara_cli.template_manager import TemplatePathManager
5
+ from ara_cli.ara_config import ConfigManager
6
+ from ara_cli.directory_navigator import DirectoryNavigator
7
+
8
+
9
+ class TemplateLoader:
10
+ """Handles template loading logic shared between CLI and chat commands"""
11
+
12
+ def __init__(self, chat_instance=None):
13
+ self.chat_instance = chat_instance
14
+
15
+ def load_template(self, template_name: str, template_type: str, chat_file_path: str, default_pattern: str | None = None) -> bool:
16
+ if not template_name:
17
+ if default_pattern:
18
+ return self.load_template_from_prompt_data(template_type, default_pattern, chat_file_path)
19
+ else:
20
+ print(f"A template name is required for template type '{template_type}'.")
21
+ return False
22
+ return self.load_template_from_global_or_local(template_name, template_type, chat_file_path)
23
+
24
+ def get_plural_template_type(self, template_type: str) -> str:
25
+ """Determines the plural form of a template type."""
26
+ plurals = {"commands": "commands", "rules": "rules"}
27
+ return plurals.get(template_type, f"{template_type}s")
28
+
29
+ def load_template_from_global_or_local(self, template_name: str, template_type: str, chat_file_path: str) -> bool:
30
+ """Load template from global or local directories"""
31
+ plural = self.get_plural_template_type(template_type)
32
+
33
+ if template_name.startswith("global/"):
34
+ return self._load_global_template(template_name, template_type, plural, chat_file_path)
35
+ else:
36
+ return self._load_local_template(template_name, template_type, plural, chat_file_path)
37
+
38
+ def _choose_file_for_cli(self, files: list[str], pattern: str) -> str | None:
39
+ """CLI-compatible file selection method"""
40
+ if len(files) <= 1:
41
+ return files[0] if files else None
42
+
43
+ if pattern in ["*", "global/*"] or "*" in pattern:
44
+ files.sort()
45
+ print("Multiple files found:")
46
+ for i, file in enumerate(files):
47
+ print(f"{i + 1}: {os.path.basename(file)}")
48
+
49
+ try:
50
+ choice = input("Please choose a file to load (enter number): ")
51
+ choice_index = int(choice) - 1
52
+ if 0 <= choice_index < len(files):
53
+ return files[choice_index]
54
+ else:
55
+ print("Invalid choice. Aborting load.")
56
+ return None
57
+ except (ValueError, KeyboardInterrupt):
58
+ print("Invalid input. Aborting load.")
59
+ return None
60
+ else:
61
+ return files[0]
62
+
63
+ def _load_global_template(self, template_name: str, template_type: str, plural: str, chat_file_path: str) -> bool:
64
+ """Load template from global directory"""
65
+ directory = f"{TemplatePathManager.get_template_base_path()}/prompt-modules/{plural}/"
66
+ template_file = template_name.removeprefix("global/")
67
+ file_pattern = os.path.join(directory, template_file)
68
+ matching_files = glob.glob(file_pattern)
69
+
70
+ if not matching_files:
71
+ print(f"No {template_type} template '{template_file}' found in global templates.")
72
+ return False
73
+
74
+ # Choose file based on context
75
+ if self.chat_instance:
76
+ file_path = self.chat_instance.choose_file_to_load(matching_files, template_file)
77
+ else:
78
+ file_path = self._choose_file_for_cli(matching_files, template_file)
79
+
80
+ if file_path is None:
81
+ return False
82
+
83
+ return self._load_file_to_chat(file_path, template_type, chat_file_path)
84
+
85
+ def _load_local_template(self, template_name: str, template_type: str, plural: str, chat_file_path: str) -> bool:
86
+ """Load template from local custom directory"""
87
+ ara_config = ConfigManager.get_config()
88
+ navigator = DirectoryNavigator()
89
+
90
+ original_directory = os.getcwd()
91
+ navigator.navigate_to_target()
92
+ local_templates_path = ara_config.local_prompt_templates_dir
93
+ os.chdir("..")
94
+ local_templates_path = os.path.join(os.getcwd(), local_templates_path)
95
+ os.chdir(original_directory)
96
+
97
+ custom_prompt_templates_subdir = ara_config.custom_prompt_templates_subdir
98
+ template_directory = f"{local_templates_path}/{custom_prompt_templates_subdir}/{plural}"
99
+ file_pattern = os.path.join(template_directory, template_name)
100
+ matching_files = glob.glob(file_pattern)
101
+
102
+ if not matching_files:
103
+ print(f"No {template_type} template '{template_name}' found in local templates.")
104
+ return False
105
+
106
+ # Choose file based on context
107
+ if self.chat_instance:
108
+ file_path = self.chat_instance.choose_file_to_load(matching_files, template_name)
109
+ else:
110
+ file_path = self._choose_file_for_cli(matching_files, template_name)
111
+
112
+ if file_path is None:
113
+ return False
114
+
115
+ return self._load_file_to_chat(file_path, template_type, chat_file_path)
116
+
117
+ def load_template_from_prompt_data(self, template_type: str, default_pattern: str, chat_file_path: str) -> bool:
118
+ """Load template from prompt.data directory with selection"""
119
+ directory_path = os.path.join(os.path.dirname(chat_file_path), "prompt.data")
120
+ file_pattern = os.path.join(directory_path, default_pattern)
121
+ matching_files = glob.glob(file_pattern)
122
+
123
+ if not matching_files:
124
+ print(f"No {template_type} file found in prompt.data directory.")
125
+ return False
126
+
127
+ # Choose file based on context
128
+ if self.chat_instance:
129
+ file_path = self.chat_instance.choose_file_to_load(matching_files, default_pattern)
130
+ else:
131
+ file_path = self._choose_file_for_cli(matching_files, "*")
132
+
133
+ if file_path is None:
134
+ return False
135
+
136
+ return self._load_file_to_chat(file_path, template_type, chat_file_path)
137
+
138
+ def _load_file_to_chat(self, file_path: str, template_type: str, chat_file_path: str) -> bool:
139
+ """Load a file into the chat file"""
140
+ if self.chat_instance:
141
+ # Use chat instance methods
142
+ self.chat_instance.add_prompt_tag_if_needed(chat_file_path)
143
+ if self.chat_instance.load_file(file_path):
144
+ print(f"Loaded {template_type} from {os.path.basename(file_path)} into {os.path.basename(chat_file_path)}")
145
+ return True
146
+ else:
147
+ # Direct file loading for CLI usage
148
+ try:
149
+ with open(file_path, 'r', encoding='utf-8') as template_file:
150
+ template_content = template_file.read().replace('\r\n', '\n')
151
+
152
+ # Add prompt tag if needed
153
+ self._add_prompt_tag_if_needed(chat_file_path)
154
+
155
+ # Append template content with newlines for separation
156
+ with open(chat_file_path, 'a', encoding='utf-8') as chat_file:
157
+ chat_file.write(f"\n{template_content}\n")
158
+
159
+ print(f"Loaded {template_type} from {os.path.basename(file_path)} into {os.path.basename(chat_file_path)}")
160
+ return True
161
+ except Exception as e:
162
+ print(f"Error loading {template_type} from {file_path}: {e}")
163
+ return False
164
+
165
+ return False
166
+
167
+ def _add_prompt_tag_if_needed(self, chat_file_path: str):
168
+ """Add prompt tag if needed for CLI usage"""
169
+ from ara_cli.chat import Chat
170
+
171
+ with open(chat_file_path, 'r', encoding='utf-8') as file:
172
+ lines = file.readlines()
173
+
174
+ prompt_tag = f"# {Chat.ROLE_PROMPT}:"
175
+ if Chat.get_last_role_marker(lines) == prompt_tag:
176
+ return
177
+
178
+ append = prompt_tag
179
+ if lines:
180
+ last_line = lines[-1].strip()
181
+ if last_line != "" and last_line != '\n':
182
+ append = f"\n{append}"
183
+
184
+ with open(chat_file_path, 'a', encoding='utf-8') as file:
185
+ file.write(append)
186
+
187
+ def _find_project_root(self, start_path: str) -> str | None:
188
+ """
189
+ Finds the project root by searching for an 'ara' directory,
190
+ starting from the given path and moving upwards.
191
+ """
192
+ current_dir = start_path
193
+ while True:
194
+ if os.path.isdir(os.path.join(current_dir, 'ara')):
195
+ return current_dir
196
+ parent_dir = os.path.dirname(current_dir)
197
+ if parent_dir == current_dir: # Reached the filesystem root
198
+ return None
199
+ current_dir = parent_dir
200
+
201
+ def _gather_templates_from_path(self, search_path: str, templates_set: set, prefix: str = ""):
202
+ """
203
+ Scans a given path for items and adds them to the provided set,
204
+ optionally prepending a prefix.
205
+ """
206
+ if not os.path.isdir(search_path):
207
+ return
208
+ for path in glob.glob(os.path.join(search_path, '*')):
209
+ templates_set.add(f"{prefix}{os.path.basename(path)}")
210
+
211
+ def get_available_templates(self, template_type: str, context_path: str) -> list[str]:
212
+ """
213
+ Scans for available global and project-local custom templates.
214
+ This method safely searches for template files without changing the
215
+ current directory, making it safe for use in autocompleters.
216
+ Args:
217
+ template_type: The type of template to search for (e.g., 'rules').
218
+ context_path: The directory path to start the search for project root from.
219
+ Returns:
220
+ A sorted list of unique template names. Global templates are
221
+ prefixed with 'global/'.
222
+ """
223
+ plural_type = self.get_plural_template_type(template_type)
224
+ templates = set()
225
+
226
+ # 1. Find Global Templates
227
+ try:
228
+ global_base_path = TemplatePathManager.get_template_base_path()
229
+ global_template_dir = os.path.join(global_base_path, "prompt-modules", plural_type)
230
+ self._gather_templates_from_path(global_template_dir, templates, prefix="global/")
231
+ except Exception:
232
+ pass # Silently ignore if global templates are not found
233
+
234
+ # 2. Find Local Custom Templates
235
+ try:
236
+ project_root = self._find_project_root(context_path)
237
+ if project_root:
238
+ config = ConfigManager.get_config()
239
+ local_templates_base = os.path.join(project_root, config.local_prompt_templates_dir)
240
+ custom_dir = os.path.join(local_templates_base, config.custom_prompt_templates_subdir, plural_type)
241
+ self._gather_templates_from_path(custom_dir, templates)
242
+ except Exception:
243
+ pass # Silently ignore if local templates cannot be resolved
244
+
245
+ return sorted(list(templates))
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
  from shutil import copy
5
5
  from ara_cli.classifier import Classifier
6
6
  from ara_cli.directory_navigator import DirectoryNavigator
7
+ from ara_cli.artefact_models.artefact_templates import template_artefact_of_type
7
8
 
8
9
 
9
10
  class TemplatePathManager:
@@ -30,14 +31,8 @@ class TemplatePathManager:
30
31
  ]
31
32
 
32
33
  def get_template_content(self, classifier):
33
- base_path = self.get_template_base_path()
34
-
35
- template_path = (base_path / f"template.{classifier}")
36
-
37
- with template_path.open('r') as file:
38
- content = file.read()
39
-
40
- return content
34
+ artefact = template_artefact_of_type(classifier)
35
+ return artefact.serialize()
41
36
 
42
37
 
43
38
  class ArtefactFileManager:
@@ -113,7 +108,7 @@ class ArtefactFileManager:
113
108
  def save_behave_steps_to_file(self, artefact_name, behave_steps):
114
109
  self.navigator.navigate_to_target()
115
110
  file_path = f"features/steps/{artefact_name}_steps.py"
116
- with open(file_path, 'w') as file:
111
+ with open(file_path, 'w', encoding='utf-8') as file:
117
112
  file.write(behave_steps)
118
113
 
119
114
 
@@ -134,6 +129,7 @@ class SpecificationBreakdownAspects:
134
129
  if aspect not in self.VALID_ASPECTS:
135
130
  raise ValueError(f"{aspect} does not exist. Please choose one of the {self.VALID_ASPECTS} list.")
136
131
 
132
+
137
133
  def create(self, artefact_name='artefact_name', classifier='classifier', aspect='specification_breakdown_aspect'):
138
134
  original_directory = os.getcwd()
139
135
  navigator = DirectoryNavigator()
@@ -144,8 +140,17 @@ class SpecificationBreakdownAspects:
144
140
  data_dir = self.file_manager.get_data_directory_path(artefact_name, classifier)
145
141
  self.file_manager.create_directory(artefact_file_path, data_dir)
146
142
  self.file_manager.copy_aspect_templates_to_directory(aspect, print_relative_to=original_directory)
143
+
147
144
  if (aspect == "step"):
148
- steps = self.file_manager.generate_behave_steps(artefact_name)
149
- self.file_manager.save_behave_steps_to_file(artefact_name, steps)
150
-
151
- os.chdir(original_directory)
145
+ # Instead of generating from behave command, read from the template file
146
+ template_file_path = f"{aspect}.md"
147
+ try:
148
+ with open(template_file_path, 'r', encoding='utf-8') as file:
149
+ steps_content = file.read()
150
+ self.file_manager.save_behave_steps_to_file(artefact_name, steps_content)
151
+ except FileNotFoundError:
152
+ # Fallback to the original behavior if template doesn't exist
153
+ steps = self.file_manager.generate_behave_steps(artefact_name)
154
+ self.file_manager.save_behave_steps_to_file(artefact_name, steps)
155
+
156
+ os.chdir(original_directory)
@@ -1,14 +1,4 @@
1
1
  ### COMMANDS FOR ...
2
2
  Your job is now:
3
- * remember you are strictly following your given RULES AS EXPERT <role defined in rules file> for code and architectural code quality
4
- * ...
5
-
6
- * return your results in the following format, ensuring your generated code blocks are not just code snippets but at complete method levels. Use for every single generated code block this format:
7
- ```python
8
- # [ ] extract
9
- # filename: {path/filename}.py
10
- {python code}
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 correctly fullfill the job!" and then explain what kind of information you think is missing and how I could easily retrieve it.
3
+ * <main list for mandotory commands>
4
+ * ...
@@ -0,0 +1,12 @@
1
+ # general file generation and file extract instructions
2
+ * return only full copy pastable file content using this markdown codeblock format with 5 backticks:
3
+ `````
4
+ # [ ] extract
5
+ # filename: <absolute filepath>/<filename>.<file_extension>
6
+ {valid file content depending on the given file_extension}
7
+ `````
8
+
9
+ * The extract and filename statements are only allowed once per markdown code block
10
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
11
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
12
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,11 @@
1
+ # general markdown file generation and file extract instructions
2
+ * return full copy pastable file content using a markdown code block with 5-backticks:
3
+ `````
4
+ # [ ] extract
5
+ # filename: <filepath>/<filename>.md
6
+ {markdown formatted text}
7
+ `````
8
+ * The extract and filename statements are only allowed once per markdown code block
9
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
10
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
11
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,13 @@
1
+ # general python code file generation and file extract instructions
2
+ * return only full copy pastable file content using this markdown codeblock format:
3
+
4
+ ```python
5
+ # [ ] extract
6
+ # filename: src/{filename}.py
7
+ {python code}
8
+
9
+ ```
10
+ * The extract and filename statements are only allowed once per markdown code block
11
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
12
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
13
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,36 @@
1
+ # COMMANDS FOR ADDING OR MODIFYING EXISTING SPECIFIED BEHAVIOR
2
+
3
+ **At first**:
4
+
5
+ Check if a set of feature files is given
6
+
7
+ * In case no feature files are given:
8
+ * Stop immediately and respond with: "Error in prompt context: no feature files are given as already specified application behavior"
9
+
10
+ * Else:
11
+ * Continue following the given instructions
12
+
13
+ # Instructions
14
+ Your job is now:
15
+ * Silently analyze the given feature files and the specified behavior.
16
+ * Silently analyze the additionally given information about new wanted behavior or changes of existing behavior
17
+ * Develop adaptation strategies that minimize feature file changes with respect to any given already existing feature files, prefer reusing and adapting existing formulations/scenarios and steps over completely new formulations
18
+ * Now formulate to fully cover the new or changed behavior (one, two or many changed or new feature files)
19
+
20
+ Follow these feature file quality rules:
21
+ * Each feature file should not consist of more than max 3 scenarios, each feature file should follow the single responsibility principle as well as the feature file formulations should follow the separation of concerns of feature files that fully cover the human user observable behavior described in the specification notes. Consider in your formulation of the Gherkin feature files that, when implementing the graphical user interfaces, the full functionality of the Python package Streamlit can be utilized.
22
+ * Follow strictly the given feature file format in order to structure your feature files.
23
+ * You are allowed to use scenario outlines where useful. But in case they are not helpful in order to increase the readability you can just use standard scenario formulations.
24
+
25
+ * Wrap and return the formulated feature files as full copy pastable file content in the following format as markdown code block:
26
+
27
+ ```artefact
28
+ # [ ] extract
29
+ # filename: ara/features/{filename}.feature
30
+ {formulation, with the valid feature file structure following the given feature files as reference}
31
+ ```
32
+
33
+ * The extract and filename statements are only allowed once per markdown code block
34
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
35
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
36
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,53 @@
1
+ # COMMANDS FOR INITIALLY SPECIFYING APPLICATION BEHAVIOR USING FEATURE FILES
2
+
3
+ * Given a description of the wanted application behavior as bullet point list, specification document, ...
4
+
5
+ * And given this feature template with placeholders in <...>
6
+
7
+ ```
8
+ @creator_Egermeier
9
+ Feature: <descriptive title>
10
+
11
+ As a <user>
12
+ I want to <do something | need something>
13
+ So that <I can achieve something>
14
+
15
+ Contributes to <here comes your parent artefact> <here comes your classifier of the parent artefact>
16
+
17
+ Description: <further optional description to understand
18
+ the rule, no format defined, the example artefact is only a placeholder>
19
+
20
+ Scenario: <descriptive scenario title>
21
+ Given <precondition>
22
+ When <action>
23
+ Then <expected result>
24
+
25
+ Scenario Outline: <descriptive scenario title>
26
+ Given <precondition>
27
+ When <action>
28
+ Then <expected result>
29
+
30
+ Examples:
31
+ | descriptive scenario title | precondition | action | expected result |
32
+ | <example title 1> | <example precond. 1> | <example action 1> | <example result 1> |
33
+ | <example title 2> | <example precond. 2> | <example action 2> | <example result 2> |
34
+ ```
35
+
36
+ # Instructions
37
+ * Now formulate a set (one, two or many, each feature file should not consist of more than max 3 scenarios
38
+ * Each feature file should follow the single responsibility principle as well as the feature file formulations should follow the separation of concerns) of feature files that fully cover the human user observable behavior described in the specification notes.
39
+ * Consider in your formulation of the Gherkin feature files when specifying the behavior of graphical user interfaces: Describe the behavior of the graphical user interfaces so that I can clearly imagine both how they work and their visual look and feel.
40
+ * Follow strictly the given template format in order to structure your feature files. You are allowed to use scenario outlines where useful. But in case they are not helpful in order to increase the readability you can just use standard scenario formulations.
41
+
42
+ * Wrap and return the formulated feature files as full copy pastable file content in the following format as markdown code block:
43
+
44
+ ```artefact
45
+ # [ ] extract
46
+ # filename: ara/features/{filename}.feature
47
+ {formulation, with the valid feature file structure as given by the feature gherkin template}
48
+ ```
49
+
50
+ * The extract and filename statements are only allowed once per markdown code block
51
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
52
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
53
+ * in case of files get deprecated give me a list of files that can be safely deleted