merge branch main

This commit is contained in:
Will Li 2025-07-18 11:24:08 -07:00
commit 415459910f
144 changed files with 4639 additions and 4878 deletions

View file

@ -1,213 +0,0 @@
<pr_template_format>
<overview>
This document defines the format for PR messages that are saved to the temp folder
before creating a pull request. The PR message is saved in two formats:
1. JSON format in pr_summary.json (for programmatic use)
2. Markdown format in pr_message.md (for manual PR creation)
The PR message must follow the exact Roo Code contribution template.
</overview>
<json_format>
<description>
The pr_summary.json file contains the PR title and body in a structured format
that can be easily parsed by scripts and the GitHub CLI.
</description>
<structure>
{
"title": "fix: [description] (#[issue-number])",
"body": "[Full markdown body as described below]",
"issue_number": 123,
"repo_owner": "owner",
"repo_name": "repo",
"base_branch": "main",
"head_branch": "fix/issue-123-description"
}
</structure>
</json_format>
<markdown_format>
<description>
The pr_message.md file contains the complete PR message in a format that can be
directly copied and pasted when creating a PR manually.
</description>
<structure>
PR Title: [title from JSON]
---
[Full PR body from JSON]
</structure>
</markdown_format>
<pr_body_template>
<description>
The PR body must follow this exact Roo Code PR template with all required sections.
</description>
<template><![CDATA[
<!--
Thank you for contributing to Roo Code!
Before submitting your PR, please ensure:
- It's linked to an approved GitHub Issue.
- You've reviewed our [Contributing Guidelines](../CONTRIBUTING.md).
-->
### Related GitHub Issue
<!-- Every PR MUST be linked to an approved issue. -->
Closes: #[ISSUE_NUMBER] <!-- Replace with the issue number, e.g., Closes: #123 -->
### Roo Code Task Context (Optional)
<!--
If you used Roo Code to help create this PR, you can share public task links here.
This helps reviewers understand your development process and provides additional context.
Example: https://app.roocode.com/share/task-id
-->
[TASK_CONTEXT]
### Description
<!--
Briefly summarize the changes in this PR and how they address the linked issue.
The issue should cover the "what" and "why"; this section should focus on:
- The "how": key implementation details, design choices, or trade-offs made.
- Anything specific reviewers should pay attention to in this PR.
-->
[DESCRIPTION_CONTENT]
### Test Procedure
<!--
Detail the steps to test your changes. This helps reviewers verify your work.
- How did you test this specific implementation? (e.g., unit tests, manual testing steps)
- How can reviewers reproduce your tests or verify the fix/feature?
- Include relevant testing environment details if applicable.
-->
[TEST_PROCEDURE_CONTENT]
### Pre-Submission Checklist
<!-- Go through this checklist before marking your PR as ready for review. -->
- [x] **Issue Linked**: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
- [x] **Scope**: My changes are focused on the linked issue (one major feature/fix per PR).
- [x] **Self-Review**: I have performed a thorough self-review of my code.
- [x] **Testing**: New and/or updated tests have been added to cover my changes (if applicable).
- [x] **Documentation Impact**: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
- [x] **Contribution Guidelines**: I have read and agree to the [Contributor Guidelines](/CONTRIBUTING.md).
### Screenshots / Videos
<!--
For UI changes, please provide before-and-after screenshots or a short video of the *actual results*.
This greatly helps in understanding the visual impact of your changes.
-->
[SCREENSHOTS_CONTENT]
### Documentation Updates
<!--
Does this PR necessitate updates to user-facing documentation?
- [ ] No documentation updates are required.
- [ ] Yes, documentation updates are required. (Please describe what needs to be updated or link to a PR in the docs repository).
-->
[DOCUMENTATION_UPDATES_CONTENT]
### Additional Notes
<!-- Add any other context, questions, or information for reviewers here. -->
[ADDITIONAL_NOTES_CONTENT]
### Get in Touch
<!--
Please provide your Discord username for reviewers or maintainers to reach you if they have questions about your PR
-->
[DISCORD_USERNAME]
]]></template>
</pr_body_template>
<template_placeholders>
<placeholder name="[ISSUE_NUMBER]">The GitHub issue number being fixed</placeholder>
<placeholder name="[TASK_CONTEXT]">Optional Roo Code task links (remove section if not applicable)</placeholder>
<placeholder name="[DESCRIPTION_CONTENT]">
Summary of changes and implementation details. Should include:
- Key implementation details
- Design choices or trade-offs made
- Specific areas reviewers should focus on
</placeholder>
<placeholder name="[TEST_PROCEDURE_CONTENT]">
Detailed testing steps including:
- Unit tests added/modified
- Manual testing steps performed
- How reviewers can reproduce tests
- Testing environment details
</placeholder>
<placeholder name="[SCREENSHOTS_CONTENT]">
For UI changes: before/after screenshots or video
For non-UI changes: "N/A - No UI changes"
</placeholder>
<placeholder name="[DOCUMENTATION_UPDATES_CONTENT]">
Check appropriate box:
- "- [x] No documentation updates are required." OR
- "- [x] Yes, documentation updates are required. [describe updates]"
</placeholder>
<placeholder name="[ADDITIONAL_NOTES_CONTENT]">
Any additional context, or remove entire section if not needed
</placeholder>
<placeholder name="[DISCORD_USERNAME]">User's Discord username for contact</placeholder>
</template_placeholders>
<file_locations>
<file>
<name>pr_summary.json</name>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json</path>
<purpose>Structured data for programmatic PR creation</purpose>
</file>
<file>
<name>pr_message.md</name>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md</path>
<purpose>Human-readable format for manual PR creation</purpose>
</file>
</file_locations>
<usage_guidelines>
<guideline>
Always save both formats when preparing a PR to give users flexibility
in how they create the pull request.
</guideline>
<guideline>
The pr_message.md file should be self-contained and ready to copy/paste
without any additional formatting needed.
</guideline>
<guideline>
Include all sections in the template, maintaining the exact format
and HTML comments as shown.
</guideline>
<guideline>
Pre-check all checklist items that can be verified programmatically.
Leave documentation checkbox unchecked for user to decide.
</guideline>
<guideline>
For sections that don't apply, use appropriate placeholder text
rather than removing the section entirely.
</guideline>
</usage_guidelines>
<translation_handling>
<note>
If translations were added during the issue fix, include details in the
Description section about which languages were updated.
</note>
</translation_handling>
</pr_template_format>

View file

