ara-cli 0.1.9.50__py3-none-any.whl → 0.1.9.52__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ara_cli/__main__.py +3 -1
- ara_cli/analyse_artefacts.py +133 -0
- ara_cli/ara_command_action.py +91 -76
- ara_cli/ara_command_parser.py +5 -0
- ara_cli/ara_config.py +65 -38
- ara_cli/artefact_creator.py +3 -4
- ara_cli/artefact_lister.py +57 -48
- ara_cli/artefact_models/artefact_model.py +28 -14
- ara_cli/artefact_models/feature_artefact_model.py +4 -1
- ara_cli/artefact_reader.py +104 -63
- ara_cli/artefact_renamer.py +8 -8
- ara_cli/artefact_scan.py +46 -0
- ara_cli/file_classifier.py +10 -32
- ara_cli/prompt_extractor.py +10 -2
- ara_cli/tag_extractor.py +6 -16
- ara_cli/templates/specification_breakdown_files/template.concept.md +12 -14
- ara_cli/tests/test_ara_command_action.py +242 -108
- ara_cli/tests/test_artefact_lister.py +598 -171
- ara_cli/tests/test_artefact_reader.py +7 -76
- ara_cli/tests/test_artefact_scan.py +126 -0
- ara_cli/tests/test_file_classifier.py +69 -30
- ara_cli/tests/test_tag_extractor.py +42 -61
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/RECORD +28 -27
- ara_cli/artefact.py +0 -172
- ara_cli/tests/test_artefact.py +0 -304
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/top_level.txt +0 -0
ara_cli/__main__.py
CHANGED
|
@@ -18,6 +18,7 @@ from ara_cli.ara_command_action import (
|
|
|
18
18
|
set_status_action,
|
|
19
19
|
set_user_action,
|
|
20
20
|
classifier_directory_action,
|
|
21
|
+
scan_action
|
|
21
22
|
)
|
|
22
23
|
import argcomplete
|
|
23
24
|
import sys
|
|
@@ -40,7 +41,8 @@ def define_action_mapping():
|
|
|
40
41
|
"read-user": read_user_action,
|
|
41
42
|
"set-status": set_status_action,
|
|
42
43
|
"set-user": set_user_action,
|
|
43
|
-
"classifier-directory": classifier_directory_action
|
|
44
|
+
"classifier-directory": classifier_directory_action,
|
|
45
|
+
"scan": scan_action
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
analyse_artefacts.py
|
|
4
|
+
|
|
5
|
+
Walk a directory tree, try to deserialize every *.vision *.epic *.task … file
|
|
6
|
+
with the appropriate Pydantic artefact model, and write the list of files that
|
|
7
|
+
fail validation (plus the reason) to 'invalid_artefacts.txt'.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
python analyse_artefacts.py <path_to_ara_folder ex: ./ara/>
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from ara_cli.artefact_models import businessgoal_artefact_model, capability_artefact_model, epic_artefact_model, example_artefact_model, feature_artefact_model, issue_artefact_model, keyfeature_artefact_model, userstory_artefact_model, task_artefact_model, vision_artefact_model
|
|
15
|
+
from ara_cli.artefact_models.artefact_model import Artefact, ArtefactType
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
from typing import Dict, Type, List, Tuple
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# --- import your domain model ----------------------------------------------
|
|
24
|
+
# Make sure this import path matches your project layout.
|
|
25
|
+
# (e.g. from ara_cli.artefact_model import Artefact, ArtefactType)
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_type_map() -> Dict[ArtefactType, Type[Artefact]]:
|
|
30
|
+
type_map: Dict[ArtefactType, Type[Artefact]] = {}
|
|
31
|
+
queue: List[Type[Artefact]] = list(Artefact.__subclasses__())
|
|
32
|
+
while queue:
|
|
33
|
+
cls = queue.pop()
|
|
34
|
+
try:
|
|
35
|
+
artefact_type = cls._artefact_type()
|
|
36
|
+
type_map[artefact_type] = cls
|
|
37
|
+
except Exception:
|
|
38
|
+
pass # abstract / helper subclass
|
|
39
|
+
queue.extend(cls.__subclasses__())
|
|
40
|
+
if not type_map:
|
|
41
|
+
raise RuntimeError("No concrete Artefact subclasses found!")
|
|
42
|
+
return type_map
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_artefact_files(root: Path, valid_exts: List[str]) -> List[Path]:
|
|
46
|
+
return [
|
|
47
|
+
p for p in root.rglob("*")
|
|
48
|
+
if p.is_file() and p.suffix.lstrip(".") in valid_exts
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def scan_folder(
|
|
53
|
+
root_folder: Path,
|
|
54
|
+
detailed_report: Path,
|
|
55
|
+
names_only_report: Path,
|
|
56
|
+
checklist_report: Path
|
|
57
|
+
) -> Tuple[int, int]:
|
|
58
|
+
type_map = build_type_map()
|
|
59
|
+
valid_exts = [t.value for t in type_map]
|
|
60
|
+
|
|
61
|
+
artefact_files = find_artefact_files(root_folder, valid_exts)
|
|
62
|
+
bad: List[Tuple[Path, str]] = []
|
|
63
|
+
|
|
64
|
+
for file_path in artefact_files:
|
|
65
|
+
artefact_type = ArtefactType(file_path.suffix.lstrip("."))
|
|
66
|
+
artefact_cls = type_map[artefact_type]
|
|
67
|
+
text = file_path.read_text(encoding="utf-8")
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
artefact_cls.deserialize(text)
|
|
71
|
+
except (ValidationError, ValueError, AssertionError) as e:
|
|
72
|
+
bad.append((file_path, str(e)))
|
|
73
|
+
except Exception as e:
|
|
74
|
+
bad.append((file_path, f"Unexpected error: {e!r}"))
|
|
75
|
+
|
|
76
|
+
# ───────────── write reports ────────────────────────────────────────────
|
|
77
|
+
if bad:
|
|
78
|
+
# 1) detailed txt
|
|
79
|
+
with detailed_report.open("w", encoding="utf-8") as f:
|
|
80
|
+
f.write("Invalid artefacts (file → reason)\n\n")
|
|
81
|
+
for path, err in bad:
|
|
82
|
+
f.write(f"{path} --> {err}\n")
|
|
83
|
+
|
|
84
|
+
# 2) names-only txt
|
|
85
|
+
with names_only_report.open("w", encoding="utf-8") as f:
|
|
86
|
+
for path, _ in bad:
|
|
87
|
+
f.write(f"{path}\n")
|
|
88
|
+
|
|
89
|
+
# 3) markdown checklist
|
|
90
|
+
with checklist_report.open("w", encoding="utf-8") as f:
|
|
91
|
+
f.write("# 📋 Artefact-fix checklist\n\n")
|
|
92
|
+
f.write("Tick a box once you’ve fixed & validated the file.\n\n")
|
|
93
|
+
for path, err in bad:
|
|
94
|
+
f.write(f"- [ ] `{path}` – {err}\n")
|
|
95
|
+
|
|
96
|
+
print(
|
|
97
|
+
f"\nFinished. {len(bad)}/{len(artefact_files)} files are invalid."
|
|
98
|
+
f"\nReports generated:"
|
|
99
|
+
f"\n • {detailed_report}"
|
|
100
|
+
f"\n • {names_only_report}"
|
|
101
|
+
f"\n • {checklist_report}"
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
print(f"\nFinished. All {len(artefact_files)} artefacts are valid ✔️")
|
|
105
|
+
# clean up stale files from previous runs
|
|
106
|
+
for p in (detailed_report, names_only_report, checklist_report):
|
|
107
|
+
if p.exists():
|
|
108
|
+
p.unlink()
|
|
109
|
+
|
|
110
|
+
return len(artefact_files), len(bad)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ─────────────────────────────── main ──────────────────────────────────────
|
|
114
|
+
def main() -> None:
|
|
115
|
+
if len(sys.argv) < 2:
|
|
116
|
+
print("Usage: python scan_artefacts.py <folder_to_scan>")
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
root_folder = Path(sys.argv[1]).expanduser().resolve()
|
|
120
|
+
if not root_folder.is_dir():
|
|
121
|
+
print(f"Error: '{root_folder}' is not a directory.")
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
scan_folder(
|
|
125
|
+
root_folder=root_folder,
|
|
126
|
+
detailed_report=Path("invalid_artefacts.txt"),
|
|
127
|
+
names_only_report=Path("invalid_artefact_names.txt"),
|
|
128
|
+
checklist_report=Path("invalid_artefacts_checklist.md"),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
main()
|
ara_cli/ara_command_action.py
CHANGED
|
@@ -269,7 +269,6 @@ def fetch_templates_action(args):
|
|
|
269
269
|
|
|
270
270
|
def read_action(args):
|
|
271
271
|
from ara_cli.artefact_reader import ArtefactReader
|
|
272
|
-
from ara_cli.artefact import Artefact
|
|
273
272
|
from ara_cli.file_classifier import FileClassifier
|
|
274
273
|
|
|
275
274
|
classifier = args.classifier
|
|
@@ -277,17 +276,22 @@ def read_action(args):
|
|
|
277
276
|
read_mode = args.read_mode
|
|
278
277
|
|
|
279
278
|
file_classifier = FileClassifier(os)
|
|
280
|
-
classified_artefacts =
|
|
281
|
-
|
|
279
|
+
classified_artefacts = ArtefactReader.read_artefacts()
|
|
280
|
+
artefacts = classified_artefacts.get(classifier, [])
|
|
281
|
+
all_artefact_names = [a.title for a in artefacts]
|
|
282
|
+
|
|
282
283
|
if artefact_name not in all_artefact_names:
|
|
283
|
-
suggest_close_name_matches(
|
|
284
|
+
suggest_close_name_matches(
|
|
285
|
+
artefact_name,
|
|
286
|
+
all_artefact_names
|
|
287
|
+
)
|
|
284
288
|
return
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
target_artefact = next(filter(
|
|
291
|
+
lambda x: x.title == artefact_name, artefacts
|
|
292
|
+
))
|
|
287
293
|
|
|
288
294
|
artefacts_by_classifier = {classifier: []}
|
|
289
|
-
file_classifier = FileClassifier(os)
|
|
290
|
-
classified_artefacts = file_classifier.classify_files()
|
|
291
295
|
|
|
292
296
|
match read_mode:
|
|
293
297
|
case "branch":
|
|
@@ -309,83 +313,78 @@ def read_action(args):
|
|
|
309
313
|
print_content=True
|
|
310
314
|
)
|
|
311
315
|
case _:
|
|
312
|
-
|
|
313
|
-
artefact_path = next((classified_artefact.file_path for classified_artefact in classified_artefacts.get(classifier, []) if classified_artefact.name == artefact.name), artefact)
|
|
314
|
-
artefact._file_path = artefact_path
|
|
315
|
-
artefacts_by_classifier[classifier].append(artefact)
|
|
316
|
+
artefacts_by_classifier[classifier].append(target_artefact)
|
|
316
317
|
file_classifier.print_classified_files(artefacts_by_classifier, print_content=True)
|
|
317
318
|
|
|
318
319
|
|
|
319
320
|
def reconnect_action(args):
|
|
320
|
-
from ara_cli.
|
|
321
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
322
|
+
from ara_cli.artefact_models.artefact_model import Contribution
|
|
321
323
|
from ara_cli.artefact_reader import ArtefactReader
|
|
322
|
-
from ara_cli.file_classifier import FileClassifier
|
|
323
|
-
from ara_cli.directory_navigator import DirectoryNavigator
|
|
324
324
|
|
|
325
325
|
classifier = args.classifier
|
|
326
326
|
artefact_name = args.parameter
|
|
327
327
|
parent_classifier = args.parent_classifier
|
|
328
328
|
parent_name = args.parent_name
|
|
329
329
|
|
|
330
|
-
|
|
331
|
-
classified_artefacts = file_classifier.classify_files()
|
|
330
|
+
read_error_message = f"Could not connect {classifier} '{artefact_name}' to {parent_classifier} '{parent_name}'"
|
|
332
331
|
|
|
333
|
-
|
|
334
|
-
if artefact_name not in all_artefact_names:
|
|
335
|
-
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
336
|
-
return
|
|
337
|
-
|
|
338
|
-
all_artefact_names = [artefact.file_name for artefact in classified_artefacts.get(parent_classifier, [])]
|
|
339
|
-
if parent_name not in all_artefact_names:
|
|
340
|
-
suggest_close_name_matches(parent_name, all_artefact_names)
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
directory_navigator = DirectoryNavigator()
|
|
344
|
-
original_directory = directory_navigator.navigate_to_target()
|
|
345
|
-
|
|
346
|
-
content, file_path = ArtefactReader.read_artefact(
|
|
332
|
+
content, artefact_info = ArtefactReader.read_artefact(
|
|
347
333
|
artefact_name=artefact_name,
|
|
348
334
|
classifier=classifier
|
|
349
335
|
)
|
|
336
|
+
if not content:
|
|
337
|
+
print(read_error_message)
|
|
338
|
+
return
|
|
350
339
|
|
|
351
|
-
parent_content,
|
|
340
|
+
parent_content, parent_info = ArtefactReader.read_artefact(
|
|
352
341
|
artefact_name=parent_name,
|
|
353
342
|
classifier=parent_classifier
|
|
354
343
|
)
|
|
344
|
+
if not parent_content:
|
|
345
|
+
print(read_error_message)
|
|
346
|
+
return
|
|
355
347
|
|
|
356
|
-
artefact =
|
|
348
|
+
artefact = artefact_from_content(
|
|
357
349
|
content=content,
|
|
358
|
-
file_path=file_path
|
|
359
|
-
)
|
|
360
|
-
parent = Artefact.from_content(
|
|
361
|
-
content=parent_content,
|
|
362
|
-
file_path=parent_path
|
|
363
350
|
)
|
|
351
|
+
artefact._file_path = artefact_info["file_path"]
|
|
364
352
|
|
|
365
|
-
|
|
366
|
-
|
|
353
|
+
parent = artefact_from_content(
|
|
354
|
+
content=parent_content
|
|
355
|
+
)
|
|
367
356
|
|
|
368
|
-
|
|
357
|
+
artefact.contribution = Contribution(
|
|
358
|
+
artefact_name=parent.title,
|
|
359
|
+
classifier=parent.artefact_type
|
|
360
|
+
)
|
|
361
|
+
with open(artefact.file_path, 'w') as file:
|
|
362
|
+
artefact_content = artefact.serialize()
|
|
363
|
+
file.write(artefact_content)
|
|
369
364
|
|
|
370
365
|
|
|
371
366
|
def read_status_action(args):
|
|
372
|
-
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
373
|
-
from ara_cli.artefact_reader import ArtefactReader
|
|
374
367
|
from ara_cli.file_classifier import FileClassifier
|
|
368
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
375
369
|
|
|
376
370
|
classifier = args.classifier
|
|
377
371
|
artefact_name = args.parameter
|
|
378
372
|
|
|
379
373
|
file_classifier = FileClassifier(os)
|
|
380
|
-
|
|
374
|
+
artefact_info = file_classifier.classify_files()
|
|
375
|
+
artefact_info_dicts = artefact_info.get(classifier, [])
|
|
381
376
|
|
|
382
|
-
all_artefact_names = [
|
|
377
|
+
all_artefact_names = [artefact_info["title"] for artefact_info in artefact_info_dicts]
|
|
383
378
|
if artefact_name not in all_artefact_names:
|
|
384
379
|
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
385
380
|
return
|
|
386
381
|
|
|
387
|
-
|
|
382
|
+
artefact_info = next(filter(
|
|
383
|
+
lambda x: x["title"] == artefact_name, artefact_info_dicts
|
|
384
|
+
))
|
|
388
385
|
|
|
386
|
+
with open(artefact_info["file_path"], 'r') as file:
|
|
387
|
+
content = file.read()
|
|
389
388
|
artefact = artefact_from_content(content)
|
|
390
389
|
|
|
391
390
|
status = artefact.status
|
|
@@ -398,21 +397,26 @@ def read_status_action(args):
|
|
|
398
397
|
|
|
399
398
|
def read_user_action(args):
|
|
400
399
|
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
401
|
-
from ara_cli.artefact_reader import ArtefactReader
|
|
402
400
|
from ara_cli.file_classifier import FileClassifier
|
|
403
401
|
|
|
404
402
|
classifier = args.classifier
|
|
405
403
|
artefact_name = args.parameter
|
|
406
404
|
|
|
407
405
|
file_classifier = FileClassifier(os)
|
|
408
|
-
|
|
406
|
+
artefact_info = file_classifier.classify_files()
|
|
407
|
+
artefact_info_dicts = artefact_info.get(classifier, [])
|
|
409
408
|
|
|
410
|
-
all_artefact_names = [
|
|
409
|
+
all_artefact_names = [artefact_info["title"] for artefact_info in artefact_info_dicts]
|
|
411
410
|
if artefact_name not in all_artefact_names:
|
|
412
411
|
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
413
412
|
return
|
|
414
413
|
|
|
415
|
-
|
|
414
|
+
artefact_info = next(filter(
|
|
415
|
+
lambda x: x["title"] == artefact_name, artefact_info_dicts
|
|
416
|
+
))
|
|
417
|
+
|
|
418
|
+
with open(artefact_info["file_path"], 'r') as file:
|
|
419
|
+
content = file.read()
|
|
416
420
|
artefact = artefact_from_content(content)
|
|
417
421
|
|
|
418
422
|
user_tags = artefact.users
|
|
@@ -427,8 +431,6 @@ def read_user_action(args):
|
|
|
427
431
|
def set_status_action(args):
|
|
428
432
|
from ara_cli.artefact_models.artefact_model import ALLOWED_STATUS_VALUES
|
|
429
433
|
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
430
|
-
from ara_cli.directory_navigator import DirectoryNavigator
|
|
431
|
-
from ara_cli.artefact_reader import ArtefactReader
|
|
432
434
|
from ara_cli.file_classifier import FileClassifier
|
|
433
435
|
|
|
434
436
|
status_tags = ALLOWED_STATUS_VALUES
|
|
@@ -443,36 +445,34 @@ def set_status_action(args):
|
|
|
443
445
|
check_validity(new_status in status_tags, "Invalid status provided. Please provide a valid status.")
|
|
444
446
|
|
|
445
447
|
file_classifier = FileClassifier(os)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
+
classified_artefacts_info = file_classifier.classify_files()
|
|
449
|
+
classified_artefact_dict = classified_artefacts_info.get(classifier, [])
|
|
450
|
+
all_artefact_names = [artefact_info["title"] for artefact_info in classified_artefact_dict]
|
|
448
451
|
|
|
449
452
|
if artefact_name not in all_artefact_names:
|
|
450
453
|
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
451
454
|
return
|
|
452
455
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
original_directory = directory_navigator.navigate_to_target()
|
|
456
|
+
artefact_info = next(filter(
|
|
457
|
+
lambda x: x["title"] == artefact_name, classified_artefact_dict
|
|
458
|
+
))
|
|
457
459
|
|
|
460
|
+
with open(artefact_info["file_path"], 'r') as file:
|
|
461
|
+
content = file.read()
|
|
458
462
|
artefact = artefact_from_content(content)
|
|
463
|
+
|
|
459
464
|
artefact.status = new_status
|
|
460
465
|
|
|
461
466
|
serialized_content = artefact.serialize()
|
|
462
|
-
with open(f"{file_path}", 'w') as file:
|
|
467
|
+
with open(f"{artefact_info['file_path']}", 'w') as file:
|
|
463
468
|
file.write(serialized_content)
|
|
464
469
|
|
|
465
|
-
os.chdir(original_directory)
|
|
466
|
-
|
|
467
470
|
print(f"Status of task '{artefact_name}' has been updated to '{new_status}'.")
|
|
468
471
|
|
|
469
472
|
|
|
470
473
|
def set_user_action(args):
|
|
471
|
-
from ara_cli.artefact import Artefact
|
|
472
|
-
from ara_cli.artefact_reader import ArtefactReader
|
|
473
474
|
from ara_cli.file_classifier import FileClassifier
|
|
474
|
-
|
|
475
|
-
user_prefix = "user_"
|
|
475
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
476
476
|
|
|
477
477
|
classifier = args.classifier
|
|
478
478
|
artefact_name = args.parameter
|
|
@@ -482,27 +482,28 @@ def set_user_action(args):
|
|
|
482
482
|
new_user = new_user.lstrip('@')
|
|
483
483
|
|
|
484
484
|
file_classifier = FileClassifier(os)
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
classified_artefacts_info = file_classifier.classify_files()
|
|
486
|
+
classified_artefact_dict = classified_artefacts_info.get(classifier, [])
|
|
487
|
+
all_artefact_names = [artefact_info["title"] for artefact_info in classified_artefact_dict]
|
|
487
488
|
|
|
488
489
|
if artefact_name not in all_artefact_names:
|
|
489
490
|
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
490
491
|
return
|
|
491
492
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
artefact._file_path = artefact_path
|
|
496
|
-
|
|
497
|
-
current_tags = artefact.tags
|
|
493
|
+
artefact_info = next(filter(
|
|
494
|
+
lambda x: x["title"] == artefact_name, classified_artefact_dict
|
|
495
|
+
))
|
|
498
496
|
|
|
499
|
-
|
|
497
|
+
with open(artefact_info["file_path"], 'r') as file:
|
|
498
|
+
content = file.read()
|
|
499
|
+
artefact = artefact_from_content(content)
|
|
500
500
|
|
|
501
|
-
|
|
501
|
+
artefact.users = [new_user]
|
|
502
502
|
|
|
503
|
-
|
|
503
|
+
serialized_content = artefact.serialize()
|
|
504
504
|
|
|
505
|
-
|
|
505
|
+
with open(artefact_info["file_path"], 'w') as file:
|
|
506
|
+
file.write(serialized_content)
|
|
506
507
|
|
|
507
508
|
print(f"User of task '{artefact_name}' has been updated to '{new_user}'.")
|
|
508
509
|
|
|
@@ -513,3 +514,17 @@ def classifier_directory_action(args):
|
|
|
513
514
|
classifier = args.classifier
|
|
514
515
|
subdirectory = Classifier.get_sub_directory(classifier)
|
|
515
516
|
print(subdirectory)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def scan_action(args):
|
|
520
|
+
from ara_cli.file_classifier import FileClassifier
|
|
521
|
+
from ara_cli.artefact_scan import find_invalid_files, show_results
|
|
522
|
+
import os
|
|
523
|
+
|
|
524
|
+
classified_artefact_info = FileClassifier(os).classify_files()
|
|
525
|
+
invalid_artefacts = {}
|
|
526
|
+
for classifier in classified_artefact_info:
|
|
527
|
+
invalid = find_invalid_files(classified_artefact_info, classifier)
|
|
528
|
+
if invalid:
|
|
529
|
+
invalid_artefacts[classifier] = invalid
|
|
530
|
+
show_results(invalid_artefacts)
|
ara_cli/ara_command_parser.py
CHANGED
|
@@ -215,6 +215,10 @@ def classifier_directory_parser(subparsers):
|
|
|
215
215
|
classifier_directory_parser.add_argument("classifier", choices=classifiers, help="Classifier of the artefact type")
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
def scan_parser(subparsers):
|
|
219
|
+
subparsers.add_parser("scan", help="Scan ARA tree for incompatible artefacts.")
|
|
220
|
+
|
|
221
|
+
|
|
218
222
|
class CustomHelpFormatter(argparse.HelpFormatter):
|
|
219
223
|
def format_help(self):
|
|
220
224
|
from sys import argv
|
|
@@ -301,5 +305,6 @@ def action_parser():
|
|
|
301
305
|
set_status_parser(subparsers)
|
|
302
306
|
set_user_parser(subparsers)
|
|
303
307
|
classifier_directory_parser(subparsers)
|
|
308
|
+
scan_parser(subparsers)
|
|
304
309
|
|
|
305
310
|
return parser
|
ara_cli/ara_config.py
CHANGED
|
@@ -11,51 +11,76 @@ DEFAULT_CONFIG_LOCATION = "./ara/.araconfig/ara_config.json"
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ARAconfig(BaseModel):
|
|
14
|
-
ext_code_dirs: List[Dict[str, str]] = [
|
|
14
|
+
ext_code_dirs: List[Dict[str, str]] = [
|
|
15
|
+
{"source_dir_1": "./src"},
|
|
16
|
+
{"source_dir_2": "./tests"},
|
|
17
|
+
]
|
|
15
18
|
glossary_dir: str = "./glossary"
|
|
16
19
|
doc_dir: str = "./docs"
|
|
17
20
|
local_prompt_templates_dir: str = "./ara/.araconfig"
|
|
18
21
|
local_ara_templates_dir: str = "./ara/.araconfig/templates/"
|
|
19
22
|
ara_prompt_given_list_includes: List[str] = [
|
|
20
|
-
"*.businessgoal",
|
|
21
|
-
"*.
|
|
23
|
+
"*.businessgoal",
|
|
24
|
+
"*.vision",
|
|
25
|
+
"*.capability",
|
|
26
|
+
"*.keyfeature",
|
|
27
|
+
"*.epic",
|
|
28
|
+
"*.userstory",
|
|
29
|
+
"*.example",
|
|
30
|
+
"*.feature",
|
|
31
|
+
"*.task",
|
|
32
|
+
"*.py",
|
|
33
|
+
"*.md",
|
|
34
|
+
"*.png",
|
|
35
|
+
"*.jpg",
|
|
36
|
+
"*.jpeg",
|
|
22
37
|
]
|
|
23
38
|
llm_config: Dict[str, Dict[str, Union[str, float]]] = {
|
|
24
|
-
"gpt-4o":
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
39
|
+
"gpt-4o": {
|
|
40
|
+
"provider": "openai",
|
|
41
|
+
"model": "openai/gpt-4o",
|
|
42
|
+
"temperature": 0.8
|
|
43
|
+
},
|
|
30
44
|
"gpt-4.5-preview": {
|
|
31
45
|
"provider": "openai",
|
|
32
46
|
"model": "openai/gpt-4.5-preview",
|
|
33
|
-
"temperature": 0.8
|
|
47
|
+
"temperature": 0.8,
|
|
48
|
+
},
|
|
49
|
+
"o3-mini": {
|
|
50
|
+
"provider": "openai",
|
|
51
|
+
"model": "openai/o3-mini",
|
|
52
|
+
"temperature": 1.0,
|
|
53
|
+
},
|
|
54
|
+
"claude-3.7": {
|
|
55
|
+
"provider": "anthropic",
|
|
56
|
+
"model": "anthropic/claude-3-7-sonnet-20250219",
|
|
57
|
+
"temperature": 0.8,
|
|
58
|
+
},
|
|
59
|
+
"together-ai-llama-2": {
|
|
60
|
+
"provider": "together_ai",
|
|
61
|
+
"model": "together_ai/togethercomputer/llama-2-70b",
|
|
62
|
+
"temperature": 0.8,
|
|
63
|
+
},
|
|
64
|
+
"groq-llama-3": {
|
|
65
|
+
"provider": "groq",
|
|
66
|
+
"model": "groq/llama3-70b-8192",
|
|
67
|
+
"temperature": 0.8,
|
|
68
|
+
},
|
|
69
|
+
"opus-4": {
|
|
70
|
+
"provider": "anthropic",
|
|
71
|
+
"model": "anthropic/claude-opus-4-20250514",
|
|
72
|
+
"temperature": 0.8,
|
|
73
|
+
},
|
|
74
|
+
"sonnet-3.7": {
|
|
75
|
+
"provider": "anthropic",
|
|
76
|
+
"model": "anthropic/claude-3-7-sonnet-20250219",
|
|
77
|
+
"temperature": 0.8,
|
|
78
|
+
},
|
|
79
|
+
"sonnet-4": {
|
|
80
|
+
"provider": "anthropic",
|
|
81
|
+
"model": "anthropic/claude-sonnet-4-20250514",
|
|
82
|
+
"temperature": 0.8,
|
|
34
83
|
},
|
|
35
|
-
"o3-mini":
|
|
36
|
-
{
|
|
37
|
-
"provider": "openai",
|
|
38
|
-
"model": "openai/o3-mini",
|
|
39
|
-
"temperature": 1.0
|
|
40
|
-
},
|
|
41
|
-
"claude-3.7":
|
|
42
|
-
{
|
|
43
|
-
"provider": "anthropic",
|
|
44
|
-
"model": "anthropic/claude-3-7-sonnet-20250219",
|
|
45
|
-
"temperature": 0.8
|
|
46
|
-
},
|
|
47
|
-
"together-ai-llama-2":
|
|
48
|
-
{
|
|
49
|
-
"provider": "together_ai",
|
|
50
|
-
"model": "together_ai/togethercomputer/llama-2-70b",
|
|
51
|
-
"temperature": 0.8
|
|
52
|
-
},
|
|
53
|
-
"groq-llama-3":
|
|
54
|
-
{
|
|
55
|
-
"provider": "groq",
|
|
56
|
-
"model": "groq/llama3-70b-8192",
|
|
57
|
-
"temperature": 0.8
|
|
58
|
-
}
|
|
59
84
|
}
|
|
60
85
|
default_llm: Optional[str] = "gpt-4o"
|
|
61
86
|
|
|
@@ -70,7 +95,7 @@ def ensure_directory_exists(directory: str):
|
|
|
70
95
|
|
|
71
96
|
|
|
72
97
|
def validate_config_data(filepath: str):
|
|
73
|
-
with open(filepath,
|
|
98
|
+
with open(filepath, "r") as file:
|
|
74
99
|
data = json.load(file)
|
|
75
100
|
return data
|
|
76
101
|
|
|
@@ -82,10 +107,12 @@ def read_data(filepath: str) -> ARAconfig:
|
|
|
82
107
|
# If file does not exist, create it with default values
|
|
83
108
|
default_config = ARAconfig()
|
|
84
109
|
|
|
85
|
-
with open(filepath,
|
|
110
|
+
with open(filepath, "w") as file:
|
|
86
111
|
json.dump(default_config.model_dump(), file, indent=4)
|
|
87
112
|
|
|
88
|
-
print(
|
|
113
|
+
print(
|
|
114
|
+
f"ara-cli configuration file '{filepath}' created with default configuration. Please modify it as needed and re-run your command"
|
|
115
|
+
)
|
|
89
116
|
exit() # Exit the application
|
|
90
117
|
|
|
91
118
|
data = validate_config_data(filepath)
|
|
@@ -94,7 +121,7 @@ def read_data(filepath: str) -> ARAconfig:
|
|
|
94
121
|
|
|
95
122
|
# Function to save the modified configuration back to the JSON file
|
|
96
123
|
def save_data(filepath: str, config: ARAconfig):
|
|
97
|
-
with open(filepath,
|
|
124
|
+
with open(filepath, "w") as file:
|
|
98
125
|
json.dump(config.dict(), file, indent=4)
|
|
99
126
|
|
|
100
127
|
|
ara_cli/artefact_creator.py
CHANGED
|
@@ -2,9 +2,9 @@ import os
|
|
|
2
2
|
from functools import lru_cache
|
|
3
3
|
from ara_cli.classifier import Classifier
|
|
4
4
|
from ara_cli.template_manager import DirectoryNavigator
|
|
5
|
-
from ara_cli.
|
|
5
|
+
from ara_cli.artefact_reader import ArtefactReader
|
|
6
|
+
from ara_cli.artefact_models.artefact_model import Artefact
|
|
6
7
|
from ara_cli.artefact_models.artefact_templates import template_artefact_of_type
|
|
7
|
-
from ara_cli.file_classifier import FileClassifier
|
|
8
8
|
from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from shutil import copyfile
|
|
@@ -75,8 +75,7 @@ class ArtefactCreator:
|
|
|
75
75
|
return True
|
|
76
76
|
|
|
77
77
|
def set_artefact_parent(self, artefact, parent_classifier, parent_file_name) -> Artefact:
|
|
78
|
-
|
|
79
|
-
classified_artefacts = file_classifier.classify_files()
|
|
78
|
+
classified_artefacts = ArtefactReader.read_artefacts()
|
|
80
79
|
if parent_classifier not in classified_artefacts:
|
|
81
80
|
return artefact
|
|
82
81
|
artefact_list = classified_artefacts[parent_classifier]
|