diff --git a/internal/exporter/json.go b/internal/exporter/json.go index d059b04..347b089 100644 --- a/internal/exporter/json.go +++ b/internal/exporter/json.go @@ -59,14 +59,16 @@ type jsonTokens struct { } type jsonSummary struct { - TotalIterations int `json:"total_iterations"` - TotalTokens int `json:"total_tokens"` - TotalCostUSD float64 `json:"total_cost_usd"` - TotalDurationMs int64 `json:"total_duration_ms"` - TerminationReason string `json:"termination_reason"` - FilesChanged []string `json:"files_changed"` - ConvergenceRate float64 `json:"convergence_rate"` - VerifyPassRate float64 `json:"verify_pass_rate"` + TotalIterations int `json:"total_iterations"` + TotalTokens int `json:"total_tokens"` + TotalCostUSD float64 `json:"total_cost_usd"` + TotalDurationMs int64 `json:"total_duration_ms"` + TerminationReason string `json:"termination_reason"` + FilesChanged []string `json:"files_changed"` + ConvergenceRate float64 `json:"convergence_rate"` + VerifyPassRate float64 `json:"verify_pass_rate"` + IterationsToFirstSuccess int `json:"iterations_to_first_success,omitempty"` + DriftScore float64 `json:"drift_score,omitempty"` } func ExportJSON(t *trace.Trace, outputPath string) error { @@ -119,14 +121,16 @@ func ExportJSON(t *trace.Trace, outputPath string) error { if t.Summary != nil { jt.Summary = &jsonSummary{ - TotalIterations: t.Summary.TotalIterations, - TotalTokens: t.Summary.TotalTokens, - TotalCostUSD: t.Summary.TotalCostUSD, - TotalDurationMs: t.Summary.TotalDurationMs, - TerminationReason: t.Summary.TerminationReason, - FilesChanged: t.Summary.FilesChanged, - ConvergenceRate: t.Summary.ConvergenceRate, - VerifyPassRate: t.Summary.VerifyPassRate, + TotalIterations: t.Summary.TotalIterations, + TotalTokens: t.Summary.TotalTokens, + TotalCostUSD: t.Summary.TotalCostUSD, + TotalDurationMs: t.Summary.TotalDurationMs, + TerminationReason: t.Summary.TerminationReason, + FilesChanged: t.Summary.FilesChanged, + ConvergenceRate: t.Summary.ConvergenceRate, + VerifyPassRate: t.Summary.VerifyPassRate, + IterationsToFirstSuccess: t.Summary.IterationsToFirstSuccess, + DriftScore: t.Summary.DriftScore, } } diff --git a/internal/parser/ltf.go b/internal/parser/ltf.go index f19e97d..21af8ef 100644 --- a/internal/parser/ltf.go +++ b/internal/parser/ltf.go @@ -31,17 +31,17 @@ type ltfEvent struct { Metadata any `json:"metadata"` // loop_summary fields - StartedAt string `json:"started_at"` - EndedAt string `json:"ended_at"` - TotalIterations int `json:"total_iterations"` - TotalTokens int `json:"total_tokens"` - TotalCostUSD float64 `json:"total_cost_usd"` - TotalDurationMs int64 `json:"total_duration_ms"` - TerminationReason string `json:"termination_reason"` - Convergence *ltfConv `json:"convergence"` - FilesChanged []string `json:"files_changed"` - Tests *ltfTests `json:"tests"` - FileStates map[string]string `json:"file_states"` + StartedAt string `json:"started_at"` + EndedAt string `json:"ended_at"` + TotalIterations int `json:"total_iterations"` + TotalTokens int `json:"total_tokens"` + TotalCostUSD float64 `json:"total_cost_usd"` + TotalDurationMs int64 `json:"total_duration_ms"` + TerminationReason string `json:"termination_reason"` + Convergence *ltfConv `json:"convergence"` + FilesChanged []string `json:"files_changed"` + Tests *ltfTests `json:"tests"` + FileStates map[string]string `json:"file_states"` } type ltfAgent struct { @@ -174,6 +174,8 @@ func buildTraceFromLTF(events []ltfEvent, sourcePath string) (*trace.Trace, erro t.Summary.TerminationReason = summaryEvent.TerminationReason if summaryEvent.Convergence != nil { t.Summary.VerifyPassRate = summaryEvent.Convergence.VerificationPassRate + t.Summary.IterationsToFirstSuccess = summaryEvent.Convergence.IterationsToFirstSuccess + t.Summary.DriftScore = summaryEvent.Convergence.DriftScore } if len(summaryEvent.FilesChanged) > 0 { t.Summary.FilesChanged = summaryEvent.FilesChanged diff --git a/internal/parser/ltf_summary_fields_test.go b/internal/parser/ltf_summary_fields_test.go new file mode 100644 index 0000000..cfc40c0 --- /dev/null +++ b/internal/parser/ltf_summary_fields_test.go @@ -0,0 +1,49 @@ +package parser + +import ( + "testing" +) + +func TestBuildTraceFromLTF_MapsConvergenceFields(t *testing.T) { + events := []ltfEvent{ + { + Type: "iteration", + Iteration: 1, + Phase: "act", + LoopID: "loop-x", + SessionID: "sess-x", + }, + { + Type: "loop_summary", + LoopID: "loop-x", + SessionID: "sess-x", + TerminationReason: "goal_met", + Convergence: <fConv{ + IterationsToFirstSuccess: 4, + VerificationPassRate: 0.5, + DriftScore: 0.05, + }, + FilesChanged: []string{"src/auth.ts"}, + }, + } + + tr, err := buildTraceFromLTF(events, "synthetic.ltf") + if err != nil { + t.Fatalf("buildTraceFromLTF: %v", err) + } + if tr.Summary == nil { + t.Fatal("expected summary") + } + if tr.Summary.IterationsToFirstSuccess != 4 { + t.Errorf("IterationsToFirstSuccess = %d, want 4", tr.Summary.IterationsToFirstSuccess) + } + if tr.Summary.DriftScore != 0.05 { + t.Errorf("DriftScore = %v, want 0.05", tr.Summary.DriftScore) + } + if tr.Summary.VerifyPassRate != 0.5 { + t.Errorf("VerifyPassRate = %v, want 0.5", tr.Summary.VerifyPassRate) + } + if tr.Summary.TerminationReason != "goal_met" { + t.Errorf("TerminationReason = %q, want goal_met", tr.Summary.TerminationReason) + } +} diff --git a/internal/trace/types.go b/internal/trace/types.go index c2aae83..9407df4 100644 --- a/internal/trace/types.go +++ b/internal/trace/types.go @@ -70,12 +70,14 @@ func (t TokenUsage) Total() int { } type Summary struct { - TotalIterations int - TotalTokens int - TotalCostUSD float64 - TotalDurationMs int64 - TerminationReason string - FilesChanged []string - ConvergenceRate float64 - VerifyPassRate float64 + TotalIterations int + TotalTokens int + TotalCostUSD float64 + TotalDurationMs int64 + TerminationReason string + FilesChanged []string + ConvergenceRate float64 + VerifyPassRate float64 + IterationsToFirstSuccess int + DriftScore float64 }