ara-cli 0.1.9.65__py3-none-any.whl → 0.1.9.67__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (35) hide show
  1. ara_cli/ara_command_action.py +15 -5
  2. ara_cli/ara_command_parser.py +4 -1
  3. ara_cli/ara_config.py +9 -19
  4. ara_cli/artefact_autofix.py +73 -27
  5. ara_cli/artefact_models/artefact_model.py +1 -4
  6. ara_cli/artefact_scan.py +11 -1
  7. ara_cli/prompt_handler.py +14 -14
  8. ara_cli/version.py +1 -1
  9. {ara_cli-0.1.9.65.dist-info → ara_cli-0.1.9.67.dist-info}/METADATA +1 -1
  10. {ara_cli-0.1.9.65.dist-info → ara_cli-0.1.9.67.dist-info}/RECORD +34 -33
  11. {ara_cli-0.1.9.65.dist-info → ara_cli-0.1.9.67.dist-info}/top_level.txt +1 -0
  12. tests/__init__.py +0 -0
  13. tests/test_ara_autofix.py +219 -0
  14. {ara_cli/tests → tests}/test_artefact_renamer.py +3 -5
  15. {ara_cli/tests → tests}/test_artefact_scan.py +37 -10
  16. {ara_cli/tests → tests}/test_chat.py +39 -22
  17. {ara_cli/tests → tests}/test_file_creator.py +1 -1
  18. {ara_cli/tests → tests}/test_template_manager.py +37 -21
  19. ara_cli/tests/test_ara_autofix.py +0 -113
  20. /ara_cli/{tests → artefact_models}/__init__.py +0 -0
  21. {ara_cli-0.1.9.65.dist-info → ara_cli-0.1.9.67.dist-info}/WHEEL +0 -0
  22. {ara_cli-0.1.9.65.dist-info → ara_cli-0.1.9.67.dist-info}/entry_points.txt +0 -0
  23. {ara_cli/tests → tests}/test_ara_command_action.py +0 -0
  24. {ara_cli/tests → tests}/test_ara_config.py +0 -0
  25. {ara_cli/tests → tests}/test_artefact_fuzzy_search.py +0 -0
  26. {ara_cli/tests → tests}/test_artefact_link_updater.py +0 -0
  27. {ara_cli/tests → tests}/test_artefact_lister.py +0 -0
  28. {ara_cli/tests → tests}/test_artefact_reader.py +0 -0
  29. {ara_cli/tests → tests}/test_classifier.py +0 -0
  30. {ara_cli/tests → tests}/test_directory_navigator.py +0 -0
  31. {ara_cli/tests → tests}/test_file_classifier.py +0 -0
  32. {ara_cli/tests → tests}/test_file_lister.py +0 -0
  33. {ara_cli/tests → tests}/test_list_filter.py +0 -0
  34. {ara_cli/tests → tests}/test_tag_extractor.py +0 -0
  35. {ara_cli/tests → tests}/test_update_config_prompt.py +0 -0
@@ -576,22 +576,32 @@ def scan_action(args):
576
576
  def autofix_action(args):
577
577
  from ara_cli.artefact_autofix import parse_report, apply_autofix, read_report_file
578
578
 
579
- content = read_report_file()
579
+ # If the user passes --non-deterministic, only_deterministic_fix becomes False.
580
+ # If the user passes --deterministic, only_non_deterministic_fix becomes False.
581
+ # If no flags are passed, both are True, and all fixes are attempted.
582
+ run_deterministic = not args.non_deterministic
583
+ run_non_deterministic = not args.deterministic
580
584
 
585
+ content = read_report_file()
581
586
  if not content:
582
587
  return False
583
588
 
584
589
  issues = parse_report(content)
585
-
586
590
  if not issues:
587
591
  print("No issues found in the report. Nothing to fix.")
588
592
  return
589
593
 
590
- # print("Found issues to fix:")
594
+ # print("\nStarting autofix process...")
591
595
  for classifier, files in issues.items():
592
596
  print(f"\nClassifier: {classifier}")
593
597
  for file_path, reason in files:
594
- print(f"Attempting to fix {file_path} for reason: {reason}")
595
- apply_autofix(file_path, classifier, reason)
598
+ apply_autofix(
599
+ file_path,
600
+ classifier,
601
+ reason,
602
+ deterministic=run_deterministic,
603
+ non_deterministic=run_non_deterministic
604
+ )
596
605
 
597
606
  print("\nAutofix process completed. Please review the changes.")
607
+
@@ -229,7 +229,10 @@ def scan_parser(subparsers):
229
229
 
230
230
 
231
231
  def autofix_parser(subparsers):
232
- subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command.")
232
+ autofix_parser = subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command.")
233
+ determinism_group = autofix_parser.add_mutually_exclusive_group()
234
+ determinism_group.add_argument("--deterministic", "-d", action="store_true", help="Run only deterministic fixes e.g Title-FileName Mismatch fix")
235
+ determinism_group.add_argument("--non-deterministic", "-nd", action="store_true", help="Run only non-deterministic fixes")
233
236
 
234
237
 
235
238
  class CustomHelpFormatter(argparse.HelpFormatter):
ara_cli/ara_config.py CHANGED
@@ -42,9 +42,9 @@ class ARAconfig(BaseModel):
42
42
  "model": "openai/gpt-4o",
43
43
  "temperature": 0.8
44
44
  },
