ara-cli 0.1.10.5__py3-none-any.whl → 0.1.13.3__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.
Files changed (106) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +95 -57
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/common.py +2 -2
  6. ara_cli/ara_subcommands/config.py +221 -0
  7. ara_cli/ara_subcommands/convert.py +43 -0
  8. ara_cli/ara_subcommands/fetch.py +41 -0
  9. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  10. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  11. ara_cli/ara_subcommands/fetch_templates.py +15 -10
  12. ara_cli/ara_subcommands/list.py +97 -23
  13. ara_cli/artefact_autofix.py +115 -62
  14. ara_cli/artefact_converter.py +256 -0
  15. ara_cli/chat.py +283 -62
  16. ara_cli/chat_agent/__init__.py +0 -0
  17. ara_cli/chat_agent/agent_process_manager.py +155 -0
  18. ara_cli/chat_script_runner/__init__.py +0 -0
  19. ara_cli/chat_script_runner/script_completer.py +23 -0
  20. ara_cli/chat_script_runner/script_finder.py +41 -0
  21. ara_cli/chat_script_runner/script_lister.py +36 -0
  22. ara_cli/chat_script_runner/script_runner.py +36 -0
  23. ara_cli/chat_web_search/__init__.py +0 -0
  24. ara_cli/chat_web_search/web_search.py +263 -0
  25. ara_cli/commands/agent_run_command.py +98 -0
  26. ara_cli/commands/fetch_agents_command.py +106 -0
  27. ara_cli/commands/fetch_scripts_command.py +43 -0
  28. ara_cli/commands/fetch_templates_command.py +39 -0
  29. ara_cli/commands/fetch_templates_commands.py +39 -0
  30. ara_cli/commands/list_agents_command.py +39 -0
  31. ara_cli/completers.py +71 -35
  32. ara_cli/constants.py +2 -0
  33. ara_cli/directory_navigator.py +37 -4
  34. ara_cli/llm_utils.py +58 -0
  35. ara_cli/prompt_chat.py +20 -4
  36. ara_cli/prompt_extractor.py +47 -32
  37. ara_cli/template_loader.py +2 -1
  38. ara_cli/template_manager.py +52 -21
  39. ara_cli/templates/global-scripts/hello_global.py +1 -0
  40. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  41. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  42. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  43. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  44. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  45. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  46. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  47. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  48. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  49. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  50. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  51. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  52. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  53. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  54. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  55. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  56. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  57. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  58. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  59. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  60. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  61. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  62. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  63. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  64. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  65. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  66. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  67. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  68. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  69. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  70. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  71. ara_cli/version.py +1 -1
  72. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +33 -1
  73. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +89 -43
  74. tests/test_ara_command_action.py +31 -19
  75. tests/test_ara_config.py +177 -90
  76. tests/test_artefact_autofix.py +170 -97
  77. tests/test_artefact_autofix_integration.py +495 -0
  78. tests/test_artefact_converter.py +357 -0
  79. tests/test_artefact_extraction.py +564 -0
  80. tests/test_chat.py +162 -126
  81. tests/test_chat_givens_images.py +603 -0
  82. tests/test_chat_script_runner.py +454 -0
  83. tests/test_llm_utils.py +164 -0
  84. tests/test_prompt_chat.py +343 -0
  85. tests/test_prompt_extractor.py +683 -0
  86. tests/test_web_search.py +467 -0
  87. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  88. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  89. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  90. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  91. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  92. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  93. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  94. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  95. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  96. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  97. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  98. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  99. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  100. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  101. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  102. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  103. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  104. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
  106. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
@@ -85,6 +85,7 @@ def mock_contribution():
85
85
  m.rule = "my_rule"
86
86
  return m
87
87
 
88
+
88
89
  @pytest.fixture
89
90
  def mock_artefact(mock_contribution):
90
91
  m = MagicMock()
@@ -177,8 +178,7 @@ def test_write_corrected_artefact():
177
178
  def test_construct_prompt_for_task():
178
179
  prompt = construct_prompt(ArtefactType.task, "some reason", "file.task", "text")
179
180
  assert (
180
- "For task artefacts, if the action items looks like template or empty"
181
- in prompt
181
+ "For task artefacts, if the action items looks like template or empty" in prompt
182
182
  )
183
183
 
184
184
 
@@ -417,9 +417,9 @@ def test_fix_title_mismatch_prefix_not_found(capsys, mock_artefact_class):
417
417
  assert "Warning: Title prefix 'Feature:' not found" in capsys.readouterr().out
418
418
 
419
419
 
420
- @patch("pydantic_ai.Agent")
421
- def test_run_agent_exception_handling(mock_agent_class):
422
- mock_agent_instance = mock_agent_class.return_value
420
+ @patch("ara_cli.llm_utils.create_pydantic_ai_agent")
421
+ def test_run_agent_exception_handling(mock_create_agent):
422
+ mock_agent_instance = mock_create_agent.return_value
423
423
  mock_agent_instance.run_sync.side_effect = Exception("Agent error")
424
424
  with pytest.raises(Exception, match="Agent error"):
425
425
  run_agent("prompt", MagicMock())
@@ -433,6 +433,7 @@ def test_ask_for_contribution_choice_valid(mock_input):
433
433
  result = ask_for_contribution_choice(choices)
434
434
  assert result == "choice1"
435
435
 
436
+
436
437
  @patch("builtins.input", side_effect=["99"])
437
438
  def test_ask_for_contribution_choice_out_of_range(mock_input, capsys):
438
439
  """Tests selecting a choice that is out of range."""
@@ -638,7 +639,7 @@ def test_apply_autofix_single_pass(
638
639
  classifier="feature",
639
640
  reason="any",
640
641
  single_pass=True,
641
- deterministic=False, # Disable fixes
642
+ deterministic=False, # Disable fixes
642
643
  non_deterministic=False,
643
644
  classified_artefact_info=mock_classified_artefact_info,
644
645
  )
@@ -652,7 +653,9 @@ def test_apply_autofix_single_pass(
652
653
 
653
654
  @patch("ara_cli.artefact_autofix._update_rule")
654
655
  @patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
655
- def test_fix_rule_with_rule(mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys):
656
+ def test_fix_rule_with_rule(
657
+ mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys
658
+ ):
656
659
  # Contribution has a rule
657
660
  artefact_class = MagicMock()
658
661
  artefact_class.deserialize.return_value = mock_artefact
@@ -680,9 +683,12 @@ def test_fix_rule_with_rule(mock_populate, mock_update_rule, mock_artefact, mock
680
683
  # Result is the serialized text
681
684
  assert result == "serialized-text"
682
685
 
686
+
683
687
  @patch("ara_cli.artefact_autofix._update_rule")
684
688
  @patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
685
- def test_fix_rule_without_rule(mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys):
689
+ def test_fix_rule_without_rule(
690
+ mock_populate, mock_update_rule, mock_artefact, mock_contribution, capsys
691
+ ):
686
692
  # Contribution rule becomes None after update
687
693
  mock_contribution.rule = None
688
694
  artefact_class = MagicMock()
@@ -700,6 +706,7 @@ def test_fix_rule_without_rule(mock_populate, mock_update_rule, mock_artefact, m
700
706
  assert "without a rule" in capsys.readouterr().out
701
707
  assert result == "serialized-text"
702
708
 
709
+
703
710
  @patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
704
711
  def test_fix_rule_contribution_none_raises(mock_populate):
705
712
  # artefact.contribution is None: should assert
@@ -717,38 +724,43 @@ def test_fix_rule_contribution_none_raises(mock_populate):
717
724
  classified_artefact_info={},
718
725
  )
719
726
 
727
+
720
728
  def test_populate_classified_artefact_info_force_true():
721
729
  """Test populate_classified_artefact_info with force=True"""
722
- with patch('ara_cli.artefact_autofix.FileClassifier') as mock_classifier:
730
+ with patch("ara_cli.artefact_autofix.FileClassifier") as mock_classifier:
723
731
  mock_instance = mock_classifier.return_value
724
732
  mock_instance.classify_files.return_value = {"new": "data"}
725
-
733
+
726
734
  result = populate_classified_artefact_info({"old": "data"}, force=True)
727
-
735
+
728
736
  assert result == {"new": "data"}
729
737
  mock_classifier.assert_called_once()
730
738
 
739
+
731
740
  def test_populate_classified_artefact_info_none_input():
732
741
  """Test populate_classified_artefact_info with None input"""
733
- with patch('ara_cli.artefact_autofix.FileClassifier') as mock_classifier:
742
+ with patch("ara_cli.artefact_autofix.FileClassifier") as mock_classifier:
734
743
  mock_instance = mock_classifier.return_value
735
744
  mock_instance.classify_files.return_value = {"classified": "data"}
736
-
745
+
737
746
  result = populate_classified_artefact_info(None)
738
-
747
+
739
748
  assert result == {"classified": "data"}
740
749
  mock_classifier.assert_called_once()
741
750
 
751
+
742
752
  def test_parse_report_empty_content():
743
753
  """Test parse_report with empty content"""
744
754
  assert parse_report("") == {}
745
755
 
756
+
746
757
  def test_parse_report_missing_reason():
747
758
  """Test parse_report with missing reason in issue line"""
748
759
  content = "# Artefact Check Report\n\n## feature\n- `file.feature`\n"
749
760
  expected = {"feature": [("file.feature", "")]}
750
761
  assert parse_report(content) == expected
751
762
 
763
+
752
764
  def test_parse_report_multiple_classifiers():
753
765
  """Test parse_report with multiple classifiers"""
754
766
  content = (
@@ -758,38 +770,44 @@ def test_parse_report_multiple_classifiers():
758
770
  )
759
771
  expected = {
760
772
  "feature": [("file1.feature", "reason1")],
761
- "task": [("file2.task", "reason2")]
773
+ "task": [("file2.task", "reason2")],
762
774
  }
763
775
  assert parse_report(content) == expected
764
776
 
777
+
765
778
  def test_construct_prompt_non_task_artefact():
766
779
  """Test construct_prompt for non-task artefact types"""
767
- prompt = construct_prompt(ArtefactType.feature, "some reason", "file.feature", "text")
780
+ prompt = construct_prompt(
781
+ ArtefactType.feature, "some reason", "file.feature", "text"
782
+ )
768
783
  assert "For task artefacts" not in prompt
769
784
  assert "some reason" in prompt
770
785
  assert "file.feature" in prompt
771
786
 
772
- @patch("pydantic_ai.Agent")
773
- def test_run_agent_success(mock_agent_class):
787
+
788
+ @patch("ara_cli.llm_utils.create_pydantic_ai_agent")
789
+ def test_run_agent_success(mock_create_agent):
774
790
  """Test successful run_agent execution"""
775
- mock_agent_instance = mock_agent_class.return_value
791
+ mock_agent_instance = mock_create_agent.return_value
776
792
  mock_result = MagicMock()
777
793
  mock_result.output = "agent output"
778
794
  mock_agent_instance.run_sync.return_value = mock_result
779
-
795
+
780
796
  result = run_agent("test prompt", MagicMock())
781
-
797
+
782
798
  assert result == "agent output"
783
- mock_agent_class.assert_called_once()
799
+ mock_create_agent.assert_called_once()
800
+
784
801
 
785
802
  def test_write_corrected_artefact_with_print(capsys):
786
803
  """Test write_corrected_artefact prints success message"""
787
804
  with patch("builtins.open", mock_open()) as m:
788
805
  write_corrected_artefact("file.feature", "corrected content")
789
-
806
+
790
807
  captured = capsys.readouterr()
791
808
  assert "Fixed artefact at file.feature" in captured.out
792
809
 
810
+
793
811
  # Tests for the new scenario placeholder functions
794
812
  def test_extract_scenario_block():
795
813
  """Test _extract_scenario_block function"""
@@ -798,15 +816,16 @@ def test_extract_scenario_block():
798
816
  "Scenario: Test scenario",
799
817
  " Given something",
800
818
  " When something",
801
- "Scenario: Another scenario"
819
+ "Scenario: Another scenario",
802
820
  ]
803
-
821
+
804
822
  scenario_lines, next_index = _extract_scenario_block(lines, 1)
805
-
823
+
806
824
  assert len(scenario_lines) == 3
807
825
  assert scenario_lines[0] == "Scenario: Test scenario"
808
826
  assert next_index == 4
809
827
 
828
+
810
829
  def test_is_scenario_boundary():
811
830
  """Test _is_scenario_boundary function"""
812
831
  assert _is_scenario_boundary("Scenario: test")
@@ -815,49 +834,50 @@ def test_is_scenario_boundary():
815
834
  assert _is_scenario_boundary("Feature: test")
816
835
  assert not _is_scenario_boundary("Given something")
817
836
 
837
+
818
838
  def test_process_scenario_block_no_placeholders():
819
839
  """Test _process_scenario_block with no placeholders"""
820
- scenario_lines = [
821
- " Scenario: Test",
822
- " Given something",
823
- " When something"
824
- ]
825
-
840
+ scenario_lines = [" Scenario: Test", " Given something", " When something"]
841
+
826
842
  result = _process_scenario_block(scenario_lines)
827
-
843
+
828
844
  assert result == scenario_lines
829
845
 
846
+
830
847
  def test_process_scenario_block_with_placeholders():
831
848
  """Test _process_scenario_block with placeholders"""
832
849
  scenario_lines = [
833
850
  " Scenario: Test",
834
851
  " Given something with <placeholder>",
835
- " When something with <another>"
852
+ " When something with <another>",
836
853
  ]
837
-
854
+
838
855
  result = _process_scenario_block(scenario_lines)
839
-
856
+
840
857
  assert "Scenario Outline:" in result[0]
841
858
  assert "Examples:" in result[-3]
842
859
 
860
+
843
861
  def test_get_line_indentation():
844
862
  """Test _get_line_indentation function"""
845
863
  assert _get_line_indentation(" indented line") == " "
846
864
  assert _get_line_indentation("no indent") == ""
847
865
  assert _get_line_indentation(" two spaces") == " "
848
866
 
867
+
849
868
  def test_extract_placeholders_from_scenario():
850
869
  """Test _extract_placeholders_from_scenario function"""
851
870
  step_lines = [
852
871
  " Given something with <placeholder1>",
853
872
  " When something with <placeholder2>",
854
- " Then something normal"
873
+ " Then something normal",
855
874
  ]
856
-
875
+
857
876
  placeholders = _extract_placeholders_from_scenario(step_lines)
858
-
877
+
859
878
  assert placeholders == {"placeholder1", "placeholder2"}
860
879
 
880
+
861
881
  def test_extract_placeholders_with_docstring():
862
882
  """Test _extract_placeholders_from_scenario ignoring docstrings"""
863
883
  step_lines = [
@@ -865,196 +885,249 @@ def test_extract_placeholders_with_docstring():
865
885
  ' """',
866
886
  " Some docstring with <not_a_placeholder>",
867
887
  ' """',
868
- " When something with <placeholder2>"
888
+ " When something with <placeholder2>",
869
889
  ]
870
-
890
+
871
891
  placeholders = _extract_placeholders_from_scenario(step_lines)
872
-
892
+
873
893
  assert placeholders == {"placeholder1", "placeholder2"}
874
894
 
895
+
875
896
  def test_update_docstring_state():
876
897
  """Test _update_docstring_state function"""
877
898
  assert _update_docstring_state('"""', False) == True
878
899
  assert _update_docstring_state('"""', True) == False
879
- assert _update_docstring_state('normal line', False) == False
880
- assert _update_docstring_state('normal line', True) == True
900
+ assert _update_docstring_state("normal line", False) == False
901
+ assert _update_docstring_state("normal line", True) == True
902
+
881
903
 
882
904
  def test_convert_to_scenario_outline():
883
905
  """Test _convert_to_scenario_outline function"""
884
906
  scenario_lines = [
885
907
  " Scenario: Test scenario",
886
908
  " Given something",
887
- " When something"
909
+ " When something",
888
910
  ]
889
911
  placeholders = {"placeholder1", "placeholder2"}
890
-
912
+
891
913
  result = _convert_to_scenario_outline(scenario_lines, placeholders, " ")
892
-
914
+
893
915
  assert "Scenario Outline: Test scenario" in result[0]
894
916
  assert "Examples:" in result[-3]
895
917
 
918
+
896
919
  def test_create_examples_table():
897
920
  """Test _create_examples_table function"""
898
921
  placeholders = {"param1", "param2"}
899
-
922
+
900
923
  result = _create_examples_table(placeholders, " ")
901
-
924
+
902
925
  assert len(result) == 3
903
926
  assert "Examples:" in result[0]
904
927
  assert "| param1 | param2 |" in result[1]
905
928
  assert "<param1_value>" in result[2]
906
929
 
930
+
907
931
  def test_fix_scenario_placeholder_mismatch_no_scenarios():
908
932
  """Test fix_scenario_placeholder_mismatch with no scenarios"""
909
933
  artefact_text = "Feature: Test\nBackground:\n Given something"
910
-
911
- result = fix_scenario_placeholder_mismatch("file.feature", artefact_text, MagicMock())
912
-
934
+
935
+ result = fix_scenario_placeholder_mismatch(
936
+ "file.feature", artefact_text, MagicMock()
937
+ )
938
+
913
939
  assert result == artefact_text
914
940
 
941
+
915
942
  def test_fix_scenario_placeholder_mismatch_with_placeholders():
916
943
  """Test fix_scenario_placeholder_mismatch converting to outline"""
917
944
  artefact_text = """Feature: Test
918
945
  Scenario: Test scenario
919
946
  Given something with <placeholder>
920
947
  When something happens"""
921
-
922
- result = fix_scenario_placeholder_mismatch("file.feature", artefact_text, MagicMock())
923
-
948
+
949
+ result = fix_scenario_placeholder_mismatch(
950
+ "file.feature", artefact_text, MagicMock()
951
+ )
952
+
924
953
  assert "Scenario Outline:" in result
925
954
  assert "Examples:" in result
926
955
  assert "<placeholder>" in result
927
956
 
957
+
928
958
  def test_should_skip_issue_non_deterministic_false():
929
959
  """Test should_skip_issue when non_deterministic is False"""
930
960
  result = should_skip_issue(None, True, False, "file.txt")
931
961
  assert result == True
932
962
 
963
+
933
964
  def test_should_skip_issue_deterministic_false():
934
965
  """Test should_skip_issue when deterministic is False"""
935
966
  result = should_skip_issue("some_issue", False, True, "file.txt")
936
967
  assert result == True
937
968
 
969
+
938
970
  def test_should_skip_issue_no_skip():
939
971
  """Test should_skip_issue when no skip is needed"""
940
972
  result = should_skip_issue("some_issue", True, True, "file.txt")
941
973
  assert result == False
942
974
 
975
+
943
976
  def test_determine_attempt_count_single_pass():
944
977
  """Test determine_attempt_count with single_pass=True"""
945
978
  result = determine_attempt_count(True, "file.txt")
946
979
  assert result == 1
947
980
 
981
+
948
982
  def test_determine_attempt_count_multiple_pass():
949
983
  """Test determine_attempt_count with single_pass=False"""
950
984
  result = determine_attempt_count(False, "file.txt")
951
985
  assert result == 3
952
986
 
987
+
953
988
  def test_apply_deterministic_fix_with_issue():
954
989
  """Test apply_deterministic_fix when deterministic issue exists"""
955
990
  mock_fix_function = MagicMock(return_value="fixed_text")
956
991
  deterministic_markers = {"test_issue": mock_fix_function}
957
-
992
+
958
993
  result = apply_deterministic_fix(
959
- True, "test_issue", "file.txt", "original", MagicMock(),
960
- {}, deterministic_markers, "corrected"
994
+ True,
995
+ "test_issue",
996
+ "file.txt",
997
+ "original",
998
+ MagicMock(),
999
+ {},
1000
+ deterministic_markers,
1001
+ "corrected",
961
1002
  )
962
-
1003
+
963
1004
  assert result == "fixed_text"
964
1005
  mock_fix_function.assert_called_once()
965
1006
 
1007
+
966
1008
  def test_apply_deterministic_fix_no_issue():
967
1009
  """Test apply_deterministic_fix when no deterministic issue"""
968
1010
  result = apply_deterministic_fix(
969
- True, None, "file.txt", "original", MagicMock(),
970
- {}, {}, "corrected"
1011
+ True, None, "file.txt", "original", MagicMock(), {}, {}, "corrected"
971
1012
  )
972
-
1013
+
973
1014
  assert result == "corrected"
974
1015
 
975
- @patch('ara_cli.artefact_autofix.construct_prompt')
976
- @patch('ara_cli.artefact_autofix.run_agent')
1016
+
1017
+ @patch("ara_cli.artefact_autofix.construct_prompt")
1018
+ @patch("ara_cli.artefact_autofix.run_agent")
977
1019
  def test_apply_non_deterministic_fix_success(mock_run_agent, mock_construct_prompt):
978
1020
  """Test apply_non_deterministic_fix successful execution"""
979
1021
  mock_construct_prompt.return_value = "test prompt"
980
1022
  mock_artefact = MagicMock()
981
1023
  mock_artefact.serialize.return_value = "fixed_text"
982
1024
  mock_run_agent.return_value = mock_artefact
983
-
1025
+
984
1026
  result = apply_non_deterministic_fix(
985
- True, None, "corrected", MagicMock(), "reason",
986
- "file.txt", "original", MagicMock()
1027
+ True,
1028
+ None,
1029
+ "corrected",
1030
+ MagicMock(),
1031
+ "reason",
1032
+ "file.txt",
1033
+ "original",
1034
+ MagicMock(),
987
1035
  )
988
-
1036
+
989
1037
  assert result == "fixed_text"
990
1038
 
1039
+
991
1040
  def test_apply_non_deterministic_fix_with_deterministic_issue():
992
1041
  """Test apply_non_deterministic_fix when deterministic issue exists"""
993
1042
  result = apply_non_deterministic_fix(
994
- True, "some_issue", "corrected", MagicMock(), "reason",
995
- "file.txt", "original", MagicMock()
1043
+ True,
1044
+ "some_issue",
1045
+ "corrected",
1046
+ MagicMock(),
1047
+ "reason",
1048
+ "file.txt",
1049
+ "original",
1050
+ MagicMock(),
996
1051
  )
997
-
1052
+
998
1053
  assert result == "corrected"
999
1054
 
1000
- @patch('ara_cli.artefact_autofix.construct_prompt')
1001
- @patch('ara_cli.artefact_autofix.run_agent', side_effect=Exception("LLM Error"))
1002
- def test_apply_non_deterministic_fix_exception(mock_run_agent, mock_construct_prompt, capsys):
1055
+
1056
+ @patch("ara_cli.artefact_autofix.construct_prompt")
1057
+ @patch("ara_cli.artefact_autofix.run_agent", side_effect=Exception("LLM Error"))
1058
+ def test_apply_non_deterministic_fix_exception(
1059
+ mock_run_agent, mock_construct_prompt, capsys
1060
+ ):
1003
1061
  """Test apply_non_deterministic_fix with exception"""
1004
1062
  mock_construct_prompt.return_value = "test prompt"
1005
-
1063
+
1006
1064
  result = apply_non_deterministic_fix(
1007
- True, None, "corrected", MagicMock(), "reason",
1008
- "file.txt", "original", MagicMock()
1065
+ True,
1066
+ None,
1067
+ "corrected",
1068
+ MagicMock(),
1069
+ "reason",
1070
+ "file.txt",
1071
+ "original",
1072
+ MagicMock(),
1009
1073
  )
1010
-
1074
+
1011
1075
  assert result is None
1012
1076
  assert "LLM agent failed" in capsys.readouterr().out
1013
1077
 
1014
- @patch('ara_cli.artefact_autofix.check_file')
1015
- @patch('ara_cli.artefact_autofix.read_artefact')
1016
- @patch('ara_cli.artefact_autofix.write_corrected_artefact')
1017
- @patch('ara_cli.artefact_autofix.populate_classified_artefact_info')
1018
- @patch('ara_cli.artefact_autofix.should_skip_issue', return_value=False)
1019
- @patch('ara_cli.artefact_autofix.apply_deterministic_fix')
1020
- @patch('ara_cli.artefact_autofix.apply_non_deterministic_fix')
1078
+
1079
+ @patch("ara_cli.artefact_autofix.check_file")
1080
+ @patch("ara_cli.artefact_autofix.read_artefact")
1081
+ @patch("ara_cli.artefact_autofix.write_corrected_artefact")
1082
+ @patch("ara_cli.artefact_autofix.populate_classified_artefact_info")
1083
+ @patch("ara_cli.artefact_autofix.should_skip_issue", return_value=False)
1084
+ @patch("ara_cli.artefact_autofix.apply_deterministic_fix")
1085
+ @patch("ara_cli.artefact_autofix.apply_non_deterministic_fix")
1021
1086
  def test_attempt_autofix_loop_max_attempts_reached(
1022
- mock_apply_non_det, mock_apply_det, mock_should_skip, mock_populate,
1023
- mock_write, mock_read, mock_check_file, capsys
1087
+ mock_apply_non_det,
1088
+ mock_apply_det,
1089
+ mock_should_skip,
1090
+ mock_populate,
1091
+ mock_write,
1092
+ mock_read,
1093
+ mock_check_file,
1094
+ capsys,
1024
1095
  ):
1025
1096
  """Test attempt_autofix_loop when max attempts are reached"""
1026
1097
  mock_check_file.return_value = (False, "persistent error")
1027
1098
  mock_read.return_value = "original text"
1028
1099
  mock_apply_det.return_value = "modified text" # Ensure text is modified
1029
1100
  mock_apply_non_det.return_value = "modified text" # Ensure text is modified
1030
-
1101
+
1031
1102
  result = attempt_autofix_loop(
1032
1103
  "file.txt", MagicMock(), MagicMock(), {}, 2, True, True, {}
1033
1104
  )
1034
-
1105
+
1035
1106
  assert result == False
1036
1107
  assert "Failed to fix file.txt after 2 attempts" in capsys.readouterr().out
1037
1108
 
1038
- @patch('ara_cli.artefact_autofix.check_file')
1109
+
1110
+ @patch("ara_cli.artefact_autofix.check_file")
1039
1111
  def test_attempt_autofix_loop_already_valid(mock_check_file, capsys):
1040
1112
  """Test attempt_autofix_loop when file is already valid"""
1041
1113
  mock_check_file.return_value = (True, "")
1042
-
1114
+
1043
1115
  result = attempt_autofix_loop(
1044
1116
  "file.txt", MagicMock(), MagicMock(), {}, 3, True, True, {}
1045
1117
  )
1046
-
1118
+
1047
1119
  assert result == True
1048
1120
  assert "is now valid" in capsys.readouterr().out
1049
1121
 
1050
- @patch('ara_cli.artefact_autofix.check_file')
1051
- @patch('ara_cli.artefact_autofix.read_artefact', return_value=None)
1122
+
1123
+ @patch("ara_cli.artefact_autofix.check_file")
1124
+ @patch("ara_cli.artefact_autofix.read_artefact", return_value=None)
1052
1125
  def test_attempt_autofix_loop_read_fails(mock_read, mock_check_file):
1053
1126
  """Test attempt_autofix_loop when reading artefact fails"""
1054
1127
  mock_check_file.return_value = (False, "some error")
1055
-
1128
+
1056
1129
  result = attempt_autofix_loop(
1057
1130
  "file.txt", MagicMock(), MagicMock(), {}, 3, True, True, {}
1058
1131
  )
1059
-
1060
- assert result == False
1132
+
1133
+ assert result == False