ara-cli 0.1.9.72__py3-none-any.whl → 0.1.9.74__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/ara_command_action.py +15 -15
- ara_cli/ara_command_parser.py +2 -1
- ara_cli/artefact_autofix.py +77 -46
- ara_cli/artefact_creator.py +1 -1
- ara_cli/artefact_models/artefact_model.py +26 -7
- ara_cli/artefact_models/artefact_templates.py +47 -31
- ara_cli/artefact_models/feature_artefact_model.py +2 -0
- ara_cli/artefact_scan.py +0 -1
- ara_cli/chat.py +24 -6
- ara_cli/template_manager.py +3 -8
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.72.dist-info → ara_cli-0.1.9.74.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.72.dist-info → ara_cli-0.1.9.74.dist-info}/RECORD +18 -28
- tests/test_artefact_autofix.py +289 -25
- tests/test_chat.py +34 -14
- ara_cli/templates/template.businessgoal +0 -10
- ara_cli/templates/template.capability +0 -10
- ara_cli/templates/template.epic +0 -15
- ara_cli/templates/template.example +0 -6
- ara_cli/templates/template.feature +0 -26
- ara_cli/templates/template.issue +0 -14
- ara_cli/templates/template.keyfeature +0 -15
- ara_cli/templates/template.task +0 -6
- ara_cli/templates/template.userstory +0 -17
- ara_cli/templates/template.vision +0 -14
- {ara_cli-0.1.9.72.dist-info → ara_cli-0.1.9.74.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.72.dist-info → ara_cli-0.1.9.74.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.72.dist-info → ara_cli-0.1.9.74.dist-info}/top_level.txt +0 -0
ara_cli/ara_command_action.py
CHANGED
|
@@ -347,6 +347,7 @@ def reconnect_action(args):
|
|
|
347
347
|
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
348
348
|
from ara_cli.artefact_models.artefact_model import Contribution
|
|
349
349
|
from ara_cli.artefact_reader import ArtefactReader
|
|
350
|
+
from ara_cli.file_classifier import FileClassifier
|
|
350
351
|
from ara_cli.artefact_fuzzy_search import find_closest_rule
|
|
351
352
|
|
|
352
353
|
classifier = args.classifier
|
|
@@ -359,31 +360,29 @@ def reconnect_action(args):
|
|
|
359
360
|
|
|
360
361
|
feedback_message = f"Updated contribution of {classifier} '{artefact_name}' to {parent_classifier} '{parent_name}'"
|
|
361
362
|
|
|
362
|
-
|
|
363
|
+
file_classifier = FileClassifier(os)
|
|
364
|
+
classified_file_info = file_classifier.classify_files()
|
|
365
|
+
|
|
366
|
+
artefact = ArtefactReader.read_artefact(
|
|
363
367
|
artefact_name=artefact_name,
|
|
364
|
-
classifier=classifier
|
|
368
|
+
classifier=classifier,
|
|
369
|
+
classified_file_info=classified_file_info
|
|
365
370
|
)
|
|
366
|
-
|
|
371
|
+
|
|
372
|
+
if not artefact:
|
|
367
373
|
print(read_error_message)
|
|
368
374
|
return
|
|
369
375
|
|
|
370
|
-
|
|
376
|
+
parent = ArtefactReader.read_artefact(
|
|
371
377
|
artefact_name=parent_name,
|
|
372
|
-
classifier=parent_classifier
|
|
378
|
+
classifier=parent_classifier,
|
|
379
|
+
classified_file_info=classified_file_info
|
|
373
380
|
)
|
|
374
|
-
|
|
381
|
+
|
|
382
|
+
if not parent:
|
|
375
383
|
print(read_error_message)
|
|
376
384
|
return
|
|
377
385
|
|
|
378
|
-
artefact = artefact_from_content(
|
|
379
|
-
content=content,
|
|
380
|
-
)
|
|
381
|
-
artefact._file_path = artefact_info["file_path"]
|
|
382
|
-
|
|
383
|
-
parent = artefact_from_content(
|
|
384
|
-
content=parent_content
|
|
385
|
-
)
|
|
386
|
-
|
|
387
386
|
contribution = Contribution(
|
|
388
387
|
artefact_name=parent.title,
|
|
389
388
|
classifier=parent.artefact_type
|
|
@@ -603,6 +602,7 @@ def autofix_action(args):
|
|
|
603
602
|
file_path,
|
|
604
603
|
classifier,
|
|
605
604
|
reason,
|
|
605
|
+
single_pass=args.single_pass,
|
|
606
606
|
deterministic=run_deterministic,
|
|
607
607
|
non_deterministic=run_non_deterministic,
|
|
608
608
|
classified_artefact_info=classified_artefact_info
|
ara_cli/ara_command_parser.py
CHANGED
|
@@ -229,7 +229,8 @@ def scan_parser(subparsers):
|
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
def autofix_parser(subparsers):
|
|
232
|
-
autofix_parser = subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command.")
|
|
232
|
+
autofix_parser = subparsers.add_parser("autofix", help="Fix ARA tree with llm models for scanned artefacts with ara scan command. By default three attemps for every file.")
|
|
233
|
+
autofix_parser.add_argument("--single-pass", action="store_true", help="Run the autofix once for every scaned file.")
|
|
233
234
|
determinism_group = autofix_parser.add_mutually_exclusive_group()
|
|
234
235
|
determinism_group.add_argument("--deterministic", "-d", action="store_true", help="Run only deterministic fixes e.g Title-FileName Mismatch fix")
|
|
235
236
|
determinism_group.add_argument("--non-deterministic", "-nd", action="store_true", help="Run only non-deterministic fixes")
|
ara_cli/artefact_autofix.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from ara_cli.artefact_scan import check_file
|
|
1
2
|
from ara_cli.artefact_fuzzy_search import (
|
|
2
3
|
find_closest_name_matches,
|
|
3
4
|
extract_artefact_names_of_classifier,
|
|
@@ -159,7 +160,7 @@ def ask_for_correct_contribution(
|
|
|
159
160
|
|
|
160
161
|
print(
|
|
161
162
|
f"Can not determine a match for contribution {contribution_message}. "
|
|
162
|
-
f"Please provide a valid contribution or contribution will be empty (
|
|
163
|
+
f"Please provide a valid contribution or contribution will be empty ([classifier] [file_name])."
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
user_input = input().strip()
|
|
@@ -179,7 +180,9 @@ def ask_for_correct_contribution(
|
|
|
179
180
|
def ask_for_contribution_choice(
|
|
180
181
|
choices, artefact_info: Optional[tuple[str, str]] = None
|
|
181
182
|
) -> Optional[str]:
|
|
182
|
-
artefact_name, artefact_classifier =
|
|
183
|
+
artefact_name, artefact_classifier = (
|
|
184
|
+
artefact_info if artefact_info else (None, None)
|
|
185
|
+
)
|
|
183
186
|
message = "Found multiple close matches for the contribution"
|
|
184
187
|
if artefact_name and artefact_classifier:
|
|
185
188
|
message += f" of the {artefact_classifier} '{artefact_name}'"
|
|
@@ -379,68 +382,96 @@ def apply_autofix(
|
|
|
379
382
|
file_path: str,
|
|
380
383
|
classifier: str,
|
|
381
384
|
reason: str,
|
|
385
|
+
single_pass: bool = False,
|
|
382
386
|
deterministic: bool = True,
|
|
383
387
|
non_deterministic: bool = True,
|
|
384
388
|
classified_artefact_info: Optional[Dict[str, List[Dict[str, str]]]] = None,
|
|
385
389
|
) -> bool:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
"""
|
|
391
|
+
Applies fixes to a single artefact file iteratively until it is valid
|
|
392
|
+
or a fix cannot be applied. If single_pass is True, it runs for only one attempt.
|
|
393
|
+
"""
|
|
390
394
|
artefact_type, artefact_class = determine_artefact_type_and_class(classifier)
|
|
391
395
|
if artefact_type is None or artefact_class is None:
|
|
392
396
|
return False
|
|
393
397
|
|
|
394
398
|
if classified_artefact_info is None:
|
|
395
399
|
file_classifier = FileClassifier(os)
|
|
396
|
-
|
|
400
|
+
classified_artefact_info = file_classifier.classify_files()
|
|
397
401
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
401
|
-
|
|
402
|
+
if single_pass:
|
|
403
|
+
max_attempts = 1
|
|
404
|
+
print(f"Single-pass mode enabled for {file_path}. Running for 1 attempt.")
|
|
405
|
+
else:
|
|
406
|
+
max_attempts = 3
|
|
407
|
+
|
|
408
|
+
for attempt in range(max_attempts):
|
|
409
|
+
is_valid, current_reason = check_file(file_path, artefact_class, classified_artefact_info)
|
|
410
|
+
|
|
411
|
+
if is_valid:
|
|
412
|
+
print(f"✅ Artefact at {file_path} is now valid.")
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
print(f"Attempting to fix {file_path} (Attempt {attempt + 1}/{max_attempts})...")
|
|
416
|
+
print(f" Reason: {current_reason}")
|
|
417
|
+
|
|
418
|
+
artefact_text = read_artefact(file_path)
|
|
419
|
+
if artefact_text is None:
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
deterministic_markers_to_functions = {
|
|
423
|
+
"Filename-Title Mismatch": fix_title_mismatch,
|
|
424
|
+
"Invalid Contribution Reference": fix_contribution,
|
|
425
|
+
}
|
|
402
426
|
|
|
403
|
-
try:
|
|
404
427
|
deterministic_issue = next(
|
|
405
428
|
(
|
|
406
429
|
marker
|
|
407
|
-
for marker in deterministic_markers_to_functions
|
|
408
|
-
if marker in
|
|
430
|
+
for marker in deterministic_markers_to_functions
|
|
431
|
+
if marker in current_reason
|
|
409
432
|
),
|
|
410
433
|
None,
|
|
411
434
|
)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
|
|
436
|
+
corrected_text = None
|
|
437
|
+
|
|
438
|
+
if deterministic and deterministic_issue:
|
|
439
|
+
print(f"Applying deterministic fix for '{deterministic_issue}'...")
|
|
440
|
+
fix_function = deterministic_markers_to_functions[deterministic_issue]
|
|
441
|
+
corrected_text = fix_function(
|
|
442
|
+
file_path=file_path,
|
|
443
|
+
artefact_text=artefact_text,
|
|
444
|
+
artefact_class=artefact_class,
|
|
445
|
+
classified_artefact_info=classified_artefact_info,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
elif non_deterministic and not deterministic_issue:
|
|
449
|
+
print("Applying non-deterministic (LLM) fix...")
|
|
450
|
+
prompt = construct_prompt(artefact_type, current_reason, file_path, artefact_text)
|
|
451
|
+
try:
|
|
452
|
+
corrected_artefact = run_agent(prompt, artefact_class)
|
|
453
|
+
corrected_text = corrected_artefact.serialize()
|
|
454
|
+
except Exception as e:
|
|
455
|
+
print(f" ❌ LLM agent failed to fix artefact at {file_path}: {e}")
|
|
456
|
+
return False
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
if not non_deterministic and not deterministic_issue:
|
|
460
|
+
print(f"Skipping non-deterministic fix for {file_path} as per request.")
|
|
461
|
+
else:
|
|
462
|
+
print(f"Skipping fix for {file_path} as per request flags.")
|
|
438
463
|
return False
|
|
439
464
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
465
|
+
if corrected_text is not None and corrected_text.strip() != artefact_text.strip():
|
|
466
|
+
write_corrected_artefact(file_path, corrected_text)
|
|
467
|
+
|
|
468
|
+
print(" File modified. Re-classifying artefact information for next check...")
|
|
469
|
+
file_classifier = FileClassifier(os)
|
|
470
|
+
classified_artefact_info = file_classifier.classify_files()
|
|
471
|
+
|
|
472
|
+
else:
|
|
473
|
+
print(" Fixing attempt did not alter the file. Stopping to prevent infinite loop.")
|
|
474
|
+
return False
|
|
445
475
|
|
|
446
|
-
|
|
476
|
+
print(f"❌ Failed to fix {file_path} after {max_attempts} attempts.")
|
|
477
|
+
return False
|
ara_cli/artefact_creator.py
CHANGED
|
@@ -106,7 +106,7 @@ class ArtefactCreator:
|
|
|
106
106
|
if not self.handle_existing_files(file_exists):
|
|
107
107
|
return
|
|
108
108
|
|
|
109
|
-
artefact = template_artefact_of_type(classifier, filename)
|
|
109
|
+
artefact = template_artefact_of_type(classifier, filename, False)
|
|
110
110
|
|
|
111
111
|
if parent_classifier and parent_name:
|
|
112
112
|
artefact.set_contribution(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
2
|
-
from typing import Optional, List, Literal, Union, Dict
|
|
2
|
+
from typing import Optional, List, Literal, Union, Dict, ClassVar
|
|
3
3
|
from typing_extensions import Self
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
@@ -47,13 +47,23 @@ class Contribution(BaseModel):
|
|
|
47
47
|
description="Rule the contribution is using. The classifier of the parent must be userstory or epic if this is used"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
PLACEHOLDER_NAME: ClassVar[str] = "<filename or title of the artefact>"
|
|
51
|
+
PLACEHOLDER_CLASSIFIER: ClassVar[str] = "<agile requirement artefact category> <(optional in case the contribution is to an artefact that is detailed with rules)"
|
|
52
|
+
PLACEHOLDER_RULE: ClassVar[str] = "<rule as it is formulated>"
|
|
53
|
+
|
|
50
54
|
@model_validator(mode="after")
|
|
51
55
|
def validate_parent(self) -> Self:
|
|
52
|
-
|
|
53
56
|
artefact_name = self.artefact_name
|
|
54
57
|
classifier = self.classifier
|
|
55
58
|
rule = self.rule
|
|
56
59
|
|
|
60
|
+
if (
|
|
61
|
+
artefact_name == Contribution.PLACEHOLDER_NAME
|
|
62
|
+
or classifier == Contribution.PLACEHOLDER_CLASSIFIER
|
|
63
|
+
or rule == Contribution.PLACEHOLDER_RULE
|
|
64
|
+
):
|
|
65
|
+
return self
|
|
66
|
+
|
|
57
67
|
if artefact_name:
|
|
58
68
|
artefact_name = replace_space_with_underscore(artefact_name)
|
|
59
69
|
if not artefact_name or not classifier:
|
|
@@ -68,7 +78,7 @@ class Contribution(BaseModel):
|
|
|
68
78
|
|
|
69
79
|
@field_validator('artefact_name')
|
|
70
80
|
def validate_artefact_name(cls, value):
|
|
71
|
-
if not value:
|
|
81
|
+
if not value or value == Contribution.PLACEHOLDER_NAME:
|
|
72
82
|
return value
|
|
73
83
|
if ' ' in value:
|
|
74
84
|
warnings.warn(message="artefact_name can not contain spaces. Replacing spaces with '_'")
|
|
@@ -77,7 +87,7 @@ class Contribution(BaseModel):
|
|
|
77
87
|
|
|
78
88
|
@field_validator('classifier', mode='after')
|
|
79
89
|
def validate_classifier(cls, v):
|
|
80
|
-
if not v:
|
|
90
|
+
if not v or v == Contribution.PLACEHOLDER_CLASSIFIER:
|
|
81
91
|
return v
|
|
82
92
|
try:
|
|
83
93
|
return ArtefactType(v)
|
|
@@ -91,12 +101,21 @@ class Contribution(BaseModel):
|
|
|
91
101
|
if not line.startswith(contribution_line_start):
|
|
92
102
|
raise ValueError(f"Contribution line '{line}' does not start with '{contribution_line_start}'")
|
|
93
103
|
|
|
104
|
+
parent_text = line[len(contribution_line_start):].strip()
|
|
105
|
+
rule_specifier = " using rule "
|
|
106
|
+
|
|
107
|
+
placeholder_line = f"{cls.PLACEHOLDER_NAME} {cls.PLACEHOLDER_CLASSIFIER}{rule_specifier}{cls.PLACEHOLDER_RULE}"
|
|
108
|
+
if parent_text == placeholder_line:
|
|
109
|
+
return cls(
|
|
110
|
+
artefact_name=cls.PLACEHOLDER_NAME,
|
|
111
|
+
classifier=cls.PLACEHOLDER_CLASSIFIER,
|
|
112
|
+
rule=cls.PLACEHOLDER_RULE
|
|
113
|
+
)
|
|
114
|
+
|
|
94
115
|
artefact_name = None
|
|
95
116
|
classifier = None
|
|
96
117
|
rule = None
|
|
97
118
|
|
|
98
|
-
parent_text = line[len(contribution_line_start):].strip()
|
|
99
|
-
rule_specifier = " using rule "
|
|
100
119
|
if rule_specifier in parent_text:
|
|
101
120
|
parent_text, rule_text = parent_text.split(rule_specifier, 1)
|
|
102
121
|
rule = rule_text
|
|
@@ -113,7 +132,7 @@ class Contribution(BaseModel):
|
|
|
113
132
|
def serialize(self) -> str:
|
|
114
133
|
if not self.classifier or not self.artefact_name:
|
|
115
134
|
return ""
|
|
116
|
-
artefact_type = Classifier.get_artefact_title(self.classifier)
|
|
135
|
+
artefact_type = Classifier.get_artefact_title(self.classifier) or self.classifier
|
|
117
136
|
artefact_name = replace_underscore_with_space(self.artefact_name)
|
|
118
137
|
contribution = f"{artefact_name} {artefact_type}"
|
|
119
138
|
if self.rule:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ara_cli.artefact_models.artefact_model import ArtefactType, Artefact
|
|
1
|
+
from ara_cli.artefact_models.artefact_model import ArtefactType, Artefact, Contribution
|
|
2
2
|
from ara_cli.artefact_models.vision_artefact_model import VisionArtefact, VisionIntent
|
|
3
3
|
from ara_cli.artefact_models.businessgoal_artefact_model import BusinessgoalArtefact, BusinessgoalIntent
|
|
4
4
|
from ara_cli.artefact_models.capability_artefact_model import CapabilityArtefact, CapabilityIntent
|
|
@@ -11,7 +11,15 @@ from ara_cli.artefact_models.task_artefact_model import TaskArtefact
|
|
|
11
11
|
from ara_cli.artefact_models.issue_artefact_model import IssueArtefact
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def default_contribution() -> Contribution:
|
|
15
|
+
return Contribution(
|
|
16
|
+
artefact_name=Contribution.PLACEHOLDER_NAME,
|
|
17
|
+
classifier=Contribution.PLACEHOLDER_CLASSIFIER,
|
|
18
|
+
rule=Contribution.PLACEHOLDER_RULE
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _default_vision(title: str, use_default_contribution: bool) -> VisionArtefact:
|
|
15
23
|
intent = VisionIntent(
|
|
16
24
|
for_="<target customer>",
|
|
17
25
|
who="<needs something>",
|
|
@@ -24,11 +32,12 @@ def _default_vision(title: str) -> VisionArtefact:
|
|
|
24
32
|
tags=["sample_tag"],
|
|
25
33
|
title=title,
|
|
26
34
|
description="<further optional description to understand the vision, markdown capable text formatting>",
|
|
27
|
-
intent=intent
|
|
35
|
+
intent=intent,
|
|
36
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
28
37
|
)
|
|
29
38
|
|
|
30
39
|
|
|
31
|
-
def _default_businessgoal(title: str) -> BusinessgoalArtefact:
|
|
40
|
+
def _default_businessgoal(title: str, use_default_contribution: bool) -> BusinessgoalArtefact:
|
|
32
41
|
intent = BusinessgoalIntent(
|
|
33
42
|
in_order_to="<reach primarily a monetary business goal>",
|
|
34
43
|
as_a="<business related role>",
|
|
@@ -37,26 +46,26 @@ def _default_businessgoal(title: str) -> BusinessgoalArtefact:
|
|
|
37
46
|
return BusinessgoalArtefact(
|
|
38
47
|
tags=["sample_tag"],
|
|
39
48
|
title=title,
|
|
40
|
-
description="<further optional description to understand the
|
|
41
|
-
intent=intent
|
|
49
|
+
description="<further optional description to understand the businessgoal, markdown capable text formatting>",
|
|
50
|
+
intent=intent,
|
|
51
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
42
52
|
)
|
|
43
53
|
|
|
44
54
|
|
|
45
|
-
def _default_capability(title: str) -> CapabilityArtefact:
|
|
55
|
+
def _default_capability(title: str, use_default_contribution: bool) -> CapabilityArtefact:
|
|
46
56
|
intent = CapabilityIntent(
|
|
47
|
-
|
|
48
|
-
as_a="<business related role>",
|
|
49
|
-
to_be_able_to="<something that helps me to reach my monetary goal>"
|
|
57
|
+
to_be_able_to="<needed capability for stakeholders that are the enablers/relevant for reaching the business goal>"
|
|
50
58
|
)
|
|
51
59
|
return CapabilityArtefact(
|
|
52
60
|
tags=["sample_tag"],
|
|
53
61
|
title=title,
|
|
54
62
|
description="<further optional description to understand the capability, markdown capable text formatting>",
|
|
55
|
-
intent=intent
|
|
63
|
+
intent=intent,
|
|
64
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
56
65
|
)
|
|
57
66
|
|
|
58
67
|
|
|
59
|
-
def _default_epic(title: str) -> EpicArtefact:
|
|
68
|
+
def _default_epic(title: str, use_default_contribution: bool) -> EpicArtefact:
|
|
60
69
|
intent = EpicIntent(
|
|
61
70
|
in_order_to="<achieve a benefit>",
|
|
62
71
|
as_a="<(user) role>",
|
|
@@ -70,13 +79,14 @@ def _default_epic(title: str) -> EpicArtefact:
|
|
|
70
79
|
return EpicArtefact(
|
|
71
80
|
tags=["sample_tag"],
|
|
72
81
|
title=title,
|
|
73
|
-
description="<further optional description to understand the
|
|
82
|
+
description="<further optional description to understand the epic, markdown capable text formatting>",
|
|
74
83
|
intent=intent,
|
|
75
|
-
rules=rules
|
|
84
|
+
rules=rules,
|
|
85
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
76
86
|
)
|
|
77
87
|
|
|
78
88
|
|
|
79
|
-
def _default_userstory(title: str) -> UserstoryArtefact:
|
|
89
|
+
def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryArtefact:
|
|
80
90
|
intent = UserstoryIntent(
|
|
81
91
|
in_order_to="<achieve a benefit>",
|
|
82
92
|
as_a="<(user) role>",
|
|
@@ -93,25 +103,27 @@ def _default_userstory(title: str) -> UserstoryArtefact:
|
|
|
93
103
|
description="<further optional description to understand the userstory, markdown capable text formatting>",
|
|
94
104
|
intent=intent,
|
|
95
105
|
rules=rules,
|
|
96
|
-
estimate="<story points, scale?>"
|
|
106
|
+
estimate="<story points, scale?>",
|
|
107
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
97
108
|
)
|
|
98
109
|
|
|
99
110
|
|
|
100
|
-
def _default_example(title: str) -> ExampleArtefact:
|
|
111
|
+
def _default_example(title: str, use_default_contribution: bool) -> ExampleArtefact:
|
|
101
112
|
return ExampleArtefact(
|
|
102
113
|
tags=["sample_tag"],
|
|
103
114
|
title=title,
|
|
104
115
|
description="<further optional description to understand the example, markdown capable text formatting>",
|
|
116
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
105
117
|
)
|
|
106
118
|
|
|
107
119
|
|
|
108
|
-
def _default_keyfeature(title: str) -> KeyfeatureArtefact:
|
|
120
|
+
def _default_keyfeature(title: str, use_default_contribution: bool) -> KeyfeatureArtefact:
|
|
109
121
|
intent = KeyfeatureIntent(
|
|
110
122
|
in_order_to="<support a capability or business goal>",
|
|
111
123
|
as_a="<main stakeholder who will benefit>",
|
|
112
124
|
i_want="<a product feature that helps me doing something so that I can achieve my named goal>"
|
|
113
125
|
)
|
|
114
|
-
description = """<further optional description to understand the
|
|
126
|
+
description = """<further optional description to understand the keyfeature, markdown capable text formatting, best practice is using
|
|
115
127
|
GIVEN any precondition
|
|
116
128
|
AND another precondition
|
|
117
129
|
WHEN some action takes place
|
|
@@ -121,11 +133,12 @@ def _default_keyfeature(title: str) -> KeyfeatureArtefact:
|
|
|
121
133
|
tags=["sample_tag"],
|
|
122
134
|
title=title,
|
|
123
135
|
description=description,
|
|
124
|
-
intent=intent
|
|
136
|
+
intent=intent,
|
|
137
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
125
138
|
)
|
|
126
139
|
|
|
127
140
|
|
|
128
|
-
def _default_feature(title: str) -> FeatureArtefact:
|
|
141
|
+
def _default_feature(title: str, use_default_contribution: bool) -> FeatureArtefact:
|
|
129
142
|
intent = FeatureIntent(
|
|
130
143
|
as_a="<user>",
|
|
131
144
|
i_want_to="<do something | need something>",
|
|
@@ -169,27 +182,28 @@ def _default_feature(title: str) -> FeatureArtefact:
|
|
|
169
182
|
]
|
|
170
183
|
)
|
|
171
184
|
]
|
|
172
|
-
description = """<further optional description to understand
|
|
173
|
-
the rule, no format defined, the example artefact is only a placeholder>"""
|
|
185
|
+
description = """<further optional description to understand the feature, no format defined, the example artefact is only a placeholder>"""
|
|
174
186
|
|
|
175
187
|
return FeatureArtefact(
|
|
176
188
|
tags=["sample_tag"],
|
|
177
189
|
title=title,
|
|
178
190
|
description=description,
|
|
179
191
|
intent=intent,
|
|
180
|
-
scenarios=scenarios
|
|
192
|
+
scenarios=scenarios,
|
|
193
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
181
194
|
)
|
|
182
195
|
|
|
183
196
|
|
|
184
|
-
def _default_task(title: str) -> TaskArtefact:
|
|
197
|
+
def _default_task(title: str, use_default_contribution: bool) -> TaskArtefact:
|
|
185
198
|
return TaskArtefact(
|
|
186
199
|
status="to-do",
|
|
187
200
|
title=title,
|
|
188
|
-
description="<further optional description to understand the task, no format defined>"
|
|
201
|
+
description="<further optional description to understand the task, no format defined>",
|
|
202
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
189
203
|
)
|
|
190
204
|
|
|
191
205
|
|
|
192
|
-
def _default_issue(title: str) -> IssueArtefact:
|
|
206
|
+
def _default_issue(title: str, use_default_contribution: bool) -> IssueArtefact:
|
|
193
207
|
description = "<further free text description to understand the issue, no format defined>"
|
|
194
208
|
additional_description = """*Optional descriptions of the issue in Gherkin style*
|
|
195
209
|
|
|
@@ -203,11 +217,12 @@ def _default_issue(title: str) -> IssueArtefact:
|
|
|
203
217
|
tags=["sample_tag"],
|
|
204
218
|
title=title,
|
|
205
219
|
description=description,
|
|
206
|
-
additional_description=additional_description
|
|
220
|
+
additional_description=additional_description,
|
|
221
|
+
contribution=default_contribution() if use_default_contribution else None
|
|
207
222
|
)
|
|
208
223
|
|
|
209
224
|
|
|
210
|
-
def template_artefact_of_type(artefact_type: ArtefactType, title: str = "<descriptive_title>") -> Artefact:
|
|
225
|
+
def template_artefact_of_type(artefact_type: ArtefactType, title: str = "<descriptive_title>", use_default_contribution: bool = True) -> Artefact:
|
|
211
226
|
default_creation_functions = {
|
|
212
227
|
ArtefactType.vision: _default_vision,
|
|
213
228
|
ArtefactType.businessgoal: _default_businessgoal,
|
|
@@ -220,5 +235,6 @@ def template_artefact_of_type(artefact_type: ArtefactType, title: str = "<descri
|
|
|
220
235
|
ArtefactType.task: _default_task,
|
|
221
236
|
ArtefactType.issue: _default_issue
|
|
222
237
|
}
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
if artefact_type not in default_creation_functions.keys():
|
|
239
|
+
return None
|
|
240
|
+
return default_creation_functions[artefact_type](title, use_default_contribution)
|
|
@@ -138,6 +138,7 @@ class Scenario(BaseModel):
|
|
|
138
138
|
@field_validator('title')
|
|
139
139
|
def validate_title(cls, v: str) -> str:
|
|
140
140
|
v = v.strip()
|
|
141
|
+
v = v.replace('_', ' ')
|
|
141
142
|
if not v:
|
|
142
143
|
raise ValueError("title must not be empty")
|
|
143
144
|
return v
|
|
@@ -181,6 +182,7 @@ class ScenarioOutline(BaseModel):
|
|
|
181
182
|
def validate_title(cls, v: str) -> str:
|
|
182
183
|
if not v:
|
|
183
184
|
raise ValueError("title must not be empty in a ScenarioOutline")
|
|
185
|
+
v = v.replace('_', ' ')
|
|
184
186
|
return v
|
|
185
187
|
|
|
186
188
|
@field_validator('steps', mode='before')
|
ara_cli/artefact_scan.py
CHANGED
|
@@ -38,7 +38,6 @@ def check_file(file_path, artefact_class, classified_artefact_info=None):
|
|
|
38
38
|
classifier=contribution.classifier
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
# Check if the referenced artefact exists
|
|
42
41
|
if contribution.artefact_name not in all_artefact_names:
|
|
43
42
|
reason = (f"Invalid Contribution Reference: The contribution references "
|
|
44
43
|
f"'{contribution.classifier}' artefact '{contribution.artefact_name}' "
|
ara_cli/chat.py
CHANGED
|
@@ -348,7 +348,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
348
348
|
|
|
349
349
|
@file_exists_check
|
|
350
350
|
def load_text_file(self, file_path, prefix: str = "", suffix: str = "", block_delimiter: str = ""):
|
|
351
|
-
with open(file_path, 'r', encoding='utf-8') as file:
|
|
351
|
+
with open(file_path, 'r', encoding='utf-8', errors="replace") as file:
|
|
352
352
|
file_content = file.read()
|
|
353
353
|
if block_delimiter:
|
|
354
354
|
file_content = f"{block_delimiter}\n{file_content}\n{block_delimiter}"
|
|
@@ -731,9 +731,27 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
|
|
|
731
731
|
@cmd2.with_category(CATEGORY_CHAT_CONTROL)
|
|
732
732
|
def do_LOAD_TEMPLATE(self, template_name):
|
|
733
733
|
"""Load artefact template"""
|
|
734
|
-
|
|
735
|
-
pattern = f"template.{template_name}"
|
|
736
|
-
file_type = "template"
|
|
737
|
-
exclude_pattern = os.path.join(directory, "template.*.prompt_log.md")
|
|
734
|
+
from ara_cli.artefact_models.artefact_templates import template_artefact_of_type
|
|
738
735
|
|
|
739
|
-
|
|
736
|
+
artefact = template_artefact_of_type(''.join(template_name))
|
|
737
|
+
if not artefact:
|
|
738
|
+
return
|
|
739
|
+
write_content = artefact.serialize()
|
|
740
|
+
self.add_prompt_tag_if_needed(self.chat_name)
|
|
741
|
+
with open(self.chat_name, 'a', encoding='utf-8') as chat_file:
|
|
742
|
+
chat_file.write(write_content)
|
|
743
|
+
print(f"Loaded {template_name} artefact template")
|
|
744
|
+
|
|
745
|
+
def complete_LOAD_TEMPLATE(self, text, line, begidx, endidx):
|
|
746
|
+
return self._complete_classifiers(self, text, line, begidx, endidx)
|
|
747
|
+
|
|
748
|
+
def _complete_classifiers(self, text, line, begidx, endidx):
|
|
749
|
+
from ara_cli.classifier import Classifier
|
|
750
|
+
|
|
751
|
+
classifiers = Classifier.ordered_classifiers()
|
|
752
|
+
if not text:
|
|
753
|
+
completions = classifiers
|
|
754
|
+
else:
|
|
755
|
+
completions = [classifier for classifier in classifiers if classifier.startswith(text)]
|
|
756
|
+
|
|
757
|
+
return completions
|
ara_cli/template_manager.py
CHANGED
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from shutil import copy
|
|
5
5
|
from ara_cli.classifier import Classifier
|
|
6
6
|
from ara_cli.directory_navigator import DirectoryNavigator
|
|
7
|
+
from ara_cli.artefact_models.artefact_templates import template_artefact_of_type
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class TemplatePathManager:
|
|
@@ -30,14 +31,8 @@ class TemplatePathManager:
|
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
def get_template_content(self, classifier):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
template_path = (base_path / f"template.{classifier}")
|
|
36
|
-
|
|
37
|
-
with template_path.open('r', encoding='utf-8') as file:
|
|
38
|
-
content = file.read()
|
|
39
|
-
|
|
40
|
-
return content
|
|
34
|
+
artefact = template_artefact_of_type(classifier)
|
|
35
|
+
return artefact.serialize()
|
|
41
36
|
|
|
42
37
|
|
|
43
38
|
class ArtefactFileManager:
|
ara_cli/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1.9.
|
|
2
|
+
__version__ = "0.1.9.74" # fith parameter like .0 for local install test purposes only. official numbers should be 4 digit numbers
|