Add testsystem

This commit is contained in:
Χγφτ Kompanion 2025-10-15 01:22:29 +13:00
parent 2210e3a260
commit 5ae22ff9f8
11 changed files with 277 additions and 23 deletions

View File

@ -1,7 +1,7 @@
{ {
"projectName": "metal-kompanion-mcp", "projectName": "metal-kompanion-mcp",
"projectDescription": "MCP backend for Kompanion: memory/context/embedding provider over MCP, built from scratch (qtmcp-based) to persist conversation state and serve embeddings + retrieval to avoid forgetting across threads.", "projectDescription": "MCP backend for Kompanion: memory/context/embedding provider over MCP, built from scratch (qtmcp-based) to persist conversation state and serve embeddings + retrieval to avoid forgetting across threads.",
"lastTaskId": 20, "lastTaskId": 23,
"tasks": [ "tasks": [
{ {
"id": 1, "id": 1,
@ -441,6 +441,169 @@
"message": "Task created with title: \"Claude Code integration rescue plan\"" "message": "Task created with title: \"Claude Code integration rescue plan\""
} }
] ]
},
{
"id": 21,
"title": "DAL Phase 1: libpq/pqxx wiring + SQL calls",
"description": "Link pqxx, implement PgDal against Postgres+pgvector: connect/tx, ensureNamespace, upsertItem/Chunks/Embeddings, searchText (FTS/trgm), searchVector (<->). Provide DSN via env; add cmake find + link.",
"status": "todo",
"priority": 510,
"priorityDisplay": "P0",
"dependsOn": [],
"createdAt": "2025-10-14T00:29:55.327Z",
"updatedAt": "2025-10-14T00:29:55.327Z",
"subtasks": [
{
"id": "21.1",
"title": "CMake: find_package(pqxx) and link; CI env var DSN",
"status": "todo",
"createdAt": "2025-10-14T00:30:00.856Z",
"updatedAt": "2025-10-14T00:30:00.857Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:00.856Z",
"type": "log",
"message": "Subtask created with title: \"CMake: find_package(pqxx) and link; CI env var DSN\""
}
]
},
{
"id": "21.2",
"title": "PgDal: implement connect/tx + prepared statements",
"status": "todo",
"createdAt": "2025-10-14T00:30:06.138Z",
"updatedAt": "2025-10-14T00:30:06.138Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:06.138Z",
"type": "log",
"message": "Subtask created with title: \"PgDal: implement connect/tx + prepared statements\""
}
]
},
{
"id": "21.3",
"title": "SQL: ensureNamespace, upsertItem/Chunks/Embeddings",
"status": "todo",
"createdAt": "2025-10-14T00:30:11.519Z",
"updatedAt": "2025-10-14T00:30:11.519Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:11.519Z",
"type": "log",
"message": "Subtask created with title: \"SQL: ensureNamespace, upsertItem/Chunks/Embeddings\""
}
]
},
{
"id": "21.4",
"title": "Search: FTS/trgm + vector <-> with filters (namespace/thread/tags)",
"status": "todo",
"createdAt": "2025-10-14T00:30:17.290Z",
"updatedAt": "2025-10-14T00:30:17.290Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:17.290Z",
"type": "log",
"message": "Subtask created with title: \"Search: FTS/trgm + vector <-> with filters (namespace/thread/tags)\""
}
]
}
],
"lastSubtaskIndex": 4,
"relatedFiles": [],
"activityLog": [
{
"timestamp": "2025-10-14T00:29:55.327Z",
"type": "log",
"message": "Task created with title: \"DAL Phase 1: libpq/pqxx wiring + SQL calls\""
}
]
},
{
"id": 22,
"title": "Handlers → DAL integration",
"description": "Wire kom.memory.v1.upsert_memory/search_memory to IDatabase. Parse JSON with a real parser, validate against schemas, enforce scope and sensitivity rules.",
"status": "todo",
"priority": 490,
"priorityDisplay": "P0",
"dependsOn": [],
"createdAt": "2025-10-14T00:30:26.285Z",
"updatedAt": "2025-10-14T00:30:26.285Z",
"subtasks": [
{
"id": "22.1",
"title": "Replace ad-hoc JSON with parser (nlohmann/json or simdjson)",
"status": "todo",
"createdAt": "2025-10-14T00:30:33.761Z",
"updatedAt": "2025-10-14T00:30:33.761Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:33.761Z",
"type": "log",
"message": "Subtask created with title: \"Replace ad-hoc JSON with parser (nlohmann/json or simdjson)\""
}
]
},
{
"id": "22.2",
"title": "Validate request bodies against schemas before DAL calls",
"status": "todo",
"createdAt": "2025-10-14T00:30:39.868Z",
"updatedAt": "2025-10-14T00:30:39.868Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:39.868Z",
"type": "log",
"message": "Subtask created with title: \"Validate request bodies against schemas before DAL calls\""
}
]
},
{
"id": "22.3",
"title": "Scope & sensitivity enforcement (namespace/user + skip secret embeddings)",
"status": "todo",
"createdAt": "2025-10-14T00:30:45.261Z",
"updatedAt": "2025-10-14T00:30:45.261Z",
"activityLog": [
{
"timestamp": "2025-10-14T00:30:45.261Z",
"type": "log",
"message": "Subtask created with title: \"Scope & sensitivity enforcement (namespace/user + skip secret embeddings)\""
}
]
}
],
"lastSubtaskIndex": 3,
"relatedFiles": [],
"activityLog": [
{
"timestamp": "2025-10-14T00:30:26.285Z",
"type": "log",
"message": "Task created with title: \"Handlers → DAL integration\""
}
]
},
{
"id": 23,
"title": "Contract tests: DAL-backed tools",
"description": "Expand CTest to cover DAL-backed upsert/search and backup export/import; include error cases and schema violations; run against build-komhands.",
"status": "todo",
"priority": 511,
"priorityDisplay": "P1",
"dependsOn": [],
"createdAt": "2025-10-14T00:30:51.716Z",
"updatedAt": "2025-10-14T00:30:51.716Z",
"subtasks": [],
"lastSubtaskIndex": 0,
"relatedFiles": [],
"activityLog": [
{
"timestamp": "2025-10-14T00:30:51.716Z",
"type": "log",
"message": "Task created with title: \"Contract tests: DAL-backed tools\""
}
]
} }
] ]
} }

