ara-cli 0.1.9.94__tar.gz → 0.1.9.96__tar.gz

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 (178) hide show
  1. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/PKG-INFO +2 -1
  2. ara_cli-0.1.9.96/ara_cli/__init__.py +20 -0
  3. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/__main__.py +57 -11
  4. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/ara_command_action.py +31 -19
  5. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/ara_config.py +17 -2
  6. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_autofix.py +171 -23
  7. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_creator.py +5 -8
  8. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_deleter.py +2 -4
  9. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_fuzzy_search.py +13 -6
  10. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/artefact_templates.py +3 -3
  11. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/feature_artefact_model.py +25 -0
  12. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_reader.py +4 -5
  13. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/chat.py +79 -37
  14. ara_cli-0.1.9.96/ara_cli/commands/extract_command.py +15 -0
  15. ara_cli-0.1.9.96/ara_cli/error_handler.py +134 -0
  16. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_classifier.py +3 -2
  17. ara_cli-0.1.9.96/ara_cli/file_loaders/document_readers.py +233 -0
  18. ara_cli-0.1.9.96/ara_cli/file_loaders/file_loaders.py +123 -0
  19. ara_cli-0.1.9.96/ara_cli/file_loaders/image_processor.py +89 -0
  20. ara_cli-0.1.9.96/ara_cli/file_loaders/markdown_reader.py +75 -0
  21. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/text_file_loader.py +9 -11
  22. ara_cli-0.1.9.96/ara_cli/global_file_lister.py +61 -0
  23. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/prompt_extractor.py +1 -1
  24. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/prompt_handler.py +24 -4
  25. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/template_manager.py +14 -4
  26. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/update_config_prompt.py +7 -1
  27. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/version.py +1 -1
  28. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/PKG-INFO +2 -1
  29. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/SOURCES.txt +7 -0
  30. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/requires.txt +1 -0
  31. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/docker/base/requirements.txt +1 -0
  32. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_ara_command_action.py +66 -52
  33. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_ara_config.py +28 -0
  34. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_autofix.py +361 -5
  35. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_chat.py +105 -36
  36. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_file_classifier.py +23 -0
  37. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_file_creator.py +3 -5
  38. ara_cli-0.1.9.96/tests/test_global_file_lister.py +131 -0
  39. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_prompt_handler.py +26 -1
  40. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_template_manager.py +5 -4
  41. ara_cli-0.1.9.94/ara_cli/__init__.py +0 -3
  42. ara_cli-0.1.9.94/ara_cli/commands/extract_command.py +0 -22
  43. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/MANIFEST.in +0 -0
  44. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/README.md +0 -0
  45. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/ara_command_parser.py +0 -0
  46. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_link_updater.py +0 -0
  47. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_lister.py +0 -0
  48. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/__init__.py +0 -0
  49. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/artefact_data_retrieval.py +0 -0
  50. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/artefact_load.py +0 -0
  51. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/artefact_mapping.py +0 -0
  52. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/artefact_model.py +0 -0
  53. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/businessgoal_artefact_model.py +0 -0
  54. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/capability_artefact_model.py +0 -0
  55. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/epic_artefact_model.py +0 -0
  56. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/example_artefact_model.py +0 -0
  57. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/issue_artefact_model.py +0 -0
  58. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/keyfeature_artefact_model.py +0 -0
  59. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/serialize_helper.py +0 -0
  60. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/task_artefact_model.py +0 -0
  61. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/userstory_artefact_model.py +0 -0
  62. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_models/vision_artefact_model.py +0 -0
  63. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_renamer.py +0 -0
  64. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/artefact_scan.py +0 -0
  65. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/classifier.py +0 -0
  66. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/codefusionretriever.py +0 -0
  67. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/codehierachieretriever.py +0 -0
  68. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commandline_completer.py +0 -0
  69. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commands/__init__.py +0 -0
  70. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commands/command.py +0 -0
  71. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commands/load_command.py +0 -0
  72. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commands/load_image_command.py +0 -0
  73. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/commands/read_command.py +0 -0
  74. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/directory_navigator.py +0 -0
  75. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_lister.py +0 -0
  76. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/__init__.py +0 -0
  77. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/binary_file_loader.py +0 -0
  78. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/document_file_loader.py +0 -0
  79. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/document_reader.py +0 -0
  80. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/file_loaders/file_loader.py +0 -0
  81. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/filename_validator.py +0 -0
  82. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/list_filter.py +0 -0
  83. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/output_suppressor.py +0 -0
  84. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/prompt_chat.py +0 -0
  85. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/prompt_rag.py +0 -0
  86. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/run_file_lister.py +0 -0
  87. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/tag_extractor.py +0 -0
  88. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/agile.artefacts +0 -0
  89. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -0
  90. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/blueprints/empty.blueprint.md +0 -0
  91. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/blueprints/task_todo_list_C4_architecture_analysis.blueprint.md +0 -0
  92. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -0
  93. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/architecture_C4_analysis.commands.md +0 -0
  94. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/architecture_radon_cc_score.commands.md +0 -0
  95. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/architecture_radon_halstead_v.commands.md +0 -0
  96. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/architecture_radon_maintainability_score.commands.md +0 -0
  97. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -0
  98. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -0
  99. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -0
  100. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -0
  101. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -0
  102. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -0
  103. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/empty.commands.md +0 -0
  104. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -0
  105. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -0
  106. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -0
  107. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -0
  108. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -0
  109. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -0
  110. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -0
  111. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -0
  112. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/classify_task.intention.md +0 -0
  113. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/empty.intention.md +0 -0
  114. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/error_fixing.intention.md +0 -0
  115. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_fix_steps_for_scenario.intention.md +0 -0
  116. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_formulation.intention.md +0 -0
  117. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_reverse_formulation_from_code.intention.md +0 -0
  118. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_scenario_implementation.intention.md +0 -0
  119. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_scenario_implementation_update.intention.md +0 -0
  120. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_scenario_outline_extension.intention.md +0 -0
  121. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/feature_update_formulation.intention.md +0 -0
  122. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/fibonacci_example_implementation.intention.md +0 -0
  123. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/js_implementation_from_task_description.intention.md +0 -0
  124. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/js_steps_implementation.intention.md +0 -0
  125. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/python_cli_implementation_with_test.intention.md +0 -0
  126. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/python_code_understanding.intention.md +0 -0
  127. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/task_implementation.intention.md +0 -0
  128. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/task_prompt_control_by_status.intention.md +0 -0
  129. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/task_stepwise_implementation_by_number.intention.md +0 -0
  130. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/intentions/task_stepwise_implementation_by_status.intention.md +0 -0
  131. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/architecture_analyst.rules.md +0 -0
  132. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/code_analyst.rules.md +0 -0
  133. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/empty.rules.md +0 -0
  134. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/error_analyst.rules.md +0 -0
  135. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/gherkin_expert.rules.md +0 -0
  136. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/js_expert_developer.rules.md +0 -0
  137. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/product_owner.rules.md +0 -0
  138. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/python_behave.rules.md +0 -0
  139. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/prompt-modules/rules/python_developer.rules.md +0 -0
  140. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.concept.exploration.md +0 -0
  141. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.concept.md +0 -0
  142. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.customer.exploration.md +0 -0
  143. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.customer.md +0 -0
  144. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.persona.exploration.md +0 -0
  145. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.persona.md +0 -0
  146. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.step.exploration.md +0 -0
  147. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.step.md +0 -0
  148. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.technology.exploration.md +0 -0
  149. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/specification_breakdown_files/template.technology.md +0 -0
  150. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.businessgoal.prompt_log.md +0 -0
  151. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.capability.prompt_log.md +0 -0
  152. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.epic.prompt_log.md +0 -0
  153. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.example.prompt_log.md +0 -0
  154. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.feature.prompt_log.md +0 -0
  155. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.issue.prompt_log.md +0 -0
  156. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.keyfeature.prompt_log.md +0 -0
  157. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.steps.prompt_log.md +0 -0
  158. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.task.prompt_log.md +0 -0
  159. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.userstory.prompt_log.md +0 -0
  160. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli/templates/template.vision.prompt_log.md +0 -0
  161. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/dependency_links.txt +0 -0
  162. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/entry_points.txt +0 -0
  163. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/ara_cli.egg-info/top_level.txt +0 -0
  164. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/setup.cfg +0 -0
  165. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/setup.py +0 -0
  166. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/__init__.py +0 -0
  167. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_fuzzy_search.py +0 -0
  168. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_link_updater.py +0 -0
  169. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_lister.py +0 -0
  170. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_reader.py +0 -0
  171. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_renamer.py +0 -0
  172. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_artefact_scan.py +0 -0
  173. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_classifier.py +0 -0
  174. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_directory_navigator.py +0 -0
  175. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_file_lister.py +0 -0
  176. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_list_filter.py +0 -0
  177. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_tag_extractor.py +0 -0
  178. {ara_cli-0.1.9.94 → ara_cli-0.1.9.96}/tests/test_update_config_prompt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ara_cli
