feat: Add project-specific rules support

This commit is contained in:
Petr Mironychev
2025-10-14 01:53:44 +02:00
parent 45df27e749
commit ff0f994ec6
7 changed files with 227 additions and 2 deletions

View File

@ -28,6 +28,9 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h> #include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/idocument.h> #include <coreplugin/idocument.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
@ -37,6 +40,7 @@
#include "Logger.hpp" #include "Logger.hpp"
#include "ProvidersManager.hpp" #include "ProvidersManager.hpp"
#include "RequestConfig.hpp" #include "RequestConfig.hpp"
#include <RulesLoader.hpp>
namespace QodeAssist::Chat { namespace QodeAssist::Chat {
@ -81,6 +85,17 @@ void ClientInterface::sendMessage(
if (chatAssistantSettings.useSystemPrompt()) { if (chatAssistantSettings.useSystemPrompt()) {
QString systemPrompt = chatAssistantSettings.systemPrompt(); 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()) { if (!linkedFiles.isEmpty()) {
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles); systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
} }

View File

@ -30,6 +30,7 @@
#include "settings/CodeCompletionSettings.hpp" #include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp" #include "settings/GeneralSettings.hpp"
#include <llmcore/RequestConfig.hpp> #include <llmcore/RequestConfig.hpp>
#include <llmcore/RulesLoader.hpp>
namespace QodeAssist { namespace QodeAssist {
@ -242,6 +243,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
&& promptTemplate->type() == LLMCore::TemplateType::Chat && promptTemplate->type() == LLMCore::TemplateType::Chat
? m_completeSettings.systemPromptForNonFimModels() ? m_completeSettings.systemPromptForNonFimModels()
: m_completeSettings.systemPrompt()); : 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()) if (updatedContext.fileContext.has_value())
systemPrompt.append(updatedContext.fileContext.value()); systemPrompt.append(updatedContext.fileContext.value());

View File

@ -29,6 +29,7 @@
#include <llmcore/PromptTemplateManager.hpp> #include <llmcore/PromptTemplateManager.hpp>
#include <llmcore/ProvidersManager.hpp> #include <llmcore/ProvidersManager.hpp>
#include <llmcore/RequestConfig.hpp> #include <llmcore/RequestConfig.hpp>
#include <llmcore/RulesLoader.hpp>
#include <logger/Logger.hpp> #include <logger/Logger.hpp>
#include <settings/ChatAssistantSettings.hpp> #include <settings/ChatAssistantSettings.hpp>
#include <settings/GeneralSettings.hpp> #include <settings/GeneralSettings.hpp>
@ -206,6 +207,18 @@ LLMCore::ContextData QuickRefactorHandler::prepareContext(
} }
QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt(); 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 += "\n\nFile information:";
systemPrompt += "\nLanguage: " + documentInfo.mimeType; systemPrompt += "\nLanguage: " + documentInfo.mimeType;
systemPrompt += "\nFile path: " + documentInfo.filePath; systemPrompt += "\nFile path: " + documentInfo.filePath;

View File

@ -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. 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 ## 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. 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.

View File

@ -15,9 +15,9 @@ add_library(LLMCore STATIC
HttpClient.hpp HttpClient.cpp HttpClient.hpp HttpClient.cpp
DataBuffers.hpp DataBuffers.hpp
SSEBuffer.hpp SSEBuffer.cpp SSEBuffer.hpp SSEBuffer.cpp
BaseTool.hpp BaseTool.hpp BaseTool.cpp
BaseTool.cpp
ContentBlocks.hpp ContentBlocks.hpp
RulesLoader.hpp RulesLoader.cpp
) )
target_link_libraries(LLMCore target_link_libraries(LLMCore

112
llmcore/RulesLoader.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "RulesLoader.hpp"
#include <QDir>
#include <QFile>
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
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

44
llmcore/RulesLoader.hpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
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