ara-cli 0.1.10.1__py3-none-any.whl → 0.1.11.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (57) hide show
  1. ara_cli/__init__.py +0 -1
  2. ara_cli/__main__.py +95 -2
  3. ara_cli/artefact_autofix.py +44 -6
  4. ara_cli/artefact_models/artefact_model.py +18 -6
  5. ara_cli/artefact_models/artefact_templates.py +2 -1
  6. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  7. ara_cli/artefact_models/feature_artefact_model.py +31 -1
  8. ara_cli/artefact_models/userstory_artefact_model.py +13 -1
  9. ara_cli/chat.py +142 -37
  10. ara_cli/chat_agent/__init__.py +0 -0
  11. ara_cli/chat_agent/agent_communicator.py +62 -0
  12. ara_cli/chat_agent/agent_process_manager.py +211 -0
  13. ara_cli/chat_agent/agent_status_manager.py +73 -0
  14. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  15. ara_cli/directory_navigator.py +37 -4
  16. ara_cli/file_loaders/text_file_loader.py +2 -2
  17. ara_cli/global_file_lister.py +5 -15
  18. ara_cli/prompt_extractor.py +179 -71
  19. ara_cli/prompt_handler.py +160 -59
  20. ara_cli/tag_extractor.py +26 -23
  21. ara_cli/template_loader.py +1 -1
  22. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  23. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  24. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  25. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  26. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  27. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  28. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  29. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  30. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  31. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  32. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  33. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  34. ara_cli/version.py +1 -1
  35. {ara_cli-0.1.10.1.dist-info → ara_cli-0.1.11.0.dist-info}/METADATA +31 -1
  36. {ara_cli-0.1.10.1.dist-info → ara_cli-0.1.11.0.dist-info}/RECORD +41 -41
  37. tests/test_global_file_lister.py +1 -1
  38. tests/test_prompt_handler.py +12 -4
  39. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  40. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  41. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  42. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  43. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  44. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  45. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  46. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  47. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  48. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  49. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  50. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  51. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  52. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  53. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  54. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  55. {ara_cli-0.1.10.1.dist-info → ara_cli-0.1.11.0.dist-info}/WHEEL +0 -0
  56. {ara_cli-0.1.10.1.dist-info → ara_cli-0.1.11.0.dist-info}/entry_points.txt +0 -0
  57. {ara_cli-0.1.10.1.dist-info → ara_cli-0.1.11.0.dist-info}/top_level.txt +0 -0
ara_cli/prompt_handler.py CHANGED
@@ -60,7 +60,12 @@ class LLMSingleton:
60
60
  # Check if there was an authentication error
61
61
  stderr_output = captured_stderr.getvalue()
62
62
  if "Authentication error" in stderr_output:
63
- warnings.warn("Invalid Langfuse credentials - prompt tracing disabled and using default prompts. Set environment variables 'ARA_CLI_LANGFUSE_PUBLIC_KEY', 'ARA_CLI_LANGFUSE_SECRET_KEY', 'LANGFUSE_HOST' and restart application to use Langfuse capabilities", UserWarning)
63
+ warnings.warn(
64
+ "Invalid Langfuse credentials - prompt tracing disabled and using default prompts. "
65
+ "Set environment variables 'ARA_CLI_LANGFUSE_PUBLIC_KEY', 'ARA_CLI_LANGFUSE_SECRET_KEY', "
66
+ "'LANGFUSE_HOST' and restart application to use Langfuse capabilities",
67
+ UserWarning,
68
+ )
64
69
 
65
70
  LLMSingleton._default_model = default_model_id
66
71
  LLMSingleton._extraction_model = extraction_model_id
@@ -160,6 +165,50 @@ def _is_valid_message(message: dict) -> bool:
160
165
  return False
161
166
 
162
167
 