3
- Version: 0.1.9.94
3
+ Version: 0.1.9.96
4
4
  Summary: Powerful, open source command-line tool for managing, structuring and automating software development artifacts in line with Business-Driven Development (BDD) and AI-assisted processes
5
5
  Description-Content-Type: text/markdown
6
6
  Requires-Dist: litellm
@@ -13,6 +13,7 @@ Requires-Dist: json-repair
13
13
  Requires-Dist: argparse
14
14
  Requires-Dist: argcomplete
15
15
  Requires-Dist: cmd2>=2.5
16
+ Requires-Dist: charset-normalizer
16
17
  Requires-Dist: pydantic
17
18
  Requires-Dist: pydantic_ai
18
19
  Requires-Dist: python-docx
@@ -0,0 +1,20 @@
1
+ import warnings
2
+ from .error_handler import ErrorHandler
3
+
4
+
5
+ whitelisted_commands = ["RERUN", "SEND", "EXTRACT", "LOAD_IMAGE", "CHOOSE_MODEL", "CHOOSE_EXTRACTION_MODEL", "CURRENT_MODEL", "CURRENT_EXTRACTION_MODEL", "LIST_MODELS"]
6
+
7
+
8
+ error_handler = ErrorHandler()
9
+
10
+
11
+ # ANSI escape codes for coloring
12
+ YELLOW = '\033[93m'
13
+ RESET = '\033[0m'
14
+
15
+
16
+ def format_warning(message, category, *args, **kwargs):
17
+ return f'{YELLOW}{category.__name__}: {message}{RESET}\n'
18
+
19
+
20
+ warnings.formatwarning = format_warning
@@ -1,5 +1,6 @@
1
1
  # PYTHON_ARGCOMPLETE_OK
