diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp index 4575368..65d4715 100644 --- a/ChatView/ClientInterface.cpp +++ b/ChatView/ClientInterface.cpp @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include #include @@ -37,6 +40,7 @@ #include "Logger.hpp" #include "ProvidersManager.hpp" #include "RequestConfig.hpp" +#include namespace QodeAssist::Chat { @@ -81,6 +85,17 @@ void ClientInterface::sendMessage( if (chatAssistantSettings.useSystemPrompt()) { QString systemPrompt = chatAssistantSettings.systemPrompt(); + + auto project = LLMCore::RulesLoader::getActiveProject(); + if (project) { + QString projectRules + = LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::RulesContext::Chat); + + if (!projectRules.isEmpty()) { + systemPrompt += "\n\n# Project Rules\n\n" + projectRules; + } + } + if (!linkedFiles.isEmpty()) { systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles); } diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 38c8677..52360bc 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -30,6 +30,7 @@ #include "settings/CodeCompletionSettings.hpp" #include "settings/GeneralSettings.hpp" #include +#include namespace QodeAssist { @@ -242,6 +243,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) && promptTemplate->type() == LLMCore::TemplateType::Chat ? m_completeSettings.systemPromptForNonFimModels() : m_completeSettings.systemPrompt()); + + auto project = LLMCore::RulesLoader::getActiveProject(); + if (project) { + QString projectRules + = LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::RulesContext::Completions); + + if (!projectRules.isEmpty()) { + systemPrompt += "\n\n# Project Rules\n\n" + projectRules; + LOG_MESSAGE("Loaded project rules for completion"); + } + } + if (updatedContext.fileContext.has_value()) systemPrompt.append(updatedContext.fileContext.value()); diff --git a/QuickRefactorHandler.cpp b/QuickRefactorHandler.cpp index faaf8b5..40921b7 100644 --- a/QuickRefactorHandler.cpp +++ b/QuickRefactorHandler.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -206,6 +207,18 @@ LLMCore::ContextData QuickRefactorHandler::prepareContext( } QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt(); + + auto project = LLMCore::RulesLoader::getActiveProject(); + if (project) { + QString projectRules = LLMCore::RulesLoader::loadRulesForProject( + project, LLMCore::RulesContext::QuickRefactor); + + if (!projectRules.isEmpty()) { + systemPrompt += "\n\n# Project Rules\n\n" + projectRules; + LOG_MESSAGE("Loaded project rules for quick refactor"); + } + } + systemPrompt += "\n\nFile information:"; systemPrompt += "\nLanguage: " + documentInfo.mimeType; systemPrompt += "\nFile path: " + documentInfo.filePath; diff --git a/README.md b/README.md index b5a346e..ddbf6bc 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,34 @@ You're all set! QodeAssist is now ready to use in Qt Creator. The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings. +## Project Rules Configuration + +QodeAssist supports project-specific rules to customize AI behavior for your codebase. Create a `.qodeassist/rules/` directory in your project root. + +### Quick Start +```bash +mkdir -p .qodeassist/rules/{common,completion,chat,quickrefactor} +``` +``` +.qodeassist/ +└── rules/ + ├── common/ # Applied to all contexts + ├── completion/ # Code completion only + ├── chat/ # Chat assistant only + └── quickrefactor/ # Quick refactor only +``` +All .md files in each directory are automatically loaded and added to the system prompt. + +Example +Create .qodeassist/rules/common/general.md: +```markdown +# Project Guidelines +- Use snake_case for private members +- Prefix interfaces with 'I' +- Always document public APIs +- Prefer Qt containers over STL +``` + ## File Context Feature QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant. diff --git a/llmcore/CMakeLists.txt b/llmcore/CMakeLists.txt index 00ed401..8137595 100644 --- a/llmcore/CMakeLists.txt +++ b/llmcore/CMakeLists.txt @@ -15,9 +15,9 @@ add_library(LLMCore STATIC HttpClient.hpp HttpClient.cpp DataBuffers.hpp SSEBuffer.hpp SSEBuffer.cpp - BaseTool.hpp - BaseTool.cpp + BaseTool.hpp BaseTool.cpp ContentBlocks.hpp + RulesLoader.hpp RulesLoader.cpp ) target_link_libraries(LLMCore diff --git a/llmcore/RulesLoader.cpp b/llmcore/RulesLoader.cpp new file mode 100644 index 0000000..f55cee9 --- /dev/null +++ b/llmcore/RulesLoader.cpp @@ -0,0 +1,112 @@ +/* + * 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 "RulesLoader.hpp" + +#include +#include + +#include +#include +#include + +namespace QodeAssist::LLMCore { + +QString RulesLoader::loadRules(const QString &projectPath, RulesContext context) +{ + if (projectPath.isEmpty()) { + return QString(); + } + + QString combined; + QString basePath = projectPath + "/.qodeassist/rules"; + + combined += loadAllMarkdownFiles(basePath + "/common"); + + switch (context) { + case RulesContext::Completions: + combined += loadAllMarkdownFiles(basePath + "/completions"); + break; + case RulesContext::Chat: + combined += loadAllMarkdownFiles(basePath + "/chat"); + break; + case RulesContext::QuickRefactor: + combined += loadAllMarkdownFiles(basePath + "/quickrefactor"); + break; + } + + return combined; +} + +QString RulesLoader::loadRulesForProject(ProjectExplorer::Project *project, RulesContext context) +{ + if (!project) { + return QString(); + } + + QString projectPath = getProjectPath(project); + return loadRules(projectPath, context); +} + +ProjectExplorer::Project *RulesLoader::getActiveProject() +{ + auto currentEditor = Core::EditorManager::currentEditor(); + if (currentEditor && currentEditor->document()) { + Utils::FilePath filePath = currentEditor->document()->filePath(); + auto project = ProjectExplorer::ProjectManager::projectForFile(filePath); + if (project) { + return project; + } + } + + return ProjectExplorer::ProjectManager::startupProject(); +} + +QString RulesLoader::loadAllMarkdownFiles(const QString &dirPath) +{ + QString combined; + QDir dir(dirPath); + + if (!dir.exists()) { + return QString(); + } + + QStringList mdFiles = dir.entryList({"*.md"}, QDir::Files, QDir::Name); + + for (const QString &fileName : mdFiles) { + QFile file(dir.filePath(fileName)); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + combined += file.readAll(); + combined += "\n\n"; + } + } + + return combined; +} + +QString RulesLoader::getProjectPath(ProjectExplorer::Project *project) +{ + if (!project) { + return QString(); + } + + return project->projectDirectory().toUrlishString(); +} + +} // namespace QodeAssist::LLMCore diff --git a/llmcore/RulesLoader.hpp b/llmcore/RulesLoader.hpp new file mode 100644 index 0000000..d0cd238 --- /dev/null +++ b/llmcore/RulesLoader.hpp @@ -0,0 +1,44 @@ +/* + * 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 + +namespace ProjectExplorer { +class Project; +} + +namespace QodeAssist::LLMCore { + +enum class RulesContext { Completions, Chat, QuickRefactor }; + +class RulesLoader +{ +public: + static QString loadRules(const QString &projectPath, RulesContext context); + static QString loadRulesForProject(ProjectExplorer::Project *project, RulesContext context); + static ProjectExplorer::Project *getActiveProject(); + +private: + static QString loadAllMarkdownFiles(const QString &dirPath); + static QString getProjectPath(ProjectExplorer::Project *project); +}; + +} // namespace QodeAssist::LLMCore