ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.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.
Files changed (151) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +189 -101
  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 +107 -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/ara_subcommands/prompt.py +266 -106
  14. ara_cli/artefact_autofix.py +117 -64
  15. ara_cli/artefact_converter.py +355 -0
  16. ara_cli/artefact_creator.py +41 -17
  17. ara_cli/artefact_lister.py +3 -3
  18. ara_cli/artefact_models/artefact_model.py +1 -1
  19. ara_cli/artefact_models/artefact_templates.py +0 -9
  20. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  21. ara_cli/artefact_reader.py +62 -43
  22. ara_cli/artefact_scan.py +39 -17
  23. ara_cli/chat.py +300 -71
  24. ara_cli/chat_agent/__init__.py +0 -0
  25. ara_cli/chat_agent/agent_process_manager.py +155 -0
  26. ara_cli/chat_script_runner/__init__.py +0 -0
  27. ara_cli/chat_script_runner/script_completer.py +23 -0
  28. ara_cli/chat_script_runner/script_finder.py +41 -0
  29. ara_cli/chat_script_runner/script_lister.py +36 -0
  30. ara_cli/chat_script_runner/script_runner.py +36 -0
  31. ara_cli/chat_web_search/__init__.py +0 -0
  32. ara_cli/chat_web_search/web_search.py +263 -0
  33. ara_cli/children_contribution_updater.py +737 -0
  34. ara_cli/classifier.py +34 -0
  35. ara_cli/commands/agent_run_command.py +98 -0
  36. ara_cli/commands/fetch_agents_command.py +106 -0
  37. ara_cli/commands/fetch_scripts_command.py +43 -0
  38. ara_cli/commands/fetch_templates_command.py +39 -0
  39. ara_cli/commands/fetch_templates_commands.py +39 -0
  40. ara_cli/commands/list_agents_command.py +39 -0
  41. ara_cli/commands/load_command.py +4 -3
  42. ara_cli/commands/load_image_command.py +1 -1
  43. ara_cli/commands/read_command.py +23 -27
  44. ara_cli/completers.py +95 -35
  45. ara_cli/constants.py +2 -0
  46. ara_cli/directory_navigator.py +37 -4
  47. ara_cli/error_handler.py +26 -11
  48. ara_cli/file_loaders/document_reader.py +0 -178
  49. ara_cli/file_loaders/factories/__init__.py +0 -0
  50. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  51. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  52. ara_cli/file_loaders/file_loader.py +1 -30
  53. ara_cli/file_loaders/loaders/__init__.py +0 -0
  54. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  55. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  56. ara_cli/file_loaders/readers/__init__.py +0 -0
  57. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  58. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  59. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  60. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  61. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  62. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  63. ara_cli/file_loaders/tools/__init__.py +0 -0
  64. ara_cli/llm_utils.py +58 -0
  65. ara_cli/output_suppressor.py +53 -0
  66. ara_cli/prompt_chat.py +20 -4
  67. ara_cli/prompt_extractor.py +47 -32
  68. ara_cli/prompt_handler.py +123 -17
  69. ara_cli/tag_extractor.py +8 -7
  70. ara_cli/template_loader.py +2 -1
  71. ara_cli/template_manager.py +52 -21
  72. ara_cli/templates/global-scripts/hello_global.py +1 -0
  73. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  79. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  82. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  83. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  84. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  92. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  93. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  94. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  95. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  96. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  97. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  98. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  99. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  100. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  101. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  102. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  103. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  104. ara_cli/version.py +1 -1
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
  106. ara_cli-0.1.14.0.dist-info/RECORD +253 -0
  107. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  108. tests/test_ara_command_action.py +31 -19
  109. tests/test_ara_config.py +177 -90
  110. tests/test_artefact_autofix.py +170 -97
  111. tests/test_artefact_autofix_integration.py +495 -0
  112. tests/test_artefact_converter.py +312 -0
  113. tests/test_artefact_extraction.py +564 -0
  114. tests/test_artefact_lister.py +11 -8
  115. tests/test_chat.py +166 -130
  116. tests/test_chat_givens_images.py +603 -0
  117. tests/test_chat_script_runner.py +454 -0
  118. tests/test_children_contribution_updater.py +98 -0
  119. tests/test_document_loader_office.py +267 -0
  120. tests/test_llm_utils.py +164 -0
  121. tests/test_prompt_chat.py +343 -0
  122. tests/test_prompt_extractor.py +683 -0
  123. tests/test_prompt_handler.py +416 -214
  124. tests/test_setup_default_chat_prompt_mode.py +198 -0
  125. tests/test_tag_extractor.py +95 -49
  126. tests/test_web_search.py +467 -0
  127. ara_cli/file_loaders/document_readers.py +0 -233
  128. ara_cli/file_loaders/file_loaders.py +0 -123
  129. ara_cli/file_loaders/text_file_loader.py +0 -187
  130. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  131. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  132. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  133. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  134. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  135. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  136. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  137. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  138. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  139. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  140. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  141. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  142. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  143. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  144. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  145. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  146. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  147. ara_cli-0.1.10.5.dist-info/RECORD +0 -194
  148. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  149. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  150. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  151. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
@@ -140,16 +140,11 @@ def construct_prompt(artefact_type, reason, file_path, artefact_text):
140
140
 
141
141
 
142
142
  def run_agent(prompt, artefact_class):
143
- from pydantic_ai import Agent
144
-
145
- # gpt-4o
146
- # anthropic:claude-3-7-sonnet-20250219
147
- # anthropic:claude-4-sonnet-20250514
148
- agent = Agent(
149
- model="anthropic:claude-4-sonnet-20250514",
150
- output_type=artefact_class,
151
- instrument=True,
152
- )
143
+ from ara_cli.llm_utils import create_pydantic_ai_agent
144
+
145
+ # Use the shared agent creation logic which respects configuration and validation
146
+ agent = create_pydantic_ai_agent(output_type=artefact_class, instrument=True)
147
+
153
148
  result = agent.run_sync(prompt)
154
149
  return result.output
155
150
 
@@ -161,7 +156,7 @@ def write_corrected_artefact(file_path, corrected_text):
161
156
 
162
157
 
163
158
  def ask_for_correct_contribution(
164
- artefact_info: Optional[tuple[str, str]] = None
159
+ artefact_info: Optional[tuple[str, str]] = None,
165
160
  ) -> tuple[str, str]:
166
161
  """
167
162
  Ask the user to provide a valid contribution when no match can be found.
@@ -199,8 +194,12 @@ def ask_for_correct_contribution(
199
194
  return name, classifier
200
195
 
201
196
 
202
- def ask_for_contribution_choice(choices: List[str], artefact_info: Optional[tuple[str, str]] = None) -> Optional[str]:
203
- artefact_name, artefact_classifier = artefact_info if artefact_info else (None, None)
197
+ def ask_for_contribution_choice(
198
+ choices: List[str], artefact_info: Optional[tuple[str, str]] = None
199
+ ) -> Optional[str]:
200
+ artefact_name, artefact_classifier = (
201
+ artefact_info if artefact_info else (None, None)
202
+ )
204
203
  message = "Found multiple close matches for the contribution"
205
204
  if artefact_name and artefact_classifier:
206
205
  message += f" of the {artefact_classifier} '{artefact_name}'"
@@ -248,12 +247,16 @@ def ask_for_rule_choice(matches: List[str]) -> Optional[str]:
248
247
 
249
248
 
250
249
  def _update_rule(
251
- artefact: Artefact, name: str, classifier: str, classified_file_info: dict, delete_if_not_found: bool = False
250
+ artefact: Artefact,
251
+ name: str,
252
+ classifier: str,
253
+ classified_file_info: dict,
254
+ delete_if_not_found: bool = False,
252
255
  ) -> None:
253
256
  """Updates the rule in the contribution if a close match is found."""
254
257
  rule = artefact.contribution.rule
255
258
 
256
- content, artefact_data = ArtefactReader.read_artefact_data(
259
+ content, artefact_data = ArtefactReader().read_artefact_data(
257
260
  artefact_name=name,
258
261
  classifier=classifier,
259
262
  classified_file_info=classified_file_info,
@@ -317,7 +320,9 @@ def set_closest_contribution(
317
320
  classifier = contribution.classifier
318
321
  rule = contribution.rule
319
322
 
320
- classified_file_info = populate_classified_artefact_info(classified_artefact_info=classified_file_info)
323
+ classified_file_info = populate_classified_artefact_info(
324
+ classified_artefact_info=classified_file_info
325
+ )
321
326
 
322
327
  all_artefact_names = extract_artefact_names_of_classifier(
323
328
  classified_files=classified_file_info, classifier=classifier
@@ -360,7 +365,7 @@ def set_closest_contribution(
360
365
  if not rule:
361
366
  return artefact, True
362
367
 
363
- content, artefact = ArtefactReader.read_artefact_data(
368
+ content, artefact = ArtefactReader().read_artefact_data(
364
369
  artefact_name=name,
365
370
  classifier=classifier,
366
371
  classified_file_info=classified_file_info,
@@ -385,12 +390,12 @@ def fix_scenario_placeholder_mismatch(
385
390
  lines = artefact_text.splitlines()
386
391
  new_lines = []
387
392
  i = 0
388
-
393
+
389
394
  while i < len(lines):
390
395
  line = lines[i]
391
396
  stripped_line = line.strip()
392
-
393
- if stripped_line.startswith('Scenario:'):
397
+
398
+ if stripped_line.startswith("Scenario:"):
394
399
  scenario_lines, next_index = _extract_scenario_block(lines, i)
395
400
  processed_lines = _process_scenario_block(scenario_lines)
396
401
  new_lines.extend(processed_lines)
@@ -398,7 +403,7 @@ def fix_scenario_placeholder_mismatch(
398
403
  else:
399
404
  new_lines.append(line)
400
405
  i += 1
401
-
406
+
402
407
  return "\n".join(new_lines)
403
408
 
404
409
 
@@ -406,20 +411,20 @@ def _extract_scenario_block(lines: list, start_index: int) -> tuple[list, int]:
406
411
  """Extract all lines belonging to a scenario block."""
407
412
  scenario_lines = [lines[start_index]]
408
413
  j = start_index + 1
409
-
414
+
410
415
  while j < len(lines):
411
416
  next_line = lines[j].strip()
412
417
  if _is_scenario_boundary(next_line):
413
418
  break
414
419
  scenario_lines.append(lines[j])
415
420
  j += 1
416
-
421
+
417
422
  return scenario_lines, j
418
423
 
419
424
 
420
425
  def _is_scenario_boundary(line: str) -> bool:
421
426
  """Check if a line marks the boundary of a scenario block."""
422
- boundaries = ['Scenario:', 'Scenario Outline:', 'Background:', 'Feature:']
427
+ boundaries = ["Scenario:", "Scenario Outline:", "Background:", "Feature:"]
423
428
  return any(line.startswith(boundary) for boundary in boundaries)
424
429
 
425
430
 
@@ -427,38 +432,38 @@ def _process_scenario_block(scenario_lines: list) -> list:
427
432
  """Process a scenario block and convert to outline if placeholders are found."""
428
433
  if not scenario_lines:
429
434
  return scenario_lines
430
-
435
+
431
436
  first_line = scenario_lines[0]
432
437
  indentation = _get_line_indentation(first_line)
433
438
  placeholders = _extract_placeholders_from_scenario(scenario_lines[1:])
434
-
439
+
435
440
  if not placeholders:
436
441
  return scenario_lines
437
-
442
+
438
443
  return _convert_to_scenario_outline(scenario_lines, placeholders, indentation)
439
444
 
440
445
 
441
446
  def _get_line_indentation(line: str) -> str:
442
447
  """Get the indentation of a line."""
443
- return line[:len(line) - len(line.lstrip())]
448
+ return line[: len(line) - len(line.lstrip())]
444
449
 
445
450
 
446
451
  def _extract_placeholders_from_scenario(step_lines: list) -> set:
447
452
  """Extract placeholders from scenario step lines, ignoring docstrings."""
448
453
  placeholders = set()
449
454
  in_docstring = False
450
-
455
+
451
456
  for line in step_lines:
452
457
  step_line = line.strip()
453
458
  if not step_line:
454
459
  continue
455
-
460
+
456
461
  in_docstring = _update_docstring_state(step_line, in_docstring)
457
-
462
+
458
463
  if not in_docstring and '"""' not in step_line:
459
- found = re.findall(r'<([^>]+)>', step_line)
464
+ found = re.findall(r"<([^>]+)>", step_line)
460
465
  placeholders.update(found)
461
-
466
+
462
467
  return placeholders
463
468
 
464
469
 
@@ -469,18 +474,20 @@ def _update_docstring_state(line: str, current_state: bool) -> bool:
469
474
  return current_state
470
475
 
471
476
 
472
- def _convert_to_scenario_outline(scenario_lines: list, placeholders: set, indentation: str) -> list:
477
+ def _convert_to_scenario_outline(
478
+ scenario_lines: list, placeholders: set, indentation: str
479
+ ) -> list:
473
480
  """Convert scenario lines to scenario outline format with examples table."""
474
481
  first_line = scenario_lines[0]
475
- title = first_line.strip()[len('Scenario:'):].strip()
476
-
482
+ title = first_line.strip()[len("Scenario:") :].strip()
483
+
477
484
  new_lines = [f"{indentation}Scenario Outline: {title}"]
478
485
  new_lines.extend(scenario_lines[1:])
479
486
  new_lines.append("")
480
-
487
+
481
488
  examples_lines = _create_examples_table(placeholders, indentation)
482
489
  new_lines.extend(examples_lines)
483
-
490
+
484
491
  return new_lines
