From 2ab716cd1b490e5d0d934f0760625a1b00b04271 Mon Sep 17 00:00:00 2001 From: daiv Date: Mon, 2 Feb 2026 12:07:56 +0000 Subject: [PATCH 1/2] feat: add /clear slash command to reset conversation context --- CHANGELOG.md | 1 + daiv/slash_commands/actions/__init__.py | 3 +- daiv/slash_commands/actions/clear.py | 53 +++++++++++ docs/features/slash-commands.md | 29 ++++++ .../slash_commands/actions/test_clear.py | 94 +++++++++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 daiv/slash_commands/actions/clear.py create mode 100644 tests/unit_tests/slash_commands/actions/test_clear.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f8d7346..9929df5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `/clear` slash command to reset conversation context in issues and merge requests - Added changelog subagent for maintaining changelogs and release notes across any format (CHANGELOG.md, CHANGES.rst, HISTORY.md, NEWS, etc.) with automatic format detection and convention preservation - Added code review and security audit agent skills for structured review and security guidance. - Added support for issue labels to configure plan and execute agent behavior: diff --git a/daiv/slash_commands/actions/__init__.py b/daiv/slash_commands/actions/__init__.py index 6f286935..fcb3e922 100644 --- a/daiv/slash_commands/actions/__init__.py +++ b/daiv/slash_commands/actions/__init__.py @@ -1,4 +1,5 @@ +from .clear import ClearSlashCommand from .clone_to_topic import CloneToTopicsSlashCommand from .help import HelpSlashCommand -__all__ = ["CloneToTopicsSlashCommand", "HelpSlashCommand"] +__all__ = ["ClearSlashCommand", "CloneToTopicsSlashCommand", "HelpSlashCommand"] diff --git a/daiv/slash_commands/actions/clear.py b/daiv/slash_commands/actions/clear.py new file mode 100644 index 00000000..fb01db63 --- /dev/null +++ b/daiv/slash_commands/actions/clear.py @@ -0,0 +1,53 @@ +import logging + +from django.conf import settings as django_settings + +from langgraph.checkpoint.postgres import PostgresSaver + +from codebase.base import Scope +from core.utils import generate_uuid +from slash_commands.base import SlashCommand +from slash_commands.decorator import slash_command + +logger = logging.getLogger("daiv.slash_commands") + + +@slash_command(command="clear", scopes=[Scope.ISSUE, Scope.MERGE_REQUEST]) +class ClearSlashCommand(SlashCommand): + """ + Command to clear the conversation context by deleting the thread. + """ + + description: str = "Clear the conversation context and start fresh." + + async def execute_for_agent( + self, *, args: str, issue_iid: int | None = None, merge_request_id: int | None = None, **kwargs + ) -> str: + """ + Execute clear command for agent middleware. + + Deletes the thread associated with the current issue or merge request. + + Args: + args: Additional parameters from the command (unused). + issue_iid: The issue IID (for Issue scope). + merge_request_id: The merge request ID (for Merge Request scope). + + Returns: + Success or error message. + """ + if self.scope == Scope.ISSUE and issue_iid is not None: + thread_id = generate_uuid(f"{self.repo_id}:{self.scope.value}/{issue_iid}") + elif self.scope == Scope.MERGE_REQUEST and merge_request_id is not None: + thread_id = generate_uuid(f"{self.repo_id}:{self.scope.value}/{merge_request_id}") + else: + return f"The /{self.command} command is only available for issues and merge requests." + + try: + with PostgresSaver.from_conn_string(django_settings.DB_URI) as checkpointer: + checkpointer.delete_thread(thread_id) + logger.info("Thread %s deleted successfully via /%s command", thread_id, self.command) + return "โœ… Conversation context cleared successfully. You can start a fresh conversation now." + except Exception as e: + logger.exception("Failed to delete thread %s via /%s command", thread_id, self.command) + return f"โŒ Failed to clear conversation context: {e}" diff --git a/docs/features/slash-commands.md b/docs/features/slash-commands.md index 43ccc4d8..05c452c8 100644 --- a/docs/features/slash-commands.md +++ b/docs/features/slash-commands.md @@ -56,9 +56,11 @@ graph TD I --> J["๐Ÿ› ๏ธ Execute Specific Logic"] J --> K["๐Ÿ“– Help Command
(show available commands)"] + J --> L["๐Ÿงน Clear Command
(reset conversation)"] J --> R["๐Ÿ“ค Clone to Topic
(clone issue to repos)"] K --> N["๐Ÿ’ฌ Posts Help Message"] + L --> O["๐Ÿ—‘๏ธ Deletes Thread
(clears context)"] R --> S["๐Ÿ“‹ Creates Issues in
Matching Repositories"] H --> Q["๐Ÿ’ฌ Posts Error Message
(suggests valid commands)"] @@ -97,6 +99,32 @@ graph TD --- +### ๐Ÿงน Clear command + +**Command**: `/clear` + +**Purpose**: Clear the conversation context and start a fresh conversation with DAIV. + +**Scopes**: Issues, Merge/Pull Requests + +**Usage**: When you want to reset the conversation history and start fresh, use this command. This deletes the thread associated with the current issue or merge request, effectively clearing DAIV's memory of the conversation. + +**Example**: +``` +@daiv /clear +``` + +**Response**: DAIV replies with a confirmation that the context has been cleared successfully. + +**Behavior**: + +- Deletes the conversation thread for the current issue or merge request +- Clears all conversation history and context +- Allows starting a new conversation from scratch +- Useful when previous context is no longer relevant or causing confusion + +--- + ### ๐Ÿ“ค Clone to topic command **Command**: `/clone-to-topic ` @@ -184,6 +212,7 @@ Slash commands log detailed information for troubleshooting: Comment one of the commands below on this issue to trigger the bot: - `@daiv /help` - Shows the help message with the available slash commands. +- `@daiv /clear` - Clear the conversation context and start fresh. - `@daiv /clone-to-topic ` - Clone this issue to all repositories matching the specified topics. ``` diff --git a/tests/unit_tests/slash_commands/actions/test_clear.py b/tests/unit_tests/slash_commands/actions/test_clear.py new file mode 100644 index 00000000..d7c0df9c --- /dev/null +++ b/tests/unit_tests/slash_commands/actions/test_clear.py @@ -0,0 +1,94 @@ +from unittest.mock import MagicMock, Mock, patch + +from django.test import override_settings + +from pytest import fixture + +from codebase.base import Scope +from slash_commands.actions.clear import ClearSlashCommand + + +@fixture +def clear_slash_command_issue() -> ClearSlashCommand: + """Set up test fixtures for issue scope.""" + command = ClearSlashCommand(scope=Scope.ISSUE, repo_id="repo1", bot_username="bot") + command.client = MagicMock(current_user=MagicMock(username="bot")) + return command + + +@fixture +def clear_slash_command_mr() -> ClearSlashCommand: + """Set up test fixtures for merge request scope.""" + command = ClearSlashCommand(scope=Scope.MERGE_REQUEST, repo_id="repo1", bot_username="bot") + command.client = MagicMock(current_user=MagicMock(username="bot")) + return command + + +def test_clear_command_has_correct_attributes(): + """Test that ClearSlashCommand has the expected attributes set by decorator.""" + assert hasattr(ClearSlashCommand, "command") + assert hasattr(ClearSlashCommand, "scopes") + assert ClearSlashCommand.command == "clear" + assert Scope.ISSUE in ClearSlashCommand.scopes + assert Scope.MERGE_REQUEST in ClearSlashCommand.scopes + assert Scope.GLOBAL not in ClearSlashCommand.scopes + + +@override_settings(DB_URI="postgresql://test:test@localhost:5432/test") +@patch("slash_commands.actions.clear.PostgresSaver") +async def test_clear_command_for_issue(mock_postgres_saver: Mock, clear_slash_command_issue: ClearSlashCommand): + """Test that ClearSlashCommand deletes the thread for an issue.""" + mock_checkpointer = MagicMock() + mock_postgres_saver.from_conn_string.return_value.__enter__.return_value = mock_checkpointer + + message = await clear_slash_command_issue.execute_for_agent(args="", issue_iid=42) + + # Verify the thread was deleted + mock_checkpointer.delete_thread.assert_called_once() + assert "cleared successfully" in message + assert "โœ…" in message + + +@override_settings(DB_URI="postgresql://test:test@localhost:5432/test") +@patch("slash_commands.actions.clear.PostgresSaver") +async def test_clear_command_for_merge_request( + mock_postgres_saver: Mock, clear_slash_command_mr: ClearSlashCommand +): + """Test that ClearSlashCommand deletes the thread for a merge request.""" + mock_checkpointer = MagicMock() + mock_postgres_saver.from_conn_string.return_value.__enter__.return_value = mock_checkpointer + + message = await clear_slash_command_mr.execute_for_agent(args="", merge_request_id=123) + + # Verify the thread was deleted + mock_checkpointer.delete_thread.assert_called_once() + assert "cleared successfully" in message + assert "โœ…" in message + + +@patch("slash_commands.actions.clear.PostgresSaver") +async def test_clear_command_without_issue_iid( + mock_postgres_saver: Mock, clear_slash_command_issue: ClearSlashCommand +): + """Test that ClearSlashCommand returns an error when issue_iid is missing.""" + message = await clear_slash_command_issue.execute_for_agent(args="") + + # Verify no thread deletion attempted + mock_postgres_saver.from_conn_string.assert_not_called() + assert "only available for issues and merge requests" in message + + +@override_settings(DB_URI="postgresql://test:test@localhost:5432/test") +@patch("slash_commands.actions.clear.PostgresSaver") +async def test_clear_command_handles_exceptions( + mock_postgres_saver: Mock, clear_slash_command_issue: ClearSlashCommand +): + """Test that ClearSlashCommand handles exceptions gracefully.""" + mock_checkpointer = MagicMock() + mock_checkpointer.delete_thread.side_effect = Exception("Database error") + mock_postgres_saver.from_conn_string.return_value.__enter__.return_value = mock_checkpointer + + message = await clear_slash_command_issue.execute_for_agent(args="", issue_iid=42) + + assert "Failed to clear" in message + assert "โŒ" in message From f0d3333d2dc6ef11a0d5c3558c60c9fb75e03de6 Mon Sep 17 00:00:00 2001 From: daiv Date: Mon, 2 Feb 2026 12:17:52 +0000 Subject: [PATCH 2/2] fix: remove unnecessary line breaks in test function definitions --- tests/unit_tests/slash_commands/actions/test_clear.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit_tests/slash_commands/actions/test_clear.py b/tests/unit_tests/slash_commands/actions/test_clear.py index d7c0df9c..8bed544b 100644 --- a/tests/unit_tests/slash_commands/actions/test_clear.py +++ b/tests/unit_tests/slash_commands/actions/test_clear.py @@ -51,9 +51,7 @@ async def test_clear_command_for_issue(mock_postgres_saver: Mock, clear_slash_co @override_settings(DB_URI="postgresql://test:test@localhost:5432/test") @patch("slash_commands.actions.clear.PostgresSaver") -async def test_clear_command_for_merge_request( - mock_postgres_saver: Mock, clear_slash_command_mr: ClearSlashCommand -): +async def test_clear_command_for_merge_request(mock_postgres_saver: Mock, clear_slash_command_mr: ClearSlashCommand): """Test that ClearSlashCommand deletes the thread for a merge request.""" mock_checkpointer = MagicMock() mock_postgres_saver.from_conn_string.return_value.__enter__.return_value = mock_checkpointer @@ -67,9 +65,7 @@ async def test_clear_command_for_merge_request( @patch("slash_commands.actions.clear.PostgresSaver") -async def test_clear_command_without_issue_iid( - mock_postgres_saver: Mock, clear_slash_command_issue: ClearSlashCommand -): +async def test_clear_command_without_issue_iid(mock_postgres_saver: Mock, clear_slash_command_issue: ClearSlashCommand): """Test that ClearSlashCommand returns an error when issue_iid is missing.""" message = await clear_slash_command_issue.execute_for_agent(args="")