Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
876 changes: 0 additions & 876 deletions pkg/cli/audit_report_render.go

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions pkg/cli/audit_report_render_performance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// This file provides command-line interface functionality for gh-aw.
// This file (audit_report_render_performance.go) contains console rendering functions
// for performance metrics, token usage, and GitHub rate limit sections of the audit report.

package cli

import (
"fmt"
"os"
"strconv"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/timeutil"
)

// renderPerformanceMetrics renders performance metrics
func renderPerformanceMetrics(metrics *PerformanceMetrics) {
auditReportLog.Printf("Rendering performance metrics: tokens_per_min=%.1f, cost_efficiency=%s, most_used_tool=%s",
metrics.TokensPerMinute, metrics.CostEfficiency, metrics.MostUsedTool)
if metrics.TokensPerMinute > 0 {
fmt.Fprintf(os.Stderr, " Tokens per Minute: %.1f\n", metrics.TokensPerMinute)
}

if metrics.CostEfficiency != "" {
efficiencyDisplay := metrics.CostEfficiency
switch metrics.CostEfficiency {
case "excellent", "good":
efficiencyDisplay = console.FormatSuccessMessage(metrics.CostEfficiency)
case "moderate":
efficiencyDisplay = console.FormatWarningMessage(metrics.CostEfficiency)
case "poor":
efficiencyDisplay = console.FormatErrorMessage(metrics.CostEfficiency)
}
fmt.Fprintf(os.Stderr, " Cost Efficiency: %s\n", efficiencyDisplay)
}

if metrics.AvgToolDuration != "" {
fmt.Fprintf(os.Stderr, " Average Tool Duration: %s\n", metrics.AvgToolDuration)
}

if metrics.MostUsedTool != "" {
fmt.Fprintf(os.Stderr, " Most Used Tool: %s\n", metrics.MostUsedTool)
}

if metrics.NetworkRequests > 0 {
fmt.Fprintf(os.Stderr, " Network Requests: %d\n", metrics.NetworkRequests)
}

fmt.Fprintln(os.Stderr)
}

// renderTokenUsage displays token usage data from the firewall proxy
func renderTokenUsage(summary *TokenUsageSummary) {
totalTokens := summary.TotalTokens()
cacheTokens := summary.TotalCacheReadTokens + summary.TotalCacheWriteTokens

fmt.Fprintf(os.Stderr, " Total: %s tokens (%s input, %s output, %s cache)\n",
console.FormatNumber(totalTokens),
console.FormatNumber(summary.TotalInputTokens),
console.FormatNumber(summary.TotalOutputTokens),
console.FormatNumber(cacheTokens))
fmt.Fprintf(os.Stderr, " Requests: %d (avg %s)\n",
summary.TotalRequests, timeutil.FormatDurationMs(summary.AvgDurationMs()))
if summary.CacheEfficiency > 0 {
fmt.Fprintf(os.Stderr, " Cache hit: %.1f%%\n", summary.CacheEfficiency*100)
}
fmt.Fprintln(os.Stderr)

rows := summary.ModelRows()
if len(rows) > 0 {
config := console.TableConfig{
Headers: []string{"Model", "Provider", "Input", "Output", "Cache Read", "Cache Write", "Requests", "Avg Duration"},
Rows: make([][]string, 0, len(rows)),
}
for _, row := range rows {
config.Rows = append(config.Rows, []string{
row.Model,
row.Provider,
console.FormatNumber(row.InputTokens),
console.FormatNumber(row.OutputTokens),
console.FormatNumber(row.CacheReadTokens),
console.FormatNumber(row.CacheWriteTokens),
strconv.Itoa(row.Requests),
row.AvgDuration,
})
}
fmt.Fprint(os.Stderr, console.RenderTable(config))
fmt.Fprintln(os.Stderr)
}
}

// renderGitHubRateLimitUsage displays GitHub API quota consumption for the run.
func renderGitHubRateLimitUsage(usage *GitHubRateLimitUsage) {
if usage == nil {
return
}

// Summary line
summary := "Total GitHub API calls: " + console.FormatNumber(usage.TotalRequestsMade)
if usage.CoreLimit > 0 {
summary += fmt.Sprintf(" | Core quota consumed: %s / %s (remaining: %s)",
console.FormatNumber(usage.CoreConsumed),
console.FormatNumber(usage.CoreLimit),
console.FormatNumber(usage.CoreRemaining),
)
}
fmt.Fprintf(os.Stderr, " %s\n\n", summary)

// Per-resource breakdown table (only when there are multiple resources or non-core resources)
rows := usage.ResourceRows()
if len(rows) == 0 {
return
}
cfg := console.TableConfig{
Headers: []string{"Resource", "API Calls", "Quota Consumed", "Remaining", "Limit"},
Rows: make([][]string, 0, len(rows)),
}
for _, row := range rows {
cfg.Rows = append(cfg.Rows, []string{
row.Resource,
console.FormatNumber(row.RequestsMade),
console.FormatNumber(row.QuotaConsumed),
console.FormatNumber(row.FinalRemaining),
console.FormatNumber(row.Limit),
})
}
fmt.Fprint(os.Stderr, console.RenderTable(cfg))
fmt.Fprintln(os.Stderr)
}
247 changes: 247 additions & 0 deletions pkg/cli/audit_report_render_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// This file provides command-line interface functionality for gh-aw.
// This file (audit_report_render_policy.go) contains console rendering functions
// for policy analysis, firewall analysis, safe output summaries, and guard policy
// sections of the audit report.

