135 lines
4.3 KiB
Go
135 lines
4.3 KiB
Go
package memorycompiler
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// StrategyReport summarizes one strategy's learned outcome counters, split by
|
|
// whether the compiled contract was provider-visible that turn.
|
|
type StrategyReport struct {
|
|
ID string
|
|
Description string
|
|
Successes int
|
|
Failures int
|
|
InjectedSuccesses int
|
|
InjectedFailures int
|
|
LastUsedAt time.Time
|
|
}
|
|
|
|
func (s StrategyReport) samples() int { return s.Successes + s.Failures }
|
|
func (s StrategyReport) injectedSamples() int { return s.InjectedSuccesses + s.InjectedFailures }
|
|
|
|
// LearningsReport is a read-only, human-readable snapshot of what Memory v5
|
|
// has learned for one project. It exposes local state only; nothing here is
|
|
// sent to a provider.
|
|
type LearningsReport struct {
|
|
UpdatedAt time.Time
|
|
Strategies []StrategyReport
|
|
NoisePatterns []string
|
|
Improvements []string
|
|
Findings []string
|
|
Nodes int
|
|
HighSignalNodes int
|
|
}
|
|
|
|
// LearningsReport loads the project's learned state. ok is false when nothing
|
|
// has been learned yet. maxItems bounds each list; <=0 uses a default.
|
|
func (r *Runtime) LearningsReport(maxItems int) (LearningsReport, bool) {
|
|
if r == nil {
|
|
return LearningsReport{}, false
|
|
}
|
|
if maxItems <= 0 {
|
|
maxItems = 8
|
|
}
|
|
st := r.loadState()
|
|
rep := LearningsReport{UpdatedAt: st.UpdatedAt, Nodes: len(st.Nodes)}
|
|
for _, node := range st.Nodes {
|
|
if node.Quality == QualityHighSignal {
|
|
rep.HighSignalNodes++
|
|
}
|
|
}
|
|
for _, s := range st.Strategies {
|
|
if s.Samples() == 0 {
|
|
continue
|
|
}
|
|
rep.Strategies = append(rep.Strategies, StrategyReport{
|
|
ID: s.ID,
|
|
Description: s.Description,
|
|
Successes: s.Successes,
|
|
Failures: s.Failures,
|
|
InjectedSuccesses: s.InjectedSuccesses,
|
|
InjectedFailures: s.InjectedFailures,
|
|
LastUsedAt: s.LastUsedAt,
|
|
})
|
|
}
|
|
sort.Slice(rep.Strategies, func(i, j int) bool {
|
|
if rep.Strategies[i].samples() == rep.Strategies[j].samples() {
|
|
return rep.Strategies[i].ID < rep.Strategies[j].ID
|
|
}
|
|
return rep.Strategies[i].samples() > rep.Strategies[j].samples()
|
|
})
|
|
for _, ref := range sortedNoisyRefs(st.NoisyRefs) {
|
|
rep.NoisePatterns = append(rep.NoisePatterns, fmt.Sprintf("%s (x%d)", ref.ref, ref.count))
|
|
}
|
|
rep.NoisePatterns = limitStrings(rep.NoisePatterns, maxItems)
|
|
for _, l := range recentLearnings(st.Learnings, maxItems) {
|
|
rep.Improvements = append(rep.Improvements, l.CompilerImprovements...)
|
|
rep.Findings = append(rep.Findings, l.CausalFindings...)
|
|
}
|
|
rep.Improvements = limitStrings(dedupeStrings(rep.Improvements), maxItems)
|
|
rep.Findings = limitStrings(dedupeStrings(rep.Findings), maxItems)
|
|
ok := len(rep.Strategies)+len(rep.NoisePatterns)+len(rep.Improvements)+len(rep.Findings) > 0
|
|
return rep, ok
|
|
}
|
|
|
|
// FormatLearningsReport renders the report as plain notice text for the CLI and
|
|
// desktop slash-command surfaces.
|
|
func FormatLearningsReport(rep LearningsReport) string {
|
|
var b strings.Builder
|
|
b.WriteString("memory-v5 learnings")
|
|
if !rep.UpdatedAt.IsZero() {
|
|
fmt.Fprintf(&b, " (updated %s)", rep.UpdatedAt.UTC().Format("2006-01-02 15:04 UTC"))
|
|
}
|
|
if rep.Nodes > 0 {
|
|
fmt.Fprintf(&b, "\nmemory nodes: %d (%d high-signal)", rep.Nodes, rep.HighSignalNodes)
|
|
}
|
|
if len(rep.Strategies) > 0 {
|
|
b.WriteString("\nstrategies:")
|
|
for _, s := range rep.Strategies {
|
|
fmt.Fprintf(&b, "\n %s: %s", s.ID, formatOutcomeSplit(s.Successes, s.Failures))
|
|
if s.injectedSamples() > 0 {
|
|
observedOK := s.Successes - s.InjectedSuccesses
|
|
observedFail := s.Failures - s.InjectedFailures
|
|
fmt.Fprintf(&b, " | injected %s vs observed %s",
|
|
formatOutcomeSplit(s.InjectedSuccesses, s.InjectedFailures),
|
|
formatOutcomeSplit(observedOK, observedFail))
|
|
}
|
|
}
|
|
}
|
|
writeReportList(&b, "repeated noise patterns", rep.NoisePatterns)
|
|
writeReportList(&b, "recent improvements", rep.Improvements)
|
|
writeReportList(&b, "recent findings", rep.Findings)
|
|
return b.String()
|
|
}
|
|
|
|
func formatOutcomeSplit(ok, fail int) string {
|
|
total := ok + fail
|
|
if total == 0 {
|
|
return "0 samples"
|
|
}
|
|
return fmt.Sprintf("%d ok / %d fail (%d%%)", ok, fail, int(float64(ok)/float64(total)*100+0.5))
|
|
}
|
|
|
|
func writeReportList(b *strings.Builder, title string, items []string) {
|
|
if len(items) == 0 {
|
|
return
|
|
}
|
|
b.WriteString("\n" + title + ":")
|
|
for _, item := range items {
|
|
b.WriteString("\n - " + summarizeText(item, 160))
|
|
}
|
|
}
|