ara-cli 0.1.10.0__py3-none-any.whl → 0.1.13.3__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.
Files changed (140) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +270 -103
  3. ara_cli/ara_command_action.py +106 -63
  4. ara_cli/ara_config.py +187 -128
  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/config.py +221 -0
  11. ara_cli/ara_subcommands/convert.py +43 -0
  12. ara_cli/ara_subcommands/create.py +75 -0
  13. ara_cli/ara_subcommands/delete.py +22 -0
  14. ara_cli/ara_subcommands/extract.py +22 -0
  15. ara_cli/ara_subcommands/fetch.py +41 -0
  16. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  17. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  18. ara_cli/ara_subcommands/fetch_templates.py +19 -0
  19. ara_cli/ara_subcommands/list.py +139 -0
  20. ara_cli/ara_subcommands/list_tags.py +25 -0
  21. ara_cli/ara_subcommands/load.py +48 -0
  22. ara_cli/ara_subcommands/prompt.py +136 -0
  23. ara_cli/ara_subcommands/read.py +47 -0
  24. ara_cli/ara_subcommands/read_status.py +20 -0
  25. ara_cli/ara_subcommands/read_user.py +20 -0
  26. ara_cli/ara_subcommands/reconnect.py +27 -0
  27. ara_cli/ara_subcommands/rename.py +22 -0
  28. ara_cli/ara_subcommands/scan.py +14 -0
  29. ara_cli/ara_subcommands/set_status.py +22 -0
  30. ara_cli/ara_subcommands/set_user.py +22 -0
  31. ara_cli/ara_subcommands/template.py +16 -0
  32. ara_cli/artefact_autofix.py +154 -63
  33. ara_cli/artefact_converter.py +256 -0
  34. ara_cli/artefact_models/artefact_model.py +106 -25
  35. ara_cli/artefact_models/artefact_templates.py +20 -10
  36. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  37. ara_cli/artefact_models/feature_artefact_model.py +31 -1
  38. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  39. ara_cli/artefact_scan.py +2 -2
  40. ara_cli/chat.py +283 -80
  41. ara_cli/chat_agent/__init__.py +0 -0
  42. ara_cli/chat_agent/agent_process_manager.py +155 -0
  43. ara_cli/chat_script_runner/__init__.py +0 -0
  44. ara_cli/chat_script_runner/script_completer.py +23 -0
  45. ara_cli/chat_script_runner/script_finder.py +41 -0
  46. ara_cli/chat_script_runner/script_lister.py +36 -0
  47. ara_cli/chat_script_runner/script_runner.py +36 -0
  48. ara_cli/chat_web_search/__init__.py +0 -0
  49. ara_cli/chat_web_search/web_search.py +263 -0
  50. ara_cli/commands/agent_run_command.py +98 -0
  51. ara_cli/commands/fetch_agents_command.py +106 -0
  52. ara_cli/commands/fetch_scripts_command.py +43 -0
  53. ara_cli/commands/fetch_templates_command.py +39 -0
  54. ara_cli/commands/fetch_templates_commands.py +39 -0
  55. ara_cli/commands/list_agents_command.py +39 -0
  56. ara_cli/commands/read_command.py +17 -4
  57. ara_cli/completers.py +180 -0
  58. ara_cli/constants.py +2 -0
  59. ara_cli/directory_navigator.py +37 -4
  60. ara_cli/file_loaders/text_file_loader.py +2 -2
  61. ara_cli/global_file_lister.py +5 -15
  62. ara_cli/llm_utils.py +58 -0
  63. ara_cli/prompt_chat.py +20 -4
  64. ara_cli/prompt_extractor.py +199 -76
  65. ara_cli/prompt_handler.py +160 -59
  66. ara_cli/tag_extractor.py +38 -18
  67. ara_cli/template_loader.py +3 -2
  68. ara_cli/template_manager.py +52 -21
  69. ara_cli/templates/global-scripts/hello_global.py +1 -0
  70. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  71. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  72. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  73. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  79. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  82. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  83. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  84. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  85. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  86. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  87. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  88. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  89. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  90. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  92. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  93. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  94. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  95. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  96. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  97. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  98. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  99. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  100. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  101. ara_cli/version.py +1 -1
  102. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
  103. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
  104. tests/test_ara_command_action.py +31 -19
  105. tests/test_ara_config.py +177 -90
  106. tests/test_artefact_autofix.py +170 -97
  107. tests/test_artefact_autofix_integration.py +495 -0
  108. tests/test_artefact_converter.py +357 -0
  109. tests/test_artefact_extraction.py +564 -0
  110. tests/test_artefact_scan.py +1 -1
  111. tests/test_chat.py +162 -126
  112. tests/test_chat_givens_images.py +603 -0
  113. tests/test_chat_script_runner.py +454 -0
  114. tests/test_global_file_lister.py +1 -1
  115. tests/test_llm_utils.py +164 -0
  116. tests/test_prompt_chat.py +343 -0
  117. tests/test_prompt_extractor.py +683 -0
  118. tests/test_prompt_handler.py +12 -4
  119. tests/test_tag_extractor.py +19 -13
  120. tests/test_web_search.py +467 -0
  121. ara_cli/ara_command_parser.py +0 -605
  122. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  123. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  124. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  125. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  126. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  127. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  128. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  129. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  130. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  131. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  132. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  133. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  134. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  135. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  136. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  137. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  138. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
  139. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
  140. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,106 @@
1
+ import os
2
+
3
+ from ara_cli.commands.command import Command
4
+ from ara_cli.directory_navigator import DirectoryNavigator
5
+
6
+
7
+ class FetchAgentsCommand(Command):
8
+ """Command to fetch binary agents from a remote URL.
9
+
10
+ This command downloads a binary agent from a hardcoded URL and
11
+ saves it to the project's ara/.araconfig/agents/ directory.
12
+ """
13
+
14
+ AGENT_URL = "https://s3-public.talsen.team/so-agents/feature-creation"
15
+
16
+ def __init__(self, output=None):
17
+ """Initialize the FetchAgentsCommand.
18
+
19
+ Parameters
20
+ ----------
21
+ output : callable, optional
22
+ Output function for displaying messages. Defaults to print.
23
+ """
24
+ self.output = output or print
25
+
26
+ def execute(self):
27
+ """Execute the fetch-agents command.
28
+
29
+ Downloads a binary agent from a remote URL and saves it to the
30
+ project's .araconfig/agents directory.
31
+ """
32
+ navigator = DirectoryNavigator()
33
+ original_directory = os.getcwd()
34
+
35
+ import requests
36
+ from rich.progress import (
37
+ BarColumn,
38
+ DownloadColumn,
39
+ Progress,
40
+ TextColumn,
41
+ TimeRemainingColumn,
42
+ TransferSpeedColumn,
43
+ )
44
+
45
+ try:
46
+ # Navigate to ara directory
47
+ navigator.navigate_to_target()
48
+
49
+ dest_dir = self._get_project_agents_dir()
50
+ os.makedirs(dest_dir, exist_ok=True)
51
+
52
+ agent_name = self.AGENT_URL.split("/")[-1]
53
+ dest_path = os.path.join(dest_dir, agent_name)
54
+
55
+ self.output(f"Downloading agent from {self.AGENT_URL}...")
56
+
57
+ response = requests.get(self.AGENT_URL, stream=True)
58
+ response.raise_for_status()
59
+
60
+ total_size = int(response.headers.get("content-length", 0))
61
+ block_size = 1024
62
+ progress = Progress(
63
+ TextColumn("[bold blue]{task.description}", justify="right"),
64
+ BarColumn(bar_width=None),
65
+ "[progress.percentage]{task.percentage:>3.1f}%",
66
+ "•",
67
+ DownloadColumn(),
68
+ "•",
69
+ TransferSpeedColumn(),
70
+ "•",
71
+ TimeRemainingColumn(),
72
+ )
73
+
74
+ with progress:
75
+ task_id = progress.add_task(
76
+ f"Downloading {agent_name}", total=total_size
77
+ )
78
+ with open(dest_path, "wb") as f:
79
+ for data in response.iter_content(block_size):
80
+ progress.update(task_id, advance=len(data))
81
+ f.write(data)
82
+
83
+ if total_size != 0 and os.path.getsize(dest_path) != total_size:
84
+ raise Exception("ERROR, something went wrong during download")
85
+
86
+ # Make the binary executable
87
+ os.chmod(dest_path, 0o755)
88
+
89
+ self.output(f"Downloaded {agent_name} to ara/.araconfig/agents/")
90
+ self.output("Binary agents fetched successfully to ara/.araconfig/agents/")
91
+
92
+ except requests.exceptions.RequestException as e:
93
+ self.output(f"Error downloading agent: {e}")
94
+ finally:
95
+ # Return to original directory
96
+ os.chdir(original_directory)
97
+
98
+ def _get_project_agents_dir(self):
99
+ """Get the path to the project agents directory.
100
+
101
+ Returns
102
+ -------
103
+ str
104
+ Path to ara/.araconfig/agents directory.
105
+ """
106
+ return os.path.join(".araconfig", "agents")
@@ -0,0 +1,43 @@
1
+ import os
2
+ import shutil
3
+ from ara_cli.commands.command import Command
4
+ from ara_cli.ara_config import ConfigManager
5
+ from ara_cli.directory_navigator import DirectoryNavigator
6
+
7
+ class FetchScriptsCommand(Command):
8
+ def __init__(self, output=None):
9
+ self.output = output or print
10
+ self.config = ConfigManager.get_config()
11
+
12
+ def execute(self):
13
+ navigator = DirectoryNavigator()
14
+ original_directory = os.getcwd()
15
+ navigator.navigate_to_target()
16
+ os.chdir('..')
17
+
18
+ global_scripts_dir = self._get_global_scripts_dir()
19
+ global_scripts_config_dir = self._get_global_scripts_config_dir()
20
+
21
+ if not os.path.exists(global_scripts_dir):
22
+ self.output("Global scripts directory not found.")
23
+ os.chdir(original_directory)
24
+ return
25
+
26
+ if not os.path.exists(global_scripts_config_dir):
27
+ os.makedirs(global_scripts_config_dir)
28
+
29
+ for item in os.listdir(global_scripts_dir):
30
+ source = os.path.join(global_scripts_dir, item)
31
+ destination = os.path.join(global_scripts_config_dir, item)
32
+ if os.path.isfile(source):
33
+ shutil.copy2(source, destination)
34
+ self.output(f"Copied {item} to global scripts directory.")
35
+
36
+ os.chdir(original_directory)
37
+
38
+ def _get_global_scripts_dir(self):
39
+ base_path = os.path.dirname(os.path.dirname(__file__))
40
+ return os.path.join(base_path, "templates", "global-scripts")
41
+
42
+ def _get_global_scripts_config_dir(self):
43
+ return os.path.join(self.config.local_prompt_templates_dir, "global-scripts")
@@ -0,0 +1,39 @@
1
+ from os.path import join
2
+ import os
3
+ import shutil
4
+ from ara_cli.commands.command import Command
5
+ from ara_cli.ara_config import ConfigManager
6
+ from ara_cli.template_manager import TemplatePathManager
7
+
8
+
9
+ class FetchTemplatesCommand(Command):
10
+ def __init__(self, output=None):
11
+ self.output = output or print
12
+
13
+ def execute(self):
14
+ config = ConfigManager().get_config()
15
+ prompt_templates_dir = config.local_prompt_templates_dir
16
+ template_base_path = TemplatePathManager.get_template_base_path()
17
+ global_prompt_templates_path = join(
18
+ template_base_path, "prompt-modules")
19
+
20
+ subdirs = ["commands", "rules", "intentions", "blueprints"]
21
+
22
+ os.makedirs(join(prompt_templates_dir,
23
+ "global-prompt-modules"), exist_ok=True)
24
+ for subdir in subdirs:
25
+ target_dir = join(prompt_templates_dir,
26
+ "global-prompt-modules", subdir)
27
+ source_dir = join(global_prompt_templates_path, subdir)
28
+ os.makedirs(target_dir, exist_ok=True)
29
+ for item in os.listdir(source_dir):
30
+ source = join(source_dir, item)
31
+ target = join(target_dir, item)
32
+ shutil.copy2(source, target)
33
+
34
+ custom_prompt_templates_subdir = config.custom_prompt_templates_subdir
35
+ local_prompt_modules_dir = join(
36
+ prompt_templates_dir, custom_prompt_templates_subdir)
37
+ os.makedirs(local_prompt_modules_dir, exist_ok=True)
38
+ for subdir in subdirs:
39
+ os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
@@ -0,0 +1,39 @@
1
+ from os.path import join
2
+ import os
3
+ import shutil
4
+ from ara_cli.commands.command import Command
5
+ from ara_cli.ara_config import ConfigManager
6
+ from ara_cli.template_manager import TemplatePathManager
7
+
8
+
9
+ class FetchTemplatesCommand(Command):
10
+ def __init__(self, output=None):
11
+ self.output = output or print
12
+
13
+ def execute(self):
14
+ config = ConfigManager().get_config()
15
+ prompt_templates_dir = config.local_prompt_templates_dir
16
+ template_base_path = TemplatePathManager.get_template_base_path()
17
+ global_prompt_templates_path = join(
18
+ template_base_path, "prompt-modules")
19
+
20
+ subdirs = ["commands", "rules", "intentions", "blueprints"]
21
+
22
+ os.makedirs(join(prompt_templates_dir,
23
+ "global-prompt-modules"), exist_ok=True)
24
+ for subdir in subdirs:
25
+ target_dir = join(prompt_templates_dir,
26
+ "global-prompt-modules", subdir)
27
+ source_dir = join(global_prompt_templates_path, subdir)
28
+ os.makedirs(target_dir, exist_ok=True)
29
+ for item in os.listdir(source_dir):
30
+ source = join(source_dir, item)
31
+ target = join(target_dir, item)
32
+ shutil.copy2(source, target)
33
+
34
+ custom_prompt_templates_subdir = config.custom_prompt_templates_subdir
35
+ local_prompt_modules_dir = join(
36
+ prompt_templates_dir, custom_prompt_templates_subdir)
37
+ os.makedirs(local_prompt_modules_dir, exist_ok=True)
38
+ for subdir in subdirs:
39
+ os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
@@ -0,0 +1,39 @@
1
+ import os
2
+ from ara_cli.commands.command import Command
3
+
4
+
5
+ def list_available_binary_agents(chat_instance):
6
+ """Helper to list executable files in the agents directory."""
7
+ try:
8
+ base_dir = chat_instance._find_project_root()
9
+ if not base_dir:
10
+ return [] # Can't find project root
11
+
12
+ agents_dir = os.path.join(base_dir, "ara", ".araconfig", "agents")
13
+ if not os.path.isdir(agents_dir):
14
+ return []
15
+
16
+ available_agents = []
17
+ for f in os.listdir(agents_dir):
18
+ path = os.path.join(agents_dir, f)
19
+ if os.path.isfile(path) and os.access(path, os.X_OK):
20
+ available_agents.append(f)
21
+ return available_agents
22
+ except Exception:
23
+ return [] # Fail silently
24
+
25
+
26
+ class ListAgentsCommand(Command):
27
+ def __init__(self, chat_instance):
28
+ self.chat_instance = chat_instance
29
+
30
+ def execute(self):
31
+ """Lists all available executable binary agents."""
32
+ print("Searching for available agents in 'ara/.araconfig/agents/'...")
33
+ available_agents = list_available_binary_agents(self.chat_instance)
34
+ if available_agents:
35
+ print("\nAvailable binary agents:")
36
+ for agent in available_agents:
37
+ print(f" - {agent}")
38
+ else:
39
+ print("No executable binary agents found.")
@@ -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
- filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
66
- file_classifier.print_classified_files(
67
- filtered_artefacts, print_content=True
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,180 @@
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.constants import VALID_ASPECTS
8
+
9
+
10
+ def complete_classifier(incomplete: str) -> List[str]:
11
+ """Complete classifier names."""
12
+ return [c for c in Classifier.ordered_classifiers() if c.startswith(incomplete)]
13
+
14
+
15
+ def complete_aspect(incomplete: str) -> List[str]:
16
+ """Complete aspect names."""
17
+ aspects = VALID_ASPECTS
18
+ return [a for a in aspects if a.startswith(incomplete)]
19
+
20
+
21
+ def complete_status(incomplete: str) -> List[str]:
22
+ """Complete task status values."""
23
+ statuses = ["to-do", "in-progress", "review", "done", "closed"]
24
+ return [s for s in statuses if s.startswith(incomplete)]
25
+
26
+
27
+ def complete_template_type(incomplete: str) -> List[str]:
28
+ """Complete template type values."""
29
+ template_types = ["rules", "intention", "commands", "blueprint"]
30
+ return [t for t in template_types if t.startswith(incomplete)]
31
+
32
+
33
+ def complete_artefact_name(classifier: str, incomplete: str = "") -> List[str]:
34
+ """Complete artefact names for a given classifier."""
35
+ try:
36
+ # Get the directory for the classifier
37
+ classifier_dir = f"ara/{Classifier.get_sub_directory(classifier)}"
38
+
39
+ if not os.path.exists(classifier_dir):
40
+ return []
41
+
42
+ # Find all files with the classifier extension
43
+ artefacts = []
44
+ suffix = f".{classifier}"
45
+
46
+ # We only care about files that match the incomplete prefix AND the suffix
47
+ # Since os.listdir gives filenames, we can check startswith(incomplete)
48
+ # but we must be careful because the file starts with the artefact name, not necessarily the incomplete part + suffix directly unless full match.
49
+ # Actually, incomplete corresponds to the artefact name part.
50
+ # So file must start with incomplete AND end with suffix.
51
+
52
+ for file in os.listdir(classifier_dir):
53
+ if file.startswith(incomplete) and file.endswith(suffix):
54
+ # Remove the extension to get the artefact name
55
+ name = file[: -len(suffix)]
56
+ artefacts.append(name)
57
+
58
+ return sorted(artefacts)
59
+ except Exception:
60
+ return []
61
+
62
+
63
+ def complete_artefact_name_for_classifier(classifier: str):
64
+ """Create a completer function for artefact names of a specific classifier."""
65
+
66
+ def completer(incomplete: str) -> List[str]:
67
+ return complete_artefact_name(classifier, incomplete)
68
+
69
+ return completer
70
+
71
+
72
+ def complete_chat_files(incomplete: str) -> List[str]:
73
+ """Complete chat file names (without .md extension)."""
74
+ try:
75
+ chat_files = []
76
+ current_dir = Path.cwd()
77
+
78
+ # Optimize by using glob with the incomplete prefix
79
+ # Note: glob pattern needs to be careful with special chars, but for autocompletion usually fine.
80
+ pattern = f"{incomplete}*.md" if incomplete else "*.md"
81
+
82
+ for file in current_dir.glob(pattern):
83
+ chat_files.append(file.stem)
84
+
85
+ return sorted(chat_files)
86
+ except Exception:
87
+ return []
88
+
89
+
90
+ # Dynamic completers that need context
91
+ class DynamicCompleters:
92
+ @staticmethod
93
+ def create_classifier_completer():
94
+ """Create a completer for classifiers."""
95
+
96
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
97
+ return complete_classifier(incomplete)
98
+
99
+ return completer
100
+
101
+ @staticmethod
102
+ def create_aspect_completer():
103
+ """Create a completer for aspects."""
104
+
105
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
106
+ return complete_aspect(incomplete)
107
+
108
+ return completer
109
+
110
+ @staticmethod
111
+ def create_status_completer():
112
+ """Create a completer for status values."""
113
+
114
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
115
+ return complete_status(incomplete)
116
+
117
+ return completer
118
+
119
+ @staticmethod
120
+ def create_template_type_completer():
121
+ """Create a completer for template types."""
122
+
123
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
124
+ return complete_template_type(incomplete)
125
+
126
+ return completer
127
+
128
+ @staticmethod
129
+ def create_artefact_name_completer():
130
+ """Create a completer for artefact names based on classifier context."""
131
+
132
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
133
+ # Try to get classifier from context
134
+ if hasattr(ctx, "params") and "classifier" in ctx.params:
135
+ classifier = ctx.params["classifier"]
136
+ if hasattr(classifier, "value"):
137
+ classifier = classifier.value
138
+ return complete_artefact_name(classifier, incomplete)
139
+ return []
140
+
141
+ return completer
142
+
143
+ @staticmethod
144
+ def create_parent_name_completer():
145
+ """Create a completer for parent artefact names based on parent classifier context."""
146
+
147
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
148
+ # Try to get parent_classifier from context
149
+ if hasattr(ctx, "params") and "parent_classifier" in ctx.params:
150
+ parent_classifier = ctx.params["parent_classifier"]
151
+ if hasattr(parent_classifier, "value"):
152
+ parent_classifier = parent_classifier.value
153
+ return complete_artefact_name(parent_classifier, incomplete)
154
+ return []
155
+
156
+ return completer
157
+
158
+ @staticmethod
159
+ def create_chat_file_completer():
160
+ """Create a completer for chat files."""
161
+
162
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
163
+ return complete_chat_files(incomplete)
164
+
165
+ return completer
166
+
167
+ @staticmethod
168
+ def create_convert_source_artefact_name_completer():
169
+ """Create a completer for convert command source artefact names based on old_classifier context."""
170
+
171
+ def completer(ctx: typer.Context, incomplete: str) -> List[str]:
172
+ # Try to get old_classifier from context
173
+ if hasattr(ctx, "params") and "old_classifier" in ctx.params:
174
+ old_classifier = ctx.params["old_classifier"]
175
+ if hasattr(old_classifier, "value"):
176
+ old_classifier = old_classifier.value
177
+ return complete_artefact_name(old_classifier, incomplete)
178
+ return []
179
+
180
+ return completer
ara_cli/constants.py ADDED
@@ -0,0 +1,2 @@
1
+
2
+ VALID_ASPECTS = ["technology", "concept", "persona", "customer", "step"]
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  from os.path import join, exists, isdir, dirname, basename
3
4
  # from ara_cli.directory_searcher import DirectorySearcher
4
5
 
@@ -23,7 +24,8 @@ class DirectoryNavigator:
23
24
  return original_directory
24
25
 
25
26
  current_directory = original_directory
26
- while current_directory != dirname(current_directory): # Ensure loop breaks at root
27
+ # Ensure loop breaks at root
28
+ while current_directory != dirname(current_directory):
27
29
  potential_path = join(current_directory, self.target_directory)
28
30
  if self.exists(potential_path):
29
31
  os.chdir(potential_path)
@@ -31,7 +33,8 @@ class DirectoryNavigator:
31
33
  current_directory = dirname(current_directory)
32
34
 
33
35
  # If the loop completes, the target directory was not found
34
- user_input = input(f"Unable to locate the '{self.target_directory}' directory. Do you want to create an 'ara' folder in the working directory? (y/N): ").strip().lower()
36
+ user_input = input(
37
+ f"Unable to locate the '{self.target_directory}' directory. Do you want to create an 'ara' folder in the working directory? (y/N): ").strip().lower()
35
38
 
36
39
  if user_input == '' or user_input == 'y':
37
40
  ara_folder_path = join(original_directory, 'ara')
@@ -40,7 +43,8 @@ class DirectoryNavigator:
40
43
  os.chdir(ara_folder_path)
41
44
  return original_directory
42
45
  else:
43
- print(f"Unable to locate the '{self.target_directory}' directory and user declined to create 'ara' folder.")
46
+ print(
47
+ f"Unable to locate the '{self.target_directory}' directory and user declined to create 'ara' folder.")
44
48
  sys.exit(0)
45
49
 
46
50
  def navigate_to_relative(self, relative_path):
@@ -56,7 +60,36 @@ class DirectoryNavigator:
56
60
  if self.exists(path):
57
61
  os.chdir(path)
58
62
  else:
59
- raise Exception(f"Unable to navigate to '{relative_path}' relative to the target directory.")
63
+ raise Exception(
64
+ f"Unable to navigate to '{relative_path}' relative to the target directory.")
65
+
66
+ @staticmethod
67
+ def find_ara_directory_root():
68
+ """Find the root ara directory by traversing up the directory tree."""
69
+ current_dir = os.getcwd()
70
+
71
+ # Check if we're already inside an ara directory structure
72
+ path_parts = current_dir.split(os.sep)
73
+
74
+ # Look for 'ara' in the path parts
75
+ if 'ara' in path_parts:
76
+ ara_index = path_parts.index('ara')
77
+ # Reconstruct path up to and including 'ara'
78
+ ara_root_parts = path_parts[:ara_index + 1]
79
+ potential_ara_root = os.sep.join(ara_root_parts)
80
+ if os.path.exists(potential_ara_root) and os.path.isdir(potential_ara_root):
81
+ return potential_ara_root
82
+
83
+ # If not inside ara directory, check current directory and parents
84
+ check_dir = current_dir
85
+ # Stop at filesystem root
86
+ while check_dir != os.path.dirname(check_dir):
87
+ ara_path = os.path.join(check_dir, 'ara')
88
+ if os.path.exists(ara_path) and os.path.isdir(ara_path):
89
+ return ara_path
90
+ check_dir = os.path.dirname(check_dir)
91
+
92
+ return None
60
93
 
61
94
  # debug version
62
95
  # def get_ara_directory(self):
@@ -19,14 +19,14 @@ class TextFileLoader(FileLoader):
19
19
 
20
20
  if is_md_file and extract_images:
21
21
  reader = MarkdownReader(file_path)
22
- file_content = reader.read(extract_images=True)
22
+ file_content = reader.read(extract_images=True).replace('\r\n', '\n')
23
23
  else:
24
24
  # Use charset-normalizer to detect encoding
25
25
  encoded_content = from_path(file_path).best()
26
26
  if not encoded_content:
27
27
  print(f"Failed to detect encoding for {file_path}")
28
28
  return False
29
- file_content = str(encoded_content)
29
+ file_content = str(encoded_content).replace('\r\n', '\n')
30
30
 
31
31
  if block_delimiter:
32
32
  file_content = f"{block_delimiter}\n{file_content}\n{block_delimiter}"
@@ -2,22 +2,19 @@ import os
2
2
  import fnmatch
3
3
  from typing import List, Dict, Any
4
4
 
5
- # Ağaç yapımız için bir tip tanımı yapalım
6
5
  DirTree = Dict[str, Any]
7
6
 
8
7
  def _build_tree(root_path: str, patterns: List[str]) -> DirTree:
9
- """Belirtilen yoldaki dizin yapısını temsil eden içe bir sözlük oluşturur."""
8
+ """Creates a nested dictionary representing the directory structure in the specified path."""
10
9
  tree: DirTree = {'files': [], 'dirs': {}}
11
10
  try:
12
11
  for item in os.listdir(root_path):
13
12
  item_path = os.path.join(root_path, item)
14
13
  if os.path.isdir(item_path):
15
14
  subtree = _build_tree(item_path, patterns)
16
- # Sadece içinde dosya olan veya dosyası olan alt klasörleri ekle
17
15
  if subtree['files'] or subtree['dirs']:
18
16
  tree['dirs'][item] = subtree
19
17
  elif os.path.isfile(item_path):
20
- # Dosyanın verilen desenlerden herhangi biriyle eşleşip eşleşmediğini kontrol et
21
18
  if any(fnmatch.fnmatch(item, pattern) for pattern in patterns):
22
19
  tree['files'].append(item)
23
20
  except OSError as e:
@@ -25,23 +22,17 @@ def _build_tree(root_path: str, patterns: List[str]) -> DirTree:
25
22
  return tree
26
23
 
27
24
  def _write_tree_to_markdown(md_file, tree: DirTree, level: int):
28
- """Ağaç veri yapısını markdown formatında dosyaya yazar."""
29
- # Dosyaları girintili olarak yaz
25
+ """Writes the tree data structure to the file in markdown format."""
30
26
  indent = ' ' * level
31
27
  for filename in sorted(tree['files']):
32
28
  md_file.write(f"{indent}- [] {filename}\n")
33
29
 
34
- # Alt dizinler için başlık oluştur ve recursive olarak devam et
35
30
  for dirname, subtree in sorted(tree['dirs'].items()):
36
- # Alt başlıklar için girinti yok, sadece başlık seviyesi artıyor
37
31
  md_file.write(f"{' ' * (level -1)}{'#' * (level + 1)} {dirname}\n")
38
32
  _write_tree_to_markdown(md_file, subtree, level + 1)
39
33
 
40
34
  def generate_global_markdown_listing(directories: List[str], file_patterns: List[str], output_file: str):
41
- """
42
- Global dizinler için hiyerarşik bir markdown dosya listesi oluşturur.
43
- En üst başlık olarak mutlak yolu kullanır, alt öğeler için göreceli isimler kullanır.
44
- """
35
+ """Creates a hierarchical list of markdown files for global directories. Uses the absolute path as the top heading and relative names for children."""
45
36
  with open(output_file, 'w', encoding='utf-8') as md_file:
46
37
  for directory in directories:
47
38
  abs_dir = os.path.abspath(directory)
@@ -49,12 +40,11 @@ def generate_global_markdown_listing(directories: List[str], file_patterns: List
49
40
  if not os.path.isdir(abs_dir):
50
41
  print(f"Warning: Global directory not found: {abs_dir}")
51
42
  md_file.write(f"# {directory}\n")
52
- md_file.write(f" - !! UYARI: Dizin bulunamadı: {abs_dir}\n\n")
43
+ md_file.write(f" - !! Warning: Global directory not found: {abs_dir}\n\n")
53
44
  continue
54
45
 
55
46
  tree = _build_tree(abs_dir, file_patterns)
56
-
57
- # Sadece ağaç boş değilse yaz
47
+
58
48
  if tree['files'] or tree['dirs']:
59
49
  md_file.write(f"# {abs_dir}\n")
60
50
  _write_tree_to_markdown(md_file, tree, 1)