refactor: Separate and simplified tools (#340)

This commit is contained in:
Petr Mironychev
2026-04-19 18:12:15 +02:00
committed by GitHub
parent ede2c01eb7
commit 2891b313d2
16 changed files with 465 additions and 245 deletions

View File

@@ -151,7 +151,8 @@ add_qtc_plugin(QodeAssist
tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp
tools/ExecuteTerminalCommandTool.hpp tools/ExecuteTerminalCommandTool.cpp tools/ExecuteTerminalCommandTool.hpp tools/ExecuteTerminalCommandTool.cpp
tools/ProjectSearchTool.hpp tools/ProjectSearchTool.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/FileSearchUtils.hpp tools/FileSearchUtils.cpp
tools/TodoTool.hpp tools/TodoTool.cpp tools/TodoTool.hpp tools/TodoTool.cpp
) )

View File

@@ -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_IN_NAVIGATION_PANEL[] = "QodeAssist.caEnableChatInNavigationPanel";
const char CA_ENABLE_CHAT_TOOLS[] = "QodeAssist.caEnableChatTools"; const char CA_ENABLE_CHAT_TOOLS[] = "QodeAssist.caEnableChatTools";
const char CA_USE_TOOLS[] = "QodeAssist.caUseTools"; 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_ALLOW_ACCESS_OUTSIDE_PROJECT[] = "QodeAssist.caAllowAccessOutsideProject";
const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileTool"; const char CA_ENABLE_LIST_PROJECT_FILES_TOOL[] = "QodeAssist.caEnableListProjectFilesTool";
const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectTool"; const char CA_ENABLE_FIND_FILE_TOOL[] = "QodeAssist.caEnableFindFileTool";
const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandTool"; const char CA_ENABLE_READ_FILE_TOOL[] = "QodeAssist.caEnableReadFileTool";
const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoTool"; 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[] = "QodeAssist.caAllowedTerminalCommands";
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux"; const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS"; const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";

View File

