ara-cli 0.1.9.73__tar.gz → 0.1.9.75__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.
Files changed (169) hide show
  1. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/PKG-INFO +1 -1
  2. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/ara_command_action.py +15 -15
  3. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/ara_command_parser.py +2 -1
  4. ara_cli-0.1.9.75/ara_cli/ara_config.py +254 -0
  5. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_autofix.py +130 -68
  6. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_creator.py +1 -1
  7. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/artefact_model.py +26 -7
  8. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/artefact_templates.py +47 -31
  9. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
  10. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/epic_artefact_model.py +23 -24
  11. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/feature_artefact_model.py +76 -46
  12. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
  13. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/task_artefact_model.py +73 -13
  14. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/userstory_artefact_model.py +22 -24
  15. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/vision_artefact_model.py +23 -42
  16. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_scan.py +55 -17
  17. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/chat.py +23 -5
  18. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/prompt_handler.py +4 -4
  19. ara_cli-0.1.9.75/ara_cli/tag_extractor.py +82 -0
  20. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/template_manager.py +3 -8
  21. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/version.py +1 -1
  22. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/PKG-INFO +1 -1
  23. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/SOURCES.txt +0 -10
  24. ara_cli-0.1.9.75/tests/test_ara_config.py +443 -0
  25. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_autofix.py +289 -25
  26. ara_cli-0.1.9.75/tests/test_artefact_scan.py +450 -0
  27. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_chat.py +35 -15
  28. ara_cli-0.1.9.73/ara_cli/ara_config.py +0 -146
  29. ara_cli-0.1.9.73/ara_cli/tag_extractor.py +0 -67
  30. ara_cli-0.1.9.73/ara_cli/templates/template.businessgoal +0 -10
  31. ara_cli-0.1.9.73/ara_cli/templates/template.capability +0 -10
  32. ara_cli-0.1.9.73/ara_cli/templates/template.epic +0 -15
  33. ara_cli-0.1.9.73/ara_cli/templates/template.example +0 -6
  34. ara_cli-0.1.9.73/ara_cli/templates/template.feature +0 -26
  35. ara_cli-0.1.9.73/ara_cli/templates/template.issue +0 -14
  36. ara_cli-0.1.9.73/ara_cli/templates/template.keyfeature +0 -15
  37. ara_cli-0.1.9.73/ara_cli/templates/template.task +0 -6
  38. ara_cli-0.1.9.73/ara_cli/templates/template.userstory +0 -17
  39. ara_cli-0.1.9.73/ara_cli/templates/template.vision +0 -14
  40. ara_cli-0.1.9.73/tests/test_ara_config.py +0 -59
  41. ara_cli-0.1.9.73/tests/test_artefact_scan.py +0 -189
  42. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/MANIFEST.in +0 -0
  43. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/README.md +0 -0
  44. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/__init__.py +0 -0
  45. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/__main__.py +0 -0
  46. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_deleter.py +0 -0
  47. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_fuzzy_search.py +0 -0
  48. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_link_updater.py +0 -0
  49. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_lister.py +0 -0
  50. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/__init__.py +0 -0
  51. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/artefact_load.py +0 -0
  52. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/artefact_mapping.py +0 -0
  53. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/capability_artefact_model.py +0 -0
  54. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/example_artefact_model.py +0 -0
  55. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/issue_artefact_model.py +0 -0
  56. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_models/serialize_helper.py +0 -0
  57. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_reader.py +0 -0
  58. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/artefact_renamer.py +0 -0
  59. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/classifier.py +0 -0
  60. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/codefusionretriever.py +0 -0
  61. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/codehierachieretriever.py +0 -0
  62. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/commandline_completer.py +0 -0
  63. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/directory_navigator.py +0 -0
  64. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/file_classifier.py +0 -0
  65. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/file_lister.py +0 -0
  66. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/filename_validator.py +0 -0
  67. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/list_filter.py +0 -0
  68. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/output_suppressor.py +0 -0
  69. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/prompt_chat.py +0 -0
  70. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/prompt_extractor.py +0 -0
  71. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/prompt_rag.py +0 -0
  72. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/run_file_lister.py +0 -0
  73. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/agile.artefacts +0 -0
  74. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -0
  75. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/blueprints/empty.blueprint.md +0 -0
  76. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/blueprints/task_todo_list_C4_architecture_analysis.blueprint.md +0 -0
  77. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -0
  78. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/architecture_C4_analysis.commands.md +0 -0
  79. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/architecture_radon_cc_score.commands.md +0 -0
  80. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/architecture_radon_halstead_v.commands.md +0 -0
  81. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/architecture_radon_maintainability_score.commands.md +0 -0
  82. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -0
  83. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -0
  84. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -0
  85. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -0
  86. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -0
  87. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -0
  88. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/empty.commands.md +0 -0
  89. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -0
  90. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -0
  91. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -0
  92. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -0
  93. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -0
  94. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -0
  95. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -0
  96. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -0
  97. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/classify_task.intention.md +0 -0
  98. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/empty.intention.md +0 -0
  99. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/error_fixing.intention.md +0 -0
  100. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_fix_steps_for_scenario.intention.md +0 -0
  101. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_formulation.intention.md +0 -0
  102. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_reverse_formulation_from_code.intention.md +0 -0
  103. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_scenario_implementation.intention.md +0 -0
  104. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_scenario_implementation_update.intention.md +0 -0
  105. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_scenario_outline_extension.intention.md +0 -0
  106. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/feature_update_formulation.intention.md +0 -0
  107. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/fibonacci_example_implementation.intention.md +0 -0
  108. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/js_implementation_from_task_description.intention.md +0 -0
  109. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/js_steps_implementation.intention.md +0 -0
  110. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/python_cli_implementation_with_test.intention.md +0 -0
  111. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/python_code_understanding.intention.md +0 -0
  112. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/task_implementation.intention.md +0 -0
  113. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/task_prompt_control_by_status.intention.md +0 -0
  114. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/task_stepwise_implementation_by_number.intention.md +0 -0
  115. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/intentions/task_stepwise_implementation_by_status.intention.md +0 -0
  116. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/architecture_analyst.rules.md +0 -0
  117. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/code_analyst.rules.md +0 -0
  118. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/empty.rules.md +0 -0
  119. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/error_analyst.rules.md +0 -0
  120. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/gherkin_expert.rules.md +0 -0
  121. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/js_expert_developer.rules.md +0 -0
  122. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/product_owner.rules.md +0 -0
  123. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/python_behave.rules.md +0 -0
  124. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/prompt-modules/rules/python_developer.rules.md +0 -0
  125. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.concept.exploration.md +0 -0
  126. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.concept.md +0 -0
  127. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.customer.exploration.md +0 -0
  128. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.customer.md +0 -0
  129. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.persona.exploration.md +0 -0
  130. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.persona.md +0 -0
  131. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.step.exploration.md +0 -0
  132. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.step.md +0 -0
  133. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.technology.exploration.md +0 -0
  134. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/specification_breakdown_files/template.technology.md +0 -0
  135. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.businessgoal.prompt_log.md +0 -0
  136. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.capability.prompt_log.md +0 -0
  137. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.epic.prompt_log.md +0 -0
  138. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.example.prompt_log.md +0 -0
  139. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.feature.prompt_log.md +0 -0
  140. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.issue.prompt_log.md +0 -0
  141. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.keyfeature.prompt_log.md +0 -0
  142. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.steps.prompt_log.md +0 -0
  143. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.task.prompt_log.md +0 -0
  144. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.userstory.prompt_log.md +0 -0
  145. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/templates/template.vision.prompt_log.md +0 -0
  146. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli/update_config_prompt.py +0 -0
  147. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/dependency_links.txt +0 -0
  148. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/entry_points.txt +0 -0
  149. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/requires.txt +0 -0
  150. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/ara_cli.egg-info/top_level.txt +0 -0
  151. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/docker/base/requirements.txt +0 -0
  152. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/setup.cfg +0 -0
  153. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/setup.py +0 -0
  154. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/__init__.py +0 -0
  155. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_ara_command_action.py +0 -0
  156. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_fuzzy_search.py +0 -0
  157. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_link_updater.py +0 -0
  158. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_lister.py +0 -0
  159. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_reader.py +0 -0
  160. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_artefact_renamer.py +0 -0
  161. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_classifier.py +0 -0
  162. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_directory_navigator.py +0 -0
  163. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_file_classifier.py +0 -0
  164. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_file_creator.py +0 -0
  165. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_file_lister.py +0 -0
  166. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_list_filter.py +0 -0
  167. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_tag_extractor.py +0 -0
  168. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/tests/test_template_manager.py +0 -0
  169. {ara_cli-0.1.9.73 → ara_cli-0.1.9.75}/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.73
