diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go
index b3cd8be..ca7cd11 100644
--- a/internal/engine/engine_test.go
+++ b/internal/engine/engine_test.go
@@ -1955,6 +1955,53 @@ func TestReportTextChineseLabelsAnomalySeverityAndLoopCost(t *testing.T) {
}
}
+func TestReportOverviewMarkdownShowsAuthority(t *testing.T) {
+ sessions := []Session{
+ {
+ Name: "build",
+ Health: 92,
+ Metrics: Metrics{
+ SourceTool: "aider",
+ ModelUsed: "gpt-4.1",
+ ToolCallsOK: 1,
+ ToolUsage: map[string]int{"go test": 1},
+ ToolAuthority: map[string]int{ToolAuthorityTestOrBuild: 1},
+ HighestAuthority: ToolAuthorityTestOrBuild,
+ },
+ },
+ {
+ Name: "shell",
+ Health: 70,
+ Metrics: Metrics{
+ SourceTool: "cursor",
+ ModelUsed: "default",
+ ToolCallsOK: 1,
+ ToolUsage: map[string]int{"bash|prod\nsecret args": 1},
+ ToolAuthority: map[string]int{ToolAuthorityShellExec: 1},
+ HighestAuthority: ToolAuthorityShellExec,
+ },
+ },
+ }
+ out := ReportOverviewMarkdown(ComputeOverview(sessions), sessions)
+ for _, want := range []string{
+ "## Tool authority",
+ "| Highest category | `shell_exec` |",
+ "| High-authority tools | `bash\\|prod
secret args` |",
+ "### Authority category counts",
+ "| `test_or_build` | 1 |",
+ "| `shell_exec` | 1 |",
+ } {
+ if !strings.Contains(out, want) {
+ t.Fatalf("markdown report missing %q:\n%s", want, out)
+ }
+ }
+ for _, unwanted := range []string{"`bash|prod", "secret args` |\n|"} {
+ if strings.Contains(out, unwanted) {
+ t.Fatalf("markdown report did not escape high-authority tool name %q:\n%s", unwanted, out)
+ }
+ }
+}
+
func TestReportOverviewHTMLIsShareableAndEscaped(t *testing.T) {
sessions := []Session{
{
diff --git a/internal/engine/report.go b/internal/engine/report.go
index 537b28c..3224c39 100644
--- a/internal/engine/report.go
+++ b/internal/engine/report.go
@@ -530,6 +530,7 @@ func ReportOverviewJSON(ov Overview, sessions []Session) string {
func ReportOverviewMarkdown(ov Overview, sessions []Session) string {
orderedSessions := canonicalOverviewSessions(sessions)
summary := overviewReportSummary(orderedSessions)
+ authority := overviewAuthoritySummary(orderedSessions)
trend := AnalyzeHealthTrend(orderedSessions)
var b strings.Builder
@@ -543,6 +544,25 @@ func ReportOverviewMarkdown(ov Overview, sessions []Session) string {
fmt.Fprintf(&b, "| %s | %d |\n", i18n.T("report_total_tokens"), summary.TotalTokens)
fmt.Fprintf(&b, "| %s | %d / %d (%.1f%%) |\n\n", i18n.T("report_tool_failures"), summary.FailedTools, summary.TotalTools, summary.ToolFailRate)
+ if authority.HasData {
+ fmt.Fprintf(&b, "## %s\n\n", i18n.T("report_tool_authority"))
+ fmt.Fprintf(&b, "| %s | %s |\n|---|---:|\n", i18n.T("report_metric"), i18n.T("report_value"))
+ if authority.Highest != "" {
+ fmt.Fprintf(&b, "| %s | `%s` |\n", i18n.T("report_highest_authority"), markdownInlineCode(authority.Highest))
+ }
+ if len(authority.HighTools) > 0 {
+ fmt.Fprintf(&b, "| %s | %s |\n", i18n.T("report_high_authority_tools"), reportMarkdownCodeList(authority.HighTools))
+ }
+ if len(authority.Counts) > 0 {
+ fmt.Fprintf(&b, "\n### %s\n\n", i18n.T("report_authority_category_counts"))
+ fmt.Fprintf(&b, "| %s | %s |\n|---|---:|\n", i18n.T("report_authority_category"), i18n.T("report_count"))
+ for _, item := range authority.Counts {
+ fmt.Fprintf(&b, "| `%s` | %d |\n", markdownInlineCode(item.Category), item.Count)
+ }
+ fmt.Fprintln(&b)
+ }
+ }
+
fmt.Fprintf(&b, "## %s\n\n", i18n.T("incident_timeline_title"))
timelines := overviewIncidentTimelines(orderedSessions, 6)
if len(timelines) == 0 {
@@ -1023,6 +1043,23 @@ func reportHTMLCodeList(values []string) string {
return strings.Join(parts, ", ")
}
+func reportMarkdownCodeList(values []string) string {
+ parts := make([]string, 0, len(values))
+ for _, value := range values {
+ if value != "" {
+ parts = append(parts, "`"+markdownInlineCode(value)+"`")
+ }
+ }
+ return strings.Join(parts, ", ")
+}
+
+func markdownInlineCode(value string) string {
+ value = strings.ReplaceAll(value, "`", "'")
+ value = strings.ReplaceAll(value, "|", "\\|")
+ value = strings.ReplaceAll(value, "\n", "
")
+ return value
+}
+
func markdownCell(value string) string {
value = strings.ReplaceAll(value, "|", "\\|")
value = strings.ReplaceAll(value, "\n", "
")
diff --git a/scripts/ci/check-report-semantics.sh b/scripts/ci/check-report-semantics.sh
index f0fa540..277a54c 100755
--- a/scripts/ci/check-report-semantics.sh
+++ b/scripts/ci/check-report-semantics.sh
@@ -27,6 +27,12 @@ grep -q "$expected_cost_label" "$out_dir/semantics/overview.txt" \
|| fail "text overview missing expected cost label"
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" \
+ || fail "markdown overview missing tool authority summary"
+grep -q "### Authority category counts" "$out_dir/semantics/overview.md" \
+ || fail "markdown overview missing authority category counts"
+grep -q '`test_or_build`' "$out_dir/semantics/overview.md" \
+ || fail "markdown overview missing highest demo authority category"
grep -q "$expected_cost_label" "$out_dir/semantics/overview.html" \
|| fail "html overview missing expected cost label"
grep -q "