diff --git a/src/drd/cli/query/dynamic_command_handler.py b/src/drd/cli/query/dynamic_command_handler.py
index 6154a89..80144ea 100644
--- a/src/drd/cli/query/dynamic_command_handler.py
+++ b/src/drd/cli/query/dynamic_command_handler.py
@@ -1,6 +1,7 @@
import traceback
import click
from ...api.main import call_dravid_api
+import xml.etree.ElementTree as ET
from ...utils import print_error, print_success, print_info, print_step, print_debug
from ...metadata.common_utils import generate_file_description
from ...prompts.error_resolution_prompt import get_error_resolution_prompt
@@ -75,6 +76,8 @@ def handle_file_operation(cmd, executor, metadata_manager):
elif operation_performed:
print_success(
f"Successfully performed {cmd['operation']} on file: {cmd['filename']}")
+ if cmd['operation'] in ['CREATE', 'UPDATE']:
+ update_file_metadata(cmd, metadata_manager, executor)
return "Success"
else:
raise Exception(
@@ -94,21 +97,57 @@ def handle_metadata_operation(cmd, metadata_manager):
def update_file_metadata(cmd, metadata_manager, executor):
- project_context = metadata_manager.get_project_context()
- folder_structure = executor.get_folder_structure()
- file_type, description, exports = generate_file_description(
- cmd['filename'],
- cmd.get('content', ''),
- project_context,
- folder_structure
- )
- metadata_manager.update_file_metadata(
- cmd['filename'],
- file_type,
- cmd.get('content', ''),
- description,
- exports
- )
+ file_info = metadata_manager.analyze_file(cmd['filename'])
+ if file_info:
+ metadata_manager.update_file_metadata(
+ file_info['path'],
+ file_info['type'],
+ cmd.get('content', ''),
+ file_info['summary'],
+ file_info['exports'],
+ file_info['imports']
+ )
+
+ # Handle dependencies from the XML response
+ handle_dependencies(file_info, metadata_manager)
+
+
+def handle_dependencies(file_info, metadata_manager):
+ if 'xml_response' in file_info:
+ try:
+ root = ET.fromstring(file_info['xml_response'])
+ dependencies = root.find('.//external_dependencies')
+ if dependencies is not None:
+ for dep in dependencies.findall('dependency'):
+ dependency_info = dep.text.strip()
+ metadata_manager.add_external_dependency(dependency_info)
+ print_info(
+ f"Added {len(dependencies)} dependencies to the project metadata.")
+
+ # Handle other metadata updates
+ update_project_info(root, metadata_manager)
+ update_dev_server_info(root, metadata_manager)
+ except ET.ParseError:
+ print_error("Failed to parse XML response for dependencies")
+
+
+def update_project_info(root, metadata_manager):
+ project_info = root.find('.//project_info')
+ if project_info is not None:
+ for field in ['name', 'version', 'description']:
+ element = project_info.find(field)
+ if element is not None and element.text:
+ metadata_manager.metadata['project_info'][field] = element.text.strip(
+ )
+
+
+def update_dev_server_info(root, metadata_manager):
+ dev_server = root.find('.//dev_server')
+ if dev_server is not None:
+ start_command = dev_server.find('start_command')
+ if start_command is not None and start_command.text:
+ metadata_manager.metadata['dev_server']['start_command'] = start_command.text.strip(
+ )
def handle_error_with_dravid(error, cmd, executor, metadata_manager, depth=0, previous_context="", debug=False):
diff --git a/src/drd/cli/query/main.py b/src/drd/cli/query/main.py
index 573e01b..1d3dea0 100644
--- a/src/drd/cli/query/main.py
+++ b/src/drd/cli/query/main.py
@@ -51,7 +51,6 @@ def execute_dravid_command(query, image_path, debug, instruction_prompt, warn=No
full_query = construct_full_query(
query, executor, project_context, files_info, reference_files)
- print(full_query, "full query")
print_info("💡 Preparing to send query to LLM...", indent=2)
if image_path:
@@ -80,9 +79,7 @@ def execute_dravid_command(query, image_path, debug, instruction_prompt, warn=No
success, step_completed, error_message, all_outputs = execute_commands(
commands, executor, metadata_manager, debug=debug)
- print("no scucess", success)
if not success:
- print("called")
print_error(
f"Failed to execute command at step {step_completed}.")
print_error(f"Error message: {error_message}")
@@ -111,7 +108,6 @@ def execute_dravid_command(query, image_path, debug, instruction_prompt, warn=No
def construct_full_query(query, executor, project_context, files_info=None, reference_files=None):
is_empty = is_directory_empty(executor.current_dir)
-
if is_empty:
print_info(
"Current directory is empty. Will create a new project.", indent=2)
@@ -123,41 +119,32 @@ def construct_full_query(query, executor, project_context, files_info=None, refe
else:
print_info(
"Constructing query with project context and file information.", indent=2)
-
project_guidelines = fetch_project_guidelines(executor.current_dir)
-
full_query = f"{project_context}\n\n"
full_query += f"Project Guidelines:\n{project_guidelines}\n\n"
-
- if files_info:
- if files_info['file_contents_to_load']:
+ if files_info and isinstance(files_info, dict):
+ if 'file_contents_to_load' in files_info:
file_contents = {}
for file in files_info['file_contents_to_load']:
content = get_file_content(file)
if content:
file_contents[file] = content
print_info(f" - Read content of {file}", indent=4)
-
file_context = "\n".join(
[f"Current content of {file}:\n{content}" for file, content in file_contents.items()])
full_query += f"Current file contents:\n{file_context}\n\n"
-
- if files_info['dependencies']:
+ if 'dependencies' in files_info:
dependency_context = "\n".join(
[f"Dependency {dep['file']} exports: {', '.join(dep['imports'])}" for dep in files_info['dependencies']])
full_query += f"Dependencies:\n{dependency_context}\n\n"
-
- if files_info['new_files']:
+ if 'new_files' in files_info:
new_files_context = "\n".join(
[f"New file to create: {new_file['file']}" for new_file in files_info['new_files']])
full_query += f"New files to create:\n{new_files_context}\n\n"
-
- if files_info['main_file']:
+ if 'main_file' in files_info:
full_query += f"Main file to modify: {files_info['main_file']}\n\n"
-
full_query += "Current directory is not empty.\n\n"
full_query += f"User query: {query}"
-
if reference_files:
print_info("📄 Reading reference file contents...", indent=2)
reference_contents = {}
@@ -166,9 +153,7 @@ def construct_full_query(query, executor, project_context, files_info=None, refe
if content:
reference_contents[file] = content
print_info(f" - Read content of {file}", indent=4)
-
reference_context = "\n\n".join(
[f"Reference file {file}:\n{content}" for file, content in reference_contents.items()])
full_query += f"\n\nReference files:\n{reference_context}"
-
return full_query
diff --git a/src/drd/metadata/project_metadata.py b/src/drd/metadata/project_metadata.py
index 9b406e9..19054ab 100644
--- a/src/drd/metadata/project_metadata.py
+++ b/src/drd/metadata/project_metadata.py
@@ -158,7 +158,7 @@ async def analyze_file(self, file_path):
file_info = {
"path": rel_path,
"type": metadata.find('type').text,
- "summary": metadata.find('description').text,
+ "summary": metadata.find('summary').text,
"exports": metadata.find('exports').text.split(',') if metadata.find('exports').text != 'None' else [],
"imports": metadata.find('imports').text.split(',') if metadata.find('imports').text != 'None' else []
}
@@ -250,3 +250,35 @@ def update_file_metadata(self, filename, file_type, content, description=None, e
'imports': imports or []
})
self.save_metadata()
+
+ def update_metadata_from_file(self):
+ if os.path.exists(self.metadata_file):
+ with open(self.metadata_file, 'r') as f:
+ content = f.read()
+ try:
+ new_metadata = json.loads(content)
+ # Update dev server info if present
+ if 'dev_server' in new_metadata:
+ self.metadata['dev_server'] = new_metadata['dev_server']
+ # Update other metadata fields
+ for key, value in new_metadata.items():
+ if key != 'files': # We'll handle files separately
+ self.metadata[key] = value
+ # Update file metadata
+ if 'files' in new_metadata:
+ for file_entry in new_metadata['files']:
+ filename = file_entry['filename']
+ file_type = file_entry.get(
+ 'type', filename.split('.')[-1])
+ file_content = file_entry.get('content', '')
+ description = file_entry.get('description', '')
+ exports = file_entry.get('exports', [])
+ imports = file_entry.get('imports', [])
+ self.update_file_metadata(
+ filename, file_type, file_content, description, exports, imports)
+ self.save_metadata()
+ return True
+ except json.JSONDecodeError:
+ print(f"Error: Invalid JSON content in {self.metadata_file}")
+ return False
+ return False
diff --git a/src/drd/metadata/rate_limit_handler.py b/src/drd/metadata/rate_limit_handler.py
index 06883ab..a83e96e 100644
--- a/src/drd/metadata/rate_limit_handler.py
+++ b/src/drd/metadata/rate_limit_handler.py
@@ -48,24 +48,26 @@ async def process_single_file(filename, content, project_context, folder_structu
async with rate_limiter.semaphore:
await rate_limiter.acquire()
response = await to_thread(call_dravid_api_with_pagination, metadata_query, include_context=True)
-
root = extract_and_parse_xml(response)
type_elem = root.find('.//type')
- desc_elem = root.find('.//description')
+ summary_elem = root.find('.//summary')
exports_elem = root.find('.//exports')
-
+ imports_elem = root.find('.//imports') # Added imports_elem
file_type = type_elem.text.strip(
) if type_elem is not None and type_elem.text else "unknown"
- description = desc_elem.text.strip(
- ) if desc_elem is not None and desc_elem.text else "No description available"
+ summary = summary_elem.text.strip(
+ ) if summary_elem is not None and summary_elem.text else "No summary available"
exports = exports_elem.text.strip(
) if exports_elem is not None and exports_elem.text else ""
-
+ imports = imports_elem.text.strip(
+ ) if imports_elem is not None and imports_elem.text else "" # Added imports
print_success(f"Processed: {filename}")
- return filename, file_type, description, exports
+ # Added imports to return tuple
+ return filename, file_type, summary, exports, imports
except Exception as e:
print_error(f"Error processing {filename}: {e}")
- return filename, "unknown", f"Error: {e}", ""
+ # Added empty string for imports in error case
+ return filename, "unknown", f"Error: {e}", "", ""
async def process_files(files, project_context, folder_structure):
diff --git a/src/drd/metadata/updater.py b/src/drd/metadata/updater.py
index 4244d5f..0099200 100644
--- a/src/drd/metadata/updater.py
+++ b/src/drd/metadata/updater.py
@@ -70,26 +70,21 @@ async def update_metadata_with_dravid_async(meta_description, current_dir):
)
print_success(
f"Updated metadata for file: {found_filename}")
+
+ # Handle external dependencies
+ metadata = file.find('metadata')
+ if metadata is not None:
+ external_deps = metadata.find('external_dependencies')
+ if external_deps is not None:
+ for dep in external_deps.findall('dependency'):
+ metadata_manager.add_external_dependency(
+ dep.text.strip())
else:
print_warning(f"Could not analyze file: {found_filename}")
except Exception as e:
print_error(f"Error processing {found_filename}: {str(e)}")
- # After processing all files, update the environment info
- all_languages = set(file['type'] for file in metadata_manager.metadata['key_files']
- if file['type'] not in ['binary', 'unknown'])
- if all_languages:
- primary_language = max(all_languages, key=lambda x: sum(
- 1 for file in metadata_manager.metadata['key_files'] if file['type'] == x))
- other_languages = list(all_languages - {primary_language})
- metadata_manager.update_environment_info(
- primary_language=primary_language,
- other_languages=other_languages,
- primary_framework=metadata_manager.metadata['environment']['primary_framework'],
- runtime_version=metadata_manager.metadata['environment']['runtime_version']
- )
-
print_success("Metadata update completed.")
except Exception as e:
print_error(f"Error parsing dravid's response: {str(e)}")
diff --git a/src/drd/prompts/file_metada_desc_prompts.py b/src/drd/prompts/file_metada_desc_prompts.py
index dfecddf..e9fa938 100644
--- a/src/drd/prompts/file_metada_desc_prompts.py
+++ b/src/drd/prompts/file_metada_desc_prompts.py
@@ -11,23 +11,32 @@ def get_file_metadata_prompt(filename, content, project_context, folder_structur
so it can be used by an AI coding assistant in future for reference.
Based on the file content, project context, and the current folder structure,
-please generate appropriate metadata for this file.
+please generate appropriate metadata for this file
-If this file appears to be a dependency management file (like package.json, requirements.txt, Cargo.toml, etc.),
-provide a list of external dependencies.
+Guidelines:
+1. 'path' should be the full path of the file within the project.
+2. 'type' should be the programming language or file type (e.g., "typescript", "python", "json").
+3. 'summary' should be a concise description of the file's main purpose.
+4. 'exports' should list the exported items with their types (fun: for functions, class: for classes, var: for variables etc).
+5. 'imports' should list imports from other project files, including the path and imported item.
+6. 'external_dependencies' should list external dependencies for dependency management files if the current file appears to
+be deps management file (package.json, requirements.txt, Cargo.toml etc).
+7. If there are no exports, imports, or external dependencies, use an empty array [].
+8. Ensure all fields are present in the JSON object.
+9. If there are no exports, use None instead of an empty tag.
+10. If there are no imports, use None instead of an empty tag.
+11. If there are no external dependencies, omit the tag entirely.
+12. Ensure that all other tags (type, description, file_category, exports, imports) are always present and non-empty.
-If it's a code file, provide a summary, list of exports (functions, classes, or variables available for importing),
-and a list of imports from other project files.
Respond with an XML structure containing the metadata:
file_type
- Description based on the file's contents, project context, and folder structure
- code_file or dependency_file
+ summary based on the file's contents, project context, and folder structure
fun:functionName,class:ClassName,var:variableName
- path/to/file:importedName
+ path/to/file
name1@version1
@@ -36,9 +45,31 @@ def get_file_metadata_prompt(filename, content, project_context, folder_structur
+examples:
+
+
+ src/components/Layout.tsx
+ typescript
+ Main layout component
+ fun:Layout
+ src/components/Footer
+
+
+
+
+
+ package.json
+ json
+ Node.js project configuration and dependencies
+ None
+ None
+
+ react@18.2.0
+ next@13.4.1
+ typescript@5.0.4
+
+
+
+
Respond strictly only with the XML response as it will be used for parsing, no other extra words.
-If there are no exports, use None instead of an empty tag.
-If there are no imports, use None instead of an empty tag.
-If there are no external dependencies, omit the tag entirely.
-Ensure that all other tags (type, description, file_category, exports, imports) are always present and non-empty.
"""
diff --git a/tests/cli/query/test_dynamic_command_handler.py b/tests/cli/query/test_dynamic_command_handler.py
index fe31c6a..8272b86 100644
--- a/tests/cli/query/test_dynamic_command_handler.py
+++ b/tests/cli/query/test_dynamic_command_handler.py
@@ -1,3 +1,4 @@
+import asyncio
import unittest
from unittest.mock import patch, MagicMock, call, mock_open
import xml.etree.ElementTree as ET
@@ -12,6 +13,7 @@
)
+# Change back to unittest.TestCase
class TestDynamicCommandHandler(unittest.TestCase):
def setUp(self):
@@ -78,19 +80,25 @@ def test_handle_file_operation(self, mock_update_metadata, mock_print_success, m
# cmd, self.metadata_manager, self.executor)
@patch('drd.cli.query.dynamic_command_handler.generate_file_description')
- def test_update_file_metadata(self, mock_generate_description):
+ async def test_update_file_metadata(self, mock_generate_description):
cmd = {'filename': 'test.txt', 'content': 'Test content'}
- mock_generate_description.return_value = (
- 'python', 'Test file', ['test_function'])
-
- update_file_metadata(cmd, self.metadata_manager, self.executor)
-
- self.metadata_manager.get_project_context.assert_called_once()
- self.executor.get_folder_structure.assert_called_once()
- mock_generate_description.assert_called_once_with(
- 'test.txt', 'Test content', self.metadata_manager.get_project_context(), self.executor.get_folder_structure())
+ mock_file_info = {
+ 'path': 'test.txt',
+ 'type': 'python',
+ 'summary': 'Test file',
+ 'exports': ['test_function'],
+ 'imports': [],
+ 'xml_response': ''
+ }
+ self.metadata_manager.analyze_file.return_value = mock_file_info
+
+ await update_file_metadata(cmd, self.metadata_manager, self.executor)
+
+ self.metadata_manager.analyze_file.assert_called_once_with('test.txt')
self.metadata_manager.update_file_metadata.assert_called_once_with(
- 'test.txt', 'python', 'Test content', 'Test file', ['test_function'])
+ 'test.txt', 'python', 'Test content', 'Test file', [
+ 'test_function'], []
+ )
@patch('drd.cli.query.dynamic_command_handler.print_error')
@patch('drd.cli.query.dynamic_command_handler.print_info')
diff --git a/tests/metadata/test_project_metadata.py b/tests/metadata/test_project_metadata.py
index 64022eb..fedbe19 100644
--- a/tests/metadata/test_project_metadata.py
+++ b/tests/metadata/test_project_metadata.py
@@ -77,7 +77,7 @@ async def test_analyze_file(self, mock_file, mock_api_call):
python
- A simple Python script
+ A simple Python script
None
None
diff --git a/tests/metadata/test_rate_limit_handler.py b/tests/metadata/test_rate_limit_handler.py
index ce69c87..6dfadb1 100644
--- a/tests/metadata/test_rate_limit_handler.py
+++ b/tests/metadata/test_rate_limit_handler.py
@@ -50,14 +50,14 @@ async def test_rate_limiter(self):
@patch('drd.metadata.rate_limit_handler.call_dravid_api_with_pagination')
@patch('drd.metadata.rate_limit_handler.extract_and_parse_xml')
async def test_process_single_file(self, mock_extract_xml, mock_call_api):
- mock_call_api.return_value = "pythonA test filetest_function"
+ mock_call_api.return_value = "pythonA test filetest_functionos,sys"
mock_root = ET.fromstring(mock_call_api.return_value)
mock_extract_xml.return_value = mock_root
result = await process_single_file("test.py", "print('Hello')", "Test project", {"test.py": "file"})
self.assertEqual(result, ("test.py", "python",
- "A test file", "test_function"))
+ "A test file", "test_function", "os,sys"))
mock_call_api.assert_called_once()
mock_extract_xml.assert_called_once_with(mock_call_api.return_value)
diff --git a/tests/metadata/test_updater.py b/tests/metadata/test_updater.py
index a30752e..05775ba 100644
--- a/tests/metadata/test_updater.py
+++ b/tests/metadata/test_updater.py
@@ -1,6 +1,7 @@
import unittest
from unittest.mock import patch, MagicMock, mock_open
import xml.etree.ElementTree as ET
+import asyncio
from drd.metadata.updater import update_metadata_with_dravid
@@ -52,7 +53,7 @@ def test_update_metadata_with_dravid(self, mock_print_error, mock_print_warning,
update
python
- Main Python file
+ Main Python file
main_function
os
@@ -69,7 +70,7 @@ def test_update_metadata_with_dravid(self, mock_print_error, mock_print_warning,
update
json
- Package configuration file
+ Package configuration file
None
None
@@ -87,19 +88,30 @@ def test_update_metadata_with_dravid(self, mock_print_error, mock_print_warning,
mock_find_file.side_effect = [
'/fake/project/dir/src/main.py', '/fake/project/dir/package.json']
- # Mock file contents
- mock_file_contents = {
- '/fake/project/dir/src/main.py': "print('Hello, World!')",
- '/fake/project/dir/package.json': '{"name": "test-project"}'
- }
-
- def mock_open_file(filename, *args, **kwargs):
- return mock_open(read_data=mock_file_contents.get(filename, ""))()
-
- with patch('builtins.open', mock_open_file):
- # Call the function
- update_metadata_with_dravid(
- self.meta_description, self.current_dir)
+ # Mock analyze_file method
+ async def mock_analyze_file(filename):
+ if filename == '/fake/project/dir/src/main.py':
+ return {
+ 'path': '/fake/project/dir/src/main.py',
+ 'type': 'python',
+ 'summary': "print('Hello, World!')",
+ 'exports': ['main_function'],
+ 'imports': ['os']
+ }
+ elif filename == '/fake/project/dir/package.json':
+ return {
+ 'path': '/fake/project/dir/package.json',
+ 'type': 'json',
+ 'summary': '{"name": "test-project"}',
+ 'exports': [],
+ 'imports': []
+ }
+ return None
+
+ mock_metadata_manager.return_value.analyze_file = mock_analyze_file
+
+ # Call the function
+ update_metadata_with_dravid(self.meta_description, self.current_dir)
# Assertions
mock_metadata_manager.assert_called_once_with(self.current_dir)
@@ -110,11 +122,11 @@ def mock_open_file(filename, *args, **kwargs):
# Check if metadata was correctly updated and removed
mock_metadata_manager.return_value.update_file_metadata.assert_any_call(
- '/fake/project/dir/src/main.py', 'python', "print('Hello, World!')", 'Main Python file', [
+ '/fake/project/dir/src/main.py', 'python', "print('Hello, World!')", [
'main_function'], ['os']
)
mock_metadata_manager.return_value.update_file_metadata.assert_any_call(
- '/fake/project/dir/package.json', 'json', '{"name": "test-project"}', 'Package configuration file', [
+ '/fake/project/dir/package.json', 'json', '{"name": "test-project"}', [
], []
)
mock_metadata_manager.return_value.remove_file_metadata.assert_called_once_with(