mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-13 02:06:24 +00:00
feat(leann-phase2): implement hybrid vector storage and graph-based search
- [x] Add AST-aware code chunking for Go, Python, and TypeScript using tree-sitter - [x] Implement LEANN-inspired hybrid vector storage with hub detection and selective embedding storage (60-80% savings) - [x] Add observation relationship graph with CSR format and edge detection (file overlap, semantic similarity, temporal, concept) - [x] Implement graph-aware search with two-level traversal and relationship-based ranking - [x] Add auto-tuning system for dynamic hub threshold adjustment based on query performance - [x] Add comprehensive metrics tracking for vector storage, queries, latency, and graph traversals - [x] Update configuration system with graph and hybrid storage settings - [x] Add graph stats and vector metrics endpoints to worker service - [x] Enhance UI sidebar with advanced metrics display and graph visualization - [x] Optimize struct field alignment throughout codebase for memory efficiency - [x] Update documentation with LEANN Phase 2 features and performance benefits - [x] Add tree-sitter dependency for AST parsing
This commit is contained in:
@@ -33,25 +33,25 @@ const (
|
||||
|
||||
// ObservationConflict tracks conflicting observations.
|
||||
type ObservationConflict struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
NewerObsID int64 `db:"newer_obs_id" json:"newer_obs_id"`
|
||||
OlderObsID int64 `db:"older_obs_id" json:"older_obs_id"`
|
||||
ResolvedAt *string `db:"resolved_at" json:"resolved_at,omitempty"`
|
||||
ConflictType ConflictType `db:"conflict_type" json:"conflict_type"`
|
||||
Resolution ConflictResolution `db:"resolution" json:"resolution"`
|
||||
Reason string `db:"reason" json:"reason"`
|
||||
DetectedAt string `db:"detected_at" json:"detected_at"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
NewerObsID int64 `db:"newer_obs_id" json:"newer_obs_id"`
|
||||
OlderObsID int64 `db:"older_obs_id" json:"older_obs_id"`
|
||||
DetectedAtEpoch int64 `db:"detected_at_epoch" json:"detected_at_epoch"`
|
||||
Resolved bool `db:"resolved" json:"resolved"`
|
||||
ResolvedAt *string `db:"resolved_at" json:"resolved_at,omitempty"`
|
||||
}
|
||||
|
||||
// ConflictDetectionResult contains the result of conflict detection.
|
||||
type ConflictDetectionResult struct {
|
||||
HasConflict bool
|
||||
Type ConflictType
|
||||
Resolution ConflictResolution
|
||||
Reason string
|
||||
OlderObsIDs []int64 // IDs of observations that conflict with the new one
|
||||
OlderObsIDs []int64
|
||||
HasConflict bool
|
||||
}
|
||||
|
||||
// NewObservationConflict creates a new conflict record.
|
||||
|
||||
@@ -51,8 +51,8 @@ func (s *ConflictSuite) TestDetectExplicitCorrection_TableDriven() {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
expectMatch bool
|
||||
expectPattern string
|
||||
expectMatch bool
|
||||
}{
|
||||
{
|
||||
name: "actually that was wrong",
|
||||
@@ -128,9 +128,9 @@ func (s *ConflictSuite) TestDetectExplicitCorrection_TableDriven() {
|
||||
// TestDetectOpposingFileChanges_TableDriven tests opposing file change detection.
|
||||
func (s *ConflictSuite) TestDetectOpposingFileChanges_TableDriven() {
|
||||
tests := []struct {
|
||||
name string
|
||||
newerObs *Observation
|
||||
olderObs *Observation
|
||||
name string
|
||||
expectConflict bool
|
||||
}{
|
||||
{
|
||||
@@ -202,9 +202,9 @@ func (s *ConflictSuite) TestDetectOpposingFileChanges_TableDriven() {
|
||||
// TestDetectConceptTagMismatch_TableDriven tests concept tag mismatch detection.
|
||||
func (s *ConflictSuite) TestDetectConceptTagMismatch_TableDriven() {
|
||||
tests := []struct {
|
||||
name string
|
||||
newerObs *Observation
|
||||
olderObs *Observation
|
||||
name string
|
||||
expectConflict bool
|
||||
}{
|
||||
{
|
||||
|
||||
+28
-36
@@ -121,48 +121,44 @@ func (j JSONInt64Map) Value() (driver.Value, error) {
|
||||
|
||||
// Observation represents a learning extracted from a Claude Code session.
|
||||
type Observation struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
FileMtimes JSONInt64Map `db:"file_mtimes" json:"file_mtimes,omitempty"`
|
||||
SDKSessionID string `db:"sdk_session_id" json:"sdk_session_id"`
|
||||
Project string `db:"project" json:"project"`
|
||||
Scope ObservationScope `db:"scope" json:"scope"`
|
||||
Type ObservationType `db:"type" json:"type"`
|
||||
Title sql.NullString `db:"title" json:"title,omitempty"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
Subtitle sql.NullString `db:"subtitle" json:"subtitle,omitempty"`
|
||||
Facts JSONStringArray `db:"facts" json:"facts,omitempty"`
|
||||
Title sql.NullString `db:"title" json:"title,omitempty"`
|
||||
Narrative sql.NullString `db:"narrative" json:"narrative,omitempty"`
|
||||
Concepts JSONStringArray `db:"concepts" json:"concepts,omitempty"`
|
||||
FilesRead JSONStringArray `db:"files_read" json:"files_read,omitempty"`
|
||||
FilesModified JSONStringArray `db:"files_modified" json:"files_modified,omitempty"`
|
||||
FileMtimes JSONInt64Map `db:"file_mtimes" json:"file_mtimes,omitempty"`
|
||||
Facts JSONStringArray `db:"facts" json:"facts,omitempty"`
|
||||
PromptNumber sql.NullInt64 `db:"prompt_number" json:"prompt_number,omitempty"`
|
||||
LastRetrievedAt sql.NullInt64 `db:"last_retrieved_at_epoch" json:"last_retrieved_at_epoch,omitempty"`
|
||||
ScoreUpdatedAt sql.NullInt64 `db:"score_updated_at_epoch" json:"score_updated_at_epoch,omitempty"`
|
||||
DiscoveryTokens int64 `db:"discovery_tokens" json:"discovery_tokens"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
CreatedAtEpoch int64 `db:"created_at_epoch" json:"created_at_epoch"`
|
||||
ImportanceScore float64 `db:"importance_score" json:"importance_score"`
|
||||
UserFeedback int `db:"user_feedback" json:"user_feedback"`
|
||||
RetrievalCount int `db:"retrieval_count" json:"retrieval_count"`
|
||||
IsStale bool `db:"-" json:"is_stale,omitempty"`
|
||||
|
||||
// Importance scoring fields
|
||||
ImportanceScore float64 `db:"importance_score" json:"importance_score"`
|
||||
UserFeedback int `db:"user_feedback" json:"user_feedback"`
|
||||
RetrievalCount int `db:"retrieval_count" json:"retrieval_count"`
|
||||
LastRetrievedAt sql.NullInt64 `db:"last_retrieved_at_epoch" json:"last_retrieved_at_epoch,omitempty"`
|
||||
ScoreUpdatedAt sql.NullInt64 `db:"score_updated_at_epoch" json:"score_updated_at_epoch,omitempty"`
|
||||
|
||||
// Conflict detection fields
|
||||
IsSuperseded bool `db:"is_superseded" json:"is_superseded,omitempty"`
|
||||
IsSuperseded bool `db:"is_superseded" json:"is_superseded,omitempty"`
|
||||
}
|
||||
|
||||
// ParsedObservation represents an observation parsed from SDK response XML.
|
||||
type ParsedObservation struct {
|
||||
FileMtimes map[string]int64
|
||||
Type ObservationType
|
||||
Title string
|
||||
Subtitle string
|
||||
Facts []string
|
||||
Narrative string
|
||||
Scope ObservationScope
|
||||
Facts []string
|
||||
Concepts []string
|
||||
FilesRead []string
|
||||
FilesModified []string
|
||||
FileMtimes map[string]int64 // File path -> mtime epoch ms
|
||||
Scope ObservationScope // Optional: if empty, will be auto-determined
|
||||
}
|
||||
|
||||
// ToStoredObservation converts a ParsedObservation to the stored Observation format.
|
||||
@@ -197,34 +193,30 @@ func DetermineScope(concepts []string) ObservationScope {
|
||||
// ObservationJSON is a JSON-friendly representation of Observation.
|
||||
// It converts sql.NullString to plain strings for clean JSON output.
|
||||
type ObservationJSON struct {
|
||||
ID int64 `json:"id"`
|
||||
FileMtimes map[string]int64 `json:"file_mtimes,omitempty"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
SDKSessionID string `json:"sdk_session_id"`
|
||||
Project string `json:"project"`
|
||||
Scope ObservationScope `json:"scope"`
|
||||
Type ObservationType `json:"type"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
Facts []string `json:"facts,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Narrative string `json:"narrative,omitempty"`
|
||||
Project string `json:"project"`
|
||||
Concepts []string `json:"concepts,omitempty"`
|
||||
Facts []string `json:"facts,omitempty"`
|
||||
FilesRead []string `json:"files_read,omitempty"`
|
||||
FilesModified []string `json:"files_modified,omitempty"`
|
||||
FileMtimes map[string]int64 `json:"file_mtimes,omitempty"`
|
||||
PromptNumber int64 `json:"prompt_number,omitempty"`
|
||||
DiscoveryTokens int64 `json:"discovery_tokens"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
CreatedAtEpoch int64 `json:"created_at_epoch"`
|
||||
DiscoveryTokens int64 `json:"discovery_tokens"`
|
||||
ID int64 `json:"id"`
|
||||
PromptNumber int64 `json:"prompt_number,omitempty"`
|
||||
ImportanceScore float64 `json:"importance_score"`
|
||||
UserFeedback int `json:"user_feedback"`
|
||||
RetrievalCount int `json:"retrieval_count"`
|
||||
LastRetrievedAt int64 `json:"last_retrieved_at_epoch,omitempty"`
|
||||
ScoreUpdatedAt int64 `json:"score_updated_at_epoch,omitempty"`
|
||||
IsStale bool `json:"is_stale,omitempty"`
|
||||
|
||||
// Importance scoring fields
|
||||
ImportanceScore float64 `json:"importance_score"`
|
||||
UserFeedback int `json:"user_feedback"`
|
||||
RetrievalCount int `json:"retrieval_count"`
|
||||
LastRetrievedAt int64 `json:"last_retrieved_at_epoch,omitempty"`
|
||||
ScoreUpdatedAt int64 `json:"score_updated_at_epoch,omitempty"`
|
||||
|
||||
// Conflict detection fields
|
||||
IsSuperseded bool `json:"is_superseded,omitempty"`
|
||||
IsSuperseded bool `json:"is_superseded,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler for Observation.
|
||||
|
||||
@@ -50,8 +50,8 @@ func (s *ObservationSuite) TestGlobalizableConcepts() {
|
||||
func (s *ObservationSuite) TestDetermineScope_TableDriven() {
|
||||
tests := []struct {
|
||||
name string
|
||||
concepts []string
|
||||
expected ObservationScope
|
||||
concepts []string
|
||||
}{
|
||||
{
|
||||
name: "empty concepts - project scope",
|
||||
@@ -121,9 +121,9 @@ func (s *ObservationSuite) TestParsedObservation_FileMtimesJSON() {
|
||||
// TestObservation_CheckStaleness_TableDriven tests staleness checking.
|
||||
func (s *ObservationSuite) TestObservation_CheckStaleness_TableDriven() {
|
||||
tests := []struct {
|
||||
name string
|
||||
storedMtimes map[string]int64
|
||||
currentMtimes map[string]int64
|
||||
name string
|
||||
expectedStale bool
|
||||
}{
|
||||
{
|
||||
@@ -300,10 +300,10 @@ func TestParsedObservation_ToStoredObservation(t *testing.T) {
|
||||
// TestJSONStringArray tests JSONStringArray scanning.
|
||||
func TestJSONStringArray(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantErr bool
|
||||
name string
|
||||
expected JSONStringArray
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil input",
|
||||
@@ -348,10 +348,10 @@ func TestJSONStringArray(t *testing.T) {
|
||||
// TestJSONInt64Map tests JSONInt64Map scanning.
|
||||
func TestJSONInt64Map(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantErr bool
|
||||
expected JSONInt64Map
|
||||
name string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil input",
|
||||
|
||||
+25
-25
@@ -39,21 +39,21 @@ const (
|
||||
// Pattern represents a recurring pattern detected across observations.
|
||||
// This enables Claude to reference historical insights: "I've encountered this pattern 12 times."
|
||||
type Pattern struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"` // e.g., "State Management Anti-Pattern"
|
||||
Type PatternType `db:"type" json:"type"` // bug, refactor, architecture, etc.
|
||||
Description sql.NullString `db:"description" json:"description"` // Detailed description
|
||||
Signature JSONStringArray `db:"signature" json:"signature"` // Keyword clusters for detection
|
||||
Recommendation sql.NullString `db:"recommendation" json:"recommendation"` // What works for this pattern
|
||||
Frequency int `db:"frequency" json:"frequency"` // How many times encountered
|
||||
Projects JSONStringArray `db:"projects" json:"projects"` // Projects where this pattern was seen
|
||||
ObservationIDs JSONInt64Array `db:"observation_ids" json:"observation_ids"` // Source observation IDs
|
||||
Status PatternStatus `db:"status" json:"status"` // active, deprecated, merged
|
||||
MergedIntoID sql.NullInt64 `db:"merged_into_id" json:"merged_into_id,omitempty"`
|
||||
Confidence float64 `db:"confidence" json:"confidence"` // Detection confidence (0.0-1.0)
|
||||
LastSeenAt string `db:"last_seen_at" json:"last_seen_at"` // Last time pattern was detected
|
||||
LastSeenEpoch int64 `db:"last_seen_at_epoch" json:"last_seen_at_epoch"`
|
||||
Status PatternStatus `db:"status" json:"status"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Type PatternType `db:"type" json:"type"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
LastSeenAt string `db:"last_seen_at" json:"last_seen_at"`
|
||||
Signature JSONStringArray `db:"signature" json:"signature"`
|
||||
Projects JSONStringArray `db:"projects" json:"projects"`
|
||||
ObservationIDs JSONInt64Array `db:"observation_ids" json:"observation_ids"`
|
||||
Recommendation sql.NullString `db:"recommendation" json:"recommendation"`
|
||||
Description sql.NullString `db:"description" json:"description"`
|
||||
MergedIntoID sql.NullInt64 `db:"merged_into_id" json:"merged_into_id,omitempty"`
|
||||
Frequency int `db:"frequency" json:"frequency"`
|
||||
Confidence float64 `db:"confidence" json:"confidence"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
LastSeenEpoch int64 `db:"last_seen_at_epoch" json:"last_seen_at_epoch"`
|
||||
CreatedAtEpoch int64 `db:"created_at_epoch" json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
@@ -95,21 +95,21 @@ func (j JSONInt64Array) Value() (driver.Value, error) {
|
||||
|
||||
// PatternJSON is a JSON-friendly representation of Pattern.
|
||||
type PatternJSON struct {
|
||||
ID int64 `json:"id"`
|
||||
Status PatternStatus `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Type PatternType `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Signature []string `json:"signature,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Recommendation string `json:"recommendation,omitempty"`
|
||||
Frequency int `json:"frequency"`
|
||||
Projects []string `json:"projects,omitempty"`
|
||||
LastSeenAt string `json:"last_seen_at"`
|
||||
Signature []string `json:"signature,omitempty"`
|
||||
ObservationIDs []int64 `json:"observation_ids,omitempty"`
|
||||
Status PatternStatus `json:"status"`
|
||||
Projects []string `json:"projects,omitempty"`
|
||||
MergedIntoID int64 `json:"merged_into_id,omitempty"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
LastSeenAt string `json:"last_seen_at"`
|
||||
Frequency int `json:"frequency"`
|
||||
LastSeenEpoch int64 `json:"last_seen_at_epoch"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ID int64 `json:"id"`
|
||||
CreatedAtEpoch int64 `json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
@@ -214,11 +214,11 @@ func (p *Pattern) updateConfidence() {
|
||||
|
||||
// PatternMatch represents a match between an observation and a potential pattern.
|
||||
type PatternMatch struct {
|
||||
PatternID int64 `json:"pattern_id"`
|
||||
Score float64 `json:"score"` // Match score (0.0-1.0)
|
||||
MatchedOn string `json:"matched_on"` // What triggered the match (concept, keyword, type, etc.)
|
||||
IsNew bool `json:"is_new"` // Whether this would create a new pattern
|
||||
MatchedOn string `json:"matched_on"`
|
||||
SuggestedName string `json:"suggested_name,omitempty"`
|
||||
PatternID int64 `json:"pattern_id"`
|
||||
Score float64 `json:"score"`
|
||||
IsNew bool `json:"is_new"`
|
||||
}
|
||||
|
||||
// PatternSignatureKeywords are common keywords used in pattern detection.
|
||||
|
||||
@@ -116,18 +116,18 @@ func TestPattern_ConfidenceCalculation(t *testing.T) {
|
||||
|
||||
func TestPatternType_Detection(t *testing.T) {
|
||||
tests := []struct {
|
||||
concepts []string
|
||||
title string
|
||||
narrative string
|
||||
expected PatternType
|
||||
concepts []string
|
||||
}{
|
||||
{[]string{"anti-pattern"}, "", "", PatternTypeAntiPattern},
|
||||
{[]string{"best-practice"}, "", "", PatternTypeBestPractice},
|
||||
{[]string{"architecture"}, "", "", PatternTypeArchitecture},
|
||||
{[]string{"refactor"}, "", "", PatternTypeRefactor},
|
||||
{[]string{}, "nil pointer bug", "", PatternTypeBug},
|
||||
{[]string{}, "Deadlock in concurrent code", "", PatternTypeBug},
|
||||
{[]string{}, "Extract interface", "", PatternTypeRefactor},
|
||||
{title: "", narrative: "", expected: PatternTypeAntiPattern, concepts: []string{"anti-pattern"}},
|
||||
{title: "", narrative: "", expected: PatternTypeBestPractice, concepts: []string{"best-practice"}},
|
||||
{title: "", narrative: "", expected: PatternTypeArchitecture, concepts: []string{"architecture"}},
|
||||
{title: "", narrative: "", expected: PatternTypeRefactor, concepts: []string{"refactor"}},
|
||||
{title: "nil pointer bug", narrative: "", expected: PatternTypeBug, concepts: []string{}},
|
||||
{title: "Deadlock in concurrent code", narrative: "", expected: PatternTypeBug, concepts: []string{}},
|
||||
{title: "Extract interface", narrative: "", expected: PatternTypeRefactor, concepts: []string{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -3,18 +3,18 @@ package models
|
||||
|
||||
// UserPrompt represents a user prompt captured during a session.
|
||||
type UserPrompt struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
ClaudeSessionID string `db:"claude_session_id" json:"claude_session_id"`
|
||||
PromptNumber int `db:"prompt_number" json:"prompt_number"`
|
||||
PromptText string `db:"prompt_text" json:"prompt_text"`
|
||||
MatchedObservations int `db:"matched_observations" json:"matched_observations"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
PromptNumber int `db:"prompt_number" json:"prompt_number"`
|
||||
MatchedObservations int `db:"matched_observations" json:"matched_observations"`
|
||||
CreatedAtEpoch int64 `db:"created_at_epoch" json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
// UserPromptWithSession includes session context for search results.
|
||||
type UserPromptWithSession struct {
|
||||
UserPrompt
|
||||
Project string `db:"project" json:"project"`
|
||||
SDKSessionID string `db:"sdk_session_id" json:"sdk_session_id"`
|
||||
UserPrompt
|
||||
}
|
||||
|
||||
@@ -60,14 +60,14 @@ const (
|
||||
|
||||
// ObservationRelation represents a directed relationship between two observations.
|
||||
type ObservationRelation struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
SourceID int64 `db:"source_id" json:"source_id"`
|
||||
TargetID int64 `db:"target_id" json:"target_id"`
|
||||
RelationType RelationType `db:"relation_type" json:"relation_type"`
|
||||
Confidence float64 `db:"confidence" json:"confidence"`
|
||||
DetectionSource RelationDetectionSource `db:"detection_source" json:"detection_source"`
|
||||
Reason string `db:"reason" json:"reason,omitempty"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
SourceID int64 `db:"source_id" json:"source_id"`
|
||||
TargetID int64 `db:"target_id" json:"target_id"`
|
||||
Confidence float64 `db:"confidence" json:"confidence"`
|
||||
CreatedAtEpoch int64 `db:"created_at_epoch" json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ func NewObservationRelation(sourceID, targetID int64, relType RelationType, conf
|
||||
|
||||
// RelationDetectionResult contains the result of relation detection.
|
||||
type RelationDetectionResult struct {
|
||||
SourceID int64
|
||||
TargetID int64
|
||||
RelationType RelationType
|
||||
Confidence float64
|
||||
DetectionSource RelationDetectionSource
|
||||
Reason string
|
||||
SourceID int64
|
||||
TargetID int64
|
||||
Confidence float64
|
||||
}
|
||||
|
||||
// DetectFileOverlapRelation checks if observations share file references and determines relationship type.
|
||||
@@ -484,6 +484,6 @@ type RelationWithDetails struct {
|
||||
|
||||
// RelationGraph represents a graph of related observations.
|
||||
type RelationGraph struct {
|
||||
CenterID int64 `json:"center_id"`
|
||||
Relations []*RelationWithDetails `json:"relations"`
|
||||
CenterID int64 `json:"center_id"`
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
func TestDetectFileOverlapRelation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newer *Observation
|
||||
older *Observation
|
||||
wantRelation bool
|
||||
name string
|
||||
wantRelType RelationType
|
||||
wantMinConfid float64
|
||||
wantRelation bool
|
||||
}{
|
||||
{
|
||||
name: "no file overlap",
|
||||
@@ -105,11 +105,11 @@ func TestDetectFileOverlapRelation(t *testing.T) {
|
||||
|
||||
func TestDetectConceptOverlapRelation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
newer *Observation
|
||||
older *Observation
|
||||
wantRelation bool
|
||||
name string
|
||||
wantMinConfid float64
|
||||
wantRelation bool
|
||||
}{
|
||||
{
|
||||
name: "no concept overlap",
|
||||
@@ -179,8 +179,8 @@ func TestDetectTypeProgressionRelation(t *testing.T) {
|
||||
name string
|
||||
newerType ObservationType
|
||||
olderType ObservationType
|
||||
wantRelation bool
|
||||
wantRelType RelationType
|
||||
wantRelation bool
|
||||
}{
|
||||
{
|
||||
name: "bugfix fixes discovery",
|
||||
@@ -314,8 +314,8 @@ func TestDetectNarrativeMentionRelation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
narrative string
|
||||
wantRelation bool
|
||||
wantRelType RelationType
|
||||
wantRelation bool
|
||||
}{
|
||||
{
|
||||
name: "fixes language",
|
||||
|
||||
+7
-23
@@ -4,8 +4,8 @@ package models
|
||||
// ConceptWeight represents a configurable weight for a concept.
|
||||
type ConceptWeight struct {
|
||||
Concept string `db:"concept" json:"concept"`
|
||||
Weight float64 `db:"weight" json:"weight"`
|
||||
UpdatedAt string `db:"updated_at" json:"updated_at"`
|
||||
Weight float64 `db:"weight" json:"weight"`
|
||||
}
|
||||
|
||||
// UserFeedbackType represents the type of user feedback.
|
||||
@@ -62,28 +62,12 @@ var TypeBaseScores = map[ObservationType]float64{
|
||||
|
||||
// ScoringConfig contains all scoring weights and parameters.
|
||||
type ScoringConfig struct {
|
||||
// RecencyHalfLifeDays is the number of days for the importance score to halve.
|
||||
// With 7 days, a 7-day old observation has 50% of a new observation's recency score.
|
||||
RecencyHalfLifeDays float64 `json:"recency_half_life_days"`
|
||||
|
||||
// FeedbackWeight scales the user feedback contribution to final score.
|
||||
// With 0.30, a thumbs up adds 0.30 to the score, thumbs down subtracts 0.30.
|
||||
FeedbackWeight float64 `json:"feedback_weight"`
|
||||
|
||||
// ConceptWeight scales the concept boost contribution.
|
||||
// The sum of matching concept weights is multiplied by this.
|
||||
ConceptWeight float64 `json:"concept_weight"`
|
||||
|
||||
// RetrievalWeight scales the retrieval boost contribution.
|
||||
// Popular observations get a logarithmic bonus.
|
||||
RetrievalWeight float64 `json:"retrieval_weight"`
|
||||
|
||||
// ConceptWeights maps concept names to their importance weights.
|
||||
ConceptWeights map[string]float64 `json:"concept_weights"`
|
||||
|
||||
// MinScore is the minimum allowed importance score.
|
||||
// Prevents observations from completely disappearing.
|
||||
MinScore float64 `json:"min_score"`
|
||||
ConceptWeights map[string]float64 `json:"concept_weights"`
|
||||
RecencyHalfLifeDays float64 `json:"recency_half_life_days"`
|
||||
FeedbackWeight float64 `json:"feedback_weight"`
|
||||
ConceptWeight float64 `json:"concept_weight"`
|
||||
RetrievalWeight float64 `json:"retrieval_weight"`
|
||||
MinScore float64 `json:"min_score"`
|
||||
}
|
||||
|
||||
// DefaultScoringConfig returns the default scoring configuration.
|
||||
|
||||
@@ -17,29 +17,29 @@ const (
|
||||
|
||||
// SDKSession represents a Claude Code session tracked by the memory system.
|
||||
type SDKSession struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
ClaudeSessionID string `db:"claude_session_id" json:"claude_session_id"`
|
||||
SDKSessionID sql.NullString `db:"sdk_session_id" json:"sdk_session_id,omitempty"`
|
||||
Project string `db:"project" json:"project"`
|
||||
UserPrompt sql.NullString `db:"user_prompt" json:"user_prompt,omitempty"`
|
||||
WorkerPort sql.NullInt64 `db:"worker_port" json:"worker_port,omitempty"`
|
||||
PromptCounter int64 `db:"prompt_counter" json:"prompt_counter"`
|
||||
Status SessionStatus `db:"status" json:"status"`
|
||||
StartedAt string `db:"started_at" json:"started_at"`
|
||||
StartedAtEpoch int64 `db:"started_at_epoch" json:"started_at_epoch"`
|
||||
SDKSessionID sql.NullString `db:"sdk_session_id" json:"sdk_session_id,omitempty"`
|
||||
UserPrompt sql.NullString `db:"user_prompt" json:"user_prompt,omitempty"`
|
||||
CompletedAt sql.NullString `db:"completed_at" json:"completed_at,omitempty"`
|
||||
WorkerPort sql.NullInt64 `db:"worker_port" json:"worker_port,omitempty"`
|
||||
CompletedAtEpoch sql.NullInt64 `db:"completed_at_epoch" json:"completed_at_epoch,omitempty"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
PromptCounter int64 `db:"prompt_counter" json:"prompt_counter"`
|
||||
StartedAtEpoch int64 `db:"started_at_epoch" json:"started_at_epoch"`
|
||||
}
|
||||
|
||||
// ActiveSession represents an in-memory active session being processed.
|
||||
type ActiveSession struct {
|
||||
SessionDBID int64
|
||||
StartTime time.Time
|
||||
ClaudeSessionID string
|
||||
SDKSessionID string
|
||||
Project string
|
||||
UserPrompt string
|
||||
SessionDBID int64
|
||||
LastPromptNumber int
|
||||
StartTime time.Time
|
||||
CumulativeInputTokens int64
|
||||
CumulativeOutputTokens int64
|
||||
}
|
||||
|
||||
@@ -9,18 +9,18 @@ import (
|
||||
|
||||
// SessionSummary represents a summary of a Claude Code session.
|
||||
type SessionSummary struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
SDKSessionID string `db:"sdk_session_id" json:"sdk_session_id"`
|
||||
Project string `db:"project" json:"project"`
|
||||
Request sql.NullString `db:"request" json:"request,omitempty"`
|
||||
Completed sql.NullString `db:"completed" json:"completed,omitempty"`
|
||||
Investigated sql.NullString `db:"investigated" json:"investigated,omitempty"`
|
||||
Learned sql.NullString `db:"learned" json:"learned,omitempty"`
|
||||
Completed sql.NullString `db:"completed" json:"completed,omitempty"`
|
||||
NextSteps sql.NullString `db:"next_steps" json:"next_steps,omitempty"`
|
||||
Notes sql.NullString `db:"notes" json:"notes,omitempty"`
|
||||
Request sql.NullString `db:"request" json:"request,omitempty"`
|
||||
PromptNumber sql.NullInt64 `db:"prompt_number" json:"prompt_number,omitempty"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
DiscoveryTokens int64 `db:"discovery_tokens" json:"discovery_tokens"`
|
||||
CreatedAt string `db:"created_at" json:"created_at"`
|
||||
CreatedAtEpoch int64 `db:"created_at_epoch" json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
@@ -56,18 +56,18 @@ func NewSessionSummary(sdkSessionID, project string, parsed *ParsedSummary, prom
|
||||
// SessionSummaryJSON is a JSON-friendly representation of SessionSummary.
|
||||
// It converts sql.NullString to plain strings for clean JSON output.
|
||||
type SessionSummaryJSON struct {
|
||||
ID int64 `json:"id"`
|
||||
Completed string `json:"completed,omitempty"`
|
||||
SDKSessionID string `json:"sdk_session_id"`
|
||||
Project string `json:"project"`
|
||||
Request string `json:"request,omitempty"`
|
||||
Investigated string `json:"investigated,omitempty"`
|
||||
Learned string `json:"learned,omitempty"`
|
||||
Completed string `json:"completed,omitempty"`
|
||||
NextSteps string `json:"next_steps,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ID int64 `json:"id"`
|
||||
PromptNumber int64 `json:"prompt_number,omitempty"`
|
||||
DiscoveryTokens int64 `json:"discovery_tokens"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
CreatedAtEpoch int64 `json:"created_at_epoch"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user