diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go index ca7cd11..af0888f 100644 --- a/internal/engine/engine_test.go +++ b/internal/engine/engine_test.go @@ -1859,6 +1859,39 @@ func TestReportOverviewJSONIncludesIncidentTimelines(t *testing.T) { } } +func TestReportOverviewTextShowsIncidentAndAuthorityCues(t *testing.T) { + sessions := []Session{{ + Name: "risky", + Health: 45, + Metrics: Metrics{ + SourceTool: "codex_cli", + ModelUsed: "gpt-5.1", + AssistantTurns: 3, + DurationSec: 180, + GapsSec: []float64{90}, + ToolCallsOK: 1, + ToolCallsFail: 2, + ToolUsage: map[string]int{"terminal": 3}, + ToolAuthority: map[string]int{ToolAuthorityTestOrBuild: 1}, + HighestAuthority: ToolAuthorityTestOrBuild, + }, + }} + out := ReportOverview(ComputeOverview(sessions), sessions) + for _, want := range []string{ + "Incident timeline", + "Last milestone", + "Touched surface", + "Tool authority", + "Highest category: test_or_build", + "Authority category counts: test_or_build=1", + "High-authority tools: terminal", + } { + if !strings.Contains(out, want) { + t.Fatalf("text overview missing %q:\n%s", want, out) + } + } +} + func TestReportOverviewCostLabelUsesEstimatedTotalCost(t *testing.T) { sessions := []Session{{ Name: "demo", diff --git a/internal/engine/report.go b/internal/engine/report.go index 3224c39..b385517 100644 --- a/internal/engine/report.go +++ b/internal/engine/report.go @@ -1107,6 +1107,8 @@ func LoopCostSection(lc LoopCost) string { // ReportOverview generates the CLI overview dashboard text. func ReportOverview(ov Overview, sessions []Session) string { + orderedSessions := canonicalOverviewSessions(sessions) + authority := overviewAuthoritySummary(orderedSessions) sep := strings.Repeat(i18n.T("separator_double"), 70) var b strings.Builder w := func(s string) { b.WriteString(s + "\n") } @@ -1131,6 +1133,39 @@ func ReportOverview(ov Overview, sessions []Session) string { wf(" 💰 "+i18n.T("total_cost")+": $%.2f", ov.TotalCost) w("") + timelines := overviewIncidentTimelines(orderedSessions, 3) + if len(timelines) > 0 { + w(" ── " + i18n.T("incident_timeline_title") + " ──") + rendered := 0 + for _, timeline := range timelines { + for _, item := range timeline.Items { + wf(" %-30s %s: %s", textCell(timeline.Session, 30), item.Label, textCell(item.Detail, 68)) + rendered++ + if rendered >= 5 { + break + } + } + if rendered >= 5 { + break + } + } + w("") + } + + if authority.HasData { + w(" ── " + i18n.T("report_tool_authority") + " ──") + if authority.Highest != "" { + wf(" %s: %s", i18n.T("report_highest_authority"), authority.Highest) + } + if len(authority.Counts) > 0 { + wf(" %s: %s", i18n.T("report_authority_category_counts"), textAuthorityCounts(authority.Counts)) + } + if len(authority.HighTools) > 0 { + wf(" %s: %s", i18n.T("report_high_authority_tools"), textCell(strings.Join(authority.HighTools, ", "), 68)) + } + w("") + } + // By agent w(" ── " + i18n.T("overview_agents") + " ──") type akv struct { @@ -1192,3 +1227,19 @@ func ReportOverview(ov Overview, sessions []Session) string { w(sep) return b.String() } + +func textAuthorityCounts(items []authorityCount) string { + parts := make([]string, 0, len(items)) + for _, item := range items { + parts = append(parts, fmt.Sprintf("%s=%d", item.Category, item.Count)) + } + return strings.Join(parts, ", ") +} + +func textCell(value string, limit int) string { + value = strings.Join(strings.Fields(value), " ") + if limit > 3 && len(value) > limit { + return value[:limit-3] + "..." + } + return value +} diff --git a/scripts/ci/check-report-semantics.sh b/scripts/ci/check-report-semantics.sh index 277a54c..5ce8b0f 100755 --- a/scripts/ci/check-report-semantics.sh +++ b/scripts/ci/check-report-semantics.sh @@ -25,6 +25,12 @@ grep -q "AGENTTRACE v$version" "$out_dir/semantics/overview.txt" \ || fail "text overview missing current version" grep -q "$expected_cost_label" "$out_dir/semantics/overview.txt" \ || fail "text overview missing expected cost label" +grep -q "Incident timeline" "$out_dir/semantics/overview.txt" \ + || fail "text overview missing incident timeline evidence" +grep -q "Tool authority" "$out_dir/semantics/overview.txt" \ + || fail "text overview missing tool authority summary" +grep -q "test_or_build" "$out_dir/semantics/overview.txt" \ + || fail "text overview missing highest demo authority category" grep -q "| $expected_cost_label |" "$out_dir/semantics/overview.md" \ || fail "markdown overview missing expected cost label" grep -q "## Tool authority" "$out_dir/semantics/overview.md" \