144 lines
4.1 KiB
C++
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
|