ara-cli 0.1.9.93__py3-none-any.whl → 0.1.9.94__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.
ara_cli/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from .version import __version__
2
2
 
3
- whitelisted_commands = ["RERUN", "SEND", "EXTRACT", "LOAD_IMAGE", "CHOOSE_MODEL", "CURRENT_MODEL", "LIST_MODELS"]
3
+ whitelisted_commands = ["RERUN", "SEND", "EXTRACT", "LOAD_IMAGE", "CHOOSE_MODEL", "CHOOSE_EXTRACTION_MODEL", "CURRENT_MODEL", "CURRENT_EXTRACTION_MODEL", "LIST_MODELS"]
@@ -165,6 +165,7 @@ def prompt_action(args):
165
165
  classifier = args.classifier
166
166
  param = args.parameter
167
167
  init = args.steps
168
+ write = getattr(args, 'write', False)
168
169
 
169
170
  def handle_init():
170
171
  from ara_cli.prompt_handler import initialize_prompt_templates
@@ -192,7 +193,7 @@ def prompt_action(args):
192
193
  def handle_extract():
193
194
  from ara_cli.prompt_extractor import extract_and_save_prompt_results
194
195
  from ara_cli.update_config_prompt import update_artefact_config_prompt_files
195
- extract_and_save_prompt_results(classifier, param)
196
+ extract_and_save_prompt_results(classifier, param, write=write)
196
197
  print(f"automatic update after extract")
197
198
  update_artefact_config_prompt_files(classifier, param, automatic_update=True)
198
199
 
@@ -294,53 +295,30 @@ def fetch_templates_action(args):
294
295
 
295
296
 
296
297
  def read_action(args):
297
- from ara_cli.artefact_reader import ArtefactReader
298
- from ara_cli.file_classifier import FileClassifier
298
+ from ara_cli.commands.read_command import ReadCommand
299
+ from ara_cli.list_filter import ListFilter
299
300
 
300
301
  classifier = args.classifier
301
302
  artefact_name = args.parameter
302
303
  read_mode = args.read_mode
303
304
 
304
- file_classifier = FileClassifier(os)
305
- classified_artefacts = ArtefactReader.read_artefacts()
306
- artefacts = classified_artefacts.get(classifier, [])
307
- all_artefact_names = [a.title for a in artefacts]
308
-
309
- if artefact_name not in all_artefact_names:
310
- suggest_close_name_matches(
311
- artefact_name,
312
- all_artefact_names
313
- )
314
- return
315
-
316
- target_artefact = next(filter(
317
- lambda x: x.title == artefact_name, artefacts
318
- ))
305
+ list_filter = ListFilter(
306
+ include_content=args.include_content,
307
+ exclude_content=args.exclude_content,
308
+ include_extension=args.include_extension,
309
+ exclude_extension=args.exclude_extension,
310
+ include_tags=args.include_tags,
311
+ exclude_tags=args.exclude_tags
312
+ )
319
313
 
320
- artefacts_by_classifier = {classifier: []}
314
+ command = ReadCommand(
315
+ classifier=classifier,
316
+ artefact_name=artefact_name,
317
+ read_mode=read_mode,
318
+ list_filter=list_filter
319
+ )
321
320
 
322
- match read_mode:
323
- case "branch":
324
- ArtefactReader.step_through_value_chain(
325
- artefact_name=artefact_name,
326
- classifier=classifier,
327
- artefacts_by_classifier=artefacts_by_classifier,
328
- classified_artefacts=classified_artefacts
329
- )
330
- file_classifier.print_classified_files(artefacts_by_classifier, print_content=True)
331
- case "children":
332
- artefacts = ArtefactReader.find_children(
333
- artefact_name=artefact_name,
334
- classifier=classifier,
335
- classified_artefacts=classified_artefacts
336
- )
337
- file_classifier.print_classified_files(
338
- files_by_classifier=artefacts,
339
- print_content=True
340
- )
341
- case _:
342
- artefacts_by_classifier[classifier].append(target_artefact)
343
- file_classifier.print_classified_files(artefacts_by_classifier, print_content=True)
321
+ command.execute()
344
322
 
345
323
 
346
324
  def reconnect_action(args):
@@ -615,11 +593,13 @@ def extract_action(args):
615
593
  from ara_cli.commands.extract_command import ExtractCommand
616
594
 
617
595
  filename = args.filename
618
- skip_queries = args.skip_queries
596
+ force = args.force
597
+ write = getattr(args, 'write', False)
619
598
  print(filename)
620
599
  command = ExtractCommand(
621
600
  file_name=filename,
622
- skip_queries=skip_queries,
601
+ force=force,
602
+ write=write,
623
603
  output=lambda msg: print(msg, file=sys.stdout),
624
604
  error_output=lambda msg: print(msg, file=sys.stderr)
625
605
  )
@@ -254,6 +254,13 @@ def prompt_parser(subparsers):
254
254
  ).completer = ArtefactCompleter()
255
255
  if step == "chat":
256
256
  add_chat_arguments(step_parser)
257
+ if step == "extract":
258
+ step_parser.add_argument(
259
+ "-w",
260
+ "--write",
261
+ action="store_true",
262
+ help="Overwrite existing files without using LLM for merging."
263
+ )
257
264
 
258
265
 
259
266
  def chat_parser(subparsers):
@@ -287,6 +294,8 @@ def read_parser(subparsers):
287
294
  "parameter", help="Filename of artefact"
288
295
  ).completer = ArtefactCompleter()
289
296
 
297
+ add_filter_flags(read_parser)
298
+
290
299
  branch_group = read_parser.add_mutually_exclusive_group()
291
300
  branch_group.add_argument(
292
301
  "-b",
@@ -429,10 +438,16 @@ def extract_parser(subparsers):
429
438
  help="Input file to extract from."
430
439
  )
431
440
  extract_parser.add_argument(
432
- "--skip-queries", "-s",
441
+ "--force", "-f",
433
442
  action="store_true",
434
443
  help="Answer queries with yes when extracting."
435
444
  )
445
+ extract_parser.add_argument(
446
+ "-w",
447
+ "--write",
448
+ action="store_true",
449
+ help="Overwrite existing files without using LLM for merging."
450
+ )
436
451
 
437
452
 
438
453
  class CustomHelpFormatter(argparse.HelpFormatter):
@@ -1,9 +1,13 @@
1
1
  from ara_cli.file_classifier import FileClassifier
2
2
  from ara_cli.artefact_reader import ArtefactReader
3
3
  from ara_cli.file_lister import list_files_in_directory
4
- from ara_cli.artefact_models.artefact_model import Artefact
5
4
  from ara_cli.list_filter import ListFilter, filter_list
6
5
  from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
6
+ from ara_cli.artefact_models.artefact_data_retrieval import (
7
+ artefact_content_retrieval,
8
+ artefact_path_retrieval,
9
+ artefact_tags_retrieval,
10
+ )
7
11
  import os
8
12
 
9
13
 
@@ -11,43 +15,18 @@ class ArtefactLister:
11
15
  def __init__(self, file_system=None):
12
16
  self.file_system = file_system or os
13
17
 
14
- @staticmethod
15
- def artefact_content_retrieval(artefact: Artefact):
16
- content = artefact.serialize()
17
- return content
18
-
19
- @staticmethod
20
- def artefact_path_retrieval(artefact: Artefact):
21
- return artefact.file_path
22
-
23
- @staticmethod
24
- def artefact_tags_retrieval(artefact: Artefact):
25
- final_tags = []
26
-
27
- if not artefact:
28
- return []
29
-
30
- final_tags.extend([f"user_{user}" for user in artefact.users])
31
- final_tags.append(artefact.status)
32
- final_tags.extend(artefact.tags)
33
-
34
- return final_tags
35
-
36
18
  def filter_artefacts(self, classified_files: list, list_filter: ListFilter):
