From 2891b313d24dd8512c9f7d6804af02cb979b4a00 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:12:15 +0200 Subject: [PATCH] refactor: Separate and simplified tools (#340) --- CMakeLists.txt | 3 +- settings/SettingsConstants.hpp | 17 ++- settings/ToolsSettings.cpp | 139 ++++++++++-------- settings/ToolsSettings.hpp | 13 +- tools/BuildProjectTool.cpp | 11 +- tools/CreateNewFileTool.cpp | 12 +- tools/EditFileTool.cpp | 2 +- tools/FindAndReadFileTool.cpp | 125 ---------------- tools/FindFileTool.cpp | 108 ++++++++++++++ ...ndAndReadFileTool.hpp => FindFileTool.hpp} | 10 +- tools/GetIssuesListTool.cpp | 7 +- tools/ListProjectFilesTool.cpp | 6 +- tools/ProjectSearchTool.cpp | 39 +++-- tools/ReadFileTool.cpp | 122 +++++++++++++++ tools/ReadFileTool.hpp | 43 ++++++ tools/ToolsRegistration.cpp | 53 +++++-- 16 files changed, 465 insertions(+), 245 deletions(-) delete mode 100644 tools/FindAndReadFileTool.cpp create mode 100644 tools/FindFileTool.cpp rename tools/{FindAndReadFileTool.hpp => FindFileTool.hpp} (83%) create mode 100644 tools/ReadFileTool.cpp create mode 100644 tools/ReadFileTool.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d51512f..9fa4f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,7 +151,8 @@ add_qtc_plugin(QodeAssist tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp tools/ExecuteTerminalCommandTool.hpp tools/ExecuteTerminalCommandTool.cpp tools/ProjectSearchTool.hpp tools/ProjectSearchTool.cpp - tools/FindAndReadFileTool.hpp tools/FindAndReadFileTool.cpp + tools/FindFileTool.hpp tools/FindFileTool.cpp + tools/ReadFileTool.hpp tools/ReadFileTool.cpp tools/FileSearchUtils.hpp tools/FileSearchUtils.cpp tools/TodoTool.hpp tools/TodoTool.cpp ) diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp index 1ca2242..270105b 100644 --- a/settings/SettingsConstants.hpp +++ b/settings/SettingsConstants.hpp @@ -99,14 +99,17 @@ const char CA_ENABLE_CHAT_IN_BOTTOM_TOOLBAR[] = "QodeAssist.caEnableChatInBottom const char CA_ENABLE_CHAT_IN_NAVIGATION_PANEL[] = "QodeAssist.caEnableChatInNavigationPanel"; const char CA_ENABLE_CHAT_TOOLS[] = "QodeAssist.caEnableChatTools"; const char CA_USE_TOOLS[] = "QodeAssist.caUseTools"; -const char CA_ALLOW_FILE_SYSTEM_READ[] = "QodeAssist.caAllowFileSystemRead"; -const char CA_ALLOW_FILE_SYSTEM_WRITE[] = "QodeAssist.caAllowFileSystemWrite"; -const char CA_ALLOW_NETWORK_ACCESS[] = "QodeAssist.caAllowNetworkAccess"; const char CA_ALLOW_ACCESS_OUTSIDE_PROJECT[] = "QodeAssist.caAllowAccessOutsideProject"; -const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileTool"; -const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectTool"; -const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandTool"; -const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoTool"; +const char CA_ENABLE_LIST_PROJECT_FILES_TOOL[] = "QodeAssist.caEnableListProjectFilesTool"; +const char CA_ENABLE_FIND_FILE_TOOL[] = "QodeAssist.caEnableFindFileTool"; +const char CA_ENABLE_READ_FILE_TOOL[] = "QodeAssist.caEnableReadFileTool"; +const char CA_ENABLE_PROJECT_SEARCH_TOOL[] = "QodeAssist.caEnableProjectSearchTool"; +const char CA_ENABLE_CREATE_NEW_FILE_TOOL[] = "QodeAssist.caEnableCreateNewFileTool"; +const char CA_ENABLE_GET_ISSUES_LIST_TOOL[] = "QodeAssist.caEnableGetIssuesListTool"; +const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileToolV2"; +const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectToolV2"; +const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandToolV2"; +const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoToolV2"; const char CA_ALLOWED_TERMINAL_COMMANDS[] = "QodeAssist.caAllowedTerminalCommands"; const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux"; const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS"; diff --git a/settings/ToolsSettings.cpp b/settings/ToolsSettings.cpp index eb5923a..c0261f7 100644 --- a/settings/ToolsSettings.cpp +++ b/settings/ToolsSettings.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2024-2025 Petr Mironychev * * This file is part of QodeAssist. @@ -42,66 +42,80 @@ ToolsSettings::ToolsSettings() setDisplayName(Tr::tr("Tools")); - allowFileSystemRead.setSettingsKey(Constants::CA_ALLOW_FILE_SYSTEM_READ); - allowFileSystemRead.setLabelText(Tr::tr("Allow File System Read Access for tools")); - allowFileSystemRead.setToolTip( - Tr::tr("Allow tools to read files from disk (project files, open editors)")); - allowFileSystemRead.setDefaultValue(true); - - allowFileSystemWrite.setSettingsKey(Constants::CA_ALLOW_FILE_SYSTEM_WRITE); - allowFileSystemWrite.setLabelText(Tr::tr("Allow File System Write Access for tools")); - allowFileSystemWrite.setToolTip( - Tr::tr("Allow tools to write and modify files on disk (WARNING: Use with caution!)")); - allowFileSystemWrite.setDefaultValue(false); - - allowNetworkAccess.setSettingsKey(Constants::CA_ALLOW_NETWORK_ACCESS); - allowNetworkAccess.setLabelText(Tr::tr("Allow Network Access for tools")); - allowNetworkAccess.setToolTip( - Tr::tr("Allow tools to make network requests (e.g., execute commands like git, curl, wget). " - "Required for ExecuteTerminalCommandTool with network-capable commands.")); - allowNetworkAccess.setDefaultValue(false); - allowAccessOutsideProject.setSettingsKey(Constants::CA_ALLOW_ACCESS_OUTSIDE_PROJECT); allowAccessOutsideProject.setLabelText(Tr::tr("Allow file access outside project")); allowAccessOutsideProject.setToolTip( - Tr::tr("Allow tools to access (read/write) files outside the project scope (system " - "headers, Qt files, external libraries)")); - allowAccessOutsideProject.setDefaultValue(true); + Tr::tr("Allow tools to read, write, and create files outside the project scope " + "(system headers, Qt files, external libraries).")); + allowAccessOutsideProject.setDefaultValue(false); autoApplyFileEdits.setSettingsKey(Constants::CA_AUTO_APPLY_FILE_EDITS); autoApplyFileEdits.setLabelText(Tr::tr("Automatically apply file edits")); autoApplyFileEdits.setToolTip( - Tr::tr("When enabled, file edits suggested by AI will be applied automatically. " - "When disabled, you will need to manually approve each edit.")); + Tr::tr("When enabled, file edits suggested by AI are applied immediately. " + "When disabled, each edit is staged for manual approval.")); autoApplyFileEdits.setDefaultValue(false); + enableListProjectFilesTool.setSettingsKey(Constants::CA_ENABLE_LIST_PROJECT_FILES_TOOL); + enableListProjectFilesTool.setLabelText(Tr::tr("List Project Files")); + enableListProjectFilesTool.setToolTip( + Tr::tr("Lists every source file tracked by the active Qt Creator project(s).")); + enableListProjectFilesTool.setDefaultValue(true); + + enableFindFileTool.setSettingsKey(Constants::CA_ENABLE_FIND_FILE_TOOL); + enableFindFileTool.setLabelText(Tr::tr("Find File")); + enableFindFileTool.setToolTip( + Tr::tr("Locates a file in the project by name or partial path. Returns paths only, " + "without file content.")); + enableFindFileTool.setDefaultValue(true); + + enableReadFileTool.setSettingsKey(Constants::CA_ENABLE_READ_FILE_TOOL); + enableReadFileTool.setLabelText(Tr::tr("Read File")); + enableReadFileTool.setToolTip( + Tr::tr("Reads the content of a file by absolute path or path relative to the project root.")); + enableReadFileTool.setDefaultValue(true); + + enableProjectSearchTool.setSettingsKey(Constants::CA_ENABLE_PROJECT_SEARCH_TOOL); + enableProjectSearchTool.setLabelText(Tr::tr("Search in Project")); + enableProjectSearchTool.setToolTip( + Tr::tr("Searches project files for text occurrences or C++ symbol definitions.")); + enableProjectSearchTool.setDefaultValue(true); + + enableCreateNewFileTool.setSettingsKey(Constants::CA_ENABLE_CREATE_NEW_FILE_TOOL); + enableCreateNewFileTool.setLabelText(Tr::tr("Create New File")); + enableCreateNewFileTool.setToolTip( + Tr::tr("Creates a new empty file at the given absolute path, making missing directories.")); + enableCreateNewFileTool.setDefaultValue(true); + enableEditFileTool.setSettingsKey(Constants::CA_ENABLE_EDIT_FILE_TOOL); - enableEditFileTool.setLabelText(Tr::tr("Enable Edit File Tool (Experimental)")); + enableEditFileTool.setLabelText(Tr::tr("Edit File")); enableEditFileTool.setToolTip( - Tr::tr("Enable the experimental edit_file tool that allows AI to directly modify files. " - "This feature is under testing and may have unexpected behavior.")); - enableEditFileTool.setDefaultValue(false); + Tr::tr("Applies find-and-replace edits to files. See \"Automatically apply file edits\" " + "to control whether edits apply immediately or wait for review.")); + enableEditFileTool.setDefaultValue(true); enableBuildProjectTool.setSettingsKey(Constants::CA_ENABLE_BUILD_PROJECT_TOOL); - enableBuildProjectTool.setLabelText(Tr::tr("Enable Build Project Tool (Experimental)")); + enableBuildProjectTool.setLabelText(Tr::tr("Build Project")); enableBuildProjectTool.setToolTip( - Tr::tr("Enable the experimental build_project tool that allows AI to build the current " - "project. This feature is under testing and may have unexpected behavior.")); - enableBuildProjectTool.setDefaultValue(false); + Tr::tr("Triggers a build of the active Qt Creator project and reports the result.")); + enableBuildProjectTool.setDefaultValue(true); + + enableGetIssuesListTool.setSettingsKey(Constants::CA_ENABLE_GET_ISSUES_LIST_TOOL); + enableGetIssuesListTool.setLabelText(Tr::tr("Get Issues List")); + enableGetIssuesListTool.setToolTip( + Tr::tr("Reads compiler/clang diagnostics from Qt Creator's Issues panel.")); + enableGetIssuesListTool.setDefaultValue(true); enableTerminalCommandTool.setSettingsKey(Constants::CA_ENABLE_TERMINAL_COMMAND_TOOL); - enableTerminalCommandTool.setLabelText(Tr::tr("Enable Terminal Command Tool (Experimental)")); + enableTerminalCommandTool.setLabelText(Tr::tr("Execute Terminal Command")); enableTerminalCommandTool.setToolTip( - Tr::tr("Enable the experimental execute_terminal_command tool that allows AI to execute " - "terminal commands from the allowed list. This feature is under testing and may have " - "unexpected behavior.")); - enableTerminalCommandTool.setDefaultValue(false); + Tr::tr("Runs a command from the OS-specific allowed list below, in the project directory.")); + enableTerminalCommandTool.setDefaultValue(true); enableTodoTool.setSettingsKey(Constants::CA_ENABLE_TODO_TOOL); - enableTodoTool.setLabelText(Tr::tr("Enable Todo Tool")); + enableTodoTool.setLabelText(Tr::tr("Todo")); enableTodoTool.setToolTip( - Tr::tr("Enable the todo_tool that helps AI track and organize multi-step tasks. " - "Useful for complex refactoring, debugging, and feature implementation workflows.")); + Tr::tr("Lets the AI maintain a session-scoped todo list for multi-step workflows.")); enableTodoTool.setDefaultValue(true); allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX); @@ -159,24 +173,30 @@ ToolsSettings::ToolsSettings() Row{Stretch{1}, resetToDefaults}, Space{8}, Group{ - title(Tr::tr("Tool Settings")), - Column{ - allowFileSystemRead, - allowFileSystemWrite, - allowNetworkAccess, - allowAccessOutsideProject - }}, - Space{8}, - Group{ - title(Tr::tr("Experimental Features")), + title(Tr::tr("Tools")), Column{ + enableListProjectFilesTool, + enableFindFileTool, + enableReadFileTool, + enableProjectSearchTool, + enableCreateNewFileTool, enableEditFileTool, enableBuildProjectTool, + enableGetIssuesListTool, enableTerminalCommandTool, - enableTodoTool, - currentOsCommands, - terminalCommandTimeout, - autoApplyFileEdits}}, + enableTodoTool}}, + Space{8}, + Group{ + title(Tr::tr("Tool Settings")), + Column{ + allowAccessOutsideProject, + Space{4}, + Group{ + title(Tr::tr("Edit File")), + Column{autoApplyFileEdits}}, + Group{ + title(Tr::tr("Execute Terminal Command")), + Column{currentOsCommands, terminalCommandTimeout}}}}, Stretch{1}}; }); } @@ -200,13 +220,16 @@ void ToolsSettings::resetSettingsToDefaults() QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { - resetAspect(allowFileSystemRead); - resetAspect(allowFileSystemWrite); - resetAspect(allowNetworkAccess); resetAspect(allowAccessOutsideProject); resetAspect(autoApplyFileEdits); + resetAspect(enableListProjectFilesTool); + resetAspect(enableFindFileTool); + resetAspect(enableReadFileTool); + resetAspect(enableProjectSearchTool); + resetAspect(enableCreateNewFileTool); resetAspect(enableEditFileTool); resetAspect(enableBuildProjectTool); + resetAspect(enableGetIssuesListTool); resetAspect(enableTerminalCommandTool); resetAspect(enableTodoTool); resetAspect(allowedTerminalCommandsLinux); diff --git a/settings/ToolsSettings.hpp b/settings/ToolsSettings.hpp index a447a64..356ac0f 100644 --- a/settings/ToolsSettings.hpp +++ b/settings/ToolsSettings.hpp @@ -32,21 +32,24 @@ public: ButtonAspect resetToDefaults{this}; - Utils::BoolAspect allowFileSystemRead{this}; - Utils::BoolAspect allowFileSystemWrite{this}; - Utils::BoolAspect allowNetworkAccess{this}; Utils::BoolAspect allowAccessOutsideProject{this}; + Utils::BoolAspect autoApplyFileEdits{this}; - // Experimental features + Utils::BoolAspect enableListProjectFilesTool{this}; + Utils::BoolAspect enableFindFileTool{this}; + Utils::BoolAspect enableReadFileTool{this}; + Utils::BoolAspect enableProjectSearchTool{this}; + Utils::BoolAspect enableCreateNewFileTool{this}; Utils::BoolAspect enableEditFileTool{this}; Utils::BoolAspect enableBuildProjectTool{this}; + Utils::BoolAspect enableGetIssuesListTool{this}; Utils::BoolAspect enableTerminalCommandTool{this}; Utils::BoolAspect enableTodoTool{this}; + Utils::StringAspect allowedTerminalCommandsLinux{this}; Utils::StringAspect allowedTerminalCommandsMacOS{this}; Utils::StringAspect allowedTerminalCommandsWindows{this}; Utils::IntegerAspect terminalCommandTimeout{this}; - Utils::BoolAspect autoApplyFileEdits{this}; private: void setupConnections(); diff --git a/tools/BuildProjectTool.cpp b/tools/BuildProjectTool.cpp index ede39bb..d709b5e 100644 --- a/tools/BuildProjectTool.cpp +++ b/tools/BuildProjectTool.cpp @@ -71,13 +71,10 @@ QString BuildProjectTool::displayName() const QString BuildProjectTool::description() const { - return "Build the current project in Qt Creator and wait for completion. " - "Optionally run the project after successful build. " - "Returns build status (success/failure) and any compilation errors/warnings after " - "the build finishes. " - "Optional 'rebuild' parameter: set to true to force a clean rebuild (default: false). " - "Optional 'run_after_build' parameter: set to true to run the project after successful build (default: false). " - "Note: This operation may take some time depending on project size."; + return "Build the active Qt Creator project using its current build configuration and block " + "until the build finishes. Returns success/failure along with the full compiler output " + "(stdout + stderr). Use `get_issues_list` afterwards to get structured diagnostics. " + "This call is blocking and may take a long time for large projects."; } QJsonObject BuildProjectTool::parametersSchema() const diff --git a/tools/CreateNewFileTool.cpp b/tools/CreateNewFileTool.cpp index 35b1eba..6013c3f 100644 --- a/tools/CreateNewFileTool.cpp +++ b/tools/CreateNewFileTool.cpp @@ -49,10 +49,10 @@ QString CreateNewFileTool::displayName() const QString CreateNewFileTool::description() const { - return "Create a new empty file at the specified path. " - "If the directory path does not exist, it will be created automatically. " - "Provide absolute file path. After creating files, add the file " - "to the project file"; + return "Create a new empty file at the given absolute path. Any missing parent directories " + "are created automatically. The file is written to disk only — it is NOT added to the " + "project's build system automatically; the user must register it in CMakeLists.txt or " + "the equivalent project file. Use `edit_file` afterwards to populate its content."; } QJsonObject CreateNewFileTool::parametersSchema() const @@ -61,7 +61,9 @@ QJsonObject CreateNewFileTool::parametersSchema() const QJsonObject filepathProperty; filepathProperty["type"] = "string"; - filepathProperty["description"] = "The absolute path where the new file should be created"; + filepathProperty["description"] + = "Absolute path where the new file should be created. Parent directories are made if " + "missing. Relative paths are rejected."; properties["filepath"] = filepathProperty; QJsonObject definition; diff --git a/tools/EditFileTool.cpp b/tools/EditFileTool.cpp index 3dec3c8..b278889 100644 --- a/tools/EditFileTool.cpp +++ b/tools/EditFileTool.cpp @@ -57,7 +57,7 @@ QString EditFileTool::description() const "and new_content to replace it with. Changes are applied immediately if auto-apply " "is enabled in settings. The user can undo or reapply changes at any time. " "\n\nIMPORTANT:" - "\n- ALWAYS read the current file content before editing to ensure accuracy." + "\n- ALWAYS use read_file to get current file content before editing to ensure accuracy." "\n- Path can be absolute (e.g., /path/to/file.cpp) or relative to project root (e.g., src/main.cpp)." "\n- For EMPTY files: use empty old_content (empty string or omit parameter)." "\n- To append at the END of file: use empty old_content." diff --git a/tools/FindAndReadFileTool.cpp b/tools/FindAndReadFileTool.cpp deleted file mode 100644 index 7377684..0000000 --- a/tools/FindAndReadFileTool.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2025 Petr Mironychev - * - * This file is part of QodeAssist. - * - * QodeAssist is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QodeAssist is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QodeAssist. If not, see . - */ - -#include "FindAndReadFileTool.hpp" - -#include - -#include -#include -#include -#include - -namespace QodeAssist::Tools { - -FindAndReadFileTool::FindAndReadFileTool(QObject *parent) - : BaseTool(parent) - , m_ignoreManager(new Context::IgnoreManager(this)) -{} - -QString FindAndReadFileTool::id() const -{ - return "find_and_read_file"; -} - -QString FindAndReadFileTool::displayName() const -{ - return "Finding and reading file"; -} - -QString FindAndReadFileTool::description() const -{ - return "Search for a file by name/path and optionally read its content. " - "Returns the best matching file and its content."; -} - -QJsonObject FindAndReadFileTool::parametersSchema() const -{ - QJsonObject properties; - - properties["query"] = QJsonObject{ - {"type", "string"}, - {"description", "Filename, partial name, or path to search for (case-insensitive)"}}; - - properties["file_pattern"] = QJsonObject{ - {"type", "string"}, {"description", "File pattern filter (e.g., '*.cpp', '*.h', '*.qml')"}}; - - properties["read_content"] = QJsonObject{ - {"type", "boolean"}, - {"description", "Read file content in addition to finding path (default: true)"}}; - - QJsonObject definition; - definition["type"] = "object"; - definition["properties"] = properties; - definition["required"] = QJsonArray{"query"}; - - return definition; -} - -QFuture FindAndReadFileTool::executeAsync(const QJsonObject &input) -{ - return QtConcurrent::run([this, input]() -> LLMQore::ToolResult { - QString query = input["query"].toString().trimmed(); - if (query.isEmpty()) { - throw LLMQore::ToolInvalidArgument("Query parameter is required"); - } - - QString filePattern = input["file_pattern"].toString(); - bool readContent = input["read_content"].toBool(true); - - LOG_MESSAGE(QString("FindAndReadFileTool: Searching for '%1' (pattern: %2, read: %3)") - .arg(query, filePattern.isEmpty() ? "none" : filePattern) - .arg(readContent)); - - FileSearchUtils::FileMatch bestMatch = FileSearchUtils::findBestMatch( - query, filePattern, 10, m_ignoreManager); - - if (bestMatch.absolutePath.isEmpty()) { - return LLMQore::ToolResult::text(QString("No file found matching '%1'").arg(query)); - } - - if (readContent) { - bestMatch.content = FileSearchUtils::readFileContent(bestMatch.absolutePath); - if (bestMatch.content.isNull()) { - bestMatch.error = "Could not read file"; - } - } - - return LLMQore::ToolResult::text(formatResult(bestMatch, readContent)); - }); -} - -QString FindAndReadFileTool::formatResult(const FileSearchUtils::FileMatch &match, - bool readContent) const -{ - QString result - = QString("Found file: %1\nAbsolute path: %2").arg(match.relativePath, match.absolutePath); - - if (readContent) { - if (!match.error.isEmpty()) { - result += QString("\nError: %1").arg(match.error); - } else { - result += QString("\n\n=== Content ===\n%1").arg(match.content); - } - } - - return result; -} - -} // namespace QodeAssist::Tools diff --git a/tools/FindFileTool.cpp b/tools/FindFileTool.cpp new file mode 100644 index 0000000..1c8d2f1 --- /dev/null +++ b/tools/FindFileTool.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2026 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "FindFileTool.hpp" + +#include "FileSearchUtils.hpp" + +#include + +#include +#include +#include +#include + +namespace QodeAssist::Tools { + +FindFileTool::FindFileTool(QObject *parent) + : BaseTool(parent) + , m_ignoreManager(new Context::IgnoreManager(this)) +{} + +QString FindFileTool::id() const +{ + return "find_file"; +} + +QString FindFileTool::displayName() const +{ + return "Finding file"; +} + +QString FindFileTool::description() const +{ + return "Find a file in the current project(s) by name or partial path. " + "Returns the absolute path, project-relative path, and the name of the project " + "that contains the match. Does NOT read file content — use `read_file` separately " + "when you need the content. Use this when you know part of the filename but not " + "the exact location."; +} + +QJsonObject FindFileTool::parametersSchema() const +{ + QJsonObject properties; + + properties["query"] = QJsonObject{ + {"type", "string"}, + {"description", + "Filename, partial filename, or partial path to look for. Case-insensitive. " + "Exact filename matches rank highest, then path matches, then partial name matches."}}; + + properties["file_pattern"] = QJsonObject{ + {"type", "string"}, + {"description", + "Optional wildcard filter applied to the filename. Examples: '*.cpp', '*.h', '*.qml'. " + "Leave empty to match any extension."}}; + + QJsonObject definition; + definition["type"] = "object"; + definition["properties"] = properties; + definition["required"] = QJsonArray{"query"}; + + return definition; +} + +QFuture FindFileTool::executeAsync(const QJsonObject &input) +{ + return QtConcurrent::run([this, input]() -> LLMQore::ToolResult { + QString query = input["query"].toString().trimmed(); + if (query.isEmpty()) { + throw LLMQore::ToolInvalidArgument("'query' parameter is required and cannot be empty"); + } + + QString filePattern = input["file_pattern"].toString(); + + LOG_MESSAGE(QString("FindFileTool: Searching for '%1' (pattern: %2)") + .arg(query, filePattern.isEmpty() ? "none" : filePattern)); + + FileSearchUtils::FileMatch match + = FileSearchUtils::findBestMatch(query, filePattern, 10, m_ignoreManager); + + if (match.absolutePath.isEmpty()) { + return LLMQore::ToolResult::text(QString("No file found matching '%1'").arg(query)); + } + + QString result = QString("Found file: %1\nAbsolute path: %2\nProject: %3") + .arg(match.relativePath, match.absolutePath, match.projectName); + + return LLMQore::ToolResult::text(result); + }); +} + +} // namespace QodeAssist::Tools diff --git a/tools/FindAndReadFileTool.hpp b/tools/FindFileTool.hpp similarity index 83% rename from tools/FindAndReadFileTool.hpp rename to tools/FindFileTool.hpp index f58d92e..2fe5ffe 100644 --- a/tools/FindAndReadFileTool.hpp +++ b/tools/FindFileTool.hpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2025 Petr Mironychev * * This file is part of QodeAssist. @@ -19,8 +19,6 @@ #pragma once -#include "FileSearchUtils.hpp" - #include #include #include @@ -29,12 +27,12 @@ namespace QodeAssist::Tools { -class FindAndReadFileTool : public ::LLMQore::BaseTool +class FindFileTool : public ::LLMQore::BaseTool { Q_OBJECT public: - explicit FindAndReadFileTool(QObject *parent = nullptr); + explicit FindFileTool(QObject *parent = nullptr); QString id() const override; QString displayName() const override; @@ -43,8 +41,6 @@ public: QFuture executeAsync(const QJsonObject &input) override; private: - QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const; - Context::IgnoreManager *m_ignoreManager; }; diff --git a/tools/GetIssuesListTool.cpp b/tools/GetIssuesListTool.cpp index 30506f6..b86c797 100644 --- a/tools/GetIssuesListTool.cpp +++ b/tools/GetIssuesListTool.cpp @@ -133,9 +133,10 @@ QString GetIssuesListTool::displayName() const QString GetIssuesListTool::description() const { - return "Get compilation errors, warnings, and diagnostics from Qt Creator's Issues panel. " - "Returns issue descriptions with file paths and line numbers. " - "Optional severity filter: 'error', 'warning', or 'all' (default)."; + return "Read diagnostics from Qt Creator's Issues panel, including the latest build output and " + "live clang-codemodel warnings/errors for open files. Each issue includes file path, " + "line number, severity, and message. Run `build_project` first if you need fresh build " + "diagnostics."; } QJsonObject GetIssuesListTool::parametersSchema() const diff --git a/tools/ListProjectFilesTool.cpp b/tools/ListProjectFilesTool.cpp index a7722b7..3d6ddc7 100644 --- a/tools/ListProjectFilesTool.cpp +++ b/tools/ListProjectFilesTool.cpp @@ -50,8 +50,10 @@ QString ListProjectFilesTool::displayName() const QString ListProjectFilesTool::description() const { - return "Get a list of all source files in the current project with absolute and relative paths. " - "Useful for understanding project structure. No parameters required."; + return "List every source file tracked by the active Qt Creator project(s), filtered by " + ".qodeassistignore. Returns absolute and project-relative paths grouped by project. " + "Useful for discovering the project layout before running focused searches or reads. " + "Takes no parameters."; } QJsonObject ListProjectFilesTool::parametersSchema() const diff --git a/tools/ProjectSearchTool.cpp b/tools/ProjectSearchTool.cpp index f7187f7..39313f4 100644 --- a/tools/ProjectSearchTool.cpp +++ b/tools/ProjectSearchTool.cpp @@ -56,39 +56,50 @@ QString ProjectSearchTool::displayName() const QString ProjectSearchTool::description() const { - return "Search project for text content or C++ symbols. " - "Text mode: finds text patterns in files. " - "Symbol mode: finds C++ definitions (classes, functions, etc)."; + return "Search across all open Qt Creator project(s) for text occurrences or C++ symbol " + "definitions. 'text' mode scans source files line-by-line for literal text or regex. " + "'symbol' mode uses Qt Creator's C++ code model and works only for C++ " + "(not QML, Python, or plain text). Respects .qodeassistignore. " + "Use `find_file` for locating files by name, not this tool."; } QJsonObject ProjectSearchTool::parametersSchema() const { QJsonObject properties; - properties["query"] - = QJsonObject{{"type", "string"}, {"description", "Text or symbol name to search for"}}; + properties["query"] = QJsonObject{ + {"type", "string"}, + {"description", "Text string or symbol name to search for. In text mode with " + "use_regex=true, this is a regular expression."}}; properties["search_type"] = QJsonObject{ {"type", "string"}, {"enum", QJsonArray{"text", "symbol"}}, - {"description", "Search mode: 'text' for content, 'symbol' for C++ definitions"}}; + {"description", "Search mode: 'text' scans file contents, 'symbol' looks up C++ " + "declarations via the code model."}}; properties["symbol_type"] = QJsonObject{ {"type", "string"}, {"enum", QJsonArray{"all", "class", "function", "enum", "variable", "namespace"}}, - {"description", "Symbol type filter (symbol mode only)"}}; + {"description", "Filter for symbol mode. Default: 'all'. Ignored in text mode."}}; - properties["case_sensitive"] - = QJsonObject{{"type", "boolean"}, {"description", "Case-sensitive search"}}; + properties["case_sensitive"] = QJsonObject{ + {"type", "boolean"}, + {"description", "Case-sensitive matching. Default: false."}}; - properties["use_regex"] - = QJsonObject{{"type", "boolean"}, {"description", "Use regex patterns"}}; + properties["use_regex"] = QJsonObject{ + {"type", "boolean"}, + {"description", "Treat the query as a regular expression. Default: false."}}; - properties["whole_words"] - = QJsonObject{{"type", "boolean"}, {"description", "Match whole words only (text mode)"}}; + properties["whole_words"] = QJsonObject{ + {"type", "boolean"}, + {"description", "Match whole words only. Text mode only; ignored in symbol mode. " + "Default: false."}}; properties["file_pattern"] = QJsonObject{ - {"type", "string"}, {"description", "File filter pattern (e.g., '*.cpp', '*.h')"}}; + {"type", "string"}, + {"description", "Wildcard to restrict which files are searched in text mode, e.g. " + "'*.cpp' or '*.h'. Default: all files."}}; QJsonObject definition; definition["type"] = "object"; diff --git a/tools/ReadFileTool.cpp b/tools/ReadFileTool.cpp new file mode 100644 index 0000000..c47da84 --- /dev/null +++ b/tools/ReadFileTool.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2026 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "ReadFileTool.hpp" + +#include "FileSearchUtils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QodeAssist::Tools { + +ReadFileTool::ReadFileTool(QObject *parent) + : BaseTool(parent) +{} + +QString ReadFileTool::id() const +{ + return "read_file"; +} + +QString ReadFileTool::displayName() const +{ + return "Reading file"; +} + +QString ReadFileTool::description() const +{ + return "Read the text content of a file. Accepts either an absolute path or a path " + "relative to the project root. Reading files outside the active project requires " + "the 'Allow file access outside project' option in settings. Use `find_file` first " + "if you only know a partial filename."; +} + +QJsonObject ReadFileTool::parametersSchema() const +{ + QJsonObject properties; + + properties["file_path"] = QJsonObject{ + {"type", "string"}, + {"description", + "Absolute path (e.g. /path/to/file.cpp) or project-relative path (e.g. src/main.cpp). " + "Relative paths are resolved against the root of the active project."}}; + + QJsonObject definition; + definition["type"] = "object"; + definition["properties"] = properties; + definition["required"] = QJsonArray{"file_path"}; + + return definition; +} + +QFuture ReadFileTool::executeAsync(const QJsonObject &input) +{ + return QtConcurrent::run([input]() -> LLMQore::ToolResult { + QString rawPath = input["file_path"].toString().trimmed(); + if (rawPath.isEmpty()) { + throw LLMQore::ToolInvalidArgument( + "'file_path' parameter is required and cannot be empty"); + } + + QFileInfo pathInfo(rawPath); + QString absolutePath; + if (pathInfo.isAbsolute()) { + absolutePath = rawPath; + } else { + QString projectRoot = Context::ProjectUtils::getProjectRoot(); + if (projectRoot.isEmpty()) { + throw LLMQore::ToolRuntimeError( + QString("Cannot resolve relative path '%1': no project is open. " + "Provide an absolute path or open a project.") + .arg(rawPath)); + } + absolutePath = QDir(projectRoot).absoluteFilePath(rawPath); + LOG_MESSAGE(QString("ReadFileTool: Resolved relative path '%1' to '%2'") + .arg(rawPath, absolutePath)); + } + + QFileInfo finalInfo(absolutePath); + if (!finalInfo.exists() || !finalInfo.isFile()) { + throw LLMQore::ToolRuntimeError(QString("File does not exist: %1").arg(absolutePath)); + } + + QString content = FileSearchUtils::readFileContent(absolutePath); + if (content.isNull()) { + throw LLMQore::ToolRuntimeError( + QString("Cannot read file '%1'. It may be outside the project scope " + "(enable 'Allow file access outside project' in settings), unreadable, " + "or use an unsupported encoding.") + .arg(absolutePath)); + } + + QString result = QString("File: %1\n\n%2").arg(absolutePath, content); + return LLMQore::ToolResult::text(result); + }); +} + +} // namespace QodeAssist::Tools diff --git a/tools/ReadFileTool.hpp b/tools/ReadFileTool.hpp new file mode 100644 index 0000000..a25a015 --- /dev/null +++ b/tools/ReadFileTool.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +namespace QodeAssist::Tools { + +class ReadFileTool : public ::LLMQore::BaseTool +{ + Q_OBJECT + +public: + explicit ReadFileTool(QObject *parent = nullptr); + + QString id() const override; + QString displayName() const override; + QString description() const override; + QJsonObject parametersSchema() const override; + QFuture executeAsync(const QJsonObject &input) override; +}; + +} // namespace QodeAssist::Tools diff --git a/tools/ToolsRegistration.cpp b/tools/ToolsRegistration.cpp index 91922a9..305023a 100644 --- a/tools/ToolsRegistration.cpp +++ b/tools/ToolsRegistration.cpp @@ -21,29 +21,62 @@ #include +#include +#include + #include "BuildProjectTool.hpp" #include "CreateNewFileTool.hpp" #include "EditFileTool.hpp" #include "ExecuteTerminalCommandTool.hpp" -#include "FindAndReadFileTool.hpp" +#include "FindFileTool.hpp" #include "GetIssuesListTool.hpp" #include "ListProjectFilesTool.hpp" #include "ProjectSearchTool.hpp" +#include "ReadFileTool.hpp" #include "TodoTool.hpp" namespace QodeAssist::Tools { +namespace { + +template +void wireTool(::LLMQore::ToolsManager *manager, + Utils::BoolAspect &aspect, + const QString &toolId) +{ + auto sync = [manager, toolId, &aspect]() { + const bool wanted = aspect.volatileValue(); + const bool present = manager->tool(toolId) != nullptr; + if (wanted && !present) { + manager->addTool(new ToolT(manager)); + } else if (!wanted && present) { + manager->removeTool(toolId); + } + }; + + sync(); + + QObject::connect(&aspect, &Utils::BoolAspect::volatileValueChanged, manager, sync); + QObject::connect(&aspect, &Utils::BaseAspect::changed, manager, sync); +} + +} // namespace + void registerQodeAssistTools(::LLMQore::ToolsManager *manager) { - manager->addTool(new ListProjectFilesTool(manager)); - manager->addTool(new GetIssuesListTool(manager)); - manager->addTool(new CreateNewFileTool(manager)); - manager->addTool(new EditFileTool(manager)); - manager->addTool(new BuildProjectTool(manager)); - manager->addTool(new ExecuteTerminalCommandTool(manager)); - manager->addTool(new ProjectSearchTool(manager)); - manager->addTool(new FindAndReadFileTool(manager)); - manager->addTool(new TodoTool(manager)); + auto &s = Settings::toolsSettings(); + + wireTool(manager, s.enableListProjectFilesTool, "list_project_files"); + wireTool(manager, s.enableFindFileTool, "find_file"); + wireTool(manager, s.enableReadFileTool, "read_file"); + wireTool(manager, s.enableProjectSearchTool, "search_project"); + wireTool(manager, s.enableCreateNewFileTool, "create_new_file"); + wireTool(manager, s.enableEditFileTool, "edit_file"); + wireTool(manager, s.enableBuildProjectTool, "build_project"); + wireTool(manager, s.enableGetIssuesListTool, "get_issues_list"); + wireTool( + manager, s.enableTerminalCommandTool, "execute_terminal_command"); + wireTool(manager, s.enableTodoTool, "todo_tool"); } } // namespace QodeAssist::Tools