45
- "gpt-4.5-preview": {
45
+ "gpt-4.1": {
46
46
  "provider": "openai",
47
- "model": "openai/gpt-4.5-preview",
47
+ "model": "openai/gpt-4.1",
48
48
  "temperature": 0.8,
49
49
  },
50
50
  "o3-mini": {
@@ -52,9 +52,14 @@ class ARAconfig(BaseModel):
52
52
  "model": "openai/o3-mini",
53
53
  "temperature": 1.0,
54
54
  },
55
- "claude-3.7": {
55
+ "opus-4": {
56
+ "provider": "anthropic",
57
+ "model": "anthropic/claude-opus-4-20250514",
58
+ "temperature": 0.8,
59
+ },
60
+ "sonnet-4": {
56
61
  "provider": "anthropic",
57
- "model": "anthropic/claude-3-7-sonnet-20250219",
62
+ "model": "anthropic/claude-sonnet-4-20250514",
58
63
  "temperature": 0.8,
59
64
  },
60
65
  "together-ai-llama-2": {
@@ -67,21 +72,6 @@ class ARAconfig(BaseModel):
67
72
  "model": "groq/llama3-70b-8192",
68
73
  "temperature": 0.8,
69
74
  },
70
- "opus-4": {
71
- "provider": "anthropic",
72
- "model": "anthropic/claude-opus-4-20250514",
73
- "temperature": 0.8,
74
- },
75
- "sonnet-3.7": {
76
- "provider": "anthropic",
77
- "model": "anthropic/claude-3-7-sonnet-20250219",
78
- "temperature": 0.8,
79
- },
80
- "sonnet-4": {
81
- "provider": "anthropic",
82
- "model": "anthropic/claude-sonnet-4-20250514",
83
- "temperature": 0.8,
84
- },
85
75
  }
86
76
  default_llm: Optional[str] = "gpt-4o"
87
77
 
@@ -1,3 +1,6 @@
1
+ import os
2
+ from typing import Dict, List, Tuple
3
+
1
4
  def read_report_file():
2
5
  file_path = "incompatible_artefacts_report.md"
3
6
  try:
@@ -5,12 +8,11 @@ def read_report_file():
5
8
  content = f.read()
6
9
  except OSError:
7
10
  print('Artefact scan results file not found. Did you run the "ara scan" command?')
8
- return
9
-
11
+ return None
10
12
  return content
11
13
 
12
14
 
13
- def parse_report(content: str) -> dict:
15
+ def parse_report(content: str) -> Dict[str, List[Tuple[str, str]]]:
14
16
  """
15
17
  Parses the incompatible artefacts report and returns structured data.
16
18
  Returns a dictionary where keys are artefact classifiers, and values are lists of (file_path, reason) tuples.
@@ -20,31 +22,27 @@ def parse_report(content: str) -> dict:
20
22
  current_classifier = None
21
23
 
22
24
  if not lines or lines[0] != "# Artefact Check Report":
23
- return issues # Geçersiz rapor formatı
25
+ return issues
24
26
 
25
27
  if len(lines) >= 3 and lines[2] == "No problems found.":
26
- return issues # Hiç sorun bulunamadı
28
+ return issues
27
29
 
28
- for line in lines[1:]: # Başlıktan sonraki satırları işle
30
+ for line in lines[1:]:
29
31
  line = line.strip()
30
32
  if not line:
31
33
  continue
32
34
 
33
- # Classifier başlığı tespiti (## ile başlayan)
34
35
  if line.startswith("## "):
35
36
  current_classifier = line[3:].strip()
36
37
  issues[current_classifier] = []
37
38
 
38
- # Dosya listesi tespiti (- ile başlayan)
39
39
  elif line.startswith("- ") and current_classifier is not None:
40
- # Format: "- `file_path`: reason"
41
40
  parts = line.split("`", 2)
42
41
  if len(parts) < 3:
43
- continue # Geçersiz format
42
+ continue
44
43
 
45
44
  file_path = parts[1]
46
- reason = parts[2].split(
47
- ":", 1)[1].strip() if ":" in parts[2] else ""
45
+ reason = parts[2].split(":", 1)[1].strip() if ":" in parts[2] else ""
48
46
  issues[current_classifier].append((file_path, reason))
49
47
 
50
48
  return issues
@@ -53,7 +51,7 @@ def parse_report(content: str) -> dict:
53
51
  def read_artefact(file_path):
54
52
  """Reads the artefact text from the given file path."""
55
53
  try:
56
- with open(file_path, 'r') as file:
54
+ with open(file_path, 'r', encoding="utf-8") as file:
57
55
  return file.read()
58
56
  except FileNotFoundError:
59
57
  print(f"File not found: {file_path}")
@@ -82,7 +80,7 @@ def construct_prompt(artefact_type, reason, file_path, artefact_text):
82
80
  from ara_cli.artefact_models.artefact_model import ArtefactType
83
81
 
84
82
  prompt = (
85
- f"Correct the following {artefact_type} artefact to fix the issue: {reason}. "
83
+ f"Correct the following {artefact_type.value} artefact to fix the issue: {reason}. "
86
84
  "Provide the corrected artefact. Do not reformulate the artefact, "
87
85
  "just fix the pydantic model errors, use correct grammar. "
88
86
  "You should follow the name of the file "
@@ -113,33 +111,81 @@ def run_agent(prompt, artefact_class):
113
111
  from pydantic_ai import Agent
114
112
  # gpt-4o
115
113
  # anthropic:claude-3-7-sonnet-20250219
116
- agent = Agent(model="anthropic:claude-3-7-sonnet-20250219",
114
+ # anthropic:claude-4-sonnet-20250514
115
+ agent = Agent(model="anthropic:claude-4-sonnet-20250514",
117
116
  result_type=artefact_class, instrument=True)
118
117
  result = agent.run_sync(prompt)
119
118
  return result.data
120
119
 
121
120
 
122
121
  def write_corrected_artefact(file_path, corrected_text):
123
- with open(file_path, 'w') as file:
122
+ with open(file_path, 'w', encoding="utf-8") as file:
124
123
  file.write(corrected_text)
125
124
  print(f"Fixed artefact at {file_path}")
126
125
 
127
126
 
128
- def apply_autofix(file_path, classifier, reason):
127
+ def fix_title_mismatch(file_path: str, artefact_text: str, artefact_class) -> str:
128
+ """
129
+ Deterministically fixes the title in the artefact text to match the filename.
130
+ """
131
+ base_name = os.path.basename(file_path)
132
+ correct_title_underscores, _ = os.path.splitext(base_name)
133
+ correct_title_spaces = correct_title_underscores.replace('_', ' ')
134
+
135
+ title_prefix = artefact_class._title_prefix()
136
+
137
+ lines = artefact_text.splitlines()
138
+ new_lines = []
139
+ title_found_and_replaced = False
140
+
141
+ for line in lines:
142
+ if not title_found_and_replaced and line.strip().startswith(title_prefix):
143
+ new_lines.append(f"{title_prefix} {correct_title_spaces}")
144
+ title_found_and_replaced = True
145
+ else:
146
+ new_lines.append(line)
147
+
148
+ if not title_found_and_replaced:
149
+ print(f"Warning: Title prefix '{title_prefix}' not found in {file_path}. Title could not be fixed.")
150
+ return artefact_text
151
+
152
+ return "\n".join(new_lines)
153
+
154
+
155
+ def apply_autofix(file_path: str, classifier: str, reason: str, deterministic: bool, non_deterministic: bool) -> bool:
129
156
  artefact_text = read_artefact(file_path)
130
157
  if artefact_text is None:
131
- return
158
+ return False
132
159
 
133
- artefact_type, artefact_class = determine_artefact_type_and_class(
134
- classifier)
160
+ artefact_type, artefact_class = determine_artefact_type_and_class(classifier)
135
161
  if artefact_type is None or artefact_class is None:
136
- return
162
+ return False
137
163
 
138
- prompt = construct_prompt(artefact_type, reason, file_path, artefact_text)
164
+ is_deterministic_issue = "Filename-Title Mismatch" in reason
139
165
 
140
- try:
141
- corrected_artefact = run_agent(prompt, artefact_class)
142
- corrected_text = corrected_artefact.serialize()
166
+ if deterministic and is_deterministic_issue:
167
+ print(f"Attempting deterministic fix for {file_path}...")
168
+ corrected_text = fix_title_mismatch(file_path, artefact_text, artefact_class)
143
169
  write_corrected_artefact(file_path, corrected_text)
144
- except Exception as e:
145
- print(f"Failed to fix artefact at {file_path}: {e}")
170
+ return True
171
+
172
+ # Attempt non-deterministic fix if requested and the issue is NOT deterministic
173
+ if non_deterministic and not is_deterministic_issue:
174
+ print(f"Attempting non-deterministic (LLM) fix for {file_path}...")
175
+ prompt = construct_prompt(artefact_type, reason, file_path, artefact_text)
176
+ try:
177
+ corrected_artefact = run_agent(prompt, artefact_class)
178
+ corrected_text = corrected_artefact.serialize()
179
+ write_corrected_artefact(file_path, corrected_text)
180
+ return True
181
+ except Exception as e:
182
+ print(f"LLM agent failed to fix artefact at {file_path}: {e}")
183
+ return False
184
+
185
+ # Log if a fix was skipped due to flags
186
+ if is_deterministic_issue and not deterministic:
187
+ print(f"Skipping deterministic fix for {file_path} as per request.")
188
+ elif not is_deterministic_issue and not non_deterministic:
189
+ print(f"Skipping non-deterministic fix for {file_path} as per request.")
190
+
191
+ return False
@@ -49,16 +49,13 @@ class Contribution(BaseModel):
49
49
 
50
50
  @model_validator(mode="after")
51
51
  def validate_parent(self) -> Self:
52
+
52
53
  artefact_name = self.artefact_name
53
54
  classifier = self.classifier
54
55
  rule = self.rule
55
56
 
56
57
  if artefact_name:
57
58
  artefact_name = replace_space_with_underscore(artefact_name)
58
- if artefact_name and classifier:
59
- artefact_name = artefact_name.removesuffix(f"_{classifier}")
60
- artefact_name = artefact_name.removesuffix(f"_{classifier.capitalize()}")
61
- self.artefact_name = artefact_name
62
59
  if not artefact_name or not classifier:
63
60
  self.artefact_name = None
64
61
  self.classifier = None
ara_cli/artefact_scan.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from textwrap import indent
2
+ import os
2
3
 
3
4
 
4
5
  def check_file(file_path, artefact_class):
@@ -9,7 +10,16 @@ def check_file(file_path, artefact_class):
9
10
  except OSError as e:
10
11
  return False, f"File error: {e}"
11
12
  try:
12
- artefact_class.deserialize(content)
13
+ artefact_instance = artefact_class.deserialize(content)
14
+
15
+ base_name = os.path.basename(file_path)
16
+ file_name_without_ext, _ = os.path.splitext(base_name)
17
+
18
+ if artefact_instance.title != file_name_without_ext:
19
+ reason = (f"Filename-Title Mismatch: The file name '{file_name_without_ext}' "
20
+ f"does not match the artefact title '{artefact_instance.title}'.")
21
+ return False, reason
22
+
13
23
  return True, None
14
24
  except (ValidationError, ValueError, AssertionError) as e:
15
25
  return False, str(e)
ara_cli/prompt_handler.py CHANGED
@@ -81,7 +81,8 @@ def send_prompt(prompt):
81
81
  completion = litellm.completion(
82
82
  **config_parameters,
83
83
  messages=prompt,
84
- stream=True
84
+ stream=True,
85
+ max_tokens=8192
85
86
  )
86
87
  for chunk in completion:
87
88
  yield chunk
@@ -357,10 +358,8 @@ def prepend_system_prompt(message_list):
357
358
  def append_images_to_message(message, image_data_list):
358
359
  if not image_data_list:
359
360
  return message
360
- new_content_list = [{"type": "text", "text": message}]
361
- new_content_list.extend(image_data_list)
362
-
363
- message["content"] = new_content_list
361
+ message_content = message["content"]
362
+ message["content"] = message_content + image_data_list
364
363
 
365
364
  return message
366
365
 
@@ -380,23 +379,24 @@ def create_and_send_custom_prompt(classifier, parameter):
380
379
  append_headings(classifier, parameter, "prompt")
381
380
  write_prompt_result(classifier, parameter, prompt)
382
381
 
383
- base_message = {
382
+ message = {
384
383
  "role": "user",
385
384
  "content": combined_content_markdown
386
385
  }
387
386
 
388
- final_message = append_images_to_message(base_message, image_data_list)
389
-
390
- message_list_to_send = [final_message]
387
+ message_list = [message]
391
388
 
389
+ message_list = append_images_to_message(message_list, image_data_list)
392
390
  append_headings(classifier, parameter, "result")
391
+
393
392
  artefact_data_path = f"ara/{sub_directory}/{parameter}.data/{classifier}.prompt_log.md"
394
393
  with open(artefact_data_path, 'a') as file:
395
- for chunk in send_prompt(message_list_to_send):
396
- content = chunk.choices[0].delta.content
397
- if content:
398
- file.write(content)
399
- file.flush()
394
+ for chunk in send_prompt(message_list):
395
+ chunk_content = chunk.choices[0].delta.content
396
+ if not chunk_content:
397
+ continue
398
+ file.write(chunk_content)
399
+ file.flush()
400
400
  # write_prompt_result(classifier, parameter, response)
401
401
 
402
402
 
ara_cli/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.9.65" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
2
+ __version__ = "0.1.9.67" # 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.65
3
+ Version: 0.1.9.67
4
4
  Requires-Dist: litellm
5
5
  Requires-Dist: llama-index
6
6
  Requires-Dist: llama-index-llms-openai
@@ -1,9 +1,9 @@
1
1
  ara_cli/__init__.py,sha256=0zl7IegxTid26EBGLav_fXZ4CCIV3H5TfAoFQiOHjvg,148
2
2
  ara_cli/__main__.py,sha256=Z6XYWRLceIoZPvfC-X9EXouSZdtFOOe84kKVWJGA4r4,1861
3
- ara_cli/ara_command_action.py,sha256=NN1DrmTEbLAUUHIFINDaLyp8gmKZA9cFw4cED0CFe9U,21177
4
- ara_cli/ara_command_parser.py,sha256=HHPHLYzvn2bGVjwZ7fhdKgwiHHJDbERRdLcTe8G9B1Q,17588
5
- ara_cli/ara_config.py,sha256=_Arkr-b9XnrNHbBlFKb9tAo3OmdP4ZZiWvbY9m6Sbo0,4178
6
- ara_cli/artefact_autofix.py,sha256=T0TJ6vBxveMoOKm6c22c6D-iMuBHlCksvNLil_O6xaM,4885
3
+ ara_cli/ara_command_action.py,sha256=dD97IeH7x5udKC_APk5TbuVXPiyU2XSBV2uqy52nQ9A,21625
4
+ ara_cli/ara_command_parser.py,sha256=v8LUdkBSI2771gI53PdrxtD8YVjhk-7E8vgTEsTGnRM,17952
5
+ ara_cli/ara_config.py,sha256=gOeaFR5Bkxk1EbN7qvJuHeaLICgy7irCREOIQD6_98M,3832
6
+ ara_cli/artefact_autofix.py,sha256=-7XHl7j5O1kwWeBeIybnA4CI8v6SlXRNkV1KQ9nSWLQ,6817
7
7
  ara_cli/artefact_creator.py,sha256=tUNCNvfFYMheyF_viyrQhm2-43AkbHFoQaHui9ntvws,6002
8
8
  ara_cli/artefact_deleter.py,sha256=Co4wwCH3yW8H9NrOq7_2p5571EeHr0TsfE-H8KqoOfY,1900
9
9
  ara_cli/artefact_fuzzy_search.py,sha256=BBDe-IP75sWZjG6nTNFtVljjL01JlQUy5ccJBZ6Trow,2429
@@ -11,7 +11,7 @@ ara_cli/artefact_link_updater.py,sha256=itMS_Z64jE8bBly9WA01z8PqkBeNW6ntTO7ryMeC
11
11
  ara_cli/artefact_lister.py,sha256=jhk4n4eqp7hDIq07q43QzS7-36BM3OfZ4EABxCeOGcw,4764
12
12
  ara_cli/artefact_reader.py,sha256=_RqBY1f1DWH2DThXETYuHeTB2ip0wgYuGvHYI_D6EJ4,8062
13
13
  ara_cli/artefact_renamer.py,sha256=loIn1DF9kVnjhH7wP1v5qUvt3s0uKeWXuQPrHXenQGE,4025
14
- ara_cli/artefact_scan.py,sha256=7PH7TE2B2Py7wGQxl7EYM3r6Fr9kVLAFtNErP0QRoXM,1950
14
+ ara_cli/artefact_scan.py,sha256=J3aCAOltVr1oS6Lwnv51gtZC_G8faORGTccFY3kkBX4,2368
15
15
  ara_cli/chat.py,sha256=7xTtPEDk052_wmIzoti7GavEJ1vpRxe5c084WQ1C7dg,28617
16
16
  ara_cli/classifier.py,sha256=zWskj7rBYdqYBGjksBm46iTgVU5IIf2PZsJr4qeiwVU,1878
17
17
  ara_cli/codefusionretriever.py,sha256=fCHgXdIBRzkVAnapX-KI2NQ44XbrrF4tEQmn5J6clUI,1980
@@ -25,16 +25,17 @@ ara_cli/list_filter.py,sha256=Not17hIngI37gZsLtIKxopB-BmyWoOGlBzSqBwh-Zpc,5273
25
25
  ara_cli/output_suppressor.py,sha256=ZByUwLH2DxOb-eJ31KQbtIziBKdykoyxvwxZ0tSammA,371
26
26
  ara_cli/prompt_chat.py,sha256=kd_OINDQFit6jN04bb7mzgY259JBbRaTaNp9F-webkc,1346
27
27
  ara_cli/prompt_extractor.py,sha256=a8LwPj6U8sG_v3SqDXQyPvDZQds4kHnYSO8eGissYJA,7503
28
- ara_cli/prompt_handler.py,sha256=1gz2VkK7zl0RAzit1UmxplNNd2Zu6J_7eHocgKiw_hQ,17473
28
+ ara_cli/prompt_handler.py,sha256=3r7GeiOOoasZ4nz0AT51Sw5M_hyHq-RFTGPQIxT_CQk,17458
29
29
  ara_cli/prompt_rag.py,sha256=vmlt4-rSboWibwgO_KUF79TK99YXT5KXjmbD9FeWdZY,7449
30
30
  ara_cli/run_file_lister.py,sha256=XbrrDTJXp1LFGx9Lv91SNsEHZPP-PyEMBF_P4btjbDA,2360
31
31
  ara_cli/tag_extractor.py,sha256=4krQyvmLR2ffhe7N7lWC7QjaxXcb90HaQdmjnBiD8ak,2523
32
32
  ara_cli/template_manager.py,sha256=YXPj2jGNDb-diIHFEK_vGJ-ZucodnXSGAPofKTnOofI,6633
33
33
  ara_cli/update_config_prompt.py,sha256=PZgNIN3dTw6p80GyX8Sp5apkAhSoykwnkEbHo3IOkUo,4571
34
- ara_cli/version.py,sha256=1Z37icnDfTLV_xa6229A5btyIc1fDW2rl72E31bdi5s,146
34
+ ara_cli/version.py,sha256=ijl3fM4h2e9CBlwsSPtThOSO0Gz29LkbJ34xKFGx-v0,146
35
+ ara_cli/artefact_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
36
  ara_cli/artefact_models/artefact_load.py,sha256=dNcwZDW2Dk0bts9YnPZ0ESmWD2NbsLIvl4Z-qQeGmTQ,401
36
37
  ara_cli/artefact_models/artefact_mapping.py,sha256=8aD0spBjkJ8toMAmFawc6UTUxB6-tEEViZXv2I-r88Q,1874
37
- ara_cli/artefact_models/artefact_model.py,sha256=SRVfeHVKCMJ8L9lqPzAr_MgI4m7mGdcYx0SE2ja-NEI,14949
38
+ ara_cli/artefact_models/artefact_model.py,sha256=nZZS6WyVuODblLnVTH6VZli--eFrDiwNMtjwHTs2KBI,14703
38
39
  ara_cli/artefact_models/artefact_templates.py,sha256=Vd7SwoRVKNGKZmxBKS6f9FE1ThUOCqZLScu0ClPfIu8,8321
39
40
  ara_cli/artefact_models/businessgoal_artefact_model.py,sha256=jqYFMXjWle0YW9RvcFLDBAwy61bdT5VuDT_6lTOFzMw,4853
40
41
  ara_cli/artefact_models/capability_artefact_model.py,sha256=SZqHx4O2mj4urn77Stnj4_Jxtlq3-LgBBU9SMkByppI,3079
@@ -130,28 +131,28 @@ ara_cli/templates/specification_breakdown_files/template.step.exploration.md,sha
130
131
  ara_cli/templates/specification_breakdown_files/template.step.md,sha256=nzDRl9Xo-fJKeDKxIRh9njidV1SOd2ZOPi7eM3YG9DU,1052
131
132
  ara_cli/templates/specification_breakdown_files/template.technology.exploration.md,sha256=zQyiJcmbUfXdte-5uZwZUpT6ey0zwfZ00P4VwI97jQk,2274
132
133
  ara_cli/templates/specification_breakdown_files/template.technology.md,sha256=bySiksz-8xtq0Nnj4svqe2MgUftWrVkbK9AcrDUE3KY,952
133
- ara_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
- ara_cli/tests/test_ara_autofix.py,sha256=8AaSuherD2dvo2wMFafgHnkLhSnMs6ZW6RtX-5VKsls,4248
135
- ara_cli/tests/test_ara_command_action.py,sha256=Y5MrG6VjXgebliKfdFaCaS8i3GoZCGSLpj3AWCbL5Lk,25695
136
- ara_cli/tests/test_ara_config.py,sha256=1LWby_iSestTIIqK-1clggL8kmbGGbtlYfsxAHaMMF8,2232
137
- ara_cli/tests/test_artefact_fuzzy_search.py,sha256=5Sh3_l9QK8-WHn6JpGPU1b6h4QEnl2JoMq1Tdp2cj1U,1261
138
- ara_cli/tests/test_artefact_link_updater.py,sha256=gN5KFF1uY7OoBh8Mr5jWpqXp02YCU5OSIpSU76Rm4Gs,2137
139
- ara_cli/tests/test_artefact_lister.py,sha256=VCEOCgDgnAOeUUgIoGAbWgz60hf9UT-tdHg18LGfB34,22656
140
- ara_cli/tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
141
- ara_cli/tests/test_artefact_renamer.py,sha256=syL3qH8pPRmxPkpmKXInZ7WTI8X487YJXsgiZOtv2C4,3490
142
- ara_cli/tests/test_artefact_scan.py,sha256=I2rpSoRQ2auBbWjmGnm7VdTSS811tkAgUyK2dS69KVQ,4780
143
- ara_cli/tests/test_chat.py,sha256=V75baLk2ZFz5WDSFTlvdbmMb6Dm7o12xoFEulmMgMDI,46765
144
- ara_cli/tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
145
- ara_cli/tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
146
- ara_cli/tests/test_file_classifier.py,sha256=hbGp0-_A_LgQ0pGv1jWDEIyCgvDyfChcvvVfbxjNY2U,10938
147
- ara_cli/tests/test_file_creator.py,sha256=G257M1duenDrgLCSql3wVWNuzcxyQqLQDybfbxiGYN0,2100
148
- ara_cli/tests/test_file_lister.py,sha256=f6B_vIv-wAulKH2ZGgNg4SG79XqGGbfwoIvZlbEnYyM,4306
149
- ara_cli/tests/test_list_filter.py,sha256=gSRKirTtFuhRS3QlFHqWl89WvCvAdVEnFsCWTYmgB2o,7928
150
- ara_cli/tests/test_tag_extractor.py,sha256=nSiAYlTKZ7TLAOtcJpwK5zTWHhFYU0tI5xKnivLc1dU,2712
151
- ara_cli/tests/test_template_manager.py,sha256=bRxka6cxHsCAOvXjfG8MrVO8qSZXhxW01tnph80UtNk,3143
152
- ara_cli/tests/test_update_config_prompt.py,sha256=vSsLvc18HZdVjVM93qXWVbJt752xTLL6VGjSVCrPufk,6729
153
- ara_cli-0.1.9.65.dist-info/METADATA,sha256=nVFr74izr8-FxmQELehwrjkkIUwOQHDkAfwXeeMAFmQ,415
154
- ara_cli-0.1.9.65.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
155
- ara_cli-0.1.9.65.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
156
- ara_cli-0.1.9.65.dist-info/top_level.txt,sha256=zzee_PwFmKqfBi9XgIunP6xy2S4TIt593CLLxenNaAE,8
157
- ara_cli-0.1.9.65.dist-info/RECORD,,
134
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
+ tests/test_ara_autofix.py,sha256=EFVzhVTNPDRVxpoK0CY6OAM88EjZTRXh-uxFGS1AVWk,10695
136
+ tests/test_ara_command_action.py,sha256=Y5MrG6VjXgebliKfdFaCaS8i3GoZCGSLpj3AWCbL5Lk,25695
137
+ tests/test_ara_config.py,sha256=1LWby_iSestTIIqK-1clggL8kmbGGbtlYfsxAHaMMF8,2232
138
+ tests/test_artefact_fuzzy_search.py,sha256=5Sh3_l9QK8-WHn6JpGPU1b6h4QEnl2JoMq1Tdp2cj1U,1261
139
+ tests/test_artefact_link_updater.py,sha256=gN5KFF1uY7OoBh8Mr5jWpqXp02YCU5OSIpSU76Rm4Gs,2137
140
+ tests/test_artefact_lister.py,sha256=VCEOCgDgnAOeUUgIoGAbWgz60hf9UT-tdHg18LGfB34,22656
141
+ tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
142
+ tests/test_artefact_renamer.py,sha256=N9JnnKBEJChTYEdltHoiDnn5UCNQUPj9H9YqOvLzbH0,3458
143
+ tests/test_artefact_scan.py,sha256=7w4wqCj8VEwk676JiSqh0FCAOT-kFq624u2JDrhzpuw,5860
144
+ tests/test_chat.py,sha256=-00mni6Kik_RO8BGUpWqaL4S0wt2MbUBi5jD06dSHJM,47538
145
+ tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
146
+ tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
147
+ tests/test_file_classifier.py,sha256=hbGp0-_A_LgQ0pGv1jWDEIyCgvDyfChcvvVfbxjNY2U,10938
148
+ tests/test_file_creator.py,sha256=D3G7MbgE0m8JmZihxnTryxLco6iZdbV--2CGc0L20FM,2109
149
+ tests/test_file_lister.py,sha256=f6B_vIv-wAulKH2ZGgNg4SG79XqGGbfwoIvZlbEnYyM,4306
150
+ tests/test_list_filter.py,sha256=gSRKirTtFuhRS3QlFHqWl89WvCvAdVEnFsCWTYmgB2o,7928
151
+ tests/test_tag_extractor.py,sha256=nSiAYlTKZ7TLAOtcJpwK5zTWHhFYU0tI5xKnivLc1dU,2712
152
+ tests/test_template_manager.py,sha256=q-LMHRG4rHkD6ON6YW4cpZxUx9hul6Or8wVVRC2kb-8,4099
153
+ tests/test_update_config_prompt.py,sha256=vSsLvc18HZdVjVM93qXWVbJt752xTLL6VGjSVCrPufk,6729
154
+ ara_cli-0.1.9.67.dist-info/METADATA,sha256=LUsIQxYDmhuivLA0VNgiE2U47Y3jznv7C6D-z2IMOAc,415
155
+ ara_cli-0.1.9.67.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
156
+ ara_cli-0.1.9.67.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
157
+ ara_cli-0.1.9.67.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
158
+ ara_cli-0.1.9.67.dist-info/RECORD,,
tests/__init__.py ADDED
File without changes
@@ -0,0 +1,219 @@
1
+ import pytest
2
+ from unittest.mock import patch, mock_open, MagicMock
3
+ from ara_cli.artefact_autofix import (
4
+ read_report_file,
5
+ parse_report,
6
+ apply_autofix,
7
+ read_artefact,
8
+ determine_artefact_type_and_class,
9
+ run_agent,
10
+ write_corrected_artefact,
11
+ construct_prompt,
12
+ fix_title_mismatch
13
+ )
14
+ from ara_cli.artefact_models.artefact_model import ArtefactType
15
+
16
+ @pytest.fixture
17
+ def mock_artefact_type():
18
+ """Provides a mock for the ArtefactType enum member."""
19
+ mock_type = MagicMock()
20
+ mock_type.value = "feature"
21
+ return mock_type
22
+
23
+ @pytest.fixture
24
+ def mock_artefact_class():
25
+ """Provides a mock for the Artefact class."""
26
+ mock_class = MagicMock()
27
+ mock_class._title_prefix.return_value = "Feature:"
28
+ # Mock the serialize method for the agent tests
29
+ mock_class.serialize.return_value = "llm corrected content"
30
+ return mock_class
31
+
32
+
33
+ def test_read_report_file_success():
34
+ """Tests successful reading of the report file."""
35
+ mock_content = "# Artefact Check Report\n- `file.feature`: reason"
36
+ with patch("builtins.open", mock_open(read_data=mock_content)) as m:
37
+ content = read_report_file()
38
+ assert content == mock_content
39
+ m.assert_called_once_with("incompatible_artefacts_report.md", "r", encoding="utf-8")
40
+
41
+ def test_read_report_file_not_found(capsys):
42
+ with patch("builtins.open", side_effect=OSError("File not found")):
43
+ content = read_report_file()
44
+ assert content is None
45
+ assert "Artefact scan results file not found" in capsys.readouterr().out
46
+
47
+ def test_parse_report_with_issues():
48
+ content = "# Artefact Check Report\n\n## feature\n- `path/to/file.feature`: A reason\n"
49
+ expected = {"feature": [("path/to/file.feature", "A reason")]}
50
+ assert parse_report(content) == expected
51
+
52
+ def test_parse_report_no_issues():
53
+ content = "# Artefact Check Report\n\nNo problems found.\n"
54
+ assert parse_report(content) == {}
55
+
56
+ def test_parse_report_invalid_format():
57
+ assert parse_report("This is not a valid report") == {}
58
+
59
+ def test_parse_report_invalid_line_format():
60
+ content = "# Artefact Check Report\n\n## feature\n- an invalid line\n"
61
+ assert parse_report(content) == {"feature": []}
62
+
63
+ def test_read_artefact_success():
64
+ mock_content = "Feature: My Feature"
65
+ with patch("builtins.open", mock_open(read_data=mock_content)) as m:
66
+ content = read_artefact("file.feature")
67
+ assert content == mock_content
68
+ m.assert_called_once_with("file.feature", 'r', encoding="utf-8")
69
+
70
+ def test_read_artefact_file_not_found(capsys):
71
+ with patch("builtins.open", side_effect=FileNotFoundError):
72
+ result = read_artefact("nonexistent.feature")
73
+ assert result is None
74
+ assert "File not found: nonexistent.feature" in capsys.readouterr().out
75
+
76
+ @patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping")
77
+ def test_determine_artefact_type_and_class_no_class_found(mock_mapping, capsys):
78
+ mock_mapping.get.return_value = None
79
+ # The function returns (None, None) if the class is not in the mapping.
80
+ artefact_type, artefact_class = determine_artefact_type_and_class("feature")
81
+ assert artefact_type is None
82
+ assert artefact_class is None
83
+ # The print statement inside the function is called before returning, so this check is valid.
84
+ assert "No artefact class found for" in capsys.readouterr().out
85
+
86
+ @patch("ara_cli.artefact_models.artefact_model.ArtefactType", side_effect=ValueError)
87
+ def test_determine_artefact_type_and_class_invalid(mock_artefact_type_enum, capsys):
88
+ artefact_type, artefact_class = determine_artefact_type_and_class("invalid_classifier")
89
+ assert artefact_type is None
90
+ assert artefact_class is None
91
+ assert "Invalid classifier: invalid_classifier" in capsys.readouterr().out
92
+
93
+ def test_write_corrected_artefact():
94
+ with patch("builtins.open", mock_open()) as m:
95
+ write_corrected_artefact("file.feature", "corrected content")
96
+ m.assert_called_once_with("file.feature", 'w', encoding="utf-8")
97
+ m().write.assert_called_once_with("corrected content")
98
+
99
+ def test_construct_prompt_for_task():
100
+ prompt = construct_prompt(ArtefactType.task, "some reason", "file.task", "text")
101
+ assert "For task artefacts, if the action items looks like template or empty" in prompt
102
+
103
+ @patch("ara_cli.artefact_autofix.run_agent")
104
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class", return_value=(None, None))
105
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
106
+ def test_apply_autofix_exits_when_classifier_is_invalid(mock_read, mock_determine, mock_run_agent):
107
+ """Tests that apply_autofix exits early if the classifier is invalid."""
108
+ result = apply_autofix("file.feature", "invalid", "reason", deterministic=True, non_deterministic=True)
109
+ assert result is False
110
+ mock_read.assert_called_once_with("file.feature")
111
+ mock_determine.assert_called_once_with("invalid")
112
+ mock_run_agent.assert_not_called()
113
+
114
+ @patch("ara_cli.artefact_autofix.run_agent")
115
+ @patch("ara_cli.artefact_autofix.write_corrected_artefact")
116
+ @patch("ara_cli.artefact_autofix.fix_title_mismatch", return_value="fixed text")
117
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
118
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
119
+ def test_apply_autofix_for_title_mismatch_with_deterministic_flag(mock_read, mock_determine, mock_fix_title, mock_write, mock_run_agent, mock_artefact_type, mock_artefact_class):
120
+ """Tests that a deterministic fix is applied when the flag is True."""
121
+ mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
122
+ reason = "Filename-Title Mismatch: some details"
123
+
124
+ result = apply_autofix("file.feature", "feature", reason, deterministic=True, non_deterministic=False)
125
+
126
+ assert result is True
127
+ mock_fix_title.assert_called_once_with("file.feature", "original text", mock_artefact_class)
128
+ mock_write.assert_called_once_with("file.feature", "fixed text")
129
+ mock_run_agent.assert_not_called()
130
+
131
+ @patch("ara_cli.artefact_autofix.run_agent")
132
+ @patch("ara_cli.artefact_autofix.write_corrected_artefact")
133
+ @patch("ara_cli.artefact_autofix.fix_title_mismatch")
134
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
135
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
136
+ def test_apply_autofix_skips_title_mismatch_without_deterministic_flag(mock_read, mock_determine, mock_fix_title, mock_write, mock_run_agent, mock_artefact_type, mock_artefact_class):
137
+ """Tests that a deterministic fix is skipped when the flag is False."""
138
+ mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
139
+ reason = "Filename-Title Mismatch: some details"
140
+
141
+ result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
142
+
143
+ assert result is False
144
+ mock_fix_title.assert_not_called()
145
+ mock_write.assert_not_called()
146
+ mock_run_agent.assert_not_called()
147
+
148
+ @patch("ara_cli.artefact_autofix.write_corrected_artefact")
149
+ @patch("ara_cli.artefact_autofix.run_agent")
150
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
151
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
152
+ def test_apply_autofix_for_llm_fix_with_non_deterministic_flag(mock_read, mock_determine, mock_run_agent, mock_write, mock_artefact_type, mock_artefact_class):
153
+ """Tests that an LLM fix is applied when the non-deterministic flag is True."""
154
+ mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
155
+ mock_run_agent.return_value = mock_artefact_class
156
+ reason = "Pydantic validation error"
157
+
158
+ result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
159
+
160
+ assert result is True
161
+ mock_run_agent.assert_called_once()
162
+ mock_write.assert_called_once_with("file.feature", "llm corrected content")
163
+
164
+ @patch("ara_cli.artefact_autofix.write_corrected_artefact")
165
+ @patch("ara_cli.artefact_autofix.run_agent")
166
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
167
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
168
+ def test_apply_autofix_skips_llm_fix_without_non_deterministic_flag(mock_read, mock_determine, mock_run_agent, mock_write, mock_artefact_type, mock_artefact_class):
169
+ """Tests that an LLM fix is skipped when the non-deterministic flag is False."""
170
+ mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
171
+ reason = "Pydantic validation error"
172
+
173
+ result = apply_autofix("file.feature", "feature", reason, deterministic=True, non_deterministic=False)
174
+
175
+ assert result is False
176
+ mock_run_agent.assert_not_called()
177
+ mock_write.assert_not_called()
178
+
179
+ @patch("ara_cli.artefact_autofix.run_agent", side_effect=Exception("LLM failed"))
180
+ @patch("ara_cli.artefact_autofix.determine_artefact_type_and_class")
181
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value="original text")
182
+ def test_apply_autofix_llm_exception(mock_read, mock_determine, mock_run_agent, capsys, mock_artefact_type, mock_artefact_class):
183
+ """Tests that an exception during an LLM fix is handled gracefully."""
184
+ mock_determine.return_value = (mock_artefact_type, mock_artefact_class)
185
+ reason = "Pydantic validation error"
186
+
187
+ result = apply_autofix("file.feature", "feature", reason, deterministic=False, non_deterministic=True)
188
+
189
+ assert result is False
190
+ assert "LLM agent failed to fix artefact at file.feature: LLM failed" in capsys.readouterr().out
191
+
192
+ # === Other Tests ===
193
+
194
+ def test_fix_title_mismatch_success(mock_artefact_class):
195
+ artefact_text = "Feature: wrong title\nSome other content"
196
+ file_path = "path/to/correct_title.feature"
197
+
198
+ expected_text = "Feature: correct title\nSome other content"
199
+
200
+ result = fix_title_mismatch(file_path, artefact_text, mock_artefact_class)
201
+
202
+ assert result == expected_text
203
+ mock_artefact_class._title_prefix.assert_called_once()
204
+
205
+ def test_fix_title_mismatch_prefix_not_found(capsys, mock_artefact_class):
206
+ artefact_text = "No title prefix here"
207
+ file_path = "path/to/correct_title.feature"
208
+
209
+ result = fix_title_mismatch(file_path, artefact_text, mock_artefact_class)
210
+
211
+ assert result == artefact_text # Should return original text
212
+ assert "Warning: Title prefix 'Feature:' not found" in capsys.readouterr().out
213
+
214
+ @patch("pydantic_ai.Agent")
215
+ def test_run_agent_exception_handling(mock_agent_class):
216
+ mock_agent_instance = mock_agent_class.return_value
217
+ mock_agent_instance.run_sync.side_effect = Exception("Agent error")
218
+ with pytest.raises(Exception, match="Agent error"):
219
+ run_agent("prompt", MagicMock())
@@ -1,8 +1,6 @@
1
- from mock import patch
2
- from unittest.mock import call, mock_open
1
+ from unittest.mock import call, mock_open, patch, Mock
3
2
  from ara_cli.artefact_renamer import ArtefactRenamer
4
3
  from ara_cli.classifier import Classifier
5
- import mock
6
4
  import pytest
7
5
  import os
8
6
  import shutil
@@ -47,7 +45,7 @@ def test_rename_checks_new_name_provided():
47
45
  def test_update_title_in_artefact(mock_file, classifier, artefact_name, read_data_prefix, old_title, new_title):
48
46
  ar = ArtefactRenamer(os)
49
47
  read_data = f"{read_data_prefix}{old_title}\nOther content that remains unchanged."
50
- mock_file.return_value.read = mock.Mock(return_value=read_data)
48
+ mock_file.return_value.read = Mock(return_value=read_data)
51
49
  artefact_path = f"path/to/{classifier}.artefact"
52
50
 
53
51
  # Ensure that the mock for get_artefact_title returns the prefix without an extra colon and space
@@ -76,7 +74,7 @@ def test_update_title_no_title_line(mock_get_artefact_title, mock_file):
76
74
  ar = ArtefactRenamer()
77
75
 
78
76
  read_data = "content that remains unchanged."
79
- mock_file.return_value.read = mock.Mock(return_value=read_data)
77
+ mock_file.return_value.read = Mock(return_value=read_data)
80
78
  artefact_path = "path/to/artefact.vision"
81
79
 
82
80
  with pytest.raises(ValueError):
@@ -5,13 +5,36 @@ from pydantic import ValidationError
5
5
 
6
6
 
7
7
  def test_check_file_valid():
8
+ """Tests the happy path where the file is valid and the title matches."""
9
+ mock_artefact_instance = MagicMock()
10
+ mock_artefact_instance.title = "dummy_path"
11
+
8
12
  mock_artefact_class = MagicMock()
9
- mock_artefact_class.deserialize.return_value = None
13
+ mock_artefact_class.deserialize.return_value = mock_artefact_instance
10
14
 
11
15
  with patch("builtins.open", mock_open(read_data="valid content")):
12
- is_valid, reason = check_file("dummy_path", mock_artefact_class)
13
- assert is_valid is True
14
- assert reason is None
16
+ is_valid, reason = check_file("dummy_path.feature", mock_artefact_class)
17
+
18
+ assert is_valid is True
19
+ assert reason is None
20
+
21
+
22
+ def test_check_file_title_mismatch():
23
+ """Tests the case where the filename and artefact title do not match."""
24
+ mock_artefact_instance = MagicMock()
25
+ mock_artefact_instance.title = "wrong_title"
26
+
27
+ mock_artefact_class = MagicMock()
28
+ mock_artefact_class.deserialize.return_value = mock_artefact_instance
29
+
30
+ with patch("builtins.open", mock_open(read_data="content")):
31
+ is_valid, reason = check_file("correct_path.feature", mock_artefact_class)
32
+
33
+ assert is_valid is False
34
+ assert "Filename-Title Mismatch" in reason
35
+
36
+ assert "'correct_path'" in reason
37
+ assert "'wrong_title'" in reason
15
38
 
16
39
 
17
40
  def test_check_file_value_error():
@@ -60,26 +83,30 @@ def test_find_invalid_files():
60
83
  mock_artefact_class = MagicMock()
61
84
  with patch("ara_cli.artefact_models.artefact_mapping.artefact_type_mapping", {"test_classifier": mock_artefact_class}):
62
85
  artefact_files = {
63
- "test_classifier": [{"file_path": "file1.txt"}, {"file_path": "file2.txt"}, {"file_path": "templates/file3.txt"}]
86
+ "test_classifier": [
87
+ {"file_path": "file1.txt"}, # Should be checked
88
+ {"file_path": "file2.txt"}, # Should be checked
89
+ {"file_path": "templates/file3.txt"}, # Should be skipped
90
+ {"file_path": "some/path/file.data"} # Should be skipped
91
+ ]
64
92
  }
65
93
 
66
94
  with patch("ara_cli.artefact_scan.check_file") as mock_check_file:
67
95
  mock_check_file.side_effect = [
68
- (True, None), # file1.txt
69
- (False, "Invalid content") # file2.txt
96
+ (True, None), # for file1.txt
97
+ (False, "Invalid content") # for file2.txt
70
98
  ]
71
99
 
72
100
  invalid_files = find_invalid_files(
73
101
  artefact_files, "test_classifier")
74
102
  assert len(invalid_files) == 1
75
103
  assert invalid_files[0] == ("file2.txt", "Invalid content")
104
+ assert mock_check_file.call_count == 2
76
105
  mock_check_file.assert_has_calls([
77
106
  call("file1.txt", mock_artefact_class),
78
107
  call("file2.txt", mock_artefact_class)
79
108
  ], any_order=False)
80
109
 
81
- # Tests for show_results
82
-
83
110
 
84
111
  def test_show_results_no_issues(capsys):
85
112
  invalid_artefacts = {}
@@ -126,4 +153,4 @@ def test_show_results_with_issues(capsys):
126
153
  call("- `file3.txt`: reason3\n"),
127
154
  call("\n")
128
155
  ]
129
- handle.write.assert_has_calls(expected_writes, any_order=False)
156
+ handle.write.assert_has_calls(expected_writes, any_order=False)
@@ -1,16 +1,15 @@
1
1
  import pytest
2
2
  import os
3
3
  import tempfile
4
- import mock
5
4
  import glob
6
5
  import cmd2
6
+ import ara_cli
7
7
  from unittest.mock import patch, MagicMock, mock_open
8
8
  from types import SimpleNamespace
9
9
  from ara_cli.chat import Chat
10
10
  from ara_cli.template_manager import TemplatePathManager
11
11
  from ara_cli.ara_config import ConfigManager
12
12
 
13
-
14
13
  def get_default_config():
15
14
  return SimpleNamespace(
16
15
  ext_code_dirs=[
@@ -63,7 +62,7 @@ def temp_load_file():
63
62
 
64
63
 
65
64
  def test_handle_existing_chat_no_reset(temp_chat_file):
66
- with mock.patch('builtins.input', return_value='n'):
65
+ with patch('builtins.input', return_value='n'):
67
66
  mock_config = get_default_config()
68
67
  with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
69
68
  chat = Chat(temp_chat_file.name, reset=None)
@@ -71,7 +70,7 @@ def test_handle_existing_chat_no_reset(temp_chat_file):
71
70
 
72
71
 
73
72
  def test_handle_existing_chat_with_reset(temp_chat_file):
74
- with mock.patch('builtins.input', return_value='y'):
73
+ with patch('builtins.input', return_value='y'):
75
74
  mock_config = get_default_config()
76
75
  with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
77
76
  chat = Chat(temp_chat_file.name, reset=None)
@@ -931,36 +930,52 @@ def test_do_LOAD_COMMANDS(monkeypatch, temp_chat_file, commands_name, expected_d
931
930
 
932
931
 
933
932
  @pytest.mark.parametrize("template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern", [
934
- ("global/test_command", "commands", "*.commands.md", "mocked_custom_modules_path", "mocked_template_base_path/prompt-modules/commands/", "test_command"),
935
- ("local_command", "commands", "*.commands.md", "custom-prompt-modules", "/project/mocked_local_templates_path/custom-prompt-modules/commands", "local_command"),
936
- ("local_command", "commands", "*.commands.md", "mocked_custom_modules_path", "/project/mocked_local_templates_path/mocked_custom_modules_path/commands", "local_command"),
933
+ ("local_command", "commands", "*.commands.md", "custom-prompt-modules", "/mocked_local_templates_path/custom-prompt-modules/commands", "local_command"),
934
+ ("local_command", "commands", "*.commands.md", "mocked_custom_modules_path", "/mocked_local_templates_path/mocked_custom_modules_path/commands", "local_command"),
935
+ ("local_rule", "rules", "*.rules.md", "custom-prompt-modules", "/mocked_local_templates_path/custom-prompt-modules/rules", "local_rule"),
936
+ ("local_rule", "rules", "*.rules.md", "mocked_custom_modules_path", "/mocked_local_templates_path/mocked_custom_modules_path/rules", "local_rule"),
937
+ ("local_intention", "intention", "*.intentions.md", "custom-prompt-modules", "/mocked_local_templates_path/custom-prompt-modules/intentions", "local_intention"),
938
+ ("local_intention", "intention", "*.intentions.md", "mocked_custom_modules_path", "/mocked_local_templates_path/mocked_custom_modules_path/intentions", "local_intention"),
939
+ ("local_blueprint", "blueprint", "*.blueprints.md", "custom-prompt-modules", "/mocked_local_templates_path/custom-prompt-modules/blueprints", "local_blueprint"),
940
+ ("local_blueprint", "blueprint", "*.blueprints.md", "mocked_custom_modules_path", "/mocked_local_templates_path/mocked_custom_modules_path/blueprints", "local_blueprint")
941
+ ])
942
+ def test_load_template_local(monkeypatch, temp_chat_file, template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern):
943
+ expected_base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
944
+ expected_directory_abs = expected_base_dir + expected_directory
945
+ mock_config = get_default_config()
946
+ with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
947
+ chat = Chat(temp_chat_file.name, reset=False)
937
948
 
938
- ("global/test_rule", "rules", "*.rules.md", "mocked_custom_modules_path", "mocked_template_base_path/prompt-modules/rules/", "test_rule"),
939
- ("local_rule", "rules", "*.rules.md", "custom-prompt-modules", "/project/mocked_local_templates_path/custom-prompt-modules/rules", "local_rule"),
940
- ("local_rule", "rules", "*.rules.md", "mocked_custom_modules_path", "/project/mocked_local_templates_path/mocked_custom_modules_path/rules", "local_rule"),
949
+ mock_local_templates_path = "mocked_local_templates_path"
941
950
 
942
- ("global/test_intention", "intention", "*.intentions.md", "mocked_custom_modules_path", "mocked_template_base_path/prompt-modules/intentions/", "test_intention"),
943
- ("local_intention", "intention", "*.intentions.md", "custom-prompt-modules", "/project/mocked_local_templates_path/custom-prompt-modules/intentions", "local_intention"),
944
- ("local_intention", "intention", "*.intentions.md", "mocked_custom_modules_path", "/project/mocked_local_templates_path/mocked_custom_modules_path/intentions", "local_intention"),
951
+ monkeypatch.setattr(ConfigManager, 'get_config', lambda: MagicMock(local_prompt_templates_dir=mock_local_templates_path))
945
952
 
946
- ("global/test_blueprint", "blueprint", "*.blueprints.md", "mocked_custom_modules_path", "mocked_template_base_path/prompt-modules/blueprints/", "test_blueprint"),
947
- ("local_blueprint", "blueprint", "*.blueprints.md", "custom-prompt-modules", "/project/mocked_local_templates_path/custom-prompt-modules/blueprints", "local_blueprint"),
948
- ("local_blueprint", "blueprint", "*.blueprints.md", "mocked_custom_modules_path", "/project/mocked_local_templates_path/mocked_custom_modules_path/blueprints", "local_blueprint")
953
+ config = chat.config
954
+ config.local_prompt_templates_dir = mock_local_templates_path
955
+ config.custom_prompt_templates_subdir = custom_template_subdir
956
+
957
+ chat.config = config
958
+
959
+ with patch.object(chat, '_load_helper') as mock_load_helper:
960
+ chat._load_template_from_global_or_local(template_name, template_type)
961
+ mock_load_helper.assert_called_once_with(expected_directory_abs, expected_pattern, template_type)
962
+
963
+ @pytest.mark.parametrize("template_name, template_type, default_pattern, expected_directory, expected_pattern", [
964
+ ("global/test_command", "commands", "*.commands.md", "mocked_template_base_path/prompt-modules/commands/", "test_command"),
965
+ ("global/test_rule", "rules", "*.rules.md", "mocked_template_base_path/prompt-modules/rules/", "test_rule"),
966
+ ("global/test_intention", "intention", "*.intentions.md", "mocked_template_base_path/prompt-modules/intentions/", "test_intention"),
967
+ ("global/test_blueprint", "blueprint", "*.blueprints.md", "mocked_template_base_path/prompt-modules/blueprints/", "test_blueprint"),
949
968
  ])
950
- def test_load_template_from_global_or_local(monkeypatch, temp_chat_file, template_name, template_type, default_pattern, custom_template_subdir, expected_directory, expected_pattern):
969
+ def test_load_template_from_global(monkeypatch, temp_chat_file, template_name, template_type, default_pattern, expected_directory, expected_pattern):
951
970
  mock_config = get_default_config()
952
971
  with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
953
972
  chat = Chat(temp_chat_file.name, reset=False)
954
973
 
955
974
  mock_template_base_path = "mocked_template_base_path"
956
- mock_local_templates_path = "mocked_local_templates_path"
957
975
 
958
976
  monkeypatch.setattr(TemplatePathManager, 'get_template_base_path', lambda: mock_template_base_path)
959
- monkeypatch.setattr(ConfigManager, 'get_config', lambda: MagicMock(local_prompt_templates_dir=mock_local_templates_path))
960
977
 
961
978
  config = chat.config
962
- config.local_prompt_templates_dir = mock_local_templates_path
963
- config.custom_prompt_templates_subdir = custom_template_subdir
964
979
  chat.config = config
965
980
 
966
981
  with patch.object(chat, '_load_helper') as mock_load_helper:
@@ -1042,7 +1057,9 @@ def test_do_LOAD_TEMPLATE(temp_chat_file):
1042
1057
  with patch('ara_cli.prompt_handler.ConfigManager.get_config', return_value=mock_config):
1043
1058
  chat = Chat(temp_chat_file.name, reset=False)
1044
1059
  template_name = 'test_template'
1045
- directory = '/project/ara_cli/templates'
1060
+
1061
+ module_path = os.path.abspath(os.path.dirname(ara_cli.__file__))
1062
+ directory = f'{module_path}/templates'
1046
1063
  pattern = f"template.{template_name}"
1047
1064
  file_type = "template"
1048
1065
  exclude_pattern = os.path.join(directory, "template.*.prompt_log.md")
@@ -1,4 +1,4 @@
1
- from mock import Mock, patch, mock_open
1
+ from unittest.mock import Mock, patch, mock_open
2
2
  from pathlib import Path
3
3
  from ara_cli.artefact_creator import ArtefactCreator
4
4
  import pytest
@@ -1,4 +1,4 @@
1
- from unittest.mock import patch
1
+ from unittest.mock import patch, MagicMock
2
2
  from ara_cli.template_manager import SpecificationBreakdownAspects, ArtefactFileManager
3
3
  from ara_cli.directory_navigator import DirectoryNavigator
4
4
 
@@ -64,27 +64,43 @@ def test_copy_templates_to_directory(aspect, expect_exception, match_str):
64
64
  mock_copy.assert_called()
65
65
  mock_print.assert_called()
66
66
 
67
+ @pytest.fixture
68
+ def mock_file_manager():
69
+ with patch("ara_cli.template_manager.ArtefactFileManager") as MockFileManager:
70
+ yield MockFileManager.return_value
67
71
 
68
- @pytest.mark.parametrize(
69
- "file_exists, dir_exists, raises, expected_exception, expected_message",
70
- [
71
- (True, True, False, None, None),
72
- (True, False, False, None, None),
73
- (False, False, True, ValueError, f"File .* does not exist. Please create it first.")
74
- ]
75
- )
76
- def test_create(file_exists, dir_exists, raises, expected_exception, expected_message):
77
- artefact_name = 'test_artefact'
78
- classifier = 'capability'
79
- aspect = 'technology'
72
+ @pytest.fixture
73
+ def mock_navigator():
74
+ with patch("ara_cli.template_manager.DirectoryNavigator") as MockNavigator:
75
+ mock_nav_instance = MockNavigator.return_value
76
+ yield mock_nav_instance
77
+
78
+ @pytest.fixture
79
+ def mock_classifier():
80
+ with patch("ara_cli.template_manager.Classifier") as MockClassifier:
81
+ MockClassifier.valid_classifiers = ['valid_classifier']
82
+ MockClassifier.is_valid_classifier.return_value = True
83
+ yield MockClassifier
84
+
85
+
86
+ def test_create_valid_step_aspect(mock_file_manager, mock_navigator, mock_classifier):
80
87
  sba = SpecificationBreakdownAspects()
81
88
 
82
- with patch('os.path.isfile', return_value=file_exists), \
83
- patch('os.path.exists', return_value=dir_exists), \
84
- patch('os.mkdir'), patch('os.chdir'), patch('shutil.copy'):
89
+ # Mock values returned by file_manager methods
90
+ mock_file_manager.get_artefact_file_path.return_value = "/tmp/path/file"
91
+ mock_file_manager.get_data_directory_path.return_value = "/tmp/path/data"
92
+ mock_file_manager.generate_behave_steps.return_value = ["step 1", "step 2"]
85
93
 
86
- if raises:
87
- with pytest.raises(expected_exception, match=expected_message):
88
- sba.create(artefact_name, classifier, aspect)
89
- else:
90
- sba.create(artefact_name, classifier, aspect)
94
+ # Run the function
95
+ sba.create(artefact_name="my_artefact", classifier="valid_classifier", aspect="step")
96
+
97
+ # Assert navigator was used
98
+ mock_navigator.navigate_to_target.assert_called_once()
99
+
100
+ # Assert file_manager method calls
101
+ mock_file_manager.get_artefact_file_path.assert_called_once_with("my_artefact", "valid_classifier")
102
+ mock_file_manager.get_data_directory_path.assert_called_once_with("my_artefact", "valid_classifier")
103
+ mock_file_manager.create_directory.assert_called_once_with("/tmp/path/file", "/tmp/path/data")
104
+ mock_file_manager.copy_aspect_templates_to_directory.assert_called_once()
105
+ mock_file_manager.generate_behave_steps.assert_called_once_with("my_artefact")
106
+ mock_file_manager.save_behave_steps_to_file.assert_called_once_with("my_artefact", ["step 1", "step 2"])
@@ -1,113 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch, mock_open, MagicMock
3
- from ara_cli.artefact_autofix import (
4
- read_report_file,
5
- parse_report,
6
- apply_autofix,
7
- read_artefact,
8
- determine_artefact_type_and_class,
9
- run_agent,
10
- write_corrected_artefact
11
- )
12
- from ara_cli.ara_command_action import autofix_action
13
-
14
-
15
- def test_read_report_file():
16
- mock_content = "# Artefact Check Report\n\n## classifier\n- `file_path`: reason\n"
17
- with patch("builtins.open", mock_open(read_data=mock_content)) as m:
18
- content = read_report_file()
19
- assert content == mock_content
20
- m.assert_called_once_with(
21
- "incompatible_artefacts_report.md", "r", encoding="utf-8")
22
-
23
-
24
- def test_parse_report():
25
- content = "# Artefact Check Report\n\n## classifier\n- `file_path`: reason\n"
26
- expected_issues = {"classifier": [("file_path", "reason")]}
27
- issues = parse_report(content)
28
- assert issues == expected_issues
29
-
30
-
31
- @patch("ara_cli.artefact_autofix.run_agent")
32
- @patch("ara_cli.artefact_autofix.write_corrected_artefact")
33
- def test_apply_autofix(mock_write_corrected_artefact, mock_run_agent):
34
- mock_run_agent.return_value.serialize.return_value = "corrected content"
35
- with patch("ara_cli.artefact_autofix.read_artefact", return_value="artefact text"):
36
- with patch("ara_cli.artefact_autofix.determine_artefact_type_and_class", return_value=("ArtefactType", MagicMock())):
37
- apply_autofix("file_path", "classifier", "reason")
38
- mock_run_agent.assert_called_once()
39
- mock_write_corrected_artefact.assert_called_once_with(
40
- "file_path", "corrected content")
41
-
42
-
43
- @patch("ara_cli.artefact_autofix.apply_autofix")
44
- @patch("ara_cli.artefact_autofix.parse_report")
45
- @patch("ara_cli.artefact_autofix.read_report_file")
46
- def test_autofix_action(mock_read_report_file, mock_parse_report, mock_apply_autofix, capsys):
47
- mock_read_report_file.return_value = "# Artefact Check Report\n\n## classifier\n- `file_path`: reason\n"
48
- mock_parse_report.return_value = {"classifier": [("file_path", "reason")]}
49
-
50
- args = MagicMock()
51
- autofix_action(args)
52
-
53
- captured = capsys.readouterr()
54
- assert "Attempting to fix file_path for reason: reason" in captured.out
55
- mock_apply_autofix.assert_called_once_with(
56
- "file_path", "classifier", "reason")
57
-
58
-
59
- def test_read_artefact():
60
- mock_content = "artefact content"
61
- with patch("builtins.open", mock_open(read_data=mock_content)) as m:
62
- content = read_artefact("file_path")
63
- assert content == mock_content
64
- m.assert_called_once_with("file_path", "r")
65
-
66
-
67
- def test_read_report_file_not_found(capsys):
68
- with patch("builtins.open", side_effect=OSError("File not found")):
69
- content = read_report_file()
70
- captured = capsys.readouterr()
71
- assert content is None
72
- assert "Artefact scan results file not found" in captured.out
73
-
74
-
75
- def test_parse_report_no_issues():
76
- content = "# Artefact Check Report\n\nNo problems found.\n"
77
- issues = parse_report(content)
78
- assert issues == {}
79
-
80
-
81
- def test_parse_report_invalid_format():
82
- content = "Invalid Format"
83
- issues = parse_report(content)
84
- assert issues == {}
85
-
86
-
87
- def test_determine_artefact_type_and_class_invalid():
88
- artefact_type, artefact_class = determine_artefact_type_and_class(
89
- "invalid_classifier")
90
- assert artefact_type is None
91
- assert artefact_class is None
92
-
93
-
94
- def test_write_corrected_artefact():
95
- corrected_content = "corrected content"
96
- with patch("builtins.open", mock_open()) as m:
97
- write_corrected_artefact("file_path", corrected_content)
98
- m.assert_called_once_with("file_path", "w")
99
- handle = m()
100
- handle.write.assert_called_once_with(corrected_content)
101
-
102
-
103
- @patch("ara_cli.artefact_autofix.read_artefact", return_value=None)
104
- def test_apply_autofix_file_not_found(mock_read_artefact):
105
- apply_autofix("file_path", "classifier", "reason")
106
- mock_read_artefact.assert_called_once_with("file_path")
107
-
108
-
109
- @patch("pydantic_ai.Agent")
110
- def test_run_agent_exception_handling(mock_agent):
111
- mock_agent.return_value.run_sync.side_effect = Exception("Agent error")
112
- with pytest.raises(Exception, match="Agent error"):
113
- run_agent("prompt", MagicMock())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes