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:
2026-01-07 00:26:20 +00:00
committed by GitHub
parent 92a99c7615
commit 7a061c85eb
85 changed files with 8445 additions and 8202 deletions
+251
View File
@@ -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)
}
+116
View File
@@ -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)
}
}
})
}
}