@@ -42,66 +42,80 @@ ToolsSettings::ToolsSettings()
setDisplayName(Tr::tr("Tools")); 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.setSettingsKey(Constants::CA_ALLOW_ACCESS_OUTSIDE_PROJECT);
allowAccessOutsideProject.setLabelText(Tr::tr("Allow file access outside project")); allowAccessOutsideProject.setLabelText(Tr::tr("Allow file access outside project"));
allowAccessOutsideProject.setToolTip( allowAccessOutsideProject.setToolTip(
Tr::tr("Allow tools to access (read/write) files outside the project scope (system " Tr::tr("Allow tools to read, write, and create files outside the project scope "
"headers, Qt files, external libraries)")); "(system headers, Qt files, external libraries)."));
allowAccessOutsideProject.setDefaultValue(true); allowAccessOutsideProject.setDefaultValue(false);
autoApplyFileEdits.setSettingsKey(Constants::CA_AUTO_APPLY_FILE_EDITS); autoApplyFileEdits.setSettingsKey(Constants::CA_AUTO_APPLY_FILE_EDITS);
autoApplyFileEdits.setLabelText(Tr::tr("Automatically apply file edits")); autoApplyFileEdits.setLabelText(Tr::tr("Automatically apply file edits"));
autoApplyFileEdits.setToolTip( autoApplyFileEdits.setToolTip(
Tr::tr("When enabled, file edits suggested by AI will be applied automatically. " Tr::tr("When enabled, file edits suggested by AI are applied immediately. "
"When disabled, you will need to manually approve each edit.")); "When disabled, each edit is staged for manual approval."));
autoApplyFileEdits.setDefaultValue(false); 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.setSettingsKey(Constants::CA_ENABLE_EDIT_FILE_TOOL);
enableEditFileTool.setLabelText(Tr::tr("Enable Edit File Tool (Experimental)")); enableEditFileTool.setLabelText(Tr::tr("Edit File"));
enableEditFileTool.setToolTip( enableEditFileTool.setToolTip(
Tr::tr("Enable the experimental edit_file tool that allows AI to directly modify files. " Tr::tr("Applies find-and-replace edits to files. See \"Automatically apply file edits\" "
"This feature is under testing and may have unexpected behavior.")); "to control whether edits apply immediately or wait for review."));
enableEditFileTool.setDefaultValue(false); enableEditFileTool.setDefaultValue(true);
enableBuildProjectTool.setSettingsKey(Constants::CA_ENABLE_BUILD_PROJECT_TOOL); 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( enableBuildProjectTool.setToolTip(
Tr::tr("Enable the experimental build_project tool that allows AI to build the current " Tr::tr("Triggers a build of the active Qt Creator project and reports the result."));
"project. This feature is under testing and may have unexpected behavior.")); enableBuildProjectTool.setDefaultValue(true);
enableBuildProjectTool.setDefaultValue(false);
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.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( enableTerminalCommandTool.setToolTip(
Tr::tr("Enable the experimental execute_terminal_command tool that allows AI to execute " Tr::tr("Runs a command from the OS-specific allowed list below, in the project directory."));
"terminal commands from the allowed list. This feature is under testing and may have " enableTerminalCommandTool.setDefaultValue(true);
"unexpected behavior."));
enableTerminalCommandTool.setDefaultValue(false);
enableTodoTool.setSettingsKey(Constants::CA_ENABLE_TODO_TOOL); enableTodoTool.setSettingsKey(Constants::CA_ENABLE_TODO_TOOL);
enableTodoTool.setLabelText(Tr::tr("Enable Todo Tool")); enableTodoTool.setLabelText(Tr::tr("Todo"));
enableTodoTool.setToolTip( enableTodoTool.setToolTip(
Tr::tr("Enable the todo_tool that helps AI track and organize multi-step tasks. " Tr::tr("Lets the AI maintain a session-scoped todo list for multi-step workflows."));
"Useful for complex refactoring, debugging, and feature implementation workflows."));
enableTodoTool.setDefaultValue(true); enableTodoTool.setDefaultValue(true);
allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX); allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX);
@@ -159,24 +173,30 @@ ToolsSettings::ToolsSettings()
Row{Stretch{1}, resetToDefaults}, Row{Stretch{1}, resetToDefaults},
Space{8}, Space{8},
Group{ Group{
title(Tr::tr("Tool Settings")), title(Tr::tr("Tools")),
Column{
allowFileSystemRead,
allowFileSystemWrite,
allowNetworkAccess,
allowAccessOutsideProject
}},
Space{8},
Group{
title(Tr::tr("Experimental Features")),
Column{ Column{
enableListProjectFilesTool,
enableFindFileTool,
enableReadFileTool,
enableProjectSearchTool,
enableCreateNewFileTool,
enableEditFileTool, enableEditFileTool,
enableBuildProjectTool, enableBuildProjectTool,
enableGetIssuesListTool,
enableTerminalCommandTool, enableTerminalCommandTool,
enableTodoTool, enableTodoTool}},
currentOsCommands, Space{8},
terminalCommandTimeout, Group{
autoApplyFileEdits}}, 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}}; Stretch{1}};
}); });
} }
@@ -200,13 +220,16 @@ void ToolsSettings::resetSettingsToDefaults()
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) { if (reply == QMessageBox::Yes) {
resetAspect(allowFileSystemRead);
resetAspect(allowFileSystemWrite);
resetAspect(allowNetworkAccess);
resetAspect(allowAccessOutsideProject); resetAspect(allowAccessOutsideProject);
resetAspect(autoApplyFileEdits); resetAspect(autoApplyFileEdits);
resetAspect(enableListProjectFilesTool);
resetAspect(enableFindFileTool);
resetAspect(enableReadFileTool);
resetAspect(enableProjectSearchTool);
resetAspect(enableCreateNewFileTool);
resetAspect(enableEditFileTool); resetAspect(enableEditFileTool);
resetAspect(enableBuildProjectTool); resetAspect(enableBuildProjectTool);
resetAspect(enableGetIssuesListTool);
resetAspect(enableTerminalCommandTool); resetAspect(enableTerminalCommandTool);
resetAspect(enableTodoTool); resetAspect(enableTodoTool);
resetAspect(allowedTerminalCommandsLinux); resetAspect(allowedTerminalCommandsLinux);

View File

@@ -32,21 +32,24 @@ public:
ButtonAspect resetToDefaults{this}; ButtonAspect resetToDefaults{this};
Utils::BoolAspect allowFileSystemRead{this};
Utils::BoolAspect allowFileSystemWrite{this};
Utils::BoolAspect allowNetworkAccess{this};
Utils::BoolAspect allowAccessOutsideProject{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 enableEditFileTool{this};
Utils::BoolAspect enableBuildProjectTool{this}; Utils::BoolAspect enableBuildProjectTool{this};
Utils::BoolAspect enableGetIssuesListTool{this};
Utils::BoolAspect enableTerminalCommandTool{this}; Utils::BoolAspect enableTerminalCommandTool{this};
Utils::BoolAspect enableTodoTool{this}; Utils::BoolAspect enableTodoTool{this};
Utils::StringAspect allowedTerminalCommandsLinux{this}; Utils::StringAspect allowedTerminalCommandsLinux{this};
Utils::StringAspect allowedTerminalCommandsMacOS{this}; Utils::StringAspect allowedTerminalCommandsMacOS{this};
Utils::StringAspect allowedTerminalCommandsWindows{this}; Utils::StringAspect allowedTerminalCommandsWindows{this};
Utils::IntegerAspect terminalCommandTimeout{this}; Utils::IntegerAspect terminalCommandTimeout{this};
Utils::BoolAspect autoApplyFileEdits{this};
private: private:
void setupConnections(); void setupConnections();

View File

@@ -71,13 +71,10 @@ QString BuildProjectTool::displayName() const
QString BuildProjectTool::description() const QString BuildProjectTool::description() const
{ {
return "Build the current project in Qt Creator and wait for completion. " return "Build the active Qt Creator project using its current build configuration and block "
"Optionally run the project after successful build. " "until the build finishes. Returns success/failure along with the full compiler output "
"Returns build status (success/failure) and any compilation errors/warnings after " "(stdout + stderr). Use `get_issues_list` afterwards to get structured diagnostics. "
"the build finishes. " "This call is blocking and may take a long time for large projects.";
"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.";
} }
QJsonObject BuildProjectTool::parametersSchema() const QJsonObject BuildProjectTool::parametersSchema() const

View File

@@ -49,10 +49,10 @@ QString CreateNewFileTool::displayName() const
QString CreateNewFileTool::description() const QString CreateNewFileTool::description() const
{ {
return "Create a new empty file at the specified path. " return "Create a new empty file at the given absolute path. Any missing parent directories "
"If the directory path does not exist, it will be created automatically. " "are created automatically. The file is written to disk only — it is NOT added to the "
"Provide absolute file path. After creating files, add the file " "project's build system automatically; the user must register it in CMakeLists.txt or "
"to the project file"; "the equivalent project file. Use `edit_file` afterwards to populate its content.";
} }
QJsonObject CreateNewFileTool::parametersSchema() const QJsonObject CreateNewFileTool::parametersSchema() const
@@ -61,7 +61,9 @@ QJsonObject CreateNewFileTool::parametersSchema() const
QJsonObject filepathProperty; QJsonObject filepathProperty;
filepathProperty["type"] = "string"; 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; properties["filepath"] = filepathProperty;
QJsonObject definition; QJsonObject definition;

View File

@@ -57,7 +57,7 @@ QString EditFileTool::description() const
"and new_content to replace it with. Changes are applied immediately if auto-apply " "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. " "is enabled in settings. The user can undo or reapply changes at any time. "
"\n\nIMPORTANT:" "\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- 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- For EMPTY files: use empty old_content (empty string or omit parameter)."
"\n- To append at the END of file: use empty old_content." "\n- To append at the END of file: use empty old_content."

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#include "FindAndReadFileTool.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <logger/Logger.hpp>
#include <QJsonArray>
#include <QJsonObject>
#include <QtConcurrent>
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<LLMQore::ToolResult> 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

108
tools/FindFileTool.cpp Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#include "FindFileTool.hpp"
#include "FileSearchUtils.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <logger/Logger.hpp>
#include <QJsonArray>
#include <QJsonObject>
#include <QtConcurrent>
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<LLMQore::ToolResult> 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

View File

@@ -19,8 +19,6 @@
#pragma once #pragma once
#include "FileSearchUtils.hpp"
#include <context/IgnoreManager.hpp> #include <context/IgnoreManager.hpp>
#include <LLMQore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
#include <QFuture> #include <QFuture>
@@ -29,12 +27,12 @@
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
class FindAndReadFileTool : public ::LLMQore::BaseTool class FindFileTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit FindAndReadFileTool(QObject *parent = nullptr); explicit FindFileTool(QObject *parent = nullptr);
QString id() const override; QString id() const override;
QString displayName() const override; QString displayName() const override;
@@ -43,8 +41,6 @@ public:
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override; QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
private: private:
QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const;
Context::IgnoreManager *m_ignoreManager; Context::IgnoreManager *m_ignoreManager;
}; };

View File

@@ -133,9 +133,10 @@ QString GetIssuesListTool::displayName() const
QString GetIssuesListTool::description() const QString GetIssuesListTool::description() const
{ {
return "Get compilation errors, warnings, and diagnostics from Qt Creator's Issues panel. " return "Read diagnostics from Qt Creator's Issues panel, including the latest build output and "
"Returns issue descriptions with file paths and line numbers. " "live clang-codemodel warnings/errors for open files. Each issue includes file path, "
"Optional severity filter: 'error', 'warning', or 'all' (default)."; "line number, severity, and message. Run `build_project` first if you need fresh build "
"diagnostics.";
} }
QJsonObject GetIssuesListTool::parametersSchema() const QJsonObject GetIssuesListTool::parametersSchema() const

View File

@@ -50,8 +50,10 @@ QString ListProjectFilesTool::displayName() const
QString ListProjectFilesTool::description() const QString ListProjectFilesTool::description() const
{ {
return "Get a list of all source files in the current project with absolute and relative paths. " return "List every source file tracked by the active Qt Creator project(s), filtered by "
"Useful for understanding project structure. No parameters required."; ".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 QJsonObject ListProjectFilesTool::parametersSchema() const

View File

@@ -56,39 +56,50 @@ QString ProjectSearchTool::displayName() const
QString ProjectSearchTool::description() const QString ProjectSearchTool::description() const
{ {
return "Search project for text content or C++ symbols. " return "Search across all open Qt Creator project(s) for text occurrences or C++ symbol "
"Text mode: finds text patterns in files. " "definitions. 'text' mode scans source files line-by-line for literal text or regex. "
"Symbol mode: finds C++ definitions (classes, functions, etc)."; "'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 ProjectSearchTool::parametersSchema() const
{ {
QJsonObject properties; QJsonObject properties;
properties["query"] properties["query"] = QJsonObject{
= QJsonObject{{"type", "string"}, {"description", "Text or symbol name to search for"}}; {"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{ properties["search_type"] = QJsonObject{
{"type", "string"}, {"type", "string"},
{"enum", QJsonArray{"text", "symbol"}}, {"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{ properties["symbol_type"] = QJsonObject{
{"type", "string"}, {"type", "string"},
{"enum", QJsonArray{"all", "class", "function", "enum", "variable", "namespace"}}, {"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"] properties["case_sensitive"] = QJsonObject{
= QJsonObject{{"type", "boolean"}, {"description", "Case-sensitive search"}}; {"type", "boolean"},
{"description", "Case-sensitive matching. Default: false."}};
properties["use_regex"] properties["use_regex"] = QJsonObject{
= QJsonObject{{"type", "boolean"}, {"description", "Use regex patterns"}}; {"type", "boolean"},
{"description", "Treat the query as a regular expression. Default: false."}};
properties["whole_words"] properties["whole_words"] = QJsonObject{
= QJsonObject{{"type", "boolean"}, {"description", "Match whole words only (text mode)"}}; {"type", "boolean"},
{"description", "Match whole words only. Text mode only; ignored in symbol mode. "
"Default: false."}};
properties["file_pattern"] = QJsonObject{ 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; QJsonObject definition;
definition["type"] = "object"; definition["type"] = "object";

122
tools/ReadFileTool.cpp Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#include "ReadFileTool.hpp"
#include "FileSearchUtils.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <context/ProjectUtils.hpp>
#include <logger/Logger.hpp>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonObject>
#include <QtConcurrent>
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<LLMQore::ToolResult> 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

43
tools/ReadFileTool.hpp Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <LLMQore/BaseTool.hpp>
#include <QFuture>
#include <QJsonObject>
#include <QObject>
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<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
};
} // namespace QodeAssist::Tools

View File

@@ -21,29 +21,62 @@
#include <LLMQore/ToolsManager.hpp> #include <LLMQore/ToolsManager.hpp>
#include <settings/ToolsSettings.hpp>
#include <utils/aspects.h>
#include "BuildProjectTool.hpp" #include "BuildProjectTool.hpp"
#include "CreateNewFileTool.hpp" #include "CreateNewFileTool.hpp"
#include "EditFileTool.hpp" #include "EditFileTool.hpp"
#include "ExecuteTerminalCommandTool.hpp" #include "ExecuteTerminalCommandTool.hpp"
#include "FindAndReadFileTool.hpp" #include "FindFileTool.hpp"
#include "GetIssuesListTool.hpp" #include "GetIssuesListTool.hpp"
#include "ListProjectFilesTool.hpp" #include "ListProjectFilesTool.hpp"
#include "ProjectSearchTool.hpp" #include "ProjectSearchTool.hpp"
#include "ReadFileTool.hpp"
#include "TodoTool.hpp" #include "TodoTool.hpp"
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
namespace {
template<typename ToolT>
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) void registerQodeAssistTools(::LLMQore::ToolsManager *manager)
{ {
manager->addTool(new ListProjectFilesTool(manager)); auto &s = Settings::toolsSettings();
manager->addTool(new GetIssuesListTool(manager));
manager->addTool(new CreateNewFileTool(manager)); wireTool<ListProjectFilesTool>(manager, s.enableListProjectFilesTool, "list_project_files");
manager->addTool(new EditFileTool(manager)); wireTool<FindFileTool>(manager, s.enableFindFileTool, "find_file");
manager->addTool(new BuildProjectTool(manager)); wireTool<ReadFileTool>(manager, s.enableReadFileTool, "read_file");
manager->addTool(new ExecuteTerminalCommandTool(manager)); wireTool<ProjectSearchTool>(manager, s.enableProjectSearchTool, "search_project");
manager->addTool(new ProjectSearchTool(manager)); wireTool<CreateNewFileTool>(manager, s.enableCreateNewFileTool, "create_new_file");
manager->addTool(new FindAndReadFileTool(manager)); wireTool<EditFileTool>(manager, s.enableEditFileTool, "edit_file");
manager->addTool(new TodoTool(manager)); wireTool<BuildProjectTool>(manager, s.enableBuildProjectTool, "build_project");
wireTool<GetIssuesListTool>(manager, s.enableGetIssuesListTool, "get_issues_list");
wireTool<ExecuteTerminalCommandTool>(
manager, s.enableTerminalCommandTool, "execute_terminal_command");
wireTool<TodoTool>(manager, s.enableTodoTool, "todo_tool");
} }
} // namespace QodeAssist::Tools } // namespace QodeAssist::Tools