3
+ Version: 0.1.9.75
4
4
  Requires-Dist: litellm
5
5
  Requires-Dist: llama-index
6
6
  Requires-Dist: llama-index-llms-openai
@@ -347,6 +347,7 @@ def reconnect_action(args):
347
347
  from ara_cli.artefact_models.artefact_load import artefact_from_content
348
348
  from ara_cli.artefact_models.artefact_model import Contribution
349
349
  from ara_cli.artefact_reader import ArtefactReader
350
+ from ara_cli.file_classifier import FileClassifier
350
351
  from ara_cli.artefact_fuzzy_search import find_closest_rule
351
352
 
352
353
  classifier = args.classifier
@@ -359,31 +360,29 @@ def reconnect_action(args):
359
360
 
360
361
  feedback_message = f"Updated contribution of {classifier} '{artefact_name}' to {parent_classifier} '{parent_name}'"
361
362
 
362
- content, artefact_info = ArtefactReader.read_artefact_data(
363
+ file_classifier = FileClassifier(os)
364
+ classified_file_info = file_classifier.classify_files()
365
+
366
+ artefact = ArtefactReader.read_artefact(
363
367
  artefact_name=artefact_name,
364
- classifier=classifier
368
+ classifier=classifier,
369
+ classified_file_info=classified_file_info
365
370
  )
366
- if not content:
371
+
372
+ if not artefact:
367
373
  print(read_error_message)
368
374
  return
369
375
 
370
- parent_content, parent_info = ArtefactReader.read_artefact_data(
376
+ parent = ArtefactReader.read_artefact(
371
377
  artefact_name=parent_name,
372
- classifier=parent_classifier
378
+ classifier=parent_classifier,
379
+ classified_file_info=classified_file_info
373
380
  )
374
- if not parent_content:
381
+
382
+ if not parent:
375
383
  print(read_error_message)
376
384
  return
377
385
 
378
- artefact = artefact_from_content(
379
- content=content,
380
- )
381
- artefact._file_path = artefact_info["file_path"]
382
-
383
- parent = artefact_from_content(
384
- content=parent_content
385
- )
386
-
387
386
  contribution = Contribution(
388
387
  artefact_name=parent.title,
389
388
  classifier=parent.artefact_type
@@ -603,6 +602,7 @@ def autofix_action(args):
603
602
  file_path,
604
603
  classifier,
605
604
  reason,
605
+ single_pass=args.single_pass,
606
606
  deterministic=run_deterministic,
607
607
  non_deterministic=run_non_deterministic,
608
608
  classified_artefact_info=classified_artefact_info
@@ -229,7 +229,8 @@ def scan_parser(subparsers):
229
229
 
230
230
 
231
231
  def autofix_parser(subparsers):
232
- autofix_parser = subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command.")
232
+ autofix_parser = subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command. By default three attemps for every file.")
233
+ autofix_parser.add_argument("--single-pass", action="store_true", help="Run the autofix once for every scaned file.")
233
234
  determinism_group = autofix_parser.add_mutually_exclusive_group()
234
235
  determinism_group.add_argument("--deterministic", "-d", action="store_true", help="Run only deterministic fixes e.g Title-FileName Mismatch fix")
235
236
  determinism_group.add_argument("--non-deterministic", "-nd", action="store_true", help="Run only non-deterministic fixes")
@@ -0,0 +1,254 @@
1
+ from typing import List, Dict, Optional, Any
2
+ from pydantic import BaseModel, ValidationError, Field, field_validator, model_validator
3
+ import json
4
+ import os
5
+ from os.path import exists, dirname
6
+ from os import makedirs
7
+ from functools import lru_cache
8
+ import sys
9
+
10
+ DEFAULT_CONFIG_LOCATION = "./ara/.araconfig/ara_config.json"
11
+
12
+ class LLMConfigItem(BaseModel):
13
+ provider: str
14
+ model: str
15
+ temperature: float = Field(ge=0.0, le=1.0)
16
+ max_tokens: Optional[int] = None
17
+
18
+ @field_validator('temperature')
19
+ @classmethod
20
+ def validate_temperature(cls, v: float, info) -> float:
21
+ if not 0.0 <= v <= 1.0:
22
+ print(f"Warning: Temperature is outside the 0.0 to 1.0 range")
23
+ # Return a valid default
24
+ return 0.8
25
+ return v
26
+
27
+ class ExtCodeDirItem(BaseModel):
28
+ source_dir: str
29
+
30
+ class ARAconfig(BaseModel):
31
+ ext_code_dirs: List[ExtCodeDirItem] = Field(default_factory=lambda: [
32
+ ExtCodeDirItem(source_dir="./src"),
33
+ ExtCodeDirItem(source_dir="./tests")
34
+ ])
35
+ glossary_dir: str = "./glossary"
36
+ doc_dir: str = "./docs"
37
+ local_prompt_templates_dir: str = "./ara/.araconfig"
38
+ custom_prompt_templates_subdir: Optional[str] = "custom-prompt-modules"
39
+ local_ara_templates_dir: str = "./ara/.araconfig/templates/"
40
+ ara_prompt_given_list_includes: List[str] = Field(default_factory=lambda: [
41
+ "*.businessgoal",
42
+ "*.vision",
43
+ "*.capability",
44
+ "*.keyfeature",
45
+ "*.epic",
46
+ "*.userstory",
47
+ "*.example",
48
+ "*.feature",
49
+ "*.task",
50
+ "*.py",
51
+ "*.md",
52
+ "*.png",
53
+ "*.jpg",
54
+ "*.jpeg",
55
+ ])
56
+ llm_config: Dict[str, LLMConfigItem] = Field(default_factory=lambda: {
57
+ "gpt-4o": LLMConfigItem(
58
+ provider="openai",
59
+ model="openai/gpt-4o",
60
+ temperature=0.8,
61
+ max_tokens=16384
62
+ ),
63
+ "gpt-4.1": LLMConfigItem(
64
+ provider="openai",
65
+ model="openai/gpt-4.1",
66
+ temperature=0.8,
67
+ max_tokens=1024
68
+ ),
69
+ "o3-mini": LLMConfigItem(
70
+ provider="openai",
71
+ model="openai/o3-mini",
72
+ temperature=1.0,
73
+ max_tokens=1024
74
+ ),
75
+ "opus-4": LLMConfigItem(
76
+ provider="anthropic",
77
+ model="anthropic/claude-opus-4-20250514",
78
+ temperature=0.8,
79
+ max_tokens=32000
80
+ ),
81
+ "sonnet-4": LLMConfigItem(
82
+ provider="anthropic",
83
+ model="anthropic/claude-sonnet-4-20250514",
84
+ temperature=0.8,
85
+ max_tokens=1024
86
+ ),
87
+ "together-ai-llama-2": LLMConfigItem(
88
+ provider="together_ai",
89
+ model="together_ai/togethercomputer/llama-2-70b",
90
+ temperature=0.8,
91
+ max_tokens=1024
92
+ ),
93
+ "groq-llama-3": LLMConfigItem(
94
+ provider="groq",
95
+ model="groq/llama3-70b-8192",
96
+ temperature=0.8,
97
+ max_tokens=1024
98
+ )
99
+ })
100
+ default_llm: Optional[str] = "gpt-4o"
101
+
102
+ model_config = {
103
+ "extra": "forbid" # This will help identify unrecognized keys
104
+ }
105
+
106
+ @model_validator(mode='after')
107
+ def check_critical_fields(self) -> 'ARAconfig':
108
+ """Check for empty critical fields and use defaults if needed"""
109
+ critical_fields = {
110
+ 'ext_code_dirs': [ExtCodeDirItem(source_dir="./src"), ExtCodeDirItem(source_dir="./tests")],
111
+ 'local_ara_templates_dir': "./ara/.araconfig/templates/",
112
+ 'local_prompt_templates_dir': "./ara/.araconfig",
113
+ 'glossary_dir': "./glossary"
114
+ }
115
+
116
+ for field, default_value in critical_fields.items():
117
+ current_value = getattr(self, field)
118
+ if (not current_value or
119
+ (isinstance(current_value, list) and len(current_value) == 0) or
120
+ (isinstance(current_value, str) and current_value.strip() == "")):
121
+ print(f"Warning: Value for '{field}' is missing or empty.")
122
+ setattr(self, field, default_value)
123
+
124
+ return self
125
+
126
+ # Function to ensure the necessary directories exist
127
+ @lru_cache(maxsize=None)
128
+ def ensure_directory_exists(directory: str):
129
+ if not exists(directory):
130
+ os.makedirs(directory)
131
+ print(f"New directory created at {directory}")
132
+ return directory
133
+
134
+ def handle_unrecognized_keys(data: dict, known_fields: set) -> dict:
135
+ """Remove unrecognized keys and warn the user"""
136
+ cleaned_data = {}
137
+ for key, value in data.items():
138
+ if key not in known_fields:
139
+ print(f"Warning: {key} is not recognized as a valid configuration option.")
140
+ else:
141
+ cleaned_data[key] = value
142
+ return cleaned_data
143
+
144
+ def fix_llm_temperatures(data: dict) -> dict:
145
+ """Fix invalid temperatures in LLM configurations"""
146
+ if 'llm_config' in data:
147
+ for model_key, model_config in data['llm_config'].items():
148
+ if isinstance(model_config, dict) and 'temperature' in model_config:
149
+ temp = model_config['temperature']
150
+ if not 0.0 <= temp <= 1.0:
151
+ print(f"Warning: Temperature for model '{model_key}' is outside the 0.0 to 1.0 range")
152
+ model_config['temperature'] = 0.8
153
+ return data
154
+
155
+ def validate_and_fix_config_data(filepath: str) -> dict:
156
+ """Load, validate, and fix configuration data"""
157
+ try:
158
+ with open(filepath, "r", encoding="utf-8") as file:
159
+ data = json.load(file)
160
+
161
+ # Get known fields from the ARAconfig model
162
+ known_fields = set(ARAconfig.model_fields.keys())
163
+
164
+ # Handle unrecognized keys
165
+ data = handle_unrecognized_keys(data, known_fields)
166
+
167
+ # Fix LLM temperatures before validation
168
+ data = fix_llm_temperatures(data)
169
+
170
+ return data
171
+ except json.JSONDecodeError as e:
172
+ print(f"Error: Invalid JSON in configuration file: {e}")
173
+ print("Creating new configuration with defaults...")
174
+ return {}
175
+ except Exception as e:
176
+ print(f"Error reading configuration file: {e}")
177
+ return {}
178
+
179
+ # Function to read the JSON file and return an ARAconfig model
180
+ @lru_cache(maxsize=1)
181
+ def read_data(filepath: str) -> ARAconfig:
182
+ # Ensure the directory for the config file exists
183
+ config_dir = dirname(filepath)
184
+ ensure_directory_exists(config_dir)
185
+
186
+ if not exists(filepath):
187
+ # If the file does not exist, create it with default values
188
+ default_config = ARAconfig()
189
+ save_data(filepath, default_config)
190
+ print(
191
+ f"ara-cli configuration file '{filepath}' created with default configuration."
192
+ f" Please modify it as needed and re-run your command"
193
+ )
194
+ sys.exit(0) # Exit the application
195
+
196
+ # Validate and load the existing configuration
197
+ data = validate_and_fix_config_data(filepath)
198
+
199
+ try:
200
+ # Try to create the config with the loaded data
201
+ config = ARAconfig(**data)
202
+
203
+ # Save the potentially fixed configuration back
204
+ save_data(filepath, config)
205
+
206
+ return config
207
+ except ValidationError as e:
208
+ print(f"ValidationError: {e}")
209
+ print("Correcting configuration with default values...")
210
+
211
+ # Create a default config
212
+ default_config = ARAconfig()
213
+
214
+ # Try to preserve valid fields from the original data
215
+ for field_name, field_value in data.items():
216
+ if field_name in ARAconfig.model_fields:
217
+ try:
218
+ # Attempt to set the field value
219
+ setattr(default_config, field_name, field_value)
220
+ except:
221
+ # If it fails, keep the default
222
+ pass
223
+
224
+ # Save the corrected configuration
225
+ save_data(filepath, default_config)
226
+ print("Fixed configuration saved to file.")
227
+
228
+ return default_config
229
+
230
+ # Function to save the modified configuration back to the JSON file
231
+ def save_data(filepath: str, config: ARAconfig):
232
+ with open(filepath, "w", encoding="utf-8") as file:
233
+ json.dump(config.model_dump(), file, indent=4)
234
+
235
+ # Singleton for configuration management
236
+ class ConfigManager:
237
+ _config_instance = None
238
+
239
+ @classmethod
240
+ def get_config(cls, filepath=DEFAULT_CONFIG_LOCATION):
241
+ if cls._config_instance is None:
242
+ config_dir = dirname(filepath)
243
+
244
+ if not exists(config_dir):
245
+ makedirs(config_dir)
246
+
247
+ cls._config_instance = read_data(filepath)
248
+ return cls._config_instance
249
+
250
+ @classmethod
251
+ def reset(cls):
252
+ """Reset the configuration instance (useful for testing)"""
253
+ cls._config_instance = None
254
+ read_data.cache_clear()
@@ -1,3 +1,4 @@
1
+ from ara_cli.artefact_scan import check_file
1
2
  from ara_cli.artefact_fuzzy_search import (
2
3
  find_closest_name_matches,
3
4
  extract_artefact_names_of_classifier,
@@ -29,36 +30,45 @@ def parse_report(content: str) -> Dict[str, List[Tuple[str, str]]]:
29
30
  Parses the incompatible artefacts report and returns structured data.
30
31
  Returns a dictionary where keys are artefact classifiers, and values are lists of (file_path, reason) tuples.
31
32
  """
33
+ def is_valid_report(lines: List[str]) -> bool:
34
+ return bool(lines) and lines[0] == "# Artefact Check Report"
35
+
36
+ def has_no_problems(lines: List[str]) -> bool:
37
+ return len(lines) >= 3 and lines[2] == "No problems found."
38
+
39
+ def parse_classifier(line: str) -> Optional[str]:
40
+ if line.startswith("## "):
41
+ return line[3:].strip()
42
+ return None
43
+
44
+ def parse_issue(line: str) -> Optional[Tuple[str, str]]:
45
+ if not line.startswith("- "):
46
+ return None
47
+ parts = line.split("`", 2)
48
+ if len(parts) < 3:
49
+ return None
50
+ file_path = parts[1]
51
+ reason = parts[2].split(":", 1)[1].strip() if ":" in parts[2] else ""
52
+ return file_path, reason
53
+
32
54
  lines = content.splitlines()
55
+ if not is_valid_report(lines) or has_no_problems(lines):
56
+ return {}
57
+
33
58
  issues = {}
34
59
  current_classifier = None
35
60
 
36
- if not lines or lines[0] != "# Artefact Check Report":
37
- return issues
38
- return issues
39
-
40
- if len(lines) >= 3 and lines[2] == "No problems found.":
41
- return issues
42
- return issues
43
-
44
- for line in lines[1:]:
45
- line = line.strip()
61
+ for line in map(str.strip, lines[1:]):
46
62
  if not line:
47
63
  continue
48
-
49
- if line.startswith("## "):
50
- current_classifier = line[3:].strip()
64
+ classifier = parse_classifier(line)
65
+ if classifier is not None:
66
+ current_classifier = classifier
51
67
  issues[current_classifier] = []
52
-
53
- elif line.startswith("- ") and current_classifier is not None:
54
- parts = line.split("`", 2)
55
- if len(parts) < 3:
56
- continue
57
-
58
- file_path = parts[1]
59
- reason = parts[2].split(":", 1)[1].strip() if ":" in parts[2] else ""
60
- issues[current_classifier].append((file_path, reason))
61
-
68
+ continue
69
+ issue = parse_issue(line)
70
+ if issue and current_classifier is not None:
71
+ issues[current_classifier].append(issue)
62
72
  return issues
63
73
 
64
74
 
@@ -159,7 +169,7 @@ def ask_for_correct_contribution(
159
169
 
160
170
  print(
161
171
  f"Can not determine a match for contribution {contribution_message}. "
162
- f"Please provide a valid contribution or contribution will be empty (<classifier> <file_name>)."
172
+ f"Please provide a valid contribution or contribution will be empty ([classifier] [file_name])."
163
173
  )
164
174
 
165
175
  user_input = input().strip()
@@ -179,7 +189,9 @@ def ask_for_correct_contribution(
179
189
  def ask_for_contribution_choice(
180
190
  choices, artefact_info: Optional[tuple[str, str]] = None
181
191
  ) -> Optional[str]:
182
- artefact_name, artefact_classifier = artefact_info
192
+ artefact_name, artefact_classifier = (
193
+ artefact_info if artefact_info else (None, None)
194
+ )
183
195
  message = "Found multiple close matches for the contribution"
184
196
  if artefact_name and artefact_classifier:
185
197
  message += f" of the {artefact_classifier} '{artefact_name}'"
@@ -379,68 +391,118 @@ def apply_autofix(
379
391
  file_path: str,
380
392
  classifier: str,
381
393
  reason: str,
394
+ single_pass: bool = False,
382
395
  deterministic: bool = True,
383
396
  non_deterministic: bool = True,
384
397
  classified_artefact_info: Optional[Dict[str, List[Dict[str, str]]]] = None,
385
398
  ) -> bool:
386
- artefact_text = read_artefact(file_path)
387
- if artefact_text is None:
399
+ """
400
+ Applies fixes to a single artefact file iteratively until it is valid
401
+ or a fix cannot be applied. If single_pass is True, it runs for only one attempt.
402
+ """
403
+ deterministic_markers_to_functions = {
404
+ "Filename-Title Mismatch": fix_title_mismatch,
405
+ "Invalid Contribution Reference": fix_contribution,
406
+ }
407
+
408
+ def populate_classified_artefact_info(force: bool = False):
409
+ nonlocal classified_artefact_info
410
+ if force or classified_artefact_info is None:
411
+ file_classifier = FileClassifier(os)
412
+ classified_artefact_info = file_classifier.classify_files()
413
+
414
+ def determine_attempt_count() -> int:
415
+ nonlocal single_pass, file_path
416
+ if single_pass:
417
+ print(f"Single-pass mode enabled for {file_path}. Running for 1 attempt.")
418
+ return 1
419
+ return 3
420
+
421
+ def apply_deterministic_fix() -> str:
422
+ nonlocal deterministic, deterministic_issue, corrected_text, file_path, artefact_text, artefact_class, classified_artefact_info
423
+ if deterministic and deterministic_issue:
424
+ print(f"Applying deterministic fix for '{deterministic_issue}'...")
425
+ fix_function = deterministic_markers_to_functions[deterministic_issue]
426
+ return fix_function(
427
+ file_path=file_path,
428
+ artefact_text=artefact_text,
429
+ artefact_class=artefact_class,
430
+ classified_artefact_info=classified_artefact_info,
431
+ )
432
+ return corrected_text
433
+
434
+ def apply_non_deterministic_fix() -> Optional[str]:
435
+ """
436
+ Applies LLM fix. Return None in case of an exception
437
+ """
438
+ nonlocal non_deterministic, deterministic_issue, corrected_text, artefact_type, current_reason, file_path, artefact_text
439
+ if non_deterministic and not deterministic_issue:
440
+ print("Applying non-deterministic (LLM) fix...")
441
+ prompt = construct_prompt(artefact_type, current_reason, file_path, artefact_text)
442
+ try:
443
+ corrected_artefact = run_agent(prompt, artefact_class)
444
+ corrected_text = corrected_artefact.serialize()
445
+ except Exception as e:
446
+ print(f" ❌ LLM agent failed to fix artefact at {file_path}: {e}")
447
+ return None
448
+ return corrected_text
449
+
450
+ def should_skip() -> bool:
451
+ nonlocal deterministic_issue, deterministic, non_deterministic
452
+ if not non_deterministic and not deterministic_issue:
453
+ print(f"Skipping non-deterministic fix for {file_path} as per request.")
454
+ return True
455
+ if not deterministic and deterministic_issue:
456
+ print(f"Skipping fix for {file_path} as per request flags.")
457
+ return True
388
458
  return False
389
459
 
390
460
  artefact_type, artefact_class = determine_artefact_type_and_class(classifier)
391
461
  if artefact_type is None or artefact_class is None:
392
462
  return False
393
463
 
394
- if classified_artefact_info is None:
395
- file_classifier = FileClassifier(os)
396
- classified_file_info = file_classifier.classified_files()
464
+ populate_classified_artefact_info()
465
+ max_attempts = determine_attempt_count()
397
466
 
398
- deterministic_markers_to_functions = {
399
- "Filename-Title Mismatch": fix_title_mismatch,
400
- "Invalid Contribution Reference": fix_contribution,
401
- }
467
+ for attempt in range(max_attempts):
468
+ is_valid, current_reason = check_file(file_path, artefact_class, classified_artefact_info)
469
+
470
+ if is_valid:
471
+ print(f"✅ Artefact at {file_path} is now valid.")
472
+ return True
473
+
474
+ print(f"Attempting to fix {file_path} (Attempt {attempt + 1}/{max_attempts})...")
475
+ print(f" Reason: {current_reason}")
476
+
477
+ artefact_text = read_artefact(file_path)
478
+ if artefact_text is None:
479
+ return False
402
480
 
403
- try:
404
481
  deterministic_issue = next(
405
482
  (
406
483
  marker
407
- for marker in deterministic_markers_to_functions.keys()
408
- if marker in reason
484
+ for marker in deterministic_markers_to_functions
485
+ if marker in current_reason
409
486
  ),
410
487
  None,
411
488
  )
412
- except StopIteration:
413
- pass
414
- is_deterministic_issue = deterministic_issue is not None
415
-
416
- if deterministic and is_deterministic_issue:
417
- print(f"Attempting deterministic fix for {file_path}...")
418
- corrected_text = deterministic_markers_to_functions[deterministic_issue](
419
- file_path=file_path,
420
- artefact_text=artefact_text,
421
- artefact_class=artefact_class,
422
- classified_artefact_info=classified_artefact_info,
423
- )
424
- write_corrected_artefact(file_path, corrected_text)
425
- return True
426
-
427
- # Attempt non-deterministic fix if requested and the issue is NOT deterministic
428
- if non_deterministic and not is_deterministic_issue:
429
- print(f"Attempting non-deterministic (LLM) fix for {file_path}...")
430
- prompt = construct_prompt(artefact_type, reason, file_path, artefact_text)
431
- try:
432
- corrected_artefact = run_agent(prompt, artefact_class)
433
- corrected_text = corrected_artefact.serialize()
434
- write_corrected_artefact(file_path, corrected_text)
435
- return True
436
- except Exception as e:
437
- print(f"LLM agent failed to fix artefact at {file_path}: {e}")
489
+
490
+ if should_skip():
438
491
  return False
439
492
 
440
- # Log if a fix was skipped due to flags
441
- if is_deterministic_issue and not deterministic:
442
- print(f"Skipping deterministic fix for {file_path} as per request.")
443
- elif not is_deterministic_issue and not non_deterministic:
444
- print(f"Skipping non-deterministic fix for {file_path} as per request.")
493
+ corrected_text = None
494
+
495
+ corrected_text = apply_deterministic_fix()
496
+ corrected_text = apply_non_deterministic_fix()
497
+
498
+ if corrected_text is None or corrected_text.strip() == artefact_text.strip():
499
+ print(" Fixing attempt did not alter the file. Stopping to prevent infinite loop.")
500
+ return False
501
+
502
+ write_corrected_artefact(file_path, corrected_text)
503
+
504
+ print(" File modified. Re-classifying artefact information for next check...")
505
+ populate_classified_artefact_info(force=True)
445
506
 
507
+ print(f"❌ Failed to fix {file_path} after {max_attempts} attempts.")
446
508
  return False
@@ -106,7 +106,7 @@ class ArtefactCreator:
106
106
  if not self.handle_existing_files(file_exists):
107
107
  return
108
108
 
109
- artefact = template_artefact_of_type(classifier, filename)
109
+ artefact = template_artefact_of_type(classifier, filename, False)
110
110
 
111
111
  if parent_classifier and parent_name:
112
112
  artefact.set_contribution(
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, Field, field_validator, model_validator
2
- from typing import Optional, List, Literal, Union, Dict
2
+ from typing import Optional, List, Literal, Union, Dict, ClassVar
3
3
  from typing_extensions import Self
4
4
  from enum import Enum
5
5
  from abc import ABC, abstractmethod
@@ -47,13 +47,23 @@ class Contribution(BaseModel):
47
47
  description="Rule the contribution is using. The classifier of the parent must be userstory or epic if this is used"
48
48
  )
49
49
 
50
+ PLACEHOLDER_NAME: ClassVar[str] = "<filename or title of the artefact>"
51
+ PLACEHOLDER_CLASSIFIER: ClassVar[str] = "<agile requirement artefact category> <(optional in case the contribution is to an artefact that is detailed with rules)"
52
+ PLACEHOLDER_RULE: ClassVar[str] = "<rule as it is formulated>"
53
+
50
54
  @model_validator(mode="after")
51
55
  def validate_parent(self) -> Self:
52
-
53
56
  artefact_name = self.artefact_name
54
57
  classifier = self.classifier
55
58
  rule = self.rule
56
59
 
60
+ if (
61
+ artefact_name == Contribution.PLACEHOLDER_NAME
62
+ or classifier == Contribution.PLACEHOLDER_CLASSIFIER
63
+ or rule == Contribution.PLACEHOLDER_RULE
64
+ ):
65
+ return self
66
+
57
67
  if artefact_name:
58
68
  artefact_name = replace_space_with_underscore(artefact_name)
59
69
  if not artefact_name or not classifier:
@@ -68,7 +78,7 @@ class Contribution(BaseModel):
68
78
 
69
79
  @field_validator('artefact_name')
70
80
  def validate_artefact_name(cls, value):
71
- if not value:
81
+ if not value or value == Contribution.PLACEHOLDER_NAME:
72
82
  return value
73
83
  if ' ' in value:
74
84
  warnings.warn(message="artefact_name can not contain spaces. Replacing spaces with '_'")
@@ -77,7 +87,7 @@ class Contribution(BaseModel):
77
87
 
78
88
  @field_validator('classifier', mode='after')
79
89
  def validate_classifier(cls, v):
80
- if not v:
90
+ if not v or v == Contribution.PLACEHOLDER_CLASSIFIER:
81
91
  return v
82
92
  try:
83
93
  return ArtefactType(v)
@@ -91,12 +101,21 @@ class Contribution(BaseModel):
91
101
  if not line.startswith(contribution_line_start):
92
102
  raise ValueError(f"Contribution line '{line}' does not start with '{contribution_line_start}'")
93
103
 
104
+ parent_text = line[len(contribution_line_start):].strip()
105
+ rule_specifier = " using rule "
106
+
107
+ placeholder_line = f"{cls.PLACEHOLDER_NAME} {cls.PLACEHOLDER_CLASSIFIER}{rule_specifier}{cls.PLACEHOLDER_RULE}"
108
+ if parent_text == placeholder_line:
109
+ return cls(
110
+ artefact_name=cls.PLACEHOLDER_NAME,
111
+ classifier=cls.PLACEHOLDER_CLASSIFIER,
112
+ rule=cls.PLACEHOLDER_RULE
113
+ )
114
+
94
115
  artefact_name = None
95
116
  classifier = None
96
117
  rule = None
97
118
 
98
- parent_text = line[len(contribution_line_start):].strip()
99
- rule_specifier = " using rule "
100
119
  if rule_specifier in parent_text:
101
120
  parent_text, rule_text = parent_text.split(rule_specifier, 1)
102
121
  rule = rule_text
@@ -113,7 +132,7 @@ class Contribution(BaseModel):
113
132
  def serialize(self) -> str:
114
133
  if not self.classifier or not self.artefact_name:
115
134
  return ""
116
- artefact_type = Classifier.get_artefact_title(self.classifier)
135
+ artefact_type = Classifier.get_artefact_title(self.classifier) or self.classifier
117
136
  artefact_name = replace_underscore_with_space(self.artefact_name)
118
137
  contribution = f"{artefact_name} {artefact_type}"
119
138
  if self.rule: