ara-cli 0.1.9.69__py3-none-any.whl → 0.1.10.8__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.

Files changed (150) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +248 -62
  3. ara_cli/ara_command_action.py +155 -86
  4. ara_cli/ara_config.py +226 -80
  5. ara_cli/ara_subcommands/__init__.py +0 -0
  6. ara_cli/ara_subcommands/autofix.py +26 -0
  7. ara_cli/ara_subcommands/chat.py +27 -0
  8. ara_cli/ara_subcommands/classifier_directory.py +16 -0
  9. ara_cli/ara_subcommands/common.py +100 -0
  10. ara_cli/ara_subcommands/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +649 -68
  28. ara_cli/artefact_creator.py +8 -11
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +22 -10
  31. ara_cli/artefact_link_updater.py +4 -4
  32. ara_cli/artefact_lister.py +29 -55
  33. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  34. ara_cli/artefact_models/artefact_load.py +11 -3
  35. ara_cli/artefact_models/artefact_model.py +146 -39
  36. ara_cli/artefact_models/artefact_templates.py +70 -44
  37. ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
  38. ara_cli/artefact_models/epic_artefact_model.py +34 -26
  39. ara_cli/artefact_models/feature_artefact_model.py +203 -64
  40. ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
  41. ara_cli/artefact_models/serialize_helper.py +1 -1
  42. ara_cli/artefact_models/task_artefact_model.py +83 -15
  43. ara_cli/artefact_models/userstory_artefact_model.py +37 -27
  44. ara_cli/artefact_models/vision_artefact_model.py +23 -42
  45. ara_cli/artefact_reader.py +92 -91
  46. ara_cli/artefact_renamer.py +8 -4
  47. ara_cli/artefact_scan.py +66 -3
  48. ara_cli/chat.py +622 -162
  49. ara_cli/chat_agent/__init__.py +0 -0
  50. ara_cli/chat_agent/agent_communicator.py +62 -0
  51. ara_cli/chat_agent/agent_process_manager.py +211 -0
  52. ara_cli/chat_agent/agent_status_manager.py +73 -0
  53. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  54. ara_cli/commands/__init__.py +0 -0
  55. ara_cli/commands/command.py +7 -0
  56. ara_cli/commands/extract_command.py +15 -0
  57. ara_cli/commands/load_command.py +65 -0
  58. ara_cli/commands/load_image_command.py +34 -0
  59. ara_cli/commands/read_command.py +117 -0
  60. ara_cli/completers.py +144 -0
  61. ara_cli/directory_navigator.py +37 -4
  62. ara_cli/error_handler.py +134 -0
  63. ara_cli/file_classifier.py +6 -5
  64. ara_cli/file_lister.py +1 -1
  65. ara_cli/file_loaders/__init__.py +0 -0
  66. ara_cli/file_loaders/binary_file_loader.py +33 -0
  67. ara_cli/file_loaders/document_file_loader.py +34 -0
  68. ara_cli/file_loaders/document_reader.py +245 -0
  69. ara_cli/file_loaders/document_readers.py +233 -0
  70. ara_cli/file_loaders/file_loader.py +50 -0
  71. ara_cli/file_loaders/file_loaders.py +123 -0
  72. ara_cli/file_loaders/image_processor.py +89 -0
  73. ara_cli/file_loaders/markdown_reader.py +75 -0
  74. ara_cli/file_loaders/text_file_loader.py +187 -0
  75. ara_cli/global_file_lister.py +51 -0
  76. ara_cli/list_filter.py +1 -1
  77. ara_cli/output_suppressor.py +1 -1
  78. ara_cli/prompt_extractor.py +215 -88
  79. ara_cli/prompt_handler.py +521 -134
  80. ara_cli/prompt_rag.py +2 -2
  81. ara_cli/tag_extractor.py +83 -38
  82. ara_cli/template_loader.py +245 -0
  83. ara_cli/template_manager.py +18 -13
  84. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  91. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  92. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  93. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  94. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  95. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  96. ara_cli/update_config_prompt.py +9 -3
  97. ara_cli/version.py +1 -1
  98. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  99. ara_cli-0.1.10.8.dist-info/RECORD +193 -0
  100. tests/test_ara_command_action.py +73 -59
  101. tests/test_ara_config.py +341 -36
  102. tests/test_artefact_autofix.py +1060 -0
  103. tests/test_artefact_link_updater.py +3 -3
  104. tests/test_artefact_lister.py +52 -132
  105. tests/test_artefact_renamer.py +2 -2
  106. tests/test_artefact_scan.py +327 -33
  107. tests/test_chat.py +2063 -498
  108. tests/test_file_classifier.py +24 -1
  109. tests/test_file_creator.py +3 -5
  110. tests/test_file_lister.py +1 -1
  111. tests/test_global_file_lister.py +131 -0
  112. tests/test_list_filter.py +2 -2
  113. tests/test_prompt_handler.py +746 -0
  114. tests/test_tag_extractor.py +19 -13
  115. tests/test_template_loader.py +192 -0
  116. tests/test_template_manager.py +5 -4
  117. tests/test_update_config_prompt.py +2 -2
  118. ara_cli/ara_command_parser.py +0 -327
  119. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  120. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  121. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  122. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  123. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  124. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  125. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  126. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  127. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  128. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  129. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  130. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  131. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  132. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  133. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  134. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  135. ara_cli/templates/template.businessgoal +0 -10
  136. ara_cli/templates/template.capability +0 -10
  137. ara_cli/templates/template.epic +0 -15
  138. ara_cli/templates/template.example +0 -6
  139. ara_cli/templates/template.feature +0 -26
  140. ara_cli/templates/template.issue +0 -14
  141. ara_cli/templates/template.keyfeature +0 -15
  142. ara_cli/templates/template.task +0 -6
  143. ara_cli/templates/template.userstory +0 -17
  144. ara_cli/templates/template.vision +0 -14
  145. ara_cli-0.1.9.69.dist-info/METADATA +0 -16
  146. ara_cli-0.1.9.69.dist-info/RECORD +0 -158
  147. tests/test_ara_autofix.py +0 -219
  148. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  149. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  150. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
@@ -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:
@@ -165,6 +184,10 @@ class Artefact(BaseModel, ABC):
165
184
  default=[],
166
185
  description="Optional list of tags (0-many)",
167
186
  )
187
+ author: Optional[str] = Field(
188
+ default="creator_unknown",
189
+ description="Author of the artefact, must be a single entry of the form 'creator_<someone>'."
190
+ )
168
191
  title: str = Field(
169
192
  ...,
170
193
  description="Descriptive Artefact title (mandatory)",
@@ -197,6 +220,15 @@ class Artefact(BaseModel, ABC):
197
220
  self._file_path = file_path
198
221
  return self
199
222
 
223
+ @model_validator(mode="after")
224
+ def validate_contribution(self):
225
+ contribution = self.contribution
226
+ classifier = self.artefact_type.value
227
+ name = self.title
228
+ if not contribution:
229
+ warnings.warn(f"Contribution of {classifier} '{name}' is not set and will be empty")
230
+ return self
231
+
200
232
  @field_validator('artefact_type')
201
233
  def validate_artefact_type(cls, v):
202
234
  if not isinstance(v, ArtefactType):
@@ -224,13 +256,28 @@ class Artefact(BaseModel, ABC):
224
256
  raise ValueError(f"Tag '{tag}' has the form of a status tag. Set `status` field instead of passing it with other tags")
225
257
  if tag.startswith("user_"):
226
258
  raise ValueError(f"Tag '{tag} has the form of a user tag. Set `users` field instead of passing it with other tags")
259
+ if tag.startswith("creator_"):
260
+ raise ValueError(f"Tag '{tag}' has the form of an author tag. Set `author` field instead of passing it with other tags")
261
+ return v
262
+
263
+ @field_validator('author')
264
+ def validate_author(cls, v):
265
+ if v:
266
+ if not v.startswith("creator_"):
267
+ raise ValueError(f"Author '{v}' must start with 'creator_'.")
268
+ if len(v) <= len("creator_"):
269
+ raise ValueError("Creator name cannot be empty in author tag.")
227
270
  return v
228
271
 
229
272
  @field_validator('title')
230
273
  def validate_title(cls, v):
231
274
  if not v.strip():
232
275
  raise ValueError("artefact_title must not be empty")
233
- v = replace_space_with_underscore(v)
276
+ v = replace_space_with_underscore(v).strip()
277
+
278
+ whitelisted_placeholder = "<descriptive_title>"
279
+ if v == whitelisted_placeholder:
280
+ return v
234
281
 
235
282
  letters = list(string.ascii_letters)
236
283
  digits = list(string.digits)
@@ -243,12 +290,6 @@ class Artefact(BaseModel, ABC):
243
290
 
244
291
  return v
245
292
 
246
- @field_validator('contribution')
247
- def validate_contribution(cls, v):
248
- if not v:
249
- warnings.warn("Contribution is not set and will be empty")
250
- return v
251
-
252
293
  @classmethod
253
294
  @abstractmethod
254
295
  def _title_prefix(cls) -> str: # pragma: no cover
@@ -265,32 +306,81 @@ class Artefact(BaseModel, ABC):
265
306
  tag_line = lines[0]
266
307
  if not tag_line.startswith('@'):
267
308
  return {}, lines
309
+
268
310
  tags = tag_line.split()
311
+ tag_dict = cls._process_tags(tags)
312
+ return tag_dict, lines[1:]
313
+
314
+ @classmethod
315
+ def _process_tags(cls, tags) -> Dict[str, str]:
316
+ """Process a list of tags and return a dictionary with categorized tags."""
269
317
  status = None
270
318
  regular_tags = []
271
319
  users = []
272
- status_list = ["@to-do", "@in-progress", "@review", "@done", "@closed"]
273
- user_prefix = "@user_"
274
- user_prefix_length = len(user_prefix)
275
-
320
+ author = None
321
+
276
322
  for tag in tags:
277
- if not tag.startswith('@'):
278
- raise ValueError(f"Tag '{tag}' should start with '@' but started with '{tag[0]}'")
279
- if tag in status_list and status is not None:
280
- raise ValueError(f"Multiple status tags found: '@{status}' and '{tag}'")
281
- if tag in status_list:
282
- status = tag[1:]
283
- continue
284
- if tag.startswith("@user_") and len(tag) > user_prefix_length + 1:
285
- users.append(tag[user_prefix_length:])
286
- continue
287
- regular_tags.append(tag[1:])
288
- tag_dict = {
323
+ cls._validate_tag_format(tag)
324
+
325
+ if cls._is_status_tag(tag):
326
+ status = cls._process_status_tag(tag, status)
327
+ elif cls._is_user_tag(tag):
328
+ users.append(cls._extract_user_from_tag(tag))
329
+ elif cls._is_author_tag(tag):
330
+ author = cls._process_author_tag(tag, author)
331
+ else:
332
+ regular_tags.append(tag[1:])
333
+
334
+ return {
289
335
  "status": status,
290
336
  "users": users,
291
- "tags": regular_tags
337
+ "tags": regular_tags,
338
+ "author": author
292
339
  }
293
- return tag_dict, lines[1:]
340
+
341
+ @classmethod
342
+ def _validate_tag_format(cls, tag):
343
+ """Validate that tag starts with @."""
344
+ if not tag.startswith('@'):
345
+ raise ValueError(f"Tag '{tag}' should start with '@' but started with '{tag[0]}'")
346
+
347
+ @classmethod
348
+ def _is_status_tag(cls, tag) -> bool:
349
+ """Check if tag is a status tag."""
350
+ status_list = ["@to-do", "@in-progress", "@review", "@done", "@closed"]
351
+ return tag in status_list
352
+
353
+ @classmethod
354
+ def _process_status_tag(cls, tag, current_status):
355
+ """Process status tag and check for duplicates."""
356
+ if current_status is not None:
357
+ raise ValueError(f"Multiple status tags found: '@{current_status}' and '{tag}'")
358
+ return tag[1:] # Remove @ prefix
359
+
360
+ @classmethod
361
+ def _is_user_tag(cls, tag) -> bool:
362
+ """Check if tag is a user tag."""
363
+ user_prefix = "@user_"
364
+ return tag.startswith(user_prefix) and len(tag) > len(user_prefix)
365
+
366
+ @classmethod
367
+ def _extract_user_from_tag(cls, tag) -> str:
368
+ """Extract username from user tag."""
369
+ user_prefix = "@user_"
370
+ return tag[len(user_prefix):]
371
+
372
+ @classmethod
373
+ def _is_author_tag(cls, tag) -> bool:
374
+ """Check if tag is an author tag."""
375
+ creator_prefix = "@creator_"
376
+ return tag.startswith(creator_prefix) and len(tag) > len(creator_prefix)
377
+
378
+ @classmethod
379
+ def _process_author_tag(cls, tag, current_author):
380
+ """Process author tag and check for duplicates."""
381
+ if current_author is not None:
382
+ raise ValueError(f"Multiple author tags found: '@{current_author}' and '@{tag[1:]}'")
383
+ return tag[1:]
294
384
 
295
385
  @classmethod
296
386
  def _deserialize_title(cls, lines) -> (str, List[str]):
@@ -320,14 +410,26 @@ class Artefact(BaseModel, ABC):
320
410
  return contribution, lines
321
411
 
322
412
  @classmethod
323
- def _deserialize_description(cls, lines) -> (Optional[str], List[str]):
413
+ def _deserialize_description(cls, lines: List[str]) -> (Optional[str], List[str]):
324
414
  description_start = cls._description_starts_with()
415
+ start_index = -1
325
416
  for i, line in enumerate(lines):
326
417
  if line.startswith(description_start):
327
- description = line[len(description_start):].strip()
328
- del lines[i]
329
- return description, lines
330
- return None, lines
418
+ start_index = i
419
+ break
420
+
421
+ if start_index == -1:
422
+ return None, lines
423
+
424
+ first_line_content = lines[start_index][len(description_start):].strip()
425
+
426
+ description_lines = ([first_line_content] if first_line_content else []) + lines[start_index + 1:]
427
+
428
+ description = "\n".join(description_lines)
429
+
430
+ remaining_lines = lines[:start_index]
431
+
432
+ return (description if description else None), remaining_lines
331
433
 
332
434
  @classmethod
333
435
  def _parse_common_fields(cls, text: str) -> dict:
@@ -341,7 +443,7 @@ class Artefact(BaseModel, ABC):
341
443
  contribution, remaining_lines = cls._deserialize_contribution(remaining_lines)
342
444
  description, remaining_lines = cls._deserialize_description(remaining_lines)
343
445
 
344
- return {
446
+ fields = {
345
447
  'artefact_type': cls._artefact_type(),
346
448
  'tags': tags.get('tags', []),
347
449
  'users': tags.get('users', []),
@@ -350,6 +452,9 @@ class Artefact(BaseModel, ABC):
350
452
  'contribution': contribution,
351
453
  'description': description,
352
454
  }
455
+ if tags.get("author"):
456
+ fields["author"] = tags.get("author")
457
+ return fields
353
458
 
354
459
  @classmethod
355
460
  def deserialize(cls, text: str) -> 'Artefact':
@@ -381,6 +486,8 @@ class Artefact(BaseModel, ABC):
381
486
  tags.append(f"@{self.status}")
382
487
  for user in self.users:
383
488
  tags.append(f"@user_{user}")
489
+ if self.author:
490
+ tags.append(f"@{self.author}")
384
491
  for tag in self.tags:
385
492
  tags.append(f"@{tag}")
386
493
  return ' '.join(tags)
@@ -404,4 +511,4 @@ class Artefact(BaseModel, ABC):
404
511
  classifier=classifier,
405
512
  rule=rule
406
513
  )
407
- self.contribution = contribution
514
+ self.contribution = contribution
@@ -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 _default_vision(title: str) -> VisionArtefact:
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>",
@@ -21,42 +29,46 @@ def _default_vision(title: str) -> VisionArtefact:
21
29
  our_product="<statement of primary differentiation>"
22
30
  )
23
31
  return VisionArtefact(
24
- tags=["sample_tag"],
32
+ tags=[],
33
+ author="creator_unknown",
25
34
  title=title,
26
35
  description="<further optional description to understand the vision, markdown capable text formatting>",
27
- intent=intent
36
+ intent=intent,
37
+ contribution=default_contribution() if use_default_contribution else None
28
38
  )
29
39
 
30
40
 
31
- def _default_businessgoal(title: str) -> BusinessgoalArtefact:
41
+ def _default_businessgoal(title: str, use_default_contribution: bool) -> BusinessgoalArtefact:
32
42
  intent = BusinessgoalIntent(
33
43
  in_order_to="<reach primarily a monetary business goal>",
34
44
  as_a="<business related role>",
35
45
  i_want="<something that helps me to reach my monetary goal>"
36
46
  )
37
47
  return BusinessgoalArtefact(
38
- tags=["sample_tag"],
48
+ tags=[],
49
+ author="creator_unknown",
39
50
  title=title,
40
- description="<further optional description to understand the vision, markdown capable text formatting>",
41
- intent=intent
51
+ description="<further optional description to understand the businessgoal, markdown capable text formatting>",
52
+ intent=intent,
53
+ contribution=default_contribution() if use_default_contribution else None
42
54
  )
43
55
 
44
56
 
45
- def _default_capability(title: str) -> CapabilityArtefact:
57
+ def _default_capability(title: str, use_default_contribution: bool) -> CapabilityArtefact:
46
58
  intent = CapabilityIntent(
47
- in_order_to="<reach primarily a monetary business goal>",
48
- as_a="<business related role>",
49
- to_be_able_to="<something that helps me to reach my monetary goal>"
59
+ to_be_able_to="<needed capability for stakeholders that are the enablers/relevant for reaching the business goal>"
50
60
  )
51
61
  return CapabilityArtefact(
52
- tags=["sample_tag"],
62
+ tags=[],
63
+ author="creator_unknown",
53
64
  title=title,
54
65
  description="<further optional description to understand the capability, markdown capable text formatting>",
55
- intent=intent
66
+ intent=intent,
67
+ contribution=default_contribution() if use_default_contribution else None
56
68
  )
57
69
 
58
70
 
59
- def _default_epic(title: str) -> EpicArtefact:
71
+ def _default_epic(title: str, use_default_contribution: bool) -> EpicArtefact:
60
72
  intent = EpicIntent(
61
73
  in_order_to="<achieve a benefit>",
62
74
  as_a="<(user) role>",
@@ -68,15 +80,17 @@ def _default_epic(title: str) -> EpicArtefact:
68
80
  "<rule needed to fulfill the wanted product behavior>"
69
81
  ]
70
82
  return EpicArtefact(
71
- tags=["sample_tag"],
83
+ tags=[],
84
+ author="creator_unknown",
72
85
  title=title,
73
- description="<further optional description to understand the vision, markdown capable text formatting>",
86
+ description="<further optional description to understand the epic, markdown capable text formatting>",
74
87
  intent=intent,
75
- rules=rules
88
+ rules=rules,
89
+ contribution=default_contribution() if use_default_contribution else None
76
90
  )
77
91
 
78
92
 
79
- def _default_userstory(title: str) -> UserstoryArtefact:
93
+ def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryArtefact:
80
94
  intent = UserstoryIntent(
81
95
  in_order_to="<achieve a benefit>",
82
96
  as_a="<(user) role>",
@@ -88,44 +102,50 @@ def _default_userstory(title: str) -> UserstoryArtefact:
88
102
  "<rule needed to fulfill the wanted product behavior>"
89
103
  ]
90
104
  return UserstoryArtefact(
91
- tags=["sample_tag"],
105
+ tags=[],
106
+ author="creator_unknown",
92
107
  title=title,
93
108
  description="<further optional description to understand the userstory, markdown capable text formatting>",
94
109
  intent=intent,
95
110
  rules=rules,
96
- estimate="<story points, scale?>"
111
+ estimate="<story points, scale?>",
112
+ contribution=default_contribution() if use_default_contribution else None
97
113
  )
98
114
 
99
115
 
100
- def _default_example(title: str) -> ExampleArtefact:
116
+ def _default_example(title: str, use_default_contribution: bool) -> ExampleArtefact:
101
117
  return ExampleArtefact(
102
- tags=["sample_tag"],
118
+ tags=[],
119
+ author="creator_unknown",
103
120
  title=title,
104
121
  description="<further optional description to understand the example, markdown capable text formatting>",
122
+ contribution=default_contribution() if use_default_contribution else None
105
123
  )
106
124
 
107
125
 
108
- def _default_keyfeature(title: str) -> KeyfeatureArtefact:
126
+ def _default_keyfeature(title: str, use_default_contribution: bool) -> KeyfeatureArtefact:
109
127
  intent = KeyfeatureIntent(
110
128
  in_order_to="<support a capability or business goal>",
111
129
  as_a="<main stakeholder who will benefit>",
112
130
  i_want="<a product feature that helps me doing something so that I can achieve my named goal>"
113
131
  )
114
- description = """<further optional description to understand the capability, markdown capable text formatting, best practice is using
132
+ description = """<further optional description to understand the keyfeature, markdown capable text formatting, best practice is using
115
133
  GIVEN any precondition
116
134
  AND another precondition
117
135
  WHEN some action takes place
118
136
  THEN some result is to be expected
119
137
  AND some other result is to be expected>"""
120
138
  return KeyfeatureArtefact(
121
- tags=["sample_tag"],
139
+ tags=[],
140
+ author="creator_unknown",
122
141
  title=title,
123
142
  description=description,
124
- intent=intent
143
+ intent=intent,
144
+ contribution=default_contribution() if use_default_contribution else None
125
145
  )
126
146
 
127
147
 
128
- def _default_feature(title: str) -> FeatureArtefact:
148
+ def _default_feature(title: str, use_default_contribution: bool) -> FeatureArtefact:
129
149
  intent = FeatureIntent(
130
150
  as_a="<user>",
131
151
  i_want_to="<do something | need something>",
@@ -135,9 +155,9 @@ def _default_feature(title: str) -> FeatureArtefact:
135
155
  Scenario(
136
156
  title="<descriptive_scenario_title>",
137
157
  steps=[
138
- "Given <precondition>",
139
- "When <action>",
140
- "Then <expected result>"
158
+ "Given [precondition]",
159
+ "When [action]",
160
+ "Then [expected result]"
141
161
  ],
142
162
  ),
143
163
  ScenarioOutline(
@@ -169,27 +189,30 @@ def _default_feature(title: str) -> FeatureArtefact:
169
189
  ]
170
190
  )
171
191
  ]
172
- description = """<further optional description to understand
173
- the rule, no format defined, the example artefact is only a placeholder>"""
192
+ description = """<further optional description to understand the feature, no format defined, the example artefact is only a placeholder>"""
174
193
 
175
194
  return FeatureArtefact(
176
- tags=["sample_tag"],
195
+ tags=[],
196
+ author="creator_unknown",
177
197
  title=title,
178
198
  description=description,
179
199
  intent=intent,
180
- scenarios=scenarios
200
+ scenarios=scenarios,
201
+ contribution=default_contribution() if use_default_contribution else None
181
202
  )
182
203
 
183
204
 
184
- def _default_task(title: str) -> TaskArtefact:
205
+ def _default_task(title: str, use_default_contribution: bool) -> TaskArtefact:
185
206
  return TaskArtefact(
186
- status="to-do",
207
+ tags=[],
208
+ status=None,
187
209
  title=title,
188
- description="<further optional description to understand the task, no format defined>"
210
+ description="<further optional description to understand the task, no format defined>",
211
+ contribution=default_contribution() if use_default_contribution else None
189
212
  )
190
213
 
191
214
 
192
- def _default_issue(title: str) -> IssueArtefact:
215
+ def _default_issue(title: str, use_default_contribution: bool) -> IssueArtefact:
193
216
  description = "<further free text description to understand the issue, no format defined>"
194
217
  additional_description = """*Optional descriptions of the issue in Gherkin style*
195
218
 
@@ -200,14 +223,16 @@ def _default_issue(title: str) -> IssueArtefact:
200
223
  *or optional free text description*"""
201
224
 
202
225
  return IssueArtefact(
203
- tags=["sample_tag"],
226
+ tags=[],
227
+ author="creator_unknown",
204
228
  title=title,
205
229
  description=description,
206
- additional_description=additional_description
230
+ additional_description=additional_description,
231
+ contribution=default_contribution() if use_default_contribution else None
207
232
  )
208
233
 
209
234
 
210
- def template_artefact_of_type(artefact_type: ArtefactType, title: str) -> Artefact:
235
+ def template_artefact_of_type(artefact_type: ArtefactType, title: str = "<descriptive_title>", use_default_contribution: bool = True) -> Artefact:
211
236
  default_creation_functions = {
212
237
  ArtefactType.vision: _default_vision,
213
238
  ArtefactType.businessgoal: _default_businessgoal,
@@ -220,5 +245,6 @@ def template_artefact_of_type(artefact_type: ArtefactType, title: str) -> Artefa
220
245
  ArtefactType.task: _default_task,
221
246
  ArtefactType.issue: _default_issue
222
247
  }
223
-
224
- return default_creation_functions[artefact_type](title)
248
+ if artefact_type not in default_creation_functions.keys():
249
+ return None
250
+ return default_creation_functions[artefact_type](title, use_default_contribution)
@@ -38,7 +38,7 @@ class BusinessgoalIntent(Intent):
38
38
  lines = []
39
39
 
40
40
  as_a_line = as_a_serializer(self.as_a)
41
-
41
+
42
42
  lines.append(f"In order to {self.in_order_to}")
43
43
  lines.append(as_a_line)
44
44
  lines.append(f"I want {self.i_want}")
@@ -47,39 +47,37 @@ class BusinessgoalIntent(Intent):
47
47
 
48
48
  @classmethod
49
49
  def deserialize_from_lines(cls, lines: List[str], start_index: int = 0) -> 'BusinessgoalIntent':
50
- in_order_to = None
51
- as_a = None
52
- i_want = None
53
-
54
- in_order_to_prefix = "In order to "
55
- as_a_prefix = "As a "
56
- as_a_prefix_alt = "As an "
57
- i_want_prefix = "I want "
50
+ prefixes = [
51
+ ("In order to ", "in_order_to"),
52
+ ("As a ", "as_a"),
53
+ ("As an ", "as_a"),
54
+ ("I want ", "i_want"),
55
+ ]
56
+ found = {"in_order_to": None, "as_a": None, "i_want": None}
57
+
58
+ def match_and_store(line):
59
+ for prefix, field in prefixes:
60
+ if line.startswith(prefix) and found[field] is None:
61
+ found[field] = line[len(prefix):].strip()
62
+ return True
63
+ return False
58
64
 
59
65
  index = start_index
60
- while index < len(lines) and (not in_order_to or not as_a or not i_want):
61
- line = lines[index]
62
- if line.startswith(in_order_to_prefix) and not in_order_to:
63
- in_order_to = line[len(in_order_to_prefix):].strip()
64
- elif line.startswith(as_a_prefix) and not as_a:
65
- as_a = line[len(as_a_prefix):].strip()
66
- elif line.startswith(as_a_prefix_alt) and not as_a:
67
- as_a = line[len(as_a_prefix_alt):].strip()
68
- elif line.startswith(i_want_prefix) and not i_want:
69
- i_want = line[len(i_want_prefix):].strip()
66
+ while index < len(lines) and any(v is None for v in found.values()):
67
+ match_and_store(lines[index])
70
68
  index += 1
71
69
 
72
- if not in_order_to:
70
+ if not found["in_order_to"]:
73
71
  raise ValueError("Could not find 'In order to' line")
74
- if not as_a:
72
+ if not found["as_a"]:
75
73
  raise ValueError("Could not find 'As a' line")
76
- if not i_want:
74
+ if not found["i_want"]:
77
75
  raise ValueError("Could not find 'I want' line")
78
76
 
79
77
  return cls(
80
- in_order_to=in_order_to,
81
- as_a=as_a,
82
- i_want=i_want
78
+ in_order_to=found["in_order_to"],
79
+ as_a=found["as_a"],
80
+ i_want=found["i_want"]
83
81
  )
84
82
 
85
83