ara-cli 0.1.10.0__py3-none-any.whl → 0.1.10.4__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.
- ara_cli/__main__.py +252 -97
- ara_cli/ara_command_action.py +11 -6
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch_templates.py +14 -0
- ara_cli/ara_subcommands/list.py +65 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +44 -6
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +18 -9
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +31 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +1 -19
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +144 -0
- ara_cli/file_loaders/text_file_loader.py +2 -2
- ara_cli/prompt_extractor.py +97 -79
- ara_cli/prompt_handler.py +160 -59
- ara_cli/tag_extractor.py +38 -18
- ara_cli/template_loader.py +1 -1
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.4.dist-info}/METADATA +2 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.4.dist-info}/RECORD +48 -26
- tests/test_artefact_scan.py +1 -1
- tests/test_prompt_handler.py +12 -4
- tests/test_tag_extractor.py +19 -13
- ara_cli/ara_command_parser.py +0 -605
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.4.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.4.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import delete_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def delete_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact to be deleted"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Filename of artefact"),
|
|
9
|
+
force: bool = typer.Option(False, "-f", "--force", help="ignore nonexistent files and arguments, never prompt")
|
|
10
|
+
):
|
|
11
|
+
"""Delete an artefact file including its data directory."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
classifier=classifier.value,
|
|
14
|
+
parameter=parameter,
|
|
15
|
+
force=force
|
|
16
|
+
)
|
|
17
|
+
delete_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(parent: typer.Typer):
|
|
21
|
+
help_text = "Delete an artefact file including its data directory"
|
|
22
|
+
parent.command(name="delete", help=help_text)(delete_main)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import MockArgs
|
|
3
|
+
from ara_cli.ara_command_action import extract_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def extract_main(
|
|
7
|
+
filename: str = typer.Argument(help="Input file to extract from"),
|
|
8
|
+
force: bool = typer.Option(False, "-f", "--force", help="Answer queries with yes when extracting"),
|
|
9
|
+
write: bool = typer.Option(False, "-w", "--write", help="Overwrite existing files without using LLM for merging")
|
|
10
|
+
):
|
|
11
|
+
"""Extract blocks of marked content from a given file."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
filename=filename,
|
|
14
|
+
force=force,
|
|
15
|
+
write=write
|
|
16
|
+
)
|
|
17
|
+
extract_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(parent: typer.Typer):
|
|
21
|
+
help_text = "Extract blocks of marked content from a given file"
|
|
22
|
+
parent.command(name="extract", help=help_text)(extract_main)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import MockArgs
|
|
3
|
+
from ara_cli.ara_command_action import fetch_templates_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fetch_templates_main():
|
|
7
|
+
"""Fetches templates and stores them in .araconfig."""
|
|
8
|
+
args = MockArgs()
|
|
9
|
+
fetch_templates_action(args)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(parent: typer.Typer):
|
|
13
|
+
help_text = "Fetches templates and stores them in .araconfig"
|
|
14
|
+
parent.command(name="fetch-templates", help=help_text)(fetch_templates_main)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from ara_cli.error_handler import AraError
|
|
3
|
+
from typing import Optional, List, Tuple
|
|
4
|
+
from .common import MockArgs
|
|
5
|
+
from ara_cli.ara_command_action import list_action
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _validate_extension_options(include_extension: Optional[List[str]], exclude_extension: Optional[List[str]]) -> None:
|
|
9
|
+
"""Validate that include and exclude extension options are mutually exclusive."""
|
|
10
|
+
if include_extension and exclude_extension:
|
|
11
|
+
raise AraError("--include-extension/-i and --exclude-extension/-e are mutually exclusive")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _validate_exclusive_options(branch: Optional[Tuple[str, str]],
|
|
15
|
+
children: Optional[Tuple[str, str]],
|
|
16
|
+
data: Optional[Tuple[str, str]]) -> None:
|
|
17
|
+
"""Validate that branch, children, and data options are mutually exclusive."""
|
|
18
|
+
exclusive_options = [branch, children, data]
|
|
19
|
+
non_none_options = [opt for opt in exclusive_options if opt is not None]
|
|
20
|
+
if len(non_none_options) > 1:
|
|
21
|
+
raise AraError("--branch, --children, and --data are mutually exclusive")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def list_main(
|
|
25
|
+
ctx: typer.Context,
|
|
26
|
+
include_content: Optional[List[str]] = typer.Option(None, "-I", "--include-content", help="filter for files which include given content"),
|
|
27
|
+
exclude_content: Optional[List[str]] = typer.Option(None, "-E", "--exclude-content", help="filter for files which do not include given content"),
|
|
28
|
+
include_tags: Optional[List[str]] = typer.Option(None, "--include-tags", help="filter for files which include given tags"),
|
|
29
|
+
exclude_tags: Optional[List[str]] = typer.Option(None, "--exclude-tags", help="filter for files which do not include given tags"),
|
|
30
|
+
include_extension: Optional[List[str]] = typer.Option(None, "-i", "--include-extension", "--include-classifier", help="list of extensions to include in listing"),
|
|
31
|
+
exclude_extension: Optional[List[str]] = typer.Option(None, "-e", "--exclude-extension", "--exclude-classifier", help="list of extensions to exclude from listing"),
|
|
32
|
+
branch: Optional[Tuple[str, str]] = typer.Option(None, "-b", "--branch", help="List artefacts in the parent chain (classifier artefact_name)"),
|
|
33
|
+
children: Optional[Tuple[str, str]] = typer.Option(None, "-c", "--children", help="List child artefacts (classifier artefact_name)"),
|
|
34
|
+
data: Optional[Tuple[str, str]] = typer.Option(None, "-d", "--data", help="List file in the data directory (classifier artefact_name)")
|
|
35
|
+
):
|
|
36
|
+
"""List files with optional tags.
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
ara list --data feature my_feature --include-extension .md
|
|
40
|
+
ara list --include-extension .feature
|
|
41
|
+
ara list --children userstory my_story
|
|
42
|
+
ara list --branch userstory my_story --include-extension .businessgoal
|
|
43
|
+
ara list --include-content "example content" --include-extension .task
|
|
44
|
+
"""
|
|
45
|
+
_validate_extension_options(include_extension, exclude_extension)
|
|
46
|
+
_validate_exclusive_options(branch, children, data)
|
|
47
|
+
|
|
48
|
+
args = MockArgs(
|
|
49
|
+
include_content=include_content,
|
|
50
|
+
exclude_content=exclude_content,
|
|
51
|
+
include_tags=include_tags,
|
|
52
|
+
exclude_tags=exclude_tags,
|
|
53
|
+
include_extension=include_extension,
|
|
54
|
+
exclude_extension=exclude_extension,
|
|
55
|
+
branch_args=tuple(branch) if branch else (None, None),
|
|
56
|
+
children_args=tuple(children) if children else (None, None),
|
|
57
|
+
data_args=tuple(data) if data else (None, None)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
list_action(args)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def register(parent: typer.Typer):
|
|
64
|
+
help_text = "List files with optional tags"
|
|
65
|
+
parent.command(name="list", help=help_text)(list_main)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from .common import ClassifierEnum, MockArgs, ClassifierOption
|
|
4
|
+
from ara_cli.ara_command_action import list_tags_action
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def list_tags_main(
|
|
8
|
+
json_output: bool = typer.Option(False, "-j", "--json/--no-json", help="Output tags as JSON"),
|
|
9
|
+
include_classifier: Optional[ClassifierEnum] = ClassifierOption("Show tags for an artefact type", "--include-classifier"),
|
|
10
|
+
exclude_classifier: Optional[ClassifierEnum] = ClassifierOption("Show tags for an artefact type", "--exclude-classifier"),
|
|
11
|
+
filtered_extra_column: bool = typer.Option(False, "--filtered-extra-column", help="Filter tags for extra column")
|
|
12
|
+
):
|
|
13
|
+
"""Show tags."""
|
|
14
|
+
args = MockArgs(
|
|
15
|
+
json=json_output,
|
|
16
|
+
include_classifier=include_classifier.value if include_classifier else None,
|
|
17
|
+
exclude_classifier=exclude_classifier.value if exclude_classifier else None,
|
|
18
|
+
filtered_extra_column=filtered_extra_column
|
|
19
|
+
)
|
|
20
|
+
list_tags_action(args)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register(parent: typer.Typer):
|
|
24
|
+
help_text = "Show tags"
|
|
25
|
+
parent.command(name="list-tags", help=help_text)(list_tags_main)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import TemplateTypeEnum, MockArgs, TemplateTypeArgument, ChatNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import load_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_template_name_completer():
|
|
7
|
+
"""Create a template name completer that can access the template_type context."""
|
|
8
|
+
def completer(incomplete: str) -> list[str]:
|
|
9
|
+
# This is a simplified version - in practice, you'd need to access
|
|
10
|
+
# the template_type from the current command context
|
|
11
|
+
from ara_cli.template_loader import TemplateLoader
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
# For all template types since we can't easily get context in typer
|
|
15
|
+
all_templates = []
|
|
16
|
+
for template_type in ['rules', 'intention', 'commands', 'blueprint']:
|
|
17
|
+
try:
|
|
18
|
+
loader = TemplateLoader()
|
|
19
|
+
templates = loader.get_available_templates(template_type, os.getcwd())
|
|
20
|
+
all_templates.extend(templates)
|
|
21
|
+
except Exception:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
return [t for t in all_templates if t.startswith(incomplete)]
|
|
25
|
+
return completer
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_main(
|
|
29
|
+
chat_name: str = ChatNameArgument("Name of the chat file to load template into (without extension)"),
|
|
30
|
+
template_type: TemplateTypeEnum = TemplateTypeArgument("Type of template to load"),
|
|
31
|
+
template_name: str = typer.Argument(
|
|
32
|
+
"",
|
|
33
|
+
help="Name of the template to load. Supports wildcards and 'global/' prefix",
|
|
34
|
+
autocompletion=create_template_name_completer()
|
|
35
|
+
)
|
|
36
|
+
):
|
|
37
|
+
"""Load a template into a chat file."""
|
|
38
|
+
args = MockArgs(
|
|
39
|
+
chat_name=chat_name,
|
|
40
|
+
template_type=template_type.value,
|
|
41
|
+
template_name=template_name
|
|
42
|
+
)
|
|
43
|
+
load_action(args)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def register(parent: typer.Typer):
|
|
47
|
+
help_text = "Load a template into a chat file"
|
|
48
|
+
parent.command(name="load", help=help_text)(load_main)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument, ChatNameArgument
|
|
4
|
+
from ara_cli.ara_command_action import prompt_action
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def prompt_init(
|
|
8
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
9
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
10
|
+
):
|
|
11
|
+
"""Initialize a macro prompt."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
classifier=classifier.value,
|
|
14
|
+
parameter=parameter,
|
|
15
|
+
steps="init"
|
|
16
|
+
)
|
|
17
|
+
prompt_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def prompt_load(
|
|
21
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
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)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prompt_send(
|
|
34
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
35
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
36
|
+
):
|
|
37
|
+
"""Send configured prompt to LLM."""
|
|
38
|
+
args = MockArgs(
|
|
39
|
+
classifier=classifier.value,
|
|
40
|
+
parameter=parameter,
|
|
41
|
+
steps="send"
|
|
42
|
+
)
|
|
43
|
+
prompt_action(args)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def prompt_load_and_send(
|
|
47
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
48
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
49
|
+
):
|
|
50
|
+
"""Load templates and send prompt to LLM."""
|
|
51
|
+
args = MockArgs(
|
|
52
|
+
classifier=classifier.value,
|
|
53
|
+
parameter=parameter,
|
|
54
|
+
steps="load-and-send"
|
|
55
|
+
)
|
|
56
|
+
prompt_action(args)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def prompt_extract(
|
|
60
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
61
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory"),
|
|
62
|
+
write: bool = typer.Option(False, "-w", "--write", help="Overwrite existing files without using LLM for merging")
|
|
63
|
+
):
|
|
64
|
+
"""Extract LLM response and save to disk."""
|
|
65
|
+
args = MockArgs(
|
|
66
|
+
classifier=classifier.value,
|
|
67
|
+
parameter=parameter,
|
|
68
|
+
steps="extract",
|
|
69
|
+
write=write
|
|
70
|
+
)
|
|
71
|
+
prompt_action(args)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def prompt_update(
|
|
75
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
76
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
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)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def prompt_chat(
|
|
88
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
89
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory"),
|
|
90
|
+
chat_name: Optional[str] = ChatNameArgument("Optional name for a specific chat", None),
|
|
91
|
+
reset: Optional[bool] = typer.Option(None, "-r", "--reset/--no-reset", help="Reset the chat file if it exists"),
|
|
92
|
+
output_mode: bool = typer.Option(False, "--out", help="Output the contents of the chat file instead of entering interactive chat mode"),
|
|
93
|
+
append: Optional[List[str]] = typer.Option(None, "--append", help="Append strings to the chat file"),
|
|
94
|
+
restricted: Optional[bool] = typer.Option(None, "--restricted/--no-restricted", help="Start with a limited set of commands")
|
|
95
|
+
):
|
|
96
|
+
"""Start chat mode for the artefact."""
|
|
97
|
+
args = MockArgs(
|
|
98
|
+
classifier=classifier.value,
|
|
99
|
+
parameter=parameter,
|
|
100
|
+
steps="chat",
|
|
101
|
+
chat_name=chat_name,
|
|
102
|
+
reset=reset,
|
|
103
|
+
output_mode=output_mode,
|
|
104
|
+
append=append,
|
|
105
|
+
restricted=restricted
|
|
106
|
+
)
|
|
107
|
+
prompt_action(args)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def prompt_init_rag(
|
|
111
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
112
|
+
parameter: str = ArtefactNameArgument("Name of artefact data directory")
|
|
113
|
+
):
|
|
114
|
+
"""Initialize RAG prompt."""
|
|
115
|
+
args = MockArgs(
|
|
116
|
+
classifier=classifier.value,
|
|
117
|
+
parameter=parameter,
|
|
118
|
+
steps="init-rag"
|
|
119
|
+
)
|
|
120
|
+
prompt_action(args)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def register(parent: typer.Typer):
|
|
124
|
+
prompt_app = typer.Typer(
|
|
125
|
+
help="Base command for prompt interaction mode",
|
|
126
|
+
add_completion=False # Disable completion on subcommand
|
|
127
|
+
)
|
|
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")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
4
|
+
from ara_cli.ara_command_action import read_action
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_main(
|
|
8
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type", default=None),
|
|
9
|
+
parameter: str = ArtefactNameArgument("Filename of artefact", default=None),
|
|
10
|
+
include_content: Optional[List[str]] = typer.Option(None, "-I", "--include-content", help="filter for files which include given content"),
|
|
11
|
+
exclude_content: Optional[List[str]] = typer.Option(None, "-E", "--exclude-content", help="filter for files which do not include given content"),
|
|
12
|
+
include_tags: Optional[List[str]] = typer.Option(None, "--include-tags", help="filter for files which include given tags"),
|
|
13
|
+
exclude_tags: Optional[List[str]] = typer.Option(None, "--exclude-tags", help="filter for files which do not include given tags"),
|
|
14
|
+
include_extension: Optional[List[str]] = typer.Option(None, "-i", "--include-extension", "--include-classifier", help="list of extensions to include in listing"),
|
|
15
|
+
exclude_extension: Optional[List[str]] = typer.Option(None, "-e", "--exclude-extension", "--exclude-classifier", help="list of extensions to exclude from listing"),
|
|
16
|
+
branch: bool = typer.Option(False, "-b", "--branch", help="Output the contents of artefacts in the parent chain"),
|
|
17
|
+
children: bool = typer.Option(False, "-c", "--children", help="Output the contents of child artefacts")
|
|
18
|
+
):
|
|
19
|
+
"""Reads contents of artefacts."""
|
|
20
|
+
# Handle mutually exclusive options
|
|
21
|
+
if branch and children:
|
|
22
|
+
typer.echo("Error: --branch and --children are mutually exclusive", err=True)
|
|
23
|
+
raise typer.Exit(1)
|
|
24
|
+
|
|
25
|
+
read_mode = None
|
|
26
|
+
if branch:
|
|
27
|
+
read_mode = "branch"
|
|
28
|
+
elif children:
|
|
29
|
+
read_mode = "children"
|
|
30
|
+
|
|
31
|
+
args = MockArgs(
|
|
32
|
+
classifier=classifier.value if classifier else None,
|
|
33
|
+
parameter=parameter,
|
|
34
|
+
include_content=include_content,
|
|
35
|
+
exclude_content=exclude_content,
|
|
36
|
+
include_tags=include_tags,
|
|
37
|
+
exclude_tags=exclude_tags,
|
|
38
|
+
include_extension=include_extension,
|
|
39
|
+
exclude_extension=exclude_extension,
|
|
40
|
+
read_mode=read_mode
|
|
41
|
+
)
|
|
42
|
+
read_action(args)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def register(parent: typer.Typer):
|
|
46
|
+
help_text = "Reads contents of artefacts"
|
|
47
|
+
parent.command(name="read", help=help_text)(read_main)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import read_status_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_status_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Filename of artefact")
|
|
9
|
+
):
|
|
10
|
+
"""Read status of an artefact by checking its tags."""
|
|
11
|
+
args = MockArgs(
|
|
12
|
+
classifier=classifier.value,
|
|
13
|
+
parameter=parameter
|
|
14
|
+
)
|
|
15
|
+
read_status_action(args)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(parent: typer.Typer):
|
|
19
|
+
help_text = "Read status of an artefact by checking its tags"
|
|
20
|
+
parent.command(name="read-status", help=help_text)(read_status_main)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import read_user_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_user_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Filename of artefact")
|
|
9
|
+
):
|
|
10
|
+
"""Read user of an artefact by checking its tags."""
|
|
11
|
+
args = MockArgs(
|
|
12
|
+
classifier=classifier.value,
|
|
13
|
+
parameter=parameter
|
|
14
|
+
)
|
|
15
|
+
read_user_action(args)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(parent: typer.Typer):
|
|
19
|
+
help_text = "Read user of an artefact by checking its tags"
|
|
20
|
+
parent.command(name="read-user", help=help_text)(read_user_main)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument, ParentNameArgument
|
|
4
|
+
from ara_cli.ara_command_action import reconnect_action
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def reconnect_main(
|
|
8
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type"),
|
|
9
|
+
parameter: str = ArtefactNameArgument("Filename of artefact"),
|
|
10
|
+
parent_classifier: ClassifierEnum = ClassifierArgument("Classifier of the parent artefact type"),
|
|
11
|
+
parent_name: str = ParentNameArgument("Filename of parent artefact"),
|
|
12
|
+
rule: Optional[str] = typer.Option(None, "-r", "--rule", help="Rule for connection")
|
|
13
|
+
):
|
|
14
|
+
"""Connect an artefact to a parent artefact."""
|
|
15
|
+
args = MockArgs(
|
|
16
|
+
classifier=classifier.value,
|
|
17
|
+
parameter=parameter,
|
|
18
|
+
parent_classifier=parent_classifier.value,
|
|
19
|
+
parent_name=parent_name,
|
|
20
|
+
rule=rule
|
|
21
|
+
)
|
|
22
|
+
reconnect_action(args)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def register(parent: typer.Typer):
|
|
26
|
+
help_text = "Connect an artefact to a parent artefact"
|
|
27
|
+
parent.command(name="reconnect", help=help_text)(reconnect_main)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import rename_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def rename_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Filename of artefact"),
|
|
9
|
+
aspect: str = typer.Argument(help="New artefact name and new data directory name")
|
|
10
|
+
):
|
|
11
|
+
"""Rename a classified artefact and its data directory."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
classifier=classifier.value,
|
|
14
|
+
parameter=parameter,
|
|
15
|
+
aspect=aspect
|
|
16
|
+
)
|
|
17
|
+
rename_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(parent: typer.Typer):
|
|
21
|
+
help_text = "Rename a classified artefact and its data directory"
|
|
22
|
+
parent.command(name="rename", help=help_text)(rename_main)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import MockArgs
|
|
3
|
+
from ara_cli.ara_command_action import scan_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def scan_main():
|
|
7
|
+
"""Scan ARA tree for incompatible artefacts."""
|
|
8
|
+
args = MockArgs()
|
|
9
|
+
scan_action(args)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(parent: typer.Typer):
|
|
13
|
+
help_text = "Scan ARA tree for incompatible artefacts"
|
|
14
|
+
parent.command(name="scan", help=help_text)(scan_main)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument, StatusArgument
|
|
3
|
+
from ara_cli.ara_command_action import set_status_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def set_status_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type, typically 'task'"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Name of the task artefact"),
|
|
9
|
+
new_status: str = StatusArgument("New status to set for the task")
|
|
10
|
+
):
|
|
11
|
+
"""Set the status of a task."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
classifier=classifier.value,
|
|
14
|
+
parameter=parameter,
|
|
15
|
+
new_status=new_status
|
|
16
|
+
)
|
|
17
|
+
set_status_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(parent: typer.Typer):
|
|
21
|
+
help_text = "Set the status of a task"
|
|
22
|
+
parent.command(name="set-status", help=help_text)(set_status_main)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument, ArtefactNameArgument
|
|
3
|
+
from ara_cli.ara_command_action import set_user_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def set_user_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type, typically 'task'"),
|
|
8
|
+
parameter: str = ArtefactNameArgument("Name of the task artefact"),
|
|
9
|
+
new_user: str = typer.Argument(help="New user to assign to the task")
|
|
10
|
+
):
|
|
11
|
+
"""Set the user of a task."""
|
|
12
|
+
args = MockArgs(
|
|
13
|
+
classifier=classifier.value,
|
|
14
|
+
parameter=parameter,
|
|
15
|
+
new_user=new_user
|
|
16
|
+
)
|
|
17
|
+
set_user_action(args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(parent: typer.Typer):
|
|
21
|
+
help_text = "Set the user of a task"
|
|
22
|
+
parent.command(name="set-user", help=help_text)(set_user_main)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .common import ClassifierEnum, MockArgs, ClassifierArgument
|
|
3
|
+
from ara_cli.ara_command_action import template_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def template_main(
|
|
7
|
+
classifier: ClassifierEnum = ClassifierArgument("Classifier of the artefact type")
|
|
8
|
+
):
|
|
9
|
+
"""Outputs a classified ara template in the terminal."""
|
|
10
|
+
args = MockArgs(classifier=classifier.value)
|
|
11
|
+
template_action(args)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register(parent: typer.Typer):
|
|
15
|
+
help_text = "Outputs a classified ara template in the terminal"
|
|
16
|
+
parent.command(name="template", help=help_text)(template_main)
|
ara_cli/artefact_autofix.py
CHANGED
|
@@ -487,7 +487,7 @@ def _convert_to_scenario_outline(scenario_lines: list, placeholders: set, indent
|
|
|
487
487
|
def _create_examples_table(placeholders: set, base_indentation: str) -> list:
|
|
488
488
|
"""Create the Examples table for the scenario outline."""
|
|
489
489
|
examples_indentation = base_indentation + " "
|
|
490
|
-
table_indentation = examples_indentation + "
|
|
490
|
+
table_indentation = examples_indentation + " "
|
|
491
491
|
|
|
492
492
|
sorted_placeholders = sorted(placeholders)
|
|
493
493
|
header = "| " + " | ".join(sorted_placeholders) + " |"
|
|
@@ -576,6 +576,42 @@ def fix_rule(
|
|
|
576
576
|
return artefact.serialize()
|
|
577
577
|
|
|
578
578
|
|
|
579
|
+
def fix_misplaced_content(file_path: str, artefact_text: str, **kwargs) -> str:
|
|
580
|
+
"""
|
|
581
|
+
Deterministically fixes content like 'Rule:' or 'Estimate:' misplaced in the description.
|
|
582
|
+
"""
|
|
583
|
+
lines = artefact_text.splitlines()
|
|
584
|
+
|
|
585
|
+
desc_start_idx = -1
|
|
586
|
+
for i, line in enumerate(lines):
|
|
587
|
+
if line.strip().startswith("Description:"):
|
|
588
|
+
desc_start_idx = i
|
|
589
|
+
break
|
|
590
|
+
|
|
591
|
+
if desc_start_idx == -1:
|
|
592
|
+
return artefact_text # No description, nothing to fix.
|
|
593
|
+
|
|
594
|
+
pre_desc_lines = lines[:desc_start_idx]
|
|
595
|
+
desc_line = lines[desc_start_idx]
|
|
596
|
+
post_desc_lines = lines[desc_start_idx+1:]
|
|
597
|
+
|
|
598
|
+
misplaced_content = []
|
|
599
|
+
new_post_desc_lines = []
|
|
600
|
+
|
|
601
|
+
for line in post_desc_lines:
|
|
602
|
+
if line.strip().startswith("Rule:") or line.strip().startswith("Estimate:"):
|
|
603
|
+
misplaced_content.append(line)
|
|
604
|
+
else:
|
|
605
|
+
new_post_desc_lines.append(line)
|
|
606
|
+
|
|
607
|
+
if not misplaced_content:
|
|
608
|
+
return artefact_text
|
|
609
|
+
|
|
610
|
+
# Rebuild the file content
|
|
611
|
+
final_lines = pre_desc_lines + misplaced_content + [""] + [desc_line] + new_post_desc_lines
|
|
612
|
+
return "\n".join(final_lines)
|
|
613
|
+
|
|
614
|
+
|
|
579
615
|
def should_skip_issue(deterministic_issue, deterministic, non_deterministic, file_path) -> bool:
|
|
580
616
|
if not non_deterministic and not deterministic_issue:
|
|
581
617
|
print(f"Skipping non-deterministic fix for {file_path} as per request.")
|
|
@@ -622,7 +658,7 @@ def apply_non_deterministic_fix(
|
|
|
622
658
|
corrected_artefact = run_agent(prompt, artefact_class)
|
|
623
659
|
corrected_text = corrected_artefact.serialize()
|
|
624
660
|
except Exception as e:
|
|
625
|
-
print(f"
|
|
661
|
+
print(f" ❌ LLM agent failed to fix artefact at {file_path}: {e}")
|
|
626
662
|
return None
|
|
627
663
|
return corrected_text
|
|
628
664
|
|
|
@@ -651,7 +687,7 @@ def attempt_autofix_loop(
|
|
|
651
687
|
print(
|
|
652
688
|
f"Attempting to fix {file_path} (Attempt {attempt + 1}/{max_attempts})..."
|
|
653
689
|
)
|
|
654
|
-
print(f"
|
|
690
|
+
print(f" Reason: {current_reason}")
|
|
655
691
|
|
|
656
692
|
artefact_text = read_artefact(file_path)
|
|
657
693
|
if artefact_text is None:
|
|
@@ -683,13 +719,13 @@ def attempt_autofix_loop(
|
|
|
683
719
|
|
|
684
720
|
if corrected_text is None or corrected_text.strip() == artefact_text.strip():
|
|
685
721
|
print(
|
|
686
|
-
"
|
|
722
|
+
" Fixing attempt did not alter the file. Stopping to prevent infinite loop."
|
|
687
723
|
)
|
|
688
724
|
return False
|
|
689
725
|
|
|
690
726
|
write_corrected_artefact(file_path, corrected_text)
|
|
691
727
|
|
|
692
|
-
print("
|
|
728
|
+
print(" File modified. Re-classifying artefact information for next check...")
|
|
693
729
|
classified_artefact_info = populate_classified_artefact_info(classified_artefact_info, force=True)
|
|
694
730
|
|
|
695
731
|
print(f"❌ Failed to fix {file_path} after {max_attempts} attempts.")
|
|
@@ -713,6 +749,8 @@ def apply_autofix(
|
|
|
713
749
|
"Invalid Contribution Reference": fix_contribution,
|
|
714
750
|
"Rule Mismatch": fix_rule,
|
|
715
751
|
"Scenario Contains Placeholders": fix_scenario_placeholder_mismatch,
|
|
752
|
+
"Found 'Rule:' inside description": fix_misplaced_content,
|
|
753
|
+
"Found 'Estimate:' inside description": fix_misplaced_content,
|
|
716
754
|
}
|
|
717
755
|
|
|
718
756
|
artefact_type, artefact_class = determine_artefact_type_and_class(classifier)
|
|
@@ -731,4 +769,4 @@ def apply_autofix(
|
|
|
731
769
|
deterministic=deterministic,
|
|
732
770
|
non_deterministic=non_deterministic,
|
|
733
771
|
classified_artefact_info=classified_artefact_info,
|
|
734
|
-
)
|
|
772
|
+
)
|