chore: align slurp config and scaffolding

This commit is contained in:
anthonyrawlins
2025-09-27 21:03:12 +10:00
parent acc4361463
commit 4a77862289
47 changed files with 5133 additions and 4274 deletions

View File

@@ -13,36 +13,36 @@ import (
// decisionNavigatorImpl implements the DecisionNavigator interface
type decisionNavigatorImpl struct {
mu sync.RWMutex
// Reference to the temporal graph
graph *temporalGraphImpl
// Navigation state
navigationSessions map[string]*NavigationSession
bookmarks map[string]*DecisionBookmark
// Configuration
maxNavigationHistory int
}
// NavigationSession represents a navigation session
type NavigationSession struct {
ID string `json:"id"`
UserID string `json:"user_id"`
StartedAt time.Time `json:"started_at"`
LastActivity time.Time `json:"last_activity"`
CurrentPosition ucxl.Address `json:"current_position"`
History []*DecisionStep `json:"history"`
Bookmarks []string `json:"bookmarks"`
Preferences *NavPreferences `json:"preferences"`
ID string `json:"id"`
UserID string `json:"user_id"`
StartedAt time.Time `json:"started_at"`
LastActivity time.Time `json:"last_activity"`
CurrentPosition ucxl.Address `json:"current_position"`
History []*DecisionStep `json:"history"`
Bookmarks []string `json:"bookmarks"`
Preferences *NavPreferences `json:"preferences"`
}
// NavPreferences represents navigation preferences
type NavPreferences struct {
MaxHops int `json:"max_hops"`
MaxHops int `json:"max_hops"`
PreferRecentDecisions bool `json:"prefer_recent_decisions"`
FilterByConfidence float64 `json:"filter_by_confidence"`
IncludeStaleContexts bool `json:"include_stale_contexts"`
FilterByConfidence float64 `json:"filter_by_confidence"`
IncludeStaleContexts bool `json:"include_stale_contexts"`
}
// NewDecisionNavigator creates a new decision navigator
@@ -50,24 +50,24 @@ func NewDecisionNavigator(graph *temporalGraphImpl) DecisionNavigator {
return &decisionNavigatorImpl{
graph: graph,
navigationSessions: make(map[string]*NavigationSession),
bookmarks: make(map[string]*DecisionBookmark),
bookmarks: make(map[string]*DecisionBookmark),
maxNavigationHistory: 100,
}
}
// NavigateDecisionHops navigates by decision distance, not time
func (dn *decisionNavigatorImpl) NavigateDecisionHops(ctx context.Context, address ucxl.Address,
func (dn *decisionNavigatorImpl) NavigateDecisionHops(ctx context.Context, address ucxl.Address,
hops int, direction NavigationDirection) (*TemporalNode, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
// Get starting node
startNode, err := dn.graph.getLatestNodeUnsafe(address)
if err != nil {
return nil, fmt.Errorf("failed to get starting node: %w", err)
}
// Navigate by hops
currentNode := startNode
for i := 0; i < hops; i++ {
@@ -77,23 +77,23 @@ func (dn *decisionNavigatorImpl) NavigateDecisionHops(ctx context.Context, addre
}
currentNode = nextNode
}
return currentNode, nil
}
// GetDecisionTimeline gets timeline ordered by decision sequence
func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, address ucxl.Address,
func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, address ucxl.Address,
includeRelated bool, maxHops int) (*DecisionTimeline, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
// Get evolution history for the primary address
history, err := dn.graph.GetEvolutionHistory(ctx, address)
if err != nil {
return nil, fmt.Errorf("failed to get evolution history: %w", err)
}
// Build decision timeline entries
decisionSequence := make([]*DecisionTimelineEntry, len(history))
for i, node := range history {
@@ -112,7 +112,7 @@ func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, addres
}
decisionSequence[i] = entry
}
// Get related decisions if requested
relatedDecisions := make([]*RelatedDecision, 0)
if includeRelated && maxHops > 0 {
@@ -136,16 +136,16 @@ func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, addres
}
}
}
// Calculate timeline analysis
analysis := dn.analyzeTimeline(decisionSequence, relatedDecisions)
// Calculate time span
var timeSpan time.Duration
if len(history) > 1 {
timeSpan = history[len(history)-1].Timestamp.Sub(history[0].Timestamp)
}
timeline := &DecisionTimeline{
PrimaryAddress: address,
DecisionSequence: decisionSequence,
@@ -154,7 +154,7 @@ func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, addres
TimeSpan: timeSpan,
AnalysisMetadata: analysis,
}
return timeline, nil
}
@@ -162,31 +162,31 @@ func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, addres
func (dn *decisionNavigatorImpl) FindStaleContexts(ctx context.Context, stalenessThreshold float64) ([]*StaleContext, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
staleContexts := make([]*StaleContext, 0)
// Check all nodes for staleness
for _, node := range dn.graph.nodes {
if node.Staleness >= stalenessThreshold {
staleness := &StaleContext{
UCXLAddress: node.UCXLAddress,
TemporalNode: node,
StalenessScore: node.Staleness,
LastUpdated: node.Timestamp,
Reasons: dn.getStalenessReasons(node),
UCXLAddress: node.UCXLAddress,
TemporalNode: node,
StalenessScore: node.Staleness,
LastUpdated: node.Timestamp,
Reasons: dn.getStalenessReasons(node),
SuggestedActions: dn.getSuggestedActions(node),
RelatedChanges: dn.getRelatedChanges(node),
Priority: dn.calculateStalePriority(node),
RelatedChanges: dn.getRelatedChanges(node),
Priority: dn.calculateStalePriority(node),
}
staleContexts = append(staleContexts, staleness)
}
}
// Sort by staleness score (highest first)
sort.Slice(staleContexts, func(i, j int) bool {
return staleContexts[i].StalenessScore > staleContexts[j].StalenessScore
})
return staleContexts, nil
}
@@ -195,28 +195,28 @@ func (dn *decisionNavigatorImpl) ValidateDecisionPath(ctx context.Context, path
if len(path) == 0 {
return fmt.Errorf("empty decision path")
}
dn.mu.RLock()
defer dn.mu.RUnlock()
// Validate each step in the path
for i, step := range path {
// Check if the temporal node exists
if step.TemporalNode == nil {
return fmt.Errorf("step %d has nil temporal node", i)
}
nodeID := step.TemporalNode.ID
if _, exists := dn.graph.nodes[nodeID]; !exists {
return fmt.Errorf("step %d references non-existent node %s", i, nodeID)
}
// Validate hop distance
if step.HopDistance != i {
return fmt.Errorf("step %d has incorrect hop distance: expected %d, got %d",
return fmt.Errorf("step %d has incorrect hop distance: expected %d, got %d",
i, i, step.HopDistance)
}
// Validate relationship to next step
if i < len(path)-1 {
nextStep := path[i+1]
@@ -225,7 +225,7 @@ func (dn *decisionNavigatorImpl) ValidateDecisionPath(ctx context.Context, path
}
}
}
return nil
}
@@ -233,16 +233,16 @@ func (dn *decisionNavigatorImpl) ValidateDecisionPath(ctx context.Context, path
func (dn *decisionNavigatorImpl) GetNavigationHistory(ctx context.Context, sessionID string) ([]*DecisionStep, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
session, exists := dn.navigationSessions[sessionID]
if !exists {
return nil, fmt.Errorf("navigation session %s not found", sessionID)
}
// Return a copy of the history
history := make([]*DecisionStep, len(session.History))
copy(history, session.History)
return history, nil
}
@@ -250,22 +250,22 @@ func (dn *decisionNavigatorImpl) GetNavigationHistory(ctx context.Context, sessi
func (dn *decisionNavigatorImpl) ResetNavigation(ctx context.Context, address ucxl.Address) error {
dn.mu.Lock()
defer dn.mu.Unlock()
// Clear any navigation sessions for this address
for sessionID, session := range dn.navigationSessions {
for _, session := range dn.navigationSessions {
if session.CurrentPosition.String() == address.String() {
// Reset to latest version
latestNode, err := dn.graph.getLatestNodeUnsafe(address)
if err != nil {
return fmt.Errorf("failed to get latest node: %w", err)
}
session.CurrentPosition = address
session.History = []*DecisionStep{}
session.LastActivity = time.Now()
}
}
return nil
}
@@ -273,13 +273,13 @@ func (dn *decisionNavigatorImpl) ResetNavigation(ctx context.Context, address uc
func (dn *decisionNavigatorImpl) BookmarkDecision(ctx context.Context, address ucxl.Address, hop int, name string) error {
dn.mu.Lock()
defer dn.mu.Unlock()
// Validate the decision point exists
node, err := dn.graph.GetVersionAtDecision(ctx, address, hop)
if err != nil {
return fmt.Errorf("decision point not found: %w", err)
}
// Create bookmark
bookmarkID := fmt.Sprintf("%s-%d-%d", address.String(), hop, time.Now().Unix())
bookmark := &DecisionBookmark{
@@ -293,14 +293,14 @@ func (dn *decisionNavigatorImpl) BookmarkDecision(ctx context.Context, address u
Tags: []string{},
Metadata: make(map[string]interface{}),
}
// Add context information to metadata
bookmark.Metadata["change_reason"] = node.ChangeReason
bookmark.Metadata["decision_id"] = node.DecisionID
bookmark.Metadata["confidence"] = node.Confidence
dn.bookmarks[bookmarkID] = bookmark
return nil
}
@@ -308,17 +308,17 @@ func (dn *decisionNavigatorImpl) BookmarkDecision(ctx context.Context, address u
func (dn *decisionNavigatorImpl) ListBookmarks(ctx context.Context) ([]*DecisionBookmark, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
bookmarks := make([]*DecisionBookmark, 0, len(dn.bookmarks))
for _, bookmark := range dn.bookmarks {
bookmarks = append(bookmarks, bookmark)
}
// Sort by creation time (newest first)
sort.Slice(bookmarks, func(i, j int) bool {
return bookmarks[i].CreatedAt.After(bookmarks[j].CreatedAt)
})
return bookmarks, nil
}
@@ -342,14 +342,14 @@ func (dn *decisionNavigatorImpl) navigateForward(currentNode *TemporalNode) (*Te
if !exists {
return nil, fmt.Errorf("no nodes found for address")
}
// Find current node in the list and get the next one
for i, node := range nodes {
if node.ID == currentNode.ID && i < len(nodes)-1 {
return nodes[i+1], nil
}
}
return nil, fmt.Errorf("no forward navigation possible")
}
@@ -358,12 +358,12 @@ func (dn *decisionNavigatorImpl) navigateBackward(currentNode *TemporalNode) (*T
if currentNode.ParentNode == nil {
return nil, fmt.Errorf("no backward navigation possible: no parent node")
}
parentNode, exists := dn.graph.nodes[*currentNode.ParentNode]
if !exists {
return nil, fmt.Errorf("parent node not found: %s", *currentNode.ParentNode)
}
return parentNode, nil
}
@@ -387,7 +387,7 @@ func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEnt
AnalyzedAt: time.Now(),
}
}
// Calculate change velocity
var changeVelocity float64
if len(sequence) > 1 {
@@ -398,27 +398,27 @@ func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEnt
changeVelocity = float64(len(sequence)-1) / duration.Hours()
}
}
// Analyze confidence trend
confidenceTrend := "stable"
if len(sequence) > 1 {
firstConfidence := sequence[0].ConfidenceEvolution
lastConfidence := sequence[len(sequence)-1].ConfidenceEvolution
diff := lastConfidence - firstConfidence
if diff > 0.1 {
confidenceTrend = "increasing"
} else if diff < -0.1 {
confidenceTrend = "decreasing"
}
}
// Count change reasons
reasonCounts := make(map[ChangeReason]int)
for _, entry := range sequence {
reasonCounts[entry.ChangeReason]++
}
// Find dominant reasons
dominantReasons := make([]ChangeReason, 0)
maxCount := 0
@@ -430,19 +430,19 @@ func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEnt
dominantReasons = append(dominantReasons, reason)
}
}
// Count decision makers
makerCounts := make(map[string]int)
for _, entry := range sequence {
makerCounts[entry.DecisionMaker]++
}
// Count impact scope distribution
scopeCounts := make(map[ImpactScope]int)
for _, entry := range sequence {
scopeCounts[entry.ImpactScope]++
}
return &TimelineAnalysis{
ChangeVelocity: changeVelocity,
ConfidenceTrend: confidenceTrend,
@@ -456,47 +456,47 @@ func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEnt
func (dn *decisionNavigatorImpl) getStalenessReasons(node *TemporalNode) []string {
reasons := make([]string, 0)
// Time-based staleness
timeSinceUpdate := time.Since(node.Timestamp)
if timeSinceUpdate > 7*24*time.Hour {
reasons = append(reasons, "not updated in over a week")
}
// Influence-based staleness
if len(node.InfluencedBy) > 0 {
reasons = append(reasons, "influenced by other contexts that may have changed")
}
// Confidence-based staleness
if node.Confidence < 0.7 {
reasons = append(reasons, "low confidence score")
}
return reasons
}
func (dn *decisionNavigatorImpl) getSuggestedActions(node *TemporalNode) []string {
actions := make([]string, 0)
actions = append(actions, "review context for accuracy")
actions = append(actions, "check related decisions for impact")
if node.Confidence < 0.7 {
actions = append(actions, "improve context confidence through additional analysis")
}
if len(node.InfluencedBy) > 3 {
actions = append(actions, "validate dependencies are still accurate")
}
return actions
}
func (dn *decisionNavigatorImpl) getRelatedChanges(node *TemporalNode) []ucxl.Address {
// Find contexts that have changed recently and might affect this one
relatedChanges := make([]ucxl.Address, 0)
cutoff := time.Now().Add(-24 * time.Hour)
for _, otherNode := range dn.graph.nodes {
if otherNode.Timestamp.After(cutoff) && otherNode.ID != node.ID {
@@ -509,18 +509,18 @@ func (dn *decisionNavigatorImpl) getRelatedChanges(node *TemporalNode) []ucxl.Ad
}
}
}
return relatedChanges
}
func (dn *decisionNavigatorImpl) calculateStalePriority(node *TemporalNode) StalePriority {
score := node.Staleness
// Adjust based on influence
if len(node.Influences) > 5 {
score += 0.2 // Higher priority if it influences many others
}
// Adjust based on impact scope
switch node.ImpactScope {
case ImpactSystem:
@@ -530,7 +530,7 @@ func (dn *decisionNavigatorImpl) calculateStalePriority(node *TemporalNode) Stal
case ImpactModule:
score += 0.1
}
if score >= 0.9 {
return PriorityCritical
} else if score >= 0.7 {
@@ -545,7 +545,7 @@ func (dn *decisionNavigatorImpl) validateStepRelationship(step, nextStep *Decisi
// Check if there's a valid relationship between the steps
currentNodeID := step.TemporalNode.ID
nextNodeID := nextStep.TemporalNode.ID
switch step.Relationship {
case "influences":
if influences, exists := dn.graph.influences[currentNodeID]; exists {
@@ -564,6 +564,6 @@ func (dn *decisionNavigatorImpl) validateStepRelationship(step, nextStep *Decisi
}
}
}
return false
}
}