37
19
  filtered_list = filter_list(
38
20
  list_to_filter=classified_files,
39
21
  list_filter=list_filter,
40
- content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
41
- file_path_retrieval=ArtefactLister.artefact_path_retrieval,
42
- tag_retrieval=ArtefactLister.artefact_tags_retrieval
22
+ content_retrieval_strategy=artefact_content_retrieval,
23
+ file_path_retrieval=artefact_path_retrieval,
24
+ tag_retrieval=artefact_tags_retrieval,
43
25
  )
44
26
  return filtered_list
45
27
 
46
28
  def list_files(
47
- self,
48
- tags=None,
49
- navigate_to_target=False,
50
- list_filter: ListFilter | None = None
29
+ self, tags=None, navigate_to_target=False, list_filter: ListFilter | None = None
51
30
  ):
52
31
  artefact_list = ArtefactReader.read_artefacts(tags=tags)
53
32
  artefact_list = self.filter_artefacts(artefact_list, list_filter)
@@ -60,20 +39,18 @@ class ArtefactLister:
60
39
  file_classifier.print_classified_files(filtered_artefact_list)
61
40
 
62
41
  def list_branch(
63
- self,
64
- classifier,
65
- artefact_name,
66
- list_filter: ListFilter | None = None
42
+ self, classifier, artefact_name, list_filter: ListFilter | None = None
67
43
  ):
68
44
  file_classifier = FileClassifier(os)
69
45
  classified_artefacts = file_classifier.classify_files()
70
46
  artefact_info = classified_artefacts.get(classifier, [])
71
- matching_artefact_info = [p for p in artefact_info if p["title"] == artefact_name]
47
+ matching_artefact_info = [
48
+ p for p in artefact_info if p["title"] == artefact_name
49
+ ]
72
50
 
73
51
  if not matching_artefact_info:
74
52
  suggest_close_name_matches(
75
- artefact_name,
76
- [info["title"] for info in artefact_info]
53
+ artefact_name, [info["title"] for info in artefact_info]
77
54
  )
78
55
 
79
56
  artefacts_by_classifier = {classifier: []}
@@ -82,29 +59,28 @@ class ArtefactLister:
82
59
  classifier=classifier,
83
60
  artefacts_by_classifier=artefacts_by_classifier,
84
61
  )
85
- artefacts_by_classifier = self.filter_artefacts(artefacts_by_classifier, list_filter)
62
+ artefacts_by_classifier = self.filter_artefacts(
63
+ artefacts_by_classifier, list_filter
64
+ )
86
65
  file_classifier.print_classified_files(artefacts_by_classifier)
87
66
 
88
67
  def list_children(
89
- self,
90
- classifier,
91
- artefact_name,
92
- list_filter: ListFilter | None = None
68
+ self, classifier, artefact_name, list_filter: ListFilter | None = None
93
69
  ):
94
70
  file_classifier = FileClassifier(os)
95
71
  classified_artefacts = file_classifier.classify_files()
96
72
  artefact_info = classified_artefacts.get(classifier, [])
97
- matching_artefact_info = [p for p in artefact_info if p["title"] == artefact_name]
73
+ matching_artefact_info = [
74
+ p for p in artefact_info if p["title"] == artefact_name
75
+ ]
98
76
 
99
77
  if not matching_artefact_info:
100
78
  suggest_close_name_matches(
101
- artefact_name,
102
- [info["title"] for info in artefact_info]
79
+ artefact_name, [info["title"] for info in artefact_info]
103
80
  )
104
81
 
105
82
  child_artefacts = ArtefactReader.find_children(
106
- artefact_name=artefact_name,
107
- classifier=classifier
83
+ artefact_name=artefact_name, classifier=classifier
108
84
  )
109
85
 
110
86
  child_artefacts = self.filter_artefacts(child_artefacts, list_filter)
@@ -112,25 +88,23 @@ class ArtefactLister:
112
88
  file_classifier.print_classified_files(child_artefacts)
113
89
 
114
90
  def list_data(
115
- self,
116
- classifier,
117
- artefact_name,
118
- list_filter: ListFilter | None = None
91
+ self, classifier, artefact_name, list_filter: ListFilter | None = None
119
92
  ):
120
93
  file_classifier = FileClassifier(os)
121
94
  classified_artefact_info = file_classifier.classify_files()
122
95
  artefact_info_dict = classified_artefact_info.get(classifier, [])
123
96
 
124
- matching_info = [info for info in artefact_info_dict if info["title"] == artefact_name]
97
+ matching_info = [
98
+ info for info in artefact_info_dict if info["title"] == artefact_name
99
+ ]
125
100
 
126
101
  if not matching_info:
127
102
  suggest_close_name_matches(
128
- artefact_name,
129
- [info["title"] for info in artefact_info_dict]
103
+ artefact_name, [info["title"] for info in artefact_info_dict]
130
104
  )
131
105
  return
132
106
 
133
107
  artefact_info = matching_info[0]
134
- data_dir = os.path.splitext(artefact_info["file_path"])[0] + '.data'
108
+ data_dir = os.path.splitext(artefact_info["file_path"])[0] + ".data"
135
109
  if os.path.exists(data_dir):
136
110
  list_files_in_directory(data_dir, list_filter)
@@ -0,0 +1,23 @@
1
+ from ara_cli.artefact_models.artefact_model import Artefact
2
+
3
+
4
+ def artefact_content_retrieval(artefact: Artefact):
5
+ content = artefact.serialize()
6
+ return content
7
+
8
+
9
+ def artefact_path_retrieval(artefact: Artefact):
10
+ return artefact.file_path
11
+
12
+
13
+ def artefact_tags_retrieval(artefact: Artefact):
14
+ final_tags = []
15
+
16
+ if not artefact:
17
+ return []
18
+
19
+ final_tags.extend([f"user_{user}" for user in artefact.users])
20
+ final_tags.append(artefact.status)
21
+ final_tags.extend(artefact.tags)
22
+
23
+ return final_tags
@@ -1,9 +1,10 @@
1
- import os
2
1
  from functools import lru_cache
3
2
  from ara_cli.classifier import Classifier
4
3
  from ara_cli.artefact_link_updater import ArtefactLinkUpdater
5
4
  from ara_cli.template_manager import DirectoryNavigator
5
+ import os
6
6
  import re
7
+ import warnings
7
8
 
8
9
 
9
10
  class ArtefactRenamer:
@@ -22,6 +23,8 @@ class ArtefactRenamer:
22
23
  return re.compile(pattern)
23
24
 
24
25
  def rename(self, old_name, new_name, classifier):
26
+ import shutil
27
+
25
28
  original_directory = self.navigate_to_target()
26
29
 
27
30
  if not new_name:
@@ -47,7 +50,8 @@ class ArtefactRenamer:
47
50
  if self.file_system.path.exists(new_file_path):
48
51
  raise FileExistsError(f"The new file name {new_file_path} already exists.")
49
52
  if self.file_system.path.exists(new_dir_path):
50
- raise FileExistsError(f"The new directory name {new_dir_path} already exists.")
53
+ warnings.warn(f"The new directory name {new_dir_path} already exists. It will be replaced by the artefact's data directory or removed entirely.", UserWarning)
54
+ shutil.rmtree(new_dir_path)
51
55
 
52
56
  # Perform the renaming of the file and directory
