// Copyright (C) 2026 Petr Mironychev // SPDX-License-Identifier: GPL-3.0-or-later // Additional attribution terms under GPLv3 §7(b) apply — see LICENSE #include "session/TurnContextBuilder.hpp" #include #include namespace QodeAssist::Session { TurnContextBuilder::TurnContextBuilder( const IProjectContextPort &project, const ISkillsContextPort *skills, const ILinkedFilesPort &linkedFiles) : m_project(project) , m_skills(skills) , m_linkedFiles(linkedFiles) {} TurnContext TurnContextBuilder::build(const TurnContextRequest &request) const { TurnContext context; context.basePrompt = request.basePrompt; context.rolePrompt = request.rolePrompt; context.project = m_project.projectInfo(); if (context.project.available) context.projectRules = m_project.projectRules(); if (m_skills) { context.alwaysOnSkills = m_skills->alwaysOnBodies(); context.skillsCatalog = m_skills->catalogText(); static const QRegularExpression skillCommand( QStringLiteral("(?:^|\\s)/([a-z0-9][a-z0-9-]*)")); QStringList invokedSkillNames; auto skillMatch = skillCommand.globalMatch(request.message); while (skillMatch.hasNext()) { const QString skillName = skillMatch.next().captured(1); if (invokedSkillNames.contains(skillName)) continue; const auto invokedSkill = m_skills->findSkill(skillName); if (invokedSkill && !invokedSkill->body.isEmpty()) { invokedSkillNames << skillName; context.invokedSkills.append(*invokedSkill); } } } context.linkedFilePaths = request.linkedFilePaths; if (!request.linkedFilePaths.isEmpty()) context.linkedFiles = m_linkedFiles.readFiles(request.linkedFilePaths); return context; } } // namespace QodeAssist::Session