ara-cli 0.1.9.96__py3-none-any.whl → 0.1.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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()
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/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.9.96" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
2
+ __version__ = "0.1.10.0" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ara_cli
3
- Version: 0.1.9.96
3
+ Version: 0.1.10.0
4
4
  Summary: Powerful, open source command-line tool for managing, structuring and automating software development artifacts in line with Business-Driven Development (BDD) and AI-assisted processes
5
5
  Description-Content-Type: text/markdown
6
+ Requires-Dist: langfuse
6
7
  Requires-Dist: litellm
7
8
  Requires-Dist: llama-index
8
9
  Requires-Dist: llama-index-llms-openai
@@ -1,8 +1,8 @@
1
- ara_cli/__init__.py,sha256=Kozfk9i2DfbDi8GTkXjbqIoIxWXmbDhxb_edRv73oUU,495
2
- ara_cli/__main__.py,sha256=1GaJ2LJPOfAKZVPaKIkJ0x1L2p8yGMAgckzoRn_6JvA,3474
3
- ara_cli/ara_command_action.py,sha256=iPDiIaN60TEpVCnOT6Esestu0fNJFzbyhNENQytbkEo,22822
4
- ara_cli/ara_command_parser.py,sha256=A1lMc9Gc0EMJt-380PTcv3aKoxbXGfx5gGax-sZqV3I,21020
5
- ara_cli/ara_config.py,sha256=VJeage_v-446OtSXIfpazUbetpH7kGNv8Un1lKYx5ZE,9321
1
+ ara_cli/__init__.py,sha256=DuzXKimZ6JtUEnp48qCQcnojDflBtYjT6Na_twO5EzM,505
2
+ ara_cli/__main__.py,sha256=sumQaIYg6wJdcT-nQjJpK9DGaBUTszjPrgZLPN2bUVs,3520
3
+ ara_cli/ara_command_action.py,sha256=VJSReKfgUQk62DeMcY4EN228FvgF1m8NiHf-ckrigdA,24450
4
+ ara_cli/ara_command_parser.py,sha256=4_LXxj9w7eAY8d_s8pMlKmxtMr9cX9y2pD5azNeJjsg,22288
5
+ ara_cli/ara_config.py,sha256=vZsY2zYJdlSExRE84L5LqRH3DjveeuMSmG5fC8HDIVc,9794
6
6
  ara_cli/artefact_autofix.py,sha256=9j_bh0HGnN6HVT9OGKVp85VgDklpx3XpSc9MxBCldU4,25050
7
7
  ara_cli/artefact_creator.py,sha256=fRrDaGZvOqJqDb_DLXqMTed2XfIvQMIHjLgOuHOi3Qg,5973
8
8
  ara_cli/artefact_deleter.py,sha256=T1vS2s3k_BW86Sd8FExx8nC3BIL05xE9KZLkeZsZrKM,1891
@@ -12,7 +12,7 @@ ara_cli/artefact_lister.py,sha256=M-ggazAgZ-OLeW9NB48r_sd6zPx0p4hEpeS63qHwI1A,41
12
12
  ara_cli/artefact_reader.py,sha256=-6E1VhIlh2oJE1Rn8ARcHRc_E9N4uk8cEViKMoywm6E,7753
13
13
  ara_cli/artefact_renamer.py,sha256=8S4QWD19_FGKsKlWojnu_RUOxx0u9rmLugydM4s4VDc,4219
14
14
  ara_cli/artefact_scan.py,sha256=msPCm-vPWOAZ_e_z5GylXxq1MtNlmJ4zvKrsdOFCWF4,4813
15
- ara_cli/chat.py,sha256=SCllSt821naybvF6K4afJEtZvFZq7UO52O4fOm0Ieic,39859
15
+ ara_cli/chat.py,sha256=Vp1HzSaaUsfOldOjxJc0ff68lQJWiIet0LJ22jjMYUs,40776
16
16
  ara_cli/classifier.py,sha256=zWskj7rBYdqYBGjksBm46iTgVU5IIf2PZsJr4qeiwVU,1878
17
17
  ara_cli/codefusionretriever.py,sha256=fCHgXdIBRzkVAnapX-KI2NQ44XbrrF4tEQmn5J6clUI,1980
18
18
  ara_cli/codehierachieretriever.py,sha256=Xd3EgEWWhkSf1TmTWtf8X5_YvyE_4B66nRrqarwSiTU,1182
@@ -27,13 +27,14 @@ ara_cli/list_filter.py,sha256=qKGwwQsrWe7L5FbdxEbBYD1bbbi8c-RMypjXqXvLbgs,5291
27
27
  ara_cli/output_suppressor.py,sha256=nwiHaQLwabOjMoJOeUESBnZszGMxrQZfJ3N2OvahX7Y,389
28
28
  ara_cli/prompt_chat.py,sha256=kd_OINDQFit6jN04bb7mzgY259JBbRaTaNp9F-webkc,1346
29
29
  ara_cli/prompt_extractor.py,sha256=WloRgfcEdIVq37BpdWAd2X3EMu0bcNN_Wuws1T2YiUg,8418
30
- ara_cli/prompt_handler.py,sha256=8a9fcMwE_C6ntbw7UeroNJeU5LxrxEppiUtvYNUTB2U,23292
30
+ ara_cli/prompt_handler.py,sha256=N0zH5k9T6udKAbMolxEAaAwiFo03h5aF-IY3BmMLEos,27924
31
31
  ara_cli/prompt_rag.py,sha256=ydlhe4CUqz0jdzlY7jBbpKaf_5fjMrAZKnriKea3ZAg,7485