53
57
  self.file_system.rename(old_file_path, new_file_path)
ara_cli/chat.py CHANGED
@@ -9,13 +9,15 @@ from ara_cli.file_loaders.text_file_loader import TextFileLoader
9
9
 
10
10
 
11
11
  extract_parser = argparse.ArgumentParser()
12
- extract_parser.add_argument('-s', '--skip-queries', action='store_true', help='Force extraction')
12
+ extract_parser.add_argument('-f', '--force', action='store_true', help='Force extraction')
13
+ extract_parser.add_argument('-w','--write', action='store_true', help='Overwrite existing files without using LLM for merging.')
13
14
 
14
15
  load_parser = argparse.ArgumentParser()
15
16
  load_parser.add_argument('file_name', nargs='?', default='', help='File to load')
16
17
  load_parser.add_argument('--load-images', action='store_true', help='Extract and describe images from documents')
17
18
 
18
19
 
20
+
19
21
  class Chat(cmd2.Cmd):
20
22
  CATEGORY_CHAT_CONTROL = "Chat control commands"
21
23
  CATEGORY_LLM_CONTROL = "Language model controls"
@@ -813,7 +815,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
813
815
 
814
816
  command = ExtractCommand(
815
817
  file_name=self.chat_name,
816
- skip_queries=args.skip_queries,
818
+ force=args.force,
819
+ write=args.write,
817
820
  output=self.poutput,
818
821
  error_output=self.perror
819
822
  )
@@ -3,15 +3,16 @@ from ara_cli.prompt_extractor import extract_responses
3
3
  import os
4
4
 
5
5
  class ExtractCommand(Command):
6
- def __init__(self, file_name, skip_queries=False, output=None, error_output=None):
6
+ def __init__(self, file_name, force=False, write=False, output=None, error_output=None):
7
7
  self.file_name = file_name
8
- self.skip_queries = skip_queries
8
+ self.force = force
9
+ self.write = write
9
10
  self.output = output # Callable for standard output (optional)
10
11
  self.error_output = error_output # Callable for errors (optional)
11
12
 
12
13
  def execute(self, *args, **kwargs):
13
14
  try:
14
- extract_responses(self.file_name, True, skip_queries=self.skip_queries)
15
+ extract_responses(self.file_name, True, force=self.force, write=self.write)
15
16
  if self.output:
16
17
  self.output("End of extraction")
17
18
  except Exception as e:
@@ -0,0 +1,104 @@
1
+ from ara_cli.commands.command import Command
2
+ from ara_cli.artefact_reader import ArtefactReader
3
+ from ara_cli.file_classifier import FileClassifier
4
+ from ara_cli.list_filter import ListFilter, filter_list
5
+ from ara_cli.artefact_models.artefact_data_retrieval import (
6
+ artefact_content_retrieval,
7
+ artefact_path_retrieval,
8
+ artefact_tags_retrieval
9
+ )
10
+ from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
11
+ import os
12
+
13
+
14
+ class ReadCommand(Command):
15
+ def __init__(
16
+ self,
17
+ classifier: str,
18
+ artefact_name: str,
19
+ read_mode: str = "default",
20
+ list_filter: ListFilter = None,
21
+ output=None
22
+ ):
23
+ self.classifier = classifier
24
+ self.artefact_name = artefact_name
25
+ self.read_mode = read_mode
26
+ self.list_filter = list_filter or ListFilter()
27
+ self.output = output or print
28
+
29
+ def execute(self) -> bool:
30
+ """Execute the read command and return success status."""
31
+ file_classifier = FileClassifier(os)
32
+ classified_artefacts = ArtefactReader.read_artefacts()
33
+ artefacts = classified_artefacts.get(self.classifier, [])
34
+ all_artefact_names = [a.title for a in artefacts]
35
+
36
+ if self.artefact_name not in all_artefact_names:
37
+ suggest_close_name_matches(
38
+ self.artefact_name,
39
+ all_artefact_names
40
+ )
41
+ return False
42
+
43
+ target_artefact = next(filter(
44
+ lambda x: x.title == self.artefact_name, artefacts
45
+ ))
46
+
47
+ artefacts_by_classifier = {self.classifier: []}
48
+
49
+ try:
50
+ match self.read_mode:
51
+ case "branch":
52
+ self._handle_branch_mode(
53
+ classified_artefacts, artefacts_by_classifier
54
+ )
55
+ case "children":
56
+ artefacts_by_classifier = self._handle_children_mode(
57
+ classified_artefacts
58
+ )
59
+ case _:
60
+ self._handle_default_mode(
61
+ target_artefact, artefacts_by_classifier
62
+ )
63
+
64
+ # Apply filtering and print results
65
+ filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
66
+ file_classifier.print_classified_files(
67
+ filtered_artefacts, print_content=True
68
+ )
69
+ return True
70
+
71
+ except Exception as e:
72
+ self.output(f"Error reading artefact: {e}")
73
+ return False
74
+
75
+ def _handle_branch_mode(self, classified_artefacts, artefacts_by_classifier):
76
+ """Handle branch read mode."""
77
+ ArtefactReader.step_through_value_chain(
78
+ artefact_name=self.artefact_name,
79
+ classifier=self.classifier,
80
+ artefacts_by_classifier=artefacts_by_classifier,
81
+ classified_artefacts=classified_artefacts
82
+ )
83
+
84
+ def _handle_children_mode(self, classified_artefacts):
85
+ """Handle children read mode."""
86
+ return ArtefactReader.find_children(
87
+ artefact_name=self.artefact_name,
88
+ classifier=self.classifier,
89
+ classified_artefacts=classified_artefacts
90
+ )
91
+
92
+ def _handle_default_mode(self, target_artefact, artefacts_by_classifier):
93
+ """Handle default read mode."""
94
+ artefacts_by_classifier[self.classifier].append(target_artefact)
95
+
96
+ def _apply_filtering(self, artefacts_by_classifier):
97
+ """Apply list filtering to artefacts."""
98
+ return filter_list(
99
+ list_to_filter=artefacts_by_classifier,
100
+ list_filter=self.list_filter,
101
+ content_retrieval_strategy=artefact_content_retrieval,
102
+ file_path_retrieval=artefact_path_retrieval,
103
+ tag_retrieval=artefact_tags_retrieval
104
+ )
@@ -16,7 +16,7 @@ def extract_code_blocks_md(markdown_text):
16
16
  return code_blocks
17
17
 
18
18
 
19
- def extract_responses(document_path, relative_to_ara_root=False, skip_queries=False):
19
+ def extract_responses(document_path, relative_to_ara_root=False, force=False, write=False):
20
20
  print(f"Debug: Starting extraction from {document_path}")
21
21
  block_extraction_counter = 0
22
22
 
