diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..b7b3cf5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,95 @@ +# nIA's System Prompt & Agent Instructions + +This document outlines the core instructions, guiding principles, and capabilities of nIA, an AI software engineer. It's intended to help developers understand how nIA works and how to interact with it effectively. + +## Core Identity + +You are nIA, an extremely skilled software engineer. Your purpose is to assist users by completing coding tasks, such as solving bugs, implementing features, and writing tests. You will also answer user questions related to the codebase and your work. You are resourceful and will use the tools at your disposal to accomplish your goals. + +## Guiding Principles + +- **Plan First:** Always start by exploring the codebase, understanding the requirements, and creating a solid, step-by-step plan using the `set_plan` tool. Ask clarifying questions to ensure you have all the necessary information. +- **Verify Your Work:** After every action that modifies the codebase (e.g., creating, editing, or deleting a file), use a read-only tool (like `read_file` or `ls`) to confirm the action was successful and had the intended effect. +- **Edit Source, Not Artifacts:** If you identify a build artifact, trace it back to its source file and make your changes there. Never edit generated files directly. +- **Practice Proactive Testing:** Run relevant tests after any code change to ensure correctness and prevent regressions. When practical, write a failing test first (Test-Driven Development). +- **Diagnose Before Changing Environment:** If you encounter a build or test failure, diagnose the root cause by reading logs and inspecting configuration files before attempting to install or uninstall packages. +- **Be Autonomous, But Ask for Help When Stuck:** Solve problems independently whenever possible. However, ask for user input if the request is ambiguous, you're stuck after multiple attempts, or a decision would significantly alter the scope of the task. + +## Specific Instructions + +### Git Merge Diffs + +When using tools that require a diff in the Git Merge diff format, the conflict markers (`<<<<<<< SEARCH`, `=======`, `>>>>>>> REPLACE`) must be exact and on their own lines. + +### Planning + +- Use the `set_plan` tool to create or modify your plan. +- The plan should be a numbered list in Markdown format. +- Always include a pre-commit step to ensure proper testing, verification, review, and reflection are done by calling the `pre_commit_instructions` tool. + +### Bash: Long-Running Processes + +- Run long-running processes (like servers) in the background using `&`. Redirect output to a file for later inspection (e.g., `npm start > npm.log 2>&1 &`). +- Avoid "port already in use" errors by killing existing processes on a port before restarting a server (e.g., `kill $(lsof -t -i :3000) 2>/dev/null || true`). + +### AGENTS.md + +- Repositories may contain `AGENTS.md` files with instructions or tips for you. +- You must obey the instructions in any `AGENTS.md` file that applies to a file you are modifying. +- More deeply-nested `AGENTS.md` files take precedence over those in parent directories. +- The user's initial problem description and direct instructions always take precedence over `AGENTS.md`. + +## nIA's Skills (Tools) + +Here is a comprehensive list of the tools nIA can use to accomplish tasks. + +### File System Operations + +- `list_files(path: str)`: Lists all files and directories under a given path. +- `read_file(filepath: str)`: Reads the content of a specified file. +- `create_file_with_block(filepath: str, content: str)`: Creates a new file with the provided content. +- `overwrite_file_with_block(filepath: str, content: str)`: Completely overwrites an existing file with new content. +- `replace_with_git_merge_diff(filepath: str, merge_diff: str)`: Performs a targeted search-and-replace using a Git merge diff. +- `delete_file(filepath: str)`: Deletes a specified file. +- `rename_file(filepath: str, new_filepath: str)`: Renames or moves a file or directory. +- `restore_file(filepath: str)`: Restores a specific file to its original state from the last commit. +- `reset_all()`: Resets the entire codebase to its original state, undoing all changes. + +### Execution & Environment + +- `run_in_bash_session(command: str)`: Runs a bash command in the sandbox from the repository root. +- `pre_commit_instructions()`: Retrieves the required pre-commit checks and instructions. + +### Planning & User Interaction + +- `set_plan(plan: str)`: Sets or updates the step-by-step plan for the task. +- `plan_step_complete(message: str)`: Marks the current plan step as complete. +- `message_user(message: str, continue_working: bool)`: Sends a message to the user. +- `request_user_input(message: str)`: Asks the user a question and waits for a response. +- `record_user_approval_for_plan()`: Records the user's approval for the plan. + +### Version Control & Submission + +- `submit(branch_name: str, commit_message: str, title: str, description: str)`: Commits the current changes and prepares them for submission. + +### Web & Image Tools + +- `google_search(query: str)`: Performs a Google search to find information online. +- `view_text_website(url: str)`: Fetches the content of a website as plain text. +- `view_image(url: str)`: Loads and displays an image from a URL. +- `read_image_file(filepath: str)`: Reads an image file from the local file system. + +### Frontend Verification + +- `frontend_verification_instructions()`: Gets instructions on how to write a Playwright script for frontend verification. +- `frontend_verification_complete(screenshot_path: str)`: Indicates that frontend changes have been verified, providing a screenshot path. + +### Code Review + +- `read_pr_comments()`: Reads pending pull request comments. +- `reply_to_pr_comments(replies: str)`: Replies to pull request comments. +- `request_code_review()`: Requests a code review for the current changes. + +### Memory + +- `initiate_memory_recording()`: Starts recording information that may be useful for future tasks. diff --git a/FULL_AUDIT.md b/FULL_AUDIT.md new file mode 100644 index 0000000..3c66b41 --- /dev/null +++ b/FULL_AUDIT.md @@ -0,0 +1,73 @@ +# Full Audit of the RuneScript Repository + +## 1. Introduction + +This document provides a comprehensive audit of the RuneScript repository. RuneScript is a Python-based Integrated Development Environment (IDE) with a focus on script editing, AI-enhanced productivity, and Git integration. The analysis covers the repository's architecture, codebase structure, strengths, and weaknesses, and concludes with actionable recommendations for improvement. This audit was performed on the `redgreenrefactor` branch. + +## 2. Architecture Overview + +The application follows a relatively modern and modular architecture, separating concerns into distinct components. The main components, as observed from the `src` directory, are: + +- **UI (`src/ui`)**: Manages the user interface, built with Tkinter. The `UIManager` acts as the central point for UI operations. +- **Controller (`src/ide`)**: The `IDEController` is the core of the application, orchestrating interactions between the UI, project management, and AI components. It serves as the central nervous system of the IDE. +- **Core Logic (`src/core`)**: Contains the `ProjectLifecycleManager`, which handles the business logic for creating and managing projects. +- **AI Integration (`src/ai`)**: The `AIAgentOrchestrator` manages the AI capabilities, including interactions with local and remote language models. +- **Models (`src/models`)**: Likely contains data models or configurations for AI models. +- **Views (`src/views`)**: Appears to contain UI view definitions, separating them from the main UI logic. + +This separation of concerns is a good practice, making the codebase easier to understand, maintain, and extend. + +## 3. Codebase Structure + +The repository is well-organized at the top level: + +- `data/`: For storing application data, including user projects. +- `docs/`: Contains documentation for the project. +- `icons/`, `images/`: Store visual assets for the UI. +- `lib/`, `tools/`: For third-party libraries and utility scripts. +- `src/`: The main application source code. +- `README.md`: A comprehensive and well-written introduction to the project. +- `requirements.txt`: Lists the Python dependencies. + +## 4. Strengths + +- **Clear Vision**: The `README.md` clearly articulates the project's ambitious goals, features, and roadmap. +- **Modular Architecture**: The separation of UI, controller, and core logic is a significant strength that will aid future development. +- **AI Integration**: The project has a forward-looking approach by integrating AI capabilities at its core with an orchestrator pattern. +- **Comprehensive README**: The documentation for getting started is excellent and welcoming to new contributors. + +## 5. Weaknesses and Recommendations + +### 5.1. Project Management (Supervision) + +The user correctly identified that the project management section is "not well supervised." The current implementation is very basic and lacks key features for a good user experience. + +- **Weakness**: In `IDEController.py`, new projects are created with a UUID as the folder name (`project_id = str(uuid.uuid4())`). Users cannot name their projects, making them difficult to identify and manage. +- **Weakness**: There is no concept of project metadata. The IDE only recognizes a folder as a project. Important information like the project name, description, type, or associated scripts is not stored. +- **Weakness**: The "Open Project" functionality is just a generic "open directory" dialog. It doesn't filter for valid project folders or provide a list of recent projects. + +- **Recommendation 1: Introduce Project Configuration File**: Create a metadata file (e.g., `runescript.json` or `.project`) in the root of each project directory. This file would store the project's name, creation date, and other relevant settings. +- **Recommendation 2: Enhance Project Creation Dialog**: Modify the `new_project` flow to prompt the user for a project name and location. The IDE would then create the directory and the project configuration file. +- **Recommendation 3: Improve Project Opening/Switching**: Create a dedicated project browser or a "Recent Projects" list on the welcome screen. This would allow users to easily find and switch between their projects instead of navigating the file system manually. + +### 5.2. Testing + +- **Weakness**: A search of the repository reveals no dedicated test files or testing framework configuration (like `pytest` or `unittest`). The lack of an automated test suite is a critical vulnerability for a project of this complexity. It makes refactoring risky and verifying new features difficult. + +- **Recommendation 1: Establish a Testing Framework**: Integrate `pytest` as the testing framework. It's a popular, powerful, and easy-to-use choice for Python projects. +- **Recommendation 2: Add Unit Tests**: Start by adding unit tests for the core logic, such as the `ProjectLifecycleManager`. Mock dependencies like the UI to test the logic in isolation. +- **Recommendation 3: Add Integration Tests**: Create tests that verify the interaction between different components, such as the `IDEController` and the `UIManager`. +- **Recommendation 4: Adopt Test-Driven Development (TDD)**: As the IDE is named "Red-Green-Refactor IDE," embracing TDD as a development practice would align with the project's philosophy and significantly improve code quality. + +### 5.3. Dependency Management + +- **Weakness**: The `requirements.txt` file is a single, long list of dependencies. This can lead to version conflicts and makes it difficult to distinguish between core dependencies and development/testing dependencies. + +- **Recommendation 1: Use a Modern Dependency Manager**: Adopt a tool like [Poetry](https://python-poetry.org/) or [PDM](https://pdm.fming.dev/) to manage dependencies, virtual environments, and packaging. This would provide lock files for reproducible builds and better separation of dependencies. +- **Recommendation 2: Split Requirements**: If a full switch is not feasible, split the dependencies into logical files: `requirements.txt` for core dependencies and `requirements-dev.txt` for development tools (e.g., linters, testing frameworks). + +## 6. Conclusion + +RuneScript is a promising project with a strong vision and a solid architectural foundation. Its primary weaknesses lie in areas that are crucial for long-term maintainability and scalability: project management, automated testing, and dependency management. + +By addressing the recommendations in this audit, particularly by improving the project supervision features and introducing a robust testing culture, RuneScript can evolve into a powerful and reliable IDE for developers and creative professionals. diff --git a/src/models/tdd_workflow_manager.py b/src/models/tdd_workflow_manager.py index 6d2717d..0de90eb 100644 --- a/src/models/tdd_workflow_manager.py +++ b/src/models/tdd_workflow_manager.py @@ -1,108 +1,200 @@ -from src.views.tk_utils import PHASE_UI_LABELS, UI_TO_INTERNAL_PHASE - +from src.agents.test_generation_agent import TestGenerationAgent +from src.utils.test_runner import run_pytest +import os +import re class TDDWorkflowManager: """ - Manages the TDD workflow state and transitions between red-green-refactor phases + Manages the intelligent, AI-assisted TDD workflow, orchestrating + the UI, AI agents, and test runner. """ - def __init__(self, project_io, ui_manager): - self.phases = ["RED", "GREEN", "REFACTOR"] - self.current_phase = "RED" - self.project_io = project_io - self.ui_manager = ui_manager - self.test_status = None - - self.last_test_output = None # 🆕 Store last test result here - - def start_new_cycle(self): - """Start a new TDD cycle""" - self.transition_to_phase("RED") - - def transition_to_phase(self, phase: str): - phase = UI_TO_INTERNAL_PHASE.get(phase, phase) - if phase not in self.phases: - raise ValueError(f"Invalid phase: {phase}") - self.current_phase = phase - ui_label = PHASE_UI_LABELS.get(phase, phase) - self.ui_manager.update_phase_ui(ui_label, self.test_status) - - def rerun_last_test(self): - """Re-run the previously executed test, if available.""" - if not self.last_test_output: - self.ui_manager.show_message("Nothing to Re-run", "No test has been run yet.") + def __init__(self, ui_panel, project_path): + self.ui = ui_panel + self.project_path = project_path + self.test_generation_agent = TestGenerationAgent(self) # Simplified for now + + # In-memory storage for generated files until commit + self.temp_test_code = None + self.temp_impl_code = None + self.temp_impl_file_path = os.path.join(self.project_path, "temp_implementation.py") + self.test_file_path = os.path.join(self.project_path, "temp_test_suite.py") + self._after_id = None + + def start_red_phase(self, user_intent: str): + """ + Kicks off the TDD cycle by generating a failing test from user intent. + """ + if not user_intent: + print("User intent cannot be empty.") + return + + self.temp_test_code = self._mock_generate_test(user_intent) + self.ui.test_code_text.config(state="normal") + self.ui.test_code_text.delete("1.0", "end") + self.ui.test_code_text.insert("1.0", self.temp_test_code) + self.ui.test_code_text.config(state="disabled") + + with open(self.test_file_path, "w") as f: + f.write(self.temp_test_code) + + test_result = run_pytest(self.project_path) + output = test_result.stdout + "\n" + test_result.stderr + + if not test_result.success: + self.ui.set_state("RED", test_output=output) + # Start the live feedback loop + self.start_live_feedback_loop() + else: + self.ui.set_state("GREEN", test_output="Warning: Generated test passed immediately.\n" + output) + + def start_live_feedback_loop(self): + """Starts the timer to continuously check the implementation code.""" + # Cancel any existing loop + if self._after_id: + self.ui.after_cancel(self._after_id) + + self.check_implementation() + + def check_implementation(self): + """Periodically checks the implementation code against the test.""" + current_impl_code = self.ui.impl_code_text.get("1.0", "end-1c") + + # Only re-run if the code has changed + if current_impl_code != self.temp_impl_code: + self.temp_impl_code = current_impl_code + + # Write the implementation code to a temporary file + # This is a simplified approach. A real IDE would handle this more robustly. + with open(self.temp_impl_file_path, "w") as f: + # We need to prepend the implementation to the test file for the test to see it. + # This is a hacky way to ensure the test can import the code. + f.write(self.temp_impl_code) + + # Now, run the test + test_result = run_pytest(self.project_path) + + if test_result.success: + output = test_result.stdout + "\n" + test_result.stderr + self.ui.set_state("GREEN", test_output="Tests passed!\n" + output) + # Stop the loop + if self._after_id: + self.ui.after_cancel(self._after_id) + self._after_id = None + return # Stop checking + + # Schedule the next check + self._after_id = self.ui.after(2000, self.check_implementation) # Check every 2 seconds + + def start_refactor_phase(self): + """ + Initiates the refactoring process on the current 'green' code. + """ + if not self.temp_impl_code: + print("No implementation code to refactor.") return - success, stdout, stderr = self.project_io.run_tests() - self.last_test_output = (success, stdout, stderr) - self.ui_manager.update_test_results(success, stdout, stderr) - self.ui_manager.tdd_panel.update_last_result_label(success) - self.ui_manager.tdd_panel.set_rerun_enabled(True, "Passed" if success else "Failed") - - def run_tests(self): - """Run tests and determine next phase based on results""" - success, stdout, stderr = self.project_io.run_tests() - - # Save last output for re-runs - self.last_test_output = (success, stdout, stderr) - - # Show results in UI - self.ui_manager.update_test_results(success, stdout, stderr) - self.ui_manager.tdd_panel.update_last_result_label(success) - - # Always allow rerun after running tests - if hasattr(self.ui_manager, "tdd_panel"): - self.ui_manager.tdd_panel.set_rerun_enabled(True) - self.ui_manager.tdd_panel.update_last_test_summary("passed" if success else "failed") - - if success: - self.test_status = "passed" - # - if self.current_phase == "RED": - self.ui_manager.show_message( - "Test Already Passes", - "Your test already passes! Write a failing test first." - ) - else: - self.transition_to_phase("REFACTOR") + # 1. Update UI state + self.ui.set_state("REFACTORING") + + # 2. Get refactoring suggestions from the AI agent (mocked for now) + suggestions_diff = self._mock_refactor_agent(self.temp_impl_code) + + # 3. Display the suggestions in the UI + # (The UI panel will need a method to handle this) + self.ui.display_refactor_suggestions(suggestions_diff) + + def apply_refactoring(self, new_code: str): + """ + Applies a refactoring suggestion and runs the safety net check. + """ + original_code = self.temp_impl_code + self.temp_impl_code = new_code + + # Apply the change to the UI and the temp file + self.ui.impl_code_text.delete("1.0", "end") + self.ui.impl_code_text.insert("1.0", new_code) + with open(self.temp_impl_file_path, "w") as f: + f.write(new_code) + + # --- THE SAFETY NET --- + test_result = run_pytest(self.project_path) + + if test_result.success: + # The refactoring is safe! Keep the code. + self.ui.set_state("GREEN", test_output="Refactor successful! Tests are still passing.") + else: + # The refactoring broke the code! Revert it. + self.temp_impl_code = original_code + self.ui.impl_code_text.delete("1.0", "end") + self.ui.impl_code_text.insert("1.0", original_code) + with open(self.temp_impl_file_path, "w") as f: + f.write(original_code) + + output = "Refactor failed and was reverted. Tests are no longer passing.\n\n" + test_result.stderr + self.ui.set_state("RED", test_output=output) + + + def _mock_generate_test(self, intent: str) -> str: + """ + A mock function to simulate the TestGenerationAgent. + In the real implementation, this will call the actual agent. + """ + # A simple heuristic to generate a plausible-looking test + function_name_match = [word for word in intent.split() if re.match(r'\w+\(\)', word)] + if function_name_match: + function_name = function_name_match[0].replace('()', '') else: - self.test_status = "failed" - if self.current_phase == "RED": - self.transition_to_phase("GREEN") - else: - self.ui_manager.show_message( - "Tests Failed", - "Your tests are failing. Fix your implementation." - ) - - return success - - def write_test(self): - """Handle actions for the RED phase (writing tests)""" - # If we're not already in RED phase, transition - if self.current_phase != "RED": - self.transition_to_phase("RED") - - # Focus on test editor - self.ui_manager.focus_test_editor() - - def implement_code(self): - """Handle actions for the GREEN phase (implementing code)""" - # Focus on implementation editor - self.ui_manager.focus_implementation_editor() - - def refactor_code(self): - """Handle actions for the REFACTOR phase""" - # No special action needed, just update phase - if self.current_phase != "REFACTOR": - self.transition_to_phase("REFACTOR") - - # We still need passing tests to be in refactor phase - if self.test_status != "passed": - self.ui_manager.show_message( - "Cannot Refactor", - "Tests must be passing before refactoring. Fix your implementation." - ) - return False - - return True \ No newline at end of file + # Fallback for simple intents + function_name = intent.split(" ")[-1] + + test_function_name = "test_" + function_name + class_name = "Test" + function_name.capitalize() + + + return f\"\"\" +import unittest +from temp_implementation import * + +# Test generated from intent: '{intent}' + +class {class_name}(unittest.TestCase): + def {function_name}(self): + # This test will fail until the feature is implemented + self.fail("🔴 RED: Test not implemented yet.") + +if __name__ == '__main__': + unittest.main() +\"\"\" + + def _mock_refactor_agent(self, code: str) -> str: + """ + A mock function to simulate the RefactoringAgent. + Returns a git-style diff. + """ + # Simple suggestion: add a docstring and type hints + original_lines = code.split('\n') + + # Assume the function def is the first line + if not original_lines or not original_lines[0].strip().startswith("def"): + return "# No suggestion available" + + func_definition = original_lines[0] + # A crude way to add type hints for this example + refactored_func = func_definition.replace("(", "(a: int, b: int) -> int", 1) + + # Build the refactored code with a docstring + refactored_lines = [ + refactored_func, + ' """This function was refactored to include a docstring and type hints."""', + ] + original_lines[1:] + + refactored_code = "\n".join(refactored_lines) + + # Create a basic diff for demonstration + import difflib + diff = difflib.unified_diff( + original_lines, refactored_lines, + fromfile='original', tofile='refactored', lineterm='' + ) + return '\n'.join(diff) diff --git a/src/models/tdd_workflow_panel.py b/src/models/tdd_workflow_panel.py index b651dd2..7650ff6 100644 --- a/src/models/tdd_workflow_panel.py +++ b/src/models/tdd_workflow_panel.py @@ -1,139 +1,118 @@ -from tkinter import ttk import tkinter as tk -from typing import Optional - -from src.views.tk_utils import PHASE_UI_LABELS - +from tkinter import ttk class TDDWorkflowPanel(ttk.Frame): - """Panel specifically for managing the Red-Green-Refactor workflow""" + """ + An intelligent, guided TDD workbench to seamlessly guide the user + through the Red-Green-Refactor cycle with AI assistance. + """ - def __init__(self, parent, on_run_tests, on_refactor, on_write_test, on_rerun_last=None): + def __init__(self, parent): super().__init__(parent) - self.current_phase = tk.StringVar(value="Write Test") - self.create_widgets(on_run_tests, on_refactor, on_write_test, on_rerun_last) - - def create_widgets(self, on_run_tests, on_refactor, on_write_test, on_rerun_last): - # Phase indicators - phase_frame = ttk.LabelFrame(self, text="TDD Cycle") - phase_frame.pack(fill=tk.X, padx=5, pady=5) - - phases = ["Write Test", "Run Test", "Write Code", "Run Test Again", "Refactor"] - self.phase_indicators = {} - - for i, phase in enumerate(phases): - col = i * 2 - indicator = ttk.Label(phase_frame, text=phase, padding=5) - indicator.grid(row=0, column=col, padx=5) - self.phase_indicators[phase] = indicator - - if i < len(phases) - 1: - ttk.Label(phase_frame, text="→").grid(row=0, column=col + 1, padx=2) - - # Action buttons - action_frame = ttk.Frame(self) - action_frame.pack(fill=tk.X, padx=5, pady=5) - - self.test_btn = ttk.Button( - action_frame, - text="1. Write Test (Red)", - style="Red.TButton", - command=on_write_test - ) - self.test_btn.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) - - self.run_tests_btn = ttk.Button( - action_frame, - text="2. Run Tests", - command=on_run_tests - ) - self.run_tests_btn.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) - - self.refactor_btn = ttk.Button( - action_frame, - text="3. Refactor (Green → Clean)", - style="Green.TButton", - command=on_refactor - ) - self.refactor_btn.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) - - # 🔁 Re-run last test button (optional) - if on_rerun_last: - self.rerun_btn = ttk.Button( - action_frame, - text="🔁 Re-run Last Test", - command=on_rerun_last, - state=tk.DISABLED # Start disabled - ) - self.rerun_btn.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) - self.last_result_label = ttk.Label(action_frame, text="") # Empty until first test - self.last_result_label.pack(side=tk.LEFT, padx=5) - - self.last_result_label = ttk.Label( - action_frame, - text="🔁 Last: None", - foreground="gray" - ) - self.last_result_label.pack(side=tk.LEFT, padx=5) - - def update_last_test_summary(self, status: Optional[str]): - """Update the label showing last test result""" - if hasattr(self, 'last_result_label'): - if status == "passed": - self.last_result_label.config(text="✔️ Last: Passed", foreground="green") - elif status == "failed": - self.last_result_label.config(text="❌ Last: Failed", foreground="red") - else: - self.last_result_label.config(text="") - - def update_phase(self, phase_name: str, test_status: Optional[str] = None): - """ - Updates the current phase indicator and button states. - - Args: - phase_name (str): The UI-label of the phase (e.g., "Write Test", "Refactor") - test_status (Optional[str]): Test result status ("passed", "failed", None) - """ - assert phase_name in self.phase_indicators, f"[TDDWorkflowPanel] Unknown UI phase label: '{phase_name}'" - - for phase, indicator in self.phase_indicators.items(): - indicator.configure(background="", foreground="") - - self.phase_indicators[phase_name].configure(background="#4a6cd4", foreground="white") - - if phase_name == "Write Test": - self.test_btn.configure(state=tk.NORMAL) - self.run_tests_btn.configure(state=tk.DISABLED) - self.refactor_btn.configure(state=tk.DISABLED) - elif phase_name in ["Run Test", "Write Code", "Run Test Again"]: - self.test_btn.configure(state=tk.DISABLED) - self.run_tests_btn.configure(state=tk.NORMAL) - self.refactor_btn.configure(state=tk.DISABLED) - elif phase_name == "Refactor": - self.test_btn.configure(state=tk.DISABLED) - self.run_tests_btn.configure(state=tk.NORMAL) - self.refactor_btn.configure(state=tk.NORMAL) - - if test_status == "failed": - self.run_tests_btn.configure(style="Red.TButton") - elif test_status == "passed": - self.run_tests_btn.configure(style="Green.TButton") - elif test_status is None: - self.run_tests_btn.configure(style="TButton") - - def set_rerun_enabled(self, enabled: bool, result_label: Optional[str] = None): - if hasattr(self, 'rerun_btn'): - if result_label: - self.rerun_btn.config(state=tk.NORMAL if enabled else tk.DISABLED) - self.rerun_btn.config(text=f"🔁 Last: {result_label}") - - def update_last_result_label(self, passed: Optional[bool]): - """Show summary icon next to rerun button""" - if not hasattr(self, "last_result_label"): - return - if passed is True: - self.last_result_label.config(text="✔️ Last: Passed", foreground="green") - elif passed is False: - self.last_result_label.config(text="❌ Last: Failed", foreground="red") + self.create_widgets() + + def create_widgets(self): + """Creates and lays out the new three-section TDD Workbench UI.""" + self.grid_rowconfigure(1, weight=1) + self.grid_columnconfigure(0, weight=1) + + # --- Section 1: Goal & Status Header --- + header_frame = ttk.Frame(self, padding=(10, 5)) + header_frame.grid(row=0, column=0, sticky="ew") + header_frame.grid_columnconfigure(1, weight=1) + + goal_label = ttk.Label(header_frame, text="Your Goal:") + goal_label.grid(row=0, column=0, padx=(0, 5), sticky="w") + + self.user_intent_input = ttk.Entry(header_frame, font=("TkDefaultFont", 10)) + self.user_intent_input.grid(row=0, column=1, sticky="ew") + + # State Indicator Frame + self.state_indicator_frame = ttk.Frame(header_frame, relief="sunken", borderwidth=1, width=150) + self.state_indicator_frame.grid(row=0, column=2, padx=(10, 0), sticky="e") + self.state_indicator_label = ttk.Label(self.state_indicator_frame, text="AWAITING GOAL", font=("TkDefaultFont", 10, "bold"), padding=(10, 5)) + self.state_indicator_label.pack(expand=True, fill="both") + + self.primary_action_button = ttk.Button(header_frame, text="Generate Failing Test") + self.primary_action_button.grid(row=0, column=3, padx=(5, 0), sticky="e") + + + # --- Section 2: Code Panes (Split View) --- + code_panes = ttk.PanedWindow(self, orient=tk.HORIZONTAL) + code_panes.grid(row=1, column=0, sticky="nsew", padx=5, pady=5) + + # Left Pane: Test Code + test_code_frame = ttk.LabelFrame(code_panes, text="Test Code (Read-Only)") + self.test_code_text = tk.Text(test_code_frame, wrap="word", state="disabled", bg="#f0f0f0") + self.test_code_text.pack(expand=True, fill="both", padx=5, pady=5) + code_panes.add(test_code_frame, weight=1) + + # Right Pane: Implementation Code + impl_code_frame = ttk.LabelFrame(code_panes, text="Implementation Code") + self.impl_code_text = tk.Text(impl_code_frame, wrap="word") + self.impl_code_text.pack(expand=True, fill="both", padx=5, pady=5) + code_panes.add(impl_code_frame, weight=1) + + + # --- Section 3: Output & Suggestions Footer --- + footer_frame = ttk.LabelFrame(self, text="Output") + footer_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=(0, 5)) + footer_frame.grid_columnconfigure(0, weight=1) + + self.output_console_text = tk.Text(footer_frame, wrap="word", height=8, state="disabled", bg="#f0f0f0") + self.output_console_text.pack(expand=True, fill="both", padx=5, pady=5) + footer_frame.grid_columnconfigure(0, weight=1) # Ensure footer expands + + # --- nIA Suggestions Frame (initially hidden) --- + self.nia_suggestions_frame = ttk.LabelFrame(self, text="nIA's Refactoring Suggestions") + self.nia_suggestions_frame.grid_rowconfigure(0, weight=1) + self.nia_suggestions_frame.grid_columnconfigure(0, weight=1) + + self.suggestion_text = tk.Text(self.nia_suggestions_frame, wrap="none", height=10, state="disabled") + self.suggestion_text.grid(row=0, column=0, columnspan=2, sticky="nsew", padx=5, pady=5) + + self.accept_button = ttk.Button(self.nia_suggestions_frame, text="Accept") + self.accept_button.grid(row=1, column=0, padx=5, pady=5, sticky="e") + + self.decline_button = ttk.Button(self.nia_suggestions_frame, text="Decline") + self.decline_button.grid(row=1, column=1, padx=5, pady=5, sticky="w") + + self.set_state("AWAITING_GOAL") + + def set_state(self, state: str, test_output: str = ""): + """Updates the UI to reflect the current TDD state.""" + state_map = { + "AWAITING_GOAL": {"text": "AWAITING GOAL", "color": "#e0e0e0", "button_text": "Generate Failing Test"}, + "RED": {"text": "🔴 RED", "color": "#ffdddd", "button_text": "Implement Solution"}, + "GREEN": {"text": "🟢 GREEN", "color": "#ddffdd", "button_text": "Suggest Refactoring"}, + "REFACTORING": {"text": "🔵 REFACTORING", "color": "#ddddff", "button_text": "Apply Suggestion"}, + } + + config = state_map.get(state, state_map["AWAITING_GOAL"]) + + self.state_indicator_frame.config(style=f"{state}.TFrame") + self.state_indicator_label.config(text=config["text"]) + self.primary_action_button.config(text=config["button_text"]) + + # Show/hide suggestions panel + if state == "REFACTORING": + self.nia_suggestions_frame.grid(row=3, column=0, sticky="ew", padx=5, pady=5) else: - self.last_result_label.config(text="🔁 Last: None", foreground="gray") + self.nia_suggestions_frame.grid_remove() + + # Update style for the frame background color + style = ttk.Style() + style.configure(f"{state}.TFrame", background=config["color"]) + + # Update output console + self.output_console_text.config(state="normal") + self.output_console_text.delete("1.0", tk.END) + self.output_console_text.insert("1.0", test_output) + self.output_console_text.config(state="disabled") + + def display_refactor_suggestions(self, diff: str): + """Displays the diff in the suggestions text widget.""" + self.suggestion_text.config(state="normal") + self.suggestion_text.delete("1.0", tk.END) + self.suggestion_text.insert("1.0", diff) + self.suggestion_text.config(state="disabled") diff --git a/src/ui/UIManager.py b/src/ui/UIManager.py index 1172e56..e309440 100644 --- a/src/ui/UIManager.py +++ b/src/ui/UIManager.py @@ -10,7 +10,6 @@ from src.models.tdd_workflow_panel import TDDWorkflowPanel -from src.models.test_result_panel import TestResultPanel from src.models.tdd_workflow_manager import TDDWorkflowManager from src.utils.ProjectIO import ProjectIO @@ -68,23 +67,26 @@ def create_main_layout(self): on_stop=self.controller.project_manager.stop_project ) - # ✅ Add TDD Test Result Panel - self.test_result_panel = TestResultPanel(right_panel) - self.test_result_panel.pack(fill=tk.BOTH, expand=False) - - # ✅ Add TDD Workflow Manager - project_io = ProjectIO(log_function=self.log_output) - self.tdd_manager = TDDWorkflowManager(project_io, self) - - # ✅ Add TDD Workflow Panel - self.tdd_panel = TDDWorkflowPanel( - right_panel, - on_run_tests=self.tdd_manager.run_tests, - on_refactor=self.tdd_manager.refactor_code, - on_write_test=self.tdd_manager.write_test, - on_rerun_last=self.tdd_manager.rerun_last_test + # Add the new AI-Assisted TDD Workbench + # This single panel now contains the entire TDD UI. + self.tdd_panel = TDDWorkflowPanel(right_panel) + self.tdd_panel.pack(fill=tk.BOTH, expand=True) # Give it space + + # Initialize the new TDD Workflow Manager + # It needs a reference to the panel to update the UI. + self.tdd_manager = TDDWorkflowManager( + ui_panel=self.tdd_panel, + project_path=self.controller.projects_base_dir # A bit simplified, will be updated when a project is open + ) + + # Wire up the primary action button to the manager + self.tdd_panel.primary_action_button.config( + command=self.on_primary_tdd_action ) - self.tdd_panel.pack(fill=tk.X) + + # Wire up the suggestion buttons + self.tdd_panel.accept_button.config(command=self.on_accept_suggestion) + self.tdd_panel.decline_button.config(command=self.on_decline_suggestion) except Exception as e: self.controller.safe_ui_call( @@ -94,20 +96,34 @@ def create_main_layout(self): ) logging.critical(f"Layout creation failed: {e}") - def update_phase_ui(self, phase_name: str, test_status: Optional[str] = None) -> None: + def on_primary_tdd_action(self): """ - Update the visual indicator and button state for the current TDD phase. - - Args: - phase_name (str): The current phase name (e.g., "Write Test", "Run Test"). - test_status (Optional[str]): The result of the last test run, e.g. "passed" or "failed". + Handles the click of the main, context-aware TDD button. """ - if hasattr(self, 'tdd_panel'): - self.tdd_panel.update_phase(phase_name, test_status) - - def update_test_results(self, passed: bool, stdout: str, stderr: str) -> None: - if hasattr(self, 'test_result_panel'): - self.test_result_panel.update_test_results(passed, stdout, stderr) + current_state = self.tdd_panel.state_indicator_label.cget("text") + + if "AWAITING GOAL" in current_state: + user_intent = self.tdd_panel.user_intent_input.get() + self.tdd_manager.start_red_phase(user_intent) + elif "GREEN" in current_state: + self.tdd_manager.start_refactor_phase() + + def on_accept_suggestion(self): + """Handles accepting a refactoring suggestion.""" + # This is a simplified approach. A real implementation would + # parse the diff and apply it programmatically. + # For now, we'll get the refactored code from the mock agent. + refactored_code = self.tdd_manager._mock_refactor_agent( + self.tdd_manager.temp_impl_code + ) + # A crude way to extract the code from the diff + lines = refactored_code.split('\n') + code_lines = [line[1:] for line in lines if line.startswith('+') and not line.startswith('+++')] + self.tdd_manager.apply_refactoring("\n".join(code_lines)) + + def on_decline_suggestion(self): + """Handles declining a refactoring suggestion.""" + self.tdd_panel.set_state("GREEN", test_output="Refactoring suggestion declined.") def show_message(self, title: str, message: str) -> None: """