2
2
  from ara_cli.ara_command_parser import action_parser
3
+ from ara_cli.error_handler import AraError
3
4
  from ara_cli.version import __version__
4
5
  from ara_cli.ara_command_action import (
5
6
  create_action,
@@ -22,8 +23,10 @@ from ara_cli.ara_command_action import (
22
23
  autofix_action,
23
24
  extract_action
24
25
  )
26
+ from . import error_handler
25
27
  import argcomplete
26
28
  import sys
29
+ from os import getenv
27
30
 
28
31
 
29
32
  def define_action_mapping():
@@ -51,31 +54,74 @@ def define_action_mapping():
51
54
 
52
55
 
53
56
  def handle_invalid_action(args):
54
- sys.exit("Invalid action provided. Type ara -h for help")
57
+ raise AraError("Invalid action provided. Type ara -h for help", error_code=1)
55
58
 
56
59
 
57
- def cli():
58
- parser = action_parser()
60
+ def is_debug_mode_enabled():
61
+ """Check if debug mode is enabled via environment variable."""
62
+ return getenv('ARA_DEBUG', '').lower() in ('1', 'true', 'yes')
59
63
 
64
+
65
+ def setup_parser():
66
+ """Create and configure the argument parser."""
67
+ parser = action_parser()
68
+
60
69
  # Show examples when help is called
61
70
  if any(arg in sys.argv for arg in ["-h", "--help"]):
62
71
  parser.add_examples = True
63
-
72
+
64
73
  parser.add_argument(
65
74
  "-v", "--version", action="version", version=f"%(prog)s {__version__}"
66
75
  )
76
+
77
+ parser.add_argument(
78
+ "--debug", action="store_true", help="Enable debug mode for detailed error output"
79
+ )
80
+
81
+ return parser
67
82
 
68
- action_mapping = define_action_mapping()
69
83
 
70
- argcomplete.autocomplete(parser)
84
+ def configure_debug_mode(args, env_debug_mode):
85
+ """Configure debug mode based on arguments and environment."""
86
+ if (hasattr(args, 'debug') and args.debug) or env_debug_mode:
87
+ error_handler.debug_mode = True
71
88
 
72
- args = parser.parse_args()
73
- if not hasattr(args, "action") or not args.action:
74
- parser.print_help()
75
- return
89
+
90
+ def should_show_help(args):
91
+ """Check if help should be displayed."""
92
+ return not hasattr(args, "action") or not args.action
93
+
94
+
95
+ def execute_action(args, action_mapping):
96
+ """Execute the specified action."""
76
97
  action = action_mapping.get(args.action, handle_invalid_action)
77
98
  action(args)
78
99
 
79
100
 
101
+ def cli():
102
+ debug_mode = is_debug_mode_enabled()
103
+
104
+ try:
105
+ parser = setup_parser()
106
+ action_mapping = define_action_mapping()
107
+
108
+ argcomplete.autocomplete(parser)
109
+ args = parser.parse_args()
110
+
111
+ configure_debug_mode(args, debug_mode)
112
+
113
+ if should_show_help(args):
114
+ parser.print_help()
115
+ return
116
+
117
+ execute_action(args, action_mapping)
118
+
119
+ except KeyboardInterrupt:
120
+ print("\n[INFO] Operation cancelled by user", file=sys.stderr)
121
+ sys.exit(130) # Standard exit code for Ctrl+C
122
+ except Exception as e:
123
+ error_handler.handle_error(e, context="cli")
124
+
125
+
80
126
  if __name__ == "__main__":
81
- cli()
127
+ cli()
@@ -2,17 +2,19 @@ from os.path import join
2
2
  import os
3
3
  import sys
4
4
  import json
5
+ from ara_cli.error_handler import AraError
6
+ from ara_cli.error_handler import handle_errors, AraValidationError
5
7
  from ara_cli.output_suppressor import suppress_stdout
6
8
  from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
7
- from . import whitelisted_commands
9
+ from . import whitelisted_commands, error_handler
8
10
 
9
11
 
10
12
  def check_validity(condition, error_message):
11
13
  if not condition:
12
- print(error_message)
13
- sys.exit(1)
14
+ raise AraValidationError(error_message)
14
15
 
15
16
 
17
+ @handle_errors(context="create action", error_handler=error_handler)
16
18
  def create_action(args):
17
19
  from ara_cli.artefact_creator import ArtefactCreator
18
20
  from ara_cli.classifier import Classifier
@@ -62,6 +64,7 @@ def create_action(args):
62
64
  artefact_creator.run(args.parameter, args.classifier, parent_classifier, parent_name, rule)
63
65
 
64
66
 
67
+ @handle_errors(context="delete action", error_handler=error_handler)
65
68
  def delete_action(args):
66
69
  from ara_cli.artefact_deleter import ArtefactDeleter
67
70
 
@@ -69,6 +72,7 @@ def delete_action(args):
69
72
  artefact_deleter.delete(args.parameter, args.classifier, args.force)
70
73
 
71
74
 
75
+ @handle_errors(context="rename action", error_handler=error_handler)
72
76
  def rename_action(args):
73
77
  from ara_cli.artefact_renamer import ArtefactRenamer
74
78
  from ara_cli.classifier import Classifier
@@ -82,6 +86,7 @@ def rename_action(args):
82
86
  artefact_renamer.rename(args.parameter, args.aspect, args.classifier)
83
87
 
84
88
 
89
+ @handle_errors(context="rename action", error_handler=error_handler)
85
90
  def list_action(args):
86
91
  from ara_cli.artefact_lister import ArtefactLister
87
92
  from ara_cli.list_filter import ListFilter
@@ -131,6 +136,7 @@ def list_action(args):
131
136
  artefact_lister.list_files(list_filter=list_filter)
132
137
 
133
138
 
139
+ @handle_errors(context="list-tags action", error_handler=error_handler)
134
140
  def list_tags_action(args):
135
141
  from ara_cli.tag_extractor import TagExtractor
136
142
  from ara_cli.list_filter import ListFilter
@@ -155,6 +161,7 @@ def list_tags_action(args):
155
161
  print(output)
156
162
 
157
163
 
164
+ @handle_errors(context="prompt action", error_handler=error_handler)
158
165
  def prompt_action(args):
159
166
  from ara_cli.classifier import Classifier
160
167
  from ara_cli.filename_validator import is_valid_filename
@@ -227,6 +234,7 @@ def prompt_action(args):
227
234
  raise ValueError(f"Unknown command '{init}' provided.")
228
235
 
229
236
 
237
+ @handle_errors(context="chat action", error_handler=error_handler)
230
238
  def chat_action(args):
231
239
  from ara_cli.chat import Chat
232
240
 
@@ -253,6 +261,7 @@ def chat_action(args):
253
261
  chat.start()
254
262
 
255
263
 
264
+ @handle_errors(context="template action", error_handler=error_handler)
256
265
  def template_action(args):
257
266
  from ara_cli.classifier import Classifier
258
267
  from ara_cli.template_manager import TemplatePathManager
@@ -265,6 +274,7 @@ def template_action(args):
265
274
  print(content)
266
275
 
267
276
 
277
+ @handle_errors(context="fetch-templates action", error_handler=error_handler)
268
278
  def fetch_templates_action(args):
269
279
  import shutil
270
280
  from ara_cli.ara_config import ConfigManager
@@ -294,6 +304,7 @@ def fetch_templates_action(args):
294
304
  os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
295
305
 
296
306
 
307
+ @handle_errors(context="read action", error_handler=error_handler)
297
308
  def read_action(args):
298
309
  from ara_cli.commands.read_command import ReadCommand
299
310
  from ara_cli.list_filter import ListFilter
@@ -321,6 +332,7 @@ def read_action(args):
321
332
  command.execute()
322
333
 
323
334
 
335
+ @handle_errors(context="reconnect action", error_handler=error_handler)
324
336
  def reconnect_action(args):
325
337
  from ara_cli.artefact_models.artefact_load import artefact_from_content
326
338
  from ara_cli.artefact_models.artefact_model import Contribution
@@ -348,8 +360,7 @@ def reconnect_action(args):
348
360
  )
