metal-kompanion/src/mcp/HandlersIntrospection.hpp

144 lines
4.1 KiB
C++

#pragma once
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
namespace Handlers {
namespace detail {
inline const std::filesystem::path& projectRoot() {
static const std::filesystem::path root =
#ifdef PROJECT_SOURCE_DIR
std::filesystem::path(PROJECT_SOURCE_DIR);
#else
std::filesystem::current_path();
#endif
return root;
}
inline std::string jsonEscape(const std::string& in) {
std::ostringstream os;
for (char c : in) {
switch (c) {
case '\"': os << "\\\""; break;
case '\\': os << "\\\\"; break;
case '\b': os << "\\b"; break;
case '\f': os << "\\f"; break;
case '\n': os << "\\n"; break;
case '\r': os << "\\r"; break;
case '\t': os << "\\t"; break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
os << "\\u" << std::hex << std::uppercase << static_cast<int>(c);
} else {
os << c;
}
break;
}
}
return os.str();
}
inline std::string readFileHead(const std::string& relativePath,
int maxLines,
std::size_t maxBytes) {
const std::filesystem::path path = projectRoot() / relativePath;
std::ifstream input(path);
if (!input) {
return std::string("missing: ") + relativePath;
}
std::ostringstream oss;
std::string line;
int lineCount = 0;
while (std::getline(input, line)) {
oss << line << '\n';
if (++lineCount >= maxLines || oss.tellp() >= static_cast<std::streampos>(maxBytes)) {
break;
}
}
return oss.str();
}
inline std::string runCommandCapture(const char* cmd, std::size_t maxBytes = 8192) {
#ifdef _WIN32
(void)cmd;
(void)maxBytes;
return "git status capture not supported on this platform";
#else
struct PipeCloser {
void operator()(FILE* file) const noexcept {
if (file != nullptr) {
pclose(file);
}
}
};
std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd, "r"), PipeCloser{});
if (!pipe) {
return "git status unavailable";
}
std::ostringstream oss;
char buffer[256];
std::size_t total = 0;
while (fgets(buffer, sizeof(buffer), pipe.get())) {
const std::size_t len = std::strlen(buffer);
total += len;
if (total > maxBytes) {
oss.write(buffer, static_cast<std::streamsize>(maxBytes - (total - len)));
break;
}
oss.write(buffer, static_cast<std::streamsize>(len));
}
return oss.str();
#endif
}
inline std::optional<std::string> currentDsnSource() {
const char* env = std::getenv("PG_DSN");
if (env && *env) {
return std::string(env);
}
return std::nullopt;
}
} // namespace detail
// Produces a JSON response summarising project state: memory docs, task table, git status.
inline std::string project_snapshot(const std::string& reqJson) {
(void)reqJson;
const std::string memorySummary =
detail::readFileHead("docs/memory-architecture.md", 40, 4096);
const std::string tasksSummary =
detail::readFileHead("tasks-table.md", 40, 4096);
const std::string gitStatus =
detail::runCommandCapture("git status --short --branch 2>/dev/null");
std::ostringstream json;
json << "{";
json << "\"sections\":[";
json << "{\"title\":\"memory_architecture\",\"body\":\"" << detail::jsonEscape(memorySummary) << "\"},";
json << "{\"title\":\"tasks_table\",\"body\":\"" << detail::jsonEscape(tasksSummary) << "\"},";
json << "{\"title\":\"git_status\",\"body\":\"" << detail::jsonEscape(gitStatus) << "\"}";
json << "]";
if (auto dsn = detail::currentDsnSource()) {
json << ",\"pg_dsn\":\"" << detail::jsonEscape(*dsn) << "\"";
}
json << ",\"notes\":\"Project snapshot generated by Kompanion to aid MCP agents.\"";
json << "}";
return json.str();
}
} // namespace Handlers