ara-cli 0.1.9.77__py3-none-any.whl → 0.1.10.8__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.

Potentially problematic release.


This version of ara-cli might be problematic. Click here for more details.

Files changed (122) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +245 -66
  3. ara_cli/ara_command_action.py +128 -63
  4. ara_cli/ara_config.py +201 -177
  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/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +214 -28
  28. ara_cli/artefact_creator.py +5 -8
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +13 -6
  31. ara_cli/artefact_lister.py +29 -55
  32. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  33. ara_cli/artefact_models/artefact_model.py +106 -25
  34. ara_cli/artefact_models/artefact_templates.py +23 -13
  35. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  36. ara_cli/artefact_models/feature_artefact_model.py +56 -1
  37. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  38. ara_cli/artefact_reader.py +4 -5
  39. ara_cli/artefact_renamer.py +6 -2
  40. ara_cli/artefact_scan.py +2 -2
  41. ara_cli/chat.py +594 -219
  42. ara_cli/chat_agent/__init__.py +0 -0
  43. ara_cli/chat_agent/agent_communicator.py +62 -0
  44. ara_cli/chat_agent/agent_process_manager.py +211 -0
  45. ara_cli/chat_agent/agent_status_manager.py +73 -0
  46. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  47. ara_cli/commands/__init__.py +0 -0
  48. ara_cli/commands/command.py +7 -0
  49. ara_cli/commands/extract_command.py +15 -0
  50. ara_cli/commands/load_command.py +65 -0
  51. ara_cli/commands/load_image_command.py +34 -0
  52. ara_cli/commands/read_command.py +117 -0
  53. ara_cli/completers.py +144 -0
  54. ara_cli/directory_navigator.py +37 -4
  55. ara_cli/error_handler.py +134 -0
  56. ara_cli/file_classifier.py +3 -2
  57. ara_cli/file_loaders/__init__.py +0 -0
  58. ara_cli/file_loaders/binary_file_loader.py +33 -0
  59. ara_cli/file_loaders/document_file_loader.py +34 -0
  60. ara_cli/file_loaders/document_reader.py +245 -0
  61. ara_cli/file_loaders/document_readers.py +233 -0
  62. ara_cli/file_loaders/file_loader.py +50 -0
  63. ara_cli/file_loaders/file_loaders.py +123 -0
  64. ara_cli/file_loaders/image_processor.py +89 -0
  65. ara_cli/file_loaders/markdown_reader.py +75 -0
  66. ara_cli/file_loaders/text_file_loader.py +187 -0
  67. ara_cli/global_file_lister.py +51 -0
  68. ara_cli/prompt_extractor.py +214 -87
  69. ara_cli/prompt_handler.py +508 -146
  70. ara_cli/tag_extractor.py +54 -24
  71. ara_cli/template_loader.py +245 -0
  72. ara_cli/template_manager.py +14 -4
  73. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  74. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  75. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  76. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  77. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  78. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  79. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  80. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  81. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  82. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  83. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  84. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  85. ara_cli/update_config_prompt.py +7 -1
  86. ara_cli/version.py +1 -1
  87. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  88. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/RECORD +104 -59
  89. tests/test_ara_command_action.py +66 -52
  90. tests/test_ara_config.py +200 -279
  91. tests/test_artefact_autofix.py +361 -5
  92. tests/test_artefact_lister.py +52 -132
  93. tests/test_artefact_scan.py +1 -1
  94. tests/test_chat.py +2009 -603
  95. tests/test_file_classifier.py +23 -0
  96. tests/test_file_creator.py +3 -5
  97. tests/test_global_file_lister.py +131 -0
  98. tests/test_prompt_handler.py +746 -0
  99. tests/test_tag_extractor.py +19 -13
  100. tests/test_template_loader.py +192 -0
  101. tests/test_template_manager.py +5 -4
  102. ara_cli/ara_command_parser.py +0 -536
  103. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  104. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  105. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  106. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  107. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  108. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  109. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  110. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  111. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  112. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  113. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  114. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  115. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  116. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  117. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  118. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  119. ara_cli-0.1.9.77.dist-info/METADATA +0 -18
  120. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  121. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  122. {ara_cli-0.1.9.77.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
ara_cli/ara_config.py CHANGED
@@ -1,254 +1,278 @@
1
1
  from typing import List, Dict, Optional, Any
2
- from pydantic import BaseModel, ValidationError, Field, field_validator, model_validator
2
+ from pydantic import BaseModel, ValidationError, Field, model_validator
3
3
  import json
4
4
  import os
5
5
  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
 
13
+
12
14
  class LLMConfigItem(BaseModel):
13
15
  provider: str
14
16
  model: str
15
17
  temperature: float = Field(ge=0.0, le=1.0)
16
18
  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
19
+ max_completion_tokens: Optional[int] = None
26
20
 
27
- class ExtCodeDirItem(BaseModel):
28
- source_dir: str
29
21
 
30
22
  class ARAconfig(BaseModel):
31
- ext_code_dirs: List[ExtCodeDirItem] = Field(default_factory=lambda: [
32
- ExtCodeDirItem(source_dir="./src"),
33
- ExtCodeDirItem(source_dir="./tests")
23
+ ext_code_dirs: List[Dict[str, str]] = Field(default_factory=lambda: [
24
+ {"source_dir": "./src"},
25
+ {"source_dir": "./tests"}
34
26
  ])
27
+ global_dirs: Optional[List[Dict[str, str]]] = Field(default=[])
35
28
  glossary_dir: str = "./glossary"
36
29
  doc_dir: str = "./docs"
37
30
  local_prompt_templates_dir: str = "./ara/.araconfig"
38
31
  custom_prompt_templates_subdir: Optional[str] = "custom-prompt-modules"
39
32
  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"""
33
+ ara_prompt_given_list_includes: List[str] = Field(
34
+ default_factory=lambda: [
35
+ "*.businessgoal",
36
+ "*.vision",
37
+ "*.capability",
38
+ "*.keyfeature",
39
+ "*.epic",
40
+ "*.userstory",
41
+ "*.example",
42
+ "*.feature",
43
+ "*.task",
44
+ "*.py",
45
+ "*.md",
46
+ "*.png",
47
+ "*.jpg",
48
+ "*.jpeg",
49
+ ]
50
+ )
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
+ )
106
+ default_llm: Optional[str] = None
107
+ extraction_llm: Optional[str] = None
108
+
109
+ @model_validator(mode="after")
110
+ def check_critical_fields(self) -> "ARAconfig":
111
+ """Check for empty critical fields and validate default_llm and extraction_llm."""
109
112
  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"
113
+ "ext_code_dirs": [{"source_dir": "./src"}, {"source_dir": "./tests"}],
114
+ "local_ara_templates_dir": "./ara/.araconfig/templates/",
115
+ "local_prompt_templates_dir": "./ara/.araconfig",
116
+ "glossary_dir": "./glossary",
114
117
  }
115
-
118
+
116
119
  for field, default_value in critical_fields.items():
117
120
  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.")
121
+ if not current_value:
122
+ print(
123
+ f"Warning: Value for '{field}' is missing or empty. Using default."
124
+ )
122
125
  setattr(self, field, default_value)
123
-
126
+
127
+ if not self.llm_config:
128
+ print(
129
+ "Warning: 'llm_config' is empty. 'default_llm' and 'extraction_llm' cannot be set."
130
+ )
131
+ self.default_llm = None
132
+ self.extraction_llm = None
133
+ return self
134
+
135
+ first_available_llm = next(iter(self.llm_config))
136
+
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
150
+
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
162
+
124
163
  return self
125
164
 
165
+
126
166
  # Function to ensure the necessary directories exist
127
167
  @lru_cache(maxsize=None)
128
168
  def ensure_directory_exists(directory: str):
169
+ """Creates a directory if it doesn't exist."""
129
170
  if not exists(directory):
130
171
  os.makedirs(directory)
131
172
  print(f"New directory created at {directory}")
132
173
  return directory
133
174
 
134
- def handle_unrecognized_keys(data: dict, known_fields: set) -> dict:
135
- """Remove unrecognized keys and warn the user"""
175
+
176
+ def handle_unrecognized_keys(data: dict) -> dict:
177
+ """Removes unrecognized keys from the data and warns the user."""
178
+ known_fields = set(ARAconfig.model_fields.keys())
136
179
  cleaned_data = {}
137
180
  for key, value in data.items():
138
181
  if key not in known_fields:
139
- print(f"Warning: {key} is not recognized as a valid configuration option.")
182
+ print(f"Warning: Unrecognized configuration key '{key}' will be ignored.")
140
183
  else:
141
184
  cleaned_data[key] = value
142
185
  return cleaned_data
143
186
 
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
187
 
179
188
  # Function to read the JSON file and return an ARAconfig model
180
189
  @lru_cache(maxsize=1)
181
190
  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)
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
+ """
196
+
197
+ def warn_on_duplicate_llm_dict_key(ordered_pairs):
198
+ """Reject duplicate keys."""
199
+ d = {}
200
+ for k, v in ordered_pairs:
201
+ if k in d:
202
+ warnings.warn(f"Duplicate LLM configuration identifier '{k}'. The previous entry will be removed.", UserWarning)
203
+ d[k] = v
204
+ return d
205
+
206
+ ensure_directory_exists(dirname(filepath))
185
207
 
186
208
  if not exists(filepath):
187
- # If the file does not exist, create it with default values
209
+ print(f"Configuration file not found. Creating a default one at '{filepath}'.")
188
210
  default_config = ARAconfig()
189
211
  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
212
+ print("Please review the default configuration and re-run your command.")
213
+ sys.exit(0)
214
+
215
+ 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)
219
+ except json.JSONDecodeError as e:
220
+ print(f"Error: Invalid JSON in configuration file: {e}")
221
+ print("Creating a new configuration with defaults...")
222
+ default_config = ARAconfig()
223
+ save_data(filepath, default_config)
224
+ return default_config
225
+
226
+ data = handle_unrecognized_keys(data)
195
227
 
196
- # Validate and load the existing configuration
197
- data = validate_and_fix_config_data(filepath)
198
-
199
228
  try:
200
- # Try to create the config with the loaded data
201
229
  config = ARAconfig(**data)
202
-
203
- # Save the potentially fixed configuration back
204
230
  save_data(filepath, config)
205
-
206
231
  return config
207
232
  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
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)
251
+ save_data(filepath, final_config)
252
+ print(f"Configuration has been corrected and saved to '{filepath}'.")
253
+
254
+ return final_config
255
+
229
256
 
230
257
  # Function to save the modified configuration back to the JSON file
231
258
  def save_data(filepath: str, config: ARAconfig):
259
+ """Saves the Pydantic config model to a JSON file."""
232
260
  with open(filepath, "w", encoding="utf-8") as file:
233
261
  json.dump(config.model_dump(), file, indent=4)
234
262
 
263
+
235
264
  # Singleton for configuration management
236
265
  class ConfigManager:
237
266
  _config_instance = None
238
267
 
239
268
  @classmethod
240
- def get_config(cls, filepath=DEFAULT_CONFIG_LOCATION):
269
+ def get_config(cls, filepath=DEFAULT_CONFIG_LOCATION) -> ARAconfig:
241
270
  if cls._config_instance is None:
242
- config_dir = dirname(filepath)
243
-
244
- if not exists(config_dir):
245
- makedirs(config_dir)
246
-
247
271
  cls._config_instance = read_data(filepath)
248
272
  return cls._config_instance
249
-
273
+
250
274
  @classmethod
251
275
  def reset(cls):
252
- """Reset the configuration instance (useful for testing)"""
276
+ """Reset the configuration instance (useful for testing)."""
253
277
  cls._config_instance = None
254
- read_data.cache_clear()
278
+ read_data.cache_clear()
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.template_manager import SpecificationBreakdownAspects
6
+ from ara_cli.completers import DynamicCompleters
7
+
8
+
9
+ # Get classifiers and aspects
10
+ classifiers = Classifier.ordered_classifiers()
11
+ aspects = SpecificationBreakdownAspects.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)