package cli

import (
"fmt"
"math"
"os"
"sort"
"strconv"
"time"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/sliceutil"
"github.com/github/gh-aw/pkg/stringutil"
)

// formatUnixTimestamp converts a Unix timestamp (float64) to a human-readable time string (HH:MM:SS).
func formatUnixTimestamp(ts float64) string {
if ts <= 0 {
return "-"
}
sec := int64(math.Floor(ts))
nsec := int64((ts - float64(sec)) * 1e9)
t := time.Unix(sec, nsec).UTC()
return t.Format("15:04:05")
}

// renderGuardPolicySummary renders the guard policy enforcement summary
func renderGuardPolicySummary(summary *GuardPolicySummary) {
auditReportLog.Printf("Rendering guard policy summary: %d total blocked", summary.TotalBlocked)

fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(
fmt.Sprintf("Guard Policy: %d tool call(s) blocked", summary.TotalBlocked)))
fmt.Fprintln(os.Stderr)

// Breakdown by reason
fmt.Fprintln(os.Stderr, " Block Reasons:")
if summary.IntegrityBlocked > 0 {
fmt.Fprintf(os.Stderr, " Integrity below minimum : %d\n", summary.IntegrityBlocked)
}
if summary.RepoScopeBlocked > 0 {
fmt.Fprintf(os.Stderr, " Repository not allowed : %d\n", summary.RepoScopeBlocked)
}
if summary.AccessDenied > 0 {
fmt.Fprintf(os.Stderr, " Access denied : %d\n", summary.AccessDenied)
}
if summary.BlockedUserDenied > 0 {
fmt.Fprintf(os.Stderr, " Blocked user : %d\n", summary.BlockedUserDenied)
}
if summary.PermissionDenied > 0 {
fmt.Fprintf(os.Stderr, " Insufficient permissions: %d\n", summary.PermissionDenied)
}
if summary.PrivateRepoDenied > 0 {
fmt.Fprintf(os.Stderr, " Private repo denied : %d\n", summary.PrivateRepoDenied)
}
fmt.Fprintln(os.Stderr)

// Most frequently blocked tools
if len(summary.BlockedToolCounts) > 0 {
toolNames := sliceutil.MapToSlice(summary.BlockedToolCounts)
sort.Slice(toolNames, func(i, j int) bool {
return summary.BlockedToolCounts[toolNames[i]] > summary.BlockedToolCounts[toolNames[j]]
})

toolRows := make([][]string, 0, len(toolNames))
for _, name := range toolNames {
toolRows = append(toolRows, []string{name, strconv.Itoa(summary.BlockedToolCounts[name])})
}
fmt.Fprint(os.Stderr, console.RenderTable(console.TableConfig{
Title: "Most Blocked Tools",
Headers: []string{"Tool", "Blocked"},
Rows: toolRows,
}))
}

// Guard policy event details
if len(summary.Events) > 0 {
fmt.Fprintln(os.Stderr)
eventRows := make([][]string, 0, len(summary.Events))
for _, evt := range summary.Events {
message := evt.Message
if len(message) > 60 {
message = message[:57] + "..."
}
repo := evt.Repository
if repo == "" {
repo = "-"
}
eventRows = append(eventRows, []string{
stringutil.Truncate(evt.ServerID, 20),
stringutil.Truncate(evt.ToolName, 25),
evt.Reason,
message,
repo,
})
}
fmt.Fprint(os.Stderr, console.RenderTable(console.TableConfig{
Title: "Guard Policy Events",
Headers: []string{"Server", "Tool", "Reason", "Message", "Repository"},
Rows: eventRows,
}))
}
}