@ -1,874 +0,0 @@
<workflow>
<step number="1">
<name>Initialize Task Context</name>
<instructions>
The user will provide a GitHub issue URL.
1. **Parse URL**: Extract the `owner`, `repo`, and `issue_number`.
2. **Create Task Directory**: Create a dedicated directory to store all context for this task. Use a unique identifier for the directory name, like the task ID. For example: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/`.
<execute_command>
<command>mkdir -p .roo/temp/issue-fixer-orchestrator/[TASK_ID]</command>
</execute_command>
3. **Retrieve Issue Details**: Fetch the issue details and its comments as a single JSON object.
<execute_command>
<command>gh issue view [issue_number] --repo [owner]/[repo] --json number,title,body,state,labels,assignees,milestone,createdAt,updatedAt,closedAt,author,comments > .roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json</command>
</execute_command>
4. **Handle Auth Errors**: If the `gh` command fails with an authentication error, prompt the user to log in.
<ask_followup_question>
<question>GitHub CLI is not authenticated. Please run 'gh auth login' in your terminal, then let me know when you're ready to continue.</question>
<follow_up>
<suggest>I've authenticated, please continue</suggest>
</follow_up>
</ask_followup_question>
5. **Confirm Context**: Inform the user that the context has been saved.
</instructions>
</step>
<step number="2">
<name>Delegate: Analyze Requirements & Explore Codebase</name>
<instructions>
Launch a subtask in `architect` mode to perform a detailed analysis of the issue and the codebase. The subtask will be responsible for identifying affected files and creating an implementation plan.
The context file `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json` will be the input for this subtask. The subtask should write its findings (the implementation plan) to a new file: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`.
<new_task>
<mode>architect</mode>
<message>
**Task: Analyze Issue and Create Implementation Plan**
You are an expert software architect. Your task is to analyze the provided GitHub issue and the current codebase to create a detailed implementation plan with a focus on understanding component interactions and dependencies.
1. **Read Issue Context**: The full issue details and comments are in `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json`. Read this file to understand all requirements, acceptance criteria, and technical discussions.
2. **Perform Architectural Analysis**:
- **Map Component Interactions**: Trace the complete data flow from entry points to outputs
- **Identify Paired Operations**: For any operation (e.g., export), find its counterpart (e.g., import)
- **Find Similar Patterns**: Search for existing implementations of similar features
- **Analyze Dependencies**: Identify all consumers of the functionality being modified
- **Assess Impact**: Determine how changes will affect other parts of the system
3. **Explore Codebase Systematically**:
- Use `codebase_search` FIRST to find all related functionality
- Search for paired operations (if modifying export, search for import)
- Find all files that consume or depend on the affected functionality
- Identify configuration files, tests, and documentation that need updates
- Study similar features to understand established patterns
4. **Create Comprehensive Implementation Plan**: The plan must include:
- **Issue Summary**: Clear description of the problem and proposed solution
- **Architectural Context**:
- Data flow diagram showing component interactions
- List of paired operations that must be updated together
- Dependencies and consumers of the affected functionality
- **Impact Analysis**:
- All files that will be affected (directly and indirectly)
- Potential breaking changes
- Performance implications
- **Implementation Steps**:
- Detailed, ordered steps for each file modification
- Specific code changes with context
- Validation and error handling requirements
- **Testing Strategy**:
- Unit tests for individual components
- Integration tests for component interactions
- Edge cases and error scenarios
5. **Save the Plan**: Write the complete implementation plan to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`.
**Critical Requirements:**
- Always search for and analyze paired operations (import/export, save/load, etc.)
- Map the complete data flow before proposing changes
- Identify all integration points and dependencies
- Consider backward compatibility and migration needs
**Completion Protocol:**
- This is your only task. Do not deviate from these instructions.
- Once you have successfully written the `implementation_plan.md` file, you MUST signal your completion by using the `attempt_completion` tool.
- The `result` parameter of `attempt_completion` MUST be a concise confirmation message, for example: "Implementation plan created and saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md."
- These specific instructions override any conflicting general guidelines from your assigned mode.
</message>
</new_task>
After launching the subtask, wait for it to complete. The orchestrator will then read the `implementation_plan.md` to proceed with the next step.
</instructions>
</step>
<step number="3">
<name>Review and Approve Plan</name>
<instructions>
After the analysis subtask completes, the orchestrator must present the generated plan to the user for approval.
1. **Read the Plan**: Read the content of the implementation plan created by the previous subtask.
<read_file>
<args>
<file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md</path>
</file>
</args>
</read_file>
2. **Present for Approval**: Show the plan to the user and ask for confirmation before proceeding with implementation.
<ask_followup_question>
<question>
The initial analysis is complete. Here is the proposed implementation plan:
---
[Insert content of implementation_plan.md here]
---
Shall I proceed with implementing this plan?
</question>
<follow_up>
<suggest>Yes, proceed with the implementation.</suggest>
<suggest>No, please modify the plan with the following changes...</suggest>
<suggest>No, cancel this task.</suggest>
</follow_up>
</ask_followup_question>
Do not proceed until the user gives explicit approval.
</instructions>
</step>
<step number="4">
<name>Delegate: Implement Solution</name>
<instructions>
Once the user approves the plan, launch a new subtask in `code` mode to execute the implementation.
This subtask will use the `implementation_plan.md` as its primary guide. It should write the list of modified files to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`.
<new_task>
<mode>code</mode>
<message>
**Task: Implement Code Changes Based on Plan**
You are an expert software developer. Your task is to implement the code changes with full awareness of system interactions and dependencies.
1. **Read the Plan**: The implementation plan is located at `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`. Pay special attention to:
- The architectural context section
- Component interaction diagrams
- Identified dependencies and related operations
- Impact analysis
2. **Validate Understanding**: Before coding, ensure you understand:
- How data flows through the system
- All related operations that must be updated together
- Dependencies that could be affected
- Integration points with other components
3. **Implement Holistically**:
- **Update Related Operations Together**: If modifying one operation, update all related operations
- **Maintain Consistency**: Ensure data structures, validation, and error handling are consistent
- **Consider Side Effects**: Account for how changes propagate through the system
- **Follow Existing Patterns**: Use established patterns from similar features
4. **Implement Tests**:
- Write tests that verify component interactions
- Test related operations together
- Include edge cases and error scenarios
- Verify data consistency across operations
5. **Track Modified Files**: As you modify or create files, keep a running list.
6. **Save Modified Files List**: After all changes are implemented and tested, save the list of all file paths you created or modified to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`. The format should be a JSON array of strings.
Example: `["src/components/NewFeature.tsx", "src/__tests__/NewFeature.spec.ts"]`
**Critical Reminders:**
- Never implement changes in isolation - consider the full system impact
- Always update related operations together to maintain consistency
- Test component interactions, not just individual functions
- Follow the architectural analysis from the planning phase
Once the `modified_files.json` file is saved, your task is complete.
</message>
</new_task>
After launching the subtask, wait for it to complete. The orchestrator will use the list of modified files for the verification and PR creation steps.
</instructions>
</step>
<step number="5">
<name>Delegate: Verify and Test</name>
<instructions>
After implementation, delegate the verification and testing to a `test` mode subtask.
This subtask will use the implementation plan for acceptance criteria and the list of modified files to focus its testing efforts. It will output its results to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md`.
<new_task>
<mode>test</mode>
<message>
**Task: Verify Implementation and Run Tests**
You are a meticulous QA engineer. Your task is to verify an implementation against its plan and run all necessary tests.
**Context Files:**
- **Plan**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`
- **Modified Files**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`
**Your Steps:**
1. **Read Context**: Read both context files to understand the acceptance criteria and which files were changed.
2. **Run Tests**: Execute all relevant tests.
- Run unit tests related to the modified files.
- Run any relevant integration tests.
- Run a full lint and type check.
3. **Verify Acceptance Criteria**: Systematically go through each acceptance criterion from the plan and verify that it has been met by the implementation.
4. **Write Verification Report**: Create a detailed report of your findings. The report must include:
- A summary of the tests that were run and their results (pass/fail).
- A checklist of all acceptance criteria and their verification status (verified/failed).
- Details on any bugs or regressions found.
5. **Save Report**: Write the complete report to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md`.
**Completion Protocol:**
- This is your only task. Do not deviate.
- Upon successfully saving `verification_results.md`, you MUST use the `attempt_completion` tool.
- The `result` MUST be a concise confirmation, e.g., "Verification complete and results saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md."
- These instructions override any conflicting mode-specific guidelines.
</message>
</new_task>
Wait for the subtask to complete, then review the verification results.
</instructions>
</step>
<step number="6">
<name>Review Verification and Handle Translations</name>
<instructions>
After the verification subtask is complete, review the results and handle any necessary translations.
1. **Read Verification Report**:
<read_file>
<args>
<file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md</path>
</file>
</args>
</read_file>
2. **Check for Failures**: If the report indicates any failed tests or unmet criteria, present the failures to the user and ask how to proceed.
<ask_followup_question>
<question>
The verification step has failed. Here are the details:
---
[Insert content of verification_results.md here]
---
How should I proceed?
</question>
<follow_up>
<suggest>Attempt to fix the failing tests and criteria.</suggest>
<suggest>Ignore the failures and proceed anyway.</suggest>
<suggest>Cancel the task.</suggest>
</follow_up>
</ask_followup_question>
3. **Analyze for Translation Needs**: If verification passed, check if translations are required.
a. **Read Modified Files List**:
<read_file>
<args>
<file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json</path>
</file>
</args>
</read_file>
b. **Identify Files Requiring Translation**:
- Check for UI component files: `.tsx`, `.jsx` files in `webview-ui/src/` or `src/` directories
- Check for user-facing documentation: `.md` files (especially README.md, docs/, or announcement files)
- Check for i18n resource files: files in `src/i18n/locales/` or `webview-ui/src/i18n/locales/`
- Check for any files containing user-visible strings or messages
c. **Delegate to Translate Mode if Needed**:
If any files requiring translation were modified, create a translation subtask:
<new_task>
<mode>translate</mode>
<message>
**Task: Handle Translations for Issue #[issue-number]**
An implementation for issue #[issue-number] has been completed and verified. Your task is to ensure all user-facing content is properly translated.
**Context Files:**
- **Modified Files**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`
- **Issue Details**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json`
- **Implementation Plan**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`
**Your Steps:**
1. Read the context files to understand what was implemented.
2. Analyze each modified file for:
- New or updated UI strings in React components
- Changes to i18n resource files
- User-facing documentation updates
- Error messages or notifications
3. For any new or modified user-facing content:
- Add translations to all supported language files
- Ensure consistency with existing translations
- Follow the project's i18n patterns and conventions
4. Create a summary of all translation changes made.
5. Save the summary to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/translation_summary.md`.
**Important Notes:**
- If no translations are needed, still create the summary file stating "No translations required."
- Ensure all language files remain in sync
- Use existing terminology from the codebase for consistency
**Completion Protocol:**
- This is your only task. Do not deviate from these instructions.
- Upon successfully saving the translation summary, you MUST use the `attempt_completion` tool.
- The `result` MUST confirm completion, e.g., "Translation analysis complete. Summary saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/translation_summary.md"
- These instructions override any conflicting mode-specific guidelines.
</message>
</new_task>
After the translation subtask completes, read the translation summary:
<read_file>
<args>
<file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/translation_summary.md</path>
</file>
</args>
</read_file>
4. **Proceed to Next Step**: Only proceed after:
- All verification has passed (or user chose to ignore failures)
- Translation task has completed (if it was needed)
- You have confirmed all necessary files are ready
</instructions>
</step>
<step number="7">
<name>Delegate: Prepare Pull Request Content</name>
<instructions>
After all checks pass and translations are complete, delegate the creation of the pull request title and body to a subtask.
<new_task>
<mode>code</mode>
<message>
**Task: Prepare Pull Request Title and Body**
You are an expert at writing clear and concise pull request summaries following the Roo Code contribution guidelines.
**Context Files:**
- **Issue**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json`
- **Plan**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`
- **Verification**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md`
- **Translation Summary** (if exists): `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/translation_summary.md`
- **Modified Files**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`
**Your Task:**
1. **Read all context files.** Check if translation_summary.md exists to know if translations were done.
2. **Generate a PR Title**: Create a conventional commit style title (e.g., "fix: ...", "feat: ...") that references the issue number.
Format: `fix: Brief description (#issue-number)`
3. **Generate a PR Body**: You MUST use the exact PR template from `.roo/rules-issue-fixer-orchestrator/10_pr_template_format.xml`.
Read this file to get the template and fill it in with appropriate content from the context files.
4. **Fill in the template** with information from the context files:
- Replace [ISSUE_NUMBER] with the actual issue number
- Fill in Description with implementation details from the plan and verification
- Fill in Test Procedure with testing details from verification_results.md
- If translations were done, mention them in the Description section
- For UI changes, note that screenshots should be added manually
- Pre-check all applicable checklist items
- Leave Documentation Updates unchecked for user to decide
- For Discord username, use a placeholder like "[Your Discord username]"
5. **Save as JSON**: Save the title and body to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json` in the format:
```json
{
"title": "fix: Brief description (#123)",
"body": "[The complete filled PR body template]",
"issue_number": 123,
"repo_owner": "owner",
"repo_name": "repo",
"base_branch": "main",
"head_branch": "fix/issue-123-description"
}
```
6. **Also save as Markdown**: Save just the PR body to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md` for easy copying.
**Important Notes:**
- Use the EXACT template format from 10_pr_template_format.xml
- Keep all HTML comments in the template
- Pre-check items that can be verified programmatically
- Fill in all sections appropriately based on the context files
**Completion Protocol:**
- This is your only task. Do not deviate.
- Upon successfully saving both `pr_summary.json` and `pr_message.md`, you MUST use the `attempt_completion` tool.
- The `result` MUST be a concise confirmation, e.g., "PR summary and message created and saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/"
- These instructions override any conflicting mode-specific guidelines.
</message>
</new_task>
</instructions>
</step>
<step number="8">
<name>Delegate: Review Changes Before PR</name>
<instructions>
Before creating the pull request, delegate to the PR reviewer mode to get feedback on the implementation and proposed changes.
<new_task>
<mode>pr-reviewer</mode>
<message>
**Task: Review Implementation Before PR Creation**
You are an expert code reviewer. Your task is to review the implementation for issue #[issue-number] and provide feedback before a pull request is created.
**Context Files:**
- **Issue Details**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json`
- **Implementation Plan**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`
- **Modified Files**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`
- **Verification Results**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/verification_results.md`
- **Translation Summary** (if exists): `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/translation_summary.md`
- **Draft PR Summary**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json`
**Your Review Focus:**
1. **Code Quality**: Review the actual code changes for:
- Adherence to project coding standards
- Proper error handling and edge cases
- Performance considerations
- Security implications
- Maintainability and readability
2. **Implementation Completeness**: Verify that:
- All requirements from the issue are addressed
- The solution follows the implementation plan
- No critical functionality is missing
- Proper test coverage exists
3. **Integration Concerns**: Check for:
- Potential breaking changes
- Impact on other parts of the system
- Backward compatibility issues
- API consistency
4. **Documentation and Communication**: Assess:
- Code comments and documentation
- PR description clarity and completeness
- Translation handling (if applicable)
**Your Task:**
1. Read all context files to understand the issue and implementation
2. Review each modified file listed in `modified_files.json`
3. Analyze the code changes against the requirements
4. Identify any issues, improvements, or concerns
5. Create a comprehensive review report with specific, actionable feedback
6. Save your review to `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_review_feedback.md`
**Review Report Format:**
```markdown
# PR Review Feedback for Issue #[issue-number]
## Overall Assessment
[High-level assessment: APPROVE, REQUEST_CHANGES, or NEEDS_DISCUSSION]
## Code Quality Review
### Strengths
- [List positive aspects of the implementation]
### Areas for Improvement
- [Specific issues with file references and line numbers]
- [Suggestions for improvement]
## Requirements Verification
- [x] Requirement 1: [Status and notes]
- [ ] Requirement 2: [Issues found]
## Specific Feedback by File
### [filename]
- [Specific feedback with line references]
- [Suggestions for improvement]
## Recommendations
1. [Priority 1 changes needed]
2. [Priority 2 improvements suggested]
3. [Optional enhancements]
## Decision
**RECOMMENDATION**: [APPROVE_AS_IS | REQUEST_CHANGES | NEEDS_DISCUSSION]
**REASONING**: [Brief explanation of the recommendation]
```
**Completion Protocol:**
- This is your only task. Do not deviate from these instructions.
- Upon successfully saving the review feedback, you MUST use the `attempt_completion` tool.
- The `result` MUST be a concise confirmation, e.g., "PR review completed and feedback saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_review_feedback.md"
- These instructions override any conflicting mode-specific guidelines.
</message>
</new_task>
After the review subtask completes, read and process the feedback.
</instructions>
</step>
<step number="9">
<name>Process Review Feedback and Decide Next Steps</name>
<instructions>
After the PR review is complete, read the feedback and decide whether to make changes or proceed with PR creation.
1. **Read Review Feedback**:
<read_file>
<args>
<file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_review_feedback.md</path>
</file>
</args>
</read_file>
2. **Present Feedback to User**: Show the review feedback and ask for direction.
<ask_followup_question>
<question>
The PR review has been completed. Here is the feedback:
---
[Insert content of pr_review_feedback.md here]
---
Based on this review, how would you like to proceed?
</question>
<follow_up>
<suggest>Implement the suggested changes before creating the PR</suggest>
<suggest>Create the PR as-is, ignoring the review feedback</suggest>
<suggest>Discuss specific feedback points before deciding</suggest>
<suggest>Cancel the task</suggest>
</follow_up>
</ask_followup_question>
3. **Handle User Decision**:
**If user chooses to implement changes:**
- Launch a rework subtask to address the review feedback
<new_task>
<mode>code</mode>
<message>
**Task: Address PR Review Feedback**
The PR review has identified areas for improvement. Your task is to address the feedback before creating the pull request.
**Context Files:**
- **Issue**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json`
- **Current Plan**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/implementation_plan.md`
- **Current Modified Files**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json`
- **Review Feedback**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_review_feedback.md`
- **Draft PR Summary**: `.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json`
**Your Task:**
1. Read the review feedback carefully
2. Address each point raised by the reviewer
3. Make the necessary code changes
4. Update tests if needed
5. **Update the `modified_files.json` file** to reflect any new or changed files
6. **Update the `implementation_plan.md`** if the approach has changed significantly
**Important Notes:**
- Focus on the specific issues identified in the review
- Maintain the overall solution approach unless the review suggests otherwise
- Ensure all changes are properly tested
- Do not proceed with any other workflow steps
**Completion Protocol:**
- Upon successfully addressing the feedback and updating context files, you MUST use the `attempt_completion` tool.
- The `result` MUST be a concise confirmation, e.g., "Review feedback addressed and context files updated."
</message>
</new_task>
- **After rework completion**: Return to **Step 5** (Verify and Test) to re-verify the changes
**If user chooses to proceed as-is:**
- Continue to the next step (Create Pull Request)
**If user wants to discuss or cancel:**
- Handle accordingly based on user input
</instructions>
</step>
<step number="10">
<name>Prepare Branch and Review Changes</name>
<instructions>
This step prepares the branch, reviews the changes, and gets user confirmation before committing.
1. Read Issue Context and PR Summary:
- Read issue context from .roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json
- Read PR summary from .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json
2. Create Branch:
Extract issue number from context and create appropriate branch:
<execute_command>
<command>
# Extract issue number from context
ISSUE_NUM=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json | jq -r '.number')
# Determine branch type based on labels or title
BRANCH_NAME="fix/issue-${ISSUE_NUM}-solution"
git checkout -b $BRANCH_NAME
</command>
</execute_command>
3. Review Files to be Committed:
a. Read the modified files list:
<read_file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json</path>
</read_file>
b. Check git status to ensure only intended files are staged:
<execute_command>
<command>git status --porcelain</command>
</execute_command>
c. Stage only the files from modified_files.json:
<execute_command>
<command>
# Stage only the files we actually modified
cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/modified_files.json | jq -r '.[]' | while read file; do
if [ -f "$file" ]; then
git add "$file"
fi
done
# Show what will be committed
git status --short
</command>
</execute_command>
4. Generate and Save Merge Diff:
<execute_command>
<command>
# Generate diff of staged changes
git diff --cached > .roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes.diff
# Count lines in diff
LINE_COUNT=$(wc -l < .roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes.diff)
echo "Diff contains $LINE_COUNT lines"
# If diff is too large, create a summary instead
if [ $LINE_COUNT -gt 2000 ]; then
echo "Creating summary due to large diff size..."
git diff --cached --stat > .roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes_summary.txt
git diff --cached --name-status >> .roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes_summary.txt
echo -e "\n\nNote: Full diff exceeds 2000 lines. Showing summary only." >> .roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes_summary.txt
fi
</command>
</execute_command>
5. Save PR Message to File:
Read the PR summary and save the formatted PR message:
<execute_command>
<command>
# Extract title and body from pr_summary.json
PR_TITLE=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json | jq -r '.title')
PR_BODY=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json | jq -r '.body')
# Save PR message to file
echo "PR Title: $PR_TITLE" > .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md
echo -e "\n---\n" >> .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md
echo "$PR_BODY" >> .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md
echo "PR message saved to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md"
</command>
</execute_command>
6. Present Changes for User Confirmation:
Read the diff (or summary) and PR message, then ask for confirmation:
a. Read the appropriate diff file:
<read_file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes.diff</path>
</read_file>
OR (if summary exists):
<read_file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/staged_changes_summary.txt</path>
</read_file>
b. Read the PR message:
<read_file>
<path>.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md</path>
</read_file>
c. Ask for user confirmation:
<ask_followup_question>
<question>
I've prepared the changes for commit. Here's what will be included:
**Files to be committed:**
[List files from modified_files.json]
**Changes summary:**
[Include diff or summary content]
**Proposed PR message:**
[Include PR message content]
How would you like to proceed?
</question>
<follow_up>
<suggest>Looks good, go ahead and commit the changes</suggest>
<suggest>I tested the changes, and something is wrong - let me explain...</suggest>
<suggest>I still need to test the changes manually before committing</suggest>
<suggest>I want to modify the PR message before proceeding</suggest>
<suggest>Cancel this task</suggest>
</follow_up>
</ask_followup_question>
7. Handle User Decision:
Based on user response:
**If "Looks good, go ahead and commit":**
- Proceed to commit and push:
<execute_command>
<command>
# Get PR title for commit message
PR_TITLE=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json | jq -r '.title')
git commit -m "$PR_TITLE"
git push -u origin $(git branch --show-current)
</command>
</execute_command>
- Continue to next step
**If "something is wrong":**
- Ask for details about the issue
- Launch a fix subtask in code mode to address the problems
- Return to Step 5 (Verify and Test) after fixes
**If "need to test manually":**
- Inform user that changes are staged but not committed
- Provide instructions for testing
- Wait for user to confirm testing is complete
**If "modify PR message":**
- Ask for the new PR title and body
- Update pr_summary.json with new content
- Return to step 5 of this workflow
**If "cancel":**
- Clean up staged changes and branch
- End workflow
</instructions>
</step>
<step number="11">
<name>Finalize and Create PR</name>
<instructions>
After user confirmation and successful commit, finalize the process.
1. Verify Push Success:
<execute_command>
<command>
# Verify the branch was pushed
git branch -r | grep $(git branch --show-current)
# Get the remote URL for PR creation
git remote get-url origin
</command>
</execute_command>
2. Present Final PR Creation Options:
<ask_followup_question>
<question>
The changes have been committed and pushed to the branch. The PR message has been saved to:
`.roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md`
You can now:
1. Create the PR manually using the saved message
2. Let me create the PR automatically
3. Make additional changes before creating the PR
What would you like to do?
</question>
<follow_up>
<suggest>Create the PR automatically using the saved message</suggest>
<suggest>I'll create the PR manually - just show me the branch name</suggest>
<suggest>I want to make more changes first</suggest>
</follow_up>
</ask_followup_question>
3. Handle Final Decision:
**If "Create PR automatically":**
- Read PR details and create:
<execute_command>
<command>
# Extract PR details
PR_TITLE=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json | jq -r '.title')
PR_BODY=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_summary.json | jq -r '.body')
ISSUE_NUM=$(cat .roo/temp/issue-fixer-orchestrator/[TASK_ID]/issue_context.json | jq -r '.number')
# Create PR
gh pr create --title "$PR_TITLE" --body "$PR_BODY" --base main
# Get PR number and link to issue
PR_NUM=$(gh pr list --head $(git branch --show-current) --json number -q '.[0].number')
gh issue comment $ISSUE_NUM --body "PR #$PR_NUM has been created to address this issue."
</command>
</execute_command>
**If "Create manually":**
- Show branch name and location of PR message:
<execute_command>
<command>
echo "Branch name: $(git branch --show-current)"
echo "PR message saved at: .roo/temp/issue-fixer-orchestrator/[TASK_ID]/pr_message.md"
echo "You can copy the PR message from the file above when creating the PR."
</command>
</execute_command>
**If "Make more changes":**
- Inform user they can continue working on the branch
- Provide the task directory location for reference
4. Cleanup:
Ask if user wants to clean up the temporary files:
<ask_followup_question>
<question>
Would you like me to clean up the temporary task files now, or keep them for reference?
</question>
<follow_up>
<suggest>Yes, clean up the temporary files</suggest>
<suggest>No, keep the files for now</suggest>
</follow_up>
</ask_followup_question>
If yes, clean up:
<execute_command>
<command>rm -rf .roo/temp/issue-fixer-orchestrator/[TASK_ID]</command>
</execute_command>
</instructions>
</step>
<step number="12">
<name>Monitor PR (Optional)</name>
<instructions>
If a PR was created automatically, offer to monitor its status.
1. Check if PR exists:
<execute_command>
<command>
# Check if there's a PR for the current branch
BRANCH=$(git branch --show-current)
gh pr list --head $BRANCH --json number,state,checks
</command>
</execute_command>
2. If PR exists, offer monitoring:
<ask_followup_question>
<question>
Would you like me to monitor the PR checks and CI status?
</question>
<follow_up>
<suggest>Yes, monitor the PR checks</suggest>
<suggest>No, I'll check it myself</suggest>
</follow_up>
</ask_followup_question>
3. If user wants monitoring:
<execute_command>
<command>
PR_NUM=$(gh pr list --head $(git branch --show-current) --json number -q '.[0].number')
echo "Monitoring PR #$PR_NUM checks..."
gh pr checks $PR_NUM --watch
</command>
</execute_command>
This concludes the orchestration workflow.
</instructions>
</step>
</workflow>

View file

@ -1,120 +0,0 @@
<best_practices>
- Always read the entire issue and all comments before starting
- Follow the project's coding standards and patterns
- Focus exclusively on addressing the issue's requirements.
- Make minimal, high-quality changes for bug fixes. The goal is a narrow, targeted fix, not a one-line hack.
- Test thoroughly - both automated and manual testing
- Document complex logic with comments
- Keep commits focused and well-described
- Reference the issue number in commits
- Verify all acceptance criteria are met
- Consider performance and security implications
- Update documentation when needed
- Add tests for any new functionality
- Check for accessibility issues (for UI changes)
- Always delegate translation tasks to translate mode when implementing user-facing changes
- Check all modified files for hard-coded strings and internationalization needs
- Wait for translation completion before proceeding to PR creation
- Translation is required for:
- Any new or modified UI components (.tsx, .jsx files)
- User-facing documentation changes (.md files)
- Error messages and notifications
- Any strings visible to end users
- The translate mode will handle:
- Adding translations to all supported language files
- Ensuring consistency with existing terminology
- Maintaining sync across all language resources
<commit_and_pr_best_practices>
<principle>Always verify files before committing</principle>
<guidelines>
- Review git status to ensure only intended files are staged
- Stage only files listed in modified_files.json
- Never commit unrelated changes or temporary files
- Always get user confirmation before committing
</guidelines>
<diff_management>
- Save full diff to staged_changes.diff for review
- If diff exceeds 2000 lines, create a summary instead
- Summary should include file stats and change types
- Always inform user when showing summary vs full diff
</diff_management>
<pr_message_handling>
- Save PR message in both JSON and Markdown formats
- pr_summary.json for programmatic use
- pr_message.md for manual PR creation
- Include all standard template sections
- Make PR message self-contained and ready to use
</pr_message_handling>
<user_confirmation_flow>
- Always ask for confirmation with clear options
- First option should be "Looks good, go ahead"
- Provide options for testing and issue reporting
- Allow PR message modification before proceeding
- Handle each user response appropriately
</user_confirmation_flow>
<temp_file_management>
- All delegated tasks must save outputs to .roo/temp/issue-fixer-orchestrator/[TASK_ID]/
- Keep all context files until user confirms cleanup
- Offer cleanup option after PR creation
- Never delete files without user permission
</temp_file_management>
</commit_and_pr_best_practices>
<codebase_exploration_guidelines>
<principle>Always use `codebase_search` FIRST to understand the codebase structure and find all related files before using other tools like `read_file`.</principle>
<architectural_understanding>
<title>Critical: Understand Component Interactions</title>
<mandatory_steps>
<step>Map the complete data flow from input to output</step>
<step>Identify ALL paired operations (import/export, save/load, encode/decode)</step>
<step>Find all consumers and dependencies of the affected code</step>
<step>Trace how data transformations occur throughout the system</step>
<step>Understand error propagation and handling patterns</step>
</mandatory_steps>
</architectural_understanding>
<for_bug_fixes>
<title>Investigation Checklist for Bug Fixes</title>
<item>Search for the specific error message or broken functionality.</item>
<item>Find all relevant error handling and logging statements.</item>
<item>Locate related test files to understand expected behavior.</item>
<item>Identify all dependencies and import/export patterns for the affected code.</item>
<item>Find similar, working patterns in the codebase to use as a reference.</item>
<item>**CRITICAL**: For any operation being fixed, find and analyze its paired operations</item>
<item>Trace the complete data flow to understand all affected components</item>
</for_bug_fixes>
<for_features>
<title>Investigation Checklist for New Features</title>
<item>Search for any similar existing features to use as a blueprint.</item>
<item>Find potential integration points (e.g., API routes, UI component registries).</item>
<item>Locate relevant configuration files that may need to be updated.</item>
<item>Identify common patterns, components, and utilities that should be reused.</item>
<item>**CRITICAL**: Design paired operations together (e.g., both import AND export)</item>
<item>Map all data transformations and state changes</item>
<item>Identify all downstream consumers of the new functionality</item>
</for_features>
<paired_operations_principle>
<title>Always Implement Paired Operations Together</title>
<examples>
<example>When fixing export, ALWAYS check and update import</example>
<example>When modifying save, ALWAYS verify load handles the changes</example>
<example>When changing serialization, ALWAYS update deserialization</example>
<example>When updating create, consider read/update/delete operations</example>
</examples>
<rationale>
Paired operations must maintain consistency. Changes to one without the other leads to data corruption, import failures, or broken functionality.
</rationale>
</paired_operations_principle>
<critical_note>
Always read multiple related files together to understand the full context. Never assume a change is isolated - trace its impact through the entire system.
</critical_note>
</codebase_exploration_guidelines>
</best_practices>

View file

@ -1,38 +0,0 @@
<common_patterns>
<bug_fix_pattern>
1. Reproduce the issue
2. Identify root cause
3. Implement minimal fix
4. Add regression test
5. Verify fix works
6. Check for side effects
</bug_fix_pattern>
<feature_implementation_pattern>
1. Understand all requirements
2. Design the solution
3. Implement incrementally
4. Test each component
5. Integrate components
6. Verify acceptance criteria
7. Add comprehensive tests
8. Update documentation
</feature_implementation_pattern>
<commit_and_pr_pattern>
1. Review git status to identify all changes
2. Stage only files from modified_files.json
3. Generate diff for review (full or summary based on size)
4. Create PR message and save to temp directory
5. Present changes to user for confirmation
6. Handle user response:
- If approved: commit and proceed to PR options
- If issues found: return to implementation
- If manual testing needed: wait for user
7. After commit, offer PR creation options:
- Create PR automatically
- Save PR message for manual creation
- Skip PR creation
8. Optionally monitor PR and offer cleanup
</commit_and_pr_pattern>
</common_patterns>

View file

@ -1,221 +0,0 @@
<github_cli_usage>
<overview>
This mode uses the GitHub CLI (gh) for all GitHub operations.
The mode assumes the user has gh installed and authenticated. If authentication errors occur,
the mode will prompt the user to authenticate.
Users must provide full GitHub issue URLs (e.g., https://github.com/owner/repo/issues/123)
so the mode can extract the repository information dynamically.
</overview>
<url_parsing>
<pattern>https://github.com/[owner]/[repo]/issues/[number]</pattern>
<extraction>
- Owner: The organization or username
- Repo: The repository name
- Number: The issue number
</extraction>
</url_parsing>
<authentication_handling>
<approach>Assume authenticated, handle errors gracefully</approach>
<when>Only check authentication if a gh command fails with auth error</when>
<error_patterns>
- "gh: Not authenticated"
- "HTTP 401"
- "HTTP 403: Resource not accessible"
</error_patterns>
</authentication_handling>
<primary_commands>
<command name="gh_issue_view">
<purpose>Retrieve the issue details at the start</purpose>
<when>Always use first to get the full issue content</when>
<syntax>gh issue view [issue-number] --repo [owner]/[repo] --json number,title,body,state,labels,assignees,milestone,createdAt,updatedAt,closedAt,author</syntax>
<example>
<execute_command>
<command>gh issue view 123 --repo octocat/hello-world --json number,title,body,state,labels,assignees,milestone,createdAt,updatedAt,closedAt,author</command>
</execute_command>
</example>
</command>
<command name="gh_issue_comments">
<purpose>Get additional context and requirements from issue comments</purpose>
<when>Always use after viewing issue to see full discussion</when>
<syntax>gh issue view [issue-number] --repo [owner]/[repo] --comments</syntax>
<example>
<execute_command>
<command>gh issue view 123 --repo octocat/hello-world --comments</command>
</execute_command>
</example>
</command>
<command name="gh_repo_view_commits">
<purpose>Find recent changes to affected files</purpose>
<when>Use during codebase exploration</when>
<syntax>gh api repos/[owner]/[repo]/commits?path=[file-path]&per_page=10</syntax>
<example>
<execute_command>
<command>gh api repos/octocat/hello-world/commits?path=src/api/index.ts&per_page=10 --jq '.[].sha + " " + .[].commit.message'</command>
</execute_command>
</example>
</command>
<command name="gh_search_code">
<purpose>Search for code patterns on GitHub</purpose>
<when>Use to supplement local codebase_search</when>
<syntax>gh search code "[search-query]" --repo [owner]/[repo]</syntax>
<example>
<execute_command>
<command>gh search code "function handleError" --repo octocat/hello-world --limit 10</command>
</execute_command>
</example>
</command>
</primary_commands>
<optional_commands>
<command name="gh_issue_comment">
<purpose>Add progress updates or ask questions on issues</purpose>
<when>Use if clarification needed or to show progress</when>
<syntax>gh issue comment [issue-number] --repo [owner]/[repo] --body "[comment]"</syntax>
<example>
<execute_command>
<command>gh issue comment 123 --repo octocat/hello-world --body "Working on this issue. Found the root cause in the theme detection logic."</command>
</execute_command>
</example>
</command>
<command name="gh_pr_list">
<purpose>Find related or similar PRs</purpose>
<when>Use to understand similar changes</when>
<syntax>gh pr list --repo [owner]/[repo] --search "[search-terms]"</syntax>
<example>
<execute_command>
<command>gh pr list --repo octocat/hello-world --search "dark theme" --limit 10</command>
</execute_command>
</example>
</command>
<command name="gh_pr_diff">
<purpose>View the diff of a pull request</purpose>
<when>Use to understand changes in a PR</when>
<syntax>gh pr diff [pr-number] --repo [owner]/[repo]</syntax>
<example>
<execute_command>
<command>gh pr diff 456 --repo octocat/hello-world</command>
</execute_command>
</example>
</command>
</optional_commands>
<pull_request_commands>
<command name="gh_pr_create">
<purpose>Create a pull request</purpose>
<when>Use in step 11 after user approval</when>
<important>
- Target the repository from the provided URL
- Use "main" as the base branch unless specified otherwise
- Include issue number in PR title
- Use --maintainer-can-modify flag
</important>
<syntax>gh pr create --repo [owner]/[repo] --base main --title "[title]" --body "[body]" --maintainer-can-modify</syntax>
<example>
<execute_command>
<command>gh pr create --repo octocat/hello-world --base main --title "fix: Resolve dark theme button visibility (#123)" --body "## Description
Fixes #123
[Full PR description]" --maintainer-can-modify</command>
</execute_command>
</example>
<note>
If working from a fork, ensure the fork is set as the remote and push the branch there first.
The gh CLI will automatically handle the fork workflow.
</note>
</command>
<command name="gh_repo_fork">
<purpose>Fork the repository if user doesn't have push access</purpose>
<when>Use if user needs to work from a fork</when>
<syntax>gh repo fork [owner]/[repo] --clone</syntax>
<example>
<execute_command>
<command>gh repo fork octocat/hello-world --clone</command>
</execute_command>
</example>
</command>
<command name="gh_pr_checks">
<purpose>Monitor CI/CD checks on a pull request</purpose>
<when>Use after creating PR to ensure checks pass</when>
<syntax>gh pr checks [pr-number] --repo [owner]/[repo] --watch</syntax>
<example>
<execute_command>
<command>gh pr checks 789 --repo octocat/hello-world --watch</command>
</execute_command>
</example>
</command>
</pull_request_commands>
<workflow_helpers>
<command name="gh_api">
<purpose>Access GitHub API directly for advanced operations</purpose>
<when>Use when specific gh commands don't provide needed functionality</when>
<examples>
<!-- Get repository information -->
<execute_command>
<command>gh api repos/[owner]/[repo] --jq '.default_branch'</command>
</execute_command>
<!-- Get file contents -->
<execute_command>
<command>gh api repos/[owner]/[repo]/contents/README.md --jq '.content' | base64 -d</command>
</execute_command>
<!-- Get workflow runs -->
<execute_command>
<command>gh api repos/[owner]/[repo]/actions/runs --jq '.workflow_runs[0:5] | .[] | .id, .status, .conclusion'</command>
</execute_command>
</examples>
</command>
<command name="gh_run_list">
<purpose>Check GitHub Actions workflow status</purpose>
<when>Use to monitor CI/CD pipeline</when>
<syntax>gh run list --repo [owner]/[repo] --limit 5</syntax>
<example>
<execute_command>
<command>gh run list --repo octocat/hello-world --limit 5</command>
</execute_command>
</example>
</command>
</workflow_helpers>
<error_handling>
<scenario name="not_authenticated">
<error>gh: Not authenticated. Run 'gh auth login' to authenticate.</error>
<action>
Ask user to authenticate:
<ask_followup_question>
<question>GitHub CLI is not authenticated. Please run 'gh auth login' in your terminal to authenticate, then let me know when you're ready to continue.</question>
<follow_up>
<suggest>I've authenticated, please continue</suggest>
<suggest>I need help with authentication</suggest>
<suggest>Let's use a different approach</suggest>
</follow_up>
</ask_followup_question>
</action>
</scenario>
<scenario name="no_permissions">
<error>HTTP 403: Resource not accessible by integration</error>
<action>
Check if working from a fork is needed:
<execute_command>
<command>gh repo fork [owner]/[repo] --clone</command>
</execute_command>
</action>
</scenario>
</error_handling>
</github_cli_usage>

View file

@ -1,106 +0,0 @@
<pull_request_workflow>
<preparation>
1. Ensure all changes are committed with proper message format
2. Push to appropriate branch (fork or direct)
3. Prepare comprehensive PR description
4. Get user approval before creating PR
5. Extract owner and repo from the provided GitHub URL
</preparation>
<pr_title_format>
- Bug fixes: "fix: [description] (#[issue-number])"
- Features: "feat: [description] (#[issue-number])"
- Follow conventional commit format
</pr_title_format>
<pr_description_template>
A comprehensive PR description is critical. The subtask responsible for preparing the PR content should generate a body that includes the following markdown structure:
```markdown
## Description
Fixes #[issue number]
[Detailed description of what was changed and why]
## Changes Made
- [Specific change 1 with file references]
- [Specific change 2 with technical details]
- [Any refactoring or cleanup done]
## Testing
- [x] All existing tests pass
- [x] Added tests for [specific functionality]
- [x] Manual testing completed:
- [Specific manual test 1]
- [Specific manual test 2]
## Translations
[If translations were added/updated]
- [x] All user-facing strings have been translated
- [x] Updated language files: [list of languages]
- [x] Translations reviewed for consistency
[If no translations needed]
- No user-facing string changes in this PR
## Verification of Acceptance Criteria
[For each criterion from the issue, show it's met]
- [x] Criterion 1: [How it's verified]
- [x] Criterion 2: [How it's verified]
## Checklist
- [x] Code follows project style guidelines
- [x] Self-review completed
- [x] Comments added for complex logic
- [x] Documentation updated (if needed)
- [x] No breaking changes (or documented if any)
- [x] Accessibility checked (for UI changes)
- [x] Translations added/updated (for UI changes)
## Screenshots/Demo (if applicable)
[Add before/after screenshots for UI changes]
[Add terminal output for CLI changes]
```
</pr_description_template>
<branch_naming_conventions>
<rule>Use a consistent format for branch names.</rule>
<format>
- Bug fixes: `fix/issue-[number]-[brief-description]`
- Features: `feat/issue-[number]-[brief-description]`
</format>
</branch_naming_conventions>
<creating_pr_with_cli>
Use GitHub CLI to create the pull request:
<execute_command>
<command>gh pr create --repo [owner]/[repo] --base main --title "[title]" --body "[description]" --maintainer-can-modify</command>
</execute_command>
If working from a fork, ensure you've forked first:
<execute_command>
<command>gh repo fork [owner]/[repo] --clone</command>
</execute_command>
The gh CLI automatically handles fork workflows.
</creating_pr_with_cli>
<after_creation>
1. Comment on original issue with PR link:
<execute_command>
<command>gh issue comment [issue-number] --repo [owner]/[repo] --body "PR #[pr-number] has been created to address this issue"</command>
</execute_command>
2. Inform user of successful creation
3. Provide next steps and tracking info
4. Monitor PR checks:
<execute_command>
<command>gh pr checks [pr-number] --repo [owner]/[repo] --watch</command>
</execute_command>
</after_creation>
</pull_request_workflow>

View file

@ -1,10 +0,0 @@
<testing_guidelines>
- Always run existing tests before making changes (baseline)
- Add tests for any new functionality
- Add regression tests for bug fixes
- Test edge cases and error conditions
- Run the full test suite before completing
- For UI changes, test in multiple themes
- Verify accessibility (keyboard navigation, screen readers)
- Test performance impact for large operations
</testing_guidelines>

View file

@ -1,27 +0,0 @@
<communication_style>
- Be clear about what you're doing at each step
- Explain technical decisions and trade-offs
- Ask for clarification if requirements are ambiguous
- Provide regular progress updates for complex issues
- Summarize changes clearly for non-technical stakeholders
- Use issue numbers and links for reference
- Inform the user when delegating to translate mode
- Include translation status in progress updates
- Mention in PR description if translations were added
<pre_commit_communication>
- Clearly list all files that will be committed
- Explain when showing a summary vs full diff (>2000 lines)
- Provide file statistics for large diffs
- Mention that PR message has been saved to temp directory
- Offer clear options for user to proceed or report issues
</pre_commit_communication>
<post_commit_communication>
- Confirm successful commit with commit hash
- Explain PR creation options clearly
- Mention that PR message is saved and ready to use
- Provide path to PR message file for manual creation
- Offer cleanup option after PR is created
</post_commit_communication>
</communication_style>

View file

@ -1,16 +0,0 @@
<github_communication_guidelines>
<issue_comments>
- Provide brief status updates when working on complex issues
- Ask specific questions if requirements are unclear
- Share findings when investigation reveals important context
- Keep progress updates factual and concise
- Example: "Found the root cause in the theme detection logic. Working on a fix that preserves backward compatibility."
</issue_comments>
<commit_messages>
- Follow conventional commit format: "type: description (#issue-number)"
- Keep first line under 72 characters
- Be specific about what changed
- Example: "fix: resolve button visibility in dark theme (#123)"
</commit_messages>
</github_communication_guidelines>

View file

@ -1,125 +0,0 @@
<translation_handling_guidelines>
<overview>
The issue-fixer-orchestrator mode must ensure all user-facing content is properly translated before creating a pull request. This is achieved by delegating translation tasks to the specialized translate mode.
</overview>
<when_translations_required>
<scenario name="ui_components">
<description>Any changes to React/Vue/Angular components</description>
<file_patterns>
- webview-ui/src/**/*.tsx
- webview-ui/src/**/*.jsx
- src/**/*.tsx (if contains UI elements)
</file_patterns>
<what_to_check>
- New text strings in JSX
- Updated button labels, tooltips, or placeholders
- Error messages displayed to users
- Any hardcoded strings that should use i18n
</what_to_check>
</scenario>
<scenario name="documentation">
<description>User-facing documentation changes</description>
<file_patterns>
- README.md
- docs/**/*.md
- webview-ui/src/components/chat/Announcement.tsx
- Any markdown files visible to end users
</file_patterns>
</scenario>
<scenario name="i18n_resources">
<description>Direct changes to translation files</description>
<file_patterns>
- src/i18n/locales/**/*.json
- webview-ui/src/i18n/locales/**/*.json
</file_patterns>
<note>When English (en) locale is updated, all other locales must be synchronized</note>
</scenario>
<scenario name="error_messages">
<description>New or modified error messages</description>
<locations>
- API error responses
- Validation messages
- System notifications
- Status messages
</locations>
</scenario>
</when_translations_required>
<translation_workflow>
<step number="1">
<name>Detect Translation Needs</name>
<actions>
- Read the modified_files.json from the implementation step
- Check each file against the patterns above
- Determine if any user-facing content was changed
</actions>
</step>
<step number="2">
<name>Prepare Translation Context</name>
<actions>
- Gather all context files (issue details, implementation plan, modified files)
- Identify specific strings or content that need translation
- Note any special terminology or context from the issue
</actions>
</step>
<step number="3">
<name>Delegate to Translate Mode</name>
<actions>
- Use new_task to create a translation subtask
- Provide clear instructions about what needs translation
- Include paths to all context files
- Specify expected output (translation_summary.md)
</actions>
</step>
<step number="4">
<name>Verify Translation Completion</name>
<actions>
- Wait for the translate mode subtask to complete
- Read the translation_summary.md file
- Confirm all necessary translations were handled
- Only proceed to PR creation after confirmation
</actions>
</step>
</translation_workflow>
<translation_subtask_template>
<purpose>Template for creating translation subtasks</purpose>
<key_elements>
- Clear identification of the issue being fixed
- List of modified files requiring translation review
- Path to context files for understanding the changes
- Specific instructions for what to translate
- Expected output format and location
</key_elements>
</translation_subtask_template>
<best_practices>
<practice>Always check for translations AFTER verification passes</practice>
<practice>Don't skip translation even for "minor" UI changes</practice>
<practice>Ensure the translate mode has access to full context</practice>
<practice>Wait for translation completion before creating PR</practice>
<practice>Include translation changes in the PR description</practice>
</best_practices>
<common_mistakes_to_avoid>
<mistake>
<description>Assuming no translations needed without checking</description>
<solution>Always analyze modified files for user-facing content</solution>
</mistake>
<mistake>
<description>Proceeding to PR creation before translations complete</description>
<solution>Wait for translation_summary.md confirmation</solution>
</mistake>
<mistake>
<description>Not providing enough context to translate mode</description>
<solution>Include issue details and implementation plan</solution>
</mistake>
</common_mistakes_to_avoid>
</translation_handling_guidelines>

View file

@ -0,0 +1,205 @@
<pr_template_instructions>
<overview>
This file contains the official Roo Code PR template that must be used when creating pull requests.
All PRs must follow this exact format to ensure consistency and proper documentation.
</overview>
<pr_body_template>
<description>
The PR body must follow this exact Roo Code PR template with all required sections.
Replace placeholder content in square brackets with actual information.
</description>
<template><![CDATA[
<!--
Thank you for contributing to Roo Code!
Before submitting your PR, please ensure:
- It's linked to an approved GitHub Issue.
- You've reviewed our [Contributing Guidelines](../CONTRIBUTING.md).
-->
### Related GitHub Issue
<!-- Every PR MUST be linked to an approved issue. -->
Closes: #[ISSUE_NUMBER] <!-- Replace with the issue number, e.g., Closes: #123 -->
### Roo Code Task Context (Optional)
<!--
If you used Roo Code to help create this PR, you can share public task links here.
This helps reviewers understand your development process and provides additional context.
Example: https://app.roocode.com/share/task-id
-->
[TASK_CONTEXT]
### Description
<!--
Briefly summarize the changes in this PR and how they address the linked issue.
The issue should cover the "what" and "why"; this section should focus on:
- The "how": key implementation details, design choices, or trade-offs made.
- Anything specific reviewers should pay attention to in this PR.
-->
[DESCRIPTION_CONTENT]
### Test Procedure
<!--
Detail the steps to test your changes. This helps reviewers verify your work.
- How did you test this specific implementation? (e.g., unit tests, manual testing steps)
- How can reviewers reproduce your tests or verify the fix/feature?
- Include relevant testing environment details if applicable.
-->
[TEST_PROCEDURE_CONTENT]
### Pre-Submission Checklist
<!-- Go through this checklist before marking your PR as ready for review. -->
- [x] **Issue Linked**: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
- [x] **Scope**: My changes are focused on the linked issue (one major feature/fix per PR).
- [x] **Self-Review**: I have performed a thorough self-review of my code.
- [x] **Testing**: New and/or updated tests have been added to cover my changes (if applicable).
- [x] **Documentation Impact**: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
- [x] **Contribution Guidelines**: I have read and agree to the [Contributor Guidelines](/CONTRIBUTING.md).
### Screenshots / Videos
<!--
For UI changes, please provide before-and-after screenshots or a short video of the *actual results*.
This greatly helps in understanding the visual impact of your changes.
-->
[SCREENSHOTS_CONTENT]
### Documentation Updates
<!--
Does this PR necessitate updates to user-facing documentation?
- [ ] No documentation updates are required.
- [ ] Yes, documentation updates are required. (Please describe what needs to be updated or link to a PR in the docs repository).
-->
[DOCUMENTATION_UPDATES_CONTENT]
### Additional Notes
<!-- Add any other context, questions, or information for reviewers here. -->
[ADDITIONAL_NOTES_CONTENT]
### Get in Touch
<!--
Please provide your Discord username for reviewers or maintainers to reach you if they have questions about your PR
-->
[DISCORD_USERNAME]
]]></template>
</pr_body_template>
<github_cli_commands>
<description>
Valid GitHub CLI commands for creating PRs with the proper template
</description>
<create_pr_command>
<description>Create a PR using the filled template</description>
<command><![CDATA[
gh pr create \
--repo [owner]/[repo] \
--base main \
--title "[Type]: [Brief description] (#[issue-number])" \
--body-file pr-body.md \
--maintainer-can-modify
]]></command>
<note>The PR body should be saved to a temporary file first, then referenced with --body-file</note>
</create_pr_command>
<create_pr_inline>
<description>Alternative: Create PR with inline body (for shorter content)</description>
<command><![CDATA[
gh pr create \
--repo [owner]/[repo] \
--base main \
--title "[Type]: [Brief description] (#[issue-number])" \
--body "[Complete PR body content]" \
--maintainer-can-modify
]]></command>
<note>Use this only if the body content doesn't contain special characters that need escaping</note>
</create_pr_inline>
<fork_if_needed>
<description>Fork repository if user doesn't have push access</description>
<command><![CDATA[
gh repo fork [owner]/[repo] --clone=false
]]></command>
<note>The --clone=false flag prevents cloning since we're already in the repo</note>
</fork_if_needed>
</github_cli_commands>
<pr_title_format>
<description>PR titles should follow conventional commit format</description>
<formats>
<format type="bug_fix">fix: [brief description] (#[issue-number])</format>
<format type="feature">feat: [brief description] (#[issue-number])</format>
<format type="docs">docs: [brief description] (#[issue-number])</format>
<format type="refactor">refactor: [brief description] (#[issue-number])</format>
<format type="test">test: [brief description] (#[issue-number])</format>
<format type="chore">chore: [brief description] (#[issue-number])</format>
</formats>
</pr_title_format>
<placeholder_guidance>
<description>How to fill in the template placeholders</description>
<placeholders>
<placeholder name="ISSUE_NUMBER">
<description>The GitHub issue number being addressed</description>
<example>123</example>
</placeholder>
<placeholder name="TASK_CONTEXT">
<description>Optional Roo Code task links if used during development</description>
<example>https://app.roocode.com/share/task-abc123</example>
<default>_No Roo Code task context for this PR_</default>
</placeholder>
<placeholder name="DESCRIPTION_CONTENT">
<description>Detailed explanation of implementation approach</description>
<guidance>
- Focus on HOW you solved the problem
- Mention key design decisions
- Highlight any trade-offs made
- Point out areas needing special review attention
</guidance>
</placeholder>
<placeholder name="TEST_PROCEDURE_CONTENT">
<description>Steps to verify the changes work correctly</description>
<guidance>
- List specific test commands run
- Describe manual testing performed
- Include steps for reviewers to reproduce tests
- Mention test environment details if relevant
</guidance>
</placeholder>
<placeholder name="SCREENSHOTS_CONTENT">
<description>Visual evidence of changes for UI modifications</description>
<default>_No UI changes in this PR_</default>
</placeholder>
<placeholder name="DOCUMENTATION_UPDATES_CONTENT">
<description>Documentation impact assessment</description>
<default>- [x] No documentation updates are required.</default>
</placeholder>
<placeholder name="ADDITIONAL_NOTES_CONTENT">
<description>Any extra context for reviewers</description>
<default>_No additional notes_</default>
</placeholder>
<placeholder name="DISCORD_USERNAME">
<description>Discord username for communication</description>
<example>@username</example>
</placeholder>
</placeholders>
</placeholder_guidance>
</pr_template_instructions>

View file

@ -308,23 +308,31 @@
<step number="8">
<name>Create GitHub Issue</name>
<instructions>
Once user confirms, create the issue using the GitHub MCP tool:
Once user confirms, create the issue using the GitHub CLI:
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>create_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"title": "[Create a descriptive title based on the issue content]",
"body": "[The complete formatted issue body from step 6]",
"labels": [Use ["bug"] for bug reports or ["proposal", "enhancement"] for features]
}
</arguments>
</use_mcp_tool>
First, save the issue body to a temporary file:
<execute_command>
<command>cat > /tmp/issue_body.md << 'EOF'
[The complete formatted issue body from step 6]
EOF</command>
</execute_command>
After creation, inform the user of the issue number and URL.
Then create the issue:
<execute_command>
<command>gh issue create --repo RooCodeInc/Roo-Code --title "[Create a descriptive title based on the issue content]" --body-file /tmp/issue_body.md --label "bug"</command>
</execute_command>
For feature requests, use labels "proposal,enhancement":
<execute_command>
<command>gh issue create --repo RooCodeInc/Roo-Code --title "[Create a descriptive title based on the issue content]" --body-file /tmp/issue_body.md --label "proposal" --label "enhancement"</command>
</execute_command>
The command will return the issue URL. Inform the user of the created issue number and URL.
Clean up the temporary file:
<execute_command>
<command>rm /tmp/issue_body.md</command>
</execute_command>
</instructions>
</step>
</workflow>

View file

@ -0,0 +1,273 @@
<github_cli_usage>
<overview>
The GitHub CLI (gh) provides comprehensive tools for interacting with GitHub.
Here's when and how to use each command in the issue creation workflow.
Note: Issue body formatting should follow the templates defined in
2_github_issue_templates.xml, with different formats for problem reporters
vs contributors.
</overview>
<pre_creation_commands>
<command name="gh issue list">
<when_to_use>
ALWAYS use this FIRST before creating any issue to check for duplicates.
Search for keywords from the user's problem description.
</when_to_use>
<example>
<execute_command>
<command>gh issue list --repo RooCodeInc/Roo-Code --search "dark theme button visibility" --state all --limit 20</command>
</execute_command>
</example>
<options>
--search: Search query for issue titles and bodies
--state: all, open, or closed
--label: Filter by specific labels
--limit: Number of results to show
--json: Get structured JSON output
</options>
</command>
<command name="gh search issues">
<when_to_use>
Use for more advanced searches across issues and pull requests.
Supports GitHub's advanced search syntax.
</when_to_use>
<example>
<execute_command>
<command>gh search issues --repo RooCodeInc/Roo-Code "dark theme button" --limit 10</command>
</execute_command>
</example>
</command>
<command name="gh issue view">
<when_to_use>
Use when you find a potentially related issue and need full details.
Check if the user's issue is already reported or related.
</when_to_use>
<example>
<execute_command>
<command>gh issue view 123 --repo RooCodeInc/Roo-Code --comments</command>
</execute_command>
</example>
<options>
--comments: Include issue comments
--json: Get structured data
--web: Open in browser
</options>
</command>
</pre_creation_commands>
<contributor_only_commands>
<note>
These commands should ONLY be used if the user has indicated they want to
contribute the implementation. Skip these for problem reporters.
</note>
<command name="gh repo view">
<when_to_use>
Get repository information and recent activity.
</when_to_use>
<example>
<execute_command>
<command>gh repo view RooCodeInc/Roo-Code --json defaultBranchRef,description,updatedAt</command>
</execute_command>
</example>
</command>
<command name="gh search prs">
<when_to_use>
Check recent PRs that might be related to the issue.
Look for PRs that modified relevant code.
</when_to_use>
<example>
<execute_command>
<command>gh search prs --repo RooCodeInc/Roo-Code "dark theme" --limit 10 --state all</command>
</execute_command>
</example>
</command>
<command name="git log">
<when_to_use>
For bug reports from contributors, check recent commits that might have introduced the issue.
Use after cloning the repository locally.
</when_to_use>
<example>
<execute_command>
<command>git log --oneline --grep="theme" -n 20</command>
</execute_command>
</example>
</command>
</contributor_only_commands>
<issue_creation_command>
<command name="gh issue create">
<when_to_use>
Only use after:
1. Confirming no duplicates exist
2. Gathering all required information
3. Determining if user is contributing or just reporting
4. Getting user confirmation
</when_to_use>
<bug_report_example>
<execute_command>
<command>gh issue create --repo RooCodeInc/Roo-Code --title "[Descriptive title of the bug]" --body-file /tmp/issue_body.md --label "bug"</command>
</execute_command>
</bug_report_example>
<feature_request_example>
<execute_command>
<command>gh issue create --repo RooCodeInc/Roo-Code --title "[Problem-focused title]" --body-file /tmp/issue_body.md --label "proposal" --label "enhancement"</command>
</execute_command>
</feature_request_example>
<options>
--title: Issue title (required)
--body: Issue body text
--body-file: Read body from file
--label: Add labels (can use multiple times)
--assignee: Assign to user
--project: Add to project
--web: Open in browser to create
</options>
</command>
</issue_creation_command>
<post_creation_commands>
<command name="gh issue comment">
<when_to_use>
ONLY use if user wants to add additional information after creation.
</when_to_use>
<example>
<execute_command>
<command>gh issue comment 456 --repo RooCodeInc/Roo-Code --body "Additional context or comments."</command>
</execute_command>
</example>
</command>
<command name="gh issue edit">
<when_to_use>
Use if user realizes they need to update the issue after creation.
Can update title, body, or labels.
</when_to_use>
<example>
<execute_command>
<command>gh issue edit 456 --repo RooCodeInc/Roo-Code --title "[Updated title]" --body "[Updated body]"</command>
</execute_command>
</example>
</command>
</post_creation_commands>
<workflow_integration>
<step_1_integration>
After user selects issue type, immediately search for related issues:
1. Use `gh issue list --search` with keywords from their description
2. Show any similar issues found
3. Ask if they want to continue or comment on existing issue
</step_1_integration>
<step_3_integration>
When searching GitHub Discussions:
1. Note that GitHub CLI doesn't currently have full discussions support
2. Use web search or instruct user to manually search discussions at:
https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests
3. Ask user to provide any related discussion numbers they find
4. Include these in the "Related Discussions" section of the issue
</step_3_integration>
<step_4_integration>
Decision point for contribution:
1. Ask user if they want to contribute implementation
2. If yes: Use contributor commands for codebase investigation
3. If no: Skip directly to creating a problem-focused issue
4. This saves time for problem reporters
</step_4_integration>
<step_5_integration>
During codebase exploration (CONTRIBUTORS ONLY):
1. Clone repo locally if needed: `gh repo clone RooCodeInc/Roo-Code`
2. Use `git log` to find recent changes to affected files
3. Use `gh search prs` for related pull requests
4. Include findings in the technical context section
</step_5_integration>
<step_6_integration>
When creating the issue:
1. Format differently based on contributor vs problem reporter
2. Problem reporters: Simple problem description + context
3. Contributors: Full template with technical sections
4. Save formatted body to temporary file
5. Use `gh issue create` with appropriate labels
6. Capture the returned issue URL
7. Show user the created issue URL
</step_6_integration>
</workflow_integration>
<best_practices>
<practice name="file_handling">
When creating issues with long bodies:
1. Save to temporary file: `cat > /tmp/issue_body.md << 'EOF'`
2. Use --body-file flag with gh issue create
3. Clean up after: `rm /tmp/issue_body.md`
</practice>
<practice name="search_efficiency">
Use specific search terms:
- Include error messages in quotes
- Use label filters when appropriate
- Limit results to avoid overwhelming output
</practice>
<practice name="json_output">
Use --json flag for structured data when needed:
- Easier to parse programmatically
- Consistent format across commands
- Example: `gh issue list --json number,title,state`
</practice>
</best_practices>
<error_handling>
<duplicate_found>
If search finds exact duplicate:
- Show the existing issue to user using `gh issue view`
- Ask if they want to add a comment instead
- Use `gh issue comment` if they agree
</duplicate_found>
<creation_failed>
If `gh issue create` fails:
- Check error message (auth, permissions, network)
- Ensure gh is authenticated: `gh auth status`
- Save the drafted issue content for user
- Suggest using --web flag to create in browser
</creation_failed>
<authentication>
Ensure GitHub CLI is authenticated:
- Check status: `gh auth status`
- Login if needed: `gh auth login`
- Select appropriate scopes for issue creation
</authentication>
</error_handling>
<command_reference>
<issues>
gh issue create - Create new issue
gh issue list - List and search issues
gh issue view - View issue details
gh issue comment - Add comment to issue
gh issue edit - Edit existing issue
gh issue close - Close an issue
gh issue reopen - Reopen closed issue
</issues>
<search>
gh search issues - Search issues and PRs
gh search prs - Search pull requests
gh search repos - Search repositories
</search>
<repository>
gh repo view - View repository info
gh repo clone - Clone repository
</repository>
</command_reference>
</github_cli_usage>

View file

@ -1,352 +0,0 @@
<github_mcp_tools_usage>
<overview>
The GitHub MCP server provides multiple tools for interacting with GitHub.
Here's when and how to use each tool in the issue creation workflow.
Note: Issue body formatting should follow the templates defined in
2_github_issue_templates.xml, with different formats for problem reporters
vs contributors.
</overview>
<pre_creation_tools>
<tool name="search_issues">
<when_to_use>
ALWAYS use this FIRST before creating any issue to check for duplicates.
Search for keywords from the user's problem description.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>search_issues</tool_name>
<arguments>
{
"q": "repo:RooCodeInc/Roo-Code dark theme button visibility",
"sort": "updated",
"order": "desc"
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="list_issues">
<when_to_use>
Use to browse recent issues if search doesn't find specific matches.
Helpful for understanding issue patterns and formatting.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>list_issues</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"state": "all",
"labels": ["bug"],
"sort": "created",
"direction": "desc",
"perPage": 10
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="get_issue">
<when_to_use>
Use when you find a potentially related issue and need full details.
Check if the user's issue is already reported or related.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"issue_number": 123
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="get_issue_comments">
<when_to_use>
Use on related issues to understand discussion context.
Helps avoid creating issues for already-discussed topics.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_issue_comments</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"issue_number": 123
}
</arguments>
</use_mcp_tool>
</example>
</tool>
</pre_creation_tools>
<contributor_only_tools>
<note>
These tools should ONLY be used if the user has indicated they want to
contribute the implementation. Skip these for problem reporters.
</note>
<tool name="list_commits">
<when_to_use>
For bug reports from contributors, check recent commits that might have introduced the issue.
Look for commits touching the affected files.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>list_commits</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"perPage": 20
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="get_commit">
<when_to_use>
When you identify a potentially problematic commit.
Get details about what changed.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_commit</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"sha": "abc123def456"
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="search_code">
<when_to_use>
Use to find code patterns across the repository on GitHub.
Complements local codebase_search tool for contributors.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>search_code</tool_name>
<arguments>
{
"q": "repo:RooCodeInc/Roo-Code language:typescript dark theme button"
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="list_pull_requests">
<when_to_use>
Check recent PRs that might be related to the issue.
Look for PRs that modified relevant code.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>list_pull_requests</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"state": "all",
"sort": "updated",
"direction": "desc",
"perPage": 10
}
</arguments>
</use_mcp_tool>
</example>
</tool>
</contributor_only_tools>
<issue_creation_tool>
<tool name="create_issue">
<when_to_use>
Only use after:
1. Confirming no duplicates exist
2. Gathering all required information
3. Determining if user is contributing or just reporting
4. Getting user confirmation
</when_to_use>
<bug_report_example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>create_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"title": "[Descriptive title of the bug]",
"body": "[Format according to bug report template]",
"labels": ["bug"]
}
</arguments>
</use_mcp_tool>
</bug_report_example>
<feature_request_problem_reporter_example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>create_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"title": "[Problem-focused title]",
"body": "[Problem description only - no technical details]",
"labels": ["proposal", "enhancement"]
}
</arguments>
</use_mcp_tool>
</feature_request_problem_reporter_example>
<feature_request_contributor_example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>create_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"title": "[Problem-focused title with implementation intent]",
"body": "[Full template including technical analysis sections]",
"labels": ["proposal", "enhancement"]
}
</arguments>
</use_mcp_tool>
</feature_request_contributor_example>
</tool>
</issue_creation_tool>
<post_creation_tools>
<tool name="add_issue_comment">
<when_to_use>
ONLY use if user wants to add additional information after creation.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>add_issue_comment</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"issue_number": 456,
"body": "Additional context or comments."
}
</arguments>
</use_mcp_tool>
</example>
</tool>
<tool name="update_issue">
<when_to_use>
Use if user realizes they need to update the issue after creation.
Can update title, body, or state.
</when_to_use>
<example>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>update_issue</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"issue_number": 456,
"title": "[Updated title if needed]",
"body": "[Updated body if needed]"
}
</arguments>
</use_mcp_tool>
</example>
</tool>
</post_creation_tools>
<workflow_integration>
<step_1_integration>
After user selects issue type, immediately search for related issues:
1. Use search_issues with keywords from their description
2. Show any similar issues found
3. Ask if they want to continue or comment on existing issue
</step_1_integration>
<step_3_integration>
When searching GitHub Discussions:
1. Note that GitHub MCP tools don't currently support discussions API
2. Instruct user to manually search discussions at:
https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests
3. Ask user to provide any related discussion numbers they find
4. Include these in the "Related Discussions" section of the issue
</step_3_integration>
<step_4_integration>
Decision point for contribution:
1. Ask user if they want to contribute implementation
2. If yes: Use contributor tools for codebase investigation
3. If no: Skip directly to creating a problem-focused issue
4. This saves time for problem reporters
</step_4_integration>
<step_5_integration>
During codebase exploration (CONTRIBUTORS ONLY):
1. Use list_commits to find recent changes to affected files
2. Use search_code for additional code references
3. Check list_pull_requests for related PRs
4. Include findings in the technical context section
</step_5_integration>
<step_6_integration>
When creating the issue:
1. Format differently based on contributor vs problem reporter
2. Problem reporters: Simple problem description + context
3. Contributors: Full template with technical sections
4. Use create_issue with appropriate body format
5. Capture the returned issue number
6. Show user the created issue URL
</step_6_integration>
</workflow_integration>
<error_handling>
<duplicate_found>
If search_issues finds exact duplicate:
- Show the existing issue to user
- Ask if they want to add a comment instead
- Use add_issue_comment if they agree
</duplicate_found>
<creation_failed>
If create_issue fails:
- Check error message (permissions, rate limit, etc.)
- Save the drafted issue content
- Provide user with the content to create manually
</creation_failed>
<api_limits>
Be aware of GitHub API rate limits:
- Authenticated requests: 5000/hour
- Search API: 30 requests/minute
- Use searches efficiently
</api_limits>
</error_handling>
</github_mcp_tools_usage>

View file

@ -1,771 +0,0 @@
<workflow>
<step number="1">
<name>Initialize PR Context</name>
<instructions>
The user will provide a GitHub PR URL or number.
1. **Parse Input**: Extract the `owner`, `repo`, and `pr_number` from the URL or use provided number.
2. **Create Task Directory**: Create a dedicated directory to store all context for this PR fix task.
<execute_command>
<command>mkdir -p .roo/temp/pr-fixer-orchestrator/[TASK_ID]</command>
</execute_command>
3. **Retrieve PR Details**: Fetch the PR details, comments, and check status as a comprehensive JSON object.
<execute_command>
<command>gh pr view [pr_number] --repo [owner]/[repo] --json number,title,body,state,labels,author,headRefName,baseRefName,mergeable,mergeStateStatus,isDraft,isCrossRepository,headRepositoryOwner,reviews,statusCheckRollup,comments > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_context.json</command>
</execute_command>
4. **Get Review Comments**: Fetch detailed review comments separately for better analysis.
<execute_command>
<command>gh pr view [pr_number] --repo [owner]/[repo] --comments > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_comments.txt</command>
</execute_command>
5. **Check CI Status**: Get current check status and any failing workflows.
<execute_command>
<command>gh pr checks [pr_number] --repo [owner]/[repo] --json name,state,conclusion,detailsUrl > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_checks.json</command>
</execute_command>
6. **Get Associated Issue**: Check if PR is linked to an issue and fetch issue details if available.
<execute_command>
<command>gh pr view [pr_number] --repo [owner]/[repo] --json closingIssuesReferences > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/linked_issues.json</command>
</execute_command>
If linked issues exist, fetch the first issue's details:
<execute_command>
<command>gh issue view [issue_number] --repo [owner]/[repo] --json number,title,body,state,labels,assignees,milestone,createdAt,updatedAt,closedAt,author,comments > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/issue_context.json</command>
</execute_command>
7. **Handle Auth Errors**: If any `gh` command fails with authentication error, prompt the user to log in.
8. **Confirm Context**: Inform the user that the PR context has been gathered.
</instructions>
</step>
<step number="2">
<name>Checkout PR Branch and Initial Analysis</name>
<instructions>
Before delegating analysis, ensure the PR branch is checked out locally.
1. **Checkout PR Branch**: Use gh to checkout the PR branch locally.
<execute_command>
<command>gh pr checkout [pr_number] --repo [owner]/[repo] --force</command>
</execute_command>
2. **Determine Remote Type**: Check if this is a cross-repository PR (from a fork).
<execute_command>
<command>gh pr view [pr_number] --repo [owner]/[repo] --json isCrossRepository,headRepositoryOwner,headRefName > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_remote_info.json</command>
</execute_command>
3. **Setup Fork Remote if Needed**: If it's a cross-repository PR, ensure fork remote is configured.
Read the pr_remote_info.json file. If isCrossRepository is true:
<execute_command>
<command>git remote add fork https://github.com/[headRepositoryOwner]/[repo].git || git remote set-url fork https://github.com/[headRepositoryOwner]/[repo].git</command>
</execute_command>
4. **Fetch Latest Main**: Ensure we have the latest main branch for comparison.
<execute_command>
<command>git fetch origin main</command>
</execute_command>
5. **Check for Conflicts**: Determine if there are merge conflicts with main.
<execute_command>
<command>git merge-tree $(git merge-base HEAD origin/main) HEAD origin/main > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_conflicts.txt</command>
</execute_command>
6. **Get PR Diff**: Fetch the files changed in this PR for context.
<execute_command>
<command>gh pr diff [pr_number] --repo [owner]/[repo] --name-only > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_changed_files.txt</command>
</execute_command>
7. **Check Merge Diff Size**: Get the full diff and check line count.
<execute_command>
<command>git diff origin/main...HEAD > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/full_merge_diff.txt</command>
</execute_command>
<execute_command>
<command>wc -l .roo/temp/pr-fixer-orchestrator/[TASK_ID]/full_merge_diff.txt</command>
</execute_command>
If the diff has over 2000 lines, create a summary instead:
<execute_command>
<command>git diff origin/main...HEAD --stat > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_diff_summary.txt</command>
</execute_command>
<execute_command>
<command>rm .roo/temp/pr-fixer-orchestrator/[TASK_ID]/full_merge_diff.txt</command>
</execute_command>
</instructions>
</step>
<step number="3">
<name>Delegate: Comprehensive Requirements and PR Analysis</name>
<instructions>
Launch a subtask in `architect` mode to perform a detailed analysis of the PR, its underlying requirements, and all issues that need to be addressed.
The context files in `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/` will be the input for this subtask.
The subtask should write its findings to: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md`.
<new_task>
<mode>architect</mode>
<message>
**Task: Analyze Pull Request Requirements and Create Comprehensive Fix Plan**
You are an expert software architect. Your task is to analyze a pull request, understand its underlying requirements, and create a comprehensive plan to address all issues.
1. **Read PR Context**: The PR details are in:
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_context.json` - Full PR metadata
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_comments.txt` - Review comments
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_checks.json` - CI/CD check status
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_conflicts.txt` - Conflict analysis
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_changed_files.txt` - Files changed in PR
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/linked_issues.json` - Associated issues (if any)
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/issue_context.json` - Issue details (if linked)
- `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/full_merge_diff.txt` OR `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_diff_summary.txt` - Diff information
2. **Understand the PR's Purpose**:
- Extract the feature or bug being addressed from PR title, body, and linked issues
- Identify the acceptance criteria (from PR description or linked issue)
- Understand the intended functionality and expected behavior
- Note any design decisions or architectural choices made
3. **Perform Architectural Analysis**:
- **Map Component Interactions**: Trace the complete data flow for the PR's changes
- **Identify Paired Operations**: For any operation (e.g., export), find its counterpart (e.g., import)
- **Find Similar Patterns**: Search for existing implementations of similar features
- **Analyze Dependencies**: Identify all consumers of the functionality being modified
- **Assess Impact**: Determine how changes affect other parts of the system
4. **Explore Codebase Systematically**:
- Use `codebase_search` FIRST to understand the feature area
- Search for related functionality that might be affected
- Find all files that consume or depend on the changed functionality
- Identify configuration files, tests, and documentation that need updates
- Study similar features to understand established patterns
5. **Analyze Review Feedback**:
- Categorize review comments by type (bug, enhancement, style, etc.)
- Identify which comments are actionable vs informational
- Prioritize changes based on reviewer authority and importance
- Note any conflicting feedback that needs clarification
6. **Investigate Failing Tests**:
- For each failing check, determine the root cause
- Use `gh run view --log-failed` to get detailed error logs
- Identify if failures are due to code issues, flaky tests, or environment problems
- Determine which files need modification to fix test failures
7. **Assess Merge Conflicts**:
- Analyze the merge_conflicts.txt file
- Identify which files have conflicts
- Determine the complexity of conflict resolution
- Plan the rebase/merge strategy
8. **Create Comprehensive Fix Plan**: The plan must include:
- **PR Purpose Summary**: Clear description of what the PR is trying to achieve
- **Requirements Analysis**:
- Original requirements from issue or PR description
- Acceptance criteria that must be met
- Any missing functionality that needs to be added
- **Architectural Context**:
- Data flow diagram showing component interactions
- List of paired operations that must be updated together
- Dependencies and consumers of the affected functionality
- **Issue Summary**: Clear categorization of all issues found
- **Priority Order**: Which issues to tackle first and why
- **Review Feedback Analysis**:
- List of all actionable review comments
- Specific code changes required for each
- Any clarifications needed from reviewers
- **Test Failure Resolution**:
- Root cause of each failing test
- Files and changes needed to fix
- Any test updates required
- **Conflict Resolution Strategy**:
- Whether to rebase or merge
- Order of operations for conflict resolution
- Risk assessment of conflicts
- **Implementation Steps**:
- Detailed, ordered steps for fixing all issues
- Specific commands and file modifications
- Validation steps after each change
- **Risk Assessment**:
- Potential side effects of changes
- Areas requiring extra testing
- Backward compatibility concerns
9. **Save the Analysis**: Write the complete analysis to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md`.
**Critical Requirements:**
- Always understand the PR's underlying purpose before analyzing issues
- Be thorough in analyzing all aspects of the PR
- Consider the interaction between different fixes
- Provide specific, actionable steps
- Include exact commands where applicable
- **IMPORTANT**: Save your analysis to the specified file in .roo/temp/pr-fixer-orchestrator/[TASK_ID]/
**Completion Protocol:**
- This is your only task. Do not deviate from these instructions.
- Once you have successfully written the analysis report, you MUST signal completion by using the `attempt_completion` tool.
- The `result` parameter MUST be: "PR analysis complete and saved to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md"
</message>
</new_task>
After launching the subtask, wait for it to complete. The orchestrator will then read the analysis report to proceed.
</instructions>
</step>
<step number="4">
<name>Review Analysis and Get User Approval</name>
<instructions>
After the analysis subtask completes, present the findings to the user for approval.
1. **Read the Analysis Report**:
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md</path>
</file>
</args>
</read_file>
2. **Present for Approval**: Show the analysis to the user and ask how to proceed.
<ask_followup_question>
<question>
I've completed the analysis of PR #[pr_number]. Here's what I found:
---
[Insert content of pr_analysis_report.md here]
---
How would you like to proceed with fixing these issues?
</question>
<follow_up>
<suggest>Fix all issues in the recommended priority order</suggest>
<suggest>Only fix the review comments, skip failing tests for now</suggest>
<suggest>Only fix failing tests and conflicts, skip review comments</suggest>
<suggest>Let me choose specific issues to fix</suggest>
</follow_up>
</ask_followup_question>
3. **Handle User Choice**: Based on the user's selection, prepare the implementation scope.
Save the user's choice to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/implementation_scope.txt`
</instructions>
</step>
<step number="5">
<name>Fetch Latest from Main and Check Differences</name>
<instructions>
Before implementing fixes, ensure we're working with the latest code and understand what has changed.
1. **Fetch Latest Changes**:
<execute_command>
<command>git fetch origin main</command>
</execute_command>
2. **Analyze Differences**: Create a detailed diff report.
<execute_command>
<command>git diff origin/main...HEAD --name-status > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_file_changes.txt</command>
</execute_command>
3. **Check Commit History**: Understand what commits are in this PR.
<execute_command>
<command>git log origin/main..HEAD --oneline > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_commits.txt</command>
</execute_command>
4. **Identify New Commits on Main**: See what has been merged to main since the PR was created.
<execute_command>
<command>git log HEAD..origin/main --oneline > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/new_main_commits.txt</command>
</execute_command>
5. **Save Merge Strategy**: Based on the analysis, determine if we should rebase or merge.
Create `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_strategy.txt` with either "rebase" or "merge"
</instructions>
</step>
<step number="6">
<name>Delegate: Implement Fixes</name>
<instructions>
Launch a subtask in `code` mode to implement all the fixes based on the analysis and user's choices.
<new_task>
<mode>code</mode>
<message>
**Task: Implement PR Fixes Based on Analysis**
You are an expert software developer. Your task is to implement fixes for a pull request based on the analysis and plan.
1. **Read Context Files**:
- Analysis Report: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md`
- Implementation Scope: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/implementation_scope.txt`
- File Changes: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_file_changes.txt`
- Merge Strategy: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/merge_strategy.txt`
2. **Handle Merge/Rebase First** (if conflicts exist):
- If merge_strategy.txt says "rebase":
<execute_command>
<command>GIT_EDITOR=true git rebase origin/main</command>
</execute_command>
- If conflicts occur, resolve them by editing the conflicted files
- Remember to escape conflict markers when using apply_diff
- After resolving each file: `git add [file]`
- Continue rebase: `git rebase --continue`
3. **Implement Missing Functionality** (if identified in analysis):
- Add any missing features or functionality noted in the requirements analysis
- Follow the architectural patterns identified in the analysis
- Ensure all acceptance criteria are met
- Update related operations to maintain consistency
4. **Implement Review Feedback**:
- Address each actionable review comment from the analysis
- Make code changes using appropriate file editing tools
- Ensure changes follow project coding standards
- Add comments where complex logic is introduced
5. **Fix Failing Tests**:
- Based on the root cause analysis, fix test failures
- This may involve fixing source code or updating tests
- Run tests locally if possible to verify fixes
- Document any test changes made
6. **Track All Changes**: As you make changes, maintain a list of:
- Files modified with brief description of changes
- Review comments addressed
- Tests fixed
- Missing functionality added
- Any additional improvements made
7. **Create Change Summary**: Write a comprehensive summary to:
`.roo/temp/pr-fixer-orchestrator/[TASK_ID]/changes_implemented.md`
Include:
- List of all files modified
- Review comments addressed (with file:line references)
- Test fixes applied
- Conflict resolutions performed
- Missing functionality implemented
- Any additional improvements
**Important Reminders:**
- Follow the implementation plan from the analysis
- Respect the user's chosen scope
- Make minimal, targeted changes
- Preserve existing functionality
- When resolving conflicts, understand both sides before choosing
- Ensure all original PR requirements are met
- **IMPORTANT**: Save all output files to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/
**Completion Protocol:**
- Once all fixes are implemented and the summary is saved, use `attempt_completion`.
- Result: "PR fixes implemented and summary saved to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/changes_implemented.md"
</message>
</new_task>
Wait for the implementation subtask to complete before proceeding.
</instructions>
</step>
<step number="7">
<name>Delegate: Test and Validate Changes</name>
<instructions>
After implementation, delegate testing and validation to ensure all fixes work correctly.
<new_task>
<mode>test</mode>
<message>
**Task: Validate PR Fixes and Run Tests**
You are a meticulous QA engineer. Your task is to validate that all PR fixes have been properly implemented.
**Context Files:**
- Original Analysis: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md`
- Changes Made: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/changes_implemented.md`
- Original PR Checks: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_checks.json`
**Your Steps:**
1. **Verify Requirements**: Check that all original PR requirements and acceptance criteria are met.
2. **Verify Review Comments**: Check that each review comment marked as addressed in changes_implemented.md has been properly fixed.
3. **Run Local Tests**: Execute relevant test suites.
- Identify test files related to changed code
- Run unit tests for modified components
- Run integration tests if applicable
- Document all test results
4. **Validate Code Quality**:
- Run linters on changed files
- Check for type errors (if TypeScript)
- Verify no console.logs or debug code remains
- Ensure proper error handling
5. **Check for Regressions**:
- Verify existing functionality still works
- Look for potential side effects of changes
- Test edge cases around modified code
6. **Create Validation Report**: Write findings to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/validation_report.md`
Include:
- Test results summary (pass/fail counts)
- Requirements verification checklist
- Review comment verification checklist
- Any issues or concerns found
- Recommendations for additional testing
- Overall assessment: READY or NEEDS_WORK
**Critical Focus Areas:**
- Ensure all originally failing tests now pass
- Verify no new test failures introduced
- Confirm review feedback properly addressed
- Check that all PR requirements are fulfilled
- Check for unintended consequences
- **IMPORTANT**: Save your report to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/
**Completion Protocol:**
- Save validation report and use `attempt_completion`
- Result: "Validation complete. Report saved to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/validation_report.md"
</message>
</new_task>
Wait for validation to complete before proceeding.
</instructions>
</step>
<step number="8">
<name>Handle Validation Results and Translation Needs</name>
<instructions>
Review validation results and check if translation updates are needed.
1. **Read Validation Report**:
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/validation_report.md</path>
</file>
</args>
</read_file>
2. **If Validation Failed**: Present issues to user and ask how to proceed.
If the report indicates NEEDS_WORK, use ask_followup_question to get direction.
3. **Check for Translation Requirements**:
Read the changes_implemented.md file and check for:
- Changes to i18n JSON files
- Modifications to UI components with user-facing text
- Updates to announcement files or documentation
- New error messages or notifications
4. **Delegate Translation if Needed**:
If translation is required:
<new_task>
<mode>translate</mode>
<message>
**Task: Update Translations for PR Fixes**
PR #[pr_number] fixes have been implemented. Please handle translation updates for the following changes:
**Changed Files:**
[List specific files from changes_implemented.md that need translation]
**Specific Changes:**
[Detail what text was added/modified]
Please ensure all supported languages are updated with appropriate translations.
Save a summary of translation changes to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/translation_summary.md`
**IMPORTANT**: Save your summary to the specified file in .roo/temp/pr-fixer-orchestrator/[TASK_ID]/
</message>
</new_task>
5. **Proceed When Ready**: Only continue after validation passes and translations complete (if needed).
</instructions>
</step>
<step number="9">
<name>Prepare PR Message and Get User Approval</name>
<instructions>
Before committing changes, prepare the PR update message and get user approval.
1. **Check Files to be Committed**: List all modified files.
<execute_command>
<command>git status --porcelain > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/files_to_commit.txt</command>
</execute_command>
2. **Read Implementation Summary**:
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/changes_implemented.md</path>
</file>
</args>
</read_file>
3. **Create PR Update Message**: Based on the changes, create a comprehensive PR update message following the template.
Save to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md`:
```markdown
## PR Update Summary
This update addresses the review feedback and fixes identified issues.
## Changes Made
[List from changes_implemented.md]
- Fixed [specific issue] in [file]
- Addressed review comment about [topic]
- Updated tests for [functionality]
- Resolved merge conflicts in [files]
## Review Comments Addressed
[For each review comment addressed]
- ✅ Comment: "[reviewer comment]"
- Fix: [what was done]
- Files: [files modified]
## Test Fixes
[If tests were fixed]
- Fixed failing test: [test name]
- Issue: [root cause]
- Solution: [fix applied]
## Translations Updated
[If translations were updated]
- Updated [X] language files for [changes]
- All user-facing strings properly translated
## Verification
- [x] All review comments addressed
- [x] All tests passing locally
- [x] No regressions introduced
- [x] Code follows project standards
- [x] Translations updated (if applicable)
## Files Modified
[List all files that will be committed]
```
4. **Get User Approval**: Present the changes and ask for confirmation.
<ask_followup_question>
<question>
I've completed all the fixes for PR #[pr_number]. Here's a summary of what will be committed:
**Files to be committed:**
[Content of files_to_commit.txt]
**PR Update Message:**
[Content of pr_update_message.md]
Would you like me to proceed with committing these changes?
</question>
<follow_up>
<suggest>Looks good, go ahead and commit the changes</suggest>
<suggest>I tested the changes and something is wrong - let me describe the issue</suggest>
<suggest>I still need to test the changes manually before committing</suggest>
<suggest>Let me review specific files before committing</suggest>
</follow_up>
</ask_followup_question>
5. **Handle User Response**:
- If approved: Continue to commit
- If issues found: Document the issue and determine next steps
- If manual testing needed: Wait for user to complete testing
- If review requested: Show requested files and wait for approval
</instructions>
</step>
<step number="10">
<name>Commit Changes and Prepare for Push</name>
<instructions>
Once user approves, commit the changes with appropriate message.
1. **Stage Only Necessary Files**: Review files and stage appropriately.
Read files_to_commit.txt and ensure only relevant files are staged.
<execute_command>
<command>git add [specific files from the implementation]</command>
</execute_command>
Note: Do NOT use `git add -A` to avoid adding unintended files.
2. **Create Commit Message**: Based on the changes made, create an appropriate commit message.
<execute_command>
<command>git commit -m "fix: address PR feedback and fix failing tests
- addressed review comments
- fixed failing tests
- resolved conflicts (if applicable)
- updated translations (if needed)
See PR for detailed changes"</command>
</execute_command>
3. **Verify Remote Configuration**: Check which remote to push to.
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_remote_info.json</path>
</file>
</args>
</read_file>
4. **Determine Push Target**:
- If isCrossRepository is false: push to origin
- If isCrossRepository is true: push to fork
</instructions>
</step>
<step number="11">
<name>Delegate: Final PR Review</name>
<instructions>
Before pushing changes, have the PR reviewer mode review all changes to ensure quality.
<new_task>
<mode>pr-reviewer</mode>
<message>
**Task: Review PR Fix Implementation**
You are reviewing the fixes applied to PR #[pr_number]. Your task is to ensure all changes are high quality and properly address the original feedback.
**Context Files:**
- Original PR Analysis: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_analysis_report.md`
- Changes Implemented: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/changes_implemented.md`
- Validation Report: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/validation_report.md`
- Translation Summary (if exists): `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/translation_summary.md`
- PR Update Message: `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md`
**Review Focus:**
1. **Completeness**: Verify all identified issues have been addressed
2. **Requirements**: Confirm all original PR requirements are met
3. **Code Quality**: Check that fixes follow best practices
4. **No Regressions**: Ensure no new issues introduced
5. **Review Feedback**: Confirm all reviewer comments properly addressed
6. **Test Coverage**: Verify tests cover the changes
7. **Documentation**: Check if docs/comments are adequate
**Your Task:**
1. Review the actual code changes using git diff
2. Cross-reference with the original review feedback
3. Verify all PR requirements are fulfilled
4. Assess the quality of the implementation
5. Check for any missed requirements
6. Create a final review report
Save your review to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/final_review.md` with:
- Overall Assessment: APPROVED or NEEDS_REVISION
- Quality Score: 1-10
- Detailed feedback on the implementation
- Any remaining concerns
- Recommendations for improvement
**IMPORTANT**: Save your review to the specified file in .roo/temp/pr-fixer-orchestrator/[TASK_ID]/
**Completion Protocol:**
- Save review and use `attempt_completion`
- Result: "Final review complete and saved to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/final_review.md"
</message>
</new_task>
Wait for the review to complete.
</instructions>
</step>
<step number="12">
<name>Process Final Review and Push Changes</name>
<instructions>
Based on the final review, either push changes or address remaining issues.
1. **Read Final Review**:
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/final_review.md</path>
</file>
</args>
</read_file>
2. **If Review Requests Revisions**:
Present the feedback to the user and ask if they want to address the issues now or push as-is.
3. **Push Changes**: If approved or user chooses to push:
Based on pr_remote_info.json, push to the correct remote:
For same-repository PRs:
<execute_command>
<command>git push --force-with-lease origin [branch_name]</command>
</execute_command>
For cross-repository PRs:
<execute_command>
<command>git push --force-with-lease fork [branch_name]</command>
</execute_command>
4. **Monitor Push Result**: Ensure the push succeeds.
If --force-with-lease fails, fetch and retry with --force.
</instructions>
</step>
<step number="13">
<name>Verify PR Status and Monitor Checks</name>
<instructions>
After pushing, verify the PR is in good state and monitor CI/CD checks.
1. **Verify PR is Up to Date**:
<execute_command>
<command>gh pr view [pr_number] --repo [owner]/[repo] --json mergeable,mergeStateStatus</command>
</execute_command>
2. **Monitor CI/CD Checks in Real-Time**:
<execute_command>
<command>gh pr checks [pr_number] --repo [owner]/[repo] --watch</command>
</execute_command>
This will continuously monitor until all checks complete.
3. **Get Final Status**: Once monitoring completes, get the final state.
<execute_command>
<command>gh pr checks [pr_number] --repo [owner]/[repo] --json name,state,conclusion > .roo/temp/pr-fixer-orchestrator/[TASK_ID]/final_checks.json</command>
</execute_command>
4. **Comment on PR**: Add a summary comment about the fixes applied.
Read the PR update message we prepared:
<read_file>
<args>
<file>
<path>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md</path>
</file>
</args>
</read_file>
Then post it as a comment:
<execute_command>
<command>gh pr comment [pr_number] --repo [owner]/[repo] --body-file .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md</command>
</execute_command>
5. **Save PR Message**: Keep the PR message for reference.
The PR update message has already been saved to `.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md`
6. **Final Summary**: Present the final status to the user, confirming:
- All requested changes have been implemented
- The branch is synced with main
- CI/CD checks status
- The PR is ready for maintainer review and merge
- PR update message has been posted and saved
7. **Optional Cleanup**: Ask user if they want to clean up temporary files.
<ask_followup_question>
<question>
PR #[pr_number] has been successfully updated!
- All changes committed and pushed
- CI/CD checks are [status]
- PR comment posted with update summary
- PR message saved to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md
Would you like me to clean up the temporary files?
</question>
<follow_up>
<suggest>Yes, clean up temporary files</suggest>
<suggest>No, keep the files for reference</suggest>
</follow_up>
</ask_followup_question>
If user chooses cleanup:
<execute_command>
<command>rm -rf .roo/temp/pr-fixer-orchestrator/[TASK_ID]</command>
</execute_command>
</instructions>
</step>
</workflow>

View file

@ -1,186 +0,0 @@
<best_practices>
<orchestration_principles>
<principle priority="critical">
<name>Always Delegate Specialized Work</name>
<description>The orchestrator coordinates but doesn't implement. Use specialized modes for analysis, coding, testing, and review.</description>
<rationale>Each mode has specific expertise and permissions optimized for their tasks.</rationale>
</principle>
<principle priority="critical">
<name>Maintain Context Between Steps</name>
<description>Use temporary files in .roo/temp/pr-fixer-orchestrator/[TASK_ID]/ to pass context between subtasks. ALL delegated tasks must save outputs to this directory.</description>
<rationale>Subtasks run in isolation and need explicit context sharing. Files saved elsewhere will be inaccessible to subsequent steps.</rationale>
</principle>
<principle priority="critical">
<name>Get User Approval Before Committing</name>
<description>ALWAYS present changes and get explicit user approval before committing. Show modified files, summarize changes, and ask for confirmation.</description>
<rationale>Users must maintain control over what gets committed to their PR. Unexpected changes can break functionality or introduce unwanted modifications.</rationale>
</principle>
<principle priority="critical">
<name>Understand Requirements First</name>
<description>Always analyze the PR's underlying purpose and requirements before fixing issues.</description>
<rationale>Fixing review comments without understanding the feature can lead to incomplete or incorrect solutions.</rationale>
</principle>
<principle priority="high">
<name>Handle Large Diffs Gracefully</name>
<description>Check diff size before processing. If over 2000 lines, create a summary instead of including the full diff.</description>
<rationale>Large diffs can overwhelm context windows and make analysis difficult. Summaries maintain clarity.</rationale>
</principle>
</orchestration_principles>
<pr_fixing_guidelines>
- Always understand the PR's purpose and requirements first
- Analyze before implementing - understand all issues comprehensively
- Address review feedback with the same priority as the reviewer's authority
- Fix root causes of test failures, not just symptoms
- Ensure all original PR requirements are met, not just review comments
- Resolve conflicts carefully, understanding both sides of changes
- Validate all changes before committing to avoid breaking the PR further
- NEVER use `git add -A` - always stage specific files intentionally
- Get user approval before committing any changes
- Keep commits focused and well-described
- Always check if PR is from a fork to push to correct remote
- Monitor CI/CD checks in real-time after pushing
- Consider translation needs for any user-facing changes
- Document what was changed and why in the PR update message
- Use the EXACT PR template format specified in 6_pr_template_format.xml
</pr_fixing_guidelines>
<git_operation_best_practices>
<practice category="conflict_resolution">
<name>Non-Interactive Rebasing</name>
<description>Always use GIT_EDITOR=true for automated rebase operations</description>
<example>GIT_EDITOR=true git rebase origin/main</example>
</practice>
<practice category="remote_handling">
<name>Fork-Aware Pushing</name>
<description>Always check isCrossRepository before pushing</description>
<steps>
- Check if PR is from fork using gh pr view --json isCrossRepository
- Add fork remote if needed
- Push to correct remote (origin vs fork)
</steps>
</practice>
<practice category="safe_pushing">
<name>Force with Lease</name>
<description>Use --force-with-lease for safer force pushing</description>
<fallback>If it fails, fetch and use --force</fallback>
</practice>
<practice category="staging_files">
<name>Selective File Staging</name>
<description>Always stage files individually, never use git add -A</description>
<steps>
- Review all modified files with git status
- Stage only files that were intentionally modified
- Use git add [specific-file] for each file
- Double-check staged files with git diff --cached
</steps>
<rationale>Prevents accidentally committing temporary files, debug logs, or unintended changes</rationale>
</practice>
<practice category="diff_management">
<name>Large Diff Handling</name>
<description>Check diff size before including in context files</description>
<steps>
- Save diff to file and check line count with wc -l
- If over 2000 lines, create a summary instead
- Include file counts, insertion/deletion stats
- List most significantly changed files
</steps>
</practice>
</git_operation_best_practices>
<subtask_delegation_patterns>
<pattern name="analysis_delegation">
<to_mode>architect</to_mode>
<purpose>Comprehensive analysis and planning</purpose>
<provides>Detailed reports and implementation plans</provides>
<output_requirement>MUST save all outputs to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/</output_requirement>
</pattern>
<pattern name="implementation_delegation">
<to_mode>code</to_mode>
<purpose>Executing code changes and fixes</purpose>
<provides>Implemented solutions and change summaries</provides>
<output_requirement>MUST save changes_implemented.md to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/</output_requirement>
</pattern>
<pattern name="validation_delegation">
<to_mode>test</to_mode>
<purpose>Testing and validating changes</purpose>
<provides>Test results and validation reports</provides>
<output_requirement>MUST save validation_report.md to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/</output_requirement>
</pattern>
<pattern name="review_delegation">
<to_mode>pr-reviewer</to_mode>
<purpose>Final quality review before submission</purpose>
<provides>Quality assessment and recommendations</provides>
<output_requirement>MUST save final_review.md to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/</output_requirement>
</pattern>
<pattern name="translation_delegation">
<to_mode>translate</to_mode>
<purpose>Updating translations for UI changes</purpose>
<provides>Synchronized translations across languages</provides>
<output_requirement>MUST save translation_summary.md to .roo/temp/pr-fixer-orchestrator/[TASK_ID]/</output_requirement>
</pattern>
</subtask_delegation_patterns>
<error_handling>
<scenario name="auth_failure">
<error>GitHub CLI authentication error</error>
<action>Prompt user to run 'gh auth login'</action>
</scenario>
<scenario name="no_linked_issue">
<error>No linked issue found</error>
<action>Extract requirements from PR description and comments</action>
</scenario>
<scenario name="push_failure">
<error>Force-with-lease push fails</error>
<action>Fetch latest and retry with --force</action>
</scenario>
<scenario name="large_diff">
<error>Diff exceeds 2000 lines</error>
<action>Create summary with stats instead of full diff</action>
</scenario>
<scenario name="missing_context_files">
<error>Expected context files not found in temp directory</error>
<action>Check if delegated task saved to correct location, re-run if needed</action>
</scenario>
</error_handling>
<user_interaction_guidelines>
<guideline priority="critical">
<name>Pre-Commit Approval</name>
<description>Always get explicit user approval before committing changes</description>
<implementation>
- Show list of modified files
- Summarize key changes made
- Present clear approval options
- Wait for user confirmation
</implementation>
</guideline>
<guideline priority="high">
<name>Clear Communication</name>
<description>Present information clearly and concisely</description>
<implementation>
- Use bullet points for lists
- Highlight important warnings
- Provide actionable suggestions
- Avoid technical jargon when possible
</implementation>
</guideline>
</user_interaction_guidelines>
</best_practices>

View file

@ -1,68 +0,0 @@
<github_cli_usage>
<overview>
This mode uses the GitHub CLI (gh) for all GitHub operations.
The mode assumes the user has gh installed and authenticated.
It can work with PRs from both the main repository and forks.
</overview>
<pr_specific_commands>
<command name="gh_pr_view">
<purpose>Get comprehensive PR details</purpose>
<syntax>gh pr view [pr-number] --repo [owner]/[repo] --json [fields]</syntax>
<fields>number,title,body,state,labels,author,headRefName,baseRefName,mergeable,mergeStateStatus,isDraft,isCrossRepository,headRepositoryOwner,reviews,statusCheckRollup,comments</fields>
</command>
<command name="gh_pr_checkout">
<purpose>Checkout PR branch locally</purpose>
<syntax>gh pr checkout [pr-number] --repo [owner]/[repo] --force</syntax>
<note>Automatically handles fork setup</note>
</command>
<command name="gh_pr_checks">
<purpose>Monitor CI/CD status</purpose>
<syntax>gh pr checks [pr-number] --repo [owner]/[repo] --watch</syntax>
<note>Use --json for programmatic access</note>
</command>
<command name="gh_pr_diff">
<purpose>Get PR changes</purpose>
<syntax>gh pr diff [pr-number] --repo [owner]/[repo] --name-only</syntax>
<note>Use without --name-only for full diff</note>
</command>
<command name="gh_pr_comment">
<purpose>Add comment to PR</purpose>
<syntax>gh pr comment [pr-number] --repo [owner]/[repo] --body "[message]"</syntax>
</command>
</pr_specific_commands>
<issue_integration>
<command name="gh_pr_linked_issues">
<purpose>Get issues linked to PR</purpose>
<syntax>gh pr view [pr-number] --repo [owner]/[repo] --json closingIssuesReferences</syntax>
<note>Returns array of linked issues</note>
</command>
<command name="gh_issue_view">
<purpose>Get issue details if linked</purpose>
<syntax>gh issue view [issue-number] --repo [owner]/[repo] --json [fields]</syntax>
<fields>number,title,body,state,labels,assignees,milestone,createdAt,updatedAt,closedAt,author,comments</fields>
</command>
</issue_integration>
<workflow_commands>
<command name="gh_run_view">
<purpose>Get detailed CI logs</purpose>
<syntax>gh run view [run-id] --repo [owner]/[repo] --log-failed</syntax>
<note>Use to debug failing tests</note>
</command>
<command name="gh_api">
<purpose>Direct API access for advanced operations</purpose>
<examples>
- Get PR reviews: gh api repos/[owner]/[repo]/pulls/[pr-number]/reviews
- Get review comments: gh api repos/[owner]/[repo]/pulls/[pr-number]/comments
</examples>
</command>
</workflow_commands>
</github_cli_usage>

View file

@ -1,120 +0,0 @@
<requirements_analysis_guidelines>
<overview>
The PR Fixer Orchestrator must understand the underlying requirements
of a PR before fixing issues. This ensures fixes align with the
original intent and all acceptance criteria are met.
</overview>
<sources_of_requirements>
<source priority="1">
<name>Linked GitHub Issues</name>
<description>Primary source of requirements and acceptance criteria</description>
<extraction>
- Issue title and body
- Acceptance criteria sections
- Technical specifications
- User stories or use cases
</extraction>
</source>
<source priority="2">
<name>PR Description</name>
<description>Often contains implementation notes and context</description>
<extraction>
- Feature description
- Implementation approach
- Testing notes
- Breaking changes
</extraction>
</source>
<source priority="3">
<name>PR Comments</name>
<description>May contain clarifications and additional requirements</description>
<extraction>
- Author clarifications
- Reviewer questions and answers
- Scope changes or additions
</extraction>
</source>
<source priority="4">
<name>Code Analysis</name>
<description>Infer requirements from the implementation</description>
<extraction>
- API contracts
- Data flow patterns
- Test cases (reveal expected behavior)
- Documentation comments
</extraction>
</source>
</sources_of_requirements>
<analysis_approach>
<step number="1">
<name>Extract Explicit Requirements</name>
<actions>
- Parse linked issues for acceptance criteria
- Extract requirements from PR description
- Identify success metrics
</actions>
</step>
<step number="2">
<name>Understand Implementation Intent</name>
<actions>
- Analyze the code changes to understand approach
- Identify design decisions made
- Note any architectural patterns used
</actions>
</step>
<step number="3">
<name>Map Requirements to Implementation</name>
<actions>
- Verify each requirement has corresponding code
- Identify any missing functionality
- Note any extra functionality added
</actions>
</step>
<step number="4">
<name>Identify Gaps</name>
<actions>
- List unimplemented requirements
- Note incomplete features
- Identify missing tests
</actions>
</step>
</analysis_approach>
<common_requirement_patterns>
<pattern name="bug_fix">
<requirements>
- Clear description of the bug
- Steps to reproduce
- Expected vs actual behavior
- Affected versions/environments
</requirements>
</pattern>
<pattern name="new_feature">
<requirements>
- Feature description
- User stories or use cases
- API design (if applicable)
- UI/UX specifications
- Performance requirements
</requirements>
</pattern>
<pattern name="refactoring">
<requirements>
- Motivation for refactoring
- Backward compatibility needs
- Performance improvements expected
- Migration path (if breaking)
</requirements>
</pattern>
</common_requirement_patterns>
</requirements_analysis_guidelines>

View file

@ -1,99 +0,0 @@
<self_contained_workflow>
<overview>
The PR Fixer Orchestrator must be completely self-contained and able
to work on any PR without requiring pre-existing context files from
other workflows like the Issue Fixer.
</overview>
<independence_principles>
<principle>
<name>No External Dependencies</name>
<description>Never assume files from other workflows exist</description>
<implementation>
- Create own temp directory structure
- Gather all needed context independently
- Generate own analysis and plans
</implementation>
</principle>
<principle>
<name>Complete Context Gathering</name>
<description>Collect all information needed for the task</description>
<implementation>
- Fetch PR details and metadata
- Get linked issues if they exist
- Analyze codebase independently
- Understand requirements from available sources
</implementation>
</principle>
<principle>
<name>Flexible Requirements Analysis</name>
<description>Work with whatever information is available</description>
<implementation>
- Use linked issues when available
- Fall back to PR description
- Infer from code changes if needed
- Ask user for clarification when necessary
</implementation>
</principle>
</independence_principles>
<context_initialization>
<step>Create dedicated task directory</step>
<step>Fetch all PR-related information</step>
<step>Check for linked issues and fetch if present</step>
<step>Analyze PR changes to understand scope</step>
<step>Build complete context from available sources</step>
</context_initialization>
<handling_different_pr_types>
<type name="pr_with_linked_issue">
<description>PR that references a GitHub issue</description>
<approach>
- Fetch issue details for requirements
- Use issue acceptance criteria
- Cross-reference PR implementation with issue requirements
</approach>
</type>
<type name="standalone_pr">
<description>PR without linked issue</description>
<approach>
- Extract requirements from PR description
- Analyze code to understand intent
- Use PR comments for additional context
- Infer acceptance criteria from tests
</approach>
</type>
<type name="fork_pr">
<description>PR from a forked repository</description>
<approach>
- Handle remote configuration properly
- Ensure push targets correct repository
- Manage permissions appropriately
</approach>
</type>
</handling_different_pr_types>
<fallback_strategies>
<strategy name="missing_requirements">
<when>No clear requirements found</when>
<action>
- Analyze code changes to infer purpose
- Look at test changes for expected behavior
- Ask user for clarification if needed
</action>
</strategy>
<strategy name="unclear_scope">
<when>PR scope is ambiguous</when>
<action>
- Present findings to user
- Ask for specific guidance on what to fix
- Proceed with user-defined scope
</action>
</strategy>
</fallback_strategies>
</self_contained_workflow>

View file

@ -1,361 +0,0 @@
<pr_template_format>
<overview>
This file defines the EXACT PR message template that must be used when updating
pull requests. The format is specific to the Roo Code project and must be followed
precisely.
</overview>
<template>
<![CDATA[
<!--
Thank you for contributing to Roo Code!
Before submitting your PR, please ensure:
- It's linked to an approved GitHub Issue.
- You've reviewed our [Contributing Guidelines](../CONTRIBUTING.md).
-->
### Related GitHub Issue
<!-- Every PR MUST be linked to an approved issue. -->
Closes: #[ISSUE_NUMBER] <!-- Replace with the issue number, e.g., Closes: #123 -->
### Roo Code Task Context (Optional)
<!--
If you used Roo Code to help create this PR, you can share public task links here.
This helps reviewers understand your development process and provides additional context.
Example: https://app.roocode.com/share/task-id
-->
[TASK_CONTEXT_IF_APPLICABLE]
### Description
<!--
Briefly summarize the changes in this PR and how they address the linked issue.
The issue should cover the "what" and "why"; this section should focus on:
- The "how": key implementation details, design choices, or trade-offs made.
- Anything specific reviewers should pay attention to in this PR.
-->
[DESCRIPTION_OF_CHANGES]
### Test Procedure
<!--
Detail the steps to test your changes. This helps reviewers verify your work.
- How did you test this specific implementation? (e.g., unit tests, manual testing steps)
- How can reviewers reproduce your tests or verify the fix/feature?
- Include relevant testing environment details if applicable.
-->
[TEST_PROCEDURE_DETAILS]
### Pre-Submission Checklist
<!-- Go through this checklist before marking your PR as ready for review. -->
- [x] **Issue Linked**: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
- [x] **Scope**: My changes are focused on the linked issue (one major feature/fix per PR).
- [x] **Self-Review**: I have performed a thorough self-review of my code.
- [x] **Testing**: New and/or updated tests have been added to cover my changes (if applicable).
- [x] **Documentation Impact**: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
- [x] **Contribution Guidelines**: I have read and agree to the [Contributor Guidelines](/CONTRIBUTING.md).
### Screenshots / Videos
<!--
For UI changes, please provide before-and-after screenshots or a short video of the *actual results*.
This greatly helps in understanding the visual impact of your changes.
-->
[SCREENSHOTS_OR_VIDEOS_IF_UI_CHANGES]
### Documentation Updates
<!--
Does this PR necessitate updates to user-facing documentation?
- [ ] No documentation updates are required.
- [ ] Yes, documentation updates are required. (Please describe what needs to be updated or link to a PR in the docs repository).
-->
[DOCUMENTATION_UPDATE_STATUS]
### Additional Notes
<!-- Add any other context, questions, or information for reviewers here. -->
[ADDITIONAL_NOTES]
### Get in Touch
<!--
Please provide your Discord username for reviewers or maintainers to reach you if they have questions about your PR
-->
[DISCORD_USERNAME]
]]>
</template>
<placeholders>
<placeholder name="[ISSUE_NUMBER]">
<description>The GitHub issue number this PR closes</description>
<source>From linked_issues.json or pr_context.json</source>
</placeholder>
<placeholder name="[TASK_CONTEXT_IF_APPLICABLE]">
<description>Optional Roo Code task links if used</description>
<default>_No Roo Code task context for this PR._</default>
</placeholder>
<placeholder name="[DESCRIPTION_OF_CHANGES]">
<description>Summary of changes and implementation details</description>
<content>
This PR addresses the review feedback and fixes identified issues for #[PR_NUMBER].
**Key Changes:**
- [List major changes from changes_implemented.md]
- [Implementation details and design choices]
- [Trade-offs or decisions made]
**Review Comments Addressed:**
[Summary of addressed review comments]
**Test Failures Fixed:**
[Summary of test fixes if applicable]
**Conflicts Resolved:**
[Summary of conflict resolutions if applicable]
</content>
</placeholder>
<placeholder name="[TEST_PROCEDURE_DETAILS]">
<description>How the changes were tested</description>
<content>
**Testing performed:**
1. Ran all unit tests locally: `[test command used]`
2. Ran integration tests: `[test command used]`
3. Manual testing steps:
- [Step 1]
- [Step 2]
- [Step 3]
**To verify these changes:**
1. Check out this branch
2. Run `[specific test commands]`
3. [Additional verification steps]
**Test Environment:**
- Node.js version: [version]
- OS: [operating system]
- [Other relevant environment details]
</content>
</placeholder>
<placeholder name="[SCREENSHOTS_OR_VIDEOS_IF_UI_CHANGES]">
<description>Visual evidence of UI changes</description>
<default>_No UI changes in this PR._</default>
</placeholder>
<placeholder name="[DOCUMENTATION_UPDATE_STATUS]">
<description>Documentation impact assessment</description>
<options>
<option condition="no_docs_needed">- [x] No documentation updates are required.</option>
<option condition="docs_needed">- [x] Yes, documentation updates are required. [Describe what needs updating]</option>
</options>
</placeholder>
<placeholder name="[ADDITIONAL_NOTES]">
<description>Any additional context for reviewers</description>
<content>
[Any special considerations, known issues, or questions for reviewers]
**Files Modified:**
```
[List of modified files from changes_implemented.md]
```
</content>
</placeholder>
<placeholder name="[DISCORD_USERNAME]">
<description>Contact information</description>
<default>Discord: @[username]</default>
</placeholder>
</placeholders>
<generation_instructions>
<instruction priority="1">
The template MUST be followed exactly - do not modify the structure or remove any sections
</instruction>
<instruction priority="2">
All placeholders must be replaced with actual content - no brackets should remain
</instruction>
<instruction priority="3">
The Pre-Submission Checklist items should all be marked as checked [x] since we're fixing an existing PR
</instruction>
<instruction priority="4">
Pull information from:
- changes_implemented.md for the description and file list
- validation_report.md for test results
- pr_context.json for issue numbers and PR details
- translation_summary.md for any translation updates
</instruction>
<instruction priority="5">
Keep the HTML comments intact - they provide guidance for reviewers
</instruction>
</generation_instructions>
<file_handling>
<location>.roo/temp/pr-fixer-orchestrator/[TASK_ID]/pr_update_message.md</location>
<purpose>
- Used as the PR comment body when updating the PR
- Saved for reference and audit trail
- Can be edited by user before posting
- Should NOT be deleted even if temp files are cleaned
</purpose>
<usage>
Post to PR using: gh pr comment [pr_number] --repo [owner]/[repo] --body-file [path_to_file]
</usage>
</file_handling>
<example_filled_template>
<![CDATA[
<!--
Thank you for contributing to Roo Code!
Before submitting your PR, please ensure:
- It's linked to an approved GitHub Issue.
- You've reviewed our [Contributing Guidelines](../CONTRIBUTING.md).
-->
### Related GitHub Issue
<!-- Every PR MUST be linked to an approved issue. -->
Closes: #456
### Roo Code Task Context (Optional)
<!--
If you used Roo Code to help create this PR, you can share public task links here.
This helps reviewers understand your development process and provides additional context.
Example: https://app.roocode.com/share/task-id
-->
_No Roo Code task context for this PR._
### Description
<!--
Briefly summarize the changes in this PR and how they address the linked issue.
The issue should cover the "what" and "why"; this section should focus on:
- The "how": key implementation details, design choices, or trade-offs made.
- Anything specific reviewers should pay attention to in this PR.
-->
This PR addresses the review feedback and fixes identified issues for #789.
**Key Changes:**
- Fixed TypeScript type errors in the API handler by adding proper type annotations
- Improved error handling in the authentication flow to handle edge cases
- Refactored complex functions for better testability and maintainability
- Added missing user role management functionality
- Resolved merge conflicts with the latest main branch
**Review Comments Addressed:**
- Added timeout handling with exponential backoff for network requests
- Refactored large functions into smaller, testable units
- Added comprehensive TypeScript interfaces for API responses
- Improved error messages for better debugging
**Test Failures Fixed:**
- Updated email validation tests to match new validation rules
- Fixed mock server responses in integration tests
- Added missing test coverage for new functionality
### Test Procedure
<!--
Detail the steps to test your changes. This helps reviewers verify your work.
- How did you test this specific implementation? (e.g., unit tests, manual testing steps)
- How can reviewers reproduce your tests or verify the fix/feature?
- Include relevant testing environment details if applicable.
-->
**Testing performed:**
1. Ran all unit tests locally: `npm test`
2. Ran integration tests: `npm run test:integration`
3. Manual testing steps:
- Created new user with various role types
- Tested authentication flow with invalid credentials
- Verified timeout handling with slow network simulation
**To verify these changes:**
1. Check out this branch
2. Run `npm install && npm test`
3. Start the dev server with `npm run dev`
4. Test the authentication flow at http://localhost:3000/login
**Test Environment:**
- Node.js version: 18.17.0
- OS: Windows 11
- Browser: Chrome 120
### Pre-Submission Checklist
<!-- Go through this checklist before marking your PR as ready for review. -->
- [x] **Issue Linked**: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
- [x] **Scope**: My changes are focused on the linked issue (one major feature/fix per PR).
- [x] **Self-Review**: I have performed a thorough self-review of my code.
- [x] **Testing**: New and/or updated tests have been added to cover my changes (if applicable).
- [x] **Documentation Impact**: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
- [x] **Contribution Guidelines**: I have read and agree to the [Contributor Guidelines](/CONTRIBUTING.md).
### Screenshots / Videos
<!--
For UI changes, please provide before-and-after screenshots or a short video of the *actual results*.
This greatly helps in understanding the visual impact of your changes.
-->
_No UI changes in this PR._
### Documentation Updates
<!--
Does this PR necessitate updates to user-facing documentation?
- [ ] No documentation updates are required.
- [ ] Yes, documentation updates are required. (Please describe what needs to be updated or link to a PR in the docs repository).
-->
- [x] No documentation updates are required.
### Additional Notes
<!-- Add any other context, questions, or information for reviewers here. -->
All review feedback has been addressed. The main architectural change was refactoring the authentication service to use dependency injection, which improves testability.
**Files Modified:**
```
src/api/handler.ts - Added type annotations, improved error handling
src/services/auth.service.ts - Refactored for dependency injection
src/services/user.service.ts - Added role management functionality
src/types/api.types.ts - New TypeScript interfaces
src/__tests__/services/auth.service.test.ts - Updated tests
src/__tests__/integration/api.test.ts - Fixed mock responses
```
### Get in Touch
<!--
Please provide your Discord username for reviewers or maintainers to reach you if they have questions about your PR
-->
Discord: @contributor123
]]>
</example_filled_template>
</pr_template_format>

View file

@ -1,6 +1,6 @@
<workflow_instructions>
<mode_overview>
This mode is designed to help resolve issues in existing pull requests. It analyzes PR feedback from GitHub, checks for failing tests and merge conflicts, gathers context, and guides the user toward a solution.
This mode is designed to help resolve issues in existing pull requests. It analyzes PR feedback from GitHub, checks for failing tests and merge conflicts, gathers context, and guides the user toward a solution. All GitHub operations are performed using the GitHub CLI.
</mode_overview>
<initialization_steps>
@ -13,9 +13,9 @@
<step number="2">
<action>Gather PR context</action>
<tools>
<tool>use_mcp_tool (github): get_pull_request, get_pull_request_comments</tool>
<tool>gh cli: Check workflow status and logs for failing tests.</tool>
<tool>gh cli: Check for merge conflicts.</tool>
<tool>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles,comments,reviews</tool>
<tool>gh pr checks [PR_NUMBER] --repo [owner]/[repo] - Check workflow status for failing tests</tool>
<tool>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json mergeable,mergeStateStatus - Check for merge conflicts</tool>
</tools>
</step>
</initialization_steps>
@ -24,9 +24,9 @@
<phase name="analysis">
<description>Analyze the gathered information to identify the core problems.</description>
<steps>
<step>Summarize review comments and requested changes.</step>
<step>Identify the root cause of failing tests by analyzing logs.</step>
<step>Determine if merge conflicts exist.</step>
<step>Summarize review comments and requested changes from gh pr view output.</step>
<step>Identify the root cause of failing tests by analyzing workflow logs with 'gh run view'.</step>
<step>Determine if merge conflicts exist from mergeable status.</step>
</steps>
</phase>
@ -41,13 +41,16 @@
<phase name="implementation">
<description>Execute the user's chosen course of action.</description>
<steps>
<step>Check out the PR branch locally using 'gh pr checkout --force'.</step>
<step>Determine if the PR is from a fork by checking 'gh pr view --json isCrossRepository'.</step>
<step>Check out the PR branch locally using 'gh pr checkout [PR_NUMBER] --repo [owner]/[repo] --force'.</step>
<step>Determine if the PR is from a fork by checking 'gh pr view [PR_NUMBER] --repo [owner]/[repo] --json isCrossRepository'.</step>
<step>Apply code changes based on review feedback using file editing tools.</step>
<step>Fix failing tests by modifying test files or source code as needed.</step>
<step>For conflict resolution: Use GIT_EDITOR=true for non-interactive rebases, then resolve conflicts via file editing.</step>
<step>If changes affect user-facing content (i18n files, UI components, announcements), delegate translation updates using the new_task tool with translate mode.</step>
<step>Commit changes using git commands.</step>
<step>Review modified files with 'git status --porcelain' to ensure no temporary files are included.</step>
<step>Stage files selectively using 'git add -u' (for modified tracked files) or 'git add <specific-files>' (for new files).</step>
<step>Verify staged files with 'git diff --cached --name-only' before committing.</step>
<step>Commit changes using git commands with descriptive messages.</step>
<step>Push changes to the correct remote (origin for same-repo PRs, fork remote for cross-repo PRs) using 'git push --force-with-lease'.</step>
</steps>
</phase>
@ -55,10 +58,10 @@
<phase name="validation">
<description>Verify that the pushed changes resolve the issues.</description>
<steps>
<step>Use 'gh pr checks --watch' to monitor check status in real-time until all checks complete.</step>
<step>If needed, check specific workflow runs with 'gh run list --pr' for detailed CI/CD pipeline status.</step>
<step>Use 'gh pr checks [PR_NUMBER] --repo [owner]/[repo] --watch' to monitor check status in real-time until all checks complete.</step>
<step>If needed, check specific workflow runs with 'gh run list --pr [PR_NUMBER] --repo [owner]/[repo]' for detailed CI/CD pipeline status.</step>
<step>Verify that all translation updates (if any) have been completed and committed.</step>
<step>Confirm PR is ready for review by checking mergeable state with 'gh pr view --json'.</step>
<step>Confirm PR is ready for review by checking mergeable state with 'gh pr view [PR_NUMBER] --repo [owner]/[repo] --json mergeable,mergeStateStatus'.</step>
</steps>
</phase>
</main_workflow>

View file

@ -20,6 +20,23 @@
<bad>Always push to origin without checking PR source</bad>
</example>
</principle>
<principle priority="high">
<name>Safe File Staging</name>
<description>Always review files before staging to avoid committing temporary files, build artifacts, or system files. Use selective git commands that respect .gitignore.</description>
<rationale>Committing unwanted files can expose sensitive data, clutter the repository, and cause CI/CD failures.</rationale>
<example>
<scenario>Staging files for commit</scenario>
<good>Use 'git add -u' to stage only modified tracked files, or explicitly list files to add</good>
<bad>Use 'git add .' which stages everything including temp files</bad>
</example>
<checklist>
<item>Review git status before staging</item>
<item>Check for temporary files (.swp, .DS_Store, *.tmp)</item>
<item>Exclude build artifacts (dist/, build/, *.pyc)</item>
<item>Avoid IDE-specific files (.idea/, .vscode/)</item>
<item>Verify .gitignore is properly configured</item>
</checklist>
</principle>
</general_principles>
<code_conventions>

View file

@ -101,10 +101,42 @@
</pattern>
<pattern name="automated_commit_operations">
<usage>Commit operations that work in automated environments.</usage>
<usage>Commit operations that work in automated environments while respecting .gitignore.</usage>
<template>
<command tool="git">git add .</command>
<comment>Review what files have been modified</comment>
<command tool="git">git status --porcelain</command>
<comment>Add only tracked files that were modified (respects .gitignore)</comment>
<command tool="git">git add -u</command>
<comment>If you need to add specific new files, list them explicitly</comment>
<command tool="git">git add <specific_file_path></command>
<command tool="git">git commit -m "<commit_message>"</command>
</template>
</pattern>
<pattern name="safe_file_staging">
<usage>Safely stage files for commit while avoiding temporary files and respecting .gitignore.</usage>
<template>
<comment>First, check what files are currently modified or untracked</comment>
<command tool="git">git status --porcelain</command>
<comment>Review the output to identify files that should NOT be committed:</comment>
<comment>- Files starting with . (hidden files like .DS_Store, .swp)</comment>
<comment>- Build artifacts (dist/, build/, *.pyc, *.o)</comment>
<comment>- IDE files (.idea/, .vscode/, *.iml)</comment>
<comment>- Temporary files (*.tmp, *.temp, *~)</comment>
<comment>Option 1: Stage only modified tracked files (safest)</comment>
<command tool="git">git add -u</command>
<comment>Option 2: Stage specific files by path</comment>
<command tool="git">git add src/file1.ts src/file2.ts</command>
<comment>Option 3: Use pathspec to add files matching a pattern</comment>
<command tool="git">git add '*.ts' '*.tsx' --</command>
<comment>Option 4: Interactive staging to review each change</comment>
<command tool="git">git add -p</command>
<comment>Always verify what's staged before committing</comment>
<command tool="git">git diff --cached --name-only</command>
</template>
</pattern>
</common_patterns>

View file

@ -1,7 +1,7 @@
<tool_usage_guide>
<tool_priorities>
<priority level="1">
<tool>use_mcp_tool (server: github)</tool>
<tool>gh pr view</tool>
<when>Use at the start to get all review comments and PR metadata.</when>
<why>Provides the core context of what needs to be fixed from a human perspective.</why>
</priority>
@ -23,14 +23,16 @@
</tool_priorities>
<tool_specific_guidance>
<tool name="use_mcp_tool (github: get_pull_request)">
<tool name="gh pr view">
<best_practices>
<practice>Always fetch details to get the branch name, owner, repo slug, and mergeable state.</practice>
<practice>Always fetch details with --json to get structured data: gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles,comments,reviews,mergeable,mergeStateStatus,isCrossRepository</practice>
<practice>Parse the JSON output to extract branch name, owner, repo slug, and mergeable state.</practice>
</best_practices>
</tool>
<tool name="use_mcp_tool (github: get_pull_request_comments)">
<tool name="gh pr comments">
<best_practices>
<practice>Use gh pr view --json comments to get all comments in structured format.</practice>
<practice>Parse all comments to create a checklist of required changes.</practice>
<practice>Ignore comments that are not actionable or have been resolved.</practice>
</best_practices>
@ -40,14 +42,15 @@
<best_practices>
<practice>Use this command to get the exact error messages from failing tests.</practice>
<practice>Search the log for keywords like 'error', 'failed', or 'exception' to quickly find the root cause.</practice>
<practice>Always specify run ID explicitly to avoid interactive selection prompts.</practice>
<practice>Always specify run ID explicitly to avoid interactive selection prompts: gh run view [RUN_ID] --log-failed</practice>
<practice>Get run IDs with: gh run list --pr [PR_NUMBER] --repo [owner]/[repo]</practice>
</best_practices>
</tool>
<tool name="gh pr checkout">
<best_practices>
<practice>Use --force flag: 'gh pr checkout <pr_number> --force'</practice>
<practice>If gh checkout fails, use: git fetch origin pull/<pr_number>/head:<branch_name></practice>
<practice>Use --force flag: 'gh pr checkout [PR_NUMBER] --repo [owner]/[repo] --force'</practice>
<practice>If gh checkout fails, use: git fetch origin pull/[PR_NUMBER]/head:[branch_name]</practice>
</best_practices>
</tool>
@ -58,9 +61,9 @@
<practice>Always determine the correct remote before pushing (origin vs fork).</practice>
</best_practices>
<remote_handling>
<step>Check if PR is from a fork: 'gh pr view <pr_number> --json isCrossRepository'</step>
<step>Check if PR is from a fork: 'gh pr view [PR_NUMBER] --repo [owner]/[repo] --json isCrossRepository'</step>
<step>If isCrossRepository is true, add fork remote if needed</step>
<step>Push to appropriate remote: 'git push --force-with-lease <remote> <branch>'</step>
<step>Push to appropriate remote: 'git push --force-with-lease [remote] [branch]'</step>
</remote_handling>
<conflict_resolution>
<step>Use 'GIT_EDITOR=true git rebase main' to start rebase</step>
@ -71,10 +74,10 @@
<tool name="gh pr checks">
<best_practices>
<practice>Use --watch flag to monitor checks in real-time: 'gh pr checks <pr_number> --watch'</practice>
<practice>For one-time status checks, use --json flag: 'gh pr checks <pr_number> --json state,conclusion,name'</practice>
<practice>Use --watch flag to monitor checks in real-time: 'gh pr checks [PR_NUMBER] --repo [owner]/[repo] --watch'</practice>
<practice>For one-time status checks, use --json flag: 'gh pr checks [PR_NUMBER] --repo [owner]/[repo] --json state,conclusion,name'</practice>
<practice>The --watch flag automatically updates the display as check statuses change.</practice>
<practice>Use 'gh run list --pr <pr_number>' to get detailed workflow status if needed.</practice>
<practice>Use 'gh run list --pr [PR_NUMBER] --repo [owner]/[repo]' to get detailed workflow status if needed.</practice>
</best_practices>
</tool>
@ -115,4 +118,19 @@ Please ensure all supported languages (ca, de, es, fr, hi, id, it, ja, ko, nl, p
]]></example_usage>
</tool>
</tool_specific_guidance>
<github_cli_reference>
<command_group name="pr_operations">
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json [fields]</command>
<command>gh pr checkout [PR_NUMBER] --repo [owner]/[repo] --force</command>
<command>gh pr checks [PR_NUMBER] --repo [owner]/[repo] [--watch|--json]</command>
<command>gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body "[text]"</command>
</command_group>
<command_group name="workflow_operations">
<command>gh run list --pr [PR_NUMBER] --repo [owner]/[repo]</command>
<command>gh run view [RUN_ID] --repo [owner]/[repo] --log-failed</command>
<command>gh workflow view [WORKFLOW_NAME] --repo [owner]/[repo]</command>
</command_group>
</github_cli_reference>
</tool_usage_guide>

View file

@ -12,28 +12,9 @@
<step number="1">
<description>Get PR details and review comments.</description>
<tool_use>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_pull_request</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"pullNumber": 4365
}
</arguments>
</use_mcp_tool>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_pull_request_comments</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"pullNumber": 4365
}
</arguments>
</use_mcp_tool>
<execute_command>
<command>gh pr view 4365 --repo RooCodeInc/Roo-Code --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles,comments,reviews,mergeable,mergeStateStatus</command>
</execute_command>
</tool_use>
<expected_outcome>Get the branch name, list of review comments, and check for mergeability.</expected_outcome>
</step>
@ -42,7 +23,7 @@
<description>Check CI status.</description>
<tool_use>
<execute_command>
<command>gh pr checks 4365</command>
<command>gh pr checks 4365 --repo RooCodeInc/Roo-Code</command>
</execute_command>
</tool_use>
<analysis>Identify which check is failing.</analysis>
@ -52,7 +33,17 @@
<description>Get logs for the failing check.</description>
<tool_use>
<execute_command>
<command>gh run view <run_id> --log-failed</command>
<command>gh run list --pr 4365 --repo RooCodeInc/Roo-Code</command>
</execute_command>
</tool_use>
<analysis>Get the run ID of the failing workflow.</analysis>
</step>
<step number="3a">
<description>View the failed logs.</description>
<tool_use>
<execute_command>
<command>gh run view [run_id] --repo RooCodeInc/Roo-Code --log-failed</command>
</execute_command>
</tool_use>
<analysis>Find the specific error message causing the test to fail.</analysis>
@ -62,7 +53,7 @@
<description>Check out the pull request branch.</description>
<tool_use>
<execute_command>
<command>gh pr checkout 4365 --force</command>
<command>gh pr checkout 4365 --repo RooCodeInc/Roo-Code --force</command>
</execute_command>
</tool_use>
<analysis>The PR branch is now ready for local edits.</analysis>
@ -85,7 +76,7 @@
<description>After pushing the changes, monitor PR checks in real-time.</description>
<tool_use>
<execute_command>
<command>gh pr checks 4365 --watch</command>
<command>gh pr checks 4365 --repo RooCodeInc/Roo-Code --watch</command>
</execute_command>
</tool_use>
<analysis>Monitor checks continuously until all complete. The --watch flag provides real-time updates as check statuses change.</analysis>
@ -94,7 +85,8 @@
<key_takeaways>
<takeaway>Always gather all information before proposing a solution.</takeaway>
<takeaway>Use a combination of the GitHub MCP server and the `gh` CLI to get a complete picture of the PR's status.</takeaway>
<takeaway>Use the GitHub CLI to get a complete picture of the PR's status.</takeaway>
<takeaway>The --watch flag on gh pr checks provides real-time monitoring of CI status.</takeaway>
</key_takeaways>
</example>
@ -111,17 +103,9 @@
<step number="1">
<description>Get PR details and analyze changes.</description>
<tool_use>
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_pull_request</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"pullNumber": 1234
}
</arguments>
</use_mcp_tool>
<execute_command>
<command>gh pr view 1234 --repo RooCodeInc/Roo-Code --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles,comments,reviews</command>
</execute_command>
</tool_use>
<expected_outcome>Identify the files changed and review feedback requiring UI text modifications.</expected_outcome>
</step>
@ -130,7 +114,7 @@
<description>Check out PR and apply the requested changes.</description>
<tool_use>
<execute_command>
<command>gh pr checkout 1234 --force</command>
<command>gh pr checkout 1234 --repo RooCodeInc/Roo-Code --force</command>
</execute_command>
</tool_use>
<analysis>Make the requested button text changes in the UI components.</analysis>
@ -158,20 +142,30 @@ Ensure consistency across all language files and maintain the same context and t
</step>
<step number="4">
<description>Commit all changes including translations with automated git configuration.</description>
<description>Review and commit changes including translations.</description>
<tool_use>
<execute_command>
<command>git add . && git commit -m "fix: update button text and translations as requested in review"</command>
<command>git status --porcelain</command>
</execute_command>
</tool_use>
<analysis>All code changes and translation updates are now committed.</analysis>
<analysis>Review the list of modified files to ensure only intended changes are present.</analysis>
</step>
<step number="4a">
<description>Stage only the intended files for commit.</description>
<tool_use>
<execute_command>
<command>git add -u && git commit -m "fix: update button text and translations as requested in review"</command>
</execute_command>
</tool_use>
<analysis>Using 'git add -u' stages only modified tracked files, avoiding any temporary files.</analysis>
</step>
<step number="5">
<description>Check if PR is from a fork and push to correct remote.</description>
<tool_use>
<execute_command>
<command>gh pr view 1234 --json isCrossRepository,headRepositoryOwner,headRefName</command>
<command>gh pr view 1234 --repo RooCodeInc/Roo-Code --json isCrossRepository,headRepositoryOwner,headRefName</command>
</execute_command>
</tool_use>
<analysis>Determine if this is a cross-repository PR to know which remote to push to.</analysis>
@ -181,7 +175,7 @@ Ensure consistency across all language files and maintain the same context and t
<description>Push changes to the appropriate remote.</description>
<tool_use>
<execute_command>
<command>git push --force-with-lease origin <branch_name></command>
<command>git push --force-with-lease origin [branch_name]</command>
</execute_command>
</tool_use>
<analysis>Push changes safely to update the pull request. Use 'fork' remote instead if PR is from a fork.</analysis>
@ -191,7 +185,7 @@ Ensure consistency across all language files and maintain the same context and t
<description>Monitor CI status in real-time.</description>
<tool_use>
<execute_command>
<command>gh pr checks 1234 --watch</command>
<command>gh pr checks 1234 --repo RooCodeInc/Roo-Code --watch</command>
</execute_command>
</tool_use>
<analysis>Watch CI checks continuously until all tests pass. The --watch flag provides automatic updates as check statuses change.</analysis>
@ -203,6 +197,7 @@ Ensure consistency across all language files and maintain the same context and t
<takeaway>Use new_task with translate mode to ensure consistent translation updates.</takeaway>
<takeaway>Include detailed context about what changed and why in translation requests.</takeaway>
<takeaway>Verify translation completeness before considering the PR fix complete.</takeaway>
<takeaway>Use gh pr view --json to get structured data about PR properties.</takeaway>
</key_takeaways>
</example>
</complete_examples>

