ara-cli 0.1.13.3__py3-none-any.whl → 0.1.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ara_cli/__init__.py +1 -1
- ara_cli/ara_command_action.py +162 -112
- ara_cli/ara_config.py +1 -1
- ara_cli/ara_subcommands/convert.py +66 -2
- ara_cli/ara_subcommands/prompt.py +266 -106
- ara_cli/artefact_autofix.py +2 -2
- ara_cli/artefact_converter.py +152 -53
- ara_cli/artefact_creator.py +41 -17
- ara_cli/artefact_lister.py +3 -3
- ara_cli/artefact_models/artefact_model.py +1 -1
- ara_cli/artefact_models/artefact_templates.py +0 -9
- ara_cli/artefact_models/feature_artefact_model.py +8 -8
- ara_cli/artefact_reader.py +62 -43
- ara_cli/artefact_scan.py +39 -17
- ara_cli/chat.py +23 -15
- ara_cli/children_contribution_updater.py +737 -0
- ara_cli/classifier.py +34 -0
- ara_cli/commands/load_command.py +4 -3
- ara_cli/commands/load_image_command.py +1 -1
- ara_cli/commands/read_command.py +23 -27
- ara_cli/completers.py +24 -0
- ara_cli/error_handler.py +26 -11
- ara_cli/file_loaders/document_reader.py +0 -178
- ara_cli/file_loaders/factories/__init__.py +0 -0
- ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
- ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
- ara_cli/file_loaders/file_loader.py +1 -30
- ara_cli/file_loaders/loaders/__init__.py +0 -0
- ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
- ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
- ara_cli/file_loaders/readers/__init__.py +0 -0
- ara_cli/file_loaders/readers/docx_reader.py +49 -0
- ara_cli/file_loaders/readers/excel_reader.py +27 -0
- ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
- ara_cli/file_loaders/readers/odt_reader.py +59 -0
- ara_cli/file_loaders/readers/pdf_reader.py +54 -0
- ara_cli/file_loaders/readers/pptx_reader.py +104 -0
- ara_cli/file_loaders/tools/__init__.py +0 -0
- ara_cli/output_suppressor.py +53 -0
- ara_cli/prompt_handler.py +123 -17
- ara_cli/tag_extractor.py +8 -7
- ara_cli/version.py +1 -1
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +18 -12
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/RECORD +58 -45
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
- tests/test_artefact_converter.py +1 -46
- tests/test_artefact_lister.py +11 -8
- tests/test_chat.py +4 -4
- tests/test_chat_givens_images.py +1 -1
- tests/test_children_contribution_updater.py +98 -0
- tests/test_document_loader_office.py +267 -0
- tests/test_prompt_handler.py +416 -214
- tests/test_setup_default_chat_prompt_mode.py +198 -0
- tests/test_tag_extractor.py +95 -49
- ara_cli/file_loaders/document_readers.py +0 -233
- ara_cli/file_loaders/file_loaders.py +0 -123
- ara_cli/file_loaders/text_file_loader.py +0 -187
- /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
- /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
|
@@ -1,136 +1,296 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from typing import Optional, List
|
|
3
|
-
from
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from .common import (
|
|
5
|
+
MockArgs,
|
|
6
|
+
ChatNameArgument,
|
|
7
|
+
)
|
|
4
8
|
from ara_cli.ara_command_action import prompt_action
|
|
9
|
+
from ara_cli.completers import DynamicCompleters
|
|
10
|
+
from ara_cli.classifier import Classifier
|
|
11
|
+
from ara_cli.error_handler import AraError, ErrorLevel, ErrorHandler
|
|
5
12
|
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prompt_action(args)
|
|
14
|
+
# Define PromptStep enum for subcommand validation
|
|
15
|
+
class PromptStep(str, Enum):
|
|
16
|
+
init = "init"
|
|
17
|
+
load = "load"
|
|
18
|
+
send = "send"
|
|
19
|
+
load_and_send = "load-and-send"
|
|
20
|
+
extract = "extract"
|
|
21
|
+
update = "update"
|
|
22
|
+
chat = "chat"
|
|
23
|
+
init_rag = "init-rag"
|
|
18
24
|
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
23
|
-
):
|
|
24
|
-
"""Load selected templates."""
|
|
25
|
-
args = MockArgs(
|
|
26
|
-
classifier=classifier.value,
|
|
27
|
-
parameter=parameter,
|
|
28
|
-
steps="load"
|
|
29
|
-
)
|
|
30
|
-
prompt_action(args)
|
|
26
|
+
# Valid step values for old format detection
|
|
27
|
+
VALID_STEPS = [step.value for step in PromptStep]
|
|
31
28
|
|
|
32
29
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
classifier=classifier.value,
|
|
40
|
-
parameter=parameter,
|
|
41
|
-
steps="send"
|
|
30
|
+
def PromptStepArgument(help_text: str):
|
|
31
|
+
"""Create a prompt step argument with autocompletion."""
|
|
32
|
+
return typer.Argument(
|
|
33
|
+
...,
|
|
34
|
+
help=help_text,
|
|
35
|
+
autocompletion=DynamicCompleters.create_prompt_step_completer(),
|
|
42
36
|
)
|
|
43
|
-
prompt_action(args)
|
|
44
37
|
|
|
45
38
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
def _warn_unused_option(option_name: str, step: str, valid_for: str):
|
|
40
|
+
"""Print warning for unused options."""
|
|
41
|
+
message = (
|
|
42
|
+
f"'{option_name}' option is ignored for '{step}' command. "
|
|
43
|
+
f"It is only valid for '{valid_for}'."
|
|
44
|
+
)
|
|
45
|
+
error_handler = ErrorHandler()
|
|
46
|
+
error_handler.report_error(
|
|
47
|
+
AraError(message, error_code=0, level=ErrorLevel.INFO)
|
|
55
48
|
)
|
|
56
|
-
prompt_action(args)
|
|
57
49
|
|
|
58
50
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
def _warn_deprecated_format(old_cmd: str, new_cmd: str):
|
|
52
|
+
"""Print deprecation warning for old command format."""
|
|
53
|
+
message = (
|
|
54
|
+
f"DEPRECATION: The command format has changed.\n"
|
|
55
|
+
f" Old: {old_cmd}\n"
|
|
56
|
+
f" New: {new_cmd}\n"
|
|
57
|
+
f" Please update your scripts. Old format will be removed in 0.1.14.0 version."
|
|
58
|
+
)
|
|
59
|
+
error_handler = ErrorHandler()
|
|
60
|
+
error_handler.report_error(
|
|
61
|
+
AraError(message, error_code=0, level=ErrorLevel.WARNING)
|
|
70
62
|
)
|
|
71
|
-
prompt_action(args)
|
|
72
63
|
|
|
73
64
|
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
):
|
|
78
|
-
"""Update artefact config prompt files."""
|
|
79
|
-
args = MockArgs(
|
|
80
|
-
classifier=classifier.value,
|
|
81
|
-
parameter=parameter,
|
|
82
|
-
steps="update"
|
|
83
|
-
)
|
|
84
|
-
prompt_action(args)
|
|
65
|
+
def _is_old_format(first_arg: str) -> bool:
|
|
66
|
+
"""Check if the first argument is a step (old format) instead of classifier (new format)."""
|
|
67
|
+
return first_arg in VALID_STEPS
|
|
85
68
|
|
|
86
69
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
70
|
+
def _validate_classifier(classifier: str) -> bool:
|
|
71
|
+
"""Validate that the classifier is valid."""
|
|
72
|
+
return Classifier.is_valid_classifier(classifier)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _validate_step(step: str) -> bool:
|
|
76
|
+
"""Validate that the step is valid."""
|
|
77
|
+
return step in VALID_STEPS
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _handle_old_format(
|
|
81
|
+
step_value: str, classifier: str, artefact_name: Optional[str]
|
|
82
|
+
) -> tuple[str, str, str]:
|
|
83
|
+
"""Handle old format: ara prompt <step> <classifier> <artefact>."""
|
|
84
|
+
if artefact_name is None:
|
|
85
|
+
typer.echo(
|
|
86
|
+
"Error: Missing artefact name. "
|
|
87
|
+
"Usage: ara prompt <step> <classifier> <artefact_name>",
|
|
88
|
+
err=True,
|
|
89
|
+
)
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
if not _validate_classifier(classifier):
|
|
93
|
+
typer.echo(
|
|
94
|
+
f"Error: Invalid classifier '{classifier}'. "
|
|
95
|
+
f"Valid classifiers: {', '.join(Classifier.ordered_classifiers())}",
|
|
96
|
+
err=True,
|
|
97
|
+
)
|
|
98
|
+
raise typer.Exit(1)
|
|
99
|
+
|
|
100
|
+
# Show deprecation warning
|
|
101
|
+
old_cmd = f"ara prompt {step_value} {classifier} {artefact_name}"
|
|
102
|
+
new_cmd = f"ara prompt {classifier} {artefact_name} {step_value}"
|
|
103
|
+
_warn_deprecated_format(old_cmd, new_cmd)
|
|
104
|
+
|
|
105
|
+
return classifier, artefact_name, step_value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _handle_new_format(
|
|
109
|
+
classifier: str, artefact_name: str, step_value: Optional[str]
|
|
110
|
+
) -> tuple[str, str, str]:
|
|
111
|
+
"""Handle new format: ara prompt <classifier> <artefact> <step>."""
|
|
112
|
+
if not _validate_classifier(classifier):
|
|
113
|
+
typer.echo(
|
|
114
|
+
f"Error: Invalid classifier '{classifier}'. "
|
|
115
|
+
f"Valid classifiers: {', '.join(Classifier.ordered_classifiers())}",
|
|
116
|
+
err=True,
|
|
117
|
+
)
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
if step_value is None:
|
|
121
|
+
typer.echo(
|
|
122
|
+
"Error: Missing step. "
|
|
123
|
+
f"Usage: ara prompt <classifier> <artefact_name> <step>\n"
|
|
124
|
+
f"Valid steps: {', '.join(VALID_STEPS)}",
|
|
125
|
+
err=True,
|
|
126
|
+
)
|
|
127
|
+
raise typer.Exit(1)
|
|
128
|
+
|
|
129
|
+
if not _validate_step(step_value):
|
|
130
|
+
typer.echo(
|
|
131
|
+
f"Error: Invalid step '{step_value}'. "
|
|
132
|
+
f"Valid steps: {', '.join(VALID_STEPS)}",
|
|
133
|
+
err=True,
|
|
134
|
+
)
|
|
135
|
+
raise typer.Exit(1)
|
|
136
|
+
|
|
137
|
+
return classifier, artefact_name, step_value
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _validate_step_options(
|
|
141
|
+
step_value: str,
|
|
142
|
+
write: bool,
|
|
143
|
+
reset: Optional[bool],
|
|
144
|
+
output_mode: bool,
|
|
145
|
+
append: Optional[List[str]],
|
|
146
|
+
restricted: Optional[bool],
|
|
147
|
+
chat_name: Optional[str],
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Validate that options are used with correct steps."""
|
|
150
|
+
if step_value != "extract" and write:
|
|
151
|
+
_warn_unused_option("--write", step_value, "extract")
|
|
152
|
+
|
|
153
|
+
if step_value != "chat":
|
|
154
|
+
chat_only_options = [
|
|
155
|
+
(reset is not None, "--reset/--no-reset"),
|
|
156
|
+
(output_mode, "--out"),
|
|
157
|
+
(append is not None, "--append"),
|
|
158
|
+
(restricted is not None, "--restricted/--no-restricted"),
|
|
159
|
+
(chat_name is not None, "chat_name"),
|
|
160
|
+
]
|
|
161
|
+
for is_set, option_name in chat_only_options:
|
|
162
|
+
if is_set:
|
|
163
|
+
_warn_unused_option(option_name, step_value, "chat")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _build_prompt_args(
|
|
167
|
+
classifier: str,
|
|
168
|
+
artefact_name: str,
|
|
169
|
+
step_value: str,
|
|
170
|
+
write: bool,
|
|
171
|
+
chat_name: Optional[str],
|
|
172
|
+
reset: Optional[bool],
|
|
173
|
+
output_mode: bool,
|
|
174
|
+
append: Optional[List[str]],
|
|
175
|
+
restricted: Optional[bool],
|
|
176
|
+
) -> MockArgs:
|
|
177
|
+
"""Build MockArgs for prompt_action."""
|
|
178
|
+
is_extract = step_value == "extract"
|
|
179
|
+
is_chat = step_value == "chat"
|
|
180
|
+
|
|
181
|
+
return MockArgs(
|
|
182
|
+
classifier=classifier,
|
|
183
|
+
parameter=artefact_name,
|
|
184
|
+
steps=step_value,
|
|
185
|
+
write=write if is_extract else False,
|
|
186
|
+
chat_name=chat_name if is_chat else None,
|
|
187
|
+
reset=reset if is_chat else None,
|
|
188
|
+
output_mode=output_mode if is_chat else False,
|
|
189
|
+
append=append if is_chat else None,
|
|
190
|
+
restricted=restricted if is_chat else None,
|
|
106
191
|
)
|
|
107
|
-
prompt_action(args)
|
|
108
192
|
|
|
109
193
|
|
|
110
|
-
def
|
|
111
|
-
classifier:
|
|
112
|
-
|
|
194
|
+
def prompt_main(
|
|
195
|
+
classifier: str = typer.Argument(
|
|
196
|
+
...,
|
|
197
|
+
help="Classifier of the artefact (e.g., feature, task, userstory)",
|
|
198
|
+
autocompletion=DynamicCompleters.create_classifier_completer(),
|
|
199
|
+
),
|
|
200
|
+
artefact_name: str = typer.Argument(
|
|
201
|
+
...,
|
|
202
|
+
help="Name of the artefact",
|
|
203
|
+
autocompletion=DynamicCompleters.create_artefact_name_completer(),
|
|
204
|
+
),
|
|
205
|
+
step: Optional[str] = typer.Argument(
|
|
206
|
+
None,
|
|
207
|
+
help="Action: init, load, send, load-and-send, extract, update, chat, init-rag",
|
|
208
|
+
autocompletion=DynamicCompleters.create_prompt_step_completer(),
|
|
209
|
+
),
|
|
210
|
+
chat_name: Optional[str] = ChatNameArgument(
|
|
211
|
+
"[chat only] Optional name for a specific chat session", None
|
|
212
|
+
),
|
|
213
|
+
# Extract-specific option
|
|
214
|
+
write: bool = typer.Option(
|
|
215
|
+
False,
|
|
216
|
+
"-w",
|
|
217
|
+
"--write",
|
|
218
|
+
help="[extract only] Overwrite existing files without using LLM for merging",
|
|
219
|
+
),
|
|
220
|
+
# Chat-specific options
|
|
221
|
+
reset: Optional[bool] = typer.Option(
|
|
222
|
+
None,
|
|
223
|
+
"-r",
|
|
224
|
+
"--reset/--no-reset",
|
|
225
|
+
help="[chat only] Reset the chat file if it exists",
|
|
226
|
+
),
|
|
227
|
+
output_mode: bool = typer.Option(
|
|
228
|
+
False,
|
|
229
|
+
"--out",
|
|
230
|
+
help="[chat only] Output the contents of the chat file instead of entering interactive chat mode",
|
|
231
|
+
),
|
|
232
|
+
append: Optional[List[str]] = typer.Option(
|
|
233
|
+
None, "--append", help="[chat only] Append strings to the chat file"
|
|
234
|
+
),
|
|
235
|
+
restricted: Optional[bool] = typer.Option(
|
|
236
|
+
None,
|
|
237
|
+
"--restricted/--no-restricted",
|
|
238
|
+
help="[chat only] Start with a limited set of commands",
|
|
239
|
+
),
|
|
113
240
|
):
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
241
|
+
"""
|
|
242
|
+
Prompt interaction mode for artefacts.
|
|
243
|
+
|
|
244
|
+
Usage:
|
|
245
|
+
ara prompt <classifier> <artefact_name> <step> [chat_name] [options]
|
|
246
|
+
|
|
247
|
+
Steps:
|
|
248
|
+
init Initialize prompt templates
|
|
249
|
+
load Load selected templates
|
|
250
|
+
send Send configured prompt to LLM
|
|
251
|
+
load-and-send Load templates and send to LLM
|
|
252
|
+
extract Extract LLM response and save
|
|
253
|
+
update Update artefact config prompt files
|
|
254
|
+
chat Start interactive chat mode
|
|
255
|
+
init-rag Initialize RAG prompt
|
|
256
|
+
|
|
257
|
+
Examples:
|
|
258
|
+
ara prompt feature my_feature init
|
|
259
|
+
ara prompt task my_task chat --reset
|
|
260
|
+
ara prompt userstory my_story extract -w
|
|
261
|
+
ara prompt feature my_feature chat my_session --out
|
|
262
|
+
"""
|
|
263
|
+
# Detect and handle old vs new format (backward compatibility)
|
|
264
|
+
if _is_old_format(classifier):
|
|
265
|
+
classifier_val, artefact_name_val, step_value = _handle_old_format(
|
|
266
|
+
classifier, artefact_name, step
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
classifier_val, artefact_name_val, step_value = _handle_new_format(
|
|
270
|
+
classifier, artefact_name, step
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Validate options match the step
|
|
274
|
+
_validate_step_options(
|
|
275
|
+
step_value, write, reset, output_mode, append, restricted, chat_name
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Build args and execute
|
|
279
|
+
args = _build_prompt_args(
|
|
280
|
+
classifier_val,
|
|
281
|
+
artefact_name_val,
|
|
282
|
+
step_value,
|
|
283
|
+
write,
|
|
284
|
+
chat_name,
|
|
285
|
+
reset,
|
|
286
|
+
output_mode,
|
|
287
|
+
append,
|
|
288
|
+
restricted,
|
|
119
289
|
)
|
|
120
290
|
prompt_action(args)
|
|
121
291
|
|
|
122
292
|
|
|
123
293
|
def register(parent: typer.Typer):
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
add_completion=False # Disable completion on subcommand
|
|
294
|
+
parent.command(name="prompt", help="Prompt interaction mode for artefacts")(
|
|
295
|
+
prompt_main
|
|
127
296
|
)
|
|
128
|
-
prompt_app.command("init")(prompt_init)
|
|
129
|
-
prompt_app.command("load")(prompt_load)
|
|
130
|
-
prompt_app.command("send")(prompt_send)
|
|
131
|
-
prompt_app.command("load-and-send")(prompt_load_and_send)
|
|
132
|
-
prompt_app.command("extract")(prompt_extract)
|
|
133
|
-
prompt_app.command("update")(prompt_update)
|
|
134
|
-
prompt_app.command("chat")(prompt_chat)
|
|
135
|
-
prompt_app.command("init-rag")(prompt_init_rag)
|
|
136
|
-
parent.add_typer(prompt_app, name="prompt")
|
ara_cli/artefact_autofix.py
CHANGED
|
@@ -256,7 +256,7 @@ def _update_rule(
|
|
|
256
256
|
"""Updates the rule in the contribution if a close match is found."""
|
|
257
257
|
rule = artefact.contribution.rule
|
|
258
258
|
|
|
259
|
-
content, artefact_data = ArtefactReader.read_artefact_data(
|
|
259
|
+
content, artefact_data = ArtefactReader().read_artefact_data(
|
|
260
260
|
artefact_name=name,
|
|
261
261
|
classifier=classifier,
|
|
262
262
|
classified_file_info=classified_file_info,
|
|
@@ -365,7 +365,7 @@ def set_closest_contribution(
|
|
|
365
365
|
if not rule:
|
|
366
366
|
return artefact, True
|
|
367
367
|
|
|
368
|
-
content, artefact = ArtefactReader.read_artefact_data(
|
|
368
|
+
content, artefact = ArtefactReader().read_artefact_data(
|
|
369
369
|
artefact_name=name,
|
|
370
370
|
classifier=classifier,
|
|
371
371
|
classified_file_info=classified_file_info,
|
ara_cli/artefact_converter.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
+
from typing import Optional
|
|
3
4
|
from ara_cli.prompt_handler import LLMSingleton
|
|
4
5
|
from langfuse.api.resources.commons.errors import Error as LangfuseError, NotFoundError
|
|
5
6
|
from ara_cli.classifier import Classifier
|
|
@@ -8,6 +9,8 @@ from ara_cli.artefact_creator import ArtefactCreator
|
|
|
8
9
|
from ara_cli.error_handler import AraError
|
|
9
10
|
from ara_cli.directory_navigator import DirectoryNavigator
|
|
10
11
|
from ara_cli.artefact_deleter import ArtefactDeleter
|
|
12
|
+
from ara_cli.children_contribution_updater import ChildrenContributionUpdater
|
|
13
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class AraArtefactConverter:
|
|
@@ -15,6 +18,7 @@ class AraArtefactConverter:
|
|
|
15
18
|
self.file_system = file_system or os
|
|
16
19
|
self.reader = ArtefactReader()
|
|
17
20
|
self.creator = ArtefactCreator(self.file_system)
|
|
21
|
+
self.children_updater = ChildrenContributionUpdater(self.file_system)
|
|
18
22
|
|
|
19
23
|
def convert(
|
|
20
24
|
self,
|
|
@@ -23,61 +27,147 @@ class AraArtefactConverter:
|
|
|
23
27
|
new_classifier: str,
|
|
24
28
|
merge: bool = False,
|
|
25
29
|
override: bool = False,
|
|
30
|
+
force: bool = False,
|
|
31
|
+
children_action: Optional[str] = None,
|
|
32
|
+
new_parent_classifier: Optional[str] = None,
|
|
33
|
+
new_parent_name: Optional[str] = None,
|
|
34
|
+
json_output: bool = False,
|
|
26
35
|
):
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
self._validate_classifiers(old_classifier, new_classifier)
|
|
37
|
+
|
|
38
|
+
content = self._read_and_validate_source(artefact_name, old_classifier)
|
|
39
|
+
|
|
40
|
+
# Handle children contributions BEFORE conversion
|
|
41
|
+
if not self._handle_children_if_needed(
|
|
42
|
+
artefact_name,
|
|
43
|
+
old_classifier,
|
|
44
|
+
new_classifier,
|
|
45
|
+
force,
|
|
46
|
+
children_action,
|
|
47
|
+
new_parent_classifier,
|
|
48
|
+
new_parent_name,
|
|
49
|
+
json_output,
|
|
50
|
+
):
|
|
51
|
+
return # User cancelled
|
|
52
|
+
|
|
53
|
+
target_content_existing = self._resolve_target_content(
|
|
54
|
+
artefact_name, new_classifier, merge, override
|
|
55
|
+
)
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
self._execute_conversion(
|
|
58
|
+
old_classifier,
|
|
59
|
+
new_classifier,
|
|
60
|
+
artefact_name,
|
|
61
|
+
content,
|
|
62
|
+
target_content_existing,
|
|
63
|
+
merge,
|
|
64
|
+
override,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self._cleanup_after_conversion(old_classifier, new_classifier, artefact_name)
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
def _read_and_validate_source(self, artefact_name: str, classifier: str) -> str:
|
|
70
|
+
"""Read source artefact and validate it can be parsed."""
|
|
71
|
+
content, artefact_info = self.reader.read_artefact_data(
|
|
72
|
+
artefact_name, classifier
|
|
73
|
+
)
|
|
74
|
+
if not content or not artefact_info:
|
|
75
|
+
raise AraError(
|
|
76
|
+
f"Artefact '{artefact_name}' of type '{classifier}' not found"
|
|
40
77
|
)
|
|
41
78
|
|
|
42
|
-
|
|
79
|
+
self._validate_artefact_parseable(content, artefact_name, is_source=True)
|
|
80
|
+
return content
|
|
43
81
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
def _validate_artefact_parseable(
|
|
83
|
+
self, content: str, artefact_name: str, is_source: bool = True
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Validate that artefact content can be parsed."""
|
|
86
|
+
artefact_type = "input" if is_source else "target"
|
|
87
|
+
try:
|
|
88
|
+
artefact = artefact_from_content(content)
|
|
89
|
+
if artefact is None:
|
|
90
|
+
raise AraError(
|
|
91
|
+
f'Invalid {artefact_type} artefact: {artefact_name}. Run "ara scan" and "ara autofix" first.'
|
|
92
|
+
)
|
|
93
|
+
except AraError:
|
|
94
|
+
raise
|
|
95
|
+
except Exception:
|
|
96
|
+
raise AraError(
|
|
97
|
+
f'Invalid {artefact_type} artefact: {artefact_name}. Run "ara scan" and "ara autofix" first.'
|
|
51
98
|
)
|
|
52
99
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
100
|
+
def _handle_children_if_needed(
|
|
101
|
+
self,
|
|
102
|
+
artefact_name: str,
|
|
103
|
+
old_classifier: str,
|
|
104
|
+
new_classifier: str,
|
|
105
|
+
force: bool,
|
|
106
|
+
children_action: Optional[str],
|
|
107
|
+
new_parent_classifier: Optional[str],
|
|
108
|
+
new_parent_name: Optional[str],
|
|
109
|
+
json_output: bool,
|
|
110
|
+
) -> bool:
|
|
111
|
+
"""Handle children contributions if classifier is changing. Returns True to continue."""
|
|
112
|
+
if old_classifier == new_classifier:
|
|
113
|
+
return True
|
|
114
|
+
return self.children_updater.update_children_contributions(
|
|
115
|
+
artefact_name,
|
|
116
|
+
old_classifier,
|
|
117
|
+
new_classifier,
|
|
118
|
+
force,
|
|
119
|
+
children_action=children_action,
|
|
120
|
+
new_parent_classifier=new_parent_classifier,
|
|
121
|
+
new_parent_name=new_parent_name,
|
|
122
|
+
json_output=json_output,
|
|
123
|
+
)
|
|
56
124
|
|
|
57
|
-
|
|
58
|
-
|
|
125
|
+
def _execute_conversion(
|
|
126
|
+
self,
|
|
127
|
+
old_classifier: str,
|
|
128
|
+
new_classifier: str,
|
|
129
|
+
artefact_name: str,
|
|
130
|
+
content: str,
|
|
131
|
+
target_content_existing: Optional[str],
|
|
132
|
+
merge: bool,
|
|
133
|
+
override: bool,
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Execute the LLM conversion and write the result."""
|
|
136
|
+
target_class = self._get_target_class(new_classifier)
|
|
137
|
+
|
|
138
|
+
prompt = self._get_prompt(
|
|
139
|
+
old_classifier=old_classifier,
|
|
140
|
+
new_classifier=new_classifier,
|
|
141
|
+
artefact_name=artefact_name,
|
|
142
|
+
content=content,
|
|
143
|
+
target_content_existing=target_content_existing,
|
|
144
|
+
merge=merge,
|
|
145
|
+
)
|
|
59
146
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
merge=merge,
|
|
65
|
-
override=override,
|
|
66
|
-
)
|
|
147
|
+
action = "Merging" if merge and target_content_existing else "Converting"
|
|
148
|
+
print(
|
|
149
|
+
f"\n{action} '{artefact_name}' from {old_classifier} to {new_classifier}..."
|
|
150
|
+
)
|
|
67
151
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
152
|
+
converted_artefact = self._run_conversion_agent(prompt, target_class)
|
|
153
|
+
self._write_artefact(
|
|
154
|
+
new_classifier,
|
|
155
|
+
artefact_name,
|
|
156
|
+
converted_artefact.serialize(),
|
|
157
|
+
merge,
|
|
158
|
+
override,
|
|
159
|
+
)
|
|
74
160
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
161
|
+
def _cleanup_after_conversion(
|
|
162
|
+
self, old_classifier: str, new_classifier: str, artefact_name: str
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Move data folder and delete old artefact after successful conversion."""
|
|
165
|
+
if old_classifier == new_classifier:
|
|
166
|
+
return
|
|
167
|
+
self._move_data_folder_content(old_classifier, new_classifier, artefact_name)
|
|
168
|
+
ArtefactDeleter(self.file_system).delete(
|
|
169
|
+
artefact_name, old_classifier, force=True
|
|
170
|
+
)
|
|
81
171
|
|
|
82
172
|
def _validate_classifiers(self, old_classifier: str, new_classifier: str):
|
|
83
173
|
if not Classifier.is_valid_classifier(old_classifier):
|
|
@@ -122,20 +212,29 @@ class AraArtefactConverter:
|
|
|
122
212
|
def _resolve_target_content(
|
|
123
213
|
self, artefact_name: str, new_classifier: str, merge: bool, override: bool
|
|
124
214
|
):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_, new_artefact_info = self.reader.read_artefact_data(
|
|
215
|
+
if merge:
|
|
216
|
+
target_content_existing, _ = self.reader.read_artefact_data(
|
|
128
217
|
artefact_name, new_classifier
|
|
129
218
|
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
219
|
+
return target_content_existing
|
|
220
|
+
|
|
221
|
+
if override:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
# Check if target already exists
|
|
225
|
+
target_content, new_artefact_info = self.reader.read_artefact_data(
|
|
226
|
+
artefact_name, new_classifier
|
|
227
|
+
)
|
|
228
|
+
if new_artefact_info:
|
|
229
|
+
# Only validate if content is not None (artefact file exists and is readable)
|
|
230
|
+
if target_content is not None:
|
|
231
|
+
self._validate_artefact_parseable(
|
|
232
|
+
target_content, artefact_name, is_source=False
|
|
133
233
|
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
artefact_name, new_classifier
|
|
234
|
+
raise ValueError(
|
|
235
|
+
f"Found already existing {new_classifier} {artefact_name}. Rerun the command with --override or --merge."
|
|
137
236
|
)
|
|
138
|
-
return
|
|
237
|
+
return None
|
|
139
238
|
|
|
140
239
|
def _get_target_class(self, new_classifier: str):
|
|
141
240
|
from ara_cli.artefact_models.artefact_mapping import artefact_type_mapping
|