168
+ def _norm(p: str) -> str:
169
+ """Normalize slashes and collapse .. segments."""
170
+ return os.path.normpath(p) if p else p
171
+
172
+
173
+ def resolve_existing_path(rel_or_abs_path: str, anchor_dir: str) -> str:
174
+ """
175
+ Resolve a potentially relative path to an existing absolute path.
176
+
177
+ Strategy:
178
+ - If already absolute and exists -> return it.
179
+ - Else, try from the anchor_dir.
180
+ - Else, walk up parent directories from anchor_dir and try joining at each level.
181
+ - If nothing is found, return the normalized original (will fail later with clear message).
182
+ """
183
+ if not rel_or_abs_path:
184
+ return rel_or_abs_path
185
+
186
+ candidate = _norm(rel_or_abs_path)
187
+
188
+ if os.path.isabs(candidate) and os.path.exists(candidate):
189
+ return candidate
190
+
191
+ anchor_dir = os.path.abspath(anchor_dir or os.getcwd())
192
+
193
+ # Try from anchor dir directly
194
+ direct = _norm(os.path.join(anchor_dir, candidate))
195
+ if os.path.exists(direct):
196
+ return direct
197
+
198
+ # Walk parents
199
+ cur = anchor_dir
200
+ prev = None
201
+ while cur and cur != prev:
202
+ test = _norm(os.path.join(cur, candidate))
203
+ if os.path.exists(test):
204
+ return test
205
+ prev = cur
206
+ cur = os.path.dirname(cur)
207
+
208
+ # Give back normalized candidate; open() will raise, but at least path is clean
209
+ return candidate
210
+
211
+
163
212
  def send_prompt(prompt, purpose="default"):
164
213
  """Prepares and sends a prompt to the LLM, streaming the response."""
165
214
  chat_instance = LLMSingleton.get_instance()
@@ -213,8 +262,6 @@ def describe_image(image_path: str) -> str:
213
262
  Returns:
214
263
  Text description of the image
215
264
  """
216
- import base64
217
-
218
265
  with LLMSingleton.get_instance().langfuse.start_as_current_span(
219
266
  name="ara-cli/describe-image"
220
267
  ) as span:
@@ -234,14 +281,19 @@ def describe_image(image_path: str) -> str:
234
281
  # Fallback to default prompt if Langfuse prompt is not available
235
282
  if not describe_image_prompt:
236
283
  logging.info("Using default describe-image prompt.")
237
- describe_image_prompt = "Please describe this image in detail. If it contains text, transcribe it exactly. If it's a diagram or chart, explain its structure and content. If it's a photo or illustration, describe what you see."
284
+ describe_image_prompt = (
285
+ "Please describe this image in detail. If it contains text, transcribe it exactly. "
286
+ "If it's a diagram or chart, explain its structure and content. If it's a photo or illustration, "
287
+ "describe what you see."
288
+ )
238
289
 
239
- # Read and encode the image
240
- with open(image_path, "rb") as image_file:
290
+ # Resolve and read the image
291
+ resolved_image_path = resolve_existing_path(image_path, os.getcwd())
292
+ with open(resolved_image_path, "rb") as image_file:
241
293
  base64_image = base64.b64encode(image_file.read()).decode("utf-8")
242
294
 
243
295
  # Determine image type
244
- image_extension = os.path.splitext(image_path)[1].lower()
296
+ image_extension = os.path.splitext(resolved_image_path)[1].lower()
245
297
  mime_type = {
246
298
  ".png": "image/png",
247
299
  ".jpg": "image/jpeg",
@@ -256,7 +308,7 @@ def describe_image(image_path: str) -> str:
256
308
  "content": [
257
309
  {
258
310
  "type": "text",
259
- "text": "Please describe this image in detail. If it contains text, transcribe it exactly. If it's a diagram or chart, explain its structure and content. If it's a photo or illustration, describe what you see.",
311
+ "text": describe_image_prompt,
260
312
  },
261
313
  {
262
314
  "type": "image_url",
@@ -288,7 +340,9 @@ def describe_image(image_path: str) -> str:
288
340
  def append_headings(classifier, param, heading_name):
289
341
  sub_directory = Classifier.get_sub_directory(classifier)
290
342
 
291
- artefact_data_path = f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
343
+ artefact_data_path = _norm(
344
+ f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
345
+ )
292
346
 
293
347
  # Check if the file exists, and if not, create an empty file
294
348
  if not os.path.exists(artefact_data_path):
@@ -309,15 +363,15 @@ def append_headings(classifier, param, heading_name):
309
363
 
310
364
  def write_prompt_result(classifier, param, text):
311
365
  sub_directory = Classifier.get_sub_directory(classifier)
312
-
313
- # TODO change absolute path to relative path with directory navigator
314
- artefact_data_path = f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
366
+ artefact_data_path = _norm(
367
+ f"ara/{sub_directory}/{param}.data/{classifier}.prompt_log.md"
368
+ )
315
369
  write_string_to_file(artefact_data_path, text, "a")
316
370
 
317
371
 
318
372
  def prompt_data_directory_creation(classifier, parameter):
319
373
  sub_directory = Classifier.get_sub_directory(classifier)
320
- prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
374
+ prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
321
375
  if not exists(prompt_data_path):
322
376
  makedirs(prompt_data_path)
323
377
  return prompt_data_path
@@ -347,9 +401,10 @@ def initialize_prompt_templates(classifier, parameter):
347
401
  artefact_to_mark=f"{parameter}.{classifier}",
348
402
  )
349
403
 
350
- generate_config_prompt_global_givens_file(prompt_data_path, "config.prompt_global_givens.md")
351
-
352
- generate_config_prompt_global_givens_file(prompt_data_path, "config.prompt_global_givens.md")
404
+ # Only once (was duplicated before)
405
+ generate_config_prompt_global_givens_file(
406
+ prompt_data_path, "config.prompt_global_givens.md"
407
+ )
353
408
 
354
409
 
355
410
  def write_template_files_to_config(template_type, config_file, base_template_path):
@@ -361,7 +416,7 @@ def write_template_files_to_config(template_type, config_file, base_template_pat
361
416
 
362
417
  def load_selected_prompt_templates(classifier, parameter):
363
418
  sub_directory = Classifier.get_sub_directory(classifier)
364
- prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
419
+ prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
365
420
  config_file_path = os.path.join(prompt_data_path, "config.prompt_templates.md")
366
421
 
367
422
  if not os.path.exists(config_file_path):
@@ -464,7 +519,9 @@ def move_and_copy_files(source_path, prompt_data_path, prompt_archive_path):
464
519
 
465
520
  def extract_and_load_markdown_files(md_prompt_file_path):
466
521
  """
467
- Extracts markdown files paths based on checked items and constructs proper paths respecting markdown header hierarchy.
522
+ Extracts markdown files paths based on checked items and constructs proper paths
523
+ respecting markdown header hierarchy. **Returns normalized relative paths**
524
+ (not resolved), and resolution happens later relative to the config file dir.
468
525
  """
469
526
  header_stack = []
470
527
  path_accumulator = []
@@ -480,53 +537,73 @@ def extract_and_load_markdown_files(md_prompt_file_path):
480
537
  header_stack.append(header)
481
538
  elif "[x]" in line:
482
539
  relative_path = line.split("]")[-1].strip()
483
- full_path = os.path.join("/".join(header_stack), relative_path)
484
- path_accumulator.append(full_path)
540
+ # Use os.path.join for OS-safe joining, then normalize
541
+ full_rel_path = os.path.join(*header_stack, relative_path) if header_stack else relative_path
542
+ path_accumulator.append(_norm(full_rel_path))
485
543
  return path_accumulator
486
544
 
487
545
 
488
546
  def load_givens(file_path):
547
+ """
548
+ Reads marked givens from a config markdown and returns:
549
+ - combined markdown content (including code fences / images)
550
+ - a list of image data dicts for the multimodal message
551
+ Paths inside the markdown are resolved robustly relative to the config file directory (and its parents).
552
+ """
489
553
  content = ""
490
554
  image_data_list = []
491
555
  markdown_items = extract_and_load_markdown_files(file_path)
492
556
 
493
- # Only proceed and add the header if there are marked items to load.
494
557
  if not markdown_items:
495
558
  return "", []
496
559
 
497
560
  content = "### GIVENS\n\n"
498
561
 
562
+ anchor_dir = os.path.dirname(os.path.abspath(file_path))
563
+
499
564
  for item in markdown_items:
500
- if item.lower().endswith((".png", ".jpeg", ".jpg")):
501
- with open(item, "rb") as image_file:
565
+ resolved = resolve_existing_path(item, anchor_dir)
566
+ # Keep the listing line readable, show the original relative item
567
+ content += item + "\n"
568
+
569
+ ext = os.path.splitext(resolved)[1].lower()
570
+
571
+ # Image branch
572
+ if ext in (".png", ".jpeg", ".jpg", ".gif", ".bmp"):
573
+ with open(resolved, "rb") as image_file:
502
574
  base64_image = base64.b64encode(image_file.read()).decode("utf-8")
575
+
576
+ mime_type = {
577
+ ".png": "image/png",
578
+ ".jpg": "image/jpeg",
579
+ ".jpeg": "image/jpeg",
580
+ ".gif": "image/gif",
581
+ ".bmp": "image/bmp",
582
+ }.get(ext, "image/png")
583
+
503
584
  image_data_list.append(
504
585
  {
505
586
  "type": "image_url",
506
- "image_url": {"url": f"data:image/png;base64,{base64_image}"},
587
+ "image_url": {"url": f"data:{mime_type};base64,{base64_image}"},
507
588
  }
508
589
  )
509
- content += item + "\n"
510
- content += f"![{item}](data:image/png;base64,{base64_image})" + "\n"
511
- else:
512
- # Check if the item specifies line ranges
513
- # TODO item has currently no trailing [] see extraction and handover method in extract and load
514
- # item = f"[10:29] {item}"
515
- # print(f"found {item}, check for subsection")
516
- # TODO re.match can not split the item with [] correctly and extract the line numbers
517
- # TODO logic of subsections is not supported by the update algorithm of the config prompt givens updater
518
- # TODO extract in lines of *.md files potential images and add them to the image list
590
+ # Also embed inline for the prompt markdown (use png as a neutral default for data URI)
591
+ content += f"![{item}](data:{mime_type};base64,{base64_image})\n"
519
592
 
593
+ else:
594
+ # Check if the item specifies line ranges: e.g. "[10:20,25:30] filePath"
520
595
  match = re.match(r".*?\[(\d+:\d+(?:,\s*\d+:\d+)*)\]\s+(.+)", item)
521
596
  if match:
522
597
  line_ranges, file_name = match.groups()
523
- content += file_name + "\n" + "```\n"
524
- content += get_partial_file_content(file_name, line_ranges) + "\n"
598
+ resolved_sub = resolve_existing_path(file_name, anchor_dir)
599
+ content += "```\n"
600
+ content += get_partial_file_content(resolved_sub, line_ranges) + "\n"
525
601
  content += "```\n\n"
526
602
  else:
527
- content += item + "\n" + "```\n"
528
- content += get_file_content(item) + "\n"
603
+ content += "```\n"
604
+ content += get_file_content(resolved) + "\n"
529
605
  content += "```\n\n"
606
+
530
607
  return content, image_data_list
531
608
 
532
609
 
@@ -535,7 +612,7 @@ def get_partial_file_content(file_name, line_ranges):
535
612
  Reads specific lines from a file based on the line ranges provided.
536
613
 
537
614
  Args:
538
- file_name (str): The path to the file.
615
+ file_name (str): The path to the file (absolute or relative, already resolved by caller).
539
616
  line_ranges (str): A string representing the line ranges to read, e.g., '10:20,25:30'.
540
617
 
541
618
  Returns:
@@ -585,9 +662,7 @@ def prepend_system_prompt(message_list):
585
662
  # Fallback to default prompt if Langfuse prompt is not available
586
663
  if not system_prompt:
587
664
  logging.info("Using default system prompt.")
588
- system_prompt = (
589
- "You are a helpful assistant that can process both text and images."
590
- )
665
+ system_prompt = "You are a helpful assistant that can process both text and images."
591
666
 
592
667
  # Prepend the system prompt
593
668
  system_prompt_message = {"role": "system", "content": system_prompt}
@@ -597,6 +672,9 @@ def prepend_system_prompt(message_list):
597
672
 
598
673
 
599
674
  def append_images_to_message(message, image_data_list):
675
+ """
676
+ Appends image data list to a single message dict (NOT to a list).
677
+ """
600
678
  logger = logging.getLogger(__name__)
601
679
 
602
680
  logger.debug(
@@ -607,13 +685,17 @@ def append_images_to_message(message, image_data_list):
607
685
  logger.debug("No images to append, returning original message")
608
686
  return message
609
687
 
610
- message_content = message["content"]
688
+ message_content = message.get("content")
611
689
  logger.debug(f"Original message content: {message_content}")
612
690
 
613
691
  if isinstance(message_content, str):
614
692
  message["content"] = [{"type": "text", "text": message_content}]
615
693
 
616
- message["content"].extend(image_data_list)
694
+ if isinstance(message["content"], list):
695
+ message["content"].extend(image_data_list)
696
+ else:
697
+ # If somehow content is not list or str, coerce to list
698
+ message["content"] = [{"type": "text", "text": str(message_content)}] + image_data_list
617
699
 
618
700
  logger.debug(f"Updated message content with {len(image_data_list)} images")
619
701
 
@@ -622,11 +704,20 @@ def append_images_to_message(message, image_data_list):
622
704
 
623
705
  def create_and_send_custom_prompt(classifier, parameter):
624
706
  sub_directory = Classifier.get_sub_directory(classifier)
625
- prompt_data_path = f"ara/{sub_directory}/{parameter}.data/prompt.data"
707
+ prompt_data_path = _norm(f"ara/{sub_directory}/{parameter}.data/prompt.data")
626
708
  prompt_file_path_markdown = join(prompt_data_path, f"{classifier}.prompt.md")
627
709
 
628
- extensions = [".blueprint.md", ".rules.md", ".prompt_givens.md", ".prompt_global_givens.md", ".intention.md", ".commands.md"]
629
- combined_content_markdown, image_data_list = collect_file_content_by_extension(prompt_data_path, extensions)
710
+ extensions = [
711
+ ".blueprint.md",
712
+ ".rules.md",
713
+ ".prompt_givens.md",
714
+ ".prompt_global_givens.md",
715
+ ".intention.md",
716
+ ".commands.md",
717
+ ]
718
+ combined_content_markdown, image_data_list = collect_file_content_by_extension(
719
+ prompt_data_path, extensions
720
+ )
630
721
 
631
722
  with open(prompt_file_path_markdown, "w", encoding="utf-8") as file:
632
723
  file.write(combined_content_markdown)
@@ -635,14 +726,14 @@ def create_and_send_custom_prompt(classifier, parameter):
635
726
  append_headings(classifier, parameter, "prompt")
636
727
  write_prompt_result(classifier, parameter, prompt)
637
728
 
729
+ # Build message and append images correctly (fixed)
638
730
  message = {"role": "user", "content": combined_content_markdown}
639
-
731
+ message = append_images_to_message(message, image_data_list)
640
732
  message_list = [message]
641
733
 
642
- message_list = append_images_to_message(message_list, image_data_list)
643
734
  append_headings(classifier, parameter, "result")
644
735
 
645
- artefact_data_path = (
736
+ artefact_data_path = _norm(
646
737
  f"ara/{sub_directory}/{parameter}.data/{classifier}.prompt_log.md"
647
738
  )
648
739
  with open(artefact_data_path, "a", encoding="utf-8") as file:
@@ -652,7 +743,6 @@ def create_and_send_custom_prompt(classifier, parameter):
652
743
  continue
653
744
  file.write(chunk_content)
654
745
  file.flush()
655
- # write_prompt_result(classifier, parameter, response)
656
746
 
657
747
 
658
748
  def generate_config_prompt_template_file(
@@ -661,7 +751,8 @@ def generate_config_prompt_template_file(
661
751
  config_prompt_templates_path = os.path.join(
662
752
  prompt_data_path, config_prompt_templates_name
663
753
  )
664
- config = ConfigManager.get_config()
754
+ # Use instance method consistently
755
+ config = ConfigManager().get_config()
665
756
  global_prompt_template_path = TemplatePathManager.get_template_base_path()
666
757
  dir_list = ["ara/.araconfig/custom-prompt-modules"] + [
667
758
  f"{os.path.join(global_prompt_template_path,'prompt-modules')}"
@@ -678,7 +769,7 @@ def generate_config_prompt_givens_file(
678
769
  config_prompt_givens_path = os.path.join(
679
770
  prompt_data_path, config_prompt_givens_name
680
771
  )
681
- config = ConfigManager.get_config()
772
+ config = ConfigManager().get_config()
682
773
  dir_list = (
683
774
  ["ara"]
684
775
  + [path for d in config.ext_code_dirs for path in d.values()]
@@ -712,14 +803,24 @@ def generate_config_prompt_givens_file(
712
803
  with open(config_prompt_givens_path, "w", encoding="utf-8") as file:
713
804
  file.write("".join(updated_listing))
714
805
 
715
- def generate_config_prompt_global_givens_file(prompt_data_path, config_prompt_givens_name, artefact_to_mark=None):
806
+
807
+ def generate_config_prompt_global_givens_file(
808
+ prompt_data_path, config_prompt_givens_name, artefact_to_mark=None
809
+ ):
716
810
  from ara_cli.global_file_lister import generate_global_markdown_listing
717
- config_prompt_givens_path = os.path.join(prompt_data_path, config_prompt_givens_name)
718
- config = ConfigManager.get_config()
719
811
 
720
- if not hasattr(config, 'global_dirs') or not config.global_dirs:
812
+ config_prompt_givens_path = os.path.join(
813
+ prompt_data_path, config_prompt_givens_name
814
+ )
815
+ config = ConfigManager().get_config()
816
+
817
+ if not hasattr(config, "global_dirs") or not config.global_dirs:
721
818
  return
722
819
 
723
820
  dir_list = [path for d in config.global_dirs for path in d.values()]
724
- print(f"used {dir_list} for global prompt givens file listing with absolute paths")
725
- generate_global_markdown_listing(dir_list, config.ara_prompt_given_list_includes, config_prompt_givens_path)
821
+ print(
822
+ f"used {dir_list} for global prompt givens file listing with absolute paths"
823
+ )
824
+ generate_global_markdown_listing(
825
+ dir_list, config.ara_prompt_given_list_includes, config_prompt_givens_path
826
+ )
ara_cli/tag_extractor.py CHANGED
@@ -6,7 +6,6 @@ from ara_cli.artefact_models.artefact_data_retrieval import (
6
6
  artefact_tags_retrieval,
7
7
  )
8
8
 
9
-
10
9
  class TagExtractor:
11
10
  def __init__(self, file_system=None):
12
11
  self.file_system = file_system or os
@@ -50,31 +49,35 @@ class TagExtractor:
50
49
  tag in status_tags or tag.startswith("priority_") or tag.startswith("user_")
51
50
  )
52
51
 
52
+ def _collect_all_tags(self, artefact):
53
+ """Collect all tags from an artefact including user tags and author."""
54
+ all_tags = []
55
+ all_tags.extend(artefact.tags)
56
+
57
+ if artefact.status:
58
+ all_tags.append(artefact.status)
59
+
60
+ user_tags = [f"user_{tag}" for tag in artefact.users]
61
+ all_tags.extend(user_tags)
62
+
63
+ if hasattr(artefact, 'author') and artefact.author:
64
+ all_tags.append(artefact.author)
65
+
66
+ return [tag for tag in all_tags if tag is not None]
67
+
68
+ def _add_tags_to_groups(self, tag_groups, tags):
69
+ """Add tags to tag groups."""
70
+ for tag in tags:
71
+ key = tag.lower()
72
+ if key not in tag_groups:
73
+ tag_groups[key] = set()
74
+ tag_groups[key].add(tag)
75
+
53
76
  def add_to_tags_set(self, tag_groups, filtered_artefacts):
54
77
  for artefact_list in filtered_artefacts.values():
55
78
  for artefact in artefact_list:
56
- user_tags = [f"user_{tag}" for tag in artefact.users]
57
-
58
- # Build list of all tags, filtering out None values
59
- all_tags = []
60
- all_tags.extend(artefact.tags)
61
-
62
- if artefact.status:
63
- all_tags.append(artefact.status)
64
-
65
- all_tags.extend(user_tags)
66
-
67
- # Safely handle author attribute
68
- if hasattr(artefact, 'author') and artefact.author:
69
- all_tags.append(artefact.author)
70
-
71
- # Filter out None values and add to tag groups
72
- for tag in all_tags:
73
- if tag is not None:
74
- key = tag.lower()
75
- if key not in tag_groups:
76
- tag_groups[key] = set()
77
- tag_groups[key].add(tag)
79
+ all_tags = self._collect_all_tags(artefact)
80
+ self._add_tags_to_groups(tag_groups, all_tags)
78
81
 
79
82
  def extract_tags(
80
83
  self,
@@ -147,7 +147,7 @@ class TemplateLoader:
147
147
  # Direct file loading for CLI usage
148
148
  try:
149
149
  with open(file_path, 'r', encoding='utf-8') as template_file:
150
- template_content = template_file.read()
150
+ template_content = template_file.read().replace('\r\n', '\n')
151
151
 
152
152
  # Add prompt tag if needed
153
153
  self._add_prompt_tag_if_needed(chat_file_path)
@@ -1,14 +1,4 @@
1
1
  ### COMMANDS FOR ...
2
2
  Your job is now:
3
- * remember you are strictly following your given RULES AS EXPERT <role defined in rules file> for code and architectural code quality
4
- * ...
5
-
6
- * return your results in the following format, ensuring your generated code blocks are not just code snippets but at complete method levels. Use for every single generated code block this format:
7
- ```python
8
- # [ ] extract
9
- # filename: {path/filename}.py
10
- {python code}
11
- ```
12
- * the extract and filename statements are only allowed once per code block
13
-
14
- * in case you think information is missing in order to generate a suffiently precise formulation, return a warning "WARNING: information is missing to correctly fullfill the job!" and then explain what kind of information you think is missing and how I could easily retrieve it.
3
+ * <main list for mandotory commands>
4
+ * ...
@@ -0,0 +1,12 @@
1
+ # general file generation and file extract instructions
2
+ * return only full copy pastable file content using this markdown codeblock format with 5 backticks:
3
+ `````
4
+ # [ ] extract
5
+ # filename: <absolute filepath>/<filename>.<file_extension>
6
+ {valid file content depending on the given file_extension}
7
+ `````
8
+
9
+ * The extract and filename statements are only allowed once per markdown code block
10
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
11
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
12
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,11 @@
1
+ # general markdown file generation and file extract instructions
2
+ * return full copy pastable file content using a markdown code block with 5-backticks:
3
+ `````
4
+ # [ ] extract
5
+ # filename: <filepath>/<filename>.md
6
+ {markdown formatted text}
7
+ `````
8
+ * The extract and filename statements are only allowed once per markdown code block
9
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
10
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
11
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,13 @@
1
+ # general python code file generation and file extract instructions
2
+ * return only full copy pastable file content using this markdown codeblock format:
3
+
4
+ ```python
5
+ # [ ] extract
6
+ # filename: src/{filename}.py
7
+ {python code}
8
+
9
+ ```
10
+ * The extract and filename statements are only allowed once per markdown code block
11
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
12
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
13
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,36 @@
1
+ # COMMANDS FOR ADDING OR MODIFYING EXISTING SPECIFIED BEHAVIOR
2
+
3
+ **At first**:
4
+
5
+ Check if a set of feature files is given
6
+
7
+ * In case no feature files are given:
8
+ * Stop immediately and respond with: "Error in prompt context: no feature files are given as already specified application behavior"
9
+
10
+ * Else:
11
+ * Continue following the given instructions
12
+
13
+ # Instructions
14
+ Your job is now:
15
+ * Silently analyze the given feature files and the specified behavior.
16
+ * Silently analyze the additionally given information about new wanted behavior or changes of existing behavior
17
+ * Develop adaptation strategies that minimize feature file changes with respect to any given already existing feature files, prefer reusing and adapting existing formulations/scenarios and steps over completely new formulations
18
+ * Now formulate to fully cover the new or changed behavior (one, two or many changed or new feature files)
19
+
20
+ Follow these feature file quality rules:
21
+ * Each feature file should not consist of more than max 3 scenarios, each feature file should follow the single responsibility principle as well as the feature file formulations should follow the separation of concerns of feature files that fully cover the human user observable behavior described in the specification notes. Consider in your formulation of the Gherkin feature files that, when implementing the graphical user interfaces, the full functionality of the Python package Streamlit can be utilized.
22
+ * Follow strictly the given feature file format in order to structure your feature files.
23
+ * You are allowed to use scenario outlines where useful. But in case they are not helpful in order to increase the readability you can just use standard scenario formulations.
24
+
25
+ * Wrap and return the formulated feature files as full copy pastable file content in the following format as markdown code block:
26
+
27
+ ```artefact
28
+ # [ ] extract
29
+ # filename: ara/features/{filename}.feature
30
+ {formulation, with the valid feature file structure following the given feature files as reference}
31
+ ```
32
+
33
+ * The extract and filename statements are only allowed once per markdown code block
34
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
35
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
36
+ * in case of files get deprecated give me a list of files that can be safely deleted
@@ -0,0 +1,53 @@
1
+ # COMMANDS FOR INITIALLY SPECIFYING APPLICATION BEHAVIOR USING FEATURE FILES
2
+
3
+ * Given a description of the wanted application behavior as bullet point list, specification document, ...
4
+
5
+ * And given this feature template with placeholders in <...>
6
+
7
+ ```
8
+ @creator_Egermeier
9
+ Feature: <descriptive title>
10
+
11
+ As a <user>
12
+ I want to <do something | need something>
13
+ So that <I can achieve something>
14
+
15
+ Contributes to <here comes your parent artefact> <here comes your classifier of the parent artefact>
16
+
17
+ Description: <further optional description to understand
18
+ the rule, no format defined, the example artefact is only a placeholder>
19
+
20
+ Scenario: <descriptive scenario title>
21
+ Given <precondition>
22
+ When <action>
23
+ Then <expected result>
24
+
25
+ Scenario Outline: <descriptive scenario title>
26
+ Given <precondition>
27
+ When <action>
28
+ Then <expected result>
29
+
30
+ Examples:
31
+ | descriptive scenario title | precondition | action | expected result |
32
+ | <example title 1> | <example precond. 1> | <example action 1> | <example result 1> |
33
+ | <example title 2> | <example precond. 2> | <example action 2> | <example result 2> |
34
+ ```
35
+
36
+ # Instructions
37
+ * Now formulate a set (one, two or many, each feature file should not consist of more than max 3 scenarios
38
+ * Each feature file should follow the single responsibility principle as well as the feature file formulations should follow the separation of concerns) of feature files that fully cover the human user observable behavior described in the specification notes.
39
+ * Consider in your formulation of the Gherkin feature files when specifying the behavior of graphical user interfaces: Describe the behavior of the graphical user interfaces so that I can clearly imagine both how they work and their visual look and feel.
40
+ * Follow strictly the given template format in order to structure your feature files. You are allowed to use scenario outlines where useful. But in case they are not helpful in order to increase the readability you can just use standard scenario formulations.
41
+
42
+ * Wrap and return the formulated feature files as full copy pastable file content in the following format as markdown code block:
43
+
44
+ ```artefact
45
+ # [ ] extract
46
+ # filename: ara/features/{filename}.feature
47
+ {formulation, with the valid feature file structure as given by the feature gherkin template}
48
+ ```
49
+
50
+ * The extract and filename statements are only allowed once per markdown code block
51
+ * The first character of the first line inside your code block must be '#' and the first character of the second line inside your code block must be '#'
52
+ * replace the '# [ ] extract' statement of the template with '# [x] extract' in your response
53
+ * in case of files get deprecated give me a list of files that can be safely deleted