View file

@ -3,7 +3,7 @@
This workflow orchestrates a comprehensive pull request review process by delegating
specialized analysis tasks to appropriate modes while maintaining context through
structured report files. The orchestrator ensures critical review coverage while
avoiding redundant feedback.
avoiding redundant feedback. All GitHub operations are performed using the GitHub CLI.
</overview>
<initialization>
@ -27,14 +27,12 @@
<step number="2">
<name>Fetch PR Details and Context</name>
<description>
Try using GitHub MCP tools first. If unavailable or failing, fall back to GitHub CLI.
Use GitHub CLI to fetch comprehensive PR details.
</description>
<mcp_approach>
Use get_pull_request tool to fetch PR details
</mcp_approach>
<cli_fallback>
<command>
gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles
</cli_fallback>
</command>
<save_to>.roo/temp/pr-[PR_NUMBER]/pr-metadata.json</save_to>
</step>
<step number="3">
@ -42,12 +40,10 @@
<description>
If PR references an issue, fetch its details for context.
</description>
<mcp_approach>
Use get_issue tool if issue is referenced
</mcp_approach>
<cli_fallback>
<command>
gh issue view [issue_number] --repo [owner]/[repo] --json number,title,body,author,state
</cli_fallback>
</command>
<save_to>.roo/temp/pr-[PR_NUMBER]/linked-issue.json</save_to>
</step>
<step number="4">
@ -55,12 +51,10 @@
<description>
CRITICAL: Get all existing feedback to avoid redundancy.
</description>
<mcp_approach>
Use get_pull_request_comments and get_pull_request_reviews
</mcp_approach>
<cli_fallback>
gh pr review [PR_NUMBER] --repo [owner]/[repo] --json comments,reviews
</cli_fallback>
<commands>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json comments --jq '.comments'</command>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json reviews --jq '.reviews'</command>
</commands>
<save_to>.roo/temp/pr-[PR_NUMBER]/existing-feedback.json</save_to>
</step>
@ -178,21 +172,26 @@
<step number="12">
<name>Post Review Comment (if approved)</name>
<description>
If user approves and not called by another mode, post review.
If user approves and not called by another mode, post review using GitHub CLI.
</description>
<mcp_approach>
Use add_issue_comment or create PR review
</mcp_approach>
<cli_fallback>
<command>
gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body-file .roo/temp/pr-[PR_NUMBER]/final-review.md
</cli_fallback>
</command>
</step>
</completion>
<error_handling>
<github_api_failures>
Always fall back to GitHub CLI commands
</github_api_failures>
<github_cli_failures>
<scenario name="authentication_failure">
<action>Inform user to run 'gh auth login' and check authentication status</action>
</scenario>
<scenario name="pr_not_found">
<action>Verify PR number and repository, ask user to confirm details</action>
</scenario>
<scenario name="rate_limit">
<action>Wait briefly and retry, inform user about rate limiting</action>
</scenario>
</github_cli_failures>
<delegation_failures>
Continue with available analysis and note limitations
</delegation_failures>

