ara-cli 0.1.10.0__py3-none-any.whl → 0.1.10.1__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 +141 -105
- 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_models/artefact_model.py +88 -19
- ara_cli/artefact_models/artefact_templates.py +18 -9
- ara_cli/artefact_models/userstory_artefact_model.py +2 -2
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +1 -0
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +144 -0
- ara_cli/tag_extractor.py +33 -16
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.1.dist-info}/METADATA +2 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.1.dist-info}/RECORD +40 -18
- tests/test_artefact_scan.py +1 -1
- 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.1.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.1.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.10.1.dist-info}/top_level.txt +0 -0
|
@@ -29,7 +29,8 @@ def _default_vision(title: str, use_default_contribution: bool) -> VisionArtefac
|
|
|
29
29
|
our_product="<statement of primary differentiation>"
|
|
30
30
|
)
|
|
31
31
|
return VisionArtefact(
|
|
32
|
-
tags=[
|
|
32
|
+
tags=[],
|
|
33
|
+
author="creator_unknown",
|
|
33
34
|
title=title,
|
|
34
35
|
description="<further optional description to understand the vision, markdown capable text formatting>",
|
|
35
36
|
intent=intent,
|
|
@@ -44,7 +45,8 @@ def _default_businessgoal(title: str, use_default_contribution: bool) -> Busines
|
|
|
44
45
|
i_want="<something that helps me to reach my monetary goal>"
|
|
45
46
|
)
|
|
46
47
|
return BusinessgoalArtefact(
|
|
47
|
-
tags=[
|
|
48
|
+
tags=[],
|
|
49
|
+
author="creator_unknown",
|
|
48
50
|
title=title,
|
|
49
51
|
description="<further optional description to understand the businessgoal, markdown capable text formatting>",
|
|
50
52
|
intent=intent,
|
|
@@ -57,7 +59,8 @@ def _default_capability(title: str, use_default_contribution: bool) -> Capabilit
|
|
|
57
59
|
to_be_able_to="<needed capability for stakeholders that are the enablers/relevant for reaching the business goal>"
|
|
58
60
|
)
|
|
59
61
|
return CapabilityArtefact(
|
|
60
|
-
tags=[
|
|
62
|
+
tags=[],
|
|
63
|
+
author="creator_unknown",
|
|
61
64
|
title=title,
|
|
62
65
|
description="<further optional description to understand the capability, markdown capable text formatting>",
|
|
63
66
|
intent=intent,
|
|
@@ -77,7 +80,8 @@ def _default_epic(title: str, use_default_contribution: bool) -> EpicArtefact:
|
|
|
77
80
|
"<rule needed to fulfill the wanted product behavior>"
|
|
78
81
|
]
|
|
79
82
|
return EpicArtefact(
|
|
80
|
-
tags=[
|
|
83
|
+
tags=[],
|
|
84
|
+
author="creator_unknown",
|
|
81
85
|
title=title,
|
|
82
86
|
description="<further optional description to understand the epic, markdown capable text formatting>",
|
|
83
87
|
intent=intent,
|
|
@@ -98,7 +102,8 @@ def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryA
|
|
|
98
102
|
"<rule needed to fulfill the wanted product behavior>"
|
|
99
103
|
]
|
|
100
104
|
return UserstoryArtefact(
|
|
101
|
-
tags=[
|
|
105
|
+
tags=[],
|
|
106
|
+
author="creator_unknown",
|
|
102
107
|
title=title,
|
|
103
108
|
description="<further optional description to understand the userstory, markdown capable text formatting>",
|
|
104
109
|
intent=intent,
|
|
@@ -110,7 +115,8 @@ def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryA
|
|
|
110
115
|
|
|
111
116
|
def _default_example(title: str, use_default_contribution: bool) -> ExampleArtefact:
|
|
112
117
|
return ExampleArtefact(
|
|
113
|
-
tags=[
|
|
118
|
+
tags=[],
|
|
119
|
+
author="creator_unknown",
|
|
114
120
|
title=title,
|
|
115
121
|
description="<further optional description to understand the example, markdown capable text formatting>",
|
|
116
122
|
contribution=default_contribution() if use_default_contribution else None
|
|
@@ -130,7 +136,8 @@ def _default_keyfeature(title: str, use_default_contribution: bool) -> Keyfeatur
|
|
|
130
136
|
THEN some result is to be expected
|
|
131
137
|
AND some other result is to be expected>"""
|
|
132
138
|
return KeyfeatureArtefact(
|
|
133
|
-
tags=[
|
|
139
|
+
tags=[],
|
|
140
|
+
author="creator_unknown",
|
|
134
141
|
title=title,
|
|
135
142
|
description=description,
|
|
136
143
|
intent=intent,
|
|
@@ -185,7 +192,8 @@ def _default_feature(title: str, use_default_contribution: bool) -> FeatureArtef
|
|
|
185
192
|
description = """<further optional description to understand the feature, no format defined, the example artefact is only a placeholder>"""
|
|
186
193
|
|
|
187
194
|
return FeatureArtefact(
|
|
188
|
-
tags=[
|
|
195
|
+
tags=[],
|
|
196
|
+
author="creator_unknown",
|
|
189
197
|
title=title,
|
|
190
198
|
description=description,
|
|
191
199
|
intent=intent,
|
|
@@ -214,7 +222,8 @@ def _default_issue(title: str, use_default_contribution: bool) -> IssueArtefact:
|
|
|
214
222
|
*or optional free text description*"""
|
|
215
223
|
|
|
216
224
|
return IssueArtefact(
|
|
217
|
-
tags=[
|
|
225
|
+
tags=[],
|
|
226
|
+
author="creator_unknown",
|
|
218
227
|
title=title,
|
|
219
228
|
description=description,
|
|
220
229
|
additional_description=additional_description,
|
|
@@ -171,7 +171,7 @@ class UserstoryArtefact(Artefact):
|
|
|
171
171
|
rules = self._serialize_rules()
|
|
172
172
|
|
|
173
173
|
lines = []
|
|
174
|
-
if self.tags
|
|
174
|
+
if tags: # Changed from self.tags to tags to include all tag types
|
|
175
175
|
lines.append(tags)
|
|
176
176
|
lines.append(title)
|
|
177
177
|
lines.append("")
|
|
@@ -188,4 +188,4 @@ class UserstoryArtefact(Artefact):
|
|
|
188
188
|
lines.append(description)
|
|
189
189
|
lines.append("")
|
|
190
190
|
|
|
191
|
-
return '\n'.join(lines)
|
|
191
|
+
return '\n'.join(lines)
|
ara_cli/artefact_scan.py
CHANGED
|
@@ -25,10 +25,10 @@ def is_rule_valid(contribution, classified_artefact_info) -> bool:
|
|
|
25
25
|
if not rule:
|
|
26
26
|
return True
|
|
27
27
|
parent = ArtefactReader.read_artefact(contribution.artefact_name, contribution.classifier)
|
|
28
|
-
if not parent
|
|
28
|
+
if not parent:
|
|
29
29
|
return True
|
|
30
30
|
rules = parent.rules
|
|
31
|
-
if rule not in rules:
|
|
31
|
+
if not rules or rule not in rules:
|
|
32
32
|
return False
|
|
33
33
|
return True
|
|
34
34
|
|
ara_cli/chat.py
CHANGED
|
@@ -763,6 +763,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
763
763
|
return
|
|
764
764
|
self.create_empty_chat_file(self.chat_name)
|
|
765
765
|
self.chat_history = self.load_chat_history(self.chat_name)
|
|
766
|
+
self.message_buffer.clear()
|
|
766
767
|
print(f"Cleared content of {self.chat_name}")
|
|
767
768
|
|
|
768
769
|
@cmd2.with_category(CATEGORY_CHAT_CONTROL)
|
ara_cli/commands/read_command.py
CHANGED
|
@@ -30,6 +30,11 @@ class ReadCommand(Command):
|
|
|
30
30
|
"""Execute the read command and return success status."""
|
|
31
31
|
file_classifier = FileClassifier(os)
|
|
32
32
|
classified_artefacts = ArtefactReader.read_artefacts()
|
|
33
|
+
|
|
34
|
+
if not self.classifier or not self.artefact_name:
|
|
35
|
+
self._filter_and_print(classified_artefacts, file_classifier)
|
|
36
|
+
return True
|
|
37
|
+
|
|
33
38
|
artefacts = classified_artefacts.get(self.classifier, [])
|
|
34
39
|
all_artefact_names = [a.title for a in artefacts]
|
|
35
40
|
|
|
@@ -62,10 +67,11 @@ class ReadCommand(Command):
|
|
|
62
67
|
)
|
|
63
68
|
|
|
64
69
|
# Apply filtering and print results
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
self._filter_and_print(artefacts_by_classifier, file_classifier)
|
|
71
|
+
# filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
|
|
72
|
+
# file_classifier.print_classified_files(
|
|
73
|
+
# filtered_artefacts, print_content=True
|
|
74
|
+
# )
|
|
69
75
|
return True
|
|
70
76
|
|
|
71
77
|
except Exception as e:
|
|
@@ -102,3 +108,10 @@ class ReadCommand(Command):
|
|
|
102
108
|
file_path_retrieval=artefact_path_retrieval,
|
|
103
109
|
tag_retrieval=artefact_tags_retrieval
|
|
104
110
|
)
|
|
111
|
+
|
|
112
|
+
def _filter_and_print(self, artefacts_by_classifier, file_classifier):
|
|
113
|
+
"""Apply list filtering and print results"""
|
|
114
|
+
filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
|
|
115
|
+
file_classifier.print_classified_files(
|
|
116
|
+
filtered_artefacts, print_content=True
|
|
117
|
+
)
|
ara_cli/completers.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ara_cli.classifier import Classifier
|
|
7
|
+
from ara_cli.template_manager import SpecificationBreakdownAspects
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def complete_classifier(incomplete: str) -> List[str]:
|
|
11
|
+
"""Complete classifier names."""
|
|
12
|
+
classifiers = Classifier.ordered_classifiers()
|
|
13
|
+
return [c for c in classifiers if c.startswith(incomplete)]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def complete_aspect(incomplete: str) -> List[str]:
|
|
17
|
+
"""Complete aspect names."""
|
|
18
|
+
aspects = SpecificationBreakdownAspects.VALID_ASPECTS
|
|
19
|
+
return [a for a in aspects if a.startswith(incomplete)]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def complete_status(incomplete: str) -> List[str]:
|
|
23
|
+
"""Complete task status values."""
|
|
24
|
+
statuses = ["to-do", "in-progress", "review", "done", "closed"]
|
|
25
|
+
return [s for s in statuses if s.startswith(incomplete)]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def complete_template_type(incomplete: str) -> List[str]:
|
|
29
|
+
"""Complete template type values."""
|
|
30
|
+
template_types = ["rules", "intention", "commands", "blueprint"]
|
|
31
|
+
return [t for t in template_types if t.startswith(incomplete)]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def complete_artefact_name(classifier: str) -> List[str]:
|
|
35
|
+
"""Complete artefact names for a given classifier."""
|
|
36
|
+
try:
|
|
37
|
+
# Get the directory for the classifier
|
|
38
|
+
classifier_dir = f"ara/{Classifier.get_sub_directory(classifier)}"
|
|
39
|
+
|
|
40
|
+
if not os.path.exists(classifier_dir):
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
# Find all files with the classifier extension
|
|
44
|
+
artefacts = []
|
|
45
|
+
for file in os.listdir(classifier_dir):
|
|
46
|
+
if file.endswith(f'.{classifier}'):
|
|
47
|
+
# Remove the extension to get the artefact name
|
|
48
|
+
name = file[:-len(f'.{classifier}')]
|
|
49
|
+
artefacts.append(name)
|
|
50
|
+
|
|
51
|
+
return sorted(artefacts)
|
|
52
|
+
except Exception:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def complete_artefact_name_for_classifier(classifier: str):
|
|
57
|
+
"""Create a completer function for artefact names of a specific classifier."""
|
|
58
|
+
def completer(incomplete: str) -> List[str]:
|
|
59
|
+
artefacts = complete_artefact_name(classifier)
|
|
60
|
+
return [a for a in artefacts if a.startswith(incomplete)]
|
|
61
|
+
return completer
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def complete_chat_files(incomplete: str) -> List[str]:
|
|
65
|
+
"""Complete chat file names (without .md extension)."""
|
|
66
|
+
try:
|
|
67
|
+
chat_files = []
|
|
68
|
+
current_dir = Path.cwd()
|
|
69
|
+
|
|
70
|
+
# Look for .md files in current directory
|
|
71
|
+
for file in current_dir.glob("*.md"):
|
|
72
|
+
name = file.stem
|
|
73
|
+
if name.startswith(incomplete):
|
|
74
|
+
chat_files.append(name)
|
|
75
|
+
|
|
76
|
+
return sorted(chat_files)
|
|
77
|
+
except Exception:
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Dynamic completers that need context
|
|
82
|
+
class DynamicCompleters:
|
|
83
|
+
@staticmethod
|
|
84
|
+
def create_classifier_completer():
|
|
85
|
+
"""Create a completer for classifiers."""
|
|
86
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
87
|
+
return complete_classifier(incomplete)
|
|
88
|
+
return completer
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def create_aspect_completer():
|
|
92
|
+
"""Create a completer for aspects."""
|
|
93
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
94
|
+
return complete_aspect(incomplete)
|
|
95
|
+
return completer
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def create_status_completer():
|
|
99
|
+
"""Create a completer for status values."""
|
|
100
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
101
|
+
return complete_status(incomplete)
|
|
102
|
+
return completer
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def create_template_type_completer():
|
|
106
|
+
"""Create a completer for template types."""
|
|
107
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
108
|
+
return complete_template_type(incomplete)
|
|
109
|
+
return completer
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def create_artefact_name_completer():
|
|
113
|
+
"""Create a completer for artefact names based on classifier context."""
|
|
114
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
115
|
+
# Try to get classifier from context
|
|
116
|
+
if hasattr(ctx, 'params') and 'classifier' in ctx.params:
|
|
117
|
+
classifier = ctx.params['classifier']
|
|
118
|
+
if hasattr(classifier, 'value'):
|
|
119
|
+
classifier = classifier.value
|
|
120
|
+
artefacts = complete_artefact_name(classifier)
|
|
121
|
+
return [a for a in artefacts if a.startswith(incomplete)]
|
|
122
|
+
return []
|
|
123
|
+
return completer
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def create_parent_name_completer():
|
|
127
|
+
"""Create a completer for parent artefact names based on parent classifier context."""
|
|
128
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
129
|
+
# Try to get parent_classifier from context
|
|
130
|
+
if hasattr(ctx, 'params') and 'parent_classifier' in ctx.params:
|
|
131
|
+
parent_classifier = ctx.params['parent_classifier']
|
|
132
|
+
if hasattr(parent_classifier, 'value'):
|
|
133
|
+
parent_classifier = parent_classifier.value
|
|
134
|
+
artefacts = complete_artefact_name(parent_classifier)
|
|
135
|
+
return [a for a in artefacts if a.startswith(incomplete)]
|
|
136
|
+
return []
|
|
137
|
+
return completer
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def create_chat_file_completer():
|
|
141
|
+
"""Create a completer for chat files."""
|
|
142
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
143
|
+
return complete_chat_files(incomplete)
|
|
144
|
+
return completer
|
ara_cli/tag_extractor.py
CHANGED
|
@@ -11,13 +11,13 @@ class TagExtractor:
|
|
|
11
11
|
def __init__(self, file_system=None):
|
|
12
12
|
self.file_system = file_system or os
|
|
13
13
|
|
|
14
|
-
def filter_column(self,
|
|
14
|
+
def filter_column(self, tag_groups, filtered_artefacts):
|
|
15
15
|
status_tags = {"to-do", "in-progress", "review", "done", "closed"}
|
|
16
16
|
|
|
17
17
|
artefacts_to_process = self._get_artefacts_without_status_tags(
|
|
18
18
|
filtered_artefacts, status_tags
|
|
19
19
|
)
|
|
20
|
-
self._add_non_status_tags_to_set(
|
|
20
|
+
self._add_non_status_tags_to_set(tag_groups, artefacts_to_process, status_tags)
|
|
21
21
|
|
|
22
22
|
def _get_artefacts_without_status_tags(self, filtered_artefacts, status_tags):
|
|
23
23
|
artefacts_to_process = []
|
|
@@ -32,7 +32,7 @@ class TagExtractor:
|
|
|
32
32
|
tags = artefact.tags + [artefact.status] if artefact.status else artefact.tags
|
|
33
33
|
return set(tag for tag in tags if tag is not None)
|
|
34
34
|
|
|
35
|
-
def _add_non_status_tags_to_set(self,
|
|
35
|
+
def _add_non_status_tags_to_set(self, tag_groups, artefacts, status_tags):
|
|
36
36
|
for artefact in artefacts:
|
|
37
37
|
tags = [
|
|
38
38
|
tag for tag in (artefact.tags + [artefact.status]) if tag is not None
|
|
@@ -40,23 +40,41 @@ class TagExtractor:
|
|
|
40
40
|
for tag in tags:
|
|
41
41
|
if self._is_skipped_tag(tag, status_tags):
|
|
42
42
|
continue
|
|
43
|
-
|
|
43
|
+
key = tag.lower()
|
|
44
|
+
if key not in tag_groups:
|
|
45
|
+
tag_groups[key] = set()
|
|
46
|
+
tag_groups[key].add(tag)
|
|
44
47
|
|
|
45
48
|
def _is_skipped_tag(self, tag, status_tags):
|
|
46
49
|
return (
|
|
47
50
|
tag in status_tags or tag.startswith("priority_") or tag.startswith("user_")
|
|
48
51
|
)
|
|
49
52
|
|
|
50
|
-
def add_to_tags_set(self,
|
|
53
|
+
def add_to_tags_set(self, tag_groups, filtered_artefacts):
|
|
51
54
|
for artefact_list in filtered_artefacts.values():
|
|
52
55
|
for artefact in artefact_list:
|
|
53
56
|
user_tags = [f"user_{tag}" for tag in artefact.users]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
|
|
58
|
+
# Build list of all tags, filtering out None values
|
|
59
|
+
all_tags = []
|
|
60
|
+
all_tags.extend(artefact.tags)
|
|
61
|
+
|
|
62
|
+
if artefact.status:
|
|
63
|
+
all_tags.append(artefact.status)
|
|
64
|
+
|
|
65
|
+
all_tags.extend(user_tags)
|
|
66
|
+
|
|
67
|
+
# Safely handle author attribute
|
|
68
|
+
if hasattr(artefact, 'author') and artefact.author:
|
|
69
|
+
all_tags.append(artefact.author)
|
|
70
|
+
|
|
71
|
+
# Filter out None values and add to tag groups
|
|
72
|
+
for tag in all_tags:
|
|
73
|
+
if tag is not None:
|
|
74
|
+
key = tag.lower()
|
|
75
|
+
if key not in tag_groups:
|
|
76
|
+
tag_groups[key] = set()
|
|
77
|
+
tag_groups[key].add(tag)
|
|
60
78
|
|
|
61
79
|
def extract_tags(
|
|
62
80
|
self,
|
|
@@ -81,12 +99,11 @@ class TagExtractor:
|
|
|
81
99
|
tag_retrieval=artefact_tags_retrieval,
|
|
82
100
|
)
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
tag_groups = {}
|
|
85
103
|
|
|
86
104
|
if filtered_extra_column:
|
|
87
|
-
self.filter_column(
|
|
105
|
+
self.filter_column(tag_groups, filtered_artefacts)
|
|
88
106
|
else:
|
|
89
|
-
self.add_to_tags_set(
|
|
107
|
+
self.add_to_tags_set(tag_groups, filtered_artefacts)
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
return sorted_tags
|
|
109
|
+
return tag_groups
|
ara_cli/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1.10.
|
|
2
|
+
__version__ = "0.1.10.1" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ara_cli
|
|
3
|
-
Version: 0.1.10.
|
|
3
|
+
Version: 0.1.10.1
|
|
4
4
|
Summary: Powerful, open source command-line tool for managing, structuring and automating software development artifacts in line with Business-Driven Development (BDD) and AI-assisted processes
|
|
5
5
|
Description-Content-Type: text/markdown
|
|
6
6
|
Requires-Dist: langfuse
|
|
@@ -19,6 +19,7 @@ Requires-Dist: pydantic
|
|
|
19
19
|
Requires-Dist: pydantic_ai
|
|
20
20
|
Requires-Dist: python-docx
|
|
21
21
|
Requires-Dist: pymupdf4llm
|
|
22
|
+
Requires-Dist: typer
|
|
22
23
|
Dynamic: description
|
|
23
24
|
Dynamic: description-content-type
|
|
24
25
|
Dynamic: requires-dist
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
ara_cli/__init__.py,sha256=DuzXKimZ6JtUEnp48qCQcnojDflBtYjT6Na_twO5EzM,505
|
|
2
|
-
ara_cli/__main__.py,sha256=
|
|
3
|
-
ara_cli/ara_command_action.py,sha256=
|
|
4
|
-
ara_cli/ara_command_parser.py,sha256=4_LXxj9w7eAY8d_s8pMlKmxtMr9cX9y2pD5azNeJjsg,22288
|
|
2
|
+
ara_cli/__main__.py,sha256=GA9iL-Hi8M4LMBALNdycCP5Uo-jLp5IHRPHrgsgwWvo,8188
|
|
3
|
+
ara_cli/ara_command_action.py,sha256=yWtZXxwGp-n5kl-I6bMvwESfyRVGHHBYHVapu-8knl4,24618
|
|
5
4
|
ara_cli/ara_config.py,sha256=vZsY2zYJdlSExRE84L5LqRH3DjveeuMSmG5fC8HDIVc,9794
|
|
6
5
|
ara_cli/artefact_autofix.py,sha256=9j_bh0HGnN6HVT9OGKVp85VgDklpx3XpSc9MxBCldU4,25050
|
|
7
6
|
ara_cli/artefact_creator.py,sha256=fRrDaGZvOqJqDb_DLXqMTed2XfIvQMIHjLgOuHOi3Qg,5973
|
|
@@ -11,12 +10,13 @@ ara_cli/artefact_link_updater.py,sha256=nKdxTpDKqWTOAMD8viKmUaklSFGWzJZ8S8E8xW_A
|
|
|
11
10
|
ara_cli/artefact_lister.py,sha256=M-ggazAgZ-OLeW9NB48r_sd6zPx0p4hEpeS63qHwI1A,4176
|
|
12
11
|
ara_cli/artefact_reader.py,sha256=-6E1VhIlh2oJE1Rn8ARcHRc_E9N4uk8cEViKMoywm6E,7753
|
|
13
12
|
ara_cli/artefact_renamer.py,sha256=8S4QWD19_FGKsKlWojnu_RUOxx0u9rmLugydM4s4VDc,4219
|
|
14
|
-
ara_cli/artefact_scan.py,sha256=
|
|
15
|
-
ara_cli/chat.py,sha256=
|
|
13
|
+
ara_cli/artefact_scan.py,sha256=qY2Gp4zVcqMXhtuP7rICW0UBG4pcj3W2ABofnL9SIG8,4806
|
|
14
|
+
ara_cli/chat.py,sha256=vpwVnknd_t8qihjxutw37efflPuXIce8ctMvcbLCa7c,40812
|
|
16
15
|
ara_cli/classifier.py,sha256=zWskj7rBYdqYBGjksBm46iTgVU5IIf2PZsJr4qeiwVU,1878
|
|
17
16
|
ara_cli/codefusionretriever.py,sha256=fCHgXdIBRzkVAnapX-KI2NQ44XbrrF4tEQmn5J6clUI,1980
|
|
18
17
|
ara_cli/codehierachieretriever.py,sha256=Xd3EgEWWhkSf1TmTWtf8X5_YvyE_4B66nRrqarwSiTU,1182
|
|
19
18
|
ara_cli/commandline_completer.py,sha256=b00Dqb5n7SecpxYIDLxAfYhp8X6e3c8a5qYz6ko0i3E,1192
|
|
19
|
+
ara_cli/completers.py,sha256=V4bcmUnuFkdgMpJ3bLAL7cnxinxZb8wwB17WnRHIrHM,5404
|
|
20
20
|
ara_cli/directory_navigator.py,sha256=6QbSAjJrJ5a6Lutol9J4HFgVDMiAQ672ny9TATrh04U,3318
|
|
21
21
|
ara_cli/error_handler.py,sha256=nNaJSq82f3xiz_QFRKPg5kX_-oI-UoFdRJ2OTj1AR18,4019
|
|
22
22
|
ara_cli/file_classifier.py,sha256=nUcNrhflUydCyCRbXHjEEXYwwwfUm65lYnNEvc86fpM,4026
|
|
@@ -30,17 +30,39 @@ ara_cli/prompt_extractor.py,sha256=WloRgfcEdIVq37BpdWAd2X3EMu0bcNN_Wuws1T2YiUg,8
|
|
|
30
30
|
ara_cli/prompt_handler.py,sha256=N0zH5k9T6udKAbMolxEAaAwiFo03h5aF-IY3BmMLEos,27924
|
|
31
31
|
ara_cli/prompt_rag.py,sha256=ydlhe4CUqz0jdzlY7jBbpKaf_5fjMrAZKnriKea3ZAg,7485
|
|
32
32
|
ara_cli/run_file_lister.py,sha256=XbrrDTJXp1LFGx9Lv91SNsEHZPP-PyEMBF_P4btjbDA,2360
|
|
33
|
-
ara_cli/tag_extractor.py,sha256=
|
|
33
|
+
ara_cli/tag_extractor.py,sha256=9yX8Ss4jP_NI-NPxxJJKoVPD-1iEweXBThUh01IU8c8,4048
|
|
34
34
|
ara_cli/template_loader.py,sha256=uEpYOchT5d-OO5r-W0-h605Xilvuv56i1VKSy4_9NaE,10734
|
|
35
35
|
ara_cli/template_manager.py,sha256=l2c785YHB7m0e2TjE0CX-nwXrS4v3EiT9qrS5KuatAc,7105
|
|
36
36
|
ara_cli/update_config_prompt.py,sha256=moqj2Kha7S7fEGzTReU0v2y8UjXC8QfnoiieOQr35C4,5157
|
|
37
|
-
ara_cli/version.py,sha256=
|
|
37
|
+
ara_cli/version.py,sha256=jCeFoAxaTryn9z3M-kDldZwmzGJM9NzWxhhEBjelIFA,146
|
|
38
|
+
ara_cli/ara_subcommands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
+
ara_cli/ara_subcommands/autofix.py,sha256=h7-6hV97Q6PisUJ_U1Qs4sHYwkHsDpeYH63y_LQsfSc,1095
|
|
40
|
+
ara_cli/ara_subcommands/chat.py,sha256=9zorWKbM0ulu9xFhW2tzV5vl8hCLOCjcp2E9hYgZJ90,1239
|
|
41
|
+
ara_cli/ara_subcommands/classifier_directory.py,sha256=7GH3w4DtvFCM1Sr6Qqk_kjp0EC8jNJDieJJMshYJ_6k,620
|
|
42
|
+
ara_cli/ara_subcommands/common.py,sha256=6bAfPbFHx3CzDSQMm4vL7keFgVnOpqiupgDDWj7QdUQ,2898
|
|
43
|
+
ara_cli/ara_subcommands/create.py,sha256=2tIpzKgzytTIdVV26p6cvrcBo8WLm_3qK7GJyn47Jaw,2527
|
|
44
|
+
ara_cli/ara_subcommands/delete.py,sha256=DxWRQ5Z8h5ZpMhyjLHNuLxONgxIQ97hVkQ8VkX15FDk,827
|
|
45
|
+
ara_cli/ara_subcommands/extract.py,sha256=11atXek579W2RP6PYHlGuyVjWBTuyh1viondCjuce_k,765
|
|
46
|
+
ara_cli/ara_subcommands/fetch_templates.py,sha256=f1bXOTlM67hyf3oGPZEQQjSwUsTte7Cd9-yqq76Ud08,432
|
|
47
|
+
ara_cli/ara_subcommands/list.py,sha256=HtX3kKQ9nrfCcJPxJng0ZnoOqLQ11-aui2zxVei8PlI,3562
|
|
48
|
+
ara_cli/ara_subcommands/list_tags.py,sha256=drEzJgJa4OqqYfIuvT7XkjG4o7VB-ikHE0ArIdljoTI,1113
|
|
49
|
+
ara_cli/ara_subcommands/load.py,sha256=czaflU5Xv-TBlpgalvm6yn5oBBAnNfxSeoIFuLPfi-U,1825
|
|
50
|
+
ara_cli/ara_subcommands/prompt.py,sha256=A8L0lJNBm5zkwJJrzz1kpdycwpm1y8hzd2iVleylpck,4630
|
|
51
|
+
ara_cli/ara_subcommands/read.py,sha256=zTPNMvEPf1WvhtUiauXu5HFiXHRIBJVCkHu44L_scSk,2396
|
|
52
|
+
ara_cli/ara_subcommands/read_status.py,sha256=ZqdxuYXC-idJ2JtMIcZzT4XYI55PnqA6sYv2FcMuchw,695
|
|
53
|
+
ara_cli/ara_subcommands/read_user.py,sha256=NuhaC7dBbi8PUHnGGpqbBVSbQ__nT_L52c7tdKkxjlA,681
|
|
54
|
+
ara_cli/ara_subcommands/reconnect.py,sha256=5Q90rbSnYf7YO6n_9esWsYv7o8GQ-Fnzgy4d8S-X-DQ,1088
|
|
55
|
+
ara_cli/ara_subcommands/rename.py,sha256=IggQdvXjTbZ5CkqzebydVVTcaxO4SDOyORqXDL5jnY8,784
|
|
56
|
+
ara_cli/ara_subcommands/scan.py,sha256=XXwIzq4T9sDMXV0ZcMTSakQ7SyosuCfKjMiiTz7533A,363
|
|
57
|
+
ara_cli/ara_subcommands/set_status.py,sha256=6zzuqLR9k-V63e5UQBpsooftbYHuENEP2s3AdI2jyG0,786
|
|
58
|
+
ara_cli/ara_subcommands/set_user.py,sha256=ADgZIj9xIWK9QKY95lIW_GJGYZysALV--y8j6IuvGxs,755
|
|
59
|
+
ara_cli/ara_subcommands/template.py,sha256=gp_BzrNHcVylU5xav1vmPe3-0vQR7UHm44G7w2i370Q,552
|
|
38
60
|
ara_cli/artefact_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
61
|
ara_cli/artefact_models/artefact_data_retrieval.py,sha256=CooXOJBYWSyiViN2xkC8baS8OUaslry3YGVVUeDxRAU,527
|
|
40
62
|
ara_cli/artefact_models/artefact_load.py,sha256=IXzWxP-Q_j_oDGMno0m-OuXCQ7Vd5c_NctshGr4ROBw,621
|
|
41
63
|
ara_cli/artefact_models/artefact_mapping.py,sha256=8aD0spBjkJ8toMAmFawc6UTUxB6-tEEViZXv2I-r88Q,1874
|
|
42
|
-
ara_cli/artefact_models/artefact_model.py,sha256=
|
|
43
|
-
ara_cli/artefact_models/artefact_templates.py,sha256=
|
|
64
|
+
ara_cli/artefact_models/artefact_model.py,sha256=nEXbHHrYFsB4mdjKzye-RAkmFkHDyhlOzXU2ba1E4SU,18471
|
|
65
|
+
ara_cli/artefact_models/artefact_templates.py,sha256=8N1gJlS1KLd79y2nasEgU8xeK-WaP6IenBK5Ojcmn9Y,10028
|
|
44
66
|
ara_cli/artefact_models/businessgoal_artefact_model.py,sha256=GYT5S2xEnQHwv-k-lEeX5NMSqA-UEfV3PhNjgPDUJpw,4698
|
|
45
67
|
ara_cli/artefact_models/capability_artefact_model.py,sha256=SZqHx4O2mj4urn77Stnj4_Jxtlq3-LgBBU9SMkByppI,3079
|
|
46
68
|
ara_cli/artefact_models/epic_artefact_model.py,sha256=h9pC00ZxCL-t_NMjwTCeOnIJZPa9hhB-R05wr110LXs,5619
|
|
@@ -50,14 +72,14 @@ ara_cli/artefact_models/issue_artefact_model.py,sha256=v6CpKnkqiUh6Wch2kkEmyyW49
|
|
|
50
72
|
ara_cli/artefact_models/keyfeature_artefact_model.py,sha256=J9oXLsCAo22AW31D5Z104y02ss0S0O4tPCcd09zYCD0,4066
|
|
51
73
|
ara_cli/artefact_models/serialize_helper.py,sha256=Wks30wy-UrwJURetydKykLgJkdGRgXFHkDT24vHe5tU,595
|
|
52
74
|
ara_cli/artefact_models/task_artefact_model.py,sha256=1BSMbz9D-RXvdpdd0RlAr9hUx84Rcuysk2YfQC8Qy14,6046
|
|
53
|
-
ara_cli/artefact_models/userstory_artefact_model.py,sha256=
|
|
75
|
+
ara_cli/artefact_models/userstory_artefact_model.py,sha256=xw1gsOjkAphd-4xwz5U6maZ6aASYn35_2DHCo1bPctA,6488
|
|
54
76
|
ara_cli/artefact_models/vision_artefact_model.py,sha256=frjaUJj-mmIlVHEhzAQztCGs-CtvNu_odSborgztfzo,5251
|
|
55
77
|
ara_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
78
|
ara_cli/commands/command.py,sha256=Y_2dNeuxRjbyI3ScXNv55lptSe8Hs_ya78L0nPYNZHA,154
|
|
57
79
|
ara_cli/commands/extract_command.py,sha256=CzUOwDembG587PYbxg5rge4XSfdsuTyOPUvkobkXCIs,573
|
|
58
80
|
ara_cli/commands/load_command.py,sha256=H3CfeHIL-criDU5oi4BONTSpyzJ4m8DzJ0ZCIiAZFeI,2204
|
|
59
81
|
ara_cli/commands/load_image_command.py,sha256=g9-PXAYdqx5Ed1PdVo-FIb4CyJGEpRFbgQf9Dxg6DmM,886
|
|
60
|
-
ara_cli/commands/read_command.py,sha256=
|
|
82
|
+
ara_cli/commands/read_command.py,sha256=xne8jlertuJNcsyzjR0bJeUUHi4NkEfd0h0DRbU9rC4,4347
|
|
61
83
|
ara_cli/file_loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
84
|
ara_cli/file_loaders/binary_file_loader.py,sha256=1HHH1Nk4lEM83CTnf4z9wYz6rMLgpxydFoRcSgkBHmQ,940
|
|
63
85
|
ara_cli/file_loaders/document_file_loader.py,sha256=VxGFChYyM9K-e6eOCK3yk5jQuEXgz01Mh_NoA6CA_RM,1017
|
|
@@ -150,7 +172,7 @@ tests/test_artefact_link_updater.py,sha256=biqbEp2jCOz8giv72hu2P2hDfeJfJ9OrVGdAv
|
|
|
150
172
|
tests/test_artefact_lister.py,sha256=35R13UU-YsX1HOsEN8M2-vIiCUA9RSBm6SwestDaFhE,20388
|
|
151
173
|
tests/test_artefact_reader.py,sha256=660K-d8ed-j8hulsUB_7baPD2-hhbg9TffUR5yVc4Uo,927
|
|
152
174
|
tests/test_artefact_renamer.py,sha256=lSnKCCfoFGgKhTdDZrEaeBq1xJAak1QoqH5aSeOe9Ro,3494
|
|
153
|
-
tests/test_artefact_scan.py,sha256=
|
|
175
|
+
tests/test_artefact_scan.py,sha256=SzMtJeh8_oOBec9yzy3vJRHxs9i1E5gEU2RfF6CJZqE,16888
|
|
154
176
|
tests/test_chat.py,sha256=D2HRRTdnvcDvB9TWz4O91ZONbLJL6w4v8TRTDwjtzzI,86104
|
|
155
177
|
tests/test_classifier.py,sha256=grYGPksydNdPsaEBQxYHZTuTdcJWz7VQtikCKA6BNaQ,1920
|
|
156
178
|
tests/test_directory_navigator.py,sha256=7G0MVrBbtBvbrFUpL0zb_9EkEWi1dulWuHsrQxMJxDY,140
|
|
@@ -160,12 +182,12 @@ tests/test_file_lister.py,sha256=Q9HwhKKx540EPzTmfzOCnvtAgON0aMmpJE2eOe1J3EA,432
|
|
|
160
182
|
tests/test_global_file_lister.py,sha256=ycvf2YL8q5QSEMwcnQfUdoWnQQ8xTSyEtccAeXwl6QU,5487
|
|
161
183
|
tests/test_list_filter.py,sha256=fJA3d_SdaOAUkE7jn68MOVS0THXGghy1fye_64Zvo1U,7964
|
|
162
184
|
tests/test_prompt_handler.py,sha256=9s1zavcW81uz8wOBM_2X2KqdLNoc3E9bt0Oqt2-Sgmk,33926
|
|
163
|
-
tests/test_tag_extractor.py,sha256=
|
|
185
|
+
tests/test_tag_extractor.py,sha256=7eVD10Y1uLkoSrEgqkXzRvPFs8lJ1RiaJzDu7ml_FZE,3118
|
|
164
186
|
tests/test_template_loader.py,sha256=R7s8HJZbKqja-1TRBMBkVKPTgajofUjjRKUJq7a3_Oc,7427
|
|
165
187
|
tests/test_template_manager.py,sha256=qliEeYgAEakn8JIqIHa8u0Ht6DY4L3T6DcHBXkjzR4I,4167
|
|
166
188
|
tests/test_update_config_prompt.py,sha256=xsqj1WTn4BsG5Q2t-sNPfu7EoMURFcS-hfb5VSXUnJc,6765
|
|
167
|
-
ara_cli-0.1.10.
|
|
168
|
-
ara_cli-0.1.10.
|
|
169
|
-
ara_cli-0.1.10.
|
|
170
|
-
ara_cli-0.1.10.
|
|
171
|
-
ara_cli-0.1.10.
|
|
189
|
+
ara_cli-0.1.10.1.dist-info/METADATA,sha256=-t9lEUM9L9Jf3Cc9LpgOqe8cSrcv2LMLe_qpTvV2aZU,6834
|
|
190
|
+
ara_cli-0.1.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
191
|
+
ara_cli-0.1.10.1.dist-info/entry_points.txt,sha256=v4h7MzysTgSIDYfEo3oj4Kz_8lzsRa3hq-KJHEcLVX8,45
|
|
192
|
+
ara_cli-0.1.10.1.dist-info/top_level.txt,sha256=WM4cLHT5DYUaWzLtRj-gu3yVNFpGQ6lLRI3FMmC-38I,14
|
|
193
|
+
ara_cli-0.1.10.1.dist-info/RECORD,,
|
tests/test_artefact_scan.py
CHANGED
|
@@ -132,7 +132,7 @@ def test_is_rule_valid_rule_is_none():
|
|
|
132
132
|
"parent,expected",
|
|
133
133
|
[
|
|
134
134
|
(None, True), # parent is None
|
|
135
|
-
(MagicMock(rules=None),
|
|
135
|
+
(MagicMock(rules=None), False), # parent.rules is None
|
|
136
136
|
],
|
|
137
137
|
)
|
|
138
138
|
def test_is_rule_valid_parent_or_rules_none(parent, expected):
|
tests/test_tag_extractor.py
CHANGED
|
@@ -8,10 +8,11 @@ from ara_cli.list_filter import ListFilter
|
|
|
8
8
|
def artefact():
|
|
9
9
|
"""Fixture to create a mock artefact object."""
|
|
10
10
|
class Artefact:
|
|
11
|
-
def __init__(self, tags, status, users, path="dummy.md", content=""):
|
|
11
|
+
def __init__(self, tags, status, users, author="creator_unknown", path="dummy.md", content=""):
|
|
12
12
|
self.tags = tags
|
|
13
13
|
self.status = status
|
|
14
14
|
self.users = users
|
|
15
|
+
self.author = author
|
|
15
16
|
self.path = path
|
|
16
17
|
self.content = content
|
|
17
18
|
return Artefact
|
|
@@ -21,33 +22,33 @@ def artefact():
|
|
|
21
22
|
(
|
|
22
23
|
False, False, None,
|
|
23
24
|
{'artefacts': [
|
|
24
|
-
(['tag1', 'tag2'], 'in-progress', ['user1']),
|
|
25
|
-
(['tag3'], 'done', ['user2'])
|
|
25
|
+
(['tag1', 'tag2'], 'in-progress', ['user1'], "creator_unknown"),
|
|
26
|
+
(['tag3'], 'done', ['user2'], "creator_unknown")
|
|
26
27
|
]},
|
|
27
|
-
['done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
|
|
28
|
+
['creator_unknown', 'done', 'in-progress', 'tag1', 'tag2', 'tag3', 'user_user1', 'user_user2']
|
|
28
29
|
),
|
|
29
30
|
(
|
|
30
31
|
False, True, None,
|
|
31
32
|
{'artefacts': [
|
|
32
|
-
(['project_a', 'priority_high'], None, ['user1']),
|
|
33
|
-
(['feature_x'], 'done', ['user2'])
|
|
33
|
+
(['project_a', 'priority_high'], None, ['user1'], "creator_unknown"),
|
|
34
|
+
(['feature_x'], 'done', ['user2'], "creator_unknown")
|
|
34
35
|
]},
|
|
35
36
|
['project_a']
|
|
36
37
|
),
|
|
37
38
|
(
|
|
38
|
-
False, False, ListFilter(include_tags=['
|
|
39
|
+
False, False, ListFilter(include_tags=['kritik']),
|
|
39
40
|
{'artefacts': [
|
|
40
|
-
(['release', 'kritik'], 'review', ['dev1']),
|
|
41
|
-
(['bugfix'], 'to-do', ['dev2'])
|
|
41
|
+
(['release', 'kritik'], 'review', ['dev1'], "creator_unknown"),
|
|
42
|
+
(['bugfix'], 'to-do', ['dev2'], "creator_unknown")
|
|
42
43
|
]},
|
|
43
|
-
['kritik', 'release', 'review', 'user_dev1']
|
|
44
|
+
['creator_unknown', 'kritik', 'release', 'review', 'user_dev1']
|
|
44
45
|
),
|
|
45
46
|
(
|
|
46
47
|
True, False, None,
|
|
47
48
|
{'artefacts': [
|
|
48
|
-
(['tag3'], 'status2', ['user3'])
|
|
49
|
+
(['tag3'], 'status2', ['user3'], "creator_unknown")
|
|
49
50
|
]},
|
|
50
|
-
['status2', 'tag3', 'user_user3']
|
|
51
|
+
['creator_unknown', 'status2', 'tag3', 'user_user3']
|
|
51
52
|
),
|
|
52
53
|
(
|
|
53
54
|
False, False, None,
|
|
@@ -80,4 +81,9 @@ def test_extract_tags(mock_directory_navigator, mock_artefact_reader, artefact,
|
|
|
80
81
|
|
|
81
82
|
mock_artefact_reader.read_artefacts.assert_called_once()
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
# Convert dictionary result to flat list for comparison
|
|
85
|
+
actual_tags = []
|
|
86
|
+
for group in result.values():
|
|
87
|
+
actual_tags.extend(group)
|
|
88
|
+
|
|
89
|
+
assert sorted(actual_tags) == sorted(expected_tags)
|