349
361
 
350
362
  if not artefact:
351
- print(read_error_message)
352
- return
363
+ raise AraError(read_error_message)
353
364
 
354
365
  parent = ArtefactReader.read_artefact(
355
366
  artefact_name=parent_name,
@@ -358,8 +369,7 @@ def reconnect_action(args):
358
369
  )
359
370
 
360
371
  if not parent:
361
- print(read_error_message)
362
- return
372
+ raise AraError(read_error_message)
363
373
 
364
374
  contribution = Contribution(
365
375
  artefact_name=parent.title,
@@ -367,13 +377,9 @@ def reconnect_action(args):
367
377
  )
368
378
 
369
379
  if rule:
370
- try:
371
- closest_rule = find_closest_rule(parent, rule)
372
- contribution.rule = closest_rule
373
- feedback_message += f" using rule '{closest_rule}'"
374
- except TypeError as e:
375
- print(f"{type(e).__name__}:", e)
376
- exit(1)
380
+ closest_rule = find_closest_rule(parent, rule)
381
+ contribution.rule = closest_rule
382
+ feedback_message += f" using rule '{closest_rule}'"
377
383
 
378
384
  artefact.contribution = contribution
379
385
  with open(artefact.file_path, 'w', encoding='utf-8') as file:
@@ -383,6 +389,7 @@ def reconnect_action(args):
383
389
  print(feedback_message + ".")