// renderFirewallAnalysis renders firewall analysis with summary and domain breakdown
func renderFirewallAnalysis(analysis *FirewallAnalysis) {
auditReportLog.Printf("Rendering firewall analysis: total=%d, allowed=%d, blocked=%d, allowed_domains=%d, blocked_domains=%d",
analysis.TotalRequests, analysis.AllowedRequests, analysis.BlockedRequests, len(analysis.AllowedDomains), len(analysis.BlockedDomains))
// Summary statistics
fmt.Fprintf(os.Stderr, " Total Requests : %d\n", analysis.TotalRequests)
fmt.Fprintf(os.Stderr, " Allowed : %d\n", analysis.AllowedRequests)
fmt.Fprintf(os.Stderr, " Blocked : %d\n", analysis.BlockedRequests)
fmt.Fprintln(os.Stderr)

// Allowed domains
if len(analysis.AllowedDomains) > 0 {
fmt.Fprintln(os.Stderr, " Allowed Domains:")
for _, domain := range analysis.AllowedDomains {
if stats, ok := analysis.RequestsByDomain[domain]; ok {
fmt.Fprintf(os.Stderr, " ✓ %s (%d requests)\n", domain, stats.Allowed)
}
}
fmt.Fprintln(os.Stderr)
}

// Blocked domains
if len(analysis.BlockedDomains) > 0 {
fmt.Fprintln(os.Stderr, " Blocked Domains:")
for _, domain := range analysis.BlockedDomains {
if stats, ok := analysis.RequestsByDomain[domain]; ok {
fmt.Fprintf(os.Stderr, " ✗ %s (%d requests)\n", domain, stats.Blocked)
}
}
fmt.Fprintln(os.Stderr)
}
}

// renderRedactedDomainsAnalysis renders redacted domains analysis
func renderRedactedDomainsAnalysis(analysis *RedactedDomainsAnalysis) {
auditReportLog.Printf("Rendering redacted domains analysis: total_domains=%d", analysis.TotalDomains)
// Summary statistics
fmt.Fprintf(os.Stderr, " Total Domains Redacted: %d\n", analysis.TotalDomains)
fmt.Fprintln(os.Stderr)

// List domains
if len(analysis.Domains) > 0 {
fmt.Fprintln(os.Stderr, " Redacted Domains:")
for _, domain := range analysis.Domains {
fmt.Fprintf(os.Stderr, " 🔒 %s\n", domain)
}
fmt.Fprintln(os.Stderr)
}
}

// renderPolicyAnalysis renders the enriched firewall policy analysis with rule attribution
func renderPolicyAnalysis(analysis *PolicyAnalysis) {
auditReportLog.Printf("Rendering policy analysis: rules=%d, denied=%d", len(analysis.RuleHits), analysis.DeniedCount)

// Policy summary using RenderStruct
display := PolicySummaryDisplay{
Policy: analysis.PolicySummary,
TotalRequests: analysis.TotalRequests,
Allowed: analysis.AllowedCount,
Denied: analysis.DeniedCount,
UniqueDomains: analysis.UniqueDomains,
}
fmt.Fprint(os.Stderr, console.RenderStruct(display))
fmt.Fprintln(os.Stderr)

// Rule hit table
if len(analysis.RuleHits) > 0 {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Policy Rules:"))
fmt.Fprintln(os.Stderr)

ruleConfig := console.TableConfig{
Headers: []string{"Rule", "Action", "Description", "Hits"},
Rows: make([][]string, 0, len(analysis.RuleHits)),
}

for _, rh := range analysis.RuleHits {
row := []string{
stringutil.Truncate(rh.Rule.ID, 30),
rh.Rule.Action,
stringutil.Truncate(rh.Rule.Description, 50),
strconv.Itoa(rh.Hits),
}
ruleConfig.Rows = append(ruleConfig.Rows, row)
}

fmt.Fprint(os.Stderr, console.RenderTable(ruleConfig))
fmt.Fprintln(os.Stderr)
}

// Denied requests detail
if len(analysis.DeniedRequests) > 0 {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Denied Requests (%d):", len(analysis.DeniedRequests))))
fmt.Fprintln(os.Stderr)

deniedConfig := console.TableConfig{
Headers: []string{"Time", "Domain", "Rule", "Reason"},
Rows: make([][]string, 0, len(analysis.DeniedRequests)),
}

for _, req := range analysis.DeniedRequests {
timeStr := formatUnixTimestamp(req.Timestamp)
row := []string{
timeStr,
stringutil.Truncate(req.Host, 40),
stringutil.Truncate(req.RuleID, 25),
stringutil.Truncate(req.Reason, 40),
}
deniedConfig.Rows = append(deniedConfig.Rows, row)
}

fmt.Fprint(os.Stderr, console.RenderTable(deniedConfig))
fmt.Fprintln(os.Stderr)
}
}

// renderSafeOutputSummary renders safe output summary with type breakdown
func renderSafeOutputSummary(summary *SafeOutputSummary) {
if summary == nil {
return
}
fmt.Fprintf(os.Stderr, " Total Items: %d\n", summary.TotalItems)
fmt.Fprintf(os.Stderr, " Summary: %s\n", summary.Summary)
fmt.Fprintln(os.Stderr)

// Type breakdown table
if len(summary.TypeDetails) > 0 {
config := console.TableConfig{
Headers: []string{"Type", "Count"},
Rows: make([][]string, 0, len(summary.TypeDetails)),
}
for _, detail := range summary.TypeDetails {
row := []string{detail.Type, strconv.Itoa(detail.Count)}
config.Rows = append(config.Rows, row)
}
fmt.Fprint(os.Stderr, console.RenderTable(config))
fmt.Fprintln(os.Stderr)
}
}
Loading
Loading