32
32
  ara_cli/run_file_lister.py,sha256=XbrrDTJXp1LFGx9Lv91SNsEHZPP-PyEMBF_P4btjbDA,2360
33
33
  ara_cli/tag_extractor.py,sha256=k2yRl7dAMZ4YTARzUke4wgY0oEIOmWkOHGet7nXB6uw,3317
34
+ ara_cli/template_loader.py,sha256=uEpYOchT5d-OO5r-W0-h605Xilvuv56i1VKSy4_9NaE,10734
34
35
  ara_cli/template_manager.py,sha256=l2c785YHB7m0e2TjE0CX-nwXrS4v3EiT9qrS5KuatAc,7105
35
36
  ara_cli/update_config_prompt.py,sha256=moqj2Kha7S7fEGzTReU0v2y8UjXC8QfnoiieOQr35C4,5157
36
- ara_cli/version.py,sha256=-IPlzWGTvEOC2B1-l8hULGEXt1iWj3_npgirLamZr10,146
37
+ ara_cli/version.py,sha256=AvikXK1EI29z3MY864MZ0oxZJZ4-tSdFYdogqQEkDac,146
37
38
  ara_cli/artefact_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  ara_cli/artefact_models/artefact_data_retrieval.py,sha256=CooXOJBYWSyiViN2xkC8baS8OUaslry3YGVVUeDxRAU,527
39
40
  ara_cli/artefact_models/artefact_load.py,sha256=IXzWxP-Q_j_oDGMno0m-OuXCQ7Vd5c_NctshGr4ROBw,621
@@ -150,7 +151,7 @@ tests/test_artefact_lister.py,sha256=35R13UU-YsX1HOsEN8M2-vIiCUA9RSBm6SwestDaFhE
150
151
  tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
151
152
  tests/test_artefact_renamer.py,sha256=lSnKCCfoFGgKhTdDZrEaeBq1xJAak1QoqH5aSeOe9Ro,3494
152
153
  tests/test_artefact_scan.py,sha256=uNWgrt7ieZ4ogKACsPqzAsh59JF2BhTKSag31hpVrTQ,16887
153
- tests/test_chat.py,sha256=UK1566DikkeXKzslw8h1it3UXqJEGLpxy02_Pch0Tfs,59433
154
+ tests/test_chat.py,sha256=D2HRRTdnvcDvB9TWz4O91ZONbLJL6w4v8TRTDwjtzzI,86104
154
155
  tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
155
156
  tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
156
157
  tests/test_file_classifier.py,sha256=4O1C_iDpGGm35b7aI-HIJd5kkWxFUOrI2n4lEpiDNTM,11855
@@ -158,12 +159,13 @@ tests/test_file_creator.py,sha256=tgBCq6KPv-qMSDhj9AZvQIJABiAqgpFRnEg1fqbVrTI,20
158
159
  tests/test_file_lister.py,sha256=Q9HwhKKx540EPzTmfzOCnvtAgON0aMmpJE2eOe1J3EA,4324
159
160
  tests/test_global_file_lister.py,sha256=ycvf2YL8q5QSEMwcnQfUdoWnQQ8xTSyEtccAeXwl6QU,5487
160
161
  tests/test_list_filter.py,sha256=fJA3d_SdaOAUkE7jn68MOVS0THXGghy1fye_64Zvo1U,7964
161
- tests/test_prompt_handler.py,sha256=kW8FU09ho4I5qC-f4G9r4ZgI-NlqdOkTmAazG7FaTrw,32299
162
+ tests/test_prompt_handler.py,sha256=9s1zavcW81uz8wOBM_2X2KqdLNoc3E9bt0Oqt2-Sgmk,33926
162
163
  tests/test_tag_extractor.py,sha256=nSiAYlTKZ7TLAOtcJpwK5zTWHhFYU0tI5xKnivLc1dU,2712
164
+ tests/test_template_loader.py,sha256=R7s8HJZbKqja-1TRBMBkVKPTgajofUjjRKUJq7a3_Oc,7427
163
165
  tests/test_template_manager.py,sha256=qliEeYgAEakn8JIqIHa8u0Ht6DY4L3T6DcHBXkjzR4I,4167
164
166
  tests/test_update_config_prompt.py,sha256=xsqj1WTn4BsG5Q2t-sNPfu7EoMURFcS-hfb5VSXUnJc,6765
165
- ara_cli-0.1.9.96.dist-info/METADATA,sha256=rwUSTke0fhW8QCSyel4M0oASJkfy6AASVOkQMzdPBDo,6789
166
- ara_cli-0.1.9.96.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
167
- ara_cli-0.1.9.96.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
168
- ara_cli-0.1.9.96.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
169
- ara_cli-0.1.9.96.dist-info/RECORD,,
167
+ ara_cli-0.1.10.0.dist-info/METADATA,sha256=9DcNT8mkoI9D6aDt0gRORWFl_MagH7yTw_ISo4uc_Tw,6813
168
+ ara_cli-0.1.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
169
+ ara_cli-0.1.10.0.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
170
+ ara_cli-0.1.10.0.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
171
+ ara_cli-0.1.10.0.dist-info/RECORD,,