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.
- ara_cli/__init__.py +18 -2
- ara_cli/__main__.py +248 -62
- ara_cli/ara_command_action.py +155 -86
- ara_cli/ara_config.py +226 -80
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch_templates.py +14 -0
- ara_cli/ara_subcommands/list.py +65 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +649 -68
- ara_cli/artefact_creator.py +8 -11
- ara_cli/artefact_deleter.py +2 -4
- ara_cli/artefact_fuzzy_search.py +22 -10
- ara_cli/artefact_link_updater.py +4 -4
- ara_cli/artefact_lister.py +29 -55
- ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
- ara_cli/artefact_models/artefact_load.py +11 -3
- ara_cli/artefact_models/artefact_model.py +146 -39
- ara_cli/artefact_models/artefact_templates.py +70 -44
- ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
- ara_cli/artefact_models/epic_artefact_model.py +34 -26
- ara_cli/artefact_models/feature_artefact_model.py +203 -64
- ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
- ara_cli/artefact_models/serialize_helper.py +1 -1
- ara_cli/artefact_models/task_artefact_model.py +83 -15
- ara_cli/artefact_models/userstory_artefact_model.py +37 -27
- ara_cli/artefact_models/vision_artefact_model.py +23 -42
- ara_cli/artefact_reader.py +92 -91
- ara_cli/artefact_renamer.py +8 -4
- ara_cli/artefact_scan.py +66 -3
- ara_cli/chat.py +622 -162
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_communicator.py +62 -0
- ara_cli/chat_agent/agent_process_manager.py +211 -0
- ara_cli/chat_agent/agent_status_manager.py +73 -0
- ara_cli/chat_agent/agent_workspace_manager.py +76 -0
- ara_cli/commands/__init__.py +0 -0
- ara_cli/commands/command.py +7 -0
- ara_cli/commands/extract_command.py +15 -0
- ara_cli/commands/load_command.py +65 -0
- ara_cli/commands/load_image_command.py +34 -0
- ara_cli/commands/read_command.py +117 -0
- ara_cli/completers.py +144 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +134 -0
- ara_cli/file_classifier.py +6 -5
- ara_cli/file_lister.py +1 -1
- ara_cli/file_loaders/__init__.py +0 -0
- ara_cli/file_loaders/binary_file_loader.py +33 -0
- ara_cli/file_loaders/document_file_loader.py +34 -0
- ara_cli/file_loaders/document_reader.py +245 -0
- ara_cli/file_loaders/document_readers.py +233 -0
- ara_cli/file_loaders/file_loader.py +50 -0
- ara_cli/file_loaders/file_loaders.py +123 -0
- ara_cli/file_loaders/image_processor.py +89 -0
- ara_cli/file_loaders/markdown_reader.py +75 -0
- ara_cli/file_loaders/text_file_loader.py +187 -0
- ara_cli/global_file_lister.py +51 -0
- ara_cli/list_filter.py +1 -1
- ara_cli/output_suppressor.py +1 -1
- ara_cli/prompt_extractor.py +215 -88
- ara_cli/prompt_handler.py +521 -134
- ara_cli/prompt_rag.py +2 -2
- ara_cli/tag_extractor.py +83 -38
- ara_cli/template_loader.py +245 -0
- ara_cli/template_manager.py +18 -13
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/update_config_prompt.py +9 -3
- ara_cli/version.py +1 -1
- ara_cli-0.1.10.8.dist-info/METADATA +241 -0
- ara_cli-0.1.10.8.dist-info/RECORD +193 -0
- tests/test_ara_command_action.py +73 -59
- tests/test_ara_config.py +341 -36
- tests/test_artefact_autofix.py +1060 -0
- tests/test_artefact_link_updater.py +3 -3
- tests/test_artefact_lister.py +52 -132
- tests/test_artefact_renamer.py +2 -2
- tests/test_artefact_scan.py +327 -33
- tests/test_chat.py +2063 -498
- tests/test_file_classifier.py +24 -1
- tests/test_file_creator.py +3 -5
- tests/test_file_lister.py +1 -1
- tests/test_global_file_lister.py +131 -0
- tests/test_list_filter.py +2 -2
- tests/test_prompt_handler.py +746 -0
- tests/test_tag_extractor.py +19 -13
- tests/test_template_loader.py +192 -0
- tests/test_template_manager.py +5 -4
- tests/test_update_config_prompt.py +2 -2
- ara_cli/ara_command_parser.py +0 -327
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- ara_cli/templates/template.businessgoal +0 -10
- ara_cli/templates/template.capability +0 -10
- ara_cli/templates/template.epic +0 -15
- ara_cli/templates/template.example +0 -6
- ara_cli/templates/template.feature +0 -26
- ara_cli/templates/template.issue +0 -14
- ara_cli/templates/template.keyfeature +0 -15
- ara_cli/templates/template.task +0 -6
- ara_cli/templates/template.userstory +0 -17
- ara_cli/templates/template.vision +0 -14
- ara_cli-0.1.9.69.dist-info/METADATA +0 -16
- ara_cli-0.1.9.69.dist-info/RECORD +0 -158
- tests/test_ara_autofix.py +0 -219
- {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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=
|
|
29
|
-
file_path_retrieval=
|
|
30
|
-
tag_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
|
-
|
|
105
|
+
tag_groups = {}
|
|
34
106
|
|
|
35
107
|
if filtered_extra_column:
|
|
36
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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))
|
ara_cli/template_manager.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
*
|
|
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
|
ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md
ADDED
|
@@ -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
|
ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md
ADDED
|
@@ -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
|