-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodegraph_snapshot.go
More file actions
204 lines (173 loc) · 6.58 KB
/
codegraph_snapshot.go
File metadata and controls
204 lines (173 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package trace
import (
"encoding/json"
"errors"
"fmt"
"time"
)
// CodeGraphSnapshot captures the state of the code graph at a point in time.
// This enables:
// - Tracking how the codebase evolves across sessions
// - Comparing graph states to detect structural changes
// - Building a history of code complexity over time
type CodeGraphSnapshot struct {
Timestamp time.Time `json:"timestamp"`
SessionID string `json:"session_id"`
ProjectRoot string `json:"project_root"`
GraphStats GraphStats `json:"graph_stats"`
SymbolCount int `json:"symbol_count"`
EdgeCount int `json:"edge_count"`
FileCount int `json:"file_count"`
TopSymbols []SymbolInfo `json:"top_symbols"`
Modules []ModuleInfo `json:"modules"`
Complexity ComplexityMetrics `json:"complexity"`
Delta *GraphDelta `json:"delta,omitempty"` // changes from previous snapshot
}
// GraphStats holds statistics about the code graph.
type GraphStats struct {
NodesByKind map[string]int `json:"nodes_by_kind"`
EdgesByKind map[string]int `json:"edges_by_kind"`
FilesByLang map[string]int `json:"files_by_lang"`
}
// SymbolInfo represents a symbol in the snapshot.
type SymbolInfo struct {
Name string `json:"name"`
Kind string `json:"kind"`
File string `json:"file"`
Line int `json:"line"`
CallCount int `json:"call_count"` // how many times called
}
// ModuleInfo represents a module/package in the snapshot.
type ModuleInfo struct {
Name string `json:"name"`
Path string `json:"path"`
Files int `json:"files"`
Functions int `json:"functions"`
Types int `json:"types"`
Imports []string `json:"imports"`
}
// ComplexityMetrics holds complexity measurements.
type ComplexityMetrics struct {
AvgCyclomatic float64 `json:"avg_cyclomatic"`
MaxCyclomatic int `json:"max_cyclomatic"`
AvgLOC float64 `json:"avg_loc"`
MaxLOC int `json:"max_loc"`
TotalFunctions int `json:"total_functions"`
}
// GraphDelta represents changes between two snapshots.
type GraphDelta struct {
FilesAdded int `json:"files_added"`
FilesRemoved int `json:"files_removed"`
FilesModified int `json:"files_modified"`
NodesAdded int `json:"nodes_added"`
NodesRemoved int `json:"nodes_removed"`
EdgesAdded int `json:"edges_added"`
EdgesRemoved int `json:"edges_removed"`
NewSymbols []string `json:"new_symbols"`
RemovedSymbols []string `json:"removed_symbols"`
ComplexityDelta float64 `json:"complexity_delta"` // change in avg complexity
}
// ErrNoSnapshots is returned when no snapshots exist in the store.
var ErrNoSnapshots = errors.New("no snapshots found")
// SnapshotStore manages code graph snapshots.
type SnapshotStore struct {
path string
}
// NewSnapshotStore creates a new snapshot store.
func NewSnapshotStore(path string) *SnapshotStore {
return &SnapshotStore{path: path}
}
// Save persists a snapshot to disk.
func (s *SnapshotStore) Save(snapshot CodeGraphSnapshot) error {
data, err := json.MarshalIndent(snapshot, "", " ")
if err != nil {
return fmt.Errorf("marshal snapshot: %w", err)
}
// Save to file
filename := s.path + "/snapshot_" + snapshot.Timestamp.Format("20060102_150405") + ".json"
return writeFile(filename, data)
}
// Load loads the most recent snapshot.
func (s *SnapshotStore) Load() (*CodeGraphSnapshot, error) {
files, err := listFiles(s.path, "snapshot_*.json")
if err != nil {
return nil, fmt.Errorf("list snapshots: %w", err)
}
if len(files) == 0 {
return nil, ErrNoSnapshots
}
// Load most recent
data, err := readFile(files[len(files)-1])
if err != nil {
return nil, fmt.Errorf("read snapshot file: %w", err)
}
var snapshot CodeGraphSnapshot
if err := json.Unmarshal(data, &snapshot); err != nil {
return nil, fmt.Errorf("unmarshal snapshot: %w", err)
}
return &snapshot, nil
}
// Compare compares two snapshots and returns the delta.
func CompareSnapshots(old, cur *CodeGraphSnapshot) *GraphDelta {
if old == nil || cur == nil {
return nil
}
delta := &GraphDelta{
FilesAdded: cur.FileCount - old.FileCount,
NodesAdded: cur.SymbolCount - old.SymbolCount,
EdgesAdded: cur.EdgeCount - old.EdgeCount,
ComplexityDelta: cur.Complexity.AvgCyclomatic - old.Complexity.AvgCyclomatic,
}
// Find new and removed symbols
oldSymbols := make(map[string]bool)
for _, s := range old.TopSymbols {
oldSymbols[s.Name] = true
}
for _, s := range cur.TopSymbols {
if !oldSymbols[s.Name] {
delta.NewSymbols = append(delta.NewSymbols, s.Name)
}
}
newSymbols := make(map[string]bool)
for _, s := range cur.TopSymbols {
newSymbols[s.Name] = true
}
for _, s := range old.TopSymbols {
if !newSymbols[s.Name] {
delta.RemovedSymbols = append(delta.RemovedSymbols, s.Name)
}
}
return delta
}
// FormatSnapshot formats a snapshot for display.
func FormatSnapshot(snapshot CodeGraphSnapshot) string {
var result string
result += "## Code Graph Snapshot\n\n"
result += fmt.Sprintf("- **Time**: %s\n", snapshot.Timestamp.Format(time.RFC3339))
result += fmt.Sprintf("- **Session**: %s\n", snapshot.SessionID)
result += fmt.Sprintf("- **Project**: %s\n\n", snapshot.ProjectRoot)
result += "### Statistics\n\n"
result += fmt.Sprintf("- Files: %d\n", snapshot.FileCount)
result += fmt.Sprintf("- Symbols: %d\n", snapshot.SymbolCount)
result += fmt.Sprintf("- Edges: %d\n\n", snapshot.EdgeCount)
result += "### Top Symbols\n\n"
for _, s := range snapshot.TopSymbols[:min(10, len(snapshot.TopSymbols))] {
result += fmt.Sprintf("- %s `%s` in %s:%d (called %d times)\n",
s.Kind, s.Name, s.File, s.Line, s.CallCount)
}
result += "\n### Complexity\n\n"
result += fmt.Sprintf("- Avg Cyclomatic: %.1f\n", snapshot.Complexity.AvgCyclomatic)
result += fmt.Sprintf("- Max Cyclomatic: %d\n", snapshot.Complexity.MaxCyclomatic)
result += fmt.Sprintf("- Total Functions: %d\n", snapshot.Complexity.TotalFunctions)
if snapshot.Delta != nil {
result += "\n### Changes Since Last Snapshot\n\n"
result += fmt.Sprintf("- Files: +%d -%d modified\n", snapshot.Delta.FilesAdded, snapshot.Delta.FilesRemoved)
result += fmt.Sprintf("- Symbols: +%d -%d\n", snapshot.Delta.NodesAdded, snapshot.Delta.NodesRemoved)
result += fmt.Sprintf("- Complexity: %+.1f\n", snapshot.Delta.ComplexityDelta)
}
return result
}
// Placeholder functions - implement with actual file I/O
func writeFile(_ string, _ []byte) error { return nil }
func listFiles(_, _ string) ([]string, error) { return nil, nil }
func readFile(_ string) ([]byte, error) { return nil, nil }