View file

@ -1,226 +1,224 @@
<github_operations>
<overview>
Guidelines for handling GitHub operations with fallback strategies
when MCP tools are unavailable or failing.
Guidelines for handling GitHub operations using the GitHub CLI (gh).
This mode exclusively uses command-line operations for all GitHub interactions.
</overview>
<mcp_vs_cli>
<principle>
Always try MCP tools first, fall back to GitHub CLI if they fail
</principle>
<benefits_of_mcp>
- Structured data responses
- Better error handling
- Integrated with the system
</benefits_of_mcp>
<benefits_of_cli>
- More reliable when MCP is down
- Direct GitHub API access
- Can handle complex queries
</benefits_of_cli>
</mcp_vs_cli>
<prerequisites>
<requirement name="github_cli">
<description>GitHub CLI must be installed and authenticated</description>
<check_command>gh auth status</check_command>
<install_url>https://cli.github.com/</install_url>
</requirement>
<requirement name="authentication">
<description>User must be authenticated with appropriate permissions</description>
<setup_command>gh auth login</setup_command>
</requirement>
</prerequisites>
<operation_patterns>
<operation name="fetch_pr_details">
<mcp_approach>
<tool>get_pull_request</tool>
<example><![CDATA[
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_pull_request</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"pullNumber": 123
}
</arguments>
</use_mcp_tool>
]]></example>
</mcp_approach>
<cli_fallback>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles</command>
<parse_json>true</parse_json>
</cli_fallback>
<description>Fetch comprehensive PR metadata</description>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles</command>
<output_format>JSON</output_format>
<save_to>.roo/temp/pr-[PR_NUMBER]/pr-metadata.json</save_to>
</operation>
<operation name="fetch_pr_diff">
<mcp_approach>
<tool>get_pull_request_diff</tool>
<example><![CDATA[
<use_mcp_tool>
<server_name>github</server_name>
<tool_name>get_pull_request_diff</tool_name>
<arguments>
{
"owner": "RooCodeInc",
"repo": "Roo-Code",
"pullNumber": 123
}
</arguments>
</use_mcp_tool>
]]></example>
</mcp_approach>
<cli_fallback>
<command>gh pr diff [PR_NUMBER] --repo [owner]/[repo]</command>
<save_to>.roo/temp/pr-[PR_NUMBER]/pr.diff</save_to>
</cli_fallback>
<description>Get the full diff of PR changes</description>
<command>gh pr diff [PR_NUMBER] --repo [owner]/[repo]</command>
<save_to>.roo/temp/pr-[PR_NUMBER]/pr.diff</save_to>
</operation>
<operation name="fetch_pr_files">
<mcp_approach>
<tool>get_pull_request_files</tool>
</mcp_approach>
<cli_fallback>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json files --jq '.files[].path'</command>
<description>Lists all files changed in the PR</description>
</cli_fallback>
<description>List all files changed in the PR</description>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json files --jq '.files[].path'</command>
<output_format>Line-separated file paths</output_format>
</operation>
<operation name="fetch_comments">
<mcp_approach>
<tool>get_pull_request_comments</tool>
</mcp_approach>
<cli_fallback>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json comments --jq '.comments'</command>
</cli_fallback>
<description>Get all comments on the PR</description>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json comments --jq '.comments'</command>
<output_format>JSON array of comments</output_format>
</operation>
<operation name="fetch_reviews">
<mcp_approach>
<tool>get_pull_request_reviews</tool>
</mcp_approach>
<cli_fallback>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json reviews --jq '.reviews'</command>
</cli_fallback>
<description>Get all reviews on the PR</description>
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json reviews --jq '.reviews'</command>
<output_format>JSON array of reviews</output_format>
</operation>
<operation name="checkout_pr">
<cli_only>
<command>gh pr checkout [PR_NUMBER] --repo [owner]/[repo]</command>
<note>No MCP equivalent - always use CLI</note>
</cli_only>
<description>Check out PR branch locally for analysis</description>
<command>gh pr checkout [PR_NUMBER] --repo [owner]/[repo]</command>
<note>This switches the current branch to the PR branch</note>
</operation>
<operation name="post_comment">
<mcp_approach>
<tool>add_issue_comment</tool>
<note>PRs use same comment system as issues</note>
</mcp_approach>
<cli_fallback>
<command>gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body-file [file_path]</command>
<alternative>gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body "[comment_text]"</alternative>
</cli_fallback>
<description>Post a comment on the PR</description>
<command>gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body-file [file_path]</command>
<alternative>gh pr comment [PR_NUMBER] --repo [owner]/[repo] --body "[comment_text]"</alternative>
</operation>
<operation name="create_review">
<mcp_approach>
<sequence>
1. create_pending_pull_request_review
2. add_pull_request_review_comment_to_pending_review (multiple times)
3. submit_pending_pull_request_review
</sequence>
</mcp_approach>
<cli_fallback>
<command>gh pr review [PR_NUMBER] --repo [owner]/[repo] --comment --body-file [review_file]</command>
</cli_fallback>
<description>Create a PR review with comments</description>
<command>gh pr review [PR_NUMBER] --repo [owner]/[repo] --comment --body-file [review_file]</command>
<options>
<option>--approve: Approve the PR</option>
<option>--request-changes: Request changes</option>
<option>--comment: Just comment without approval/rejection</option>
</options>
</operation>
<operation name="fetch_issue">
<description>Get issue details (for linked issues)</description>
<command>gh issue view [issue_number] --repo [owner]/[repo] --json number,title,body,author,state</command>
<output_format>JSON</output_format>
</operation>
</operation_patterns>
<error_handling>
<scenario name="mcp_server_unavailable">
<scenario name="authentication_failure">
<detection>
Error message contains "MCP server" or "github server not found"
Error contains "authentication" or "not logged in"
</detection>
<action>
Immediately switch to CLI commands for all operations
1. Inform user about auth issue
2. Suggest running: gh auth login
3. Check status with: gh auth status
</action>
</scenario>
<scenario name="api_rate_limit">
<detection>
Error contains "rate limit" or status code 403
Error contains "rate limit" or "API rate limit exceeded"
</detection>
<action>
1. Wait briefly (30 seconds)
2. Retry with CLI using --limit flag
3. Reduce number of API calls
</action>
</scenario>
<scenario name="authentication_failure">
<detection>
Error contains "authentication" or status code 401
</detection>
<action>
1. Inform user about auth issue
2. Suggest checking gh auth status
3. Continue with available data
1. Wait 30-60 seconds before retry
2. Inform user about rate limiting
3. Consider reducing API calls
</action>
</scenario>
<scenario name="pr_not_found">
<detection>
Error contains "not found" or status code 404
Error contains "not found" or "could not find pull request"
</detection>
<action>
1. Verify PR number and repository
2. Ask user to confirm details
3. Check if PR is from a fork
1. Verify PR number and repository format
2. Check if repository is accessible
3. Ensure correct owner/repo format
</action>
</scenario>
<scenario name="permission_denied">
<detection>
Error contains "permission denied" or "403"
</detection>
<action>
1. Check repository permissions
2. Verify authentication scope
3. May need to re-authenticate with proper scopes
</action>
</scenario>
</error_handling>
<data_handling>
<principle name="save_everything">
<description>Always save API responses to temp files</description>
<reason>Preserve data in case of failures</reason>
<description>Always save command outputs to temp files</description>
<reason>Preserve data for analysis and recovery</reason>
</principle>
<principle name="parse_json_safely">
<description>Use jq or built-in JSON parsing</description>
<description>Use jq for JSON parsing when available</description>
<example>
gh pr view --json files --jq '.files[].path'
</example>
</principle>
<principle name="handle_large_prs">
<description>For PRs with many files, process in batches</description>
<description>For PRs with many files, save outputs to files first</description>
<threshold>More than 50 files</threshold>
<approach>Save to file, then process in chunks</approach>
</principle>
<principle name="validate_json">
<description>Always validate JSON before parsing</description>
<command>jq empty < file.json || echo "Invalid JSON"</command>
</principle>
</data_handling>
<cli_command_reference>
<command_group name="pr_info">
<command>gh pr view [number] --json [fields]</command>
<fields>
<base_command>gh pr view [number]</base_command>
<options>
<option>--repo [owner]/[repo]: Specify repository</option>
<option>--json [fields]: Get JSON output</option>
<option>--jq [expression]: Parse JSON with jq</option>
</options>
<json_fields>
number, title, author, state, body, url,
headRefName, baseRefName, files, additions,
deletions, changedFiles, comments, reviews
</fields>
deletions, changedFiles, comments, reviews,
isDraft, mergeable, mergeStateStatus
</json_fields>
</command_group>
<command_group name="pr_interaction">
<command>gh pr checkout [number]</command>
<command>gh pr diff [number]</command>
<command>gh pr comment [number] --body "[text]"</command>
<command>gh pr review [number] --comment --body "[text]"</command>
<commands>
<command>gh pr checkout [number]: Check out PR locally</command>
<command>gh pr diff [number]: View PR diff</command>
<command>gh pr comment [number] --body "[text]": Add comment</command>
<command>gh pr review [number]: Create review</command>
<command>gh pr close [number]: Close PR</command>
<command>gh pr reopen [number]: Reopen PR</command>
</commands>
</command_group>
<command_group name="issue_info">
<command>gh issue view [number] --json [fields]</command>
<fields>
<base_command>gh issue view [number]</base_command>
<json_fields>
number, title, body, author, state,
labels, assignees, milestone
</fields>
labels, assignees, milestone, comments
</json_fields>
</command_group>
<command_group name="repo_info">
<commands>
<command>gh repo view --json [fields]: Get repo info</command>
<command>gh repo clone [owner]/[repo]: Clone repository</command>
</commands>
</command_group>
</cli_command_reference>
<best_practices>
<practice>Always specify --repo to avoid ambiguity</practice>
<practice>Use --json for structured data</practice>
<practice>Save command outputs to temp files</practice>
<practice>Check gh auth status before operations</practice>
<practice>Handle both personal repos and org repos</practice>
<practice>Use --json for structured data that needs parsing</practice>
<practice>Save command outputs to temp files for reliability</practice>
<practice>Check gh auth status before starting operations</practice>
<practice>Handle both personal repos and organization repos</practice>
<practice>Use meaningful file names when saving outputs</practice>
<practice>Include error handling for all commands</practice>
<practice>Document the expected format of saved files</practice>
</best_practices>
<example_workflows>
<workflow name="complete_pr_fetch">
<description>Fetch all PR data for analysis</description>
<steps>
<step>gh pr view 123 --repo owner/repo --json number,title,author,state,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles > .roo/temp/pr-123/metadata.json</step>
<step>gh pr view 123 --repo owner/repo --json comments > .roo/temp/pr-123/comments.json</step>
<step>gh pr view 123 --repo owner/repo --json reviews > .roo/temp/pr-123/reviews.json</step>
<step>gh pr diff 123 --repo owner/repo > .roo/temp/pr-123/pr.diff</step>
</steps>
</workflow>
<workflow name="post_review">
<description>Post a comprehensive review</description>
<steps>
<step>Create review content in .roo/temp/pr-123/review.md</step>
<step>gh pr review 123 --repo owner/repo --comment --body-file .roo/temp/pr-123/review.md</step>
</steps>
</workflow>
</example_workflows>
</github_operations>

View file

@ -18,6 +18,7 @@ customModes:
- Ensuring instructions are well-organized with proper XML tags
- Following established patterns from existing modes
whenToUse: Use this mode when you need to create a new custom mode.
description: Create and implement custom modes.
groups:
- read
- - edit
@ -33,6 +34,7 @@ customModes:
Your focus is on maintaining high test quality and coverage across the codebase, working primarily with: - Test files in __tests__ directories - Mock implementations in __mocks__ - Test utilities and helpers - Vitest configuration and setup
You ensure tests are: - Well-structured and maintainable - Following Vitest best practices - Properly typed with TypeScript - Providing meaningful coverage - Using appropriate mocking strategies
whenToUse: Use this mode when you need to write, modify, or maintain tests for the codebase.
description: Write, modify, and maintain tests.
groups:
- read
- browser
@ -55,6 +57,8 @@ customModes:
- slug: design-engineer
name: 🎨 Design Engineer
roleDefinition: "You are Roo, an expert Design Engineer focused on VSCode Extension development. Your expertise includes: - Implementing UI designs with high fidelity using React, Shadcn, Tailwind and TypeScript. - Ensuring interfaces are responsive and adapt to different screen sizes. - Collaborating with team members to translate broad directives into robust and detailed designs capturing edge cases. - Maintaining uniformity and consistency across the user interface."
whenToUse: Implement UI designs and ensure consistency.
description: Implement UI designs; ensure consistency.
groups:
- read
- - edit
@ -68,6 +72,8 @@ customModes:
- slug: release-engineer
name: 🚀 Release Engineer
roleDefinition: You are Roo, a release engineer specialized in automating the release process for software projects. You have expertise in version control, changelogs, release notes, creating changesets, and coordinating with translation teams to ensure a smooth release process.
whenToUse: Automate the release process for software projects.
description: Automate the release process.
customInstructions: |-
When preparing a release: 1. Identify the SHA corresponding to the most recent release using GitHub CLI: `gh release view --json tagName,targetCommitish,publishedAt ` 2. Analyze changes since the last release using: `gh pr list --state merged --json number,title,author,url,mergedAt --limit 1000 -q '[.[] | select(.mergedAt > "TIMESTAMP") | {number, title, author: .author.login, url, mergedAt}] | sort_by(.number)'` 3. Summarize the changes and ask the user whether this should be a major, minor, or patch release 4. Create a changeset in .changeset/v[version].md instead of directly modifying package.json. The format is:
``` --- "roo-cline": patch|minor|major ---
@ -86,6 +92,8 @@ customModes:
- slug: translate
name: 🌐 Translate
roleDefinition: You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.
whenToUse: Translate and manage localization files.
description: Translate and manage localization files.
groups:
- read
- command
@ -107,6 +115,7 @@ customModes:
You work with issues from any GitHub repository, transforming them into working code that addresses all requirements while maintaining code quality and consistency. You use the GitHub CLI (gh) for all GitHub operations instead of MCP tools.
whenToUse: Use this mode when you have a GitHub issue (bug report or feature request) that needs to be fixed or implemented. Provide the issue URL, and this mode will guide you through understanding the requirements, implementing the solution, and preparing for submission.
description: Fix GitHub issues and implement features.
groups:
- read
- edit
@ -118,6 +127,7 @@ customModes:
You are Roo, a GitHub issue creation specialist focused on crafting well-structured, detailed issues based on the project's issue templates. Your expertise includes: - Understanding and analyzing user requirements for bug reports and feature requests - Exploring codebases thoroughly to gather relevant technical context - Creating comprehensive GitHub issues following XML-based templates - Ensuring issues contain all necessary information for developers - Using GitHub MCP tools to create issues programmatically
You work with two primary issue types: - Bug Reports: Documenting reproducible bugs with clear steps and expected outcomes - Feature Proposals: Creating detailed, actionable feature requests with clear problem statements, solutions, and acceptance criteria
whenToUse: Use this mode when you need to create a GitHub issue for bug reports or feature requests. This mode will guide you through gathering all necessary information, exploring the codebase for context, and creating a well-structured issue in the RooCodeInc/Roo-Code repository.
description: Create well-structured GitHub issues.
groups:
- read
- command
@ -129,6 +139,8 @@ customModes:
You are Roo, an integration testing specialist focused on VSCode E2E tests with expertise in: - Writing and maintaining integration tests using Mocha and VSCode Test framework - Testing Roo Code API interactions and event-driven workflows - Creating complex multi-step task scenarios and mode switching sequences - Validating message formats, API responses, and event emission patterns - Test data generation and fixture management - Coverage analysis and test scenario identification
Your focus is on ensuring comprehensive integration test coverage for the Roo Code extension, working primarily with: - E2E test files in apps/vscode-e2e/src/suite/ - Test utilities and helpers - API type definitions in packages/types/ - Extension API testing patterns
You ensure integration tests are: - Comprehensive and cover critical user workflows - Following established Mocha TDD patterns - Using async/await with proper timeout handling - Validating both success and failure scenarios - Properly typed with TypeScript
whenToUse: Write, modify, or maintain integration tests.
description: Write and maintain integration tests.
groups:
- read
- command
@ -156,6 +168,7 @@ customModes:
You work primarily with the RooCodeInc/Roo-Code repository, creating context reports to track findings and delegating complex pattern analysis to specialized modes while maintaining overall review coordination. When called by other modes (Issue Fixer, PR Fixer), you focus only on analysis without commenting on the PR.
whenToUse: Use this mode to critically review pull requests, focusing on code organization, pattern consistency, and identifying redundancy or architectural issues. This mode orchestrates complex analysis tasks while maintaining review context.
description: Critically review pull requests.
groups:
- read
- - edit
@ -168,6 +181,7 @@ customModes:
name: 📚 Docs Extractor
roleDefinition: You are Roo, a comprehensive documentation extraction specialist focused on analyzing and documenting all technical and non-technical information about features and components within codebases.
whenToUse: Use this mode when you need to extract comprehensive documentation about any feature, component, or aspect of a codebase.
description: Extract comprehensive documentation.
groups:
- read
- - edit
@ -179,26 +193,9 @@ customModes:
name: 🛠️ PR Fixer
roleDefinition: "You are Roo, a pull request resolution specialist. Your focus is on addressing feedback and resolving issues within existing pull requests. Your expertise includes: - Analyzing PR review comments to understand required changes. - Checking CI/CD workflow statuses to identify failing tests. - Fetching and analyzing test logs to diagnose failures. - Identifying and resolving merge conflicts. - Guiding the user through the resolution process."
whenToUse: Use this mode to fix pull requests. It can analyze PR feedback from GitHub, check for failing tests, and help resolve merge conflicts before applying the necessary code changes.
description: Fix pull requests.
groups:
- read
- edit
- command
- mcp
- slug: pr-fixer-orchestrator
name: 🛠️ PR Fixer Orchestrator
roleDefinition: |-
You are an orchestrator for fixing pull requests. Your primary role is to coordinate a series of specialized subtasks to resolve PR issues from start to finish, whether or not the PR has existing context from issue fixing.
**Your Orchestration Responsibilities:** - Delegate analysis, implementation, testing, and review to specialized subtasks using the `new_task` tool. - Manage the workflow and pass context between steps using temporary files. - Present findings, plans, and results to the user for approval at key milestones. - Ensure the PR branch is properly synced with main and ready for merge.
**Your Core Expertise Includes:** - Analyzing PR feedback, failing tests, and merge conflicts. - Understanding the underlying issue or feature being implemented. - Exploring codebases to identify all affected files and dependencies. - Understanding CI/CD pipeline failures and test results. - Coordinating code fixes based on review comments. - Managing git operations including rebases and conflict resolution. - Ensuring proper testing and validation of changes. - Overseeing PR review before final submission. - Using GitHub CLI (gh) for all GitHub operations.
whenToUse: Use this mode to orchestrate the process of fixing a pull request. Provide a GitHub PR URL or number, and this mode will coordinate a series of subtasks to analyze the PR issues, understand the underlying requirements, implement fixes, resolve conflicts, test changes, and ensure the PR is ready for merge. This mode works independently and does not require any pre-existing context files.
groups: []
source: project
- slug: issue-fixer-orchestrator
name: 🔧 Issue Fixer Orchestrator
roleDefinition: |-
You are an orchestrator for fixing GitHub issues. Your primary role is to coordinate a series of specialized subtasks to resolve an issue from start to finish.
**Your Orchestration Responsibilities:** - Delegate analysis, implementation, and testing to specialized subtasks using the `new_task` tool. - Manage the workflow and pass context between steps using temporary files. - Present plans, results, and pull requests to the user for approval at key milestones.
**Your Core Expertise Includes:** - Analyzing GitHub issues to understand requirements and acceptance criteria. - Exploring codebases to identify all affected files and dependencies. - Guiding the implementation of high-quality fixes and features. - Ensuring comprehensive test coverage. - Overseeing the creation of well-documented pull requests. - Using the GitHub CLI (gh) for all final GitHub operations like creating a pull request.
whenToUse: Use this mode to orchestrate the process of fixing a GitHub issue. Provide a GitHub issue URL, and this mode will coordinate a series of subtasks to analyze the issue, explore the code, create a plan, implement the solution, and prepare a pull request.
groups: []
source: project

View file

@ -1,5 +1,26 @@
# Roo Code Changelog
## [3.23.14] - 2025-07-17
- Log api-initiated tasks to a tmp directory
## [3.23.13] - 2025-07-17
- Add the ability to "undo" enhance prompt changes
- Fix a bug where the path component of the baseURL for the LiteLLM provider contains path in it (thanks @ChuKhaLi)
- Add support for Vertex AI model name formatting when using Claude Code with Vertex AI (thanks @janaki-sasidhar)
- The list-files tool must include at least the first-level directory contents (thanks @qdaxb)
- Add a configurable limit that controls both consecutive errors and tool repetitions (thanks @MuriloFP)
- Add `.terraform/` and `.terragrunt-cache/` directories to the checkpoint exclusion patterns (thanks @MuriloFP)
- Increase Ollama API timeout values (thanks @daniel-lxs)
- Fix an issue where you need to "discard changes" before saving even though there are no settings changes
- Fix `DirectoryScanner` memory leak and improve file limit handling (thanks @daniel-lxs)
- Fix time formatting in environment (thanks @chrarnoldus)
- Prevent empty mode names from being saved (thanks @daniel-lxs)
- Improve auto-approve checkbox UX
- Improve the chat message edit / delete functionality (thanks @liwilliam2021)
- Add `commandExecutionTimeout` to `GlobalSettings`
## [3.23.12] - 2025-07-15
- Update the max-token calculation in model-params to better support Kimi K2 and others

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
"version": "1.32.0",
"version": "1.35.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",

View file

@ -0,0 +1,79 @@
import { describe, it, expect } from "vitest"
import { getApiProtocol } from "../provider-settings.js"
describe("getApiProtocol", () => {
describe("Anthropic-style providers", () => {
it("should return 'anthropic' for anthropic provider", () => {
expect(getApiProtocol("anthropic")).toBe("anthropic")
expect(getApiProtocol("anthropic", "gpt-4")).toBe("anthropic")
})
it("should return 'anthropic' for claude-code provider", () => {
expect(getApiProtocol("claude-code")).toBe("anthropic")
expect(getApiProtocol("claude-code", "some-model")).toBe("anthropic")
})
})
describe("Vertex provider with Claude models", () => {
it("should return 'anthropic' for vertex provider with claude models", () => {
expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "Claude-3-Sonnet")).toBe("anthropic")
expect(getApiProtocol("vertex", "CLAUDE-instant")).toBe("anthropic")
expect(getApiProtocol("vertex", "anthropic/claude-3-haiku")).toBe("anthropic")
})
it("should return 'openai' for vertex provider with non-claude models", () => {
expect(getApiProtocol("vertex", "gpt-4")).toBe("openai")
expect(getApiProtocol("vertex", "gemini-pro")).toBe("openai")
expect(getApiProtocol("vertex", "llama-2")).toBe("openai")
})
})
describe("Bedrock provider with Claude models", () => {
it("should return 'anthropic' for bedrock provider with claude models", () => {
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("bedrock", "Claude-3-Sonnet")).toBe("anthropic")
expect(getApiProtocol("bedrock", "CLAUDE-instant")).toBe("anthropic")
expect(getApiProtocol("bedrock", "anthropic.claude-v2")).toBe("anthropic")
})
it("should return 'openai' for bedrock provider with non-claude models", () => {
expect(getApiProtocol("bedrock", "gpt-4")).toBe("openai")
expect(getApiProtocol("bedrock", "titan-text")).toBe("openai")
expect(getApiProtocol("bedrock", "llama-2")).toBe("openai")
})
})
describe("Other providers with Claude models", () => {
it("should return 'openai' for non-vertex/bedrock providers with claude models", () => {
expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai")
expect(getApiProtocol("openai", "claude-3-sonnet")).toBe("openai")
expect(getApiProtocol("litellm", "claude-instant")).toBe("openai")
expect(getApiProtocol("ollama", "claude-model")).toBe("openai")
})
})
describe("Edge cases", () => {
it("should return 'openai' when provider is undefined", () => {
expect(getApiProtocol(undefined)).toBe("openai")
expect(getApiProtocol(undefined, "claude-3-opus")).toBe("openai")
})
it("should return 'openai' when model is undefined", () => {
expect(getApiProtocol("openai")).toBe("openai")
expect(getApiProtocol("vertex")).toBe("openai")
expect(getApiProtocol("bedrock")).toBe("openai")
})
it("should handle empty strings", () => {
expect(getApiProtocol("vertex", "")).toBe("openai")
expect(getApiProtocol("bedrock", "")).toBe("openai")
})
it("should be case-insensitive for claude detection", () => {
expect(getApiProtocol("vertex", "CLAUDE-3-OPUS")).toBe("anthropic")
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "ClAuDe-InStAnT")).toBe("anthropic")
})
})
})

View file

@ -50,6 +50,8 @@ export const globalSettingsSchema = z.object({
alwaysAllowUpdateTodoList: z.boolean().optional(),
allowedCommands: z.array(z.string()).optional(),
deniedCommands: z.array(z.string()).optional(),
commandExecutionTimeout: z.number().optional(),
preventCompletionWithOpenTodos: z.boolean().optional(),
allowedMaxRequests: z.number().nullish(),
autoCondenseContext: z.boolean().optional(),
autoCondenseContextPercent: z.number().optional(),
@ -200,6 +202,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
alwaysAllowUpdateTodoList: true,
followupAutoApproveTimeoutMs: 0,
allowedCommands: ["*"],
commandExecutionTimeout: 30_000,
preventCompletionWithOpenTodos: false,
browserToolEnabled: false,
browserViewportSize: "900x600",

View file

@ -303,7 +303,23 @@ export const getModelId = (settings: ProviderSettings): string | undefined => {
// Providers that use Anthropic-style API protocol
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code"]
// Helper function to determine API protocol for a provider
export const getApiProtocol = (provider: ProviderName | undefined): "anthropic" | "openai" => {
return provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider) ? "anthropic" : "openai"
// Helper function to determine API protocol for a provider and model
export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
// First check if the provider is an Anthropic-style provider
if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) {
return "anthropic"
}
// For vertex and bedrock providers, check if the model ID contains "claude" (case-insensitive)
if (
provider &&
(provider === "vertex" || provider === "bedrock") &&
modelId &&
modelId.toLowerCase().includes("claude")
) {
return "anthropic"
}
// Default to OpenAI protocol
return "openai"
}

View file

@ -401,6 +401,14 @@ export class CustomModesManager {
public async updateCustomMode(slug: string, config: ModeConfig): Promise<void> {
try {
// Validate the mode configuration before saving
const validationResult = modeConfigSchema.safeParse(config)
if (!validationResult.success) {
const errors = validationResult.error.errors.map((e) => e.message).join(", ")
logger.error(`Invalid mode configuration for ${slug}`, { errors: validationResult.error.errors })
throw new Error(`Invalid mode configuration: ${errors}`)
}
const isProjectMode = config.source === "project"
let targetPath: string

View file

@ -179,22 +179,12 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
// Add current time information with timezone.
const now = new Date()
const formatter = new Intl.DateTimeFormat(undefined, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: true,
})
const timeZone = formatter.resolvedOptions().timeZone
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation
const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset))
const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60))
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}`
details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})`
details += `\n\n# Current Time\nCurrent time in ISO 8601 UTC format: ${now.toISOString()}\nUser time zone: ${timeZone}, UTC${timeZoneOffsetStr}`
// Add context tokens information.
const { contextTokens, totalCost } = getApiMetrics(cline.clineMessages)

View file

@ -23,6 +23,7 @@ import {
TelemetryEventName,
TodoItem,
getApiProtocol,
getModelId,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService } from "@roo-code/cloud"
@ -1211,8 +1212,9 @@ export class Task extends EventEmitter<ClineEvents> {
// take a few seconds. For the best UX we show a placeholder api_req_started
// message with a loading spinner as this happens.
// Determine API protocol based on provider
const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider)
// Determine API protocol based on provider and model
const modelId = getModelId(this.apiConfiguration)
const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider, modelId)
await this.say(
"api_req_started",

View file

@ -1398,5 +1398,100 @@ describe("Cline", () => {
expect(task.diffStrategy).toBeUndefined()
})
})
describe("getApiProtocol", () => {
it("should determine API protocol based on provider and model", async () => {
// Test with Anthropic provider
const anthropicConfig = {
...mockApiConfig,
apiProvider: "anthropic" as const,
apiModelId: "gpt-4",
}
const anthropicTask = new Task({
provider: mockProvider,
apiConfiguration: anthropicConfig,
task: "test task",
startTask: false,
})
// Should use anthropic protocol even with non-claude model
expect(anthropicTask.apiConfiguration.apiProvider).toBe("anthropic")
// Test with OpenRouter provider and Claude model
const openrouterClaudeConfig = {
apiProvider: "openrouter" as const,
openRouterModelId: "anthropic/claude-3-opus",
}
const openrouterClaudeTask = new Task({
provider: mockProvider,
apiConfiguration: openrouterClaudeConfig,
task: "test task",
startTask: false,
})
expect(openrouterClaudeTask.apiConfiguration.apiProvider).toBe("openrouter")
// Test with OpenRouter provider and non-Claude model
const openrouterGptConfig = {
apiProvider: "openrouter" as const,
openRouterModelId: "openai/gpt-4",
}
const openrouterGptTask = new Task({
provider: mockProvider,
apiConfiguration: openrouterGptConfig,
task: "test task",
startTask: false,
})
expect(openrouterGptTask.apiConfiguration.apiProvider).toBe("openrouter")
// Test with various Claude model formats
const claudeModelFormats = [
"claude-3-opus",
"Claude-3-Sonnet",
"CLAUDE-instant",
"anthropic/claude-3-haiku",
"some-provider/claude-model",
]
for (const modelId of claudeModelFormats) {
const config = {
apiProvider: "openai" as const,
openAiModelId: modelId,
}
const task = new Task({
provider: mockProvider,
apiConfiguration: config,
task: "test task",
startTask: false,
})
// Verify the model ID contains claude (case-insensitive)
expect(modelId.toLowerCase()).toContain("claude")
}
})
it("should handle edge cases for API protocol detection", async () => {
// Test with undefined provider
const undefinedProviderConfig = {
apiModelId: "claude-3-opus",
}
const undefinedProviderTask = new Task({
provider: mockProvider,
apiConfiguration: undefinedProviderConfig,
task: "test task",
startTask: false,
})
expect(undefinedProviderTask.apiConfiguration.apiProvider).toBeUndefined()
// Test with no model ID
const noModelConfig = {
apiProvider: "openai" as const,
}
const noModelTask = new Task({
provider: mockProvider,
apiConfiguration: noModelConfig,
task: "test task",
startTask: false,
})
expect(noModelTask.apiConfiguration.apiProvider).toBe("openai")
})
})
})
})

View file

@ -0,0 +1,412 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { TodoItem } from "@roo-code/types"
import { AttemptCompletionToolUse } from "../../../shared/tools"
// Mock the formatResponse module before importing the tool
vi.mock("../../prompts/responses", () => ({
formatResponse: {
toolError: vi.fn((msg: string) => `Error: ${msg}`),
},
}))
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
getConfiguration: vi.fn(() => ({
get: vi.fn(),
})),
},
}))
// Mock Package module
vi.mock("../../../shared/package", () => ({
Package: {
name: "roo-cline",
},
}))
import { attemptCompletionTool } from "../attemptCompletionTool"
import { Task } from "../../task/Task"
import * as vscode from "vscode"
describe("attemptCompletionTool", () => {
let mockTask: Partial<Task>
let mockPushToolResult: ReturnType<typeof vi.fn>
let mockAskApproval: ReturnType<typeof vi.fn>
let mockHandleError: ReturnType<typeof vi.fn>
let mockRemoveClosingTag: ReturnType<typeof vi.fn>
let mockToolDescription: ReturnType<typeof vi.fn>
let mockAskFinishSubTaskApproval: ReturnType<typeof vi.fn>
let mockGetConfiguration: ReturnType<typeof vi.fn>
beforeEach(() => {
mockPushToolResult = vi.fn()
mockAskApproval = vi.fn()
mockHandleError = vi.fn()
mockRemoveClosingTag = vi.fn()
mockToolDescription = vi.fn()
mockAskFinishSubTaskApproval = vi.fn()
mockGetConfiguration = vi.fn(() => ({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return defaultValue // Default to false unless overridden in test
}
return defaultValue
}),
}))
// Setup vscode mock
vi.mocked(vscode.workspace.getConfiguration).mockImplementation(mockGetConfiguration)
mockTask = {
consecutiveMistakeCount: 0,
recordToolError: vi.fn(),
todoList: undefined,
}
})
describe("todo list validation", () => {
it("should allow completion when there is no todo list", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
mockTask.todoList = undefined
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should not call pushToolResult with an error for empty todo list
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should allow completion when todo list is empty", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
mockTask.todoList = []
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should allow completion when all todos are completed", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const completedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "completed" },
]
mockTask.todoList = completedTodos
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should prevent completion when there are pending todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when there are in-progress todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithInProgress: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "in_progress" },
]
mockTask.todoList = todosWithInProgress
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when there are mixed incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const mixedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
{ id: "3", content: "Third task", status: "in_progress" },
]
mockTask.todoList = mixedTodos
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should allow completion when setting is disabled even with incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Ensure the setting is disabled (default behavior)
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return false // Setting is disabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should not prevent completion when setting is disabled
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
expect(mockPushToolResult).not.toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when setting is enabled with incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Enable the setting
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should prevent completion when setting is enabled and there are incomplete todos
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should allow completion when setting is enabled but all todos are completed", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const completedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "completed" },
]
mockTask.todoList = completedTodos
// Enable the setting
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should allow completion when setting is enabled but all todos are completed
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
expect(mockPushToolResult).not.toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
})
})

View file

@ -1,4 +1,5 @@
import Anthropic from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import { TelemetryService } from "@roo-code/telemetry"
@ -14,6 +15,7 @@ import {
AskFinishSubTaskApproval,
} from "../../shared/tools"
import { formatResponse } from "../prompts/responses"
import { Package } from "../../shared/package"
export async function attemptCompletionTool(
cline: Task,
@ -28,6 +30,25 @@ export async function attemptCompletionTool(
const result: string | undefined = block.params.result
const command: string | undefined = block.params.command
// Get the setting for preventing completion with open todos from VSCode configuration
const preventCompletionWithOpenTodos = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("preventCompletionWithOpenTodos", false)
// Check if there are incomplete todos (only if the setting is enabled)
const hasIncompleteTodos = cline.todoList && cline.todoList.some((todo) => todo.status !== "completed")
if (preventCompletionWithOpenTodos && hasIncompleteTodos) {
cline.consecutiveMistakeCount++
cline.recordToolError("attempt_completion")
pushToolResult(
formatResponse.toolError(
"Cannot complete task while there are incomplete todos. Please finish all todos before attempting completion.",
),
)
return
}
try {
const lastMessage = cline.clineMessages.at(-1)

View file

@ -1163,15 +1163,10 @@ describe("ClineProvider", () => {
describe("deleteMessage", () => {
beforeEach(async () => {
// Mock window.showInformationMessage
;(vscode.window.showInformationMessage as any) = vi.fn()
await provider.resolveWebviewView(mockWebviewView)
})
test('handles "Just this message" deletion correctly', async () => {
// Mock user selecting "Just this message"
;(vscode.window.showInformationMessage as any).mockResolvedValue("confirmation.delete_just_this_message")
test("handles deletion with confirmation dialog", async () => {
// Setup mock messages
const mockMessages = [
{ ts: 1000, type: "say", say: "user_feedback" }, // User message 1
@ -1202,103 +1197,58 @@ describe("ClineProvider", () => {
historyItem: { id: "test-task-id" },
})
// Mock initClineWithHistoryItem
;(provider as any).initClineWithHistoryItem = vi.fn()
// Trigger message deletion
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
await messageHandler({ type: "deleteMessage", value: 4000 })
// Verify correct messages were kept
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([
mockMessages[0],
mockMessages[1],
mockMessages[4],
mockMessages[5],
])
// Verify that the dialog message was sent to webview
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 4000,
})
// Verify correct API messages were kept
// Simulate user confirming deletion through the dialog
await messageHandler({ type: "deleteMessageConfirm", messageTs: 4000 })
// Verify only messages before the deleted message were kept
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0], mockMessages[1]])
// Verify only API messages before the deleted message were kept
expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([
mockApiHistory[0],
mockApiHistory[1],
mockApiHistory[4],
mockApiHistory[5],
])
// Verify initClineWithHistoryItem was called
expect((provider as any).initClineWithHistoryItem).toHaveBeenCalledWith({ id: "test-task-id" })
})
test('handles "This and all subsequent messages" deletion correctly', async () => {
// Mock user selecting "This and all subsequent messages"
;(vscode.window.showInformationMessage as any).mockResolvedValue("confirmation.delete_this_and_subsequent")
// Setup mock messages
const mockMessages = [
{ ts: 1000, type: "say", say: "user_feedback" },
{ ts: 2000, type: "say", say: "text", value: 3000 }, // Message to delete
{ ts: 3000, type: "say", say: "user_feedback" },
{ ts: 4000, type: "say", say: "user_feedback" },
] as ClineMessage[]
const mockApiHistory = [
{ ts: 1000 },
{ ts: 2000 },
{ ts: 3000 },
{ ts: 4000 },
] as (Anthropic.MessageParam & {
ts?: number
})[]
// Setup Cline instance with auto-mock from the top of the file
const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
mockCline.clineMessages = mockMessages
mockCline.apiConversationHistory = mockApiHistory
await provider.addClineToStack(mockCline)
// Mock getTaskWithId
;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({
historyItem: { id: "test-task-id" },
})
// Trigger message deletion
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
await messageHandler({ type: "deleteMessage", value: 3000 })
// Verify only messages before the deleted message were kept
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]])
// Verify only API messages before the deleted message were kept
expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([mockApiHistory[0]])
})
test("handles Cancel correctly", async () => {
// Mock user selecting "Cancel"
;(vscode.window.showInformationMessage as any).mockResolvedValue("Cancel")
// Setup Cline instance with auto-mock from the top of the file
const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
mockCline.clineMessages = [{ ts: 1000 }, { ts: 2000 }] as ClineMessage[]
mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }] as (Anthropic.MessageParam & {
ts?: number
})[]
await provider.addClineToStack(mockCline)
test("handles case when no current task exists", async () => {
// Clear the cline stack
;(provider as any).clineStack = []
// Trigger message deletion
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
await messageHandler({ type: "deleteMessage", value: 2000 })
// Verify no messages were deleted
expect(mockCline.overwriteClineMessages).not.toHaveBeenCalled()
expect(mockCline.overwriteApiConversationHistory).not.toHaveBeenCalled()
// Verify no dialog was shown since there's no current cline
expect(mockPostMessage).not.toHaveBeenCalledWith(
expect.objectContaining({
type: "showDeleteMessageDialog",
}),
)
})
})
describe("editMessage", () => {
beforeEach(async () => {
// Mock window.showWarningMessage
;(vscode.window.showWarningMessage as any) = vi.fn()
await provider.resolveWebviewView(mockWebviewView)
})
test('handles "Proceed" edit correctly', async () => {
// Mock user selecting "Proceed" - need to use the localized string key
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
test("handles edit with confirmation dialog", async () => {
// Setup mock messages
const mockMessages = [
{ ts: 1000, type: "say", say: "user_feedback" }, // User message 1
@ -1346,6 +1296,20 @@ describe("ClineProvider", () => {
editedMessageContent: "Edited message content",
})
// Verify that the dialog message was sent to webview
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 4000,
text: "Edited message content",
})
// Simulate user confirming edit through the dialog
await messageHandler({
type: "editMessageConfirm",
messageTs: 4000,
text: "Edited message content",
})
// Verify correct messages were kept (only messages before the edited one)
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0], mockMessages[1]])
@ -1355,12 +1319,9 @@ describe("ClineProvider", () => {
mockApiHistory[1],
])
// Verify handleWebviewAskResponse was called with the edited content
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith(
"messageResponse",
"Edited message content",
undefined,
)
// The new flow calls webviewMessageHandler recursively with askResponse
// We need to verify the recursive call happened by checking if the handler was called again
expect((mockWebviewView.webview.onDidReceiveMessage as any).mock.calls.length).toBeGreaterThanOrEqual(1)
})
})
@ -2705,13 +2666,10 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
describe("Edit Messages with Images and Attachments", () => {
beforeEach(async () => {
;(vscode.window.showInformationMessage as any) = vi.fn()
await provider.resolveWebviewView(mockWebviewView)
})
test("handles editing messages containing images", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message" },
{
@ -2746,17 +2704,26 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited message with preserved images",
})
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith(
"messageResponse",
"Edited message with preserved images",
undefined,
)
// Verify dialog was shown
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 3000,
text: "Edited message with preserved images",
})
// Simulate confirmation
await messageHandler({
type: "editMessageConfirm",
messageTs: 3000,
text: "Edited message with preserved images",
})
// Verify messages were edited correctly - only the first message should remain
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]])
expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([{ ts: 1000 }])
})
test("handles editing messages with file attachments", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message" },
{
@ -2789,6 +2756,20 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited message with file attachment",
})
// Verify dialog was shown
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 3000,
text: "Edited message with file attachment",
})
// Simulate user confirming the edit
await messageHandler({
type: "editMessageConfirm",
messageTs: 3000,
text: "Edited message with file attachment",
})
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith(
"messageResponse",
@ -2805,8 +2786,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles network timeout during edit submission", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message", value: 2000 },
@ -2833,12 +2812,20 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
}),
).resolves.toBeUndefined()
// Verify dialog was shown
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: "Edited message",
})
// Simulate user confirming the edit
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message" })
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
})
test("handles connection drops during edit operation", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message", value: 2000 },
@ -2865,6 +2852,17 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
}),
).resolves.toBeUndefined()
// Verify dialog was shown
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: "Edited message",
})
// Simulate user confirming the edit
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message" })
// The error should be caught and shown
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Connection lost")
})
})
@ -2876,8 +2874,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles race conditions with simultaneous edits", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Message 1", value: 2000 },
@ -2912,6 +2908,22 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await Promise.all([edit1Promise, edit2Promise])
// Verify dialogs were shown for both edits
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: "Edited message 1",
})
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 4000,
text: "Edited message 2",
})
// Simulate user confirming both edits
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message 1" })
await messageHandler({ type: "editMessageConfirm", messageTs: 4000, text: "Edited message 2" })
// Both operations should complete without throwing
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
})
@ -2940,8 +2952,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles authorization failures during edit", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message", value: 2000 },
@ -2965,6 +2975,13 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited message",
})
// Simulate confirmation
await messageHandler({
type: "editMessageConfirm",
messageTs: 2000,
text: "Edited message",
})
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Unauthorized")
})
@ -3058,8 +3075,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles edit operations on deleted messages", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Existing message" },
@ -3083,17 +3098,26 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited non-existent message",
})
// Should show confirmation dialog but not perform any operations
expect(vscode.window.showWarningMessage).toHaveBeenCalled()
// Should show edit dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 5000,
text: "Edited non-existent message",
})
// Simulate user confirming the edit
await messageHandler({
type: "editMessageConfirm",
messageTs: 5000,
text: "Edited non-existent message",
})
// Should not perform any operations since message doesn't exist
expect(mockCline.overwriteClineMessages).not.toHaveBeenCalled()
expect(mockCline.handleWebviewAskResponse).not.toHaveBeenCalled()
})
test("handles delete operations on non-existent messages", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue(
"confirmation.delete_just_this_message",
)
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Existing message" },
@ -3115,8 +3139,16 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
value: 5000,
})
// Should show confirmation dialog but not perform any operations
expect(vscode.window.showInformationMessage).toHaveBeenCalled()
// Should show delete dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 5000,
})
// Simulate user confirming the delete
await messageHandler({ type: "deleteMessageConfirm", messageTs: 5000 })
// Should not perform any operations since message doesn't exist
expect(mockCline.overwriteClineMessages).not.toHaveBeenCalled()
})
})
@ -3128,8 +3160,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("validates proper cleanup during failed edit operations", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Original message", value: 2000 },
@ -3159,16 +3189,22 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited message",
})
// Should show edit dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: "Edited message",
})
// Simulate user confirming the edit
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message" })
// Verify cleanup was attempted before failure
expect(cleanupSpy).toHaveBeenCalled()
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Operation failed")
})
test("validates proper cleanup during failed delete operations", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue(
"confirmation.delete_just_this_message",
)
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Message to delete" },
@ -3193,6 +3229,15 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await messageHandler({ type: "deleteMessage", value: 2000 })
// Should show delete dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 2000,
})
// Simulate user confirming the delete
await messageHandler({ type: "deleteMessageConfirm", messageTs: 2000 })
// Verify cleanup was attempted before failure
expect(cleanupSpy).toHaveBeenCalled()
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
@ -3208,8 +3253,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles editing messages with large text content", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
// Create a large message (10KB of text)
const largeText = "A".repeat(10000)
const mockMessages = [
@ -3238,6 +3281,16 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: largeEditedContent,
})
// Should show edit dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: largeEditedContent,
})
// Simulate user confirming the edit
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: largeEditedContent })
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith(
"messageResponse",
@ -3247,10 +3300,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles deleting messages with large payloads", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue(
"confirmation.delete_this_and_subsequent",
)
// Create messages with large payloads
const largeText = "X".repeat(50000)
const mockMessages = [
@ -3275,6 +3324,15 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await messageHandler({ type: "deleteMessage", value: 3000 })
// Should show delete dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 3000,
})
// Simulate user confirming the delete
await messageHandler({ type: "deleteMessageConfirm", messageTs: 3000 })
// Should handle large payloads without issues
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]])
expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([{ ts: 1000 }])
@ -3285,10 +3343,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
// Note: Error messaging test removed as the implementation may not have proper error handling in place
test("provides user feedback for successful operations", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue(
"confirmation.delete_just_this_message",
)
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Message to delete" },
@ -3308,6 +3362,15 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await messageHandler({ type: "deleteMessage", value: 2000 })
// Should show delete dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 2000,
})
// Simulate user confirming the delete
await messageHandler({ type: "deleteMessageConfirm", messageTs: 2000 })
// Verify successful operation completed
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
expect(provider.initClineWithHistoryItem).toHaveBeenCalled()
@ -3315,8 +3378,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles user cancellation gracefully", async () => {
// Mock user canceling the operation
;(vscode.window.showWarningMessage as any).mockResolvedValue(undefined)
// Test cancellation by not sending confirmation
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
@ -3353,10 +3415,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
})
test("handles messages with identical timestamps", async () => {
;(vscode.window.showInformationMessage as any).mockResolvedValue(
"confirmation.delete_just_this_message",
)
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
{ ts: 1000, type: "say", say: "user_feedback", text: "Message 1" },
@ -3377,13 +3435,20 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await messageHandler({ type: "deleteMessage", value: 1000 })
// Should show delete dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 1000,
})
// Simulate user confirming the delete
await messageHandler({ type: "deleteMessageConfirm", messageTs: 1000 })
// Should handle identical timestamps gracefully
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
})
test("handles messages with future timestamps", async () => {
;(vscode.window.showWarningMessage as any).mockResolvedValue("confirmation.proceed")
const futureTimestamp = Date.now() + 100000 // Future timestamp
const mockCline = new Task(defaultTaskOptions)
mockCline.clineMessages = [
@ -3419,6 +3484,20 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
editedMessageContent: "Edited future message",
})
// Should show edit dialog
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: futureTimestamp + 1000,
text: "Edited future message",
})
// Simulate user confirming the edit
await messageHandler({
type: "editMessageConfirm",
messageTs: futureTimestamp + 1000,
text: "Edited future message",
})
// Should handle future timestamps correctly
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalled()

View file

@ -28,9 +28,13 @@ const mockClineProvider = {
globalStorageUri: { fsPath: "/mock/global/storage" },
},
setValue: vi.fn(),
getValue: vi.fn(),
},
log: vi.fn(),
postStateToWebview: vi.fn(),
getCurrentCline: vi.fn(),
getTaskWithId: vi.fn(),
initClineWithHistoryItem: vi.fn(),
} as unknown as ClineProvider
import { t } from "../../../i18n"
@ -482,3 +486,51 @@ describe("webviewMessageHandler - deleteCustomMode", () => {
expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalled()
})
})
describe("webviewMessageHandler - message dialog preferences", () => {
beforeEach(() => {
vi.clearAllMocks()
// Mock a current Cline instance
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({
taskId: "test-task-id",
apiConversationHistory: [],
clineMessages: [],
} as any)
// Reset getValue mock
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
})
describe("deleteMessage", () => {
it("should always show dialog for delete confirmation", async () => {
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists
await webviewMessageHandler(mockClineProvider, {
type: "deleteMessage",
value: 123456789, // Changed from messageTs to value
})
expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "showDeleteMessageDialog",
messageTs: 123456789,
})
})
})
describe("submitEditedMessage", () => {
it("should always show dialog for edit confirmation", async () => {
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists
await webviewMessageHandler(mockClineProvider, {
type: "submitEditedMessage",
value: 123456789, // messageTs as number
editedMessageContent: "edited content", // text content in editedMessageContent field
})
expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 123456789,
text: "edited content",
})
})
})
})

View file