View File

@ -8,25 +8,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# find_package(Qt6 COMPONENTS Core Network REQUIRED) # find_package(Qt6 COMPONENTS Core Network REQUIRED)
# find_package(qtmcp REQUIRED) # find_package(qtmcp REQUIRED)
find_package(PkgConfig REQUIRED) add_subdirectory(src/dal)
pkg_check_modules(PQXX REQUIRED libpqxx)
add_executable(kom_mcp add_executable(kom_mcp
src/main.cpp src/main.cpp
src/dal/PgDal.cpp
) )
# target_link_libraries(kom_mcp PRIVATE Qt6::Core Qt6::Network qtmcp) target_include_directories(kom_mcp PRIVATE src)
target_include_directories(kom_mcp PRIVATE src ${PQXX_INCLUDE_DIRS}) target_link_libraries(kom_mcp PRIVATE kom_dal)
target_link_libraries(kom_mcp PRIVATE ${PQXX_LIBRARIES})
install(TARGETS kom_mcp RUNTIME DESTINATION bin) install(TARGETS kom_mcp RUNTIME DESTINATION bin)
add_executable(test_mcp_tools option(BUILD_TESTS "Build tests" ON)
tests/contract/test_mcp_tools.cpp if (BUILD_TESTS)
src/dal/PgDal.cpp enable_testing()
) add_subdirectory(tests)
target_include_directories(test_mcp_tools PRIVATE src ${PQXX_INCLUDE_DIRS}) endif()
target_link_libraries(test_mcp_tools PRIVATE ${PQXX_LIBRARIES})
enable_testing()
add_test(NAME contract_mcp_tools COMMAND test_mcp_tools)

View File

@ -1 +0,0 @@
-- Kompanion quick checks

View File

@ -1 +0,0 @@
-- placeholder: see chat for full Branch Embeddings schema

View File

View File

