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.
Files changed (140) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +270 -103
  3. ara_cli/ara_command_action.py +106 -63
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/__init__.py +0 -0
  6. ara_cli/ara_subcommands/autofix.py +26 -0
  7. ara_cli/ara_subcommands/chat.py +27 -0
  8. ara_cli/ara_subcommands/classifier_directory.py +16 -0
  9. ara_cli/ara_subcommands/common.py +100 -0
  10. ara_cli/ara_subcommands/config.py +221 -0
  11. ara_cli/ara_subcommands/convert.py +43 -0
  12. ara_cli/ara_subcommands/create.py +75 -0
  13. ara_cli/ara_subcommands/delete.py +22 -0
  14. ara_cli/ara_subcommands/extract.py +22 -0
  15. ara_cli/ara_subcommands/fetch.py +41 -0
  16. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  17. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  18. ara_cli/ara_subcommands/fetch_templates.py +19 -0
  19. ara_cli/ara_subcommands/list.py +139 -0
  20. ara_cli/ara_subcommands/list_tags.py +25 -0
  21. ara_cli/ara_subcommands/load.py +48 -0
  22. ara_cli/ara_subcommands/prompt.py +136 -0
  23. ara_cli/ara_subcommands/read.py +47 -0
  24. ara_cli/ara_subcommands/read_status.py +20 -0
  25. ara_cli/ara_subcommands/read_user.py +20 -0
  26. ara_cli/ara_subcommands/reconnect.py +27 -0
  27. ara_cli/ara_subcommands/rename.py +22 -0
  28. ara_cli/ara_subcommands/scan.py +14 -0
  29. ara_cli/ara_subcommands/set_status.py +22 -0
  30. ara_cli/ara_subcommands/set_user.py +22 -0
  31. ara_cli/ara_subcommands/template.py +16 -0
  32. ara_cli/artefact_autofix.py +154 -63
  33. ara_cli/artefact_converter.py +256 -0
  34. ara_cli/artefact_models/artefact_model.py +106 -25
  35. ara_cli/artefact_models/artefact_templates.py +20 -10
  36. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  37. ara_cli/artefact_models/feature_artefact_model.py +31 -1
  38. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  39. ara_cli/artefact_scan.py +2 -2
  40. ara_cli/chat.py +283 -80
  41. ara_cli/chat_agent/__init__.py +0 -0
  42. ara_cli/chat_agent/agent_process_manager.py +155 -0
  43. ara_cli/chat_script_runner/__init__.py +0 -0
  44. ara_cli/chat_script_runner/script_completer.py +23 -0
  45. ara_cli/chat_script_runner/script_finder.py +41 -0
  46. ara_cli/chat_script_runner/script_lister.py +36 -0
  47. ara_cli/chat_script_runner/script_runner.py +36 -0
  48. ara_cli/chat_web_search/__init__.py +0 -0
  49. ara_cli/chat_web_search/web_search.py +263 -0
  50. ara_cli/commands/agent_run_command.py +98 -0
  51. ara_cli/commands/fetch_agents_command.py +106 -0
  52. ara_cli/commands/fetch_scripts_command.py +43 -0
  53. ara_cli/commands/fetch_templates_command.py +39 -0
  54. ara_cli/commands/fetch_templates_commands.py +39 -0
  55. ara_cli/commands/list_agents_command.py +39 -0
  56. ara_cli/commands/read_command.py +17 -4
  57. ara_cli/completers.py +180 -0
  58. ara_cli/constants.py +2 -0
  59. ara_cli/directory_navigator.py +37 -4
  60. ara_cli/file_loaders/text_file_loader.py +2 -2
  61. ara_cli/global_file_lister.py +5 -15
  62. ara_cli/llm_utils.py +58 -0
  63. ara_cli/prompt_chat.py +20 -4
  64. ara_cli/prompt_extractor.py +199 -76
  65. ara_cli/prompt_handler.py +160 -59
  66. ara_cli/tag_extractor.py +38 -18
  67. ara_cli/template_loader.py +3 -2
  68. ara_cli/template_manager.py +52 -21
  69. ara_cli/templates/global-scripts/hello_global.py +1 -0
  70. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  71. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  72. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  73. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  79. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  82. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  83. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  84. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  85. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  86. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  87. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  88. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  89. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  90. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  92. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  93. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  94. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  95. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  96. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  97. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  98. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  99. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  100. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  101. ara_cli/version.py +1 -1
  102. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
  103. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
  104. tests/test_ara_command_action.py +31 -19
  105. tests/test_ara_config.py +177 -90
  106. tests/test_artefact_autofix.py +170 -97
  107. tests/test_artefact_autofix_integration.py +495 -0
  108. tests/test_artefact_converter.py +357 -0
  109. tests/test_artefact_extraction.py +564 -0
  110. tests/test_artefact_scan.py +1 -1
  111. tests/test_chat.py +162 -126
  112. tests/test_chat_givens_images.py +603 -0
  113. tests/test_chat_script_runner.py +454 -0
  114. tests/test_global_file_lister.py +1 -1
  115. tests/test_llm_utils.py +164 -0
  116. tests/test_prompt_chat.py +343 -0
  117. tests/test_prompt_extractor.py +683 -0
  118. tests/test_prompt_handler.py +12 -4
  119. tests/test_tag_extractor.py +19 -13
  120. tests/test_web_search.py +467 -0
  121. ara_cli/ara_command_parser.py +0 -605
  122. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  123. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  124. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  125. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  126. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  127. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  128. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  129. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  130. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  131. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  132. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  133. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  134. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  135. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  136. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  137. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  138. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
  139. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
  140. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
ara_cli/ara_config.py CHANGED
@@ -1,9 +1,9 @@
1
- from typing import List, Dict, Optional, Any
1
+ from typing import List, Dict, Optional
2
2
  from pydantic import BaseModel, ValidationError, Field, model_validator
3
3
  import json
4
4
  import os
5
- from os.path import exists, dirname
6
5
  from os import makedirs
6
+ from os.path import exists, dirname
7
7
  from functools import lru_cache
8
8
  import sys
9
9
  import warnings
@@ -14,21 +14,92 @@ DEFAULT_CONFIG_LOCATION = "./ara/.araconfig/ara_config.json"
14
14
  class LLMConfigItem(BaseModel):
15
15
  provider: str
16
16
  model: str
17
- temperature: float = Field(ge=0.0, le=1.0)
17
+ temperature: Optional[float] = Field(ge=0.0, le=2.0)
18
18
  max_tokens: Optional[int] = None
19
19
  max_completion_tokens: Optional[int] = None
20
20
 
21
21
 
22
+ def get_default_llm_config() -> Dict[str, "LLMConfigItem"]:
23
+ """Returns the default LLM configuration."""
24
+ return {
25
+ "gpt-5.2": LLMConfigItem(
26
+ provider="openai",
27
+ model="openai/gpt-5.2",
28
+ temperature=1,
29
+ max_completion_tokens=16000,
30
+ ),
31
+ "gpt-5-mini": LLMConfigItem(
32
+ provider="openai", model="openai/gpt-5-mini", temperature=1
33
+ ),
34
+ "gpt-5-web": LLMConfigItem(
35
+ provider="openai",
36
+ model="openai/gpt-5-search-api",
37
+ temperature=1,
38
+ max_completion_tokens=16000,
39
+ ),
40
+ "gpt-4o": LLMConfigItem(
41
+ provider="openai",
42
+ model="openai/gpt-4o",
43
+ temperature=0.8,
44
+ max_tokens=16000,
45
+ ),
46
+ "gpt-4o-search-preview": LLMConfigItem(
47
+ provider="openai",
48
+ model="openai/gpt-4o-search-preview",
49
+ temperature=None,
50
+ max_tokens=None,
51
+ max_completion_tokens=None,
52
+ ),
53
+ "opus-4.5-advanced": LLMConfigItem(
54
+ provider="anthropic",
55
+ model="anthropic/claude-opus-4-5-20251101",
56
+ temperature=0.5,
57
+ max_tokens=32000,
58
+ ),
59
+ "opus-4.1-exceptional": LLMConfigItem(
60
+ provider="anthropic",
61
+ model="anthropic/claude-opus-4-1-20250805",
62
+ temperature=0.5,
63
+ max_tokens=32000,
64
+ ),
65
+ "sonnet-4.5-coding": LLMConfigItem(
66
+ provider="anthropic",
67
+ model="anthropic/claude-sonnet-4-5-20250929",
68
+ temperature=0.5,
69
+ max_tokens=32000,
70
+ ),
71
+ "haiku-4-5": LLMConfigItem(
72
+ provider="anthropic",
73
+ model="anthropic/claude-haiku-4-5-20251001",
74
+ temperature=0.8,
75
+ max_tokens=32000,
76
+ ),
77
+ "together-ai-llama-2": LLMConfigItem(
78
+ provider="together_ai",
79
+ model="together_ai/togethercomputer/llama-2-70b",
80
+ temperature=0.8,
81
+ max_tokens=4000,
82
+ ),
83
+ "groq-llama-3": LLMConfigItem(
84
+ provider="groq",
85
+ model="groq/llama3-70b-8192",
86
+ temperature=0.8,
87
+ max_tokens=4000,
88
+ ),
89
+ }
90
+
91
+
22
92
  class ARAconfig(BaseModel):
23
- ext_code_dirs: List[Dict[str, str]] = Field(default_factory=lambda: [
24
- {"source_dir": "./src"},
25
- {"source_dir": "./tests"}
26
- ])
93
+ ext_code_dirs: List[Dict[str, str]] = Field(
94
+ default_factory=lambda: [{"source_dir": "./src"}, {"source_dir": "./tests"}]
95
+ )
27
96
  global_dirs: Optional[List[Dict[str, str]]] = Field(default=[])
28
97
  glossary_dir: str = "./glossary"
29
98
  doc_dir: str = "./docs"
30
99
  local_prompt_templates_dir: str = "./ara/.araconfig"
31
100
  custom_prompt_templates_subdir: Optional[str] = "custom-prompt-modules"
101
+ local_scripts_dir: str = "./ara/.araconfig"
102
+ custom_scripts_subdir: Optional[str] = "custom-scripts"
32
103
  local_ara_templates_dir: str = "./ara/.araconfig/templates/"
33
104
  ara_prompt_given_list_includes: List[str] = Field(
34
105
  default_factory=lambda: [
@@ -48,63 +119,26 @@ class ARAconfig(BaseModel):
48
119
  "*.jpeg",
49
120
  ]
50
121
  )
51
- llm_config: Dict[str, LLMConfigItem] = Field(
52
- default_factory=lambda: {
53
- "gpt-5": LLMConfigItem(
54
- provider="openai",
55
- model="openai/gpt-5",
56
- temperature=1,
57
- max_completion_tokens=16000,
58
- ),
59
- "gpt-5-mini": LLMConfigItem(
60
- provider="openai", model="openai/gpt-5-mini-2025-08-07", temperature=1
61
- ),
62
- "gpt-4o": LLMConfigItem(
63
- provider="openai",
64
- model="openai/gpt-4o",
65
- temperature=0.8,
66
- max_tokens=16000,
67
- ),
68
- "gpt-4.1": LLMConfigItem(
69
- provider="openai",
70
- model="openai/gpt-4.1",
71
- temperature=0.8,
72
- max_tokens=16000,
73
- ),
74
- "o3-mini": LLMConfigItem(
75
- provider="openai",
76
- model="openai/o3-mini",
77
- temperature=1.0,
78
- max_tokens=8000,
79
- ),
80
- "opus-4": LLMConfigItem(
81
- provider="anthropic",
82
- model="anthropic/claude-opus-4-20250514",
83
- temperature=0.5,
84
- max_tokens=32000,
85
- ),
86
- "sonnet-4": LLMConfigItem(
87
- provider="anthropic",
88
- model="anthropic/claude-sonnet-4-20250514",
89
- temperature=0.5,
90
- max_tokens=32000,
91
- ),
92
- "together-ai-llama-2": LLMConfigItem(
93
- provider="together_ai",
94
- model="together_ai/togethercomputer/llama-2-70b",
95
- temperature=0.8,
96
- max_tokens=4000,
97
- ),
98
- "groq-llama-3": LLMConfigItem(
99
- provider="groq",
100
- model="groq/llama3-70b-8192",
101
- temperature=0.8,
102
- max_tokens=4000,
103
- ),
104
- }
105
- )
122
+ # llm_config defaults to the standard set of models
123
+ llm_config: Dict[str, LLMConfigItem] = Field(default_factory=get_default_llm_config)
106
124
  default_llm: Optional[str] = None
107
125
  extraction_llm: Optional[str] = None
126
+ conversion_llm: Optional[str] = None
127
+
128
+ def _validate_llm_field(
129
+ self, field: str, fallback: str, missing_msg: str, invalid_msg: str
130
+ ):
131
+ """Helper to validate and set fallback for LLM fields."""
132
+ value = getattr(self, field)
133
+ if not value:
134
+ print(f"Warning: '{field}' is not set. {missing_msg}: '{fallback}'.")
135
+ setattr(self, field, fallback)
136
+ elif value not in self.llm_config:
137
+ print(
138
+ f"Warning: The configured '{field}' ('{value}') does not exist in 'llm_config'."
139
+ )
140
+ print(f"-> Reverting to {invalid_msg}: '{fallback}'.")
141
+ setattr(self, field, fallback)
108
142
 
109
143
  @model_validator(mode="after")
110
144
  def check_critical_fields(self) -> "ARAconfig":
@@ -113,12 +147,12 @@ class ARAconfig(BaseModel):
113
147
  "ext_code_dirs": [{"source_dir": "./src"}, {"source_dir": "./tests"}],
114
148
  "local_ara_templates_dir": "./ara/.araconfig/templates/",
115
149
  "local_prompt_templates_dir": "./ara/.araconfig",
150
+ "local_scripts_dir": "./ara/.araconfig",
116
151
  "glossary_dir": "./glossary",
117
152
  }
118
153
 
119
154
  for field, default_value in critical_fields.items():
120
- current_value = getattr(self, field)
121
- if not current_value:
155
+ if not getattr(self, field):
122
156
  print(
123
157
  f"Warning: Value for '{field}' is missing or empty. Using default."
124
158
  )
@@ -132,33 +166,25 @@ class ARAconfig(BaseModel):
132
166
  self.extraction_llm = None
133
167
  return self
134
168
 
135
- first_available_llm = next(iter(self.llm_config))
169
+ first_available = next(iter(self.llm_config))
170
+ self._validate_llm_field(
171
+ "default_llm",
172
+ first_available,
173
+ "Defaulting to the first available model",
174
+ "the first available model",
175
+ )
136
176
 
137
- if not self.default_llm:
138
- print(
139
- f"Warning: 'default_llm' is not set. Defaulting to the first available model: '{first_available_llm}'."
140
- )
141
- self.default_llm = first_available_llm
142
- elif self.default_llm not in self.llm_config:
143
- print(
144
- f"Warning: The configured 'default_llm' ('{self.default_llm}') does not exist in 'llm_config'."
145
- )
146
- print(
147
- f"-> Reverting to the first available model: '{first_available_llm}'."
148
- )
149
- self.default_llm = first_available_llm
177
+ # Now used as fallback for others
178
+ fallback_val = self.default_llm
179
+ fallback_missing_msg = "Setting it to the same as 'default_llm'"
180
+ fallback_invalid_msg = "the 'default_llm' value"
150
181
 
151
- if not self.extraction_llm:
152
- print(
153
- f"Warning: 'extraction_llm' is not set. Setting it to the same as 'default_llm': '{self.default_llm}'."
154
- )
155
- self.extraction_llm = self.default_llm
156
- elif self.extraction_llm not in self.llm_config:
157
- print(
158
- f"Warning: The configured 'extraction_llm' ('{self.extraction_llm}') does not exist in 'llm_config'."
159
- )
160
- print(f"-> Reverting to the 'default_llm' value: '{self.default_llm}'.")
161
- self.extraction_llm = self.default_llm
182
+ self._validate_llm_field(
183
+ "extraction_llm", fallback_val, fallback_missing_msg, fallback_invalid_msg
184
+ )
185
+ self._validate_llm_field(
186
+ "conversion_llm", fallback_val, fallback_missing_msg, fallback_invalid_msg
187
+ )
162
188
 
163
189
  return self
164
190
 
@@ -186,71 +212,101 @@ def handle_unrecognized_keys(data: dict) -> dict:
186
212
 
187
213
 
188
214
  # Function to read the JSON file and return an ARAconfig model
189
- @lru_cache(maxsize=1)
190
- def read_data(filepath: str) -> ARAconfig:
191
- """
192
- Reads, validates, and repairs the configuration file.
193
- If the file doesn't exist, it creates a default one.
194
- If the file is invalid, it corrects only the broken parts.
195
- """
215
+
216
+
217
+ def _create_default_config(filepath: str) -> ARAconfig:
218
+ """Create and save a default configuration."""
219
+ print(f"Configuration file not found. Creating a default one at '{filepath}'.")
220
+ default_config = ARAconfig(llm_config=get_default_llm_config())
221
+ save_data(filepath, default_config)
222
+ print("Please review the default configuration and re-run your command.")
223
+ sys.exit(0)
224
+
225
+
226
+ def _load_json_config(filepath: str) -> dict:
227
+ """Load and parse JSON configuration file."""
196
228
 
197
229
  def warn_on_duplicate_llm_dict_key(ordered_pairs):
198
230
  """Reject duplicate keys."""
199
231
  d = {}
200
232
  for k, v in ordered_pairs:
201
233
  if k in d:
202
- warnings.warn(f"Duplicate LLM configuration identifier '{k}'. The previous entry will be removed.", UserWarning)
234
+ warnings.warn(
235
+ f"Duplicate LLM configuration identifier '{k}'. The previous entry will be removed.",
236
+ UserWarning,
237
+ )
203
238
  d[k] = v
204
239
  return d
205
240
 
241
+ with open(filepath, "r", encoding="utf-8") as file:
242
+ content = file.read()
243
+ return json.loads(content, object_pairs_hook=warn_on_duplicate_llm_dict_key)
244
+
245
+
246
+ def _correct_validation_errors(data: dict, errors: list) -> ARAconfig:
247
+ """Correct validation errors and return a valid config."""
248
+ print("--- Configuration Error Detected ---")
249
+ print(
250
+ "Some settings in your configuration file are invalid. Attempting to fix them."
251
+ )
252
+
253
+ corrected_data = data.copy()
254
+ defaults = ARAconfig(llm_config=get_default_llm_config()).model_dump()
255
+ error_fields = {err["loc"][0] for err in errors if err["loc"]}
256
+
257
+ for field_name in error_fields:
258
+ print(
259
+ f"-> Field '{field_name}' is invalid and will be reverted to its default value."
260
+ )
261
+ if field_name in corrected_data:
262
+ corrected_data[field_name] = defaults.get(field_name)
263
+
264
+ print("--- End of Error Report ---")
265
+ return ARAconfig(**corrected_data)
266
+
267
+
268
+ @lru_cache(maxsize=1)
269
+ def read_data(filepath: str) -> ARAconfig:
270
+ """
271
+ Reads, validates, and repairs the configuration file.
272
+ If the file doesn't exist, it creates a default one.
273
+ If the file is invalid, it corrects only the broken parts.
274
+ """
206
275
  ensure_directory_exists(dirname(filepath))
207
276
 
208
277
  if not exists(filepath):
209
- print(f"Configuration file not found. Creating a default one at '{filepath}'.")
210
- default_config = ARAconfig()
211
- save_data(filepath, default_config)
212
- print("Please review the default configuration and re-run your command.")
213
- sys.exit(0)
278
+ return _create_default_config(filepath)
214
279
 
215
280
  try:
216
- with open(filepath, "r", encoding="utf-8") as file:
217
- content = file.read()
218
- data = json.loads(content, object_pairs_hook=warn_on_duplicate_llm_dict_key)
281
+ data = _load_json_config(filepath)
219
282
  except json.JSONDecodeError as e:
220
283
  print(f"Error: Invalid JSON in configuration file: {e}")
221
284
  print("Creating a new configuration with defaults...")
222
- default_config = ARAconfig()
285
+ default_config = ARAconfig(llm_config=get_default_llm_config())
223
286
  save_data(filepath, default_config)
224
287
  return default_config
225
288
 
226
289
  data = handle_unrecognized_keys(data)
227
290
 
291
+ needs_save = False
292
+ if "llm_config" not in data or not data.get("llm_config"):
293
+ print(
294
+ "Info: 'llm_config' is missing or empty. Populating with default LLM configurations."
295
+ )
296
+ data["llm_config"] = {
297
+ k: v.model_dump() for k, v in get_default_llm_config().items()
298
+ }
299
+ needs_save = True
300
+
228
301
  try:
229
302
  config = ARAconfig(**data)
230
- save_data(filepath, config)
303
+ if needs_save:
304
+ save_data(filepath, config)
231
305
  return config
232
306
  except ValidationError as e:
233
- print("--- Configuration Error Detected ---")
234
- print(
235
- "Some settings in your configuration file are invalid. Attempting to fix them."
236
- )
237
-
238
- corrected_data = data.copy()
239
- defaults = ARAconfig().model_dump()
240
-
241
- error_fields = {err["loc"][0] for err in e.errors() if err["loc"]}
242
-
243
- for field_name in error_fields:
244
- print(f"-> Field '{field_name}' is invalid and will be reverted to its default value.")
245
- if field_name in corrected_data:
246
- corrected_data[field_name] = defaults.get(field_name)
247
-
248
- print("--- End of Error Report ---")
249
-
250
- final_config = ARAconfig(**corrected_data)
307
+ final_config = _correct_validation_errors(data, e.errors())
251
308
  save_data(filepath, final_config)
252
309
  print(f"Configuration has been corrected and saved to '{filepath}'.")
253
-
254
310
  return final_config
255
311
 
256
312
 
@@ -266,9 +322,12 @@ class ConfigManager:
266
322
  _config_instance = None
267
323
 
268
324
  @classmethod
269
- def get_config(cls, filepath=DEFAULT_CONFIG_LOCATION) -> ARAconfig:
325
+ def get_config(cls, filepath=None) -> ARAconfig:
326
+ if filepath:
327
+ return read_data(filepath)
328
+
270
329
  if cls._config_instance is None:
271
- cls._config_instance = read_data(filepath)
330
+ cls._config_instance = read_data(DEFAULT_CONFIG_LOCATION)
272
331
  return cls._config_instance
273
332
 
274
333
  @classmethod
File without changes
@@ -0,0 +1,26 @@
1
+ import typer
2
+ from .common import MockArgs
3
+ from ara_cli.ara_command_action import autofix_action
4
+
5
+
6
+ def autofix_main(
7
+ single_pass: bool = typer.Option(False, "--single-pass", help="Run the autofix once for every scanned file"),
8
+ deterministic: bool = typer.Option(False, "-d", "--deterministic", help="Run only deterministic fixes e.g Title-FileName Mismatch fix"),
9
+ non_deterministic: bool = typer.Option(False, "-nd", "--non-deterministic", help="Run only non-deterministic fixes")
10
+ ):
11
+ """Fix ARA tree with llm models for scanned artefacts with ara scan command."""
12
+ if deterministic and non_deterministic:
13
+ typer.echo("Error: --deterministic and --non-deterministic are mutually exclusive", err=True)
14
+ raise typer.Exit(1)
15
+
16
+ args = MockArgs(
17
+ single_pass=single_pass,
18
+ deterministic=deterministic,
19
+ non_deterministic=non_deterministic
20
+ )
21
+ autofix_action(args)
22
+
23
+
24
+ def register(parent: typer.Typer):
25
+ help_text = "Fix ARA tree with llm models for scanned artefacts"
26
+ parent.command(name="autofix", help=help_text)(autofix_main)
@@ -0,0 +1,27 @@
1
+ import typer
2
+ from typing import Optional, List
3
+ from .common import MockArgs, ChatNameArgument
4
+ from ara_cli.ara_command_action import chat_action
5
+
6
+
7
+ def chat_main(
8
+ chat_name: Optional[str] = ChatNameArgument("Optional name for a specific chat. Pass the .md file to continue an existing chat", None),
9
+ reset: Optional[bool] = typer.Option(None, "-r", "--reset/--no-reset", help="Reset the chat file if it exists"),
10
+ output_mode: bool = typer.Option(False, "--out", help="Output the contents of the chat file instead of entering interactive chat mode"),
11
+ append: Optional[List[str]] = typer.Option(None, "--append", help="Append strings to the chat file"),
12
+ restricted: Optional[bool] = typer.Option(None, "--restricted/--no-restricted", help="Start with a limited set of commands")
13
+ ):
14
+ """Command line chatbot. Chat control with SEND/s | RERUN/r | QUIT/q"""
15
+ args = MockArgs(
16
+ chat_name=chat_name,
17
+ reset=reset,
18
+ output_mode=output_mode,
19
+ append=append,
20
+ restricted=restricted
21
+ )
22
+ chat_action(args)
23
+
24
+
25
+ def register(parent: typer.Typer):
26
+ help_text = "Command line chatbot. Chat control with SEND/s | RERUN/r | QUIT/q"
27
+ parent.command(name="chat", help=help_text)(chat_main)
@@ -0,0 +1,16 @@
1
+ import typer
2
+ from .common import ClassifierEnum, MockArgs, ClassifierArgument
3
+ from ara_cli.ara_command_action import classifier_directory_action
4
+
5
+
6
+ def classifier_directory_main(
7
+ classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type")
8
+ ):
9
+ """Print the ara subdirectory for an artefact classifier."""
10
+ args = MockArgs(classifier=classifier.value)
11
+ classifier_directory_action(args)
12
+
13
+
14
+ def register(parent: typer.Typer):
15
+ help_text = "Print the ara subdirectory for an artefact classifier"
16
+ parent.command(name="classifier-directory", help=help_text)(classifier_directory_main)
@@ -0,0 +1,100 @@
1
+ from typing import Optional
2
+ from enum import Enum
3
+ import typer
4
+ from ara_cli.classifier import Classifier
5
+ from ara_cli.constants import VALID_ASPECTS
6
+ from ara_cli.completers import DynamicCompleters
7
+
8
+
9
+ # Get classifiers and aspects
10
+ classifiers = Classifier.ordered_classifiers()
11
+ aspects = VALID_ASPECTS
12
+
13
+
14
+ # Create enums for better type safety
15
+ ClassifierEnum = Enum('ClassifierEnum', {c: c for c in classifiers})
16
+ AspectEnum = Enum('AspectEnum', {a: a for a in aspects})
17
+ TemplateTypeEnum = Enum('TemplateTypeEnum', {
18
+ 'rules': 'rules',
19
+ 'intention': 'intention',
20
+ 'commands': 'commands',
21
+ 'blueprint': 'blueprint'
22
+ })
23
+
24
+
25
+ # Create typed arguments and options with autocompletion
26
+ def ClassifierArgument(help_text: str, default=...):
27
+ """Create a classifier argument with autocompletion."""
28
+ return typer.Argument(
29
+ default,
30
+ help=help_text,
31
+ autocompletion=DynamicCompleters.create_classifier_completer()
32
+ )
33
+
34
+
35
+ def ClassifierOption(help_text: str, *names):
36
+ """Create a classifier option with autocompletion."""
37
+ return typer.Option(
38
+ None,
39
+ *names,
40
+ help=help_text,
41
+ autocompletion=DynamicCompleters.create_classifier_completer()
42
+ )
43
+
44
+
45
+ def ArtefactNameArgument(help_text: str, default=...):
46
+ """Create an artefact name argument with autocompletion."""
47
+ return typer.Argument(
48
+ default,
49
+ help=help_text,
50
+ autocompletion=DynamicCompleters.create_artefact_name_completer()
51
+ )
52
+
53
+
54
+
55
+ def ParentNameArgument(help_text: str):
56
+ """Create a parent name argument with autocompletion."""
57
+ return typer.Argument(
58
+ help=help_text,
59
+ autocompletion=DynamicCompleters.create_parent_name_completer()
60
+ )
61
+
62
+
63
+ def AspectArgument(help_text: str):
64
+ """Create an aspect argument with autocompletion."""
65
+ return typer.Argument(
66
+ help=help_text,
67
+ autocompletion=DynamicCompleters.create_aspect_completer()
68
+ )
69
+
70
+
71
+ def StatusArgument(help_text: str):
72
+ """Create a status argument with autocompletion."""
73
+ return typer.Argument(
74
+ help=help_text,
75
+ autocompletion=DynamicCompleters.create_status_completer()
76
+ )
77
+
78
+
79
+ def TemplateTypeArgument(help_text: str):
80
+ """Create a template type argument with autocompletion."""
81
+ return typer.Argument(
82
+ help=help_text,
83
+ autocompletion=DynamicCompleters.create_template_type_completer()
84
+ )
85
+
86
+
87
+ def ChatNameArgument(help_text: str, default=None):
88
+ """Create a chat name argument with autocompletion."""
89
+ return typer.Argument(
90
+ default,
91
+ help=help_text,
92
+ autocompletion=DynamicCompleters.create_chat_file_completer()
93
+ )
94
+
95
+
96
+ # Mock args class to maintain compatibility with existing action functions
97
+ class MockArgs:
98
+ def __init__(self, **kwargs):
99
+ for key, value in kwargs.items():
100
+ setattr(self, key, value)