485
492
 
486
493
 
@@ -488,15 +495,15 @@ def _create_examples_table(placeholders: set, base_indentation: str) -> list:
488
495
  """Create the Examples table for the scenario outline."""
489
496
  examples_indentation = base_indentation + " "
490
497
  table_indentation = examples_indentation + " "
491
-
498
+
492
499
  sorted_placeholders = sorted(placeholders)
493
500
  header = "| " + " | ".join(sorted_placeholders) + " |"
494
501
  sample_row = "| " + " | ".join(f"<{p}_value>" for p in sorted_placeholders) + " |"
495
-
502
+
496
503
  return [
497
504
  f"{examples_indentation}Examples:",
498
505
  f"{table_indentation}{header}",
499
- f"{table_indentation}{sample_row}"
506
+ f"{table_indentation}{sample_row}",
500
507
  ]
501
508
 
502
509
 
@@ -539,7 +546,9 @@ def fix_contribution(
539
546
  classified_artefact_info: dict,
540
547
  **kwargs,
541
548
  ):
542
- classified_artefact_info = populate_classified_artefact_info(classified_artefact_info=classified_artefact_info)
549
+ classified_artefact_info = populate_classified_artefact_info(
550
+ classified_artefact_info=classified_artefact_info
551
+ )
543
552
  artefact = artefact_class.deserialize(artefact_text)
544
553
  artefact, _ = set_closest_contribution(artefact)
545
554
  artefact_text = artefact.serialize()
@@ -553,7 +562,9 @@ def fix_rule(
553
562
  classified_artefact_info: dict,
554
563
  **kwargs,
555
564
  ):