6
src/dal/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
add_library(kom_dal STATIC
PgDal.cpp
)
target_compile_features(kom_dal PUBLIC cxx_std_20)
target_include_directories(kom_dal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -3,11 +3,11 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro
> ## 📈 Project Summary > ## 📈 Project Summary
> >
> **✅ Done**: 0 | **🔄 In Progress**: 0 | **⬜ Todo**: 19 | **❌ Blocked**: 0 > **✅ Done**: 0 | **🔄 In Progress**: 0 | **⬜ Todo**: 29 | **❌ Blocked**: 0
> >
> **Progress**: 0% `░░░░░░░░░░░░░░░░░░░░` 0/19 tasks > **Progress**: 0% `░░░░░░░░░░░░░░░░░░░░` 0/29 tasks
> >
> **Priorities**: 🚨 **Critical**: 0 | 🔴 **High**: 1 | 🟡 **Medium**: 20 | 🟢 **Low**: 0 > **Priorities**: 🚨 **Critical**: 0 | 🔴 **High**: 1 | 🟡 **Medium**: 30 | 🟢 **Low**: 0
## Tasks ## Tasks
@ -33,6 +33,9 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro
| #18 | ⬜ todo | 492 | **Expose Agentic-Control-Framework as a tool** | Wrap ACF endpoints into a too... | | #18 | ⬜ todo | 492 | **Expose Agentic-Control-Framework as a tool** | Wrap ACF endpoints into a too... |
| #19 | ⬜ todo | 509 | **DAL skeleton + SQL calls (pgvector)** | Create DAL interfaces and pgv... | | #19 | ⬜ todo | 509 | **DAL skeleton + SQL calls (pgvector)** | Create DAL interfaces and pgv... |
| #20 | ⬜ todo | 491 | **Claude Code integration rescue plan** | Stabilize Qwen-2.5-Coder insi... | | #20 | ⬜ todo | 491 | **Claude Code integration rescue plan** | Stabilize Qwen-2.5-Coder insi... |
| #21 | ⬜ todo | 510 | **DAL Phase 1: libpq/pqxx wiring + SQL calls** | Link pqxx, implement PgDal ag... |
| #22 | ⬜ todo | 490 | **Handlers → DAL integration** | Wire kom.memory.v1.upsert_mem... |
| #23 | ⬜ todo | 511 | **Contract tests: DAL-backed tools** | Expand CTest to cover DAL-bac... |
### Task #2: Design MCP memory/context API - Subtasks ### Task #2: Design MCP memory/context API - Subtasks
@ -41,3 +44,20 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro
|:--:|:------:|:------| |:--:|:------:|:------|
| #2.1 | ⬜ todo | Write JSON Schemas for tools (done) | | #2.1 | ⬜ todo | Write JSON Schemas for tools (done) |
### Task #21: DAL Phase 1: libpq/pqxx wiring + SQL calls - Subtasks
| ID | Status | Title |
|:--:|:------:|:------|
| #21.1 | ⬜ todo | CMake: find_package(pqxx) and link; CI env var DSN |
| #21.2 | ⬜ todo | PgDal: implement connect/tx + prepared statements |
| #21.3 | ⬜ todo | SQL: ensureNamespace, upsertItem/Chunks/Embeddings |
| #21.4 | ⬜ todo | Search: FTS/trgm + vector <-> with filters (namespace/thread/tags) |
### Task #22: Handlers → DAL integration - Subtasks
| ID | Status | Title |
|:--:|:------:|:------|
| #22.1 | ⬜ todo | Replace ad-hoc JSON with parser (nlohmann/json or simdjson) |
| #22.2 | ⬜ todo | Validate request bodies against schemas before DAL calls |
| #22.3 | ⬜ todo | Scope & sensitivity enforcement (namespace/user + skip secret embeddings) |

13
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
add_executable(test_mcp_tools
contract/test_mcp_tools.cpp
)
target_include_directories(test_mcp_tools PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_link_libraries(test_mcp_tools PRIVATE kom_dal)
add_test(NAME contract_mcp_tools COMMAND test_mcp_tools)
add_library(contract_memory STATIC
contract_memory.cpp
)
target_include_directories(contract_memory PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_link_libraries(contract_memory PRIVATE kom_dal)

View File

@ -20,15 +20,34 @@ int main() {
KomMcpServer server; KomMcpServer server;
register_default_tools(server); register_default_tools(server);
const std::string upsertReq = R"({"namespace":"tests","items":[{"text":"hello world"},{"text":"hola mundo"}]})"; const std::string upsertReq = R"({"namespace":"tests","items":[{"text":"hello world","tags":["greeting"],"embedding":[0.1,0.2,0.3]},{"text":"hola mundo","embedding":[0.05,0.1,0.15]}]})";
std::string upsertResp = server.dispatch("kom.memory.v1.upsert_memory", upsertReq); std::string upsertResp = server.dispatch("kom.memory.v1.upsert_memory", upsertReq);
if (!expect_contains(upsertResp, "\"upserted\":2", "upsert_memory count")) return 1; if (!expect_contains(upsertResp, "\"upserted\":2", "upsert_memory count")) return 1;
if (!expect_contains(upsertResp, "\"status\":\"ok\"", "upsert_memory status")) return 1; if (!expect_contains(upsertResp, "\"status\":\"ok\"", "upsert_memory status")) return 1;
std::string firstId;
const std::string idsAnchor = "\"ids\":[\"";
auto idsPos = upsertResp.find(idsAnchor);
if (idsPos != std::string::npos) {
idsPos += idsAnchor.size();
auto endPos = upsertResp.find("\"", idsPos);
if (endPos != std::string::npos) {
firstId = upsertResp.substr(idsPos, endPos - idsPos);
}
}
if (firstId.empty()) {
std::cerr << "Failed to extract first item id from upsert response: " << upsertResp << "\n";
return 1;
}
const std::string searchReq = R"({"namespace":"tests","query":{"text":"hello","k":3}})"; const std::string searchReq = R"({"namespace":"tests","query":{"text":"hello","k":3}})";
std::string searchResp = server.dispatch("kom.memory.v1.search_memory", searchReq); std::string searchResp = server.dispatch("kom.memory.v1.search_memory", searchReq);
if (!expect_contains(searchResp, "\"matches\"", "search_memory matches key")) return 1; if (!expect_contains(searchResp, "\"matches\"", "search_memory matches key")) return 1;
if (!expect_contains(searchResp, "\"text\":\"hello\"", "search_memory echo text")) return 1; if (!expect_contains(searchResp, "\"text\":\"hello world\"", "search_memory returns stored text")) return 1;
const std::string vectorReq = R"({"namespace":"tests","query":{"embedding":[0.1,0.2,0.3],"k":1}})";
std::string vectorResp = server.dispatch("kom.memory.v1.search_memory", vectorReq);
if (!expect_contains(vectorResp, "\"id\":\""+firstId+"\"", "vector search returns stored id")) return 1;
const std::string exportReq = R"({"namespace":"tests","destination":"/tmp/example.enc"})"; const std::string exportReq = R"({"namespace":"tests","destination":"/tmp/example.enc"})";
std::string exportResp = server.dispatch("kom.local.v1.backup.export_encrypted", exportReq); std::string exportResp = server.dispatch("kom.local.v1.backup.export_encrypted", exportReq);

42
tests/contract_memory.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "dal/PgDal.hpp"
#include <string>
#include <vector>
static void contract_pgdal_basic() {
kom::PgDal dal;
dal.connect("stub://memory");
auto ns = dal.ensureNamespace("tests");
static_cast<void>(ns);
kom::ItemRow item;
item.namespace_id = "tests";
item.text = std::string("example");
item.tags = {"alpha", "beta"};
item.id = dal.upsertItem(item);
kom::ChunkRow chunk;
chunk.item_id = item.id;
chunk.text = "chunk-text";
auto chunkIds = dal.upsertChunks(std::vector<kom::ChunkRow>{chunk});
if (!chunkIds.empty()) {
chunk.id = chunkIds.front();
}
kom::EmbeddingRow embedding;
embedding.chunk_id = chunk.id;
embedding.model = "stub-model";
embedding.dim = 3;
embedding.vector = {0.1f, 0.2f, 0.3f};
dal.upsertEmbeddings(std::vector<kom::EmbeddingRow>{embedding});
static_cast<void>(dal.searchText("tests", "chunk", 5));
static_cast<void>(dal.searchVector("tests", embedding.vector, 5));
static_cast<void>(dal.getItemById(item.id));
static_cast<void>(dal.hybridSearch(embedding.vector, "stub-model", "tests", "chunk", 5));
}
static const bool contract_pgdal_compiles = [] {
contract_pgdal_basic();
return true;
}();