384
390
 
385
391
 
392
+ @handle_errors(context="read-status action", error_handler=error_handler)
386
393
  def read_status_action(args):
387
394
  from ara_cli.file_classifier import FileClassifier
388
395
  from ara_cli.artefact_models.artefact_load import artefact_from_content
@@ -396,7 +403,7 @@ def read_status_action(args):
396
403
 
397
404
  all_artefact_names = [artefact_info["title"] for artefact_info in artefact_info_dicts]
398
405
  if artefact_name not in all_artefact_names:
399
- suggest_close_name_matches(artefact_name, all_artefact_names)
406
+ suggest_close_name_matches(artefact_name, all_artefact_names, report_as_error=True)
400
407
  return
401
408
 
402
409
  artefact_info = next(filter(
@@ -415,6 +422,7 @@ def read_status_action(args):
415
422
  print(status)
416
423
 
417
424
 
425
+ @handle_errors(context="read-user action", error_handler=error_handler)
418
426
  def read_user_action(args):
419
427
  from ara_cli.artefact_models.artefact_load import artefact_from_content
420
428
  from ara_cli.file_classifier import FileClassifier
@@ -428,7 +436,7 @@ def read_user_action(args):
428
436
 
429
437
  all_artefact_names = [artefact_info["title"] for artefact_info in artefact_info_dicts]
430
438
  if artefact_name not in all_artefact_names:
431
- suggest_close_name_matches(artefact_name, all_artefact_names)
439
+ suggest_close_name_matches(artefact_name, all_artefact_names, report_as_error=True)
432
440
  return
433
441
 
434
442
  artefact_info = next(filter(
@@ -448,6 +456,7 @@ def read_user_action(args):
448
456
  print(f" - {tag}")
449
457
 
450
458
 
459
+ @handle_errors(context="set-status action", error_handler=error_handler)
451
460
  def set_status_action(args):
452
461
  from ara_cli.artefact_models.artefact_model import ALLOWED_STATUS_VALUES
453
462
  from ara_cli.artefact_models.artefact_load import artefact_from_content
@@ -490,6 +499,7 @@ def set_status_action(args):
490
499
  print(f"Status of task '{artefact_name}' has been updated to '{new_status}'.")
491
500
 
492
501
 
502
+ @handle_errors(context="set-user action", error_handler=error_handler)
493
503
  def set_user_action(args):
494
504
  from ara_cli.file_classifier import FileClassifier
495
505
  from ara_cli.artefact_models.artefact_load import artefact_from_content
@@ -528,6 +538,7 @@ def set_user_action(args):
528
538
  print(f"User of task '{artefact_name}' has been updated to '{new_user}'.")
529
539
 
530
540
 
541
+ @handle_errors(context="classifier-directory action", error_handler=error_handler)
531
542
  def classifier_directory_action(args):
532
543
  from ara_cli.classifier import Classifier
533
544
 
@@ -536,6 +547,7 @@ def classifier_directory_action(args):
536
547
  print(subdirectory)
537
548
 
538
549
 
550
+ @handle_errors(context="scan action", error_handler=error_handler)
539
551
  def scan_action(args):
540
552
  from ara_cli.file_classifier import FileClassifier
541
553
  from ara_cli.artefact_scan import find_invalid_files, show_results
@@ -550,6 +562,7 @@ def scan_action(args):
550
562
  show_results(invalid_artefacts)
551
563
 
552
564
 
565
+ @handle_errors(context="autofix_action", error_handler=error_handler)
553
566
  def autofix_action(args):
554
567
  from ara_cli.artefact_autofix import parse_report, apply_autofix, read_report_file
555
568
  from ara_cli.file_classifier import FileClassifier
@@ -589,18 +602,17 @@ def autofix_action(args):
589
602
  print("\nAutofix process completed. Please review the changes.")
590
603
 
591
604
 
605
+ @handle_errors(context="extract action", error_handler=error_handler)
592
606
  def extract_action(args):
593
607
  from ara_cli.commands.extract_command import ExtractCommand
594
608
 
595
609
  filename = args.filename
596
610
  force = args.force
597
611
  write = getattr(args, 'write', False)
598
- print(filename)
599
612
  command = ExtractCommand(
600
613
  file_name=filename,
601
614
  force=force,
602
615
  write=write,
603
- output=lambda msg: print(msg, file=sys.stdout),
604
- error_output=lambda msg: print(msg, file=sys.stderr)
616
+ output=lambda msg: print(msg, file=sys.stdout)
605
617
  )
606
618
  command.execute()
@@ -6,6 +6,7 @@ from os.path import exists, dirname
6
6
  from os import makedirs
7
7
  from functools import lru_cache
8
8
  import sys
9
+ import warnings
9
10
 
10
11
  DEFAULT_CONFIG_LOCATION = "./ara/.araconfig/ara_config.json"
11
12
 
@@ -21,6 +22,7 @@ class ARAconfig(BaseModel):
21
22
  {"source_dir": "./src"},
22
23
  {"source_dir": "./tests"}
23
24
  ])
25
+ global_dirs: Optional[List[Dict[str, str]]] = Field(default=[])
24
26
  glossary_dir: str = "./glossary"
25
27
  doc_dir: str = "./docs"
26
28
  local_prompt_templates_dir: str = "./ara/.araconfig"
@@ -162,6 +164,7 @@ def handle_unrecognized_keys(data: dict) -> dict:
162
164
  cleaned_data[key] = value
163
165
  return cleaned_data
164
166
 
167
+
165
168
  # Function to read the JSON file and return an ARAconfig model
166
169
  @lru_cache(maxsize=1)
167
170
  def read_data(filepath: str) -> ARAconfig:
@@ -170,6 +173,16 @@ def read_data(filepath: str) -> ARAconfig:
170
173
  If the file doesn't exist, it creates a default one.
171
174
  If the file is invalid, it corrects only the broken parts.
172
175
  """
176
+
177
+ def warn_on_duplicate_llm_dict_key(ordered_pairs):
178
+ """Reject duplicate keys."""
179
+ d = {}
180
+ for k, v in ordered_pairs:
181
+ if k in d:
182
+ warnings.warn(f"Duplicate LLM configuration identifier '{k}'. The previous entry will be removed.", UserWarning)
183
+ d[k] = v
184
+ return d
185
+
173
186
  ensure_directory_exists(dirname(filepath))
174
187
 
175
188
  if not exists(filepath):
@@ -181,7 +194,8 @@ def read_data(filepath: str) -> ARAconfig:
181
194
 
182
195
  try:
183
196
  with open(filepath, "r", encoding="utf-8") as file:
184
- data = json.load(file)
197
+ content = file.read()
198
+ data = json.loads(content, object_pairs_hook=warn_on_duplicate_llm_dict_key)
185
199
  except json.JSONDecodeError as e:
186
200
  print(f"Error: Invalid JSON in configuration file: {e}")
187
201
  print("Creating a new configuration with defaults...")
@@ -206,7 +220,8 @@ def read_data(filepath: str) -> ARAconfig:
206
220
 
207
221
  for field_name in error_fields:
208
222
  print(f"-> Field '{field_name}' is invalid and will be reverted to its default value.")
209
- corrected_data[field_name] = defaults.get(field_name)
223
+ if field_name in corrected_data:
224
+ corrected_data[field_name] = defaults.get(field_name)
210
225
 
211
226
  print("--- End of Error Report ---")
212
227
 
@@ -1,3 +1,4 @@
1
+ from ara_cli.error_handler import AraError
1
2
  from ara_cli.artefact_scan import check_file
2
3
  from ara_cli.artefact_fuzzy_search import (
3
4
  find_closest_name_matches,
@@ -10,6 +11,7 @@ from ara_cli.artefact_models.artefact_model import Artefact
10
11
  from typing import Optional, Dict, List, Tuple
11
12
  import difflib
12
13
  import os
14
+ import re
13
15
 
14
16
 
15
17
  def populate_classified_artefact_info(
@@ -104,8 +106,9 @@ def determine_artefact_type_and_class(classifier):
104
106
 
105
107
  artefact_class = artefact_type_mapping.get(artefact_type)
106
108
  if not artefact_class:
107
- print(f"No artefact class found for {artefact_type}")
108
- return None, None
109
+ raise AraError(f"No artefact class found for {artefact_type}")
110
+ # print(f"No artefact class found for {artefact_type}")
111
+ # return None, None
109
112
 
110
113
  return artefact_type, artefact_class
111
114
 
@@ -144,11 +147,11 @@ def run_agent(prompt, artefact_class):
144
147
  # anthropic:claude-4-sonnet-20250514
145
148
  agent = Agent(
146
149
  model="anthropic:claude-4-sonnet-20250514",
147
- result_type=artefact_class,
150
+ output_type=artefact_class,
148
151
  instrument=True,
149
152
  )
150
153
  result = agent.run_sync(prompt)
151
- return result.data
154
+ return result.output
152
155
 
153
156
 
154
157
  def write_corrected_artefact(file_path, corrected_text):
@@ -196,36 +199,52 @@ def ask_for_correct_contribution(
196
199
  return name, classifier
197
200
 
198
201
 
199
- def ask_for_contribution_choice(
200
- choices, artefact_info: Optional[tuple[str, str]] = None
201
- ) -> Optional[str]:
202
- artefact_name, artefact_classifier = (
203
- artefact_info if artefact_info else (None, None)
204
- )
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)
205
204
  message = "Found multiple close matches for the contribution"
206
205
  if artefact_name and artefact_classifier:
207
206
  message += f" of the {artefact_classifier} '{artefact_name}'"
208
- print(f"{message}.")
209
- for i, contribution in enumerate(choices):
210
- print(f"{i + 1}: {contribution}")
211
- choice_number = input(
212
- "Please choose the artefact to use for contribution (enter number): "
213
- )
207
+ message += "."
208
+ return get_user_choice(choices, message)
209
+
210
+
211
+ def _has_valid_contribution(artefact: Artefact) -> bool:
212
+ contribution = artefact.contribution
213
+ return contribution and contribution.artefact_name and contribution.classifier
214
+
215
+
216
+ def get_user_choice(choices: List[str], message: str) -> Optional[str]:
217
+ """
218
+ Generic function to present user with a list of choices and return their selection.
219
+
220
+ Args:
221
+ choices: A list of strings representing the choices to display.
222
+ message: A message to display before listing the choices.
223
+
224
+ Returns:
225
+ The chosen item from the list or None if the input was invalid.
226
+ """
227
+ print(message)
228
+ for i, choice in enumerate(choices):
229
+ print(f"{i + 1}: {choice}")
230
+
231
+ choice_number = input("Please enter your choice (number): ")
232
+
214
233
  try:
215
234
  choice_index = int(choice_number) - 1
216
235
  if choice_index < 0 or choice_index >= len(choices):
217
- print("Invalid choice. Aborting contribution choice.")
236
+ print("Invalid choice. Aborting operation.")
218
237
  return None
219
- choice = choices[choice_index]
238
+ return choices[choice_index]
220
239
  except ValueError:
221
- print("Invalid input. Aborting contribution choice.")
240
+ print("Invalid input. Aborting operation.")
222
241
  return None
223
- return choice
224
242
 
225
243
 
226
- def _has_valid_contribution(artefact: Artefact) -> bool:
227
- contribution = artefact.contribution
228
- return contribution and contribution.artefact_name and contribution.classifier
244
+ def ask_for_rule_choice(matches: List[str]) -> Optional[str]:
245
+ """Asks the user for a choice between multiple rule matches"""
246
+ message = "Multiple rule matches found:"
247
+ return get_user_choice(matches, message)
229
248
 
230
249
 
231
250
  def _update_rule(
@@ -249,6 +268,9 @@ def _update_rule(
249
268
  return
250
269
  if not closest_rule_match:
251
270
  return
271
+ if len(closest_rule_match) > 1:
272
+ artefact.contribution.rule = ask_for_rule_choice(closest_rule_match)
273
+ return
252
274
  artefact.contribution.rule = closest_rule_match[0]
253
275
 
254
276
 
@@ -353,6 +375,131 @@ def set_closest_contribution(
353
375
  return artefact, True
354
376
 
355
377
 
378
+ def fix_scenario_placeholder_mismatch(
379
+ file_path: str, artefact_text: str, artefact_class, **kwargs
380
+ ) -> str:
381
+ """
382
+ Converts a regular Scenario with placeholders to a Scenario Outline.
383
+ This is a deterministic fix that detects placeholders and converts the format.
384
+ """
385
+ lines = artefact_text.splitlines()
386
+ new_lines = []
387
+ i = 0
388
+
389
+ while i < len(lines):
390
+ line = lines[i]
391
+ stripped_line = line.strip()
392
+
393
+ if stripped_line.startswith('Scenario:'):
394
+ scenario_lines, next_index = _extract_scenario_block(lines, i)
395
+ processed_lines = _process_scenario_block(scenario_lines)
396
+ new_lines.extend(processed_lines)
397
+ i = next_index
398
+ else:
399
+ new_lines.append(line)
400
+ i += 1
401
+
402
+ return "\n".join(new_lines)
403
+
404
+
405
+ def _extract_scenario_block(lines: list, start_index: int) -> tuple[list, int]:
406
+ """Extract all lines belonging to a scenario block."""
407
+ scenario_lines = [lines[start_index]]
408
+ j = start_index + 1
409
+
410
+ while j < len(lines):
411
+ next_line = lines[j].strip()
412
+ if _is_scenario_boundary(next_line):
413
+ break
414
+ scenario_lines.append(lines[j])
415
+ j += 1
416
+
417
+ return scenario_lines, j
418
+
419
+
420
+ def _is_scenario_boundary(line: str) -> bool:
421
+ """Check if a line marks the boundary of a scenario block."""
422
+ boundaries = ['Scenario:', 'Scenario Outline:', 'Background:', 'Feature:']
423
+ return any(line.startswith(boundary) for boundary in boundaries)
424
+
425
+
426
+ def _process_scenario_block(scenario_lines: list) -> list:
427
+ """Process a scenario block and convert to outline if placeholders are found."""
428
+ if not scenario_lines:
429
+ return scenario_lines
430
+
431
+ first_line = scenario_lines[0]
432
+ indentation = _get_line_indentation(first_line)
433
+ placeholders = _extract_placeholders_from_scenario(scenario_lines[1:])
434
+
435
+ if not placeholders:
436
+ return scenario_lines
437
+
438
+ return _convert_to_scenario_outline(scenario_lines, placeholders, indentation)
439
+
440
+
441
+ def _get_line_indentation(line: str) -> str:
442
+ """Get the indentation of a line."""
443
+ return line[:len(line) - len(line.lstrip())]
444
+
445
+
446
+ def _extract_placeholders_from_scenario(step_lines: list) -> set:
447
+ """Extract placeholders from scenario step lines, ignoring docstrings."""
448
+ placeholders = set()
449
+ in_docstring = False
450
+
451
+ for line in step_lines:
452
+ step_line = line.strip()
453
+ if not step_line:
454
+ continue
455
+
456
+ in_docstring = _update_docstring_state(step_line, in_docstring)
457
+
458
+ if not in_docstring and '"""' not in step_line:
459
+ found = re.findall(r'<([^>]+)>', step_line)
460
+ placeholders.update(found)
461
+
462
+ return placeholders
463
+
464
+
465
+ def _update_docstring_state(line: str, current_state: bool) -> bool:
466
+ """Update the docstring state based on the current line."""
467
+ if '"""' in line:
468
+ return not current_state
469
+ return current_state
470
+
471
+
472
+ def _convert_to_scenario_outline(scenario_lines: list, placeholders: set, indentation: str) -> list:
473
+ """Convert scenario lines to scenario outline format with examples table."""
474
+ first_line = scenario_lines[0]
475
+ title = first_line.strip()[len('Scenario:'):].strip()
476
+
477
+ new_lines = [f"{indentation}Scenario Outline: {title}"]
478
+ new_lines.extend(scenario_lines[1:])
479
+ new_lines.append("")
480
+
481
+ examples_lines = _create_examples_table(placeholders, indentation)
482
+ new_lines.extend(examples_lines)
483
+
484
+ return new_lines
485
+
486
+
487
+ def _create_examples_table(placeholders: set, base_indentation: str) -> list:
488
+ """Create the Examples table for the scenario outline."""
489
+ examples_indentation = base_indentation + " "
490
+ table_indentation = examples_indentation + " "
491
+
492
+ sorted_placeholders = sorted(placeholders)
493
+ header = "| " + " | ".join(sorted_placeholders) + " |"
494
+ sample_row = "| " + " | ".join(f"<{p}_value>" for p in sorted_placeholders) + " |"
495
+
496
+ return [
497
+ f"{examples_indentation}Examples:",
498
+ f"{table_indentation}{header}",
499
+ f"{table_indentation}{sample_row}"
500
+ ]
501
+
502
+
356
503
  def fix_title_mismatch(
357
504
  file_path: str, artefact_text: str, artefact_class, **kwargs
358
505
  ) -> str:
@@ -565,6 +712,7 @@ def apply_autofix(
565
712
  "Filename-Title Mismatch": fix_title_mismatch,
566
713
  "Invalid Contribution Reference": fix_contribution,
567
714
  "Rule Mismatch": fix_rule,
715
+ "Scenario Contains Placeholders": fix_scenario_placeholder_mismatch,
568
716
  }
569
717
 
570
718
  artefact_type, artefact_class = determine_artefact_type_and_class(classifier)