556
- classified_artefact_info = populate_classified_artefact_info(classified_artefact_info=classified_artefact_info)
565
+ classified_artefact_info = populate_classified_artefact_info(
566
+ classified_artefact_info=classified_artefact_info
567
+ )
557
568
  artefact = artefact_class.deserialize(artefact_text)
558
569
  contribution = artefact.contribution
559
570
  assert contribution is not None
@@ -562,11 +573,13 @@ def fix_rule(
562
573
  name=contribution.artefact_name,
563
574
  classifier=contribution.classifier,
564
575
  classified_file_info=classified_artefact_info,
565
- delete_if_not_found=True
576
+ delete_if_not_found=True,
577
+ )
578
+ feedback_message = (
579
+ f"Updating contribution of {artefact._artefact_type().value} "
580
+ f"'{artefact.title}' to {contribution.classifier} "
581
+ f"'{contribution.artefact_name}' "
566
582
  )
567
- feedback_message = (f"Updating contribution of {artefact._artefact_type().value} "
568
- f"'{artefact.title}' to {contribution.classifier} "
569
- f"'{contribution.artefact_name}' ")
570
583
  rule = contribution.rule
571
584
  if rule:
572
585
  feedback_message += f"with rule '{rule}'"
@@ -593,7 +606,7 @@ def fix_misplaced_content(file_path: str, artefact_text: str, **kwargs) -> str:
593
606
 
594
607
  pre_desc_lines = lines[:desc_start_idx]
595
608
  desc_line = lines[desc_start_idx]
596
- post_desc_lines = lines[desc_start_idx+1:]
609
+ post_desc_lines = lines[desc_start_idx + 1 :]
597
610
 
598
611
  misplaced_content = []
599
612
  new_post_desc_lines = []
@@ -608,11 +621,15 @@ def fix_misplaced_content(file_path: str, artefact_text: str, **kwargs) -> str:
608
621
  return artefact_text
609
622
 
610
623
  # Rebuild the file content
611
- final_lines = pre_desc_lines + misplaced_content + [""] + [desc_line] + new_post_desc_lines
624
+ final_lines = (
625
+ pre_desc_lines + misplaced_content + [""] + [desc_line] + new_post_desc_lines
626
+ )
612
627
  return "\n".join(final_lines)
613
628
 
614
629
 
615
- def should_skip_issue(deterministic_issue, deterministic, non_deterministic, file_path) -> bool:
630
+ def should_skip_issue(
631
+ deterministic_issue, deterministic, non_deterministic, file_path
632
+ ) -> bool:
616
633
  if not non_deterministic and not deterministic_issue:
617
634
  print(f"Skipping non-deterministic fix for {file_path} as per request.")
618
635
  return True
