mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-06 23:13:50 +00:00
general improvements (#17)
* refactor(hooks): simplify hook execution with shared context - [x] Extract BaseInput struct to eliminate duplicate fields across hooks - [x] Create RunHook handler pattern for session-start and user-prompt - [x] Create RunStatuslineHook for fast statusline rendering without worker startup - [x] Add HookContext struct to pass port, project, CWD, SessionID to handlers - [x] Add db/interface.go with ObservationReader/Writer interfaces - [x] Add comprehensive conflict management tests in sqlite/conflict_test.go - [x] Add vector client tests for Count, ModelVersion, NeedsRebuild, GetStaleVectors - [x] Add FilterByThreshold helper tests for query result filtering - [x] Make handlers_test more robust for network-dependent update checks - [x] Update package versions in UI * Move to GORM + general cleanup * feat(mcp): add observation relations discovery and scoring integration - [x] Add find_related_observations MCP tool for discovering related observations by confidence - [x] Integrate scoring calculator and recalculator into MCP server initialization - [x] Add pattern, relation, and session stores to MCP server dependencies - [x] Register MCP server in Claude Code settings during plugin installation - [x] Update install scripts (bash, PowerShell) to configure MCP server settings - [x] Switch plugin manifest files to template-based versioning (plugin.json.tpl, marketplace.json.tpl) - [x] Update all MCP server tests to pass new dependency parameters
This commit is contained in:
@@ -501,3 +501,254 @@ func TestClient_DeleteDocuments_NonExistent(t *testing.T) {
|
||||
err = client.DeleteDocuments(context.Background(), []string{"non-existent-id"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestClient_Count_Empty(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := client.Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestClient_Count_WithVectors(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add some documents
|
||||
docs := []Document{
|
||||
{ID: "doc-1", Content: "test content 1"},
|
||||
{ID: "doc-2", Content: "test content 2"},
|
||||
{ID: "doc-3", Content: "test content 3"},
|
||||
}
|
||||
err = client.AddDocuments(context.Background(), docs)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := client.Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
}
|
||||
|
||||
func TestClient_ModelVersion(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
version := client.ModelVersion()
|
||||
assert.NotEmpty(t, version)
|
||||
// Should match the embedding service version
|
||||
assert.Equal(t, embedSvc.Version(), version)
|
||||
}
|
||||
|
||||
func TestClient_NeedsRebuild_EmptyDatabase(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
needsRebuild, reason := client.NeedsRebuild(context.Background())
|
||||
assert.True(t, needsRebuild)
|
||||
assert.Equal(t, "empty", reason)
|
||||
}
|
||||
|
||||
func TestClient_NeedsRebuild_ModelMismatch(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert vectors with wrong model version
|
||||
embedding := make([]float32, 384)
|
||||
for i := range embedding {
|
||||
embedding[i] = 0.1
|
||||
}
|
||||
embeddingBytes, err := sqlite_vec.SerializeFloat32(embedding)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO vectors (doc_id, embedding, model_version, sqlite_id, doc_type, field_type, project, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, "doc-1", embeddingBytes, "old-model-v1", 1, "observation", "content", "test", "project")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO vectors (doc_id, embedding, model_version, sqlite_id, doc_type, field_type, project, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, "doc-2", embeddingBytes, "old-model-v1", 2, "observation", "content", "test", "project")
|
||||
require.NoError(t, err)
|
||||
|
||||
needsRebuild, reason := client.NeedsRebuild(context.Background())
|
||||
assert.True(t, needsRebuild)
|
||||
assert.Contains(t, reason, "model_mismatch:2")
|
||||
}
|
||||
|
||||
func TestClient_NeedsRebuild_CurrentModel(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add documents with current model version
|
||||
docs := []Document{
|
||||
{ID: "doc-1", Content: "test content 1"},
|
||||
{ID: "doc-2", Content: "test content 2"},
|
||||
}
|
||||
err = client.AddDocuments(context.Background(), docs)
|
||||
require.NoError(t, err)
|
||||
|
||||
needsRebuild, reason := client.NeedsRebuild(context.Background())
|
||||
assert.False(t, needsRebuild)
|
||||
assert.Empty(t, reason)
|
||||
}
|
||||
|
||||
func TestClient_GetStaleVectors_Empty(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
stale, err := client.GetStaleVectors(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, stale)
|
||||
}
|
||||
|
||||
func TestClient_GetStaleVectors_WithMismatch(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert vectors with wrong model version
|
||||
embedding := make([]float32, 384)
|
||||
embeddingBytes, err := sqlite_vec.SerializeFloat32(embedding)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO vectors (doc_id, embedding, model_version, sqlite_id, doc_type, field_type, project, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, "doc-1", embeddingBytes, "old-model", 1, "observation", "content", "project-1", "project")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO vectors (doc_id, embedding, model_version, sqlite_id, doc_type, field_type, project, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, "doc-2", embeddingBytes, embedSvc.Version(), 2, "observation", "title", "project-1", "project")
|
||||
require.NoError(t, err)
|
||||
|
||||
stale, err := client.GetStaleVectors(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, stale, 1)
|
||||
assert.Equal(t, "doc-1", stale[0].DocID)
|
||||
assert.Equal(t, int64(1), stale[0].SQLiteID)
|
||||
assert.Equal(t, "observation", stale[0].DocType)
|
||||
assert.Equal(t, "content", stale[0].FieldType)
|
||||
assert.Equal(t, "project-1", stale[0].Project)
|
||||
assert.Equal(t, "project", stale[0].Scope)
|
||||
}
|
||||
|
||||
func TestClient_DeleteVectorsByDocIDs_Empty(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Deleting empty slice should not error
|
||||
err = client.DeleteVectorsByDocIDs(context.Background(), []string{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestClient_DeleteVectorsByDocIDs_Success(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add documents
|
||||
docs := []Document{
|
||||
{ID: "doc-1", Content: "test 1"},
|
||||
{ID: "doc-2", Content: "test 2"},
|
||||
{ID: "doc-3", Content: "test 3"},
|
||||
}
|
||||
err = client.AddDocuments(context.Background(), docs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify 3 documents exist
|
||||
count, err := client.Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
|
||||
// Delete doc-1 and doc-3
|
||||
err = client.DeleteVectorsByDocIDs(context.Background(), []string{"doc-1", "doc-3"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have 1 document remaining
|
||||
count, err = client.Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
|
||||
// Verify doc-2 still exists
|
||||
var exists int
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM vectors WHERE doc_id = ?", "doc-2").Scan(&exists)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, exists)
|
||||
}
|
||||
|
||||
func TestClient_DeleteVectorsByDocIDs_NonExistent(t *testing.T) {
|
||||
db, dbCleanup := testDB(t)
|
||||
defer dbCleanup()
|
||||
|
||||
embedSvc, embedCleanup := testEmbeddingService(t)
|
||||
defer embedCleanup()
|
||||
|
||||
client, err := NewClient(Config{DB: db}, embedSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Deleting non-existent IDs should not error
|
||||
err = client.DeleteVectorsByDocIDs(context.Background(), []string{"non-existent-1", "non-existent-2"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -572,3 +572,119 @@ func TestExtractedIDs_Empty(t *testing.T) {
|
||||
assert.Nil(t, ids.SummaryIDs)
|
||||
assert.Nil(t, ids.PromptIDs)
|
||||
}
|
||||
|
||||
func TestFilterByThreshold(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
results []QueryResult
|
||||
threshold float64
|
||||
maxResults int
|
||||
expectedLen int
|
||||
expectedIDs []string
|
||||
}{
|
||||
{
|
||||
name: "empty_results",
|
||||
results: []QueryResult{},
|
||||
threshold: 0.5,
|
||||
maxResults: 0,
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "all_above_threshold",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.9},
|
||||
{ID: "doc-2", Similarity: 0.8},
|
||||
{ID: "doc-3", Similarity: 0.7},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 0,
|
||||
expectedLen: 3,
|
||||
expectedIDs: []string{"doc-1", "doc-2", "doc-3"},
|
||||
},
|
||||
{
|
||||
name: "some_below_threshold",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.9},
|
||||
{ID: "doc-2", Similarity: 0.4},
|
||||
{ID: "doc-3", Similarity: 0.7},
|
||||
{ID: "doc-4", Similarity: 0.3},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 0,
|
||||
expectedLen: 2,
|
||||
expectedIDs: []string{"doc-1", "doc-3"},
|
||||
},
|
||||
{
|
||||
name: "all_below_threshold",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.3},
|
||||
{ID: "doc-2", Similarity: 0.2},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 0,
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "max_results_limit",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.9},
|
||||
{ID: "doc-2", Similarity: 0.8},
|
||||
{ID: "doc-3", Similarity: 0.7},
|
||||
{ID: "doc-4", Similarity: 0.6},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 2,
|
||||
expectedLen: 2,
|
||||
expectedIDs: []string{"doc-1", "doc-2"},
|
||||
},
|
||||
{
|
||||
name: "max_results_with_threshold",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.9},
|
||||
{ID: "doc-2", Similarity: 0.3},
|
||||
{ID: "doc-3", Similarity: 0.8},
|
||||
{ID: "doc-4", Similarity: 0.2},
|
||||
{ID: "doc-5", Similarity: 0.7},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 2,
|
||||
expectedLen: 2,
|
||||
expectedIDs: []string{"doc-1", "doc-3"},
|
||||
},
|
||||
{
|
||||
name: "exact_threshold_included",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.5},
|
||||
{ID: "doc-2", Similarity: 0.4},
|
||||
},
|
||||
threshold: 0.5,
|
||||
maxResults: 0,
|
||||
expectedLen: 1,
|
||||
expectedIDs: []string{"doc-1"},
|
||||
},
|
||||
{
|
||||
name: "zero_threshold",
|
||||
results: []QueryResult{
|
||||
{ID: "doc-1", Similarity: 0.1},
|
||||
{ID: "doc-2", Similarity: 0.0},
|
||||
},
|
||||
threshold: 0.0,
|
||||
maxResults: 0,
|
||||
expectedLen: 2,
|
||||
expectedIDs: []string{"doc-1", "doc-2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filtered := FilterByThreshold(tt.results, tt.threshold, tt.maxResults)
|
||||
assert.Len(t, filtered, tt.expectedLen)
|
||||
|
||||
if tt.expectedLen > 0 {
|
||||
for i, id := range tt.expectedIDs {
|
||||
assert.Equal(t, id, filtered[i].ID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user