diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a02469..c412bac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ project(metal_kompanion_mcp LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +include(GNUInstallDirs) + option(KOM_ENABLE_PG "Enable Postgres-backed DAL support" ON) set(KOM_HAVE_PG OFF) @@ -38,6 +40,9 @@ if (KOMPANION_KCONFIG_TARGET STREQUAL "") message(WARNING "KConfig (KF6/KF5) not found; defaulting to environment-based configuration.") endif() +set(KOMPANION_DB_INIT_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/kompanion/db/init") +install(DIRECTORY db/init/ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/kompanion/db/init FILES_MATCHING PATTERN "*.sql") + option(BUILD_KOMPANION_CLI "Build Kompanion Qt command-line client" ON) if (BUILD_KOMPANION_CLI) find_package(Qt6 COMPONENTS Core QUIET) @@ -58,7 +63,10 @@ add_executable(kom_mcp ) target_include_directories(kom_mcp PRIVATE src) target_link_libraries(kom_mcp PRIVATE kom_dal) -target_compile_definitions(kom_mcp PRIVATE PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}") +target_compile_definitions(kom_mcp PRIVATE + PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + KOMPANION_DB_INIT_INSTALL_DIR="${KOMPANION_DB_INIT_INSTALL_DIR}" +) if (NOT KOMPANION_KCONFIG_TARGET STREQUAL "") target_link_libraries(kom_mcp PRIVATE ${KOMPANION_KCONFIG_TARGET}) target_compile_definitions(kom_mcp PRIVATE HAVE_KCONFIG) @@ -74,7 +82,10 @@ if (BUILD_KOMPANION_CLI) ) target_include_directories(kompanion PRIVATE src) target_link_libraries(kompanion PRIVATE Qt6::Core kom_dal) - target_compile_definitions(kompanion PRIVATE PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}") + target_compile_definitions(kompanion PRIVATE + PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + KOMPANION_DB_INIT_INSTALL_DIR="${KOMPANION_DB_INIT_INSTALL_DIR}" + ) if (NOT KOMPANION_KCONFIG_TARGET STREQUAL "") target_link_libraries(kompanion PRIVATE ${KOMPANION_KCONFIG_TARGET}) target_compile_definitions(kompanion PRIVATE HAVE_KCONFIG) diff --git a/db/core.sql b/db/core.sql deleted file mode 100644 index 6b7b0b6..0000000 --- a/db/core.sql +++ /dev/null @@ -1,59 +0,0 @@ --- metal-kompanion core schema (pgvector) -CREATE EXTENSION IF NOT EXISTS vector; - -CREATE TABLE IF NOT EXISTS knowledge ( - id BIGSERIAL PRIMARY KEY, - source TEXT, - path TEXT, - sha256 TEXT, - lineno INT, - text TEXT NOT NULL, - tags TEXT[], - created_at TIMESTAMPTZ DEFAULT now() -); - --- embeddings: 1024-dim space (extend with more tables if needed) -CREATE TABLE IF NOT EXISTS embeddings_1024 ( - id BIGSERIAL PRIMARY KEY, - knowledge_id BIGINT REFERENCES knowledge(id) ON DELETE CASCADE, - model TEXT NOT NULL, - embedding vector(1024) NOT NULL, - created_at TIMESTAMPTZ DEFAULT now() -); -CREATE INDEX IF NOT EXISTS embeddings_1024_l2 ON embeddings_1024 USING ivfflat (embedding vector_l2_ops) WITH (lists=100); - --- memory branches (git-like) -CREATE TABLE IF NOT EXISTS mem_branch ( - id BIGSERIAL PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - purpose TEXT, - created_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS mem_commit ( - id BIGSERIAL PRIMARY KEY, - branch_id BIGINT REFERENCES mem_branch(id) ON DELETE CASCADE, - parent_id BIGINT, - author_did TEXT, - message TEXT, - created_at TIMESTAMPTZ DEFAULT now() -); -CREATE INDEX IF NOT EXISTS mem_commit_branch ON mem_commit(branch_id); - --- commit deltas referencing knowledge rows -CREATE TABLE IF NOT EXISTS mem_delta ( - id BIGSERIAL PRIMARY KEY, - commit_id BIGINT REFERENCES mem_commit(id) ON DELETE CASCADE, - knowledge_id BIGINT REFERENCES knowledge(id) ON DELETE CASCADE, - action SMALLINT NOT NULL CHECK (action IN (0,1,2)) -- 0:add,1:update,2:delete -); -CREATE INDEX IF NOT EXISTS mem_delta_commit ON mem_delta(commit_id); - --- per-branch centroid for fast routing -CREATE TABLE IF NOT EXISTS branch_embedding_1024 ( - branch_id BIGINT REFERENCES mem_branch(id) ON DELETE CASCADE, - model TEXT NOT NULL, - embedding vector(1024) NOT NULL, - updated_at TIMESTAMPTZ DEFAULT now(), - PRIMARY KEY(branch_id, model) -); diff --git a/db/db-init.sql b/db/db-init.sql deleted file mode 100644 index 896bfe3..0000000 --- a/db/db-init.sql +++ /dev/null @@ -1,5 +0,0 @@ -INSERT INTO komp.space(name, model, dim) VALUES - ('dev_knowledge','mxbai-embed-large',1024), - ('pattern_exchange','mxbai-embed-large',1024), - ('runtime_memory','mxbai-embed-large',1024) -ON CONFLICT (name) DO NOTHING; diff --git a/db/schema.sql b/db/schema.sql deleted file mode 100644 index ccdbe2f..0000000 --- a/db/schema.sql +++ /dev/null @@ -1,105 +0,0 @@ --- Requires: CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS ltree; - -CREATE SCHEMA IF NOT EXISTS komp; -CREATE EXTENSION IF NOT EXISTS vector; -CREATE EXTENSION IF NOT EXISTS ltree; - -CREATE TABLE IF NOT EXISTS komp.source ( - id BIGSERIAL PRIMARY KEY, - kind TEXT NOT NULL, -- filesystem|repo|url|note - uri TEXT NOT NULL, -- path or URL - repo TEXT, - ref TEXT, - meta JSONB DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS komp.chunk ( - id BIGSERIAL PRIMARY KEY, - source_id BIGINT REFERENCES komp.source(id) ON DELETE CASCADE, - lineno INT, - text TEXT NOT NULL, - sha256 TEXT NOT NULL, - tokens INT, - created_at TIMESTAMPTZ DEFAULT now() -); -CREATE INDEX IF NOT EXISTS idx_chunk_source ON komp.chunk(source_id); - --- A space is a distinct memory with its own model+dim & policy -CREATE TABLE IF NOT EXISTS komp.space ( - id SERIAL PRIMARY KEY, - name TEXT UNIQUE, -- dev_knowledge | pattern_exchange | runtime_memory - model TEXT NOT NULL, - dim INT NOT NULL, - metric TEXT NOT NULL DEFAULT 'cosine' -); - --- Embedding tables per common dimension (add more as needed) -CREATE TABLE IF NOT EXISTS komp.embedding_768 ( - id BIGSERIAL PRIMARY KEY, - chunk_id BIGINT REFERENCES komp.chunk(id) ON DELETE CASCADE, - space_id INT REFERENCES komp.space(id) ON DELETE CASCADE, - embedding VECTOR(768) NOT NULL, - created_at TIMESTAMPTZ DEFAULT now(), - UNIQUE(chunk_id, space_id) -); -CREATE INDEX IF NOT EXISTS idx_embed768_space ON komp.embedding_768(space_id); -CREATE INDEX IF NOT EXISTS ivf_embed768 ON komp.embedding_768 USING ivfflat (embedding vector_cosine_ops) WITH (lists=100); - -CREATE TABLE IF NOT EXISTS komp.embedding_1024 ( - id BIGSERIAL PRIMARY KEY, - chunk_id BIGINT REFERENCES komp.chunk(id) ON DELETE CASCADE, - space_id INT REFERENCES komp.space(id) ON DELETE CASCADE, - embedding VECTOR(1024) NOT NULL, - created_at TIMESTAMPTZ DEFAULT now(), - UNIQUE(chunk_id, space_id) -); -CREATE INDEX IF NOT EXISTS idx_embed1024_space ON komp.embedding_1024(space_id); -CREATE INDEX IF NOT EXISTS ivf_embed1024 ON komp.embedding_1024 USING ivfflat (embedding vector_cosine_ops) WITH (lists=100); - --- Branch hierarchy (Branch Embeddings): path encodes the cluster tree (e.g., physics.quantum.tunneling) -CREATE TABLE IF NOT EXISTS komp.branch ( - id BIGSERIAL PRIMARY KEY, - space_id INT REFERENCES komp.space(id) ON DELETE CASCADE, - path LTREE NOT NULL, - label TEXT, - meta JSONB DEFAULT '{}'::jsonb, - UNIQUE(space_id, path) -); -CREATE INDEX IF NOT EXISTS gist_branch_path ON komp.branch USING GIST (path); - --- Centroids per dimension (store only the dim matching the space) -CREATE TABLE IF NOT EXISTS komp.branch_centroid_768 ( - branch_id BIGINT PRIMARY KEY REFERENCES komp.branch(id) ON DELETE CASCADE, - embedding VECTOR(768) NOT NULL, - updated_at TIMESTAMPTZ DEFAULT now() -); -CREATE TABLE IF NOT EXISTS komp.branch_centroid_1024 ( - branch_id BIGINT PRIMARY KEY REFERENCES komp.branch(id) ON DELETE CASCADE, - embedding VECTOR(1024) NOT NULL, - updated_at TIMESTAMPTZ DEFAULT now() -); - --- Soft membership of chunks to branches -CREATE TABLE IF NOT EXISTS komp.chunk_branch ( - chunk_id BIGINT REFERENCES komp.chunk(id) ON DELETE CASCADE, - branch_id BIGINT REFERENCES komp.branch(id) ON DELETE CASCADE, - weight REAL NOT NULL CHECK (weight >= 0 AND weight <= 1), - PRIMARY KEY(chunk_id, branch_id) -); - --- Relations between chunks (similarity / cites / derives / contradicts / …) -CREATE TABLE IF NOT EXISTS komp.chunk_edge ( - src_chunk_id BIGINT REFERENCES komp.chunk(id) ON DELETE CASCADE, - dst_chunk_id BIGINT REFERENCES komp.chunk(id) ON DELETE CASCADE, - relation TEXT NOT NULL, - weight REAL, - meta JSONB DEFAULT '{}'::jsonb, - PRIMARY KEY(src_chunk_id, dst_chunk_id, relation) -); - -CREATE OR REPLACE VIEW komp.latest_sources AS - SELECT s.*, max(c.created_at) AS last_chunk_at - FROM komp.source s LEFT JOIN komp.chunk c ON c.source_id = s.id - GROUP BY s.id; - diff --git a/db/schema_pgvector.sql b/db/schema_pgvector.sql deleted file mode 100644 index 5b1be28..0000000 --- a/db/schema_pgvector.sql +++ /dev/null @@ -1,5 +0,0 @@ --- pgvector knowledge schema (v0.2, branch embeddings)nCREATE EXTENSION IF NOT EXISTS vector;nn-- Documents tracked by content hash; multiple logical paths may point to same blobnCREATE TABLE IF NOT EXISTS doc_blob (n blob_sha256 text PRIMARY KEY,n bytes bigint NOT NULL,n created_at timestamptz DEFAULT now()n);nn-- Logical document identity and branch/commit lineagenCREATE TABLE IF NOT EXISTS doc (n id bigserial PRIMARY KEY,n origin text, -- e.g., filesystem, forgejo, exportn root_path text, -- repo root or base dirn rel_path text, -- path relative to rootn blob_sha256 text REFERENCES doc_blob(blob_sha256)n);nCREATE TABLE IF NOT EXISTS branch (n id bigserial PRIMARY KEY,n name text UNIQUE,n created_at timestamptz DEFAULT now()n);nCREATE TABLE IF NOT EXISTS revision (n id bigserial PRIMARY KEY,n branch_id bigint REFERENCES branch(id) ON DELETE CASCADE,n parent_id bigint REFERENCES revision(id),n commit_sha text, -- optional link to VCSn message text,n created_at timestamptz DEFAULT now()n);nn-- Chunked text with provenance; each revision can re-chunk same docnCREATE TABLE IF NOT EXISTS chunk (n id bigserial PRIMARY KEY,n revision_id bigint REFERENCES revision(id) ON DELETE CASCADE,n doc_id bigint REFERENCES doc(id) ON DELETE CASCADE,n lineno int,n text text NOT NULL,n tags text[],n sha256 text,n created_at timestamptz DEFAULT now()n);nCREATE INDEX IF NOT EXISTS idx_chunk_doc ON chunk(doc_id);nCREATE INDEX IF NOT EXISTS idx_chunk_rev ON chunk(revision_id);nn-- Embedding models registrynCREATE TABLE IF NOT EXISTS embed_model (n id bigserial PRIMARY KEY,n name text UNIQUE NOT NULL, -- e.g., mxbai-embed-largen dims int NOT NULL,n tokenizer text,n notes textn);nn-- Branch embeddings: per revision, per chunk, per model (dims parametrized by table)nCREATE TABLE IF NOT EXISTS embedding_1024 (n id bigserial PRIMARY KEY,n chunk_id bigint REFERENCES chunk(id) ON DELETE CASCADE,n model_id bigint REFERENCES embed_model(id),n embedding vector(1024) NOT NULL,n created_at timestamptz DEFAULT now()n);nCREATE INDEX IF NOT EXISTS idx_emb1024_chunk ON embedding_1024(chunk_id);nCREATE INDEX IF NOT EXISTS idx_emb1024_vec ON embedding_1024 USING ivfflat (embedding vector_l2_ops) WITH (lists=100);nn-- Views: latest chunks per branchnCREATE OR REPLACE VIEW v_latest_chunks AS -SELECT c.* FROM chunk c -JOIN (SELECT revision_id, max(id) over (partition by doc_id, branch_id) as maxid, doc_id - FROM revision r JOIN chunk c2 ON c2.revision_id=r.id) t - ON c.id=t.maxid;nn-- Ledger mirror (optional)nCREATE TABLE IF NOT EXISTS ledger_mirror (n id bigserial PRIMARY KEY,n ts timestamptz NOT NULL,n actor text,n action text,n body jsonbn);n \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index 7ac4f0f..275da19 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,6 +9,19 @@ Kompanion adheres to KDE’s KConfig conventions so deployments are kioskable an The CLI (`kompanion`) and MCP runner (`kom_mcp`) fall back to this entry when the `PG_DSN` environment variable is not set. If neither are present the in-memory DAL stub is used. +## Initialization Wizard +- Run `kompanion --init` to launch an interactive wizard. + - Autodetects reachable Postgres instances (tries `postgresql://kompanion:komup@localhost/kompanion_test`). + - Prompts for host, port, database, user, password, or Unix socket path with sensible defaults. + - Writes the resulting DSN to `kompanionrc` and exports `PG_DSN` for the current session. + - If the target database is empty, it applies the SQL migrations shipped under `share/kompanion/db/init/*.sql`. +- The wizard is also triggered automatically the first time you run `kompanion` without a configured DSN. + +## CLI Modes +- Standard invocation: `kompanion --request payload.json` +- Interactive prompt: `kompanion -I ` keeps a REPL open; enter JSON payloads or `!prompt text` to wrap plain text. Use `-V/--verbose` to echo request/response JSON streams. +- Use `kompanion --list` to enumerate available tools, including `kom.meta.v1.project_snapshot` for quick project context dumps. + ## Future HTTP Streaming While current tooling focuses on stdio dispatch (for editor and agent integration), the roadmap includes an HTTP/2 or WebSocket streaming surface so MCP clients can maintain persistent conversations without leaving CLI compatibility behind. The same configuration keys will apply for both transports. diff --git a/docs/memory-architecture.md b/docs/memory-architecture.md index 399897c..028e326 100644 --- a/docs/memory-architecture.md +++ b/docs/memory-architecture.md @@ -73,6 +73,7 @@ - **Observability**: CLI should read the `jobs/semantic_sync` state block to display cursors, pending counts, and last error logs; dry-run mode estimates embeddings without committing. - **Activation parity**: Long term, mirror the KDE `akonadiclient`/`akonadi-console` pattern—Kompanion CLI doubles as an MCP surface today and later as a DBus-activated helper so tools can be socket-triggered into the memory service. - **KConfig defaults**: `kom_mcp` and `kompanion` load `Database/PgDsn` from `~/.config/kompanionrc` (see `docs/configuration.md`) when `PG_DSN` is unset, keeping deployments kioskable. +- **CLI UX**: `kompanion --init` guides first-run setup (auto-detects databases, applies schemas); `-I/--interactive` keeps a JSON REPL open, and `-V/--verbose` echoes request/response streams for future HTTP transport parity. ## Next-Step Checklist - [x] Detect pqxx via CMake and plumb `HAVE_PG`.