From 6d9015c74645ac851aacc1277b5d3369528a89df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 03:35:10 +0000 Subject: [PATCH 1/4] Initial plan From e578ba9353e3e1a0cdb379bfa03e3e75cd59e5c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 03:54:06 +0000 Subject: [PATCH 2/4] Extend time delta parsing to support negative relative times Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/time_delta.go | 102 +++++++++++++---- pkg/workflow/time_delta_test.go | 187 ++++++++++++++++++++++++++++---- 2 files changed, 246 insertions(+), 43 deletions(-) diff --git a/pkg/workflow/time_delta.go b/pkg/workflow/time_delta.go index c71f5041260..aa8e53ee110 100644 --- a/pkg/workflow/time_delta.go +++ b/pkg/workflow/time_delta.go @@ -13,30 +13,42 @@ type TimeDelta struct { Hours int Days int Minutes int + Sign int // 1 for positive (future), -1 for negative (past) } -// parseTimeDelta parses a relative time delta string like "+25h", "+3d", "+1d12h30m", etc. +// parseTimeDelta parses a relative time delta string like "+25h", "-24h", "+3d", "-1d12h30m", etc. // Supported formats: -// - +25h (25 hours) -// - +3d (3 days) -// - +30m (30 minutes) -// - +1d12h (1 day and 12 hours) -// - +2d5h30m (2 days, 5 hours, 30 minutes) +// - +25h (25 hours in the future) +// - -24h (24 hours in the past) +// - +3d (3 days in the future) +// - -3d (3 days in the past) +// - +30m (30 minutes in the future) +// - -30m (30 minutes in the past) +// - +1d12h (1 day and 12 hours in the future) +// - -1d12h30m (1 day, 12 hours, 30 minutes in the past) func parseTimeDelta(deltaStr string) (*TimeDelta, error) { if deltaStr == "" { return nil, fmt.Errorf("empty time delta") } - // Must start with '+' - if !strings.HasPrefix(deltaStr, "+") { - return nil, fmt.Errorf("time delta must start with '+', got: %s", deltaStr) + var sign int + var prefix string + + // Determine sign and remove prefix + if strings.HasPrefix(deltaStr, "+") { + sign = 1 + prefix = "+" + deltaStr = deltaStr[1:] + } else if strings.HasPrefix(deltaStr, "-") { + sign = -1 + prefix = "-" + deltaStr = deltaStr[1:] + } else { + return nil, fmt.Errorf("time delta must start with '+' or '-', got: %s", deltaStr) } - // Remove the '+' prefix - deltaStr = deltaStr[1:] - if deltaStr == "" { - return nil, fmt.Errorf("empty time delta after '+'") + return nil, fmt.Errorf("empty time delta after '%s'", prefix) } // Parse components using regex @@ -45,7 +57,7 @@ func parseTimeDelta(deltaStr string) (*TimeDelta, error) { matches := pattern.FindAllStringSubmatch(deltaStr, -1) if len(matches) == 0 { - return nil, fmt.Errorf("invalid time delta format: +%s. Expected format like +25h, +3d, +1d12h30m", deltaStr) + return nil, fmt.Errorf("invalid time delta format: %s%s. Expected format like %s25h, %s3d, %s1d12h30m", prefix, deltaStr, prefix, prefix, prefix) } // Check that all characters are consumed by matches @@ -54,10 +66,10 @@ func parseTimeDelta(deltaStr string) (*TimeDelta, error) { consumed += len(match[0]) } if consumed != len(deltaStr) { - return nil, fmt.Errorf("invalid time delta format: +%s. Extra characters detected", deltaStr) + return nil, fmt.Errorf("invalid time delta format: %s%s. Extra characters detected", prefix, deltaStr) } - delta := &TimeDelta{} + delta := &TimeDelta{Sign: sign} seenUnits := make(map[string]bool) for _, match := range matches { @@ -70,17 +82,17 @@ func parseTimeDelta(deltaStr string) (*TimeDelta, error) { // Check for duplicate units if seenUnits[unit] { - return nil, fmt.Errorf("duplicate unit '%s' in time delta: +%s", unit, deltaStr) + return nil, fmt.Errorf("duplicate unit '%s' in time delta: %s%s", unit, prefix, deltaStr) } seenUnits[unit] = true value, err := strconv.Atoi(valueStr) if err != nil { - return nil, fmt.Errorf("invalid number '%s' in time delta: +%s", valueStr, deltaStr) + return nil, fmt.Errorf("invalid number '%s' in time delta: %s%s", valueStr, prefix, deltaStr) } if value < 0 { - return nil, fmt.Errorf("negative values not allowed in time delta: +%s", deltaStr) + return nil, fmt.Errorf("negative values not allowed in time delta: %s%s", prefix, deltaStr) } switch unit { @@ -91,7 +103,7 @@ func parseTimeDelta(deltaStr string) (*TimeDelta, error) { case "m": delta.Minutes = value default: - return nil, fmt.Errorf("unsupported time unit '%s' in time delta: +%s", unit, deltaStr) + return nil, fmt.Errorf("unsupported time unit '%s' in time delta: %s%s", unit, prefix, deltaStr) } } @@ -114,7 +126,7 @@ func (td *TimeDelta) toDuration() time.Duration { duration := time.Duration(td.Days) * 24 * time.Hour duration += time.Duration(td.Hours) * time.Hour duration += time.Duration(td.Minutes) * time.Minute - return duration + return time.Duration(td.Sign) * duration } // String returns a human-readable representation of the TimeDelta @@ -132,7 +144,12 @@ func (td *TimeDelta) String() string { if len(parts) == 0 { return "0m" } - return "+" + strings.Join(parts, "") + + prefix := "+" + if td.Sign < 0 { + prefix = "-" + } + return prefix + strings.Join(parts, "") } // isRelativeStopTime checks if a stop-time value is a relative time delta @@ -140,6 +157,11 @@ func isRelativeStopTime(stopTime string) bool { return strings.HasPrefix(stopTime, "+") } +// isRelativeTime checks if a time value is a relative time delta (supports both + and -) +func isRelativeTime(timeStr string) bool { + return strings.HasPrefix(timeStr, "+") || strings.HasPrefix(timeStr, "-") +} + // parseAbsoluteDateTime parses various date-time formats and returns a standardized timestamp func parseAbsoluteDateTime(dateTimeStr string) (string, error) { // Try multiple date-time formats in order of preference @@ -263,3 +285,39 @@ func resolveStopTime(stopTime string, compilationTime time.Time) (string, error) // Parse absolute date-time with flexible format support return parseAbsoluteDateTime(stopTime) } + +// resolveRelativeTime resolves a relative or absolute time value to an absolute timestamp +// If the time is relative (starts with '+' or '-'), it calculates the absolute time +// from the reference time. Otherwise, it parses the absolute time using various formats. +// Returns the time as a time.Time object instead of a string. +func resolveRelativeTime(timeStr string, referenceTime time.Time) (time.Time, error) { + if timeStr == "" { + return time.Time{}, fmt.Errorf("empty time value") + } + + if isRelativeTime(timeStr) { + // Parse the relative time delta + delta, err := parseTimeDelta(timeStr) + if err != nil { + return time.Time{}, err + } + + // Calculate absolute time in UTC + absoluteTime := referenceTime.UTC().Add(delta.toDuration()) + return absoluteTime, nil + } + + // Parse absolute date-time with flexible format support + absoluteTimeStr, err := parseAbsoluteDateTime(timeStr) + if err != nil { + return time.Time{}, err + } + + // Convert back to time.Time + parsedTime, err := time.Parse("2006-01-02 15:04:05", absoluteTimeStr) + if err != nil { + return time.Time{}, fmt.Errorf("failed to parse resolved time: %w", err) + } + + return parsedTime.UTC(), nil +} diff --git a/pkg/workflow/time_delta_test.go b/pkg/workflow/time_delta_test.go index ac472d6fda6..3f8bf72e6f8 100644 --- a/pkg/workflow/time_delta_test.go +++ b/pkg/workflow/time_delta_test.go @@ -17,47 +17,63 @@ func TestParseTimeDelta(t *testing.T) { { name: "hours only", input: "+25h", - expected: &TimeDelta{Hours: 25}, + expected: &TimeDelta{Hours: 25, Sign: 1}, }, { name: "days only", input: "+3d", - expected: &TimeDelta{Days: 3}, + expected: &TimeDelta{Days: 3, Sign: 1}, }, { name: "minutes only", input: "+30m", - expected: &TimeDelta{Minutes: 30}, + expected: &TimeDelta{Minutes: 30, Sign: 1}, }, { name: "days and hours", input: "+1d12h", - expected: &TimeDelta{Days: 1, Hours: 12}, + expected: &TimeDelta{Days: 1, Hours: 12, Sign: 1}, }, { name: "all units", input: "+2d5h30m", - expected: &TimeDelta{Days: 2, Hours: 5, Minutes: 30}, + expected: &TimeDelta{Days: 2, Hours: 5, Minutes: 30, Sign: 1}, }, { name: "different order", input: "+5h2d30m", - expected: &TimeDelta{Days: 2, Hours: 5, Minutes: 30}, + expected: &TimeDelta{Days: 2, Hours: 5, Minutes: 30, Sign: 1}, }, { name: "single digit", input: "+1d", - expected: &TimeDelta{Days: 1}, + expected: &TimeDelta{Days: 1, Sign: 1}, }, { name: "large numbers", input: "+100h", - expected: &TimeDelta{Hours: 100}, + expected: &TimeDelta{Hours: 100, Sign: 1}, }, { name: "zero values allowed in middle", input: "+0d5h", - expected: &TimeDelta{Days: 0, Hours: 5}, + expected: &TimeDelta{Days: 0, Hours: 5, Sign: 1}, + }, + // Negative cases + { + name: "negative hours only", + input: "-24h", + expected: &TimeDelta{Hours: 24, Sign: -1}, + }, + { + name: "negative days only", + input: "-3d", + expected: &TimeDelta{Days: 3, Sign: -1}, + }, + { + name: "negative complex", + input: "-1d12h30m", + expected: &TimeDelta{Days: 1, Hours: 12, Minutes: 30, Sign: -1}, }, // Error cases @@ -150,9 +166,9 @@ func TestParseTimeDelta(t *testing.T) { t.Errorf("parseTimeDelta(%q) returned nil result", tt.input) return } - if result.Days != tt.expected.Days || result.Hours != tt.expected.Hours || result.Minutes != tt.expected.Minutes { - t.Errorf("parseTimeDelta(%q) = {Days: %d, Hours: %d, Minutes: %d}, want {Days: %d, Hours: %d, Minutes: %d}", - tt.input, result.Days, result.Hours, result.Minutes, tt.expected.Days, tt.expected.Hours, tt.expected.Minutes) + if result.Days != tt.expected.Days || result.Hours != tt.expected.Hours || result.Minutes != tt.expected.Minutes || result.Sign != tt.expected.Sign { + t.Errorf("parseTimeDelta(%q) = {Days: %d, Hours: %d, Minutes: %d, Sign: %d}, want {Days: %d, Hours: %d, Minutes: %d, Sign: %d}", + tt.input, result.Days, result.Hours, result.Minutes, result.Sign, tt.expected.Days, tt.expected.Hours, tt.expected.Minutes, tt.expected.Sign) } } }) @@ -166,28 +182,33 @@ func TestTimeDeltaToDuration(t *testing.T) { expected time.Duration }{ { - name: "hours only", - delta: &TimeDelta{Hours: 25}, + name: "positive hours only", + delta: &TimeDelta{Hours: 25, Sign: 1}, expected: 25 * time.Hour, }, { - name: "days only", - delta: &TimeDelta{Days: 3}, + name: "positive days only", + delta: &TimeDelta{Days: 3, Sign: 1}, expected: 3 * 24 * time.Hour, }, { - name: "minutes only", - delta: &TimeDelta{Minutes: 30}, + name: "positive minutes only", + delta: &TimeDelta{Minutes: 30, Sign: 1}, expected: 30 * time.Minute, }, { - name: "all units", - delta: &TimeDelta{Days: 2, Hours: 5, Minutes: 30}, + name: "positive all units", + delta: &TimeDelta{Days: 2, Hours: 5, Minutes: 30, Sign: 1}, expected: 2*24*time.Hour + 5*time.Hour + 30*time.Minute, }, + { + name: "negative hours only", + delta: &TimeDelta{Hours: 24, Sign: -1}, + expected: -24 * time.Hour, + }, { name: "zero values", - delta: &TimeDelta{Days: 0, Hours: 0, Minutes: 0}, + delta: &TimeDelta{Days: 0, Hours: 0, Minutes: 0, Sign: 1}, expected: 0, }, } @@ -612,3 +633,127 @@ func containsSubstring(s, substr string) bool { } return false } + +// TestIsRelativeTime tests the new isRelativeTime function +func TestIsRelativeTime(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + { + name: "positive relative time", + input: "+25h", + expected: true, + }, + { + name: "negative relative time", + input: "-24h", + expected: true, + }, + { + name: "absolute timestamp", + input: "2025-12-31 23:59:59", + expected: false, + }, + { + name: "empty string", + input: "", + expected: false, + }, + { + name: "just plus", + input: "+", + expected: true, + }, + { + name: "just minus", + input: "-", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isRelativeTime(tt.input) + if result != tt.expected { + t.Errorf("isRelativeTime(%q) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} + +// TestResolveRelativeTime tests the new resolveRelativeTime function +func TestResolveRelativeTime(t *testing.T) { + baseTime := time.Date(2025, 8, 15, 12, 0, 0, 0, time.UTC) + + tests := []struct { + name string + timeStr string + referenceTime time.Time + expectedTime time.Time + expectError bool + errorMsg string + }{ + { + name: "negative hours", + timeStr: "-24h", + referenceTime: baseTime, + expectedTime: baseTime.Add(-24 * time.Hour), + }, + { + name: "negative days", + timeStr: "-3d", + referenceTime: baseTime, + expectedTime: baseTime.Add(-3 * 24 * time.Hour), + }, + { + name: "positive hours", + timeStr: "+25h", + referenceTime: baseTime, + expectedTime: baseTime.Add(25 * time.Hour), + }, + { + name: "absolute time standard format", + timeStr: "2025-12-31 23:59:59", + referenceTime: baseTime, + expectedTime: time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC), + }, + { + name: "empty string", + timeStr: "", + expectError: true, + errorMsg: "empty time value", + }, + { + name: "invalid relative format", + timeStr: "-25x", + expectError: true, + errorMsg: "invalid time delta format", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := resolveRelativeTime(tt.timeStr, tt.referenceTime) + + if tt.expectError { + if err == nil { + t.Errorf("resolveRelativeTime(%q, %v) expected error but got none", tt.timeStr, tt.referenceTime) + return + } + if tt.errorMsg != "" && !containsString(err.Error(), tt.errorMsg) { + t.Errorf("resolveRelativeTime(%q, %v) error = %v, want to contain %v", tt.timeStr, tt.referenceTime, err.Error(), tt.errorMsg) + } + } else { + if err != nil { + t.Errorf("resolveRelativeTime(%q, %v) unexpected error: %v", tt.timeStr, tt.referenceTime, err) + return + } + if !result.Equal(tt.expectedTime) { + t.Errorf("resolveRelativeTime(%q, %v) = %v, want %v", tt.timeStr, tt.referenceTime, result, tt.expectedTime) + } + } + }) + } +} From c5fc605031d9eef7b8edad9dac89abec3ccbd623 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 04:01:36 +0000 Subject: [PATCH 3/4] Add --before and --after time filtering flags to inspect command Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/inspect.go | 39 +++++++++++++++++++++++++++++++-- pkg/cli/inspect_test.go | 4 ++-- pkg/workflow/time_delta.go | 4 ++-- pkg/workflow/time_delta_test.go | 12 +++++----- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/pkg/cli/inspect.go b/pkg/cli/inspect.go index 9bb8d4b40e2..34de6e2b221 100644 --- a/pkg/cli/inspect.go +++ b/pkg/cli/inspect.go @@ -16,7 +16,7 @@ import ( ) // InspectWorkflowMCP inspects MCP servers used by a workflow and lists available tools, resources, and roots -func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter string, verbose bool) error { +func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter string, beforeTime string, afterTime string, verbose bool) error { workflowsDir := getWorkflowsDir() // If no workflow file specified, show available workflow files with MCP configs @@ -45,6 +45,19 @@ func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter str if verbose { fmt.Println(console.FormatInfoMessage(fmt.Sprintf("Inspecting MCP servers in: %s", workflowPath))) + + // Show time filtering information if provided + if beforeTime != "" || afterTime != "" { + fmt.Println(console.FormatInfoMessage("Time filtering enabled:")) + if afterTime != "" { + resolvedAfter, _ := workflow.ResolveRelativeTime(afterTime, time.Now()) + fmt.Printf(" • After: %s (resolved to %s)\n", afterTime, resolvedAfter.Format("2006-01-02 15:04:05 UTC")) + } + if beforeTime != "" { + resolvedBefore, _ := workflow.ResolveRelativeTime(beforeTime, time.Now()) + fmt.Printf(" • Before: %s (resolved to %s)\n", beforeTime, resolvedBefore.Format("2006-01-02 15:04:05 UTC")) + } + } } // Parse the workflow file @@ -194,6 +207,8 @@ func NewInspectCommand() *cobra.Command { var serverFilter string var toolFilter string var spawnInspector bool + var beforeTime string + var afterTime string cmd := &cobra.Command{ Use: "inspect [workflow-file]", @@ -203,11 +218,17 @@ func NewInspectCommand() *cobra.Command { This command starts each MCP server configured in the workflow, queries its capabilities, and displays the results in a formatted table. It supports stdio, Docker, and HTTP MCP servers. +Time filtering allows you to filter results based on workflow run history using absolute dates +or relative time expressions like "-24h" (24 hours ago) or "-3d" (3 days ago). + Examples: gh aw inspect # List workflows with MCP servers gh aw inspect weekly-research # Inspect MCP servers in weekly-research.md gh aw inspect repomind --server repo-mind # Inspect only the repo-mind server gh aw inspect weekly-research --server github --tool create_issue # Show details for a specific tool + gh aw inspect weekly-research --after "-24h" # Filter results after 24 hours ago + gh aw inspect weekly-research --before "2024-01-01" # Filter results before specific date + gh aw inspect weekly-research --after "-3d" --before "-1d" # Filter results between 3 and 1 days ago gh aw inspect weekly-research -v # Verbose output with detailed connection info gh aw inspect weekly-research --inspector # Launch @modelcontextprotocol/inspector @@ -235,17 +256,31 @@ The command will: return fmt.Errorf("--tool flag requires --server flag to be specified") } + // Validate time filter formats if provided + if beforeTime != "" { + if _, err := workflow.ResolveRelativeTime(beforeTime, time.Now()); err != nil { + return fmt.Errorf("invalid --before time format: %w", err) + } + } + if afterTime != "" { + if _, err := workflow.ResolveRelativeTime(afterTime, time.Now()); err != nil { + return fmt.Errorf("invalid --after time format: %w", err) + } + } + // Handle spawn inspector flag if spawnInspector { return spawnMCPInspector(workflowFile, serverFilter, verbose) } - return InspectWorkflowMCP(workflowFile, serverFilter, toolFilter, verbose) + return InspectWorkflowMCP(workflowFile, serverFilter, toolFilter, beforeTime, afterTime, verbose) }, } cmd.Flags().StringVar(&serverFilter, "server", "", "Filter to inspect only the specified MCP server") cmd.Flags().StringVar(&toolFilter, "tool", "", "Show detailed information about a specific tool (requires --server)") + cmd.Flags().StringVar(&beforeTime, "before", "", "Filter results before this time (supports absolute dates and relative times like '-24h', '-3d')") + cmd.Flags().StringVar(&afterTime, "after", "", "Filter results after this time (supports absolute dates and relative times like '-24h', '-3d')") cmd.Flags().BoolP("verbose", "v", false, "Enable verbose output with detailed connection information") cmd.Flags().BoolVar(&spawnInspector, "inspector", false, "Launch the official @modelcontextprotocol/inspector tool") diff --git a/pkg/cli/inspect_test.go b/pkg/cli/inspect_test.go index 068ad739e40..f9cf89d7dec 100644 --- a/pkg/cli/inspect_test.go +++ b/pkg/cli/inspect_test.go @@ -141,7 +141,7 @@ This workflow has no MCP servers.`, for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := InspectWorkflowMCP(tt.workflowFile, tt.serverFilter, "", false) + err := InspectWorkflowMCP(tt.workflowFile, tt.serverFilter, "", "", "", false) if tt.expectError && err == nil { t.Errorf("Expected error but got none") @@ -179,7 +179,7 @@ func TestInspectWorkflowMCPWithToolFilter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := InspectWorkflowMCP(tt.workflowFile, tt.serverFilter, tt.toolFilter, false) + err := InspectWorkflowMCP(tt.workflowFile, tt.serverFilter, tt.toolFilter, "", "", false) if tt.expectError && err == nil { t.Errorf("Expected error but got none") diff --git a/pkg/workflow/time_delta.go b/pkg/workflow/time_delta.go index aa8e53ee110..07c3a3cbd76 100644 --- a/pkg/workflow/time_delta.go +++ b/pkg/workflow/time_delta.go @@ -286,11 +286,11 @@ func resolveStopTime(stopTime string, compilationTime time.Time) (string, error) return parseAbsoluteDateTime(stopTime) } -// resolveRelativeTime resolves a relative or absolute time value to an absolute timestamp +// ResolveRelativeTime resolves a relative or absolute time value to an absolute timestamp // If the time is relative (starts with '+' or '-'), it calculates the absolute time // from the reference time. Otherwise, it parses the absolute time using various formats. // Returns the time as a time.Time object instead of a string. -func resolveRelativeTime(timeStr string, referenceTime time.Time) (time.Time, error) { +func ResolveRelativeTime(timeStr string, referenceTime time.Time) (time.Time, error) { if timeStr == "" { return time.Time{}, fmt.Errorf("empty time value") } diff --git a/pkg/workflow/time_delta_test.go b/pkg/workflow/time_delta_test.go index 3f8bf72e6f8..39776ee6ec8 100644 --- a/pkg/workflow/time_delta_test.go +++ b/pkg/workflow/time_delta_test.go @@ -683,7 +683,7 @@ func TestIsRelativeTime(t *testing.T) { } } -// TestResolveRelativeTime tests the new resolveRelativeTime function +// TestResolveRelativeTime tests the new ResolveRelativeTime function func TestResolveRelativeTime(t *testing.T) { baseTime := time.Date(2025, 8, 15, 12, 0, 0, 0, time.UTC) @@ -735,23 +735,23 @@ func TestResolveRelativeTime(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := resolveRelativeTime(tt.timeStr, tt.referenceTime) + result, err := ResolveRelativeTime(tt.timeStr, tt.referenceTime) if tt.expectError { if err == nil { - t.Errorf("resolveRelativeTime(%q, %v) expected error but got none", tt.timeStr, tt.referenceTime) + t.Errorf("ResolveRelativeTime(%q, %v) expected error but got none", tt.timeStr, tt.referenceTime) return } if tt.errorMsg != "" && !containsString(err.Error(), tt.errorMsg) { - t.Errorf("resolveRelativeTime(%q, %v) error = %v, want to contain %v", tt.timeStr, tt.referenceTime, err.Error(), tt.errorMsg) + t.Errorf("ResolveRelativeTime(%q, %v) error = %v, want to contain %v", tt.timeStr, tt.referenceTime, err.Error(), tt.errorMsg) } } else { if err != nil { - t.Errorf("resolveRelativeTime(%q, %v) unexpected error: %v", tt.timeStr, tt.referenceTime, err) + t.Errorf("ResolveRelativeTime(%q, %v) unexpected error: %v", tt.timeStr, tt.referenceTime, err) return } if !result.Equal(tt.expectedTime) { - t.Errorf("resolveRelativeTime(%q, %v) = %v, want %v", tt.timeStr, tt.referenceTime, result, tt.expectedTime) + t.Errorf("ResolveRelativeTime(%q, %v) = %v, want %v", tt.timeStr, tt.referenceTime, result, tt.expectedTime) } } }) From f9ad395595ec99b42e873c0ad63e69cb6bb10684 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 04:04:37 +0000 Subject: [PATCH 4/4] Add --before and --after time filtering with relative time support to gh aw inspect Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/inspect.go | 2 +- pkg/workflow/time_delta.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cli/inspect.go b/pkg/cli/inspect.go index 34de6e2b221..e87a1d44efc 100644 --- a/pkg/cli/inspect.go +++ b/pkg/cli/inspect.go @@ -45,7 +45,7 @@ func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter str if verbose { fmt.Println(console.FormatInfoMessage(fmt.Sprintf("Inspecting MCP servers in: %s", workflowPath))) - + // Show time filtering information if provided if beforeTime != "" || afterTime != "" { fmt.Println(console.FormatInfoMessage("Time filtering enabled:")) diff --git a/pkg/workflow/time_delta.go b/pkg/workflow/time_delta.go index 07c3a3cbd76..edfef97bdfb 100644 --- a/pkg/workflow/time_delta.go +++ b/pkg/workflow/time_delta.go @@ -33,7 +33,7 @@ func parseTimeDelta(deltaStr string) (*TimeDelta, error) { var sign int var prefix string - + // Determine sign and remove prefix if strings.HasPrefix(deltaStr, "+") { sign = 1 @@ -144,7 +144,7 @@ func (td *TimeDelta) String() string { if len(parts) == 0 { return "0m" } - + prefix := "+" if td.Sign < 0 { prefix = "-" @@ -312,12 +312,12 @@ func ResolveRelativeTime(timeStr string, referenceTime time.Time) (time.Time, er if err != nil { return time.Time{}, err } - + // Convert back to time.Time parsedTime, err := time.Parse("2006-01-02 15:04:05", absoluteTimeStr) if err != nil { return time.Time{}, fmt.Errorf("failed to parse resolved time: %w", err) } - + return parsedTime.UTC(), nil }