@ -77,55 +77,6 @@ export const webviewMessageHandler = async (
return { messageIndex, apiConversationHistoryIndex }
}
/**
* Removes just the target message, preserving messages after the next user message
*/
const removeMessagesJustThis = async (
currentCline: any,
messageIndex: number,
apiConversationHistoryIndex: number,
) => {
// Find the next user message first
const nextUserMessage = currentCline.clineMessages
.slice(messageIndex + 1)
.find((msg: ClineMessage) => msg.type === "say" && msg.say === "user_feedback")
// Handle UI messages
if (nextUserMessage) {
// Find absolute index of next user message
const nextUserMessageIndex = currentCline.clineMessages.findIndex(
(msg: ClineMessage) => msg === nextUserMessage,
)
// Keep messages before current message and after next user message
await currentCline.overwriteClineMessages([
...currentCline.clineMessages.slice(0, messageIndex),
...currentCline.clineMessages.slice(nextUserMessageIndex),
])
} else {
// If no next user message, keep only messages before current message
await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex))
}
// Handle API messages
if (apiConversationHistoryIndex !== -1) {
if (nextUserMessage && nextUserMessage.ts) {
// Keep messages before current API message and after next user message
await currentCline.overwriteApiConversationHistory([
...currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
...currentCline.apiConversationHistory.filter(
(msg: ApiMessage) => msg.ts && msg.ts >= nextUserMessage.ts,
),
])
} else {
// If no next user message, keep only messages before current API message
await currentCline.overwriteApiConversationHistory(
currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
)
}
}
}
/**
* Removes the target message and all subsequent messages
*/
@ -148,19 +99,19 @@ export const webviewMessageHandler = async (
* Handles message deletion operations with user confirmation
*/
const handleDeleteOperation = async (messageTs: number): Promise<void> => {
const options = [
t("common:confirmation.delete_just_this_message"),
t("common:confirmation.delete_this_and_subsequent"),
]
// Send message to webview to show delete confirmation dialog
await provider.postMessageToWebview({
type: "showDeleteMessageDialog",
messageTs,
})
}
const answer = await vscode.window.showInformationMessage(
t("common:confirmation.delete_message"),
{ modal: true },
...options,
)
// Only proceed if user selected one of the options and we have a current cline
if (answer && options.includes(answer) && provider.getCurrentCline()) {
/**
* Handles confirmed message deletion from webview dialog
*/
const handleDeleteMessageConfirm = async (messageTs: number): Promise<void> => {
// Only proceed if we have a current cline
if (provider.getCurrentCline()) {
const currentCline = provider.getCurrentCline()!
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
@ -168,14 +119,8 @@ export const webviewMessageHandler = async (
try {
const { historyItem } = await provider.getTaskWithId(currentCline.taskId)
// Check which option the user selected
if (answer === options[0]) {
// Delete just this message
await removeMessagesJustThis(currentCline, messageIndex, apiConversationHistoryIndex)
} else if (answer === options[1]) {
// Delete this message and all subsequent
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
}
// Delete this message and all subsequent messages
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
// Initialize with history item after deletion
await provider.initClineWithHistoryItem(historyItem)
@ -192,15 +137,26 @@ export const webviewMessageHandler = async (
/**
* Handles message editing operations with user confirmation
*/
const handleEditOperation = async (messageTs: number, editedContent: string): Promise<void> => {
const answer = await vscode.window.showWarningMessage(
t("common:confirmation.edit_warning"),
{ modal: true },
t("common:confirmation.proceed"),
)
const handleEditOperation = async (messageTs: number, editedContent: string, images?: string[]): Promise<void> => {
// Send message to webview to show edit confirmation dialog
await provider.postMessageToWebview({
type: "showEditMessageDialog",
messageTs,
text: editedContent,
images,
})
}
// Only proceed if user selected "Proceed" and we have a current cline
if (answer === t("common:confirmation.proceed") && provider.getCurrentCline()) {
/**
* Handles confirmed message editing from webview dialog
*/
const handleEditMessageConfirm = async (
messageTs: number,
editedContent: string,
images?: string[],
): Promise<void> => {
// Only proceed if we have a current cline
if (provider.getCurrentCline()) {
const currentCline = provider.getCurrentCline()!
// Use findMessageIndices to find messages based on timestamp
@ -217,6 +173,7 @@ export const webviewMessageHandler = async (
type: "askResponse",
askResponse: "messageResponse",
text: editedContent,
images,
})
// Don't initialize with history item for edit operations
@ -242,11 +199,12 @@ export const webviewMessageHandler = async (
messageTs: number,
operation: "delete" | "edit",
editedContent?: string,
images?: string[],
): Promise<void> => {
if (operation === "delete") {
await handleDeleteOperation(messageTs)
} else if (operation === "edit" && editedContent) {
await handleEditOperation(messageTs, editedContent)
await handleEditOperation(messageTs, editedContent, images)
}
}
@ -416,7 +374,12 @@ export const webviewMessageHandler = async (
break
case "selectImages":
const images = await selectImages()
await provider.postMessageToWebview({ type: "selectedImages", images })
await provider.postMessageToWebview({
type: "selectedImages",
images,
context: message.context,
messageTs: message.messageTs,
})
break
case "exportCurrentTask":
const currentTaskId = provider.getCurrentCline()?.taskId
@ -1209,7 +1172,12 @@ export const webviewMessageHandler = async (
message.value &&
message.editedMessageContent
) {
await handleMessageModificationsOperation(message.value, "edit", message.editedMessageContent)
await handleMessageModificationsOperation(
message.value,
"edit",
message.editedMessageContent,
message.images,
)
}
break
}
@ -1542,6 +1510,16 @@ export const webviewMessageHandler = async (
}
}
break
case "deleteMessageConfirm":
if (message.messageTs) {
await handleDeleteMessageConfirm(message.messageTs)
}
break
case "editMessageConfirm":
if (message.messageTs && message.text) {
await handleEditMessageConfirm(message.messageTs, message.text, message.images)
}
break
case "getListApiConfiguration":
try {
const listApiConfig = await provider.providerSettingsManager.listConfig()

View file

@ -2,6 +2,7 @@ import { EventEmitter } from "events"
import * as vscode from "vscode"
import fs from "fs/promises"
import * as path from "path"
import * as os from "os"
import {
RooCodeAPI,
@ -50,7 +51,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
console.log(args)
}
this.logfile = path.join(getWorkspacePath(), "roo-code-messages.log")
this.logfile = path.join(os.tmpdir(), "roo-code-messages.log")
} else {
this.log = () => {}
}
@ -125,6 +126,22 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
.getConfiguration(Package.name)
.update("allowedCommands", configuration.allowedCommands, vscode.ConfigurationTarget.Global)
}
if (configuration.deniedCommands) {
await vscode.workspace
.getConfiguration(Package.name)
.update("deniedCommands", configuration.deniedCommands, vscode.ConfigurationTarget.Global)
}
if (configuration.commandExecutionTimeout !== undefined) {
await vscode.workspace
.getConfiguration(Package.name)
.update(
"commandExecutionTimeout",
configuration.commandExecutionTimeout,
vscode.ConfigurationTarget.Global,
)
}
}
await provider.removeClineFromStack()
@ -223,9 +240,11 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
cline.on("taskCompleted", async (_, tokenUsage, toolUsage) => {
let isSubtask = false
if (cline.rootTask != undefined) {
isSubtask = true
}
this.emit(RooCodeEventName.TaskCompleted, cline.taskId, tokenUsage, toolUsage, { isSubtask: isSubtask })
this.taskMap.delete(cline.taskId)

View file

@ -21,12 +21,7 @@
"confirmation": {
"reset_state": "Estàs segur que vols restablir tots els estats i emmagatzematge secret a l'extensió? Això no es pot desfer.",
"delete_config_profile": "Estàs segur que vols eliminar aquest perfil de configuració?",
"delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest mode {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}",
"delete_message": "Què vols eliminar?",
"edit_warning": "Editar aquest missatge eliminarà tots els missatges posteriors de la conversa. Vols continuar?",
"delete_just_this_message": "Només aquest missatge",
"delete_this_and_subsequent": "Aquest i tots els missatges posteriors",
"proceed": "Continuar"
"delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest mode {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Format d'URI de dades no vàlid",
@ -171,5 +166,10 @@
"descriptionNoRules": "Esteu segur que voleu suprimir aquest mode personalitzat?",
"confirm": "Suprimeix"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Evitar la finalització de tasques quan hi ha todos incomplets a la llista de todos"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Möchtest du wirklich alle Zustände und geheimen Speicher in der Erweiterung zurücksetzen? Dies kann nicht rückgängig gemacht werden.",
"delete_config_profile": "Möchtest du dieses Konfigurationsprofil wirklich löschen?",
"delete_custom_mode_with_rules": "Bist du sicher, dass du diesen {scope}-Modus löschen möchtest?\n\nDadurch wird auch der zugehörige Regelordner unter folgender Adresse gelöscht:\n{rulesFolderPath}",
"delete_message": "Was möchtest du löschen?",
"edit_warning": "Das Bearbeiten dieser Nachricht wird alle nachfolgenden Nachrichten in der Unterhaltung löschen. Möchtest du fortfahren?",
"delete_just_this_message": "Nur diese Nachricht",
"delete_this_and_subsequent": "Diese und alle nachfolgenden Nachrichten",
"proceed": "Fortfahren"
"delete_custom_mode_with_rules": "Bist du sicher, dass du diesen {scope}-Modus löschen möchtest?\n\nDadurch wird auch der zugehörige Regelordner unter folgender Adresse gelöscht:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Ungültiges Daten-URI-Format",
@ -171,5 +166,10 @@
"descriptionNoRules": "Bist du sicher, dass du diesen benutzerdefinierten Modus löschen möchtest?",
"confirm": "Löschen"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Aufgabenabschluss verhindern, wenn unvollständige Todos in der Todo-Liste vorhanden sind"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Are you sure you want to reset all state and secret storage in the extension? This cannot be undone.",
"delete_config_profile": "Are you sure you want to delete this configuration profile?",
"delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}",
"delete_message": "What would you like to delete?",
"edit_warning": "Editing this message will delete all subsequent messages in the conversation. Do you want to proceed?",
"delete_just_this_message": "Just this message",
"delete_this_and_subsequent": "This and all subsequent messages",
"proceed": "Proceed"
"delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Invalid data URI format",
@ -160,5 +155,10 @@
"descriptionNoRules": "Are you sure you want to delete this custom mode?",
"confirm": "Delete"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Prevent task completion when there are incomplete todos in the todo list"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "¿Estás seguro de que deseas restablecer todo el estado y el almacenamiento secreto en la extensión? Esta acción no se puede deshacer.",
"delete_config_profile": "¿Estás seguro de que deseas eliminar este perfil de configuración?",
"delete_custom_mode_with_rules": "¿Estás seguro de que quieres eliminar este modo {scope}?\n\nEsto también eliminará la carpeta de reglas asociada en:\n{rulesFolderPath}",
"delete_message": "¿Qué deseas eliminar?",
"edit_warning": "Editar este mensaje eliminará todos los mensajes posteriores en la conversación. ¿Deseas continuar?",
"delete_just_this_message": "Solo este mensaje",
"delete_this_and_subsequent": "Este y todos los mensajes posteriores",
"proceed": "Continuar"
"delete_custom_mode_with_rules": "¿Estás seguro de que quieres eliminar este modo {scope}?\n\nEsto también eliminará la carpeta de reglas asociada en:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Formato de URI de datos no válido",
@ -171,5 +166,10 @@
"descriptionNoRules": "¿Estás seguro de que quieres eliminar este modo personalizado?",
"confirm": "Eliminar"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Prevenir la finalización de tareas cuando hay todos incompletos en la lista de todos"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Êtes-vous sûr de vouloir réinitialiser le global state et le stockage de secrets de l'extension ? Cette action est irréversible.",
"delete_config_profile": "Êtes-vous sûr de vouloir supprimer ce profil de configuration ?",
"delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce mode {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}",
"delete_message": "Que souhaitez-vous supprimer ?",
"edit_warning": "Modifier ce message supprimera tous les messages suivants dans la conversation. Voulez-vous continuer ?",
"delete_just_this_message": "Uniquement ce message",
"delete_this_and_subsequent": "Ce message et tous les messages suivants",
"proceed": "Continuer"
"delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce mode {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Format d'URI de données invalide",
@ -171,5 +166,10 @@
"descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce mode personnalisé ?",
"confirm": "Supprimer"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Empêcher la finalisation des tâches lorsqu'il y a des todos incomplets dans la liste de todos"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "क्या आप वाकई एक्सटेंशन में सभी स्टेट और गुप्त स्टोरेज रीसेट करना चाहते हैं? इसे पूर्ववत नहीं किया जा सकता है।",
"delete_config_profile": "क्या आप वाकई इस कॉन्फ़िगरेशन प्रोफ़ाइल को हटाना चाहते हैं?",
"delete_custom_mode_with_rules": "क्या आप वाकई इस {scope} मोड को हटाना चाहते हैं?\n\nयह संबंधित नियम फ़ोल्डर को भी यहाँ हटा देगा:\n{rulesFolderPath}",
"delete_message": "आप क्या हटाना चाहते हैं?",
"edit_warning": "इस संदेश को संपादित करने से बातचीत के सभी बाद के संदेश हट जाएंगे। क्या आप जारी रखना चाहते हैं?",
"delete_just_this_message": "सिर्फ यह संदेश",
"delete_this_and_subsequent": "यह और सभी बाद के संदेश",
"proceed": "जारी रखें"
"delete_custom_mode_with_rules": "क्या आप वाकई इस {scope} मोड को हटाना चाहते हैं?\n\nयह संबंधित नियम फ़ोल्डर को भी यहाँ हटा देगा:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "अमान्य डेटा URI फॉर्मेट",
@ -171,5 +166,10 @@
"descriptionNoRules": "क्या आप वाकई इस कस्टम मोड को हटाना चाहते हैं?",
"confirm": "हटाएं"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "जब टूडू सूची में अधूरे टूडू हों तो कार्य पूर्ण होने से रोकें"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Apakah kamu yakin ingin mereset semua state dan secret storage di ekstensi? Ini tidak dapat dibatalkan.",
"delete_config_profile": "Apakah kamu yakin ingin menghapus profil konfigurasi ini?",
"delete_custom_mode_with_rules": "Anda yakin ingin menghapus mode {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}",
"delete_message": "Apa yang ingin kamu hapus?",
"edit_warning": "Mengedit pesan ini akan menghapus semua pesan selanjutnya dalam percakapan. Apakah kamu ingin melanjutkan?",
"delete_just_this_message": "Hanya pesan ini",
"delete_this_and_subsequent": "Ini dan semua pesan selanjutnya",
"proceed": "Lanjutkan"
"delete_custom_mode_with_rules": "Anda yakin ingin menghapus mode {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Format data URI tidak valid",
@ -171,5 +166,10 @@
"descriptionNoRules": "Anda yakin ingin menghapus mode kustom ini?",
"confirm": "Hapus"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Mencegah penyelesaian tugas ketika ada todo yang belum selesai dalam daftar todo"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Sei sicuro di voler reimpostare tutti gli stati e l'archiviazione segreta nell'estensione? Questa azione non può essere annullata.",
"delete_config_profile": "Sei sicuro di voler eliminare questo profilo di configurazione?",
"delete_custom_mode_with_rules": "Sei sicuro di voler eliminare questa modalità {scope}?\n\nQuesto eliminerà anche la cartella delle regole associata in:\n{rulesFolderPath}",
"delete_message": "Cosa desideri eliminare?",
"edit_warning": "Modificare questo messaggio eliminerà tutti i messaggi successivi nella conversazione. Vuoi continuare?",
"delete_just_this_message": "Solo questo messaggio",
"delete_this_and_subsequent": "Questo e tutti i messaggi successivi",
"proceed": "Continua"
"delete_custom_mode_with_rules": "Sei sicuro di voler eliminare questa modalità {scope}?\n\nQuesto eliminerà anche la cartella delle regole associata in:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Formato URI dati non valido",
@ -171,5 +166,10 @@
"descriptionNoRules": "Sei sicuro di voler eliminare questa modalità personalizzata?",
"confirm": "Elimina"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Impedire il completamento delle attività quando ci sono todo incompleti nella lista dei todo"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "拡張機能のすべての状態とシークレットストレージをリセットしてもよろしいですか?この操作は元に戻せません。",
"delete_config_profile": "この設定プロファイルを削除してもよろしいですか?",
"delete_custom_mode_with_rules": "この{scope}モードを削除してもよろしいですか?\n\nこれにより、関連するルールフォルダも次の場所で削除されます:\n{rulesFolderPath}",
"delete_message": "何を削除しますか?",
"edit_warning": "このメッセージを編集すると、会話内のすべての後続メッセージが削除されます。続行しますか?",
"delete_just_this_message": "このメッセージのみ",
"delete_this_and_subsequent": "これ以降のすべてのメッセージ",
"proceed": "続行"
"delete_custom_mode_with_rules": "この{scope}モードを削除してもよろしいですか?\n\nこれにより、関連するルールフォルダも次の場所で削除されます:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "データURIフォーマットが無効です",
@ -171,5 +166,10 @@
"descriptionNoRules": "このカスタムモードを削除してもよろしいですか?",
"confirm": "削除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Todoリストに未完了のTodoがある場合、タスクの完了を防ぐ"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "확장 프로그램의 모든 상태와 보안 저장소를 재설정하시겠습니까? 이 작업은 취소할 수 없습니다.",
"delete_config_profile": "이 구성 프로필을 삭제하시겠습니까?",
"delete_custom_mode_with_rules": "이 {scope} 모드를 삭제하시겠습니까?\n\n이렇게 하면 연결된 규칙 폴더도 다음 위치에서 삭제됩니다:\n{rulesFolderPath}",
"delete_message": "무엇을 삭제하시겠습니까?",
"edit_warning": "이 메시지를 편집하면 대화의 모든 후속 메시지가 삭제됩니다. 계속하시겠습니까?",
"delete_just_this_message": "이 메시지만",
"delete_this_and_subsequent": "이 메시지와 모든 후속 메시지",
"proceed": "계속"
"delete_custom_mode_with_rules": "이 {scope} 모드를 삭제하시겠습니까?\n\n이렇게 하면 연결된 규칙 폴더도 다음 위치에서 삭제됩니다:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "잘못된 데이터 URI 형식",
@ -171,5 +166,10 @@
"descriptionNoRules": "이 사용자 정의 모드를 삭제하시겠습니까?",
"confirm": "삭제"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "할 일 목록에 미완료된 할 일이 있을 때 작업 완료를 방지"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Weet je zeker dat je alle status en geheime opslag in de extensie wilt resetten? Dit kan niet ongedaan worden gemaakt.",
"delete_config_profile": "Weet je zeker dat je dit configuratieprofiel wilt verwijderen?",
"delete_custom_mode_with_rules": "Weet je zeker dat je deze {scope}-modus wilt verwijderen?\n\nDit verwijdert ook de bijbehorende regelsmap op:\n{rulesFolderPath}",
"delete_message": "Wat wil je verwijderen?",
"delete_just_this_message": "Alleen dit bericht",
"delete_this_and_subsequent": "Dit en alle volgende berichten",
"edit_warning": "Het bewerken van dit bericht zal alle volgende berichten in het gesprek verwijderen. Wil je doorgaan?",
"proceed": "Doorgaan"
"delete_custom_mode_with_rules": "Weet je zeker dat je deze {scope}-modus wilt verwijderen?\n\nDit verwijdert ook de bijbehorende regelsmap op:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Ongeldig data-URI-formaat",
@ -171,5 +166,10 @@
"descriptionNoRules": "Weet je zeker dat je deze aangepaste modus wilt verwijderen?",
"confirm": "Verwijderen"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Voorkom taakafronding wanneer er onvolledige todos in de todolijst staan"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Czy na pewno chcesz zresetować wszystkie stany i tajne magazyny w rozszerzeniu? Tej operacji nie można cofnąć.",
"delete_config_profile": "Czy na pewno chcesz usunąć ten profil konfiguracyjny?",
"delete_custom_mode_with_rules": "Czy na pewno chcesz usunąć ten tryb {scope}?\n\nSpowoduje to również usunięcie powiązanego folderu reguł pod adresem:\n{rulesFolderPath}",
"delete_message": "Co chcesz usunąć?",
"delete_just_this_message": "Tylko tę wiadomość",
"delete_this_and_subsequent": "Tę i wszystkie kolejne wiadomości",
"edit_warning": "Edytowanie tej wiadomości usunie wszystkie kolejne wiadomości w rozmowie. Czy chcesz kontynuować?",
"proceed": "Kontynuuj"
"delete_custom_mode_with_rules": "Czy na pewno chcesz usunąć ten tryb {scope}?\n\nSpowoduje to również usunięcie powiązanego folderu reguł pod adresem:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Nieprawidłowy format URI danych",
@ -171,5 +166,10 @@
"descriptionNoRules": "Czy na pewno chcesz usunąć ten tryb niestandardowy?",
"confirm": "Usuń"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Zapobiegaj ukończeniu zadania gdy na liście zadań są nieukończone zadania"
}
}
}

View file

@ -21,12 +21,7 @@
"confirmation": {
"reset_state": "Tem certeza de que deseja redefinir todo o estado e armazenamento secreto na extensão? Isso não pode ser desfeito.",
"delete_config_profile": "Tem certeza de que deseja excluir este perfil de configuração?",
"delete_custom_mode_with_rules": "Tem certeza de que deseja excluir este modo {scope}?\n\nIsso também excluirá a pasta de regras associada em:\n{rulesFolderPath}",
"delete_message": "O que você gostaria de excluir?",
"delete_just_this_message": "Apenas esta mensagem",
"delete_this_and_subsequent": "Esta e todas as mensagens subsequentes",
"edit_warning": "Editar esta mensagem excluirá todas as mensagens subsequentes na conversa. Deseja continuar?",
"proceed": "Continuar"
"delete_custom_mode_with_rules": "Tem certeza de que deseja excluir este modo {scope}?\n\nIsso também excluirá a pasta de regras associada em:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Formato de URI de dados inválido",
@ -171,5 +166,10 @@
"descriptionNoRules": "Tem certeza de que deseja excluir este modo personalizado?",
"confirm": "Excluir"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Impedir a conclusão de tarefas quando há todos incompletos na lista de todos"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Вы уверены, что хотите сбросить все состояние и секретное хранилище в расширении? Это действие нельзя отменить.",
"delete_config_profile": "Вы уверены, что хотите удалить этот профиль конфигурации?",
"delete_custom_mode_with_rules": "Вы уверены, что хотите удалить этот режим {scope}?\n\nЭто также приведет к удалению соответствующей папки правил по адресу:\n{rulesFolderPath}",
"delete_message": "Что вы хотите удалить?",
"delete_just_this_message": "Только это сообщение",
"delete_this_and_subsequent": "Это и все последующие сообщения",
"edit_warning": "Редактирование этого сообщения удалит все последующие сообщения в разговоре. Хотите продолжить?",
"proceed": "Продолжить"
"delete_custom_mode_with_rules": "Вы уверены, что хотите удалить этот режим {scope}?\n\nЭто также приведет к удалению соответствующей папки правил по адресу:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Неверный формат URI данных",
@ -171,5 +166,10 @@
"descriptionNoRules": "Вы уверены, что хотите удалить этот пользовательский режим?",
"confirm": "Удалить"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Предотвратить завершение задач при наличии незавершенных дел в списке дел"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Uzantıdaki tüm durumları ve gizli depolamayı sıfırlamak istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"delete_config_profile": "Bu yapılandırma profilini silmek istediğinizden emin misiniz?",
"delete_custom_mode_with_rules": "Bu {scope} modunu silmek istediğinizden emin misiniz?\n\nBu işlem, ilişkili kurallar klasörünü de şu konumdan silecektir:\n{rulesFolderPath}",
"delete_message": "Neyi silmek istersiniz?",
"delete_just_this_message": "Sadece bu mesajı",
"delete_this_and_subsequent": "Bu ve sonraki tüm mesajları",
"edit_warning": "Bu mesajı düzenlemek konuşmadaki tüm sonraki mesajları silecektir. Devam etmek istiyor musunuz?",
"proceed": "Devam et"
"delete_custom_mode_with_rules": "Bu {scope} modunu silmek istediğinizden emin misiniz?\n\nBu işlem, ilişkili kurallar klasörünü de şu konumdan silecektir:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Geçersiz veri URI formatı",
@ -171,5 +166,10 @@
"descriptionNoRules": "Bu özel modu silmek istediğinizden emin misiniz?",
"confirm": "Sil"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Todo listesinde tamamlanmamış todolar olduğunda görev tamamlanmasını engelle"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "Bạn có chắc chắn muốn đặt lại tất cả trạng thái và lưu trữ bí mật trong tiện ích mở rộng không? Hành động này không thể hoàn tác.",
"delete_config_profile": "Bạn có chắc chắn muốn xóa hồ sơ cấu hình này không?",
"delete_custom_mode_with_rules": "Bạn có chắc chắn muốn xóa chế độ {scope} này không?\n\nThao tác này cũng sẽ xóa thư mục quy tắc liên quan tại:\n{rulesFolderPath}",
"delete_message": "Bạn muốn xóa gì?",
"delete_just_this_message": "Chỉ tin nhắn này",
"delete_this_and_subsequent": "Tin nhắn này và tất cả tin nhắn tiếp theo",
"edit_warning": "Chỉnh sửa tin nhắn này sẽ xóa tất cả tin nhắn tiếp theo trong cuộc trò chuyện. Bạn có muốn tiếp tục không?",
"proceed": "Tiếp tục"
"delete_custom_mode_with_rules": "Bạn có chắc chắn muốn xóa chế độ {scope} này không?\n\nThao tác này cũng sẽ xóa thư mục quy tắc liên quan tại:\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "Định dạng URI dữ liệu không hợp lệ",
@ -178,5 +173,10 @@
"descriptionNoRules": "Bạn có chắc chắn muốn xóa chế độ tùy chỉnh này không?",
"confirm": "Xóa"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Ngăn chặn hoàn thành nhiệm vụ khi có các todo chưa hoàn thành trong danh sách todo"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "您确定要重置扩展中的所有状态和密钥存储吗?此操作无法撤消。",
"delete_config_profile": "您确定要删除此配置文件吗?",
"delete_custom_mode_with_rules": "您确定要删除此 {scope} 模式吗?\n\n这也将删除位于以下位置的关联规则文件夹\n{rulesFolderPath}",
"delete_message": "您想删除什么?",
"edit_warning": "编辑此消息将删除对话中的所有后续消息。您要继续吗?",
"delete_just_this_message": "仅此消息",
"delete_this_and_subsequent": "此消息及所有后续消息",
"proceed": "继续"
"delete_custom_mode_with_rules": "您确定要删除此 {scope} 模式吗?\n\n这也将删除位于以下位置的关联规则文件夹\n{rulesFolderPath}"
},
"errors": {
"invalid_mcp_config": "项目MCP配置格式无效",
@ -176,5 +171,10 @@
"descriptionNoRules": "您确定要删除此自定义模式吗?",
"confirm": "删除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "当待办事项列表中有未完成的待办事项时阻止任务完成"
}
}
}

View file

@ -17,12 +17,7 @@
"confirmation": {
"reset_state": "您確定要重設擴充套件中的所有狀態和金鑰儲存嗎?此操作無法復原。",
"delete_config_profile": "您確定要刪除此設定檔案嗎?",
"delete_custom_mode_with_rules": "您確定要刪除此 {scope} 模式嗎?\n\n這也將刪除位於以下位置的關聯規則資料夾\n{rulesFolderPath}",
"delete_message": "您想刪除哪些內容?",
"edit_warning": "編輯此訊息將刪除對話中的所有後續訊息。您要繼續嗎?",
"delete_just_this_message": "僅這則訊息",
"delete_this_and_subsequent": "這則訊息及所有後續訊息",
"proceed": "繼續"
"delete_custom_mode_with_rules": "您確定要刪除此 {scope} 模式嗎?\n\n這也將刪除位於以下位置的關聯規則資料夾\n{rulesFolderPath}"
},
"errors": {
"invalid_data_uri": "資料 URI 格式無效",
@ -171,5 +166,10 @@
"descriptionNoRules": "您確定要刪除此自訂模式嗎?",
"confirm": "刪除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "當待辦事項清單中有未完成的待辦事項時阻止工作完成"
}
}
}

View file

@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "RooVeterinaryInc",
"version": "3.23.12",
"version": "3.23.14",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
@ -224,38 +224,38 @@
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@3",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@4",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "overflow@1",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "roo-cline.promptsButtonClicked",
"group": "overflow@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@3",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@4",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"group": "overflow@5",
"group": "overflow@4",
"when": "view == roo-cline.SidebarProvider"
}
],
@ -266,38 +266,38 @@
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "overflow@1",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "roo-cline.promptsButtonClicked",
"group": "overflow@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"group": "overflow@5",
"group": "overflow@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
}
]
@ -345,6 +345,11 @@
"maximum": 600,
"description": "%commands.commandExecutionTimeout.description%"
},
"roo-cline.preventCompletionWithOpenTodos": {
"type": "boolean",
"default": false,
"description": "%commands.preventCompletionWithOpenTodos.description%"
},
"roo-cline.vsCodeLmModelSelector": {
"type": "object",
"properties": {

View file

@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
"commands.commandExecutionTimeout.description": "Maximum time in seconds to wait for command execution to complete before timing out (0 = no timeout, 1-600s, default: 0s)",
"commands.preventCompletionWithOpenTodos.description": "Prevent task completion when there are incomplete todos in the todo list",
"settings.vsCodeLmModelSelector.description": "Settings for VSCode Language Model API",
"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",

View file

@ -15,7 +15,7 @@ export const QDRANT_CODE_BLOCK_NAMESPACE = "f47ac10b-58cc-4372-a567-0e02b2c3d479
export const MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024 // 1MB
/**Directory Scanner */
export const MAX_LIST_FILES_LIMIT = 3_000
export const MAX_LIST_FILES_LIMIT_CODE_INDEX = 50_000
export const BATCH_SEGMENT_THRESHOLD = 60 // Number of code segments to batch for embeddings/upserts
export const MAX_BATCH_RETRIES = 3
export const INITIAL_RETRY_DELAY_MS = 500

View file

@ -7,6 +7,10 @@ import { withValidationErrorHandling, sanitizeErrorMessage } from "../shared/val
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
// Timeout constants for Ollama API requests
const OLLAMA_EMBEDDING_TIMEOUT_MS = 60000 // 60 seconds for embedding requests
const OLLAMA_VALIDATION_TIMEOUT_MS = 30000 // 30 seconds for validation requests
/**
* Implements the IEmbedder interface using a local Ollama instance.
*/
@ -61,7 +65,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
// Add timeout to prevent indefinite hanging
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS)
const response = await fetch(url, {
method: "POST",
@ -140,7 +144,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
// Add timeout to prevent indefinite hanging
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
const modelsResponse = await fetch(modelsUrl, {
method: "GET",
@ -197,7 +201,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
// Add timeout for test request too
const testController = new AbortController()
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
const testTimeoutId = setTimeout(() => testController.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
const testResponse = await fetch(testUrl, {
method: "POST",

View file

@ -38,7 +38,6 @@ export interface IDirectoryScanner {
onBlocksIndexed?: (indexedCount: number) => void,
onFileParsed?: (fileBlockCount: number) => void,
): Promise<{
codeBlocks: CodeBlock[]
stats: {
processed: number
skipped: number

View file

@ -168,7 +168,16 @@ describe("DirectoryScanner", () => {
expect(mockCodeParser.parseFile).not.toHaveBeenCalled()
})
it("should parse changed files and return code blocks", async () => {
it("should parse changed files and return empty codeBlocks array", async () => {
// Create scanner without embedder to test the non-embedding path
const scannerNoEmbeddings = new DirectoryScanner(
null as any, // No embedder
null as any, // No vector store
mockCodeParser,
mockCacheManager,
mockIgnoreInstance,
)
const { listFiles } = await import("../../../glob/list-files")
vi.mocked(listFiles).mockResolvedValue([["test/file1.js"], false])
const mockBlocks: any[] = [
@ -185,8 +194,7 @@ describe("DirectoryScanner", () => {
]
;(mockCodeParser.parseFile as any).mockResolvedValue(mockBlocks)
const result = await scanner.scanDirectory("/test")
expect(result.codeBlocks).toEqual(mockBlocks)
const result = await scannerNoEmbeddings.scanDirectory("/test")
expect(result.stats.processed).toBe(1)
})
@ -252,6 +260,15 @@ describe("DirectoryScanner", () => {
})
it("should process markdown files alongside code files", async () => {
// Create scanner without embedder to test the non-embedding path
const scannerNoEmbeddings = new DirectoryScanner(
null as any, // No embedder
null as any, // No vector store
mockCodeParser,
mockCacheManager,
mockIgnoreInstance,
)
const { listFiles } = await import("../../../glob/list-files")
vi.mocked(listFiles).mockResolvedValue([["test/README.md", "test/app.js", "docs/guide.markdown"], false])
@ -306,7 +323,7 @@ describe("DirectoryScanner", () => {
return []
})
const result = await scanner.scanDirectory("/test")
const result = await scannerNoEmbeddings.scanDirectory("/test")
// Verify all files were processed
expect(mockCodeParser.parseFile).toHaveBeenCalledTimes(3)
@ -314,16 +331,7 @@ describe("DirectoryScanner", () => {
expect(mockCodeParser.parseFile).toHaveBeenCalledWith("test/app.js", expect.any(Object))
expect(mockCodeParser.parseFile).toHaveBeenCalledWith("docs/guide.markdown", expect.any(Object))
// Verify code blocks include both markdown and code content
expect(result.codeBlocks).toHaveLength(3)
expect(result.codeBlocks).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: "markdown_header_h1" }),
expect.objectContaining({ type: "function" }),
expect.objectContaining({ type: "markdown_header_h2" }),
]),
)
// Verify processing still works without codeBlocks accumulation
expect(result.stats.processed).toBe(3)
})

View file

@ -17,7 +17,7 @@ import { t } from "../../../i18n"
import {
QDRANT_CODE_BLOCK_NAMESPACE,
MAX_FILE_SIZE_BYTES,
MAX_LIST_FILES_LIMIT,
MAX_LIST_FILES_LIMIT_CODE_INDEX,
BATCH_SEGMENT_THRESHOLD,
MAX_BATCH_RETRIES,
INITIAL_RETRY_DELAY_MS,
@ -51,13 +51,13 @@ export class DirectoryScanner implements IDirectoryScanner {
onError?: (error: Error) => void,
onBlocksIndexed?: (indexedCount: number) => void,
onFileParsed?: (fileBlockCount: number) => void,
): Promise<{ codeBlocks: CodeBlock[]; stats: { processed: number; skipped: number }; totalBlockCount: number }> {
): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
const directoryPath = directory
// Capture workspace context at scan start
const scanWorkspace = getWorkspacePathForContext(directoryPath)
// Get all files recursively (handles .gitignore automatically)
const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT)
const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT_CODE_INDEX)
// Filter out directories (marked with trailing '/')
const filePaths = allPaths.filter((p) => !p.endsWith("/"))
@ -85,7 +85,6 @@ export class DirectoryScanner implements IDirectoryScanner {
// Initialize tracking variables
const processedFiles = new Set<string>()
const codeBlocks: CodeBlock[] = []
let processedCount = 0
let skippedCount = 0
@ -98,7 +97,7 @@ export class DirectoryScanner implements IDirectoryScanner {
let currentBatchBlocks: CodeBlock[] = []
let currentBatchTexts: string[] = []
let currentBatchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[] = []
const activeBatchPromises: Promise<void>[] = []
const activeBatchPromises = new Set<Promise<void>>()
// Initialize block counter
let totalBlockCount = 0
@ -125,6 +124,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Check against cache
const cachedFileHash = this.cacheManager.getHash(filePath)
const isNewFile = !cachedFileHash
if (cachedFileHash === currentFileHash) {
// File is unchanged
skippedCount++
@ -135,7 +135,6 @@ export class DirectoryScanner implements IDirectoryScanner {
const blocks = await this.codeParser.parseFile(filePath, { content, fileHash: currentFileHash })
const fileBlockCount = blocks.length
onFileParsed?.(fileBlockCount)
codeBlocks.push(...blocks)
processedCount++
// Process embeddings if configured
@ -146,20 +145,11 @@ export class DirectoryScanner implements IDirectoryScanner {
const trimmedContent = block.content.trim()
if (trimmedContent) {
const release = await mutex.acquire()
totalBlockCount += fileBlockCount
try {
currentBatchBlocks.push(block)
currentBatchTexts.push(trimmedContent)
addedBlocksFromFile = true
if (addedBlocksFromFile) {
currentBatchFileInfos.push({
filePath,
fileHash: currentFileHash,
isNew: !this.cacheManager.getHash(filePath),
})
}
// Check if batch threshold is met
if (currentBatchBlocks.length >= BATCH_SEGMENT_THRESHOLD) {
// Copy current batch data and clear accumulators
@ -181,13 +171,33 @@ export class DirectoryScanner implements IDirectoryScanner {
onBlocksIndexed,
),
)
activeBatchPromises.push(batchPromise)
activeBatchPromises.add(batchPromise)
// Clean up completed promises to prevent memory accumulation
batchPromise.finally(() => {
activeBatchPromises.delete(batchPromise)
})
}
} finally {
release()
}
}
}
// Add file info once per file (outside the block loop)
if (addedBlocksFromFile) {
const release = await mutex.acquire()
try {
totalBlockCount += fileBlockCount
currentBatchFileInfos.push({
filePath,
fileHash: currentFileHash,
isNew: isNewFile,
})
} finally {
release()
}
}
} else {
// Only update hash if not being processed in a batch
await this.cacheManager.updateHash(filePath, currentFileHash)
@ -232,7 +242,12 @@ export class DirectoryScanner implements IDirectoryScanner {
const batchPromise = batchLimiter(() =>
this.processBatch(batchBlocks, batchTexts, batchFileInfos, scanWorkspace, onError, onBlocksIndexed),
)
activeBatchPromises.push(batchPromise)
activeBatchPromises.add(batchPromise)
// Clean up completed promises to prevent memory accumulation
batchPromise.finally(() => {
activeBatchPromises.delete(batchPromise)
})
} finally {
release()
}
@ -280,7 +295,6 @@ export class DirectoryScanner implements IDirectoryScanner {
}
return {
codeBlocks,
stats: {
processed: processedCount,
skipped: skippedCount,

View file

@ -107,6 +107,8 @@ export interface ExtensionMessage {
| "codeIndexSecretStatus"
| "rulesGenerationStatus"
| "existingRuleFiles"
| "showDeleteMessageDialog"
| "showEditMessageDialog"
text?: string
payload?: any // Add a generic payload for now, can refine later
files?: string[] // For existingRuleFiles
@ -161,6 +163,8 @@ export interface ExtensionMessage {
visibility?: ShareVisibility
rulesFolderPath?: string
settings?: any
messageTs?: number
context?: string
}
export type ExtensionState = Pick<

View file

@ -111,7 +111,9 @@ export interface WebviewMessage {
| "enhancedPrompt"
| "draggedImages"
| "deleteMessage"
| "deleteMessageConfirm"
| "submitEditedMessage"
| "editMessageConfirm"
| "terminalOutputLineLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
@ -200,6 +202,7 @@ export interface WebviewMessage {
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
disabled?: boolean
context?: string
dataUri?: string
askResponse?: ClineAskResponse
apiConfiguration?: ProviderSettings
@ -228,6 +231,7 @@ export interface WebviewMessage {
ids?: string[]
hasSystemPromptOverride?: boolean
terminalOperation?: "continue" | "abort"
messageTs?: number
historyPreviewCollapsed?: boolean
filters?: { type?: string; search?: string; tags?: string[] }
url?: string // For openExternal

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState, useMemo } from "react"
import React, { useCallback, useEffect, useRef, useState, useMemo } from "react"
import { useEvent } from "react-use"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
@ -18,6 +18,7 @@ import McpView from "./components/mcp/McpView"
import { MarketplaceView } from "./components/marketplace/MarketplaceView"
import ModesView from "./components/modes/ModesView"
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
import { DeleteMessageDialog, EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog"
import { AccountView } from "./components/account/AccountView"
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
import { TooltipProvider } from "./components/ui/tooltip"
@ -25,6 +26,29 @@ import { STANDARD_TOOLTIP_DELAY } from "./components/ui/standard-tooltip"
type Tab = "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
interface HumanRelayDialogState {
isOpen: boolean
requestId: string
promptText: string
}
interface DeleteMessageDialogState {
isOpen: boolean
messageTs: number
}
interface EditMessageDialogState {
isOpen: boolean
messageTs: number
text: string
images?: string[]
}
// Memoize dialog components to prevent unnecessary re-renders
const MemoizedDeleteMessageDialog = React.memo(DeleteMessageDialog)
const MemoizedEditMessageDialog = React.memo(EditMessageDialog)
const MemoizedHumanRelayDialog = React.memo(HumanRelayDialog)
const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]>, Tab>> = {
chatButtonClicked: "chat",
settingsButtonClicked: "settings",
@ -56,16 +80,24 @@ const App = () => {
const [showAnnouncement, setShowAnnouncement] = useState(false)
const [tab, setTab] = useState<Tab>("chat")
const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
isOpen: boolean
requestId: string
promptText: string
}>({
const [humanRelayDialogState, setHumanRelayDialogState] = useState<HumanRelayDialogState>({
isOpen: false,
requestId: "",
promptText: "",
})
const [deleteMessageDialogState, setDeleteMessageDialogState] = useState<DeleteMessageDialogState>({
isOpen: false,
messageTs: 0,
})
const [editMessageDialogState, setEditMessageDialogState] = useState<EditMessageDialogState>({
isOpen: false,
messageTs: 0,
text: "",
images: [],
})
const settingsRef = useRef<SettingsViewRef>(null)
const chatViewRef = useRef<ChatViewRef>(null)
@ -121,6 +153,19 @@ const App = () => {
setHumanRelayDialogState({ isOpen: true, requestId, promptText })
}
if (message.type === "showDeleteMessageDialog" && message.messageTs) {
setDeleteMessageDialogState({ isOpen: true, messageTs: message.messageTs })
}
if (message.type === "showEditMessageDialog" && message.messageTs && message.text) {
setEditMessageDialogState({
isOpen: true,
messageTs: message.messageTs,
text: message.text,
images: message.images || [],
})
}
if (message.type === "acceptInput") {
chatViewRef.current?.acceptInput()
}
@ -199,7 +244,7 @@ const App = () => {
showAnnouncement={showAnnouncement}
hideAnnouncement={() => setShowAnnouncement(false)}
/>
<HumanRelayDialog
<MemoizedHumanRelayDialog
isOpen={humanRelayDialogState.isOpen}
requestId={humanRelayDialogState.requestId}
promptText={humanRelayDialogState.promptText}
@ -207,6 +252,30 @@ const App = () => {
onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })}
onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })}
/>
<MemoizedDeleteMessageDialog
open={deleteMessageDialogState.isOpen}
onOpenChange={(open) => setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))}
onConfirm={() => {
vscode.postMessage({
type: "deleteMessageConfirm",
messageTs: deleteMessageDialogState.messageTs,
})
setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: false }))
}}
/>
<MemoizedEditMessageDialog
open={editMessageDialogState.isOpen}
onOpenChange={(open) => setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))}
onConfirm={() => {
vscode.postMessage({
type: "editMessageConfirm",
messageTs: editMessageDialogState.messageTs,
text: editMessageDialogState.text,
images: editMessageDialogState.images,
})
setEditMessageDialogState((prev) => ({ ...prev, isOpen: false }))
}}
/>
</>
)
}

View file

@ -6,6 +6,9 @@ import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
interface AutoApproveMenuProps {
style?: React.CSSProperties
@ -17,16 +20,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const {
autoApprovalEnabled,
setAutoApprovalEnabled,
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
allowedMaxRequests,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
@ -43,10 +37,24 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const { t } = useAppTranslation()
const baseToggles = useAutoApprovalToggles()
// AutoApproveMenu needs alwaysApproveResubmit in addition to the base toggles
const toggles = useMemo(
() => ({
...baseToggles,
alwaysApproveResubmit: alwaysApproveResubmit,
}),
[baseToggles, alwaysApproveResubmit],
)
const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)
const onAutoApproveToggle = useCallback(
(key: AutoApproveSetting, value: boolean) => {
vscode.postMessage({ type: key, bool: value })
// Update the specific toggle state
switch (key) {
case "alwaysAllowReadOnly":
setAlwaysAllowReadOnly(value)
@ -79,8 +87,30 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowUpdateTodoList(value)
break
}
// Check if we need to update the master auto-approval state
// Create a new toggles state with the updated value
const updatedToggles = {
...toggles,
[key]: value,
}
const willHaveEnabledOptions = Object.values(updatedToggles).some((v) => !!v)
// If enabling the first option, enable master auto-approval
if (value && !hasEnabledOptions && willHaveEnabledOptions) {
setAutoApprovalEnabled(true)
vscode.postMessage({ type: "autoApprovalEnabled", bool: true })
}
// If disabling the last option, disable master auto-approval
else if (!value && hasEnabledOptions && !willHaveEnabledOptions) {
setAutoApprovalEnabled(false)
vscode.postMessage({ type: "autoApprovalEnabled", bool: false })
}
},
[
toggles,
hasEnabledOptions,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
setAlwaysAllowExecute,
@ -91,43 +121,32 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysApproveResubmit,
setAlwaysAllowFollowupQuestions,
setAlwaysAllowUpdateTodoList,
setAutoApprovalEnabled,
],
)
const toggleExpanded = useCallback(() => setIsExpanded((prev) => !prev), [])
const toggleExpanded = useCallback(() => {
setIsExpanded((prev) => !prev)
}, [])
const toggles = useMemo(
() => ({
alwaysAllowReadOnly: alwaysAllowReadOnly,
alwaysAllowWrite: alwaysAllowWrite,
alwaysAllowExecute: alwaysAllowExecute,
alwaysAllowBrowser: alwaysAllowBrowser,
alwaysAllowMcp: alwaysAllowMcp,
alwaysAllowModeSwitch: alwaysAllowModeSwitch,
alwaysAllowSubtasks: alwaysAllowSubtasks,
alwaysApproveResubmit: alwaysApproveResubmit,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList: alwaysAllowUpdateTodoList,
}),
[
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
],
)
// Disable main checkbox while menu is open or no options selected
const isCheckboxDisabled = useMemo(() => {
return !hasEnabledOptions || isExpanded
}, [hasEnabledOptions, isExpanded])
const enabledActionsList = Object.entries(toggles)
.filter(([_key, value]) => !!value)
.map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey))
.join(", ")
// Update displayed text logic
const displayText = useMemo(() => {
if (!effectiveAutoApprovalEnabled || !hasEnabledOptions) {
return t("chat:autoApprove.none")
}
return enabledActionsList || t("chat:autoApprove.none")
}, [effectiveAutoApprovalEnabled, hasEnabledOptions, enabledActionsList, t])
const handleOpenSettings = useCallback(
() =>
window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }),
@ -155,14 +174,26 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
}}
onClick={toggleExpanded}>
<div onClick={(e) => e.stopPropagation()}>
<VSCodeCheckbox
checked={autoApprovalEnabled ?? false}
onChange={() => {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}}
/>
<StandardTooltip
content={!hasEnabledOptions ? t("chat:autoApprove.selectOptionsFirst") : undefined}>
<VSCodeCheckbox
checked={effectiveAutoApprovalEnabled}
disabled={isCheckboxDisabled}
aria-label={
hasEnabledOptions
? t("chat:autoApprove.toggleAriaLabel")
: t("chat:autoApprove.disabledAriaLabel")
}
onChange={() => {
if (hasEnabledOptions) {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}
// If no options enabled, do nothing
}}
/>
</StandardTooltip>
</div>
<div
style={{
@ -188,7 +219,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
flex: 1,
minWidth: 0,
}}>
{enabledActionsList || t("chat:autoApprove.none")}
{displayText}
</span>
<span
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}

View file

@ -1,4 +1,5 @@
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { appendImages } from "@src/utils/imageUtils"
import { McpExecution } from "./McpExecution"
import { useSize } from "react-use"
import { useTranslation, Trans } from "react-i18next"
@ -6,6 +7,7 @@ import deepEqual from "fast-deep-equal"
import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import type { ClineMessage } from "@roo-code/types"
import { Mode } from "@roo/modes"
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/ExtensionMessage"
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
@ -20,6 +22,9 @@ import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanu
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import { Button } from "@src/components/ui"
import ChatTextArea from "./ChatTextArea"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import CodeAccordian from "../common/CodeAccordian"
@ -109,14 +114,29 @@ export const ChatRowContent = ({
editable,
}: ChatRowContentProps) => {
const { t } = useTranslation()
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode } = useExtensionState()
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
const [editMode, setEditMode] = useState<Mode>(mode || "code")
const [editImages, setEditImages] = useState<string[]>([])
const { copyWithFeedback } = useCopyToClipboard()
// Handle message events for image selection during edit mode
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const msg = event.data
if (msg.type === "selectedImages" && msg.context === "edit" && msg.messageTs === message.ts && isEditing) {
setEditImages((prevImages) => appendImages(prevImages, msg.images, MAX_IMAGES_PER_MESSAGE))
}
}
window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
}, [isEditing, message.ts])
// Memoized callback to prevent re-renders caused by inline arrow functions
const handleToggleExpand = useCallback(() => {
onToggleExpand(message.ts)
@ -126,15 +146,19 @@ export const ChatRowContent = ({
const handleEditClick = useCallback(() => {
setIsEditing(true)
setEditedContent(message.text || "")
setEditImages(message.images || [])
setEditMode(mode || "code")
// Edit mode is now handled entirely in the frontend
// No need to notify the backend
}, [message.text])
}, [message.text, message.images, mode])
// Handle cancel edit
const handleCancelEdit = useCallback(() => {
setIsEditing(false)
setEditedContent(message.text || "")
}, [message.text])
setEditImages(message.images || [])
setEditMode(mode || "code")
}, [message.text, message.images, mode])
// Handle save edit
const handleSaveEdit = useCallback(() => {
@ -144,8 +168,14 @@ export const ChatRowContent = ({
type: "submitEditedMessage",
value: message.ts,
editedMessageContent: editedContent,
images: editImages,
})
}, [message.ts, editedContent])
}, [message.ts, editedContent, editImages])
// Handle image selection for editing
const handleSelectImages = useCallback(() => {
vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
}, [message.ts])
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
@ -1032,21 +1062,23 @@ export const ChatRowContent = ({
<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
{isEditing ? (
<div className="flex flex-col gap-2 p-2">
<textarea
className="w-full p-2 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded-xs"
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
rows={5}
autoFocus
<ChatTextArea
inputValue={editedContent}
setInputValue={setEditedContent}
sendingDisabled={false}
selectApiConfigDisabled={true}
placeholderText={t("chat:editMessage.placeholder")}
selectedImages={editImages}
setSelectedImages={setEditImages}
onSend={handleSaveEdit}
onSelectImages={handleSelectImages}
shouldDisableImages={false}
mode={editMode}
setMode={setEditMode}
modeShortcutText=""
isEditMode={true}
onCancel={handleCancelEdit}
/>
<div className="flex justify-end gap-2">
<Button variant="secondary" size="sm" onClick={handleCancelEdit}>
{t("chat:cancel.title")}
</Button>
<Button variant="default" size="sm" onClick={handleSaveEdit}>
{t("chat:save.title")}
</Button>
</div>
</div>
) : (
<div className="flex justify-between">

View file

@ -29,6 +29,7 @@ import { VolumeX, Pin, Check, Image, WandSparkles, SendHorizontal } from "lucide
import { IndexingStatusBadge } from "./IndexingStatusBadge"
import { cn } from "@/lib/utils"
import { usePromptHistory } from "./hooks/usePromptHistory"
import { EditModeControls } from "./EditModeControls"
interface ChatTextAreaProps {
inputValue: string
@ -45,6 +46,9 @@ interface ChatTextAreaProps {
mode: Mode
setMode: (value: Mode) => void
modeShortcutText: string
// Edit mode props
isEditMode?: boolean
onCancel?: () => void
}
const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
@ -64,6 +68,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
mode,
setMode,
modeShortcutText,
isEditMode = false,
onCancel,
},
ref,
) => {
@ -796,6 +802,378 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const placeholderBottomText = `\n(${t("chat:addContext")}${shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`})`
// Common mode selector handler
const handleModeChange = useCallback(
(value: Mode) => {
setMode(value)
vscode.postMessage({ type: "mode", text: value })
},
[setMode],
)
// Helper function to render mode selector
const renderModeSelector = () => (
<ModeSelector
value={mode}
title={t("chat:selectMode")}
onChange={handleModeChange}
triggerClassName="w-full"
modeShortcutText={modeShortcutText}
customModes={customModes}
customModePrompts={customModePrompts}
/>
)
// Helper function to get API config dropdown options
const getApiConfigOptions = useMemo(() => {
const pinnedConfigs = (listApiConfigMeta || [])
.filter((config) => pinnedApiConfigs && pinnedApiConfigs[config.id])
.map((config) => ({
value: config.id,
label: config.name,
name: config.name,
type: DropdownOptionType.ITEM,
pinned: true,
}))
.sort((a, b) => a.label.localeCompare(b.label))
const unpinnedConfigs = (listApiConfigMeta || [])
.filter((config) => !pinnedApiConfigs || !pinnedApiConfigs[config.id])
.map((config) => ({
value: config.id,
label: config.name,
name: config.name,
type: DropdownOptionType.ITEM,
pinned: false,
}))
.sort((a, b) => a.label.localeCompare(b.label))
const hasPinnedAndUnpinned = pinnedConfigs.length > 0 && unpinnedConfigs.length > 0
return [
...pinnedConfigs,
...(hasPinnedAndUnpinned
? [
{
value: "sep-pinned",
label: t("chat:separator"),
type: DropdownOptionType.SEPARATOR,
},
]
: []),
...unpinnedConfigs,
{
value: "sep-2",
label: t("chat:separator"),
type: DropdownOptionType.SEPARATOR,
},
{
value: "settingsButtonClicked",
label: t("chat:edit"),
type: DropdownOptionType.ACTION,
},
]
}, [listApiConfigMeta, pinnedApiConfigs, t])
// Helper function to handle API config change
const handleApiConfigChange = useCallback((value: string) => {
if (value === "settingsButtonClicked") {
vscode.postMessage({
type: "loadApiConfiguration",
text: value,
values: { section: "providers" },
})
} else {
vscode.postMessage({ type: "loadApiConfigurationById", text: value })
}
}, [])
// Helper function to render API config item
const renderApiConfigItem = useCallback(
({ type, value, label, pinned }: any) => {
if (type !== DropdownOptionType.ITEM) {
return label
}
const config = listApiConfigMeta?.find((c) => c.id === value)
const isCurrentConfig = config?.name === currentApiConfigName
return (
<div className="flex justify-between gap-2 w-full h-5">
<div
className={cn("truncate min-w-0 overflow-hidden", {
"font-medium": isCurrentConfig,
})}>
{label}
</div>
<div className="flex justify-end w-10 flex-shrink-0">
<div
className={cn("size-5 p-1", {
"block group-hover:hidden": !pinned,
hidden: !isCurrentConfig,
})}>
<Check className="size-3" />
</div>
<StandardTooltip content={pinned ? t("chat:unpin") : t("chat:pin")}>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
togglePinnedApiConfig(value)
vscode.postMessage({
type: "toggleApiConfigPin",
text: value,
})
}}
className={cn("size-5", {
"hidden group-hover:flex": !pinned,
"bg-accent": pinned,
})}>
<Pin className="size-3 p-0.5 opacity-50" />
</Button>
</StandardTooltip>
</div>
</div>
)
},
[listApiConfigMeta, currentApiConfigName, t, togglePinnedApiConfig],
)
// Helper function to render non-edit mode controls
const renderNonEditModeControls = () => (
<div className={cn("flex", "justify-between", "items-center", "mt-auto")}>
<div className={cn("flex", "items-center", "gap-1", "min-w-0")}>
<div className="shrink-0">{renderModeSelector()}</div>
<div className={cn("flex-1", "min-w-0", "overflow-hidden")}>
<SelectDropdown
value={currentConfigId}
disabled={selectApiConfigDisabled}
title={t("chat:selectApiConfig")}
disableSearch={false}
placeholder={displayName}
options={getApiConfigOptions}
onChange={handleApiConfigChange}
triggerClassName="w-full text-ellipsis overflow-hidden"
itemClassName="group"
renderItem={renderApiConfigItem}
/>
</div>
</div>
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
{isTtsPlaying && (
<StandardTooltip content={t("chat:stopTts")}>
<button
aria-label={t("chat:stopTts")}
onClick={() => vscode.postMessage({ type: "stopTts" })}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<VolumeX className="w-4 h-4" />
</button>
</StandardTooltip>
)}
<IndexingStatusBadge />
<StandardTooltip content={t("chat:addImages")}>
<button
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
"mr-1",
)}>
<Image className="w-4 h-4" />
</button>
</StandardTooltip>
</div>
</div>
)
// Helper function to render the text area section
const renderTextAreaSection = () => (
<div
className={cn(
"relative",
"flex-1",
"flex",
"flex-col-reverse",
"min-h-0",
"overflow-hidden",
"rounded",
)}>
<div
ref={highlightLayerRef}
className={cn(
"absolute",
"inset-0",
"pointer-events-none",
"whitespace-pre-wrap",
"break-words",
"text-transparent",
"overflow-hidden",
"font-vscode-font-family",
"text-vscode-editor-font-size",
"leading-vscode-editor-line-height",
"py-2",
"px-[9px]",
"z-10",
"forced-color-adjust-none",
)}
style={{
color: "transparent",
}}
/>
<DynamicTextArea
ref={(el) => {
if (typeof ref === "function") {
ref(el)
} else if (ref) {
ref.current = el
}
textAreaRef.current = el
}}
value={inputValue}
onChange={(e) => {
handleInputChange(e)
updateHighlights()
}}
onFocus={() => setIsFocused(true)}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onBlur={handleBlur}
onPaste={handlePaste}
onSelect={updateCursorPosition}
onMouseUp={updateCursorPosition}
onHeightChange={(height) => {
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
setTextAreaBaseHeight(height)
}
onHeightChange?.(height)
}}
placeholder={placeholderText}
minRows={3}
maxRows={15}
autoFocus={true}
className={cn(
"w-full",
"text-vscode-input-foreground",
"font-vscode-font-family",
"text-vscode-editor-font-size",
"leading-vscode-editor-line-height",
"cursor-text",
isEditMode ? "pt-1.5 pb-10 px-2" : "py-1.5 px-2",
isFocused
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
: isDraggingOver
? "border-2 border-dashed border-vscode-focusBorder"
: "border border-transparent",
isDraggingOver
? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
: "bg-vscode-input-background",
"transition-background-color duration-150 ease-in-out",
"will-change-background-color",
"min-h-[90px]",
"box-border",
"rounded",
"resize-none",
"overflow-x-hidden",
"overflow-y-auto",
"pr-9",
"flex-none flex-grow",
"z-[2]",
"scrollbar-none",
"scrollbar-hide",
)}
onScroll={() => updateHighlights()}
/>
<div className="absolute top-1 right-1 z-30">
<StandardTooltip content={t("chat:enhancePrompt")}>
<button
aria-label={t("chat:enhancePrompt")}
disabled={sendingDisabled}
onClick={!sendingDisabled ? handleEnhancePrompt : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
</button>
</StandardTooltip>
</div>
{!isEditMode && (
<div className="absolute bottom-1 right-1 z-30">
<StandardTooltip content={t("chat:sendMessage")}>
<button
aria-label={t("chat:sendMessage")}
disabled={sendingDisabled}
onClick={!sendingDisabled ? onSend : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</StandardTooltip>
</div>
)}
{!inputValue && !isEditMode && (
<div
className="absolute left-2 z-30 pr-9 flex items-center h-8"
style={{
bottom: "0.25rem",
color: "var(--vscode-tab-inactiveForeground)",
userSelect: "none",
pointerEvents: "none",
}}>
{placeholderBottomText}
</div>
)}
</div>
)
return (
<div
className={cn(
@ -804,12 +1182,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
"flex-col",
"gap-1",
"bg-editor-background",
"px-1.5",
isEditMode ? "px-0" : "px-1.5",
"pb-1",
"outline-none",
"border",
"border-none",
"w-[calc(100%-16px)]",
isEditMode ? "w-full" : "w-[calc(100%-16px)]",
"ml-auto",
"mr-auto",
"box-border",
@ -870,165 +1248,24 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
/>
</div>
)}
<div
className={cn(
"relative",
"flex-1",
"flex",
"flex-col-reverse",
"min-h-0",
"overflow-hidden",
"rounded",
)}>
<div
ref={highlightLayerRef}
className={cn(
"absolute",
"inset-0",
"pointer-events-none",
"whitespace-pre-wrap",
"break-words",
"text-transparent",
"overflow-hidden",
"font-vscode-font-family",
"text-vscode-editor-font-size",
"leading-vscode-editor-line-height",
"py-2",
"px-[9px]",
"z-10",
"forced-color-adjust-none",
)}
style={{
color: "transparent",
}}
/>
<DynamicTextArea
ref={(el) => {
if (typeof ref === "function") {
ref(el)
} else if (ref) {
ref.current = el
}
textAreaRef.current = el
}}
value={inputValue}
onChange={(e) => {
handleInputChange(e)
updateHighlights()
}}
onFocus={() => setIsFocused(true)}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onBlur={handleBlur}
onPaste={handlePaste}
onSelect={updateCursorPosition}
onMouseUp={updateCursorPosition}
onHeightChange={(height) => {
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
setTextAreaBaseHeight(height)
}
onHeightChange?.(height)
}}
placeholder={placeholderText}
minRows={3}
maxRows={15}
autoFocus={true}
className={cn(
"w-full",
"text-vscode-input-foreground",
"font-vscode-font-family",
"text-vscode-editor-font-size",
"leading-vscode-editor-line-height",
"cursor-text",
"py-1.5 px-2",
isFocused
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
: isDraggingOver
? "border-2 border-dashed border-vscode-focusBorder"
: "border border-transparent",
isDraggingOver
? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
: "bg-vscode-input-background",
"transition-background-color duration-150 ease-in-out",
"will-change-background-color",
"min-h-[90px]",
"box-border",
"rounded",
"resize-none",
"overflow-x-hidden",
"overflow-y-auto",
"pr-9",
"flex-none flex-grow",
"z-[2]",
"scrollbar-none",
"scrollbar-hide",
)}
onScroll={() => updateHighlights()}
/>
<div className="absolute top-1 right-1 z-30">
<StandardTooltip content={t("chat:enhancePrompt")}>
<button
aria-label={t("chat:enhancePrompt")}
disabled={sendingDisabled}
onClick={!sendingDisabled ? handleEnhancePrompt : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
</button>
</StandardTooltip>
</div>
<div className="absolute bottom-1 right-1 z-30">
<StandardTooltip content={t("chat:sendMessage")}>
<button
aria-label={t("chat:sendMessage")}
disabled={sendingDisabled}
onClick={!sendingDisabled ? onSend : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</StandardTooltip>
</div>
{!inputValue && (
<div
className="absolute left-2 z-30 pr-9 flex items-center h-8"
style={{
bottom: "0.25rem",
color: "var(--vscode-tab-inactiveForeground)",
userSelect: "none",
pointerEvents: "none",
}}>
{placeholderBottomText}
</div>
)}
</div>
{renderTextAreaSection()}
</div>
{isEditMode && (
<EditModeControls
mode={mode}
onModeChange={handleModeChange}
modeShortcutText={modeShortcutText}
customModes={customModes}
customModePrompts={customModePrompts}
onCancel={onCancel}
onSend={onSend}
onSelectImages={onSelectImages}
sendingDisabled={sendingDisabled}
shouldDisableImages={shouldDisableImages}
/>
)}
</div>
{selectedImages.length > 0 && (
@ -1043,186 +1280,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
/>
)}
<div className={cn("flex", "justify-between", "items-center", "mt-auto")}>
<div className={cn("flex", "items-center", "gap-1", "min-w-0")}>
<div className="shrink-0">
<ModeSelector
value={mode}
title={t("chat:selectMode")}
onChange={(value) => {
setMode(value)
vscode.postMessage({ type: "mode", text: value })
}}
triggerClassName="w-full"
modeShortcutText={modeShortcutText}
customModes={customModes}
customModePrompts={customModePrompts}
/>
</div>
<div className={cn("flex-1", "min-w-0", "overflow-hidden")}>
<SelectDropdown
value={currentConfigId}
disabled={selectApiConfigDisabled}
title={t("chat:selectApiConfig")}
disableSearch={false}
placeholder={displayName}
options={[
// Pinned items first.
...(listApiConfigMeta || [])
.filter((config) => pinnedApiConfigs && pinnedApiConfigs[config.id])
.map((config) => ({
value: config.id,
label: config.name,
name: config.name, // Keep name for comparison with currentApiConfigName.
type: DropdownOptionType.ITEM,
pinned: true,
}))
.sort((a, b) => a.label.localeCompare(b.label)),
// If we have pinned items and unpinned items, add a separator.
...(pinnedApiConfigs &&
Object.keys(pinnedApiConfigs).length > 0 &&
(listApiConfigMeta || []).some((config) => !pinnedApiConfigs[config.id])
? [
{
value: "sep-pinned",
label: t("chat:separator"),
type: DropdownOptionType.SEPARATOR,
},
]
: []),
// Unpinned items sorted alphabetically.
...(listApiConfigMeta || [])
.filter((config) => !pinnedApiConfigs || !pinnedApiConfigs[config.id])
.map((config) => ({
value: config.id,
label: config.name,
name: config.name, // Keep name for comparison with currentApiConfigName.
type: DropdownOptionType.ITEM,
pinned: false,
}))
.sort((a, b) => a.label.localeCompare(b.label)),
{
value: "sep-2",
label: t("chat:separator"),
type: DropdownOptionType.SEPARATOR,
},
{
value: "settingsButtonClicked",
label: t("chat:edit"),
type: DropdownOptionType.ACTION,
},
]}
onChange={(value) => {
if (value === "settingsButtonClicked") {
vscode.postMessage({
type: "loadApiConfiguration",
text: value,
values: { section: "providers" },
})
} else {
vscode.postMessage({ type: "loadApiConfigurationById", text: value })
}
}}
triggerClassName="w-full text-ellipsis overflow-hidden"
itemClassName="group"
renderItem={({ type, value, label, pinned }) => {
if (type !== DropdownOptionType.ITEM) {
return label
}
const config = listApiConfigMeta?.find((c) => c.id === value)
const isCurrentConfig = config?.name === currentApiConfigName
return (
<div className="flex justify-between gap-2 w-full h-5">
<div
className={cn("truncate min-w-0 overflow-hidden", {
"font-medium": isCurrentConfig,
})}>
{label}
</div>
<div className="flex justify-end w-10 flex-shrink-0">
<div
className={cn("size-5 p-1", {
"block group-hover:hidden": !pinned,
hidden: !isCurrentConfig,
})}>
<Check className="size-3" />
</div>
<StandardTooltip content={pinned ? t("chat:unpin") : t("chat:pin")}>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
togglePinnedApiConfig(value)
vscode.postMessage({
type: "toggleApiConfigPin",
text: value,
})
}}
className={cn("size-5", {
"hidden group-hover:flex": !pinned,
"bg-accent": pinned,
})}>
<Pin className="size-3 p-0.5 opacity-50" />
</Button>
</StandardTooltip>
</div>
</div>
)
}}
/>
</div>
</div>
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
{isTtsPlaying && (
<StandardTooltip content={t("chat:stopTts")}>
<button
aria-label={t("chat:stopTts")}
onClick={() => vscode.postMessage({ type: "stopTts" })}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<VolumeX className="w-4 h-4" />
</button>
</StandardTooltip>
)}
<IndexingStatusBadge />
<StandardTooltip content={t("chat:addImages")}>
<button
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
"mr-1",
)}>
<Image className="w-4 h-4" />
</button>
</StandardTooltip>
</div>
</div>
{!isEditMode && renderNonEditModeControls()}
</div>
)
},

View file

@ -9,6 +9,7 @@ import useSound from "use-sound"
import { LRUCache } from "lru-cache"
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
import { appendImages } from "@src/utils/imageUtils"
import type { ClineAsk, ClineMessage } from "@roo-code/types"
@ -38,6 +39,8 @@ import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
import RooHero from "@src/components/welcome/RooHero"
import RooTips from "@src/components/welcome/RooTips"
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
import TelemetryBanner from "../common/TelemetryBanner"
import VersionIndicator from "../common/VersionIndicator"
@ -720,10 +723,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
}
break
case "selectedImages":
const newImages = message.images ?? []
if (newImages.length > 0) {
// Only handle selectedImages if it's not for editing context
// When context is "edit", ChatRow will handle the images
if (message.context !== "edit") {
setSelectedImages((prevImages) =>
[...prevImages, ...newImages].slice(0, MAX_IMAGES_PER_MESSAGE),
appendImages(prevImages, message.images, MAX_IMAGES_PER_MESSAGE),
)
}
break
@ -959,12 +963,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
[deniedCommands],
)
// Create toggles object for useAutoApprovalState hook
const autoApprovalToggles = useAutoApprovalToggles()
const { hasEnabledOptions } = useAutoApprovalState(autoApprovalToggles, autoApprovalEnabled)
const isAutoApproved = useCallback(
(message: ClineMessage | undefined) => {
// First check if auto-approval is enabled AND we have at least one permission
if (!autoApprovalEnabled || !message || message.type !== "ask") {
return false
}
// Use the hook's result instead of duplicating the logic
if (!hasEnabledOptions) {
return false
}
if (message.ask === "followup") {
return alwaysAllowFollowupQuestions
}
@ -1038,6 +1053,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
},
[
autoApprovalEnabled,
hasEnabledOptions,
alwaysAllowBrowser,
alwaysAllowReadOnly,
alwaysAllowReadOnlyOutsideWorkspace,

View file

@ -97,6 +97,10 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => {
.min(1, t("settings:codeIndex.validation.ollamaBaseUrlRequired"))
.url(t("settings:codeIndex.validation.invalidOllamaUrl")),
codebaseIndexEmbedderModelId: z.string().min(1, t("settings:codeIndex.validation.modelIdRequired")),
codebaseIndexEmbedderModelDimension: z
.number()
.min(1, t("settings:codeIndex.validation.modelDimensionRequired"))
.optional(),
})
case "openai-compatible":
@ -709,40 +713,50 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
<label className="text-sm font-medium">
{t("settings:codeIndex.modelLabel")}
</label>
<VSCodeDropdown
value={currentSettings.codebaseIndexEmbedderModelId}
onChange={(e: any) =>
<VSCodeTextField
value={currentSettings.codebaseIndexEmbedderModelId || ""}
onInput={(e: any) =>
updateSetting("codebaseIndexEmbedderModelId", e.target.value)
}
placeholder={t("settings:codeIndex.modelPlaceholder")}
className={cn("w-full", {
"border-red-500": formErrors.codebaseIndexEmbedderModelId,
})}>
<VSCodeOption value="" className="p-2">
{t("settings:codeIndex.selectModel")}
</VSCodeOption>
{getAvailableModels().map((modelId) => {
const model =
codebaseIndexModels?.[
currentSettings.codebaseIndexEmbedderProvider
]?.[modelId]
return (
<VSCodeOption key={modelId} value={modelId} className="p-2">
{modelId}{" "}
{model
? t("settings:codeIndex.modelDimensions", {
dimension: model.dimension,
})
: ""}
</VSCodeOption>
)
})}
</VSCodeDropdown>
/>
{formErrors.codebaseIndexEmbedderModelId && (
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
{formErrors.codebaseIndexEmbedderModelId}
</p>
)}
</div>
<div className="space-y-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.modelDimensionLabel")}
</label>
<VSCodeTextField
value={
currentSettings.codebaseIndexEmbedderModelDimension?.toString() ||
""
}
onInput={(e: any) => {
const value = e.target.value
? parseInt(e.target.value, 10) || undefined
: undefined
updateSetting("codebaseIndexEmbedderModelDimension", value)
}}
placeholder={t("settings:codeIndex.modelDimensionPlaceholder")}
className={cn("w-full", {
"border-red-500":
formErrors.codebaseIndexEmbedderModelDimension,
})}
/>
{formErrors.codebaseIndexEmbedderModelDimension && (
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
{formErrors.codebaseIndexEmbedderModelDimension}
</p>
)}
</div>
</>
)}
@ -835,7 +849,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
}
onInput={(e: any) => {
const value = e.target.value
? parseInt(e.target.value)
? parseInt(e.target.value, 10) || undefined
: undefined
updateSetting("codebaseIndexEmbedderModelDimension", value)
}}

View file

@ -0,0 +1,115 @@
import React from "react"
import { Mode } from "@roo/modes"
import { Button, StandardTooltip } from "@/components/ui"
import { Image, SendHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import ModeSelector from "./ModeSelector"
import { useAppTranslation } from "@/i18n/TranslationContext"
interface EditModeControlsProps {
mode: Mode
onModeChange: (value: Mode) => void
modeShortcutText: string
customModes: any
customModePrompts: any
onCancel?: () => void
onSend: () => void
onSelectImages: () => void
sendingDisabled: boolean
shouldDisableImages: boolean
}
export const EditModeControls: React.FC<EditModeControlsProps> = ({
mode,
onModeChange,
modeShortcutText,
customModes,
customModePrompts,
onCancel,
onSend,
onSelectImages,
sendingDisabled,
shouldDisableImages,
}) => {
const { t } = useAppTranslation()
return (
<div
className={cn(
"flex",
"items-center",
"justify-between",
"absolute",
"bottom-2",
"left-2",
"right-2",
"z-30",
)}>
<div className={cn("flex", "items-center", "gap-1", "flex-1", "min-w-0")}>
<div className="shrink-0">
<ModeSelector
value={mode}
title={t("chat:selectMode")}
onChange={onModeChange}
triggerClassName="w-full"
modeShortcutText={modeShortcutText}
customModes={customModes}
customModePrompts={customModePrompts}
/>
</div>
</div>
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0", "ml-2")}>
<Button
variant="secondary"
size="sm"
onClick={onCancel}
disabled={sendingDisabled}
className="text-xs bg-vscode-toolbar-hoverBackground hover:bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground">
Cancel
</Button>
<StandardTooltip content={t("chat:addImages")}>
<button
aria-label={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<Image className="w-4 h-4" />
</button>
</StandardTooltip>
<StandardTooltip content={t("chat:save.tooltip")}>
<button
aria-label={t("chat:save.tooltip")}
disabled={sendingDisabled}
onClick={!sendingDisabled ? onSend : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</StandardTooltip>
</div>
</div>
)
}

View file

@ -0,0 +1,62 @@
import React from "react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@src/components/ui"
interface MessageModificationConfirmationDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
onConfirm: () => void
type: "edit" | "delete"
}
export const MessageModificationConfirmationDialog: React.FC<MessageModificationConfirmationDialogProps> = ({
open,
onOpenChange,
onConfirm,
type,
}) => {
const { t } = useAppTranslation()
const isEdit = type === "edit"
const title = isEdit ? t("common:confirmation.editMessage") : t("common:confirmation.deleteMessage")
const description = isEdit ? t("common:confirmation.editWarning") : t("common:confirmation.deleteWarning")
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="text-lg">{title}</AlertDialogTitle>
<AlertDialogDescription className="text-base">{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className="flex-col gap-2">
<AlertDialogCancel className="bg-vscode-button-secondaryBackground hover:bg-vscode-button-secondaryHoverBackground text-vscode-button-secondaryForeground border-vscode-button-border">
{t("common:answers.cancel")}
</AlertDialogCancel>
<AlertDialogAction
onClick={onConfirm}
className="bg-vscode-button-background hover:bg-vscode-button-hoverBackground text-vscode-button-foreground border-vscode-button-border">
{t("common:confirmation.proceed")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
// Export convenience components for backward compatibility
export const EditMessageDialog: React.FC<Omit<MessageModificationConfirmationDialogProps, "type">> = (props) => (
<MessageModificationConfirmationDialog {...props} type="edit" />
)
export const DeleteMessageDialog: React.FC<Omit<MessageModificationConfirmationDialogProps, "type">> = (props) => (
<MessageModificationConfirmationDialog {...props} type="delete" />
)

View file

@ -0,0 +1,307 @@
import { render, fireEvent, screen, waitFor } from "@/utils/test-utils"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import AutoApproveMenu from "../AutoApproveMenu"
// Mock vscode API
vi.mock("@src/utils/vscode", () => ({
vscode: {
postMessage: vi.fn(),
},
}))
// Mock ExtensionStateContext
vi.mock("@src/context/ExtensionStateContext")
// Mock translation hook
vi.mock("@src/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
"chat:autoApprove.title": "Auto-approve",
"chat:autoApprove.none": "None selected",
"chat:autoApprove.selectOptionsFirst": "Select at least one option below to enable auto-approval",
"chat:autoApprove.description": "Configure auto-approval settings",
"settings:autoApprove.readOnly.label": "Read-only operations",
"settings:autoApprove.write.label": "Write operations",
"settings:autoApprove.execute.label": "Execute operations",
"settings:autoApprove.browser.label": "Browser operations",
"settings:autoApprove.modeSwitch.label": "Mode switches",
"settings:autoApprove.mcp.label": "MCP operations",
"settings:autoApprove.subtasks.label": "Subtasks",
"settings:autoApprove.resubmit.label": "Resubmit",
"settings:autoApprove.followupQuestions.label": "Follow-up questions",
"settings:autoApprove.updateTodoList.label": "Update todo list",
"settings:autoApprove.apiRequestLimit.title": "API request limit",
"settings:autoApprove.apiRequestLimit.unlimited": "Unlimited",
"settings:autoApprove.apiRequestLimit.description": "Limit the number of API requests",
"settings:autoApprove.readOnly.outsideWorkspace": "Also allow outside workspace",
"settings:autoApprove.write.outsideWorkspace": "Also allow outside workspace",
"settings:autoApprove.write.delay": "Delay",
}
return translations[key] || key
},
}),
}))
// Get the mocked postMessage function
const mockPostMessage = vscode.postMessage as ReturnType<typeof vi.fn>
describe("AutoApproveMenu", () => {
const defaultExtensionState = {
autoApprovalEnabled: true,
alwaysAllowReadOnly: false,
alwaysAllowReadOnlyOutsideWorkspace: false,
alwaysAllowWrite: false,
alwaysAllowWriteOutsideWorkspace: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysAllowModeSwitch: false,
alwaysAllowSubtasks: false,
alwaysApproveResubmit: false,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: false,
writeDelayMs: 3000,
allowedMaxRequests: undefined,
setAutoApprovalEnabled: vi.fn(),
setAlwaysAllowReadOnly: vi.fn(),
setAlwaysAllowWrite: vi.fn(),
setAlwaysAllowExecute: vi.fn(),
setAlwaysAllowBrowser: vi.fn(),
setAlwaysAllowMcp: vi.fn(),
setAlwaysAllowModeSwitch: vi.fn(),
setAlwaysAllowSubtasks: vi.fn(),
setAlwaysApproveResubmit: vi.fn(),
setAlwaysAllowFollowupQuestions: vi.fn(),
setAlwaysAllowUpdateTodoList: vi.fn(),
setAllowedMaxRequests: vi.fn(),
}
beforeEach(() => {
vi.clearAllMocks()
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue(defaultExtensionState)
})
describe("Master checkbox behavior", () => {
it("should show 'None selected' when no sub-options are selected", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: false,
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
})
render(<AutoApproveMenu />)
// Check that the text shows "None selected"
expect(screen.getByText("None selected")).toBeInTheDocument()
})
it("should show enabled options when sub-options are selected", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
})
render(<AutoApproveMenu />)
// Check that the text shows the enabled option
expect(screen.getByText("Read-only operations")).toBeInTheDocument()
})
it("should not allow toggling master checkbox when no options are selected", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: false,
alwaysAllowReadOnly: false,
})
render(<AutoApproveMenu />)
// Click on the master checkbox
const masterCheckbox = screen.getByRole("checkbox")
fireEvent.click(masterCheckbox)
// Should not send any message since no options are selected
expect(mockPostMessage).not.toHaveBeenCalled()
})
it("should toggle master checkbox when options are selected", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
})
render(<AutoApproveMenu />)
// Click on the master checkbox
const masterCheckbox = screen.getByRole("checkbox")
fireEvent.click(masterCheckbox)
// Should toggle the master checkbox
expect(mockPostMessage).toHaveBeenCalledWith({
type: "autoApprovalEnabled",
bool: false,
})
})
})
describe("Sub-option toggles", () => {
it("should toggle read-only operations", async () => {
const mockSetAlwaysAllowReadOnly = vi.fn()
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly,
})
render(<AutoApproveMenu />)
// Expand the menu
const menuContainer = screen.getByText("Auto-approve").parentElement
fireEvent.click(menuContainer!)
// Wait for the menu to expand and find the read-only button
await waitFor(() => {
expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()
})
const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle")
fireEvent.click(readOnlyButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "alwaysAllowReadOnly",
bool: true,
})
})
it("should toggle write operations", async () => {
const mockSetAlwaysAllowWrite = vi.fn()
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
setAlwaysAllowWrite: mockSetAlwaysAllowWrite,
})
render(<AutoApproveMenu />)
// Expand the menu
const menuContainer = screen.getByText("Auto-approve").parentElement
fireEvent.click(menuContainer!)
await waitFor(() => {
expect(screen.getByTestId("always-allow-write-toggle")).toBeInTheDocument()
})
const writeButton = screen.getByTestId("always-allow-write-toggle")
fireEvent.click(writeButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "alwaysAllowWrite",
bool: true,
})
})
})
describe("Complex scenarios", () => {
it("should display multiple enabled options in summary text", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
})
render(<AutoApproveMenu />)
// Should show all enabled options in the summary
expect(screen.getByText("Read-only operations, Write operations, Execute operations")).toBeInTheDocument()
})
it("should handle enabling first option when none selected", async () => {
const mockSetAutoApprovalEnabled = vi.fn()
const mockSetAlwaysAllowReadOnly = vi.fn()
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: false,
alwaysAllowReadOnly: false,
setAutoApprovalEnabled: mockSetAutoApprovalEnabled,
setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly,
})
render(<AutoApproveMenu />)
// Expand the menu
const menuContainer = screen.getByText("Auto-approve").parentElement
fireEvent.click(menuContainer!)
await waitFor(() => {
expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()
})
// Enable read-only
const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle")
fireEvent.click(readOnlyButton)
// Should enable the sub-option
expect(mockPostMessage).toHaveBeenCalledWith({
type: "alwaysAllowReadOnly",
bool: true,
})
// Should also enable master auto-approval
expect(mockPostMessage).toHaveBeenCalledWith({
type: "autoApprovalEnabled",
bool: true,
})
})
it("should handle disabling last option", async () => {
const mockSetAutoApprovalEnabled = vi.fn()
const mockSetAlwaysAllowReadOnly = vi.fn()
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
setAutoApprovalEnabled: mockSetAutoApprovalEnabled,
setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly,
})
render(<AutoApproveMenu />)
// Expand the menu
const menuContainer = screen.getByText("Auto-approve").parentElement
fireEvent.click(menuContainer!)
await waitFor(() => {
expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()
})
// Disable read-only (the last enabled option)
const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle")
fireEvent.click(readOnlyButton)
// Should disable the sub-option
expect(mockPostMessage).toHaveBeenCalledWith({
type: "alwaysAllowReadOnly",
bool: false,
})
// Should also disable master auto-approval
expect(mockPostMessage).toHaveBeenCalledWith({
type: "autoApprovalEnabled",
bool: false,
})
})
})
})

View file

@ -920,4 +920,54 @@ describe("ChatTextArea", () => {
expect(apiConfigDropdown).toHaveAttribute("disabled")
})
})
describe("edit mode integration", () => {
it("should render edit mode UI when isEditMode is true", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
filePaths: [],
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
customModes: [],
customModePrompts: {},
})
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
// The edit mode UI should be rendered
// We can verify this by checking for the presence of elements that are unique to edit mode
const cancelButton = screen.getByRole("button", { name: /cancel/i })
expect(cancelButton).toBeInTheDocument()
// Should show save button instead of send button
const saveButton = screen.getByRole("button", { name: /save/i })
expect(saveButton).toBeInTheDocument()
// Should not show send button in edit mode
const sendButton = screen.queryByRole("button", { name: /send.*message/i })
expect(sendButton).not.toBeInTheDocument()
})
it("should not render edit mode UI when isEditMode is false", () => {
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
filePaths: [],
openedTabs: [],
taskHistory: [],
cwd: "/test/workspace",
})
render(<ChatTextArea {...defaultProps} isEditMode={false} />)
// The edit mode UI should not be rendered
const cancelButton = screen.queryByRole("button", { name: /cancel/i })
expect(cancelButton).not.toBeInTheDocument()
// Should show send button when not in edit mode
const sendButton = screen.getByRole("button", { name: /send.*message/i })
expect(sendButton).toBeInTheDocument()
// Should not show save button when not in edit mode
const saveButton = screen.queryByRole("button", { name: /save/i })
expect(saveButton).not.toBeInTheDocument()
})
})
})

View file

@ -0,0 +1,480 @@
// npx vitest run src/components/chat/__tests__/ChatView.auto-approve-new.spec.tsx
import { render, waitFor } from "@/utils/test-utils"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ExtensionStateContextProvider } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import ChatView, { ChatViewProps } from "../ChatView"
// Mock vscode API
vi.mock("@src/utils/vscode", () => ({
vscode: {
postMessage: vi.fn(),
},
}))
// Mock all problematic dependencies
vi.mock("rehype-highlight", () => ({
default: () => () => {},
}))
vi.mock("hast-util-to-text", () => ({
default: () => "",
}))
// Mock components that use ESM dependencies
vi.mock("../BrowserSessionRow", () => ({
default: function MockBrowserSessionRow({ messages }: { messages: any[] }) {
return <div data-testid="browser-session">{JSON.stringify(messages)}</div>
},
}))
vi.mock("../ChatRow", () => ({
default: function MockChatRow({ message }: { message: any }) {
return <div data-testid="chat-row">{JSON.stringify(message)}</div>
},
}))
vi.mock("../TaskHeader", () => ({
default: function MockTaskHeader({ task }: { task: any }) {
return <div data-testid="task-header">{JSON.stringify(task)}</div>
},
}))
vi.mock("../AutoApproveMenu", () => ({
default: () => null,
}))
vi.mock("@src/components/common/CodeBlock", () => ({
default: () => null,
CODE_BLOCK_BG_COLOR: "rgb(30, 30, 30)",
}))
vi.mock("@src/components/common/CodeAccordion", () => ({
default: () => null,
}))
vi.mock("@src/components/chat/ContextMenu", () => ({
default: () => null,
}))
// Mock window.postMessage to trigger state hydration
const mockPostMessage = (state: any) => {
window.postMessage(
{
type: "state",
state: {
version: "1.0.0",
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
allowedCommands: [],
alwaysAllowExecute: false,
autoApprovalEnabled: true,
...state,
},
},
"*",
)
}
const queryClient = new QueryClient()
const defaultProps: ChatViewProps = {
isHidden: false,
showAnnouncement: false,
hideAnnouncement: () => {},
}
const renderChatView = (props: Partial<ChatViewProps> = {}) => {
return render(
<ExtensionStateContextProvider>
<QueryClientProvider client={queryClient}>
<ChatView {...defaultProps} {...props} />
</QueryClientProvider>
</ExtensionStateContextProvider>,
)
}
describe("ChatView - New Auto Approval Logic Tests", () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe("Master auto-approval with no sub-options enabled", () => {
it("should NOT auto-approve when autoApprovalEnabled is true but no sub-options are enabled", async () => {
renderChatView()
// First hydrate state with initial task
mockPostMessage({
autoApprovalEnabled: true, // Master is enabled
alwaysAllowReadOnly: false, // But no sub-options are enabled
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then send a read tool ask message
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify({ tool: "readFile", path: "test.txt" }),
partial: false,
},
],
})
// Wait and verify no auto-approval message was sent
await new Promise((resolve) => setTimeout(resolve, 100))
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
it("should NOT auto-approve write operations when only master is enabled", async () => {
renderChatView()
// First hydrate state with initial task
mockPostMessage({
autoApprovalEnabled: true, // Master is enabled
alwaysAllowReadOnly: false,
alwaysAllowWrite: false, // Write is not enabled
writeDelayMs: 0,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then send a write tool ask message
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
writeDelayMs: 0,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify({ tool: "editedExistingFile", path: "test.txt" }),
partial: false,
},
],
})
// Wait and verify no auto-approval message was sent
await new Promise((resolve) => setTimeout(resolve, 100))
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
it("should NOT auto-approve browser actions when only master is enabled", async () => {
renderChatView()
// First hydrate state with initial task
mockPostMessage({
autoApprovalEnabled: true, // Master is enabled
alwaysAllowBrowser: false, // Browser is not enabled
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then send a browser action ask message
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowBrowser: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "browser_action_launch",
ts: Date.now(),
text: JSON.stringify({ action: "launch", url: "http://example.com" }),
partial: false,
},
],
})
// Wait and verify no auto-approval message was sent
await new Promise((resolve) => setTimeout(resolve, 100))
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
})
describe("Correct auto-approval with sub-options enabled", () => {
it("should auto-approve when master and at least one sub-option are enabled", async () => {
renderChatView()
// First hydrate state with initial task
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: true, // At least one sub-option is enabled
alwaysAllowWrite: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then send a read tool ask message
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify({ tool: "readFile", path: "test.txt" }),
partial: false,
},
],
})
// Wait for the auto-approval message
await waitFor(() => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
})
it("should auto-approve when multiple sub-options are enabled", async () => {
renderChatView()
// First hydrate state with initial task
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: true, // Multiple sub-options enabled
alwaysAllowWrite: true,
alwaysAllowExecute: true,
writeDelayMs: 0,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then send a write tool ask message
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
writeDelayMs: 0,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify({ tool: "editedExistingFile", path: "test.txt" }),
partial: false,
},
],
})
// Wait for the auto-approval message
await waitFor(() => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
})
})
describe("Edge cases", () => {
it("should handle state transitions correctly", async () => {
renderChatView()
// Start with auto-approval properly configured
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Then transition to a state where no sub-options are enabled
mockPostMessage({
autoApprovalEnabled: true, // Master still true
alwaysAllowReadOnly: false, // All sub-options now false
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify({ tool: "readFile", path: "test.txt" }),
partial: false,
},
],
})
// Wait and verify no auto-approval message was sent
await new Promise((resolve) => setTimeout(resolve, 100))
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
})
it("should respect the hasEnabledOptions check in isAutoApproved", async () => {
renderChatView()
// Configure state where master is true but effective approval should be false
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: false,
alwaysAllowReadOnlyOutsideWorkspace: false,
alwaysAllowWrite: false,
alwaysAllowWriteOutsideWorkspace: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})
// Try various tool types - none should auto-approve
const toolRequests = [
{ tool: "readFile", path: "test.txt" },
{ tool: "editedExistingFile", path: "test.txt" },
{ tool: "executeCommand", command: "ls" },
{ tool: "switchMode", mode: "architect" },
]
for (const toolRequest of toolRequests) {
vi.clearAllMocks()
mockPostMessage({
autoApprovalEnabled: true,
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowModeSwitch: false,
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
text: JSON.stringify(toolRequest),
partial: false,
},
],
})
// Wait and verify no auto-approval for any tool type
await new Promise((resolve) => setTimeout(resolve, 100))
expect(vscode.postMessage).not.toHaveBeenCalledWith({
type: "askResponse",
askResponse: "yesButtonClicked",
})
}
})
})
})

View file

@ -0,0 +1,138 @@
import React from "react"
import { render, screen, fireEvent } from "@testing-library/react"
import { describe, it, expect, vi, beforeEach } from "vitest"
import { EditModeControls } from "../EditModeControls"
import { Mode } from "@roo/modes"
// Mock the translation hook
vi.mock("@/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
t: (key: string) => key,
}),
}))
// Mock the UI components
vi.mock("@/components/ui", () => ({
Button: ({ children, onClick, disabled, ...props }: any) => (
<button onClick={onClick} disabled={disabled} {...props}>
{children}
</button>
),
StandardTooltip: ({ children, content }: any) => <div title={content}>{children}</div>,
}))
// Mock ModeSelector
vi.mock("../ModeSelector", () => ({
default: ({ value, onChange, title }: any) => (
<select value={value} onChange={(e) => onChange(e.target.value)} title={title}>
<option value="code">Code</option>
<option value="architect">Architect</option>
</select>
),
}))
describe("EditModeControls", () => {
const defaultProps = {
mode: "code" as Mode,
onModeChange: vi.fn(),
modeShortcutText: "Ctrl+M",
customModes: [],
customModePrompts: {},
onCancel: vi.fn(),
onSend: vi.fn(),
onSelectImages: vi.fn(),
sendingDisabled: false,
shouldDisableImages: false,
}
beforeEach(() => {
vi.clearAllMocks()
})
it("renders all controls correctly", () => {
render(<EditModeControls {...defaultProps} />)
// Check for mode selector
expect(screen.getByTitle("chat:selectMode")).toBeInTheDocument()
// Check for Cancel button
expect(screen.getByText("Cancel")).toBeInTheDocument()
// Check for image button
expect(screen.getByTitle("chat:addImages")).toBeInTheDocument()
// Check for send button
expect(screen.getByTitle("chat:save.tooltip")).toBeInTheDocument()
})
it("calls onCancel when Cancel button is clicked", () => {
render(<EditModeControls {...defaultProps} />)
const cancelButton = screen.getByText("Cancel")
fireEvent.click(cancelButton)
expect(defaultProps.onCancel).toHaveBeenCalledTimes(1)
})
it("calls onSend when send button is clicked", () => {
render(<EditModeControls {...defaultProps} />)
const sendButton = screen.getByLabelText("chat:save.tooltip")
fireEvent.click(sendButton)
expect(defaultProps.onSend).toHaveBeenCalledTimes(1)
})
it("calls onSelectImages when image button is clicked", () => {
render(<EditModeControls {...defaultProps} />)
const imageButton = screen.getByLabelText("chat:addImages")
fireEvent.click(imageButton)
expect(defaultProps.onSelectImages).toHaveBeenCalledTimes(1)
})
it("disables buttons when sendingDisabled is true", () => {
render(<EditModeControls {...defaultProps} sendingDisabled={true} />)
const cancelButton = screen.getByText("Cancel")
const sendButton = screen.getByLabelText("chat:save.tooltip")
expect(cancelButton).toBeDisabled()
expect(sendButton).toBeDisabled()
})
it("disables image button when shouldDisableImages is true", () => {
render(<EditModeControls {...defaultProps} shouldDisableImages={true} />)
const imageButton = screen.getByLabelText("chat:addImages")
expect(imageButton).toBeDisabled()
})
it("does not call onSelectImages when image button is disabled", () => {
render(<EditModeControls {...defaultProps} shouldDisableImages={true} />)
const imageButton = screen.getByLabelText("chat:addImages")
fireEvent.click(imageButton)
expect(defaultProps.onSelectImages).not.toHaveBeenCalled()
})
it("does not call onSend when send button is disabled", () => {
render(<EditModeControls {...defaultProps} sendingDisabled={true} />)
const sendButton = screen.getByLabelText("chat:save.tooltip")
fireEvent.click(sendButton)
expect(defaultProps.onSend).not.toHaveBeenCalled()
})
it("calls onModeChange when mode is changed", () => {
render(<EditModeControls {...defaultProps} />)
const modeSelector = screen.getByTitle("chat:selectMode")
fireEvent.change(modeSelector, { target: { value: "architect" } })
expect(defaultProps.onModeChange).toHaveBeenCalledWith("architect")
})
})

View file

@ -110,6 +110,10 @@ const ModesView = ({ onDone }: ModesViewProps) => {
const [searchValue, setSearchValue] = useState("")
const searchInputRef = useRef<HTMLInputElement>(null)
// Local state for mode name input to allow visual emptying
const [localModeName, setLocalModeName] = useState<string>("")
const [currentEditingModeSlug, setCurrentEditingModeSlug] = useState<string | null>(null)
// Direct update functions
const updateAgentPrompt = useCallback(
(mode: Mode, promptData: PromptComponent) => {
@ -218,6 +222,14 @@ const ModesView = ({ onDone }: ModesViewProps) => {
}
}, [getCurrentMode, checkRulesDirectory, hasRulesToExport])
// Reset local name state when mode changes
useEffect(() => {
if (currentEditingModeSlug && currentEditingModeSlug !== visualMode) {
setCurrentEditingModeSlug(null)
setLocalModeName("")
}
}, [visualMode, currentEditingModeSlug])
// Helper function to safely access mode properties
const getModeProperty = <T extends keyof ModeConfig>(
mode: ModeConfig | undefined,
@ -725,16 +737,34 @@ const ModesView = ({ onDone }: ModesViewProps) => {
<div className="flex gap-2">
<Input
type="text"
value={getModeProperty(findModeBySlug(visualMode, customModes), "name") ?? ""}
onChange={(e) => {
value={
currentEditingModeSlug === visualMode
? localModeName
: (getModeProperty(findModeBySlug(visualMode, customModes), "name") ??
"")
}
onFocus={() => {
const customMode = findModeBySlug(visualMode, customModes)
if (customMode) {
setCurrentEditingModeSlug(visualMode)
setLocalModeName(customMode.name)
}
}}
onChange={(e) => {
setLocalModeName(e.target.value)
}}
onBlur={() => {
const customMode = findModeBySlug(visualMode, customModes)
if (customMode && localModeName.trim()) {
// Only update if the name is not empty
updateCustomMode(visualMode, {
...customMode,
name: e.target.value,
name: localModeName,
source: customMode.source || "global",
})
}
// Clear the editing state
setCurrentEditingModeSlug(null)
}}
className="w-full"
/>

View file

@ -167,10 +167,10 @@ const ApiOptions = ({
// Update `apiModelId` whenever `selectedModelId` changes.
useEffect(() => {
if (selectedModelId) {
if (selectedModelId && apiConfiguration.apiModelId !== selectedModelId) {
setApiConfigurationField("apiModelId", selectedModelId)
}
}, [selectedModelId, setApiConfigurationField])
}, [selectedModelId, setApiConfigurationField, apiConfiguration.apiModelId])
// Debounced refresh model updates, only executed 250ms after the user
// stops typing.

View file

@ -4,12 +4,15 @@ import { X } from "lucide-react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "@/utils/vscode"
import { Button, Input, Slider } from "@/components/ui"
import { Button, Input, Slider, StandardTooltip } from "@/components/ui"
import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
import { AutoApproveToggle } from "./AutoApproveToggle"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAutoApprovalState } from "@/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
alwaysAllowReadOnly?: boolean
@ -77,6 +80,11 @@ export const AutoApproveSettings = ({
const { t } = useAppTranslation()
const [commandInput, setCommandInput] = useState("")
const [deniedCommandInput, setDeniedCommandInput] = useState("")
const { autoApprovalEnabled, setAutoApprovalEnabled } = useExtensionState()
const toggles = useAutoApprovalToggles()
const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)
const handleAddCommand = () => {
const currentCommands = allowedCommands ?? []
@ -104,6 +112,30 @@ export const AutoApproveSettings = ({
<div {...props}>
<SectionHeader description={t("settings:autoApprove.description")}>
<div className="flex items-center gap-2">
{!hasEnabledOptions ? (
<StandardTooltip content={t("settings:autoApprove.selectOptionsFirst")}>
<VSCodeCheckbox
checked={effectiveAutoApprovalEnabled}
disabled={!hasEnabledOptions}
aria-label={t("settings:autoApprove.disabledAriaLabel")}
onChange={() => {
// Do nothing when no options are enabled
return
}}
/>
</StandardTooltip>
) : (
<VSCodeCheckbox
checked={effectiveAutoApprovalEnabled}
disabled={!hasEnabledOptions}
aria-label={t("settings:autoApprove.toggleAriaLabel")}
onChange={() => {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}}
/>
)}
<span className="codicon codicon-check w-4" />
<div>{t("settings:sections.autoApprove")}</div>
</div>

View file

@ -218,7 +218,15 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
return prevState
}
setChangeDetected(true)
const previousValue = prevState.apiConfiguration?.[field]
// Don't treat initial sync from undefined to a defined value as a user change
// This prevents the dirty state when the component initializes and auto-syncs the model ID
const isInitialSync = previousValue === undefined && value !== undefined
if (!isInitialSync) {
setChangeDetected(true)
}
return { ...prevState, apiConfiguration: { ...prevState.apiConfiguration, [field]: value } }
})
},

View file

@ -0,0 +1,282 @@
import { renderHook } from "@testing-library/react"
import { useAutoApprovalState } from "../useAutoApprovalState"
describe("useAutoApprovalState", () => {
describe("hasEnabledOptions", () => {
it("should return false when all toggles are false", () => {
const toggles = {
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysAllowModeSwitch: false,
alwaysAllowSubtasks: false,
alwaysApproveResubmit: false,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: false,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(false)
})
it("should return false when all toggles are undefined", () => {
const toggles = {
alwaysAllowReadOnly: undefined,
alwaysAllowWrite: undefined,
alwaysAllowExecute: undefined,
alwaysAllowBrowser: undefined,
alwaysAllowMcp: undefined,
alwaysAllowModeSwitch: undefined,
alwaysAllowSubtasks: undefined,
alwaysApproveResubmit: undefined,
alwaysAllowFollowupQuestions: undefined,
alwaysAllowUpdateTodoList: undefined,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(false)
})
it("should return true when at least one toggle is true", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysAllowModeSwitch: false,
alwaysAllowSubtasks: false,
alwaysApproveResubmit: false,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: false,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(true)
})
it("should return true when multiple toggles are true", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysAllowModeSwitch: false,
alwaysAllowSubtasks: false,
alwaysApproveResubmit: false,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: false,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(true)
})
it("should return true when all toggles are true", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
alwaysAllowBrowser: true,
alwaysAllowMcp: true,
alwaysAllowModeSwitch: true,
alwaysAllowSubtasks: true,
alwaysApproveResubmit: true,
alwaysAllowFollowupQuestions: true,
alwaysAllowUpdateTodoList: true,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(true)
})
})
describe("effectiveAutoApprovalEnabled", () => {
it("should return false when autoApprovalEnabled is false regardless of toggles", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, false))
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
it("should return false when autoApprovalEnabled is undefined regardless of toggles", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
alwaysAllowExecute: true,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, undefined))
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
it("should return false when autoApprovalEnabled is true but no toggles are enabled", () => {
const toggles = {
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysAllowModeSwitch: false,
alwaysAllowSubtasks: false,
alwaysApproveResubmit: false,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: false,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
it("should return true when autoApprovalEnabled is true and at least one toggle is enabled", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
})
})
describe("memoization", () => {
it("should not recompute hasEnabledOptions when toggles object reference changes but values are the same", () => {
const initialToggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
}
const { result, rerender } = renderHook(
({ toggles, autoApprovalEnabled }) => useAutoApprovalState(toggles, autoApprovalEnabled),
{
initialProps: {
toggles: initialToggles,
autoApprovalEnabled: true,
},
},
)
const firstHasEnabledOptions = result.current.hasEnabledOptions
const firstEffectiveAutoApprovalEnabled = result.current.effectiveAutoApprovalEnabled
// Create new object with same values
const newToggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
}
rerender({ toggles: newToggles, autoApprovalEnabled: true })
// The computed values should be the same due to memoization
expect(result.current.hasEnabledOptions).toBe(firstHasEnabledOptions)
expect(result.current.effectiveAutoApprovalEnabled).toBe(firstEffectiveAutoApprovalEnabled)
})
it("should recompute when toggle values change", () => {
const initialToggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
}
const { result, rerender } = renderHook(
({ toggles, autoApprovalEnabled }) => useAutoApprovalState(toggles, autoApprovalEnabled),
{
initialProps: {
toggles: initialToggles,
autoApprovalEnabled: true,
},
},
)
expect(result.current.hasEnabledOptions).toBe(true)
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
// Change toggle values
const newToggles = {
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
}
rerender({ toggles: newToggles, autoApprovalEnabled: true })
expect(result.current.hasEnabledOptions).toBe(false)
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
it("should recompute effectiveAutoApprovalEnabled when autoApprovalEnabled changes", () => {
const toggles = {
alwaysAllowReadOnly: true,
alwaysAllowWrite: false,
}
const { result, rerender } = renderHook(
({ toggles, autoApprovalEnabled }) => useAutoApprovalState(toggles, autoApprovalEnabled),
{
initialProps: {
toggles,
autoApprovalEnabled: true,
},
},
)
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
rerender({ toggles, autoApprovalEnabled: false })
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
})
describe("edge cases", () => {
it("should handle partial toggle objects", () => {
const toggles = {
alwaysAllowReadOnly: true,
// Other properties are optional
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(true)
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
})
it("should handle empty toggle object", () => {
const toggles = {}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(false)
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
})
it("should handle mixed truthy/falsy values correctly", () => {
const toggles = {
alwaysAllowReadOnly: 1 as any, // truthy non-boolean
alwaysAllowWrite: "" as any, // falsy non-boolean
alwaysAllowExecute: null as any, // falsy non-boolean
alwaysAllowBrowser: "yes" as any, // truthy non-boolean
}
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
expect(result.current.hasEnabledOptions).toBe(true) // Because some values are truthy
})
})
})

View file

@ -0,0 +1,29 @@
import { useMemo } from "react"
interface AutoApprovalToggles {
alwaysAllowReadOnly?: boolean
alwaysAllowWrite?: boolean
alwaysAllowExecute?: boolean
alwaysAllowBrowser?: boolean
alwaysAllowMcp?: boolean
alwaysAllowModeSwitch?: boolean
alwaysAllowSubtasks?: boolean
alwaysApproveResubmit?: boolean
alwaysAllowFollowupQuestions?: boolean
alwaysAllowUpdateTodoList?: boolean
}
export function useAutoApprovalState(toggles: AutoApprovalToggles, autoApprovalEnabled?: boolean) {
const hasEnabledOptions = useMemo(() => {
return Object.values(toggles).some((value) => !!value)
}, [toggles])
const effectiveAutoApprovalEnabled = useMemo(() => {
return hasEnabledOptions && (autoApprovalEnabled ?? false)
}, [hasEnabledOptions, autoApprovalEnabled])
return {
hasEnabledOptions,
effectiveAutoApprovalEnabled,
}
}

View file

@ -0,0 +1,50 @@
import { useMemo } from "react"
import { useExtensionState } from "@src/context/ExtensionStateContext"
/**
* Custom hook that creates and returns the auto-approval toggles object
* This encapsulates the logic for creating the toggles object from extension state
*/
export function useAutoApprovalToggles() {
const {
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
} = useExtensionState()
const toggles = useMemo(
() => ({
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
}),
[
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
],
)
return toggles
}

View file

@ -44,7 +44,7 @@
},
"save": {
"title": "Desar",
"tooltip": "Desa els canvis del fitxer"
"tooltip": "Desa els canvis del missatge"
},
"reject": {
"title": "Rebutjar",
@ -223,7 +223,10 @@
"autoApprove": {
"title": "Aprovació automàtica:",
"none": "Cap",
"description": "L'aprovació automàtica permet a Roo Code realitzar accions sense demanar permís. Activa-la només per a accions en les que confies plenament. Configuració més detallada disponible a la <settingsLink>Configuració</settingsLink>."
"description": "L'aprovació automàtica permet a Roo Code realitzar accions sense demanar permís. Activa-la només per a accions en les que confies plenament. Configuració més detallada disponible a la <settingsLink>Configuració</settingsLink>.",
"selectOptionsFirst": "Selecciona almenys una opció a continuació per activar l'aprovació automàtica",
"toggleAriaLabel": "Commuta l'aprovació automàtica",
"disabledAriaLabel": "Aprovació automàtica desactivada: seleccioneu primer les opcions"
},
"reasoning": {
"thinking": "Pensant",
@ -319,5 +322,8 @@
},
"versionIndicator": {
"ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament"
},
"editMessage": {
"placeholder": "Edita el teu missatge..."
}
}

View file

@ -51,5 +51,12 @@
"success": {
"imageDataUriCopied": "URI de dades de la imatge copiada al porta-retalls"
}
},
"confirmation": {
"deleteMessage": "Eliminar missatge",
"deleteWarning": "Eliminar aquest missatge eliminarà tots els missatges posteriors de la conversa. Vols continuar?",
"editMessage": "Editar missatge",
"editWarning": "Editar aquest missatge eliminarà tots els missatges posteriors de la conversa. Vols continuar?",
"proceed": "Continuar"
}
}

View file

@ -123,6 +123,8 @@
},
"autoApprove": {
"description": "Permet que Roo realitzi operacions automàticament sense requerir aprovació. Activeu aquesta configuració només si confieu plenament en la IA i enteneu els riscos de seguretat associats.",
"toggleAriaLabel": "Commuta l'aprovació automàtica",
"disabledAriaLabel": "Aprovació automàtica desactivada: seleccioneu primer les opcions",
"readOnly": {
"label": "Llegir",
"description": "Quan està activat, Roo veurà automàticament el contingut del directori i llegirà fitxers sense que calgui fer clic al botó Aprovar.",
@ -190,7 +192,8 @@
"title": "Màximes Sol·licituds",
"description": "Fes aquesta quantitat de sol·licituds API automàticament abans de demanar aprovació per continuar amb la tasca.",
"unlimited": "Il·limitat"
}
},
"selectOptionsFirst": "Seleccioneu almenys una opció a continuació per activar l'aprovació automàtica"
},
"providers": {
"providerDocumentation": "Documentació de {{provider}}",

View file

@ -44,7 +44,7 @@
},
"save": {
"title": "Speichern",
"tooltip": "Dateiänderungen speichern"
"tooltip": "Nachrichtenänderungen speichern"
},
"reject": {
"title": "Ablehnen",
@ -223,7 +223,10 @@
"autoApprove": {
"title": "Automatische Genehmigung:",
"none": "Keine",
"description": "Automatische Genehmigung erlaubt Roo Code, Aktionen ohne Nachfrage auszuführen. Aktiviere dies nur für Aktionen, denen du vollständig vertraust. Detailliertere Konfiguration verfügbar in den <settingsLink>Einstellungen</settingsLink>."
"description": "Automatische Genehmigung erlaubt Roo Code, Aktionen ohne Nachfrage auszuführen. Aktiviere dies nur für Aktionen, denen du vollständig vertraust. Detailliertere Konfiguration verfügbar in den <settingsLink>Einstellungen</settingsLink>.",
"selectOptionsFirst": "Wähle mindestens eine der folgenden Optionen aus, um die automatische Genehmigung zu aktivieren",
"toggleAriaLabel": "Automatische Genehmigung umschalten",
"disabledAriaLabel": "Automatische Genehmigung deaktiviert - zuerst Optionen auswählen"
},
"reasoning": {
"thinking": "Denke nach",
@ -319,5 +322,8 @@
},
"versionIndicator": {
"ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen"
},
"editMessage": {
"placeholder": "Bearbeite deine Nachricht..."
}
}

View file

@ -51,5 +51,12 @@
"success": {
"imageDataUriCopied": "Bild-Daten-URI in die Zwischenablage kopiert"
}
},
"confirmation": {
"deleteMessage": "Nachricht löschen",
"deleteWarning": "Das Löschen dieser Nachricht wird alle nachfolgenden Nachrichten in der Unterhaltung löschen. Möchtest du fortfahren?",
"editMessage": "Nachricht bearbeiten",
"editWarning": "Das Bearbeiten dieser Nachricht wird alle nachfolgenden Nachrichten in der Unterhaltung löschen. Möchtest du fortfahren?",
"proceed": "Fortfahren"
}
}

View file

@ -123,6 +123,8 @@
},
"autoApprove": {
"description": "Erlaubt Roo, Operationen automatisch ohne Genehmigung durchzuführen. Aktiviere diese Einstellungen nur, wenn du der KI vollständig vertraust und die damit verbundenen Sicherheitsrisiken verstehst.",
"toggleAriaLabel": "Automatische Genehmigung umschalten",
"disabledAriaLabel": "Automatische Genehmigung deaktiviert - zuerst Optionen auswählen",
"readOnly": {
"label": "Lesen",
"description": "Wenn aktiviert, wird Roo automatisch Verzeichnisinhalte anzeigen und Dateien lesen, ohne dass du auf die Genehmigen-Schaltfläche klicken musst.",
@ -190,7 +192,8 @@
"title": "Maximale Anfragen",
"description": "Automatisch so viele API-Anfragen stellen, bevor du um die Erlaubnis gebeten wirst, mit der Aufgabe fortzufahren.",
"unlimited": "Unbegrenzt"
}
},
"selectOptionsFirst": "Wähle mindestens eine Option unten aus, um die automatische Genehmigung zu aktivieren"
},
"providers": {
"providerDocumentation": "{{provider}}-Dokumentation",

View file

@ -39,7 +39,7 @@
},
"save": {
"title": "Save",
"tooltip": "Save the file changes"
"tooltip": "Save the message changes"
},
"tokenProgress": {
"availableSpace": "Available space: {{amount}} tokens",
@ -87,6 +87,9 @@
"title": "Cancel",
"tooltip": "Cancel the current operation"
},
"editMessage": {
"placeholder": "Edit your message..."
},
"scrollToBottom": "Scroll to bottom of chat",
"about": "Generate, refactor, and debug code with AI assistance. Check out our <DocsLink>documentation</DocsLink> to learn more.",
"onboarding": "Your task list in this workspace is empty.",
@ -244,7 +247,10 @@
"autoApprove": {
"title": "Auto-approve:",
"none": "None",
"description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust. More detailed configuration available in <settingsLink>Settings</settingsLink>."
"description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust. More detailed configuration available in <settingsLink>Settings</settingsLink>.",
"selectOptionsFirst": "Select at least one option below to enable auto-approval",
"toggleAriaLabel": "Toggle auto-approval",
"disabledAriaLabel": "Auto-approval disabled - select options first"
},
"announcement": {
"title": "🎉 Roo Code {{version}} Released",

View file

@ -51,5 +51,12 @@
"success": {
"imageDataUriCopied": "Image data URI copied to clipboard"
}
},
"confirmation": {
"deleteMessage": "Delete Message",
"deleteWarning": "Deleting this message will delete all subsequent messages in the conversation. Do you want to proceed?",
"editMessage": "Edit Message",
"editWarning": "Editing this message will delete all subsequent messages in the conversation. Do you want to proceed?",
"proceed": "Proceed"
}
}

View file

@ -190,7 +190,10 @@
"title": "Max Requests",
"description": "Automatically make this many API requests before asking for approval to continue with the task.",
"unlimited": "Unlimited"
}
},
"toggleAriaLabel": "Toggle auto-approval",
"disabledAriaLabel": "Auto-approval disabled - select options first",
"selectOptionsFirst": "Select at least one option below to enable auto-approval"
},
"providers": {
"providerDocumentation": "{{provider}} documentation",

View file

@ -39,7 +39,7 @@
},
"save": {
"title": "Guardar",
"tooltip": "Guardar los cambios del archivo"
"tooltip": "Guardar los cambios del mensaje"
},
"tokenProgress": {
"availableSpace": "Espacio disponible: {{amount}} tokens",
@ -223,7 +223,10 @@
"autoApprove": {
"title": "Auto-aprobar:",
"none": "Ninguno",
"description": "Auto-aprobar permite a Roo Code realizar acciones sin pedir permiso. Habilita solo para acciones en las que confíes plenamente. Configuración más detallada disponible en <settingsLink>Configuración</settingsLink>."
"description": "Auto-aprobar permite a Roo Code realizar acciones sin pedir permiso. Habilita solo para acciones en las que confíes plenamente. Configuración más detallada disponible en <settingsLink>Configuración</settingsLink>.",
"selectOptionsFirst": "Selecciona al menos una opción a continuación para habilitar la aprobación automática",
"toggleAriaLabel": "Alternar aprobación automática",
"disabledAriaLabel": "Aprobación automática desactivada: seleccione primero las opciones"
},
"reasoning": {
"thinking": "Pensando",
@ -319,5 +322,8 @@
},
"versionIndicator": {
"ariaLabel": "Versión {{version}} - Haz clic para ver las notas de la versión"
},
"editMessage": {
"placeholder": "Edita tu mensaje..."
}
}

View file

@ -51,5 +51,12 @@
"success": {
"imageDataUriCopied": "URI de datos de imagen copiada al portapapeles"
}
},
"confirmation": {
"deleteMessage": "Eliminar mensaje",
"deleteWarning": "Eliminar este mensaje eliminará todos los mensajes posteriores en la conversación. ¿Deseas continuar?",
"editMessage": "Editar mensaje",
"editWarning": "Editar este mensaje eliminará todos los mensajes posteriores en la conversación. ¿Deseas continuar?",
"proceed": "Continuar"
}
}

Some files were not shown because too many files have changed in this diff Show more