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.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +270 -103
- ara_cli/ara_command_action.py +106 -63
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +19 -0
- ara_cli/ara_subcommands/list.py +139 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +154 -63
- ara_cli/artefact_converter.py +256 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +20 -10
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +31 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +283 -80
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +180 -0
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/file_loaders/text_file_loader.py +2 -2
- ara_cli/global_file_lister.py +5 -15
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +199 -76
- ara_cli/prompt_handler.py +160 -59
- ara_cli/tag_extractor.py +38 -18
- ara_cli/template_loader.py +3 -2
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +357 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_global_file_lister.py +1 -1
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_prompt_handler.py +12 -4
- tests/test_tag_extractor.py +19 -13
- tests/test_web_search.py +467 -0
- ara_cli/ara_command_parser.py +0 -605
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
ara_cli/llm_utils.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from ara_cli.ara_config import ConfigManager
|
|
2
|
+
from pydantic_ai import Agent
|
|
3
|
+
|
|
4
|
+
FALLBACK_MODEL = "anthropic:claude-4-sonnet-20250514"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_configured_conversion_llm_model() -> str:
|
|
8
|
+
"""
|
|
9
|
+
Retrieves the configured conversion LLM model string, adapted for pydantic_ai.
|
|
10
|
+
Falls back to a default model if configuration is missing or invalid.
|
|
11
|
+
"""
|
|
12
|
+
model_name = FALLBACK_MODEL
|
|
13
|
+
try:
|
|
14
|
+
config = ConfigManager.get_config()
|
|
15
|
+
conversion_llm_key = config.conversion_llm
|
|
16
|
+
|
|
17
|
+
if conversion_llm_key and conversion_llm_key in config.llm_config:
|
|
18
|
+
llm_config_item = config.llm_config[conversion_llm_key]
|
|
19
|
+
raw_model_name = llm_config_item.model
|
|
20
|
+
|
|
21
|
+
# Adapt LiteLLM model string to PydanticAI format
|
|
22
|
+
# LiteLLM: provider/model-name (e.g. openai/gpt-4o)
|
|
23
|
+
# PydanticAI: provider:model-name (e.g. openai:gpt-4o)
|
|
24
|
+
if "/" in raw_model_name and ":" not in raw_model_name:
|
|
25
|
+
parts = raw_model_name.split("/", 1)
|
|
26
|
+
if len(parts) == 2:
|
|
27
|
+
model_name = f"{parts[0]}:{parts[1]}"
|
|
28
|
+
else:
|
|
29
|
+
model_name = raw_model_name
|
|
30
|
+
else:
|
|
31
|
+
model_name = raw_model_name
|
|
32
|
+
else:
|
|
33
|
+
print(
|
|
34
|
+
f"Warning: Conversion LLM configuration issue. Using fallback model: {FALLBACK_MODEL}"
|
|
35
|
+
)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(
|
|
38
|
+
f"Warning: Error resolving LLM config ({e}). Using fallback model: {FALLBACK_MODEL}"
|
|
39
|
+
)
|
|
40
|
+
model_name = FALLBACK_MODEL
|
|
41
|
+
|
|
42
|
+
return model_name
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_pydantic_ai_agent(
|
|
46
|
+
output_type, model_name: str = None, instrument: bool = True
|
|
47
|
+
) -> Agent:
|
|
48
|
+
"""
|
|
49
|
+
Creates a pydantic_ai Agent with the specified or configured model.
|
|
50
|
+
"""
|
|
51
|
+
if not model_name:
|
|
52
|
+
model_name = get_configured_conversion_llm_model()
|
|
53
|
+
|
|
54
|
+
return Agent(
|
|
55
|
+
model=model_name,
|
|
56
|
+
output_type=output_type,
|
|
57
|
+
instrument=instrument,
|
|
58
|
+
)
|
ara_cli/prompt_chat.py
CHANGED
|
@@ -6,9 +6,18 @@ from ara_cli.update_config_prompt import update_artefact_config_prompt_files
|
|
|
6
6
|
from ara_cli.output_suppressor import suppress_stdout
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def initialize_prompt_chat_mode(
|
|
9
|
+
def initialize_prompt_chat_mode(
|
|
10
|
+
classifier,
|
|
11
|
+
param,
|
|
12
|
+
chat_name,
|
|
13
|
+
reset=None,
|
|
14
|
+
output_mode=False,
|
|
15
|
+
append_strings=[],
|
|
16
|
+
restricted=False,
|
|
17
|
+
):
|
|
10
18
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
11
|
-
|
|
19
|
+
# f"ara/{sub_directory}/{parameter}.data"
|
|
20
|
+
artefact_data_path = os.path.join("ara", sub_directory, f"{param}.data")
|
|
12
21
|
|
|
13
22
|
if chat_name is None:
|
|
14
23
|
chat_name = classifier
|
|
@@ -17,11 +26,18 @@ def initialize_prompt_chat_mode(classifier, param, chat_name, reset=None, output
|
|
|
17
26
|
update_artefact_config_prompt_files(classifier, param, automatic_update=True)
|
|
18
27
|
|
|
19
28
|
classifier_chat_file = os.path.join(artefact_data_path, f"{chat_name}")
|
|
20
|
-
start_chat_session(
|
|
29
|
+
start_chat_session(
|
|
30
|
+
classifier_chat_file, reset, output_mode, append_strings, restricted
|
|
31
|
+
)
|
|
32
|
+
|
|
21
33
|
|
|
22
34
|
def start_chat_session(chat_file, reset, output_mode, append_strings, restricted):
|
|
23
35
|
with suppress_stdout(suppress=output_mode):
|
|
24
|
-
chat =
|
|
36
|
+
chat = (
|
|
37
|
+
Chat(chat_file, reset=reset)
|
|
38
|
+
if not restricted
|
|
39
|
+
else Chat(chat_file, reset=reset, enable_commands=whitelisted_commands)
|
|
40
|
+
)
|
|
25
41
|
if append_strings:
|
|
26
42
|
chat.append_strings(append_strings)
|
|
27
43
|
if output_mode:
|
ara_cli/prompt_extractor.py
CHANGED
|
@@ -9,90 +9,208 @@ from ara_cli.directory_navigator import DirectoryNavigator
|
|
|
9
9
|
from ara_cli.artefact_models.artefact_mapping import title_prefix_to_artefact_class
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
def _find_extract_token(tokens):
|
|
13
|
+
"""Find the first token that needs to be processed."""
|
|
14
|
+
for token in tokens:
|
|
15
|
+
if token.type == 'fence' and token.content.strip().startswith("# [x] extract"):
|
|
16
|
+
return token
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _extract_file_path(content_lines):
|
|
21
|
+
"""Extract file path from content lines."""
|
|
22
|
+
if not content_lines:
|
|
23
|
+
return None
|
|
24
|
+
file_path_search = re.search(r"# filename: (.+)", content_lines[0])
|
|
25
|
+
return file_path_search.group(1).strip() if file_path_search else None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _find_artefact_class(content_lines):
|
|
29
|
+
"""Find the appropriate artefact class from content lines."""
|
|
30
|
+
for line in content_lines[:2]:
|
|
31
|
+
words = line.strip().split(' ')
|
|
32
|
+
if not words:
|
|
33
|
+
continue
|
|
34
|
+
first_word = words[0]
|
|
35
|
+
if first_word in title_prefix_to_artefact_class:
|
|
36
|
+
return title_prefix_to_artefact_class[first_word]
|
|
37
|
+
return None
|
|
17
38
|
|
|
18
39
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
def _process_file_extraction(file_path, code_content, force, write):
|
|
41
|
+
"""Process file extraction logic."""
|
|
42
|
+
print(f"Filename extracted: {file_path}")
|
|
43
|
+
handle_existing_file(file_path, code_content, force, write)
|
|
22
44
|
|
|
23
|
-
with open(document_path, 'r', encoding='utf-8', errors='replace') as file:
|
|
24
|
-
content = file.read()
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
os.chdir('..')
|
|
46
|
+
def _process_artefact_extraction(artefact_class, content_lines, force, write):
|
|
47
|
+
"""Process artefact extraction logic."""
|
|
48
|
+
artefact = artefact_class.deserialize('\n'.join(content_lines))
|
|
49
|
+
serialized_artefact = artefact.serialize()
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
original_directory = os.getcwd()
|
|
52
|
+
directory_navigator = DirectoryNavigator()
|
|
53
|
+
directory_navigator.navigate_to_target()
|
|
34
54
|
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
artefact_path = artefact.file_path
|
|
56
|
+
directory = os.path.dirname(artefact_path)
|
|
57
|
+
os.makedirs(directory, exist_ok=True)
|
|
58
|
+
handle_existing_file(artefact_path, serialized_artefact, force, write)
|
|
37
59
|
|
|
38
|
-
|
|
39
|
-
continue
|
|
40
|
-
print("Block found and processed.")
|
|
41
|
-
|
|
42
|
-
block_lines = block_lines[1:]
|
|
60
|
+
os.chdir(original_directory)
|
|
43
61
|
|
|
44
|
-
file_path_search = re.search(r"# filename: (.+)", block_lines[0])
|
|
45
62
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
def _perform_extraction_for_block(source_lines, block_start, block_end, force, write):
|
|
64
|
+
"""Helper function to process a single, identified block."""
|
|
65
|
+
original_block_text = '\n'.join(source_lines[block_start:block_end + 1])
|
|
66
|
+
block_content_lines = source_lines[block_start + 1:block_end]
|
|
67
|
+
block_content = '\n'.join(block_content_lines)
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
block_lines = block_content.split('\n')
|
|
70
|
+
content_lines_after_extract = block_lines[1:]
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
block_extraction_counter += 1
|
|
72
|
+
file_path = _extract_file_path(content_lines_after_extract)
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
if file_path:
|
|
75
|
+
code_content = '\n'.join(content_lines_after_extract[1:])
|
|
76
|
+
_process_file_extraction(file_path, code_content, force, write)
|
|
77
|
+
else:
|
|
78
|
+
artefact_class = _find_artefact_class(content_lines_after_extract)
|
|
79
|
+
if artefact_class:
|
|
80
|
+
_process_artefact_extraction(
|
|
81
|
+
artefact_class, content_lines_after_extract, force, write)
|
|
58
82
|
else:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
print(
|
|
84
|
+
"No filename or valid artefact found, skipping processing for this block.")
|
|
85
|
+
return None, None
|
|
86
|
+
|
|
87
|
+
modified_block_text = original_block_text.replace(
|
|
88
|
+
"# [x] extract", "# [v] extract", 1)
|
|
89
|
+
return original_block_text, modified_block_text
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class FenceDetector:
|
|
93
|
+
"""Helper class to detect and match fence blocks."""
|
|
94
|
+
|
|
95
|
+
def __init__(self, source_lines):
|
|
96
|
+
self.source_lines = source_lines
|
|
97
|
+
|
|
98
|
+
def is_extract_fence(self, line_num):
|
|
99
|
+
"""Check if line is a fence with extract marker."""
|
|
100
|
+
line = self.source_lines[line_num]
|
|
101
|
+
stripped_line = line.strip()
|
|
102
|
+
|
|
103
|
+
is_fence = stripped_line.startswith(
|
|
104
|
+
'```') or stripped_line.startswith('~~~')
|
|
105
|
+
if not is_fence:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
if not (line_num + 1 < len(self.source_lines)):
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
return self.source_lines[line_num + 1].strip().startswith("# [x] extract")
|
|
112
|
+
|
|
113
|
+
def find_matching_fence_end(self, start_line):
|
|
114
|
+
"""Find the matching end fence for a given start fence."""
|
|
115
|
+
fence_line = self.source_lines[start_line]
|
|
116
|
+
indentation = len(fence_line) - len(fence_line.lstrip())
|
|
117
|
+
stripped_fence_line = fence_line.strip()
|
|
118
|
+
fence_char = stripped_fence_line[0]
|
|
119
|
+
fence_length = len(stripped_fence_line) - \
|
|
120
|
+
len(stripped_fence_line.lstrip(fence_char))
|
|
121
|
+
|
|
122
|
+
for i in range(start_line + 1, len(self.source_lines)):
|
|
123
|
+
scan_line = self.source_lines[i]
|
|
124
|
+
stripped_scan_line = scan_line.strip()
|
|
125
|
+
|
|
126
|
+
if not stripped_scan_line or stripped_scan_line[0] != fence_char:
|
|
71
127
|
continue
|
|
72
|
-
artefact = artefact_class.deserialize('\n'.join(block_lines))
|
|
73
|
-
serialized_artefact = artefact.serialize()
|
|
74
128
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
directory_navigator.navigate_to_target()
|
|
129
|
+
if not all(c == fence_char for c in stripped_scan_line):
|
|
130
|
+
continue
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
132
|
+
candidate_indentation = len(scan_line) - len(scan_line.lstrip())
|
|
133
|
+
candidate_length = len(stripped_scan_line)
|
|
134
|
+
|
|
135
|
+
if candidate_length == fence_length and candidate_indentation == indentation:
|
|
136
|
+
return i
|
|
137
|
+
|
|
138
|
+
return -1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _process_document_blocks(source_lines, force, write):
|
|
142
|
+
"""Process all extract blocks in the document."""
|
|
143
|
+
fence_detector = FenceDetector(source_lines)
|
|
144
|
+
replacements = []
|
|
145
|
+
line_num = 0
|
|
146
|
+
|
|
147
|
+
while line_num < len(source_lines):
|
|
148
|
+
if not fence_detector.is_extract_fence(line_num):
|
|
149
|
+
line_num += 1
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
block_start_line = line_num
|
|
153
|
+
block_end_line = fence_detector.find_matching_fence_end(
|
|
154
|
+
block_start_line)
|
|
155
|
+
|
|
156
|
+
if block_end_line != -1:
|
|
157
|
+
print(
|
|
158
|
+
f"Block found and processed starting on line {block_start_line + 1}.")
|
|
159
|
+
original, modified = _perform_extraction_for_block(
|
|
160
|
+
source_lines, block_start_line, block_end_line, force, write
|
|
161
|
+
)
|
|
162
|
+
if original and modified:
|
|
163
|
+
replacements.append((original, modified))
|
|
164
|
+
line_num = block_end_line + 1
|
|
165
|
+
else:
|
|
166
|
+
line_num += 1
|
|
167
|
+
|
|
168
|
+
return replacements
|
|
83
169
|
|
|
84
|
-
os.chdir(original_directory)
|
|
85
170
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
171
|
+
def _apply_replacements(content, replacements):
|
|
172
|
+
"""Apply all replacements to the content."""
|
|
173
|
+
updated_content = content
|
|
174
|
+
for original, modified in replacements:
|
|
175
|
+
updated_content = updated_content.replace(original, modified, 1)
|
|
176
|
+
return updated_content
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _setup_working_directory(relative_to_ara_root):
|
|
180
|
+
"""Setup working directory and return original cwd."""
|
|
181
|
+
cwd = os.getcwd()
|
|
182
|
+
if relative_to_ara_root:
|
|
183
|
+
navigator = DirectoryNavigator()
|
|
184
|
+
navigator.navigate_to_target()
|
|
185
|
+
os.chdir('..')
|
|
186
|
+
return cwd
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def extract_responses(document_path, relative_to_ara_root=False, force=False, write=False):
|
|
190
|
+
print(f"Starting extraction from '{document_path}'", flush=True)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
with open(document_path, 'r', encoding='utf-8', errors='replace') as file:
|
|
194
|
+
content = file.read()
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
print(
|
|
197
|
+
f"Error: File not found at '{document_path}'. Skipping extraction.")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
cwd = _setup_working_directory(relative_to_ara_root)
|
|
201
|
+
|
|
202
|
+
source_lines = content.split('\n')
|
|
203
|
+
replacements = _process_document_blocks(source_lines, force, write)
|
|
204
|
+
|
|
205
|
+
updated_content = _apply_replacements(content, replacements)
|
|
89
206
|
|
|
90
207
|
os.chdir(cwd)
|
|
91
|
-
# Save the updated markdown content
|
|
92
208
|
with open(document_path, 'w', encoding='utf-8') as file:
|
|
93
209
|
file.write(updated_content)
|
|
94
210
|
|
|
95
|
-
|
|
211
|
+
if replacements:
|
|
212
|
+
print(
|
|
213
|
+
f"End of extraction. Found and processed {len(replacements)} blocks in '{os.path.basename(document_path)}'.")
|
|
96
214
|
|
|
97
215
|
|
|
98
216
|
def modify_and_save_file(response, file_path):
|
|
@@ -107,7 +225,8 @@ def modify_and_save_file(response, file_path):
|
|
|
107
225
|
""")
|
|
108
226
|
|
|
109
227
|
if filename_from_response != file_path:
|
|
110
|
-
user_decision = prompt_user_decision(
|
|
228
|
+
user_decision = prompt_user_decision(
|
|
229
|
+
"Filename does not match, overwrite? (y/n): ")
|
|
111
230
|
if user_decision.lower() not in ['y', 'yes']:
|
|
112
231
|
print("Debug: User chose not to overwrite")
|
|
113
232
|
print("Skipping block.")
|
|
@@ -126,8 +245,11 @@ def prompt_user_decision(prompt):
|
|
|
126
245
|
|
|
127
246
|
def determine_should_create(skip_query=False):
|
|
128
247
|
if skip_query:
|
|
248
|
+
print("[DEBUG] skip_query is True, allowing creation.", flush=True)
|
|
129
249
|
return True
|
|
130
|
-
|
|
250
|
+
print(f"[DEBUG] About to prompt for file creation: File does not exist. Create? (y/n): ", flush=True)
|
|
251
|
+
user_decision = prompt_user_decision(
|
|
252
|
+
"File does not exist. Create? (y/n): ")
|
|
131
253
|
if user_decision.lower() in ['y', 'yes']:
|
|
132
254
|
return True
|
|
133
255
|
return False
|
|
@@ -138,7 +260,9 @@ def create_file_if_not_exist(filename, content, skip_query=False):
|
|
|
138
260
|
if not os.path.exists(filename):
|
|
139
261
|
if determine_should_create(skip_query):
|
|
140
262
|
# Ensure the directory exists
|
|
141
|
-
os.
|
|
263
|
+
dir_name = os.path.dirname(filename)
|
|
264
|
+
if dir_name:
|
|
265
|
+
os.makedirs(dir_name, exist_ok=True)
|
|
142
266
|
|
|
143
267
|
with open(filename, 'w', encoding='utf-8') as file:
|
|
144
268
|
file.write(content)
|
|
@@ -156,8 +280,8 @@ def create_prompt_for_file_modification(content_str, filename):
|
|
|
156
280
|
print(f"WARNING: {filename} for merge prompt creation does not exist.")
|
|
157
281
|
return
|
|
158
282
|
|
|
159
|
-
content_of_existing_file =
|
|
160
|
-
content =
|
|
283
|
+
content_of_existing_file = get_file_content(filename)
|
|
284
|
+
content = content_str
|
|
161
285
|
|
|
162
286
|
prompt_text = f"""
|
|
163
287
|
* given this new_content:
|
|
@@ -185,9 +309,15 @@ def create_prompt_for_file_modification(content_str, filename):
|
|
|
185
309
|
def handle_existing_file(filename, block_content, skip_query=False, write=False):
|
|
186
310
|
if not os.path.isfile(filename):
|
|
187
311
|
print(f"File {filename} does not exist, attempting to create")
|
|
312
|
+
# Ensure directory exists before writing
|
|
313
|
+
directory = os.path.dirname(filename)
|
|
314
|
+
if directory:
|
|
315
|
+
os.makedirs(directory, exist_ok=True)
|
|
188
316
|
create_file_if_not_exist(filename, block_content, skip_query)
|
|
317
|
+
|
|
189
318
|
elif write:
|
|
190
|
-
print(
|
|
319
|
+
print(
|
|
320
|
+
f"File {filename} exists. Overwriting without LLM merge as requested.")
|
|
191
321
|
try:
|
|
192
322
|
directory = os.path.dirname(filename)
|
|
193
323
|
if directory:
|
|
@@ -200,7 +330,8 @@ def handle_existing_file(filename, block_content, skip_query=False, write=False)
|
|
|
200
330
|
print(f"Failed to overwrite file {filename} due to an OS error")
|
|
201
331
|
else:
|
|
202
332
|
print(f"File {filename} exists, creating modification prompt")
|
|
203
|
-
prompt_text = create_prompt_for_file_modification(
|
|
333
|
+
prompt_text = create_prompt_for_file_modification(
|
|
334
|
+
block_content, filename)
|
|
204
335
|
if prompt_text is None:
|
|
205
336
|
return
|
|
206
337
|
|
|
@@ -220,11 +351,3 @@ def extract_and_save_prompt_results(classifier, param, write=False):
|
|
|
220
351
|
print(f"Extract marked sections from: {prompt_log_file}")
|
|
221
352
|
|
|
222
353
|
extract_responses(prompt_log_file, write=write)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def update_markdown(original_content, block_content, filename):
|
|
226
|
-
"""
|
|
227
|
-
Update the markdown content by changing the extract block from "# [x] extract" to "# [v] extract"
|
|
228
|
-
"""
|
|
229
|
-
updated_content = original_content.replace("# [x] extract", "# [v] extract")
|
|
230
|
-
return updated_content
|