@@ -621,15 +638,23 @@ def should_skip_issue(deterministic_issue, deterministic, non_deterministic, fil
621
638
  return True
622
639
  return False
623
640
 
641
+
624
642
  def determine_attempt_count(single_pass, file_path) -> int:
625
643
  if single_pass:
626
644
  print(f"Single-pass mode enabled for {file_path}. Running for 1 attempt.")
627
645
  return 1
628
646
  return 3
629
647
 
648
+
630
649
  def apply_deterministic_fix(
631
- deterministic, deterministic_issue, file_path, artefact_text, artefact_class, classified_artefact_info,
632
- deterministic_markers_to_functions, corrected_text
650
+ deterministic,
651
+ deterministic_issue,
652
+ file_path,
653
+ artefact_text,
654
+ artefact_class,
655
+ classified_artefact_info,
656
+ deterministic_markers_to_functions,
657
+ corrected_text,
633
658
  ) -> str:
634
659
  if deterministic and deterministic_issue:
635
660
  print(f"Applying deterministic fix for '{deterministic_issue}'...")
@@ -642,9 +667,16 @@ def apply_deterministic_fix(
642
667
  )
643
668
  return corrected_text
644
669
 
670
+
645
671
  def apply_non_deterministic_fix(
646
- non_deterministic, deterministic_issue, corrected_text,
647
- artefact_type, current_reason, file_path, artefact_text, artefact_class
672
+ non_deterministic,
673
+ deterministic_issue,
674
+ corrected_text,
675
+ artefact_type,
676
+ current_reason,
677
+ file_path,
678
+ artefact_text,
679
+ artefact_class,
648
680
  ) -> Optional[str]:
649
681
  """
650
682
  Applies LLM fix. Return None in case of an exception
@@ -662,6 +694,7 @@ def apply_non_deterministic_fix(
662
694
  return None
663
695
  return corrected_text
664
696
 
697
+
665
698
  def attempt_autofix_loop(
666
699
  file_path: str,
667
700
  artefact_type,
@@ -702,19 +735,32 @@ def attempt_autofix_loop(
702
735
  None,
703
736
  )
704
737
 
705
- if should_skip_issue(deterministic_issue, deterministic, non_deterministic, file_path):
738
+ if should_skip_issue(
739
+ deterministic_issue, deterministic, non_deterministic, file_path
740
+ ):
706
741
  return False
707
742
 
708
743
  corrected_text = None
709
744
 
710
745
  corrected_text = apply_deterministic_fix(
711
- deterministic, deterministic_issue, file_path, artefact_text,
712
- artefact_class, classified_artefact_info,
713
- deterministic_markers_to_functions, corrected_text
746
+ deterministic,
747
+ deterministic_issue,
748
+ file_path,
749
+ artefact_text,
750
+ artefact_class,
751
+ classified_artefact_info,
752
+ deterministic_markers_to_functions,
753
+ corrected_text,
714
754
  )
715
755
  corrected_text = apply_non_deterministic_fix(
716
- non_deterministic, deterministic_issue, corrected_text,
717
- artefact_type, current_reason, file_path, artefact_text, artefact_class
756
+ non_deterministic,
757
+ deterministic_issue,
758
+ corrected_text,
759
+ artefact_type,
760
+ current_reason,
761
+ file_path,
762
+ artefact_text,
763
+ artefact_class,
718
764
  )
719
765
 
720
766
  if corrected_text is None or corrected_text.strip() == artefact_text.strip():
@@ -725,12 +771,17 @@ def attempt_autofix_loop(
725
771
 
726
772
  write_corrected_artefact(file_path, corrected_text)
727
773
 
728
- print(" File modified. Re-classifying artefact information for next check...")
729
- classified_artefact_info = populate_classified_artefact_info(classified_artefact_info, force=True)
774
+ print(
775
+ " File modified. Re-classifying artefact information for next check..."
776
+ )
777
+ classified_artefact_info = populate_classified_artefact_info(
778
+ classified_artefact_info, force=True
779
+ )
730
780
 
731
781
  print(f"❌ Failed to fix {file_path} after {max_attempts} attempts.")
732
782
  return False
733
783
 
784
+
734
785
  def apply_autofix(
735
786
  file_path: str,
736
787
  classifier: str,
@@ -757,7 +808,9 @@ def apply_autofix(
757
808
  if artefact_type is None or artefact_class is None:
758
809
  return False
759
810
 
760
- classified_artefact_info = populate_classified_artefact_info(classified_artefact_info)
811
+ classified_artefact_info = populate_classified_artefact_info(
812
+ classified_artefact_info
813
+ )
761
814
  max_attempts = determine_attempt_count(single_pass, file_path)
762
815
 
763
816
  return attempt_autofix_loop(
@@ -769,4 +822,4 @@ def apply_autofix(
769
822
  deterministic=deterministic,
770
823
  non_deterministic=non_deterministic,
771
824
  classified_artefact_info=classified_artefact_info,
772
- )
825
+ )