ara-cli 0.1.9.63__py3-none-any.whl → 0.1.9.64__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.
@@ -334,11 +334,15 @@ def reconnect_action(args):
334
334
  from ara_cli.artefact_models.artefact_load import artefact_from_content
335
335
  from ara_cli.artefact_models.artefact_model import Contribution
336
336
  from ara_cli.artefact_reader import ArtefactReader
337
+ from ara_cli.artefact_fuzzy_search import find_closest_rule
337
338
 
338
339
  classifier = args.classifier
339
340
  artefact_name = args.parameter
340
341
  parent_classifier = args.parent_classifier
341
342
  parent_name = args.parent_name
343
+ rule = None
344
+ if args.rule:
345
+ rule = ' '.join(args.rule)
342
346
 
343
347
  read_error_message = f"Could not connect {classifier} '{artefact_name}' to {parent_classifier} '{parent_name}'"
344
348
 
@@ -367,10 +371,17 @@ def reconnect_action(args):
367
371
  content=parent_content
368
372
  )
369
373
 
370
- artefact.contribution = Contribution(
374
+ contribution = Contribution(
371
375
  artefact_name=parent.title,
372
376
  classifier=parent.artefact_type
373
377
  )
378
+
379
+ if rule:
380
+ closest_rule = find_closest_rule(parent, rule)
381
+ print("")
382
+ contribution.rule = closest_rule
383
+
384
+ artefact.contribution = contribution
374
385
  with open(artefact.file_path, 'w') as file:
375
386
  artefact_content = artefact.serialize()
376
387
  file.write(artefact_content)
@@ -184,6 +184,7 @@ def reconnect_parser(subparsers):
184
184
  reconnect_parser.add_argument("parameter", help="Filename of artefact").completer = ArtefactCompleter()
185
185
  reconnect_parser.add_argument("parent_classifier", choices=classifiers, help="Classifier of the parent artefact type")
186
186
  reconnect_parser.add_argument("parent_name", help="Filename of parent artefact").completer = ParentNameCompleter()
187
+ reconnect_parser.add_argument("-r", "--rule", dest="rule", nargs='+')
187
188
 
188
189
 
189
190
  def read_status_parser(subparsers):
@@ -83,9 +83,8 @@ def construct_prompt(artefact_type, reason, file_path, artefact_text):
83
83
 
84
84
  prompt = (
85
85
  f"Correct the following {artefact_type} artefact to fix the issue: {reason}. "
86
- "Provide the complete, corrected artefact. Do not reformulate the artefact, "
86
+ "Provide the corrected artefact. Do not reformulate the artefact, "
87
87
  "just fix the pydantic model errors, use correct grammar. "
88
- "Do not remove comments. "
89
88
  "You should follow the name of the file "
90
89
  f"from its path {file_path} for naming the arteafact's title. "
91
90
  "You are not allowed to use file extention in the artefact title. "
@@ -114,7 +113,7 @@ def run_agent(prompt, artefact_class):
114
113
  from pydantic_ai import Agent
115
114
  # gpt-4o
116
115
  # anthropic:claude-3-7-sonnet-20250219
117
- agent = Agent(model="gpt-4o",
116
+ agent = Agent(model="anthropic:claude-3-7-sonnet-20250219",
118
117
  result_type=artefact_class, instrument=True)
119
118
  result = agent.run_sync(prompt)
120
119
  return result.data
@@ -1,9 +1,10 @@
1
1
  import difflib
2
+ from textwrap import indent
2
3
  from typing import Optional
3
4
 
4
5
 
5
- def suggest_close_names(artefact_name: str, all_artefact_names: list[str], message: str):
6
- closest_matches = difflib.get_close_matches(artefact_name, all_artefact_names, cutoff=0.5)
6
+ def suggest_close_names(artefact_name: str, all_artefact_names: list[str], message: str, cutoff=0.5):
7
+ closest_matches = difflib.get_close_matches(artefact_name, all_artefact_names, cutoff=cutoff)
7
8
  print(message)
8
9
  if not closest_matches:
9
10
  return
@@ -38,3 +39,21 @@ def find_closest_name_match(artefact_name: str, all_artefact_names: list[str]) -
38
39
  return None
39
40
  closest_match = closest_matches[0]
40
41
  return closest_match
42
+
43
+
44
+ def find_closest_rule(parent_artefact, rule):
45
+ parent_classifier = parent_artefact.artefact_type.value
46
+ parent_title = parent_artefact.title
47
+ if not hasattr(parent_artefact, 'rules'):
48
+ raise TypeError(f"{parent_classifier.capitalize()} artefact '{parent_title}' can not possess rules. Only userstories and epics have rules.")
49
+ rules = parent_artefact.rules
50
+ if rule in rules:
51
+ return rule
52
+ print(f"Rule '{rule}' does not match existing rules in {parent_classifier} artefact '{parent_title}'. Attempting to find closest match among existing rules.")
53
+ closest_matches = difflib.get_close_matches(rule, rules, cutoff=0.5)
54
+ rules_list_string = indent('\n'.join(rules), prefix='\t- ')
55
+ if not closest_matches:
56
+ raise ValueError(f"Can not determine a match for rule '{rule}' in {parent_classifier} artefact '{parent_title}'. Found rules:\n{rules_list_string}")
57
+ closest_match = closest_matches[0]
58
+ print(f"Found closest matching rule of '{closest_match}'")
59
+ return closest_match
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, field_validator, model_validator, Field
2
- from typing import List, Dict, Tuple, Union
2
+ from typing import List, Dict, Tuple, Union, Optional
3
3
  from ara_cli.artefact_models.artefact_model import Artefact, ArtefactType, Intent
4
4
  import re
5
5
 
@@ -98,6 +98,35 @@ class Example(BaseModel):
98
98
  return cls(values=values)
99
99
 
100
100
 
101
+ class Background(BaseModel):
102
+ steps: List[str] = Field(
103
+ description="A list of Gherkin 'Given' type steps that describe what the background does."
104
+ )
105
+
106
+ @field_validator('steps', mode='before')
107
+ def validate_steps(cls, v: List[str]) -> List[str]:
108
+ """Ensure steps are non-empty and stripped."""
109
+ steps = [step.strip() for step in v if step.strip()]
110
+ if not steps:
111
+ raise ValueError("steps list must not be empty")
112
+ return steps
113
+
114
+ @classmethod
115
+ def from_lines(cls, lines: List[str], start_idx: int) -> Tuple['Background', int]:
116
+ """Parse a Background from a list of lines starting at start_idx."""
117
+ if not lines[start_idx].startswith('Background:'):
118
+ raise ValueError("Expected 'Background:' at start index")
119
+
120
+ steps = []
121
+ idx = start_idx + 1
122
+ while idx < len(lines) and not lines[idx].startswith('Background:'):
123
+ step = lines[idx].strip()
124
+ if step:
125
+ steps.append(step)
126
+ idx += 1
127
+ return cls(steps=steps), idx
128
+
129
+
101
130
  class Scenario(BaseModel):
102
131
  title: str = Field(
103
132
  description="The name of the scenario, giving a short summary of the test case. It comes from the 'Scenario:' line in the feature file."
@@ -213,6 +242,8 @@ class FeatureArtefact(Artefact):
213
242
  artefact_type: ArtefactType = ArtefactType.feature
214
243
  intent: FeatureIntent
215
244
  scenarios: List[Union[Scenario, ScenarioOutline]] = Field(default=None)
245
+ background: Optional[Background] = Field(
246
+ default=None, description="Highly optional background Gherkin steps for Feature Artefacts. This steps apply for all scenarios and scenario outlines in this feature file.")
216
247
 
217
248
  @field_validator('artefact_type')
218
249
  def validate_artefact_type(cls, v):
@@ -229,6 +260,16 @@ class FeatureArtefact(Artefact):
229
260
  def _artefact_type(cls) -> ArtefactType:
230
261
  return ArtefactType.feature
231
262
 
263
+ def _serialize_background(self) -> str:
264
+ """Helper method to dispatch background serialization."""
265
+ if not self.background:
266
+ return ""
267
+ lines = []
268
+ lines.append(" Background:")
269
+ for step in self.background.steps:
270
+ lines.append(f" {step}")
271
+ return "\n".join(lines)
272
+
232
273
  def _serialize_scenario(self, scenario: Union[Scenario, ScenarioOutline]) -> str:
233
274
  """Helper method to dispatch scenario serialization."""
234
275
  if isinstance(scenario, Scenario):
@@ -309,6 +350,10 @@ class FeatureArtefact(Artefact):
309
350
  lines.append(description)
310
351
  lines.append("")
311
352
 
353
+ if self.background:
354
+ lines.append(self._serialize_background())
355
+ lines.append("")
356
+
312
357
  if self.scenarios:
313
358
  for scenario in self.scenarios:
314
359
  lines.append(self._serialize_scenario(scenario))
@@ -321,9 +366,11 @@ class FeatureArtefact(Artefact):
321
366
  fields = super()._parse_common_fields(text)
322
367
 
323
368
  intent = FeatureIntent.deserialize(text)
369
+ background = cls.deserialize_background(text)
324
370
  scenarios = cls.deserialize_scenarios(text)
325
371
 
326
372
  fields['scenarios'] = scenarios
373
+ fields['background'] = background
327
374
  fields['intent'] = intent
328
375
 
329
376
  return cls(**fields)
@@ -348,3 +395,19 @@ class FeatureArtefact(Artefact):
348
395
  else:
349
396
  idx += 1
350
397
  return scenarios
398
+
399
+ @classmethod
400
+ def deserialize_background(cls, text):
401
+ lines = [line.strip()
402
+ for line in text.strip().splitlines() if line.strip()]
403
+
404
+ background = None
405
+ idx = 0
406
+ while idx < len(lines):
407
+ line = lines[idx].strip()
408
+ if line.startswith('Background:'):
409
+ background, next_idx = Background.from_lines(lines, idx)
410
+ break
411
+ else:
412
+ idx += 1
413
+ return background
@@ -184,10 +184,14 @@ def handle_existing_file(filename, block_content):
184
184
  create_file_if_not_exist(filename, block_content)
185
185
  else:
186
186
  print(f"File {filename} exists, creating modification prompt")
187
- prompt = create_prompt_for_file_modification(block_content, filename)
187
+ prompt_text = create_prompt_for_file_modification(block_content, filename)
188
+ messages = [{"role": "user", "content": prompt_text}]
188
189
  response = ""
189
- for chunk in send_prompt(prompt):
190
- response += chunk.content
190
+
191
+ for chunk in send_prompt(messages):
192
+ content = chunk.choices[0].delta.content
193
+ if content:
194
+ response += content
191
195
  modify_and_save_file(response, filename)
192
196
 
193
197
 
ara_cli/prompt_handler.py CHANGED
@@ -355,8 +355,12 @@ def prepend_system_prompt(message_list):
355
355
 
356
356
 
357
357
  def append_images_to_message(message, image_data_list):
358
- message_content = message["content"]
359
- message["content"] = message_content + image_data_list
358
+ if not image_data_list:
359
+ 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
360
364
 
361
365
  return message
362
366
 
@@ -376,14 +380,23 @@ def create_and_send_custom_prompt(classifier, parameter):
376
380
  append_headings(classifier, parameter, "prompt")
377
381
  write_prompt_result(classifier, parameter, prompt)
378
382
 
379
- message_list = append_images_to_message(combined_content_markdown, image_data_list)
380
- append_headings(classifier, parameter, "result")
383
+ base_message = {
384
+ "role": "user",
385
+ "content": combined_content_markdown
386
+ }
387
+
388
+ final_message = append_images_to_message(base_message, image_data_list)
381
389
 
390
+ message_list_to_send = [final_message]
391
+
392
+ append_headings(classifier, parameter, "result")
382
393
  artefact_data_path = f"ara/{sub_directory}/{parameter}.data/{classifier}.prompt_log.md"
383
394
  with open(artefact_data_path, 'a') as file:
384
- for chunk in send_prompt(message_list):
385
- file.write(chunk.content)
386
- file.flush()
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()
387
400
  # write_prompt_result(classifier, parameter, response)
388
401
 
389
402
 
ara_cli/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.9.63" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
2
+ __version__ = "0.1.9.64" # 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.63
3
+ Version: 0.1.9.64
4
4
  Requires-Dist: litellm
5
5
  Requires-Dist: llama-index
6
6
  Requires-Dist: llama-index-llms-openai
@@ -1,12 +1,12 @@
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=SfWcXsLjaUdOpME2qJJ0pcCSXKY5M-zbSyj0W3u_8h4,19709
4
- ara_cli/ara_command_parser.py,sha256=aRxqKr0-pAuI45IuM-H4-ouc101h1_xTuEVJHXocPV0,17182
3
+ ara_cli/ara_command_action.py,sha256=5Q9dwS4urZCkD52trqTghnYViBLvOBqwcG3oDYBNgHg,20003
4
+ ara_cli/ara_command_parser.py,sha256=iG0IHL3So96viB7_lyetj_wbOeCLlToUpRY9HvBNY8w,17256
5
5
  ara_cli/ara_config.py,sha256=_Arkr-b9XnrNHbBlFKb9tAo3OmdP4ZZiWvbY9m6Sbo0,4178
6
- ara_cli/artefact_autofix.py,sha256=kkmf9hhvxqYicAm4kUDxFD1LoTr8KSPI5l12KRoZvqw,4900
6
+ ara_cli/artefact_autofix.py,sha256=T0TJ6vBxveMoOKm6c22c6D-iMuBHlCksvNLil_O6xaM,4885
7
7
  ara_cli/artefact_creator.py,sha256=mkxKHkVIK2GdmUrKHAjKvhq66eg21S3x_cvK1ZA9DPw,5964
8
8
  ara_cli/artefact_deleter.py,sha256=Co4wwCH3yW8H9NrOq7_2p5571EeHr0TsfE-H8KqoOfY,1900
9
- ara_cli/artefact_fuzzy_search.py,sha256=XAvoiRafd1u21uKbX5-bow7hdq7uiLLy1KtxHNAFbCk,1337
9
+ ara_cli/artefact_fuzzy_search.py,sha256=TD96fV5FoyJzLClXiHHlysPruK4t7yAgQ_kPnQqyMRo,2412
10
10
  ara_cli/artefact_link_updater.py,sha256=itMS_Z64jE8bBly9WA01z8PqkBeNW6ntTO7ryMeCTRg,3703
11
11
  ara_cli/artefact_lister.py,sha256=jhk4n4eqp7hDIq07q43QzS7-36BM3OfZ4EABxCeOGcw,4764
12
12
  ara_cli/artefact_reader.py,sha256=qNaMPWShmWtDU5LLdh9efFB27djI4NAoq6zEFwdTd38,6983
@@ -24,14 +24,14 @@ ara_cli/filename_validator.py,sha256=Aw9PL8d5-Ymhp3EY6lDrUBk3cudaNqo1Uw5RzPpI1jA
24
24
  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
- ara_cli/prompt_extractor.py,sha256=gidrCI8wTLfPL0ktqiXyPeGdQEB0S0sZegSOiF1Nn0Y,7358
28
- ara_cli/prompt_handler.py,sha256=PzHoIPTAWtRleOMtprhyYlFfo59S5T_kzHHkrwL-cNU,17155
27
+ ara_cli/prompt_extractor.py,sha256=a8LwPj6U8sG_v3SqDXQyPvDZQds4kHnYSO8eGissYJA,7503
28
+ ara_cli/prompt_handler.py,sha256=1gz2VkK7zl0RAzit1UmxplNNd2Zu6J_7eHocgKiw_hQ,17473
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=IrjX8R8pdas3_qWuDsXXnwCVDD0uUrgqjzfqTD81t3I,2173
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=BPYHhB5DXV3V9HaoG9jWNjJoI6MwS8Abg07dp1EjppU,146
34
+ ara_cli/version.py,sha256=rBCA0K3GrvOvnsjnnIVwI8EKzmt0b2Eh7phC0VgDNIM,146
35
35
  ara_cli/artefact_models/artefact_load.py,sha256=dNcwZDW2Dk0bts9YnPZ0ESmWD2NbsLIvl4Z-qQeGmTQ,401
36
36
  ara_cli/artefact_models/artefact_mapping.py,sha256=8aD0spBjkJ8toMAmFawc6UTUxB6-tEEViZXv2I-r88Q,1874
37
37
  ara_cli/artefact_models/artefact_model.py,sha256=W_jxafOd7d-1SxvB2G8dKG7_rT5t-EZo1-PnvlPHqso,14915
@@ -40,7 +40,7 @@ ara_cli/artefact_models/businessgoal_artefact_model.py,sha256=jqYFMXjWle0YW9RvcF
40
40
  ara_cli/artefact_models/capability_artefact_model.py,sha256=SZqHx4O2mj4urn77Stnj4_Jxtlq3-LgBBU9SMkByppI,3079
41
41
  ara_cli/artefact_models/epic_artefact_model.py,sha256=IadQWs6SWNcLgwvtOQWmYDyV9xLr3WwAsx-YMFan5fA,5765
42
42
  ara_cli/artefact_models/example_artefact_model.py,sha256=UXrKbaPotg1jwcrVSdCeo-XH4tTD_-U1e3giaBn5_xg,1384
43
- ara_cli/artefact_models/feature_artefact_model.py,sha256=TWJ7N0P2Wd8VtpbBPwJoiDnDSbHSRPPjyDfpvA-IqYc,13238
43
+ ara_cli/artefact_models/feature_artefact_model.py,sha256=BeLsq9ZinzIDwss8nurIbxJZQ2qPgyRKRHrRlrl-zHA,15583
44
44
  ara_cli/artefact_models/issue_artefact_model.py,sha256=v6CpKnkqiUh6Wch2kkEmyyW49c8ysdy1qz8l1Ft9uJA,2552
45
45
  ara_cli/artefact_models/keyfeature_artefact_model.py,sha256=a3MyAiePN9n_GTN6QkTvamdsaorwVUff6w-9CdRZSlo,4243
46
46
  ara_cli/artefact_models/serialize_helper.py,sha256=0XCruO70-fyfLfTn7pnt8NrSQe79eYNUAjuQaV8K6_8,586
@@ -150,8 +150,8 @@ ara_cli/tests/test_list_filter.py,sha256=gSRKirTtFuhRS3QlFHqWl89WvCvAdVEnFsCWTYm
150
150
  ara_cli/tests/test_tag_extractor.py,sha256=n2xNApbDciqKO3QuaveEWSPXU1PCUa_EhxlZMrukONw,2074
151
151
  ara_cli/tests/test_template_manager.py,sha256=bRxka6cxHsCAOvXjfG8MrVO8qSZXhxW01tnph80UtNk,3143
152
152
  ara_cli/tests/test_update_config_prompt.py,sha256=vSsLvc18HZdVjVM93qXWVbJt752xTLL6VGjSVCrPufk,6729
153
- ara_cli-0.1.9.63.dist-info/METADATA,sha256=gxNdNrzLFrDWHMMskXW_kMFj0qBeoxoyFxilCHDsQiQ,415
154
- ara_cli-0.1.9.63.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
155
- ara_cli-0.1.9.63.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
156
- ara_cli-0.1.9.63.dist-info/top_level.txt,sha256=zzee_PwFmKqfBi9XgIunP6xy2S4TIt593CLLxenNaAE,8
157
- ara_cli-0.1.9.63.dist-info/RECORD,,
153
+ ara_cli-0.1.9.64.dist-info/METADATA,sha256=hMa8LLYlDMGNBHL5R7CwM1QamWGbbsMe7hTbajvHQ1I,415
154
+ ara_cli-0.1.9.64.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
155
+ ara_cli-0.1.9.64.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
156
+ ara_cli-0.1.9.64.dist-info/top_level.txt,sha256=zzee_PwFmKqfBi9XgIunP6xy2S4TIt593CLLxenNaAE,8
157
+ ara_cli-0.1.9.64.dist-info/RECORD,,