mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-09 23:59:40 +00:00
7a061c85eb
* 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
332 lines
10 KiB
Go
332 lines
10 KiB
Go
//go:build fts5
|
|
|
|
// Package gorm provides GORM-based database operations for claude-mnemonic.
|
|
package gorm
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gorm.io/gorm/logger"
|
|
|
|
"github.com/lukaszraczylo/claude-mnemonic/pkg/models"
|
|
)
|
|
|
|
// setupBenchStore creates a temporary store for benchmarking.
|
|
func setupBenchStore(b *testing.B) (*Store, func()) {
|
|
b.Helper()
|
|
|
|
tmpDir, err := os.MkdirTemp("", "gorm_bench_*")
|
|
if err != nil {
|
|
b.Fatalf("create temp dir: %v", err)
|
|
}
|
|
|
|
dbPath := filepath.Join(tmpDir, "bench.db")
|
|
cfg := Config{
|
|
Path: dbPath,
|
|
MaxConns: 4,
|
|
LogLevel: logger.Silent,
|
|
}
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
os.RemoveAll(tmpDir)
|
|
b.Fatalf("NewStore failed: %v", err)
|
|
}
|
|
|
|
cleanup := func() {
|
|
store.Close()
|
|
os.RemoveAll(tmpDir)
|
|
}
|
|
|
|
return store, cleanup
|
|
}
|
|
|
|
// BenchmarkSessionStore_CreateSDKSession benchmarks session creation (most frequent operation).
|
|
func BenchmarkSessionStore_CreateSDKSession(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
sessionID := fmt.Sprintf("claude-bench-%d", i)
|
|
_, err := sessionStore.CreateSDKSession(ctx, sessionID, "bench-project", "test prompt")
|
|
if err != nil {
|
|
b.Fatalf("CreateSDKSession failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkSessionStore_CreateSDKSession_Idempotent benchmarks idempotent session creation (INSERT OR IGNORE).
|
|
func BenchmarkSessionStore_CreateSDKSession_Idempotent(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
ctx := context.Background()
|
|
|
|
// Pre-create session
|
|
sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "test prompt")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "updated prompt")
|
|
if err != nil {
|
|
b.Fatalf("CreateSDKSession failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkObservationStore_StoreObservation benchmarks observation storage (high frequency).
|
|
func BenchmarkObservationStore_StoreObservation(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
obs := &models.ParsedObservation{
|
|
Type: models.ObsTypeDiscovery,
|
|
Title: fmt.Sprintf("Observation %d", i),
|
|
Narrative: "Benchmark observation content",
|
|
}
|
|
_, _, err := obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs, int(sessionID), int64(i+1))
|
|
if err != nil {
|
|
b.Fatalf("StoreObservation failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkObservationStore_GetRecentObservations benchmarks recent observation retrieval.
|
|
func BenchmarkObservationStore_GetRecentObservations(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session and observations
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
for i := 0; i < 100; i++ {
|
|
obs := &models.ParsedObservation{
|
|
Type: models.ObsTypeDiscovery,
|
|
Title: fmt.Sprintf("Observation %d", i),
|
|
Narrative: "Benchmark observation content",
|
|
}
|
|
obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs, int(sessionID), int64(i+1))
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := obsStore.GetRecentObservations(ctx, "bench-project", 20)
|
|
if err != nil {
|
|
b.Fatalf("GetRecentObservations failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkObservationStore_SearchObservationsFTS benchmarks FTS5 search (latency-sensitive).
|
|
func BenchmarkObservationStore_SearchObservationsFTS(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session and observations with searchable content
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
for i := 0; i < 100; i++ {
|
|
obs := &models.ParsedObservation{
|
|
Type: models.ObsTypeDiscovery,
|
|
Title: fmt.Sprintf("Security best practice %d", i),
|
|
Narrative: "This observation discusses security patterns and authentication mechanisms",
|
|
}
|
|
obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs, int(sessionID), int64(i+1))
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := obsStore.SearchObservationsFTS(ctx, "security authentication", "bench-project", 10)
|
|
if err != nil {
|
|
b.Fatalf("SearchObservationsFTS failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkObservationStore_UpdateImportanceScore benchmarks scoring updates.
|
|
func BenchmarkObservationStore_UpdateImportanceScore(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session and observation
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
obs := &models.ParsedObservation{Type: models.ObsTypeDiscovery, Title: "Test"}
|
|
obsID, _, _ := obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs, int(sessionID), 1)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
score := float64(i%10) + 1.0
|
|
err := obsStore.UpdateImportanceScore(ctx, obsID, score)
|
|
if err != nil {
|
|
b.Fatalf("UpdateImportanceScore failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkObservationStore_UpdateImportanceScores_Bulk benchmarks bulk scoring updates.
|
|
func BenchmarkObservationStore_UpdateImportanceScores_Bulk(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session and 100 observations
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
var obsIDs []int64
|
|
for i := 0; i < 100; i++ {
|
|
obs := &models.ParsedObservation{Type: models.ObsTypeDiscovery, Title: fmt.Sprintf("Obs %d", i)}
|
|
obsID, _, _ := obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs, int(sessionID), int64(i+1))
|
|
obsIDs = append(obsIDs, obsID)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
scores := make(map[int64]float64)
|
|
for _, id := range obsIDs {
|
|
scores[id] = float64(i%10) + 1.0
|
|
}
|
|
err := obsStore.UpdateImportanceScores(ctx, scores)
|
|
if err != nil {
|
|
b.Fatalf("UpdateImportanceScores failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkPromptStore_SaveUserPromptWithMatches benchmarks prompt storage with matches.
|
|
func BenchmarkPromptStore_SaveUserPromptWithMatches(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
promptStore := NewPromptStore(store, nil)
|
|
ctx := context.Background()
|
|
|
|
// Create session
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := promptStore.SaveUserPromptWithMatches(ctx, "claude-bench", int(sessionID), fmt.Sprintf("Prompt %d", i), i+1)
|
|
if err != nil {
|
|
b.Fatalf("SaveUserPromptWithMatches failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkSummaryStore_StoreSummary benchmarks summary storage.
|
|
func BenchmarkSummaryStore_StoreSummary(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
summaryStore := NewSummaryStore(store)
|
|
ctx := context.Background()
|
|
|
|
// Create session
|
|
sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
summary := &models.ParsedSummary{
|
|
Request: fmt.Sprintf("Request %d", i),
|
|
Investigated: "Investigation details",
|
|
Learned: "Learning summary",
|
|
Completed: "Completion status",
|
|
}
|
|
_, _, err := summaryStore.StoreSummary(ctx, "claude-bench", "bench-project", summary, i+1, 100)
|
|
if err != nil {
|
|
b.Fatalf("StoreSummary failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkRelationStore_StoreRelation benchmarks relation storage.
|
|
func BenchmarkRelationStore_StoreRelation(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
sessionStore := NewSessionStore(store)
|
|
obsStore := NewObservationStore(store, nil, nil, nil)
|
|
relationStore := NewRelationStore(store)
|
|
ctx := context.Background()
|
|
|
|
// Create session and observations
|
|
sessionID, _ := sessionStore.CreateSDKSession(ctx, "claude-bench", "bench-project", "")
|
|
obs1 := &models.ParsedObservation{Type: models.ObsTypeDiscovery, Title: "Source"}
|
|
obsID1, _, _ := obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs1, int(sessionID), 1)
|
|
obs2 := &models.ParsedObservation{Type: models.ObsTypeDiscovery, Title: "Target"}
|
|
obsID2, _, _ := obsStore.StoreObservation(ctx, "claude-bench", "bench-project", obs2, int(sessionID), 2)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
relation := &models.ObservationRelation{
|
|
SourceID: obsID1,
|
|
TargetID: obsID2,
|
|
RelationType: models.RelationCauses,
|
|
Confidence: 0.9,
|
|
DetectionSource: models.DetectionSourceFileOverlap,
|
|
}
|
|
_, err := relationStore.StoreRelation(ctx, relation)
|
|
if err != nil {
|
|
b.Fatalf("StoreRelation failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkPatternStore_StorePattern benchmarks pattern storage.
|
|
func BenchmarkPatternStore_StorePattern(b *testing.B) {
|
|
store, cleanup := setupBenchStore(b)
|
|
defer cleanup()
|
|
|
|
patternStore := NewPatternStore(store)
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
pattern := &models.Pattern{
|
|
Name: fmt.Sprintf("Pattern %d", i),
|
|
Type: models.PatternTypeBug,
|
|
Description: sql.NullString{String: "Benchmark pattern", Valid: true},
|
|
Frequency: 1,
|
|
Confidence: 0.8,
|
|
Projects: []string{"bench-project"},
|
|
Status: models.PatternStatusActive,
|
|
}
|
|
_, err := patternStore.StorePattern(ctx, pattern)
|
|
if err != nil {
|
|
b.Fatalf("StorePattern failed: %v", err)
|
|
}
|
|
}
|
|
}
|