@@ -50,7 +50,7 @@ def extract_responses(document_path, relative_to_ara_root=False, skip_queries=Fa
50
50
  block_lines = block_lines[1:] # Remove first line again after removing filename line
51
51
  block = '\n'.join(block_lines)
52
52
 
53
- handle_existing_file(file_path, block, skip_queries)
53
+ handle_existing_file(file_path, block, force, write)
54
54
  block_extraction_counter += 1
55
55
 
56
56
  # Update the markdown content
@@ -79,7 +79,7 @@ def extract_responses(document_path, relative_to_ara_root=False, skip_queries=Fa
79
79
  artefact_path = artefact.file_path
80
80
  directory = os.path.dirname(artefact_path)
81
81
  os.makedirs(directory, exist_ok=True)
82
- handle_existing_file(artefact_path, serialized_artefact, skip_queries)
82
+ handle_existing_file(artefact_path, serialized_artefact, force, write)
83
83
 
84
84
  os.chdir(original_directory)
85
85
 
@@ -182,13 +182,28 @@ def create_prompt_for_file_modification(content_str, filename):
182
182
  return prompt_text
183
183
 
184
184
 
185
- def handle_existing_file(filename, block_content, skip_query=False):
185
+ def handle_existing_file(filename, block_content, skip_query=False, write=False):
186
186
  if not os.path.isfile(filename):
187
187
  print(f"File {filename} does not exist, attempting to create")
188
188
  create_file_if_not_exist(filename, block_content, skip_query)
189
+ elif write:
190
+ print(f"File {filename} exists. Overwriting without LLM merge as requested.")
191
+ try:
192
+ directory = os.path.dirname(filename)
193
+ if directory:
194
+ os.makedirs(directory, exist_ok=True)
195
+ with open(filename, 'w', encoding='utf-8', errors='replace') as file:
196
+ file.write(block_content)
197
+ print(f"File {filename} overwritten successfully.")
198
+ except OSError as e:
199
+ print(f"Error: {e}")
200
+ print(f"Failed to overwrite file {filename} due to an OS error")
189
201
  else:
190
202
  print(f"File {filename} exists, creating modification prompt")
191
203
  prompt_text = create_prompt_for_file_modification(block_content, filename)
204
+ if prompt_text is None:
205
+ return
206
+
192
207
  messages = [{"role": "user", "content": prompt_text}]
193
208
  response = ""
194
209
 
@@ -199,12 +214,12 @@ def handle_existing_file(filename, block_content, skip_query=False):
199
214
  modify_and_save_file(response, filename)
200
215
 
201
216
 
202
- def extract_and_save_prompt_results(classifier, param):
217
+ def extract_and_save_prompt_results(classifier, param, write=False):
203
218
  sub_directory = Classifier.get_sub_directory(classifier)
204
219
  prompt_log_file = f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
205
220
  print(f"Extract marked sections from: {prompt_log_file}")
206
221
 
207
- extract_responses(prompt_log_file)
222
+ extract_responses(prompt_log_file, write=write)
208
223
 
209
224
 
210
225
  def update_markdown(original_content, block_content, filename):
ara_cli/tag_extractor.py CHANGED
@@ -1,6 +1,10 @@
1
1
  import os
2
2
  from ara_cli.list_filter import ListFilter, filter_list
3
- from ara_cli.artefact_lister import ArtefactLister
3
+ from ara_cli.artefact_models.artefact_data_retrieval import (
4
+ artefact_content_retrieval,
5
+ artefact_path_retrieval,
6
+ artefact_tags_retrieval,
7
+ )
4
8
 
5
9
 
6
10
  class TagExtractor:
@@ -10,7 +14,9 @@ class TagExtractor:
10
14
  def filter_column(self, tags_set, filtered_artefacts):
11
15
  status_tags = {"to-do", "in-progress", "review", "done", "closed"}
12
16
 
13
- artefacts_to_process = self._get_artefacts_without_status_tags(filtered_artefacts, status_tags)
17
+ artefacts_to_process = self._get_artefacts_without_status_tags(
18
+ filtered_artefacts, status_tags
19
+ )
14
20
  self._add_non_status_tags_to_set(tags_set, artefacts_to_process, status_tags)
15
21
 
16
22
  def _get_artefacts_without_status_tags(self, filtered_artefacts, status_tags):
@@ -28,7 +34,9 @@ class TagExtractor:
28
34
 
29
35
  def _add_non_status_tags_to_set(self, tags_set, artefacts, status_tags):
30
36
  for artefact in artefacts:
31
- tags = [tag for tag in (artefact.tags + [artefact.status]) if tag is not None]
37
+ tags = [
38
+ tag for tag in (artefact.tags + [artefact.status]) if tag is not None
39
+ ]
32
40
  for tag in tags:
33
41
  if self._is_skipped_tag(tag, status_tags):
34
42
  continue
@@ -36,23 +44,25 @@ class TagExtractor:
36
44
 
37
45
  def _is_skipped_tag(self, tag, status_tags):
38
46
  return (
39
- tag in status_tags
40
- or tag.startswith("priority_")
41
- or tag.startswith("user_")
47
+ tag in status_tags or tag.startswith("priority_") or tag.startswith("user_")
42
48
  )
43
49
 
44
50
  def add_to_tags_set(self, tags_set, filtered_artefacts):
45
51
  for artefact_list in filtered_artefacts.values():
46
52
  for artefact in artefact_list:
47
53
  user_tags = [f"user_{tag}" for tag in artefact.users]
48
- tags = [tag for tag in (artefact.tags + [artefact.status] + user_tags) if tag is not None]
54
+ tags = [
55
+ tag
56
+ for tag in (artefact.tags + [artefact.status] + user_tags)
57
+ if tag is not None
58
+ ]
49
59
  tags_set.update(tags)
50
60
 
51
61
  def extract_tags(
52
62
  self,
53
63
  navigate_to_target=False,
54
64
  filtered_extra_column=False,
55
- list_filter: ListFilter | None = None
65
+ list_filter: ListFilter | None = None,
56
66
  ):
57
67
  from ara_cli.template_manager import DirectoryNavigator
58
68
  from ara_cli.artefact_reader import ArtefactReader
@@ -66,9 +76,9 @@ class TagExtractor:
66
76
  filtered_artefacts = filter_list(
67
77
  list_to_filter=artefacts,
68
78
  list_filter=list_filter,
69
- content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
70
- file_path_retrieval=ArtefactLister.artefact_path_retrieval,
71
- tag_retrieval=ArtefactLister.artefact_tags_retrieval
79
+ content_retrieval_strategy=artefact_content_retrieval,
80
+ file_path_retrieval=artefact_path_retrieval,
81
+ tag_retrieval=artefact_tags_retrieval,
72
82
  )
73
83
 
74
84
  unique_tags = set()
ara_cli/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.9.93" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
2
+ __version__ = "0.1.9.94" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ara_cli
3
- Version: 0.1.9.93
3
+ Version: 0.1.9.94
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
6
  Requires-Dist: litellm
@@ -108,22 +108,22 @@ ara autofix
108
108
 
109
109
  ## Command Overview
110
110
 
111
- | Action | Description |
112
- |--------------------|-----------------------------------------------------------------------------|
113
- | create | Create a classified artefact with data directory |
114
- | delete | Delete an artefact and its data directory |
115
- | rename | Rename an artefact and its data directory |
116
- | list, list-tags | List artefacts, show tags, filter by content, extension, hierarchy etc. |
117
- | prompt, chat | Use AI-powered chat and prompt templates for artefact management |
118
- | template | Print artefact templates in the terminal |
119
- | fetch-templates | Download and manage reusable prompt templates |
120
- | read | Output artefact contents and their full contribution chain |
121
- | reconnect | Connect artefacts to parent artefacts |
122
- | read-status, set-status | Query and assign status to artefacts |
123
- | read-user, set-user | Query and assign responsible users |
124
- | classifier-directory | Show directory of artefact classifiers |
125
- | scan | Scan the ARA tree for incompatible or inconsistent artefacts |
126
- | autofix | Automatically correct artefact issues with LLM assistance |
111
+ | Action | Description |
112
+ | ----------------------- | ----------------------------------------------------------------------- |
113
+ | create | Create a classified artefact with data directory |
114
+ | delete | Delete an artefact and its data directory |
115
+ | rename | Rename an artefact and its data directory |
116
+ | list, list-tags | List artefacts, show tags, filter by content, extension, hierarchy etc. |
117
+ | prompt, chat | Use AI-powered chat and prompt templates for artefact management |
118
+ | template | Print artefact templates in the terminal |
119
+ | fetch-templates | Download and manage reusable prompt templates |
120
+ | read | Output artefact contents and their full contribution chain |
121
+ | reconnect | Connect artefacts to parent artefacts |
122
+ | read-status, set-status | Query and assign status to artefacts |
123
+ | read-user, set-user | Query and assign responsible users |
124
+ | classifier-directory | Show directory of artefact classifiers |
125
+ | scan | Scan the ARA tree for incompatible or inconsistent artefacts |
126
+ | autofix | Automatically correct artefact issues with LLM assistance |
127
127
 
128
128
  See `ara -h` for the complete list of commands and usage examples.
129
129
 
@@ -1,18 +1,18 @@
1
- ara_cli/__init__.py,sha256=0zl7IegxTid26EBGLav_fXZ4CCIV3H5TfAoFQiOHjvg,148
1
+ ara_cli/__init__.py,sha256=oieeo07y5FLq0tmPzWZdp65j6tN8NOucSjSzrJw8qrQ,203
2
2
  ara_cli/__main__.py,sha256=J5DCDLRZ6UcpYwM1-NkjaLo4PTetcSj2dB4HrrftkUw,2064
3
- ara_cli/ara_command_action.py,sha256=_LHE2V5hbJxN7ccYiptuPktRfbTnXmQEt_D_FxDBlBY,22456
4
- ara_cli/ara_command_parser.py,sha256=I-e9W-QwTIMKMzlHycSlCWCyBFQfiFYvGre1XsDbrFI,20573
3
+ ara_cli/ara_command_action.py,sha256=uyMN05ZYffWqN9nwL53MmQ_yHpuxHVqZ_scAMEoD1jw,21516
4
+ ara_cli/ara_command_parser.py,sha256=A1lMc9Gc0EMJt-380PTcv3aKoxbXGfx5gGax-sZqV3I,21020
5
5
  ara_cli/ara_config.py,sha256=5uBo_flNgZSk7B9lmyfvzWyxfIQzb13LbieCpJfdZJI,8765
6
6
  ara_cli/artefact_autofix.py,sha256=WVTiIR-jo4YKmmz4eS3qTFvl45W1YKwAk1XSuz9QX10,20015
7
7
  ara_cli/artefact_creator.py,sha256=0Ory6cB-Ahkw-BDNb8QHnTbp_OHGABdkb9bhwcEdcIc,6063
8
8
  ara_cli/artefact_deleter.py,sha256=Co4wwCH3yW8H9NrOq7_2p5571EeHr0TsfE-H8KqoOfY,1900
9
9
  ara_cli/artefact_fuzzy_search.py,sha256=iBlDqjZf-_D3VUjFf7ZwkiQbpQDcwRndIU7aG_sRTgE,2668
10
10
  ara_cli/artefact_link_updater.py,sha256=nKdxTpDKqWTOAMD8viKmUaklSFGWzJZ8S8E8xW_ADuM,3775
11
- ara_cli/artefact_lister.py,sha256=jhk4n4eqp7hDIq07q43QzS7-36BM3OfZ4EABxCeOGcw,4764
11
+ ara_cli/artefact_lister.py,sha256=M-ggazAgZ-OLeW9NB48r_sd6zPx0p4hEpeS63qHwI1A,4176
12
12
  ara_cli/artefact_reader.py,sha256=Pho0_Eqm7kD9CNbVMhKb6mkNM0I3iJiCJXbXmVp1DJU,7827
13
- ara_cli/artefact_renamer.py,sha256=Hnz_3zD9xxnBa1FHyUE6mIktLk_9ttP2rFRvQIkmz-o,4061
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=mbpv5XQOcJSAUBJxCrfmyl7W5a0GhQU9cblxiTJNpP8,37841
15
+ ara_cli/chat.py,sha256=t17TCmx9xvKj5wrnUOJBqjWoSjEVOFrVd8RivChVX50,37980
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
@@ -24,15 +24,16 @@ ara_cli/filename_validator.py,sha256=Aw9PL8d5-Ymhp3EY6lDrUBk3cudaNqo1Uw5RzPpI1jA
24
24
  ara_cli/list_filter.py,sha256=qKGwwQsrWe7L5FbdxEbBYD1bbbi8c-RMypjXqXvLbgs,5291
25
25
  ara_cli/output_suppressor.py,sha256=nwiHaQLwabOjMoJOeUESBnZszGMxrQZfJ3N2OvahX7Y,389
26
26
  ara_cli/prompt_chat.py,sha256=kd_OINDQFit6jN04bb7mzgY259JBbRaTaNp9F-webkc,1346
27
- ara_cli/prompt_extractor.py,sha256=Sk1aQkvn8W_YNcGhfChsbT19wUAWEJmOHTr3mveyQww,7754
27
+ ara_cli/prompt_extractor.py,sha256=-_17aVYXYH6kPX5FOSb9T8lbEkKPXE6nlHWq1pvO_Og,8423
28
28
  ara_cli/prompt_handler.py,sha256=6yfiMFNHGHANREAsjT8dv9jKxBKeazPkF7xQQI4l6vQ,22312
29
29
  ara_cli/prompt_rag.py,sha256=ydlhe4CUqz0jdzlY7jBbpKaf_5fjMrAZKnriKea3ZAg,7485
30
30
  ara_cli/run_file_lister.py,sha256=XbrrDTJXp1LFGx9Lv91SNsEHZPP-PyEMBF_P4btjbDA,2360
31
- ara_cli/tag_extractor.py,sha256=TGdaQOVnjy25R0zDsAifB67C5oom0Fwo24s0_fr5A_I,3151
31
+ ara_cli/tag_extractor.py,sha256=k2yRl7dAMZ4YTARzUke4wgY0oEIOmWkOHGet7nXB6uw,3317
32
32
  ara_cli/template_manager.py,sha256=YwrN6AYPpl6ZrW8BVQpVXx8yTRf-oNpJUIKeg4NAggs,6606
33
33
  ara_cli/update_config_prompt.py,sha256=Oy9vNTw6UhDohyTEfSKkqE5ifEMPlmWNYkKHgUrK_pY,4607
34
- ara_cli/version.py,sha256=UYGRHXHl4vgX5k6OLDjyffvrV4jDkHj_fVeKiGqQ8XU,146
34
+ ara_cli/version.py,sha256=MWb524qaDHLrSJsHhOq7o0m78CPLFil9qeyel6kPW-A,146
35
35
  ara_cli/artefact_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ ara_cli/artefact_models/artefact_data_retrieval.py,sha256=CooXOJBYWSyiViN2xkC8baS8OUaslry3YGVVUeDxRAU,527
36
37
  ara_cli/artefact_models/artefact_load.py,sha256=IXzWxP-Q_j_oDGMno0m-OuXCQ7Vd5c_NctshGr4ROBw,621
37
38
  ara_cli/artefact_models/artefact_mapping.py,sha256=8aD0spBjkJ8toMAmFawc6UTUxB6-tEEViZXv2I-r88Q,1874
38
39
  ara_cli/artefact_models/artefact_model.py,sha256=qSbcrmFWAYgBqcNl9QARI1_uLQJm-TPVgP5q2AEFnjE,15983
@@ -50,9 +51,10 @@ ara_cli/artefact_models/userstory_artefact_model.py,sha256=2awH31ROtm7j4T44Bv4cy
50
51
  ara_cli/artefact_models/vision_artefact_model.py,sha256=frjaUJj-mmIlVHEhzAQztCGs-CtvNu_odSborgztfzo,5251
51
52
  ara_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
53
  ara_cli/commands/command.py,sha256=Y_2dNeuxRjbyI3ScXNv55lptSe8Hs_ya78L0nPYNZHA,154
53
- ara_cli/commands/extract_command.py,sha256=TfKuOnKQzJ8JPpJyKDm7qhm5mvbZnHspCem8g6YgACo,835
54
+ ara_cli/commands/extract_command.py,sha256=qpi2_ac3DyxS7FiOz4GsTtRR4xtpegckUmfXzDOwymM,858
54
55
  ara_cli/commands/load_command.py,sha256=H3CfeHIL-criDU5oi4BONTSpyzJ4m8DzJ0ZCIiAZFeI,2204
55
56
  ara_cli/commands/load_image_command.py,sha256=g9-PXAYdqx5Ed1PdVo-FIb4CyJGEpRFbgQf9Dxg6DmM,886
57
+ ara_cli/commands/read_command.py,sha256=bo1BvRWuNKdFqBNN1EWORNrX_yuFAOyBruDUolHq1Vc,3791
56
58
  ara_cli/file_loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
59
  ara_cli/file_loaders/binary_file_loader.py,sha256=1HHH1Nk4lEM83CTnf4z9wYz6rMLgpxydFoRcSgkBHmQ,940
58
60
  ara_cli/file_loaders/document_file_loader.py,sha256=VxGFChYyM9K-e6eOCK3yk5jQuEXgz01Mh_NoA6CA_RM,1017
@@ -138,11 +140,11 @@ tests/test_ara_config.py,sha256=H5GwDbab0GMSa6IbHdruzmbsHy5Ia0xX0uteJdfZ9Rg,1427
138
140
  tests/test_artefact_autofix.py,sha256=pApZ-N0dW8Ujt-cNLbgvd4bhiIIK8oXb-saLf6QlA-8,25022
139
141
  tests/test_artefact_fuzzy_search.py,sha256=5Sh3_l9QK8-WHn6JpGPU1b6h4QEnl2JoMq1Tdp2cj1U,1261
140
142
  tests/test_artefact_link_updater.py,sha256=biqbEp2jCOz8giv72hu2P2hDfeJfJ9OrVGdAv5d9cK4,2191
141
- tests/test_artefact_lister.py,sha256=VCEOCgDgnAOeUUgIoGAbWgz60hf9UT-tdHg18LGfB34,22656
143
+ tests/test_artefact_lister.py,sha256=35R13UU-YsX1HOsEN8M2-vIiCUA9RSBm6SwestDaFhE,20388
142
144
  tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
143
145
  tests/test_artefact_renamer.py,sha256=lSnKCCfoFGgKhTdDZrEaeBq1xJAak1QoqH5aSeOe9Ro,3494
144
146
  tests/test_artefact_scan.py,sha256=uNWgrt7ieZ4ogKACsPqzAsh59JF2BhTKSag31hpVrTQ,16887
145
- tests/test_chat.py,sha256=-2vs3ORlym0yv05BLSCNX-6tNYXqoZiBFar8gswPuw8,57072
147
+ tests/test_chat.py,sha256=cCNIuYiSGoNtjgjyFiTvkMHJgCmMNXQhpawNc23-fmM,57037
146
148
  tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
147
149
  tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
148
150
  tests/test_file_classifier.py,sha256=kLWPiePu3F5mkVuI_lK_2QlLh2kXD_Mt2K8KZZ1fAnA,10940
@@ -153,8 +155,8 @@ tests/test_prompt_handler.py,sha256=3-lYBvyHLQgD29MODkXB3YylUWXmRCYdAwrQrtlW8WU,
153
155
  tests/test_tag_extractor.py,sha256=nSiAYlTKZ7TLAOtcJpwK5zTWHhFYU0tI5xKnivLc1dU,2712
154
156
  tests/test_template_manager.py,sha256=q-LMHRG4rHkD6ON6YW4cpZxUx9hul6Or8wVVRC2kb-8,4099
155
157
  tests/test_update_config_prompt.py,sha256=xsqj1WTn4BsG5Q2t-sNPfu7EoMURFcS-hfb5VSXUnJc,6765
156
- ara_cli-0.1.9.93.dist-info/METADATA,sha256=BEbyd6dizmW-wO3XCdsyU9t2FrgKvQdIPMhAcxm8__M,6739
157
- ara_cli-0.1.9.93.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
158
- ara_cli-0.1.9.93.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
159
- ara_cli-0.1.9.93.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
160
- ara_cli-0.1.9.93.dist-info/RECORD,,
158
+ ara_cli-0.1.9.94.dist-info/METADATA,sha256=CPz0aDXI8_4hJtbN9tm56-aNUNEum3G01dD9DMIwGdg,6755
159
+ ara_cli-0.1.9.94.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
160
+ ara_cli-0.1.9.94.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
161
+ ara_cli-0.1.9.94.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
162
+ ara_cli-0.1.9.94.dist-info/RECORD,,
@@ -2,6 +2,11 @@ import pytest
2
2
  from unittest.mock import MagicMock, patch
3
3
  from ara_cli.artefact_lister import ArtefactLister
4
4
  from ara_cli.list_filter import ListFilter
5
+ from ara_cli.artefact_models.artefact_data_retrieval import (
6
+ artefact_content_retrieval,
7
+ artefact_path_retrieval,
8
+ artefact_tags_retrieval,
9
+ )
5
10
 
6
11
 
7
12
  @pytest.fixture
@@ -9,66 +14,6 @@ def artefact_lister():
9
14
  return ArtefactLister()
10
15
 
11
16
 
12
- @pytest.mark.parametrize(
13
- "users, status, tags, expected_tags",
14
- [
15
- # Normal case with all fields populated
16
- (
17
- ["john", "alice"],
18
- "in-progress",
19
- ["important", "urgent"],
20
- ["user_john", "user_alice", "in-progress", "important", "urgent"]
21
- ),
22
- # Case with empty users
23
- (
24
- [],
25
- "to-do",
26
- ["feature", "backend"],
27
- ["to-do", "feature", "backend"]
28
- ),
29
- # Case with empty tags
30
- (
31
- ["bob"],
32
- "done",
33
- [],
34
- ["user_bob", "done"]
35
- ),
36
- # Case with all empty fields
37
- (
38
- [],
39
- "",
40
- [],
41
- [""]
42
- ),
43
- # Case with None values for tags (should handle gracefully)
44
- (
45
- ["admin"],
46
- "closed",
47
- None,
48
- ["user_admin", "closed"]
49
- ),
50
- ]
51
- )
52
- def test_artefact_tags_retrieval(users, status, tags, expected_tags):
53
- # Create a mock artefact with the specified attributes
54
- artefact_mock = MagicMock()
55
- artefact_mock.users = users
56
- artefact_mock.status = status
57
- artefact_mock.tags = tags if tags is not None else []
58
-
59
- # Call the method under test
60
- result = ArtefactLister.artefact_tags_retrieval(artefact_mock)
61
-
62
- # Verify the result
63
- assert result == expected_tags
64
-
65
-
66
- def test_artefact_tags_retrieval_with_none():
67
- # Test with None artefact
68
- result = ArtefactLister.artefact_tags_retrieval(None)
69
- assert result == []
70
-
71
-
72
17
  @pytest.mark.parametrize(
73
18
  "classified_files, list_filter, filter_result",
74
19
  [
@@ -76,39 +21,36 @@ def test_artefact_tags_retrieval_with_none():
76
21
  (
77
22
  {"type1": [MagicMock(), MagicMock()]},
78
23
  None,
79
- {"type1": [MagicMock(), MagicMock()]}
24
+ {"type1": [MagicMock(), MagicMock()]},
80
25
  ),
81
26
  # Case 2: Filter with include tags
82
27
  (
83
28
  {"type1": [MagicMock(), MagicMock()]},
84
29
  ListFilter(include_tags=["tag1"]),
85
- {"type1": [MagicMock()]}
30
+ {"type1": [MagicMock()]},
86
31
  ),
87
32
  # Case 3: Filter with exclude tags
88
33
  (
89
34
  {"type1": [MagicMock(), MagicMock()], "type2": [MagicMock()]},
90
35
  ListFilter(exclude_tags=["tag2"]),
91
- {"type1": [MagicMock()], "type2": []}
36
+ {"type1": [MagicMock()], "type2": []},
92
37
  ),
93
38
  # Case 4: Empty result after filtering
94
39
  (
95
40
  {"type1": [MagicMock(), MagicMock()]},
96
41
  ListFilter(include_tags=["nonexistent"]),
97
- {"type1": []}
42
+ {"type1": []},
98
43
  ),
99
44
  # Case 5: Multiple artefact types
100
45
  (
101
46
  {"type1": [MagicMock()], "type2": [MagicMock(), MagicMock()]},
102
47
  ListFilter(exclude_extension=[".txt"]),
103
- {"type1": [], "type2": [MagicMock()]}
48
+ {"type1": [], "type2": [MagicMock()]},
104
49
  ),
105
50
  ],
106
51
  )
107
52
  def test_filter_artefacts(
108
- artefact_lister,
109
- classified_files,
110
- list_filter,
111
- filter_result
53
+ artefact_lister, classified_files, list_filter, filter_result
112
54
  ):
113
55
  # Mock the filter_list function
114
56
  with patch("ara_cli.artefact_lister.filter_list") as mock_filter_list:
@@ -121,9 +63,9 @@ def test_filter_artefacts(
121
63
  mock_filter_list.assert_called_once_with(
122
64
  list_to_filter=classified_files,
123
65
  list_filter=list_filter,
124
- content_retrieval_strategy=ArtefactLister.artefact_content_retrieval,
125
- file_path_retrieval=ArtefactLister.artefact_path_retrieval,
126
- tag_retrieval=ArtefactLister.artefact_tags_retrieval
66
+ content_retrieval_strategy=artefact_content_retrieval,
67
+ file_path_retrieval=artefact_path_retrieval,
68
+ tag_retrieval=artefact_tags_retrieval,
127
69
  )
128
70
 
129
71
  # Verify the structure matches (don't compare the actual MagicMock objects)
@@ -132,30 +74,6 @@ def test_filter_artefacts(
132
74
  assert len(result[key]) == len(filter_result[key])
133
75
 
134
76
 
135
- @pytest.mark.parametrize("artefact_content", ["content1", "content2", "content3"])
136
- def test_artefact_content_retrieval(artefact_content):
137
- artefact_mock = MagicMock()
138
- artefact_mock.serialize.return_value = artefact_content # Mock serialize()
139
-
140
- content = ArtefactLister.artefact_content_retrieval(artefact_mock)
141
- assert content == artefact_content
142
-
143
-
144
- @pytest.mark.parametrize(
145
- "file_path",
146
- [
147
- ("./ara/userstories/test.userstory"),
148
- ("./ara/epics/test.epic"),
149
- ],
150
- )
151
- def test_artefact_path_retrieval(file_path):
152
- artefact_mock = MagicMock()
153
- artefact_mock.file_path = file_path
154
-
155
- path = ArtefactLister.artefact_path_retrieval(artefact_mock)
156
- assert path == file_path
157
-
158
-
159
77
  @pytest.mark.parametrize(
160
78
  "tags, navigate_to_target, list_filter, mock_artefacts, filtered_artefacts, expected_filtered",
161
79
  [
@@ -476,20 +394,21 @@ def test_list_data_artefact_found_data_exists(artefact_lister):
476
394
  classified_artefacts = {
477
395
  "epic": [
478
396
  {"title": "Epic1", "file_path": "path/to/Epic1.epic"},
479
- {"title": "Epic2", "file_path": "path/to/Epic2.epic"}
397
+ {"title": "Epic2", "file_path": "path/to/Epic2.epic"},
480
398
  ]
481
399
  }
482
-
483
- with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
484
- patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
485
- patch("ara_cli.artefact_lister.os") as mock_os, \
486
- patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
400
+
401
+ with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
402
+ "ara_cli.artefact_lister.suggest_close_name_matches"
403
+ ) as mock_suggest, patch("ara_cli.artefact_lister.os") as mock_os, patch(
404
+ "ara_cli.artefact_lister.list_files_in_directory"
405
+ ) as mock_list_files:
487
406
 
488
407
  # Configure mocks
489
408
  mock_classifier_instance = MagicMock()
490
409
  mock_file_classifier.return_value = mock_classifier_instance
491
410
  mock_classifier_instance.classify_files.return_value = classified_artefacts
492
-
411
+
493
412
  mock_os.path.splitext.return_value = ("path/to/Epic1", ".epic")
494
413
  mock_os.path.exists.return_value = True
495
414
 
@@ -510,20 +429,21 @@ def test_list_data_artefact_found_data_not_exists(artefact_lister):
510
429
  classified_artefacts = {
511
430
  "epic": [
512
431
  {"title": "Epic1", "file_path": "path/to/Epic1.epic"},
513
- {"title": "Epic2", "file_path": "path/to/Epic2.epic"}
432
+ {"title": "Epic2", "file_path": "path/to/Epic2.epic"},
514
433
  ]
515
434
  }
516
-
517
- with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
518
- patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
519
- patch("ara_cli.artefact_lister.os") as mock_os, \
520
- patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
435
+
436
+ with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
437
+ "ara_cli.artefact_lister.suggest_close_name_matches"
438
+ ) as mock_suggest, patch("ara_cli.artefact_lister.os") as mock_os, patch(
439
+ "ara_cli.artefact_lister.list_files_in_directory"
440
+ ) as mock_list_files:
521
441
 
522
442
  # Configure mocks
523
443
  mock_classifier_instance = MagicMock()
524
444
  mock_file_classifier.return_value = mock_classifier_instance
525
445
  mock_classifier_instance.classify_files.return_value = classified_artefacts
526
-
446
+
527
447
  mock_os.path.splitext.return_value = ("path/to/Epic1", ".epic")
528
448
  mock_os.path.exists.return_value = False
529
449
 
@@ -544,14 +464,15 @@ def test_list_data_artefact_not_found(artefact_lister):
544
464
  classified_artefacts = {
545
465
  "epic": [
546
466
  {"title": "Epic1", "file_path": "path/to/Epic1.epic"},
547
- {"title": "Epic2", "file_path": "path/to/Epic2.epic"}
467
+ {"title": "Epic2", "file_path": "path/to/Epic2.epic"},
548
468
  ]
549
469
  }
550
-
551
- with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
552
- patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
553
- patch("ara_cli.artefact_lister.os") as mock_os, \
554
- patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
470
+
471
+ with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
472
+ "ara_cli.artefact_lister.suggest_close_name_matches"
473
+ ) as mock_suggest, patch("ara_cli.artefact_lister.os") as mock_os, patch(
474
+ "ara_cli.artefact_lister.list_files_in_directory"
475
+ ) as mock_list_files:
555
476
 
556
477
  # Configure mocks
557
478
  mock_classifier_instance = MagicMock()
@@ -562,10 +483,7 @@ def test_list_data_artefact_not_found(artefact_lister):
562
483
  artefact_lister.list_data(classifier, artefact_name, list_filter)
563
484
 
564
485
  # Verify interactions
565
- mock_suggest.assert_called_once_with(
566
- artefact_name,
567
- ["Epic1", "Epic2"]
568
- )
486
+ mock_suggest.assert_called_once_with(artefact_name, ["Epic1", "Epic2"])
569
487
  mock_os.path.splitext.assert_not_called()
570
488
  mock_os.path.exists.assert_not_called()
571
489
  mock_list_files.assert_not_called()
@@ -578,20 +496,21 @@ def test_list_data_with_filter(artefact_lister):
578
496
  classified_artefacts = {
579
497
  "userstory": [
580
498
  {"title": "Story1", "file_path": "path/to/Story1.userstory"},
581
- {"title": "Story2", "file_path": "path/to/Story2.userstory"}
499
+ {"title": "Story2", "file_path": "path/to/Story2.userstory"},
582
500
  ]
583
501
  }
584
-
585
- with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
586
- patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
587
- patch("ara_cli.artefact_lister.os") as mock_os, \
588
- patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
502
+
503
+ with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
504
+ "ara_cli.artefact_lister.suggest_close_name_matches"
505
+ ) as mock_suggest, patch("ara_cli.artefact_lister.os") as mock_os, patch(
506
+ "ara_cli.artefact_lister.list_files_in_directory"
507
+ ) as mock_list_files:
589
508
 
590
509
  # Configure mocks
591
510
  mock_classifier_instance = MagicMock()
592
511
  mock_file_classifier.return_value = mock_classifier_instance
593
512
  mock_classifier_instance.classify_files.return_value = classified_artefacts
594
-
513
+
595
514
  mock_os.path.splitext.return_value = ("path/to/Story1", ".userstory")
596
515
  mock_os.path.exists.return_value = True
597
516
 
@@ -610,11 +529,12 @@ def test_list_data_empty_artefact_list(artefact_lister):
610
529
  artefact_name = "Epic1"
611
530
  list_filter = None
612
531
  classified_artefacts = {"epic": []}
613
-
614
- with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, \
615
- patch("ara_cli.artefact_lister.suggest_close_name_matches") as mock_suggest, \
616
- patch("ara_cli.artefact_lister.os") as mock_os, \
617
- patch("ara_cli.artefact_lister.list_files_in_directory") as mock_list_files:
532
+
533
+ with patch("ara_cli.artefact_lister.FileClassifier") as mock_file_classifier, patch(
534
+ "ara_cli.artefact_lister.suggest_close_name_matches"
535
+ ) as mock_suggest, patch("ara_cli.artefact_lister.os") as mock_os, patch(
536
+ "ara_cli.artefact_lister.list_files_in_directory"
537
+ ) as mock_list_files:
618
538
 
619
539
  # Configure mocks
620
540
  mock_classifier_instance = MagicMock()
tests/test_chat.py CHANGED
@@ -635,14 +635,14 @@ def test_load_file(temp_chat_file, file_name, file_type, mime_type):
635
635
  patch.object(chat, 'load_text_file', return_value=True) as mock_load_text, \
636
636
  patch.object(chat, 'load_document_file', return_value=True) as mock_load_document:
637
637
 
638
- chat.load_file(file_name=file_name, prefix="p-", suffix="-s", block_delimiter="b", extract_images=False)
638
+ chat.load_file(file_name=file_name, prefix="p-", suffix="-f", block_delimiter="b", extract_images=False)
639
639
 
640
640
  if file_type == "binary":
641
641
  mock_load_binary.assert_called_once_with(
642
642
  file_path=file_name,
643
643
  mime_type=mime_type,
644
644
  prefix="p-",
645
- suffix="-s"
645
+ suffix="-f"
646
646
  )
647
647
  mock_load_text.assert_not_called()
648
648
  mock_load_document.assert_not_called()
@@ -652,7 +652,7 @@ def test_load_file(temp_chat_file, file_name, file_type, mime_type):
652
652
  mock_load_document.assert_called_once_with(
653
653
  file_path=file_name,
654
654
  prefix="p-",
655
- suffix="-s",
655
+ suffix="-f",
656
656
  block_delimiter="b",
657
657
  extract_images=False
658
658
  )
@@ -661,7 +661,7 @@ def test_load_file(temp_chat_file, file_name, file_type, mime_type):
661
661
  mock_load_text.assert_called_once_with(
662
662
  file_path=file_name,
663
663
  prefix="p-",
664
- suffix="-s",
664
+ suffix="-f",
665
665
  block_delimiter="b",
666
666
  extract_images=False
667
667
  )
@@ -714,7 +714,7 @@ def test_load_helper(monkeypatch, capsys, temp_chat_file, directory, pattern, fi
714
714
  def mock_input(prompt):
715
715
  return user_input
716
716
 
717
- def mock_load_file(self, file_path, prefix="", suffix=""):
717
+ def mock_load_file(self, file_path):
718
718
  return True
719
719
 
720
720
  monkeypatch.setattr(glob, 'glob', mock_glob)
@@ -750,7 +750,7 @@ def test_load_helper_with_exclude(monkeypatch, capsys, temp_chat_file, directory
750
750
  def mock_input(prompt):
751
751
  return user_input
752
752
 
753
- def mock_load_file(self, file_path, prefix="", suffix=""):
753
+ def mock_load_file(self, file_path):
754
754
  return True
755
755
 
756
756
  monkeypatch.setattr(glob, 'glob', mock_glob)
@@ -907,7 +907,7 @@ def test_load_image(capsys, temp_chat_file, file_name, is_image, expected_mime):
907
907
  chat = Chat(temp_chat_file.name, reset=False)
908
908
 
909
909
  with patch.object(chat, 'load_binary_file', return_value=True) as mock_load_binary:
910
- chat.load_image(file_name=file_name, prefix="p-", suffix="-s")
910
+ chat.load_image(file_name=file_name, prefix="p-", suffix="-f")
911
911
 
912
912
  if is_image:
913
913
  # FIX: The called method's parameter is `file_path`, not `file_name`.
@@ -915,7 +915,7 @@ def test_load_image(capsys, temp_chat_file, file_name, is_image, expected_mime):
915
915
  file_path=file_name,
916
916
  mime_type=expected_mime,
917
917
  prefix="p-",
918
- suffix="-s"
918
+ suffix="-f"
919
919
  )
920
920
  else:
921
921
  mock_load_binary.assert_not_called()
@@ -1232,13 +1232,14 @@ def test_do_EXTRACT(MockExtractCommand, temp_chat_file):
1232
1232
  with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1233
1233
  chat = Chat(temp_chat_file.name, reset=False)
1234
1234
 
1235
- # FIX: The `onecmd_plus_hooks` method requires the `orig_rl_history_length` argument.
1235
+ # The `onecmd_plus_hooks` method requires the `orig_rl_history_length` argument.
1236
1236
  # We can pass a dummy value like 0 for the test.
1237
1237
  chat.onecmd_plus_hooks("EXTRACT", orig_rl_history_length=0)
1238
1238
 
1239
1239
  MockExtractCommand.assert_called_once_with(
1240
1240
  file_name=chat.chat_name,
1241
- skip_queries=False,
1241
+ force=False,
1242
+ write=False,
1242
1243
  output=chat.poutput,
1243
1244
  error_output=chat.perror
1244
1245
  )