ara-cli 0.1.10.0__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.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +270 -103
- ara_cli/ara_command_action.py +106 -63
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +19 -0
- ara_cli/ara_subcommands/list.py +139 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +154 -63
- ara_cli/artefact_converter.py +256 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +20 -10
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +31 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +283 -80
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +180 -0
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/file_loaders/text_file_loader.py +2 -2
- ara_cli/global_file_lister.py +5 -15
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +199 -76
- ara_cli/prompt_handler.py +160 -59
- ara_cli/tag_extractor.py +38 -18
- ara_cli/template_loader.py +3 -2
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +357 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_global_file_lister.py +1 -1
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_prompt_handler.py +12 -4
- tests/test_tag_extractor.py +19 -13
- tests/test_web_search.py +467 -0
- ara_cli/ara_command_parser.py +0 -605
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
ara_cli/artefact_autofix.py
CHANGED
|
@@ -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
|
|
144
|
-
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
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(
|
|
203
|
-
|
|
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,7 +247,11 @@ def ask_for_rule_choice(matches: List[str]) -> Optional[str]:
|
|
|
248
247
|
|
|
249
248
|
|
|
250
249
|
def _update_rule(
|
|
251
|
-
artefact: Artefact,
|
|
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
|
|
@@ -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(
|
|
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
|
|
@@ -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(
|
|
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 = [
|
|
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
|
|
464
|
+
found = re.findall(r"<([^>]+)>", step_line)
|
|
460
465
|
placeholders.update(found)
|
|
461
|
-
|
|
466
|
+
|
|
462
467
|
return placeholders
|
|
463
468
|
|
|
464
469
|
|
|
@@ -469,34 +474,36 @@ 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(
|
|
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(
|
|
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
|
|
|
487
494
|
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
|
-
table_indentation = examples_indentation + "
|
|
491
|
-
|
|
497
|
+
table_indentation = examples_indentation + " "
|
|
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(
|
|
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(
|
|
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}'"
|
|
@@ -576,7 +589,47 @@ def fix_rule(
|
|
|
576
589
|
return artefact.serialize()
|
|
577
590
|
|
|
578
591
|
|
|
579
|
-
def
|
|
592
|
+
def fix_misplaced_content(file_path: str, artefact_text: str, **kwargs) -> str:
|
|
593
|
+
"""
|
|
594
|
+
Deterministically fixes content like 'Rule:' or 'Estimate:' misplaced in the description.
|
|
595
|
+
"""
|
|
596
|
+
lines = artefact_text.splitlines()
|
|
597
|
+
|
|
598
|
+
desc_start_idx = -1
|
|
599
|
+
for i, line in enumerate(lines):
|
|
600
|
+
if line.strip().startswith("Description:"):
|
|
601
|
+
desc_start_idx = i
|
|
602
|
+
break
|
|
603
|
+
|
|
604
|
+
if desc_start_idx == -1:
|
|
605
|
+
return artefact_text # No description, nothing to fix.
|
|
606
|
+
|
|
607
|
+
pre_desc_lines = lines[:desc_start_idx]
|
|
608
|
+
desc_line = lines[desc_start_idx]
|
|
609
|
+
post_desc_lines = lines[desc_start_idx + 1 :]
|
|
610
|
+
|
|
611
|
+
misplaced_content = []
|
|
612
|
+
new_post_desc_lines = []
|
|
613
|
+
|
|
614
|
+
for line in post_desc_lines:
|
|
615
|
+
if line.strip().startswith("Rule:") or line.strip().startswith("Estimate:"):
|
|
616
|
+
misplaced_content.append(line)
|
|
617
|
+
else:
|
|
618
|
+
new_post_desc_lines.append(line)
|
|
619
|
+
|
|
620
|
+
if not misplaced_content:
|
|
621
|
+
return artefact_text
|
|
622
|
+
|
|
623
|
+
# Rebuild the file content
|
|
624
|
+
final_lines = (
|
|
625
|
+
pre_desc_lines + misplaced_content + [""] + [desc_line] + new_post_desc_lines
|
|
626
|
+
)
|
|
627
|
+
return "\n".join(final_lines)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def should_skip_issue(
|
|
631
|
+
deterministic_issue, deterministic, non_deterministic, file_path
|
|
632
|
+
) -> bool:
|
|
580
633
|
if not non_deterministic and not deterministic_issue:
|
|
581
634
|
print(f"Skipping non-deterministic fix for {file_path} as per request.")
|
|
582
635
|
return True
|
|
@@ -585,15 +638,23 @@ def should_skip_issue(deterministic_issue, deterministic, non_deterministic, fil
|
|
|
585
638
|
return True
|
|
586
639
|
return False
|
|
587
640
|
|
|
641
|
+
|
|
588
642
|
def determine_attempt_count(single_pass, file_path) -> int:
|
|
589
643
|
if single_pass:
|
|
590
644
|
print(f"Single-pass mode enabled for {file_path}. Running for 1 attempt.")
|
|
591
645
|
return 1
|
|
592
646
|
return 3
|
|
593
647
|
|
|
648
|
+
|
|
594
649
|
def apply_deterministic_fix(
|
|
595
|
-
deterministic,
|
|
596
|
-
|
|
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,
|
|
597
658
|
) -> str:
|
|
598
659
|
if deterministic and deterministic_issue:
|
|
599
660
|
print(f"Applying deterministic fix for '{deterministic_issue}'...")
|
|
@@ -606,9 +667,16 @@ def apply_deterministic_fix(
|
|
|
606
667
|
)
|
|
607
668
|
return corrected_text
|
|
608
669
|
|
|
670
|
+
|
|
609
671
|
def apply_non_deterministic_fix(
|
|
610
|
-
non_deterministic,
|
|
611
|
-
|
|
672
|
+
non_deterministic,
|
|
673
|
+
deterministic_issue,
|
|
674
|
+
corrected_text,
|
|
675
|
+
artefact_type,
|
|
676
|
+
current_reason,
|
|
677
|
+
file_path,
|
|
678
|
+
artefact_text,
|
|
679
|
+
artefact_class,
|
|
612
680
|
) -> Optional[str]:
|
|
613
681
|
"""
|
|
614
682
|
Applies LLM fix. Return None in case of an exception
|
|
@@ -622,10 +690,11 @@ def apply_non_deterministic_fix(
|
|
|
622
690
|
corrected_artefact = run_agent(prompt, artefact_class)
|
|
623
691
|
corrected_text = corrected_artefact.serialize()
|
|
624
692
|
except Exception as e:
|
|
625
|
-
print(f"
|
|
693
|
+
print(f" ❌ LLM agent failed to fix artefact at {file_path}: {e}")
|
|
626
694
|
return None
|
|
627
695
|
return corrected_text
|
|
628
696
|
|
|
697
|
+
|
|
629
698
|
def attempt_autofix_loop(
|
|
630
699
|
file_path: str,
|
|
631
700
|
artefact_type,
|
|
@@ -651,7 +720,7 @@ def attempt_autofix_loop(
|
|
|
651
720
|
print(
|
|
652
721
|
f"Attempting to fix {file_path} (Attempt {attempt + 1}/{max_attempts})..."
|
|
653
722
|
)
|
|
654
|
-
print(f"
|
|
723
|
+
print(f" Reason: {current_reason}")
|
|
655
724
|
|
|
656
725
|
artefact_text = read_artefact(file_path)
|
|
657
726
|
if artefact_text is None:
|
|
@@ -666,35 +735,53 @@ def attempt_autofix_loop(
|
|
|
666
735
|
None,
|
|
667
736
|
)
|
|
668
737
|
|
|
669
|
-
if should_skip_issue(
|
|
738
|
+
if should_skip_issue(
|
|
739
|
+
deterministic_issue, deterministic, non_deterministic, file_path
|
|
740
|
+
):
|
|
670
741
|
return False
|
|
671
742
|
|
|
672
743
|
corrected_text = None
|
|
673
744
|
|
|
674
745
|
corrected_text = apply_deterministic_fix(
|
|
675
|
-
deterministic,
|
|
676
|
-
|
|
677
|
-
|
|
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,
|
|
678
754
|
)
|
|
679
755
|
corrected_text = apply_non_deterministic_fix(
|
|
680
|
-
non_deterministic,
|
|
681
|
-
|
|
756
|
+
non_deterministic,
|
|
757
|
+
deterministic_issue,
|
|
758
|
+
corrected_text,
|
|
759
|
+
artefact_type,
|
|
760
|
+
current_reason,
|
|
761
|
+
file_path,
|
|
762
|
+
artefact_text,
|
|
763
|
+
artefact_class,
|
|
682
764
|
)
|
|
683
765
|
|
|
684
766
|
if corrected_text is None or corrected_text.strip() == artefact_text.strip():
|
|
685
767
|
print(
|
|
686
|
-
"
|
|
768
|
+
" Fixing attempt did not alter the file. Stopping to prevent infinite loop."
|
|
687
769
|
)
|
|
688
770
|
return False
|
|
689
771
|
|
|
690
772
|
write_corrected_artefact(file_path, corrected_text)
|
|
691
773
|
|
|
692
|
-
print(
|
|
693
|
-
|
|
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
|
+
)
|
|
694
780
|
|
|
695
781
|
print(f"❌ Failed to fix {file_path} after {max_attempts} attempts.")
|
|
696
782
|
return False
|
|
697
783
|
|
|
784
|
+
|
|
698
785
|
def apply_autofix(
|
|
699
786
|
file_path: str,
|
|
700
787
|
classifier: str,
|
|
@@ -713,13 +800,17 @@ def apply_autofix(
|
|
|
713
800
|
"Invalid Contribution Reference": fix_contribution,
|
|
714
801
|
"Rule Mismatch": fix_rule,
|
|
715
802
|
"Scenario Contains Placeholders": fix_scenario_placeholder_mismatch,
|
|
803
|
+
"Found 'Rule:' inside description": fix_misplaced_content,
|
|
804
|
+
"Found 'Estimate:' inside description": fix_misplaced_content,
|
|
716
805
|
}
|
|
717
806
|
|
|
718
807
|
artefact_type, artefact_class = determine_artefact_type_and_class(classifier)
|
|
719
808
|
if artefact_type is None or artefact_class is None:
|
|
720
809
|
return False
|
|
721
810
|
|
|
722
|
-
classified_artefact_info = populate_classified_artefact_info(
|
|
811
|
+
classified_artefact_info = populate_classified_artefact_info(
|
|
812
|
+
classified_artefact_info
|
|
813
|
+
)
|
|
723
814
|
max_attempts = determine_attempt_count(single_pass, file_path)
|
|
724
815
|
|
|
725
816
|
return attempt_autofix_loop(
|