From 1858686917de2a064375f0e7b7d1ea82490ffdff Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Wed, 23 Sep 2026 10:01:54 -0700 Subject: [PATCH 1/6] feat(app): add 'major app slow-queries list' Lists an app's resource operations ranked by total time spent, with p50/p95, error rate, and the extractor-matched call site, over a 7- or 30-day window and an execution-environment filter. Backed by GET /cli/applications/:applicationId/slow-operations. Skills: mention the command in the major table, app-builder, and debug-issue so the agent reaches for it when the complaint is slowness. --- clients/api/client.go | 23 +++++++- clients/api/structs.go | 37 ++++++++++++ cmd/app/app.go | 1 + cmd/app/slow_queries.go | 56 +++++++++++++++++++ cmd/app/slow_queries_test.go | 31 ++++++++++ .../major-build/skills/app-builder/SKILL.md | 1 + .../major-build/skills/debug-issue/SKILL.md | 2 + 7 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 cmd/app/slow_queries.go create mode 100644 cmd/app/slow_queries_test.go diff --git a/clients/api/client.go b/clients/api/client.go index 7746fe1..3e53bb9 100644 --- a/clients/api/client.go +++ b/clients/api/client.go @@ -330,7 +330,6 @@ func (c *Client) SaveApplicationResources(organizationID, applicationID string, return &resp, nil } - // --- Version Check endpoints --- // CheckVersion checks if the CLI version is up to date @@ -648,6 +647,28 @@ func (c *Client) ListAppErrors(applicationID string, req ListAppErrorsRequest) ( return &resp, nil } +// ListSlowOperations lists an app's resource operations ranked by total time spent +func (c *Client) ListSlowOperations(applicationID string, req ListSlowOperationsRequest) (*ListSlowOperationsResponse, error) { + query := url.Values{} + if req.ExecutionEnvironment != "" { + query.Set("executionEnvironment", req.ExecutionEnvironment) + } + if req.WindowDays > 0 { + query.Set("windowDays", fmt.Sprintf("%d", req.WindowDays)) + } + + path := fmt.Sprintf("/applications/%s/slow-operations", applicationID) + if encoded := query.Encode(); encoded != "" { + path = path + "?" + encoded + } + + var resp ListSlowOperationsResponse + if err := c.doRequest("GET", path, nil, &resp); err != nil { + return nil, err + } + return &resp, nil +} + // GetAppError retrieves one error's full detail, including its stack trace func (c *Client) GetAppError(applicationID, errorID string) (map[string]any, error) { var resp map[string]any diff --git a/clients/api/structs.go b/clients/api/structs.go index c433808..d349e54 100644 --- a/clients/api/structs.go +++ b/clients/api/structs.go @@ -559,6 +559,43 @@ type AppError struct { } // ListAppErrorsRequest are the filters for GET /applications/:applicationId/errors +// ListSlowOperationsRequest filters GET /applications/:applicationId/slow-operations +type ListSlowOperationsRequest struct { + ExecutionEnvironment string + WindowDays int +} + +// SlowOperationCallSite is where the operation lives in the app's source, when the +// query extractor matched the operation key to a call site. +type SlowOperationCallSite struct { + FilePath string `json:"filePath"` + Line *int `json:"line"` + SourceText *string `json:"sourceText"` +} + +// SlowOperation is one aggregated resource operation over the window +type SlowOperation struct { + ResourceID string `json:"resourceId"` + ResourceName string `json:"resourceName"` + ResourceSubtype string `json:"resourceSubtype"` + EnvironmentID *string `json:"environmentId"` + EnvironmentName *string `json:"environmentName"` + OperationKey string `json:"operationKey"` + Calls int `json:"calls"` + P50Ms float64 `json:"p50Ms"` + P95Ms float64 `json:"p95Ms"` + TotalMs float64 `json:"totalMs"` + ErrorRate float64 `json:"errorRate"` + LastSeenAt string `json:"lastSeenAt"` + CallSite *SlowOperationCallSite `json:"callSite"` +} + +// ListSlowOperationsResponse represents GET /applications/:applicationId/slow-operations +type ListSlowOperationsResponse struct { + Error *AppErrorDetail `json:"error,omitempty"` + Operations []SlowOperation `json:"operations"` +} + type ListAppErrorsRequest struct { Environment string Limit int diff --git a/cmd/app/app.go b/cmd/app/app.go index 28b64d8..da42996 100644 --- a/cmd/app/app.go +++ b/cmd/app/app.go @@ -32,6 +32,7 @@ func init() { Cmd.AddCommand(deployCmd) Cmd.AddCommand(deployStatusCmd) Cmd.AddCommand(errorsCmd) + Cmd.AddCommand(slowQueriesCmd) Cmd.AddCommand(infoCmd) Cmd.AddCommand(listCmd) Cmd.AddCommand(logsCmd) diff --git a/cmd/app/slow_queries.go b/cmd/app/slow_queries.go new file mode 100644 index 0000000..b562816 --- /dev/null +++ b/cmd/app/slow_queries.go @@ -0,0 +1,56 @@ +package app + +import ( + "github.com/major-technology/cli/clients/api" + "github.com/major-technology/cli/errors" + "github.com/major-technology/cli/singletons" + "github.com/major-technology/cli/utils" + "github.com/spf13/cobra" +) + +var ( + flagSlowQueriesEnvironment string + flagSlowQueriesDays int +) + +var slowQueriesCmd = &cobra.Command{ + Use: "slow-queries", + Short: "Inspect an application's slow resource operations", + Args: utils.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Help() + return nil + }, +} + +var slowQueriesListCmd = &cobra.Command{ + Use: "list", + Short: "List resource operations ranked by total time spent, with p95 latency", + Long: `Aggregates every resource call the app made over the window by operation +(the invocationKey for typed clients, METHOD host/path for proxy calls) and +reports calls, p50, p95, total time, error rate, and the call site when the +query extractor matched one. Sorted by total time descending.`, + RunE: func(cmd *cobra.Command, args []string) error { + applicationID, err := getApplicationID() + if err != nil { + return err + } + + resp, err := singletons.GetAPIClient().ListSlowOperations(applicationID, api.ListSlowOperationsRequest{ + ExecutionEnvironment: flagSlowQueriesEnvironment, + WindowDays: flagSlowQueriesDays, + }) + if err != nil { + return errors.WrapError("failed to list slow operations", err) + } + + return utils.WriteJSON(cmd, resp) + }, +} + +func init() { + slowQueriesListCmd.Flags().StringVar(&flagSlowQueriesEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, or local-dev") + slowQueriesListCmd.Flags().IntVar(&flagSlowQueriesDays, "days", 0, "Aggregation window in days (1-30, default 7)") + + slowQueriesCmd.AddCommand(slowQueriesListCmd) +} diff --git a/cmd/app/slow_queries_test.go b/cmd/app/slow_queries_test.go new file mode 100644 index 0000000..d0ac266 --- /dev/null +++ b/cmd/app/slow_queries_test.go @@ -0,0 +1,31 @@ +package app + +import "testing" + +func TestSlowQueriesListRegistered(t *testing.T) { + for _, sub := range slowQueriesCmd.Commands() { + if sub.Name() == "list" { + return + } + } + + t.Fatal("app slow-queries missing subcommand \"list\"") +} + +func TestSlowQueriesListExposesFlags(t *testing.T) { + for _, name := range []string{"environment", "days"} { + if slowQueriesListCmd.Flags().Lookup(name) == nil { + t.Fatalf("app slow-queries list missing --%s", name) + } + } +} + +func TestSlowQueriesRegisteredOnAppCmd(t *testing.T) { + for _, sub := range Cmd.Commands() { + if sub.Name() == "slow-queries" { + return + } + } + + t.Fatal("app slow-queries not registered on the app command") +} diff --git a/plugins/major-build/skills/app-builder/SKILL.md b/plugins/major-build/skills/app-builder/SKILL.md index b10711e..c6ce21b 100644 --- a/plugins/major-build/skills/app-builder/SKILL.md +++ b/plugins/major-build/skills/app-builder/SKILL.md @@ -88,6 +88,7 @@ Run these commands in the mounted app workspace through `mcp__plugin_major-build - `major app logs` — deployed app logs - `major app errors list` / `major app errors get ` — inspect runtime errors - `major app errors resolve ` — after committing a fix for a confirmed runtime error +- `major app slow-queries list [--environment coding-session] [--days 30]` — resource operations ranked by total time, with p95 and call site - `major app errors enable` — after adding the Major error-reporter scaffolding to the repo Use each command's `--help` for filters and pagination. diff --git a/plugins/major-build/skills/debug-issue/SKILL.md b/plugins/major-build/skills/debug-issue/SKILL.md index cd4f813..231d0c3 100644 --- a/plugins/major-build/skills/debug-issue/SKILL.md +++ b/plugins/major-build/skills/debug-issue/SKILL.md @@ -58,6 +58,8 @@ For blank pages, error overlays, server crashes, failed route handlers, or deplo - Use `major app errors list` to find recent errors. - Use `major app errors get ` for details before editing. - Prefer sourcemapped stack traces and request context from app errors over broad log searches. + +When the complaint is slowness rather than failure, run `major app slow-queries list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`); open that file, and for SQL run EXPLAIN through the connector's tools before editing. - After fixing and committing a confirmed app error, use `major app errors resolve ` only when the issue is actually addressed. - If app errors are unavailable and the task is specifically about runtime error monitoring, add the reporter scaffolding before running `major app errors enable`. From 51fa64a1e2761fd2e7ac19b247d1bc52ed493cd6 Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Wed, 23 Sep 2026 13:24:45 -0700 Subject: [PATCH 2/6] feat(app): rename slow-queries to 'major app performance list'; add --all Matches the Performance tab in the product. The server now floors results at 500 ms p95; --all sends minP95Ms=0 to include everything. --- clients/api/client.go | 3 ++ clients/api/structs.go | 2 ++ cmd/app/app.go | 2 +- cmd/app/{slow_queries.go => performance.go} | 30 ++++++++++-------- cmd/app/performance_test.go | 31 +++++++++++++++++++ cmd/app/slow_queries_test.go | 31 ------------------- .../major-build/skills/app-builder/SKILL.md | 2 +- .../major-build/skills/debug-issue/SKILL.md | 2 +- 8 files changed, 56 insertions(+), 47 deletions(-) rename cmd/app/{slow_queries.go => performance.go} (55%) create mode 100644 cmd/app/performance_test.go delete mode 100644 cmd/app/slow_queries_test.go diff --git a/clients/api/client.go b/clients/api/client.go index 3e53bb9..763ab01 100644 --- a/clients/api/client.go +++ b/clients/api/client.go @@ -656,6 +656,9 @@ func (c *Client) ListSlowOperations(applicationID string, req ListSlowOperations if req.WindowDays > 0 { query.Set("windowDays", fmt.Sprintf("%d", req.WindowDays)) } + if req.IncludeAll { + query.Set("minP95Ms", "0") + } path := fmt.Sprintf("/applications/%s/slow-operations", applicationID) if encoded := query.Encode(); encoded != "" { diff --git a/clients/api/structs.go b/clients/api/structs.go index d349e54..39d3b58 100644 --- a/clients/api/structs.go +++ b/clients/api/structs.go @@ -563,6 +563,8 @@ type AppError struct { type ListSlowOperationsRequest struct { ExecutionEnvironment string WindowDays int + // IncludeAll disables the server's 500 ms p95 floor. + IncludeAll bool } // SlowOperationCallSite is where the operation lives in the app's source, when the diff --git a/cmd/app/app.go b/cmd/app/app.go index da42996..bdb7a27 100644 --- a/cmd/app/app.go +++ b/cmd/app/app.go @@ -32,7 +32,7 @@ func init() { Cmd.AddCommand(deployCmd) Cmd.AddCommand(deployStatusCmd) Cmd.AddCommand(errorsCmd) - Cmd.AddCommand(slowQueriesCmd) + Cmd.AddCommand(performanceCmd) Cmd.AddCommand(infoCmd) Cmd.AddCommand(listCmd) Cmd.AddCommand(logsCmd) diff --git a/cmd/app/slow_queries.go b/cmd/app/performance.go similarity index 55% rename from cmd/app/slow_queries.go rename to cmd/app/performance.go index b562816..4ff5930 100644 --- a/cmd/app/slow_queries.go +++ b/cmd/app/performance.go @@ -9,13 +9,14 @@ import ( ) var ( - flagSlowQueriesEnvironment string - flagSlowQueriesDays int + flagPerformanceEnvironment string + flagPerformanceDays int + flagPerformanceAll bool ) -var slowQueriesCmd = &cobra.Command{ - Use: "slow-queries", - Short: "Inspect an application's slow resource operations", +var performanceCmd = &cobra.Command{ + Use: "performance", + Short: "Inspect an application's resource-call performance", Args: utils.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { cmd.Help() @@ -23,13 +24,14 @@ var slowQueriesCmd = &cobra.Command{ }, } -var slowQueriesListCmd = &cobra.Command{ +var performanceListCmd = &cobra.Command{ Use: "list", - Short: "List resource operations ranked by total time spent, with p95 latency", + Short: "List resource operations over 500 ms p95, ranked by total time spent", Long: `Aggregates every resource call the app made over the window by operation (the invocationKey for typed clients, METHOD host/path for proxy calls) and reports calls, p50, p95, total time, error rate, and the call site when the -query extractor matched one. Sorted by total time descending.`, +query extractor matched one. Only operations whose p95 exceeds 500 ms are +returned unless --all is set. Sorted by total time descending.`, RunE: func(cmd *cobra.Command, args []string) error { applicationID, err := getApplicationID() if err != nil { @@ -37,8 +39,9 @@ query extractor matched one. Sorted by total time descending.`, } resp, err := singletons.GetAPIClient().ListSlowOperations(applicationID, api.ListSlowOperationsRequest{ - ExecutionEnvironment: flagSlowQueriesEnvironment, - WindowDays: flagSlowQueriesDays, + ExecutionEnvironment: flagPerformanceEnvironment, + WindowDays: flagPerformanceDays, + IncludeAll: flagPerformanceAll, }) if err != nil { return errors.WrapError("failed to list slow operations", err) @@ -49,8 +52,9 @@ query extractor matched one. Sorted by total time descending.`, } func init() { - slowQueriesListCmd.Flags().StringVar(&flagSlowQueriesEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, or local-dev") - slowQueriesListCmd.Flags().IntVar(&flagSlowQueriesDays, "days", 0, "Aggregation window in days (1-30, default 7)") + performanceListCmd.Flags().StringVar(&flagPerformanceEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, or local-dev") + performanceListCmd.Flags().IntVar(&flagPerformanceDays, "days", 0, "Aggregation window in days (1-30, default 7)") + performanceListCmd.Flags().BoolVar(&flagPerformanceAll, "all", false, "Include operations at or under 500 ms p95") - slowQueriesCmd.AddCommand(slowQueriesListCmd) + performanceCmd.AddCommand(performanceListCmd) } diff --git a/cmd/app/performance_test.go b/cmd/app/performance_test.go new file mode 100644 index 0000000..614d40b --- /dev/null +++ b/cmd/app/performance_test.go @@ -0,0 +1,31 @@ +package app + +import "testing" + +func TestPerformanceListRegistered(t *testing.T) { + for _, sub := range performanceCmd.Commands() { + if sub.Name() == "list" { + return + } + } + + t.Fatal("app performance missing subcommand \"list\"") +} + +func TestPerformanceListExposesFlags(t *testing.T) { + for _, name := range []string{"environment", "days", "all"} { + if performanceListCmd.Flags().Lookup(name) == nil { + t.Fatalf("app performance list missing --%s", name) + } + } +} + +func TestPerformanceRegisteredOnAppCmd(t *testing.T) { + for _, sub := range Cmd.Commands() { + if sub.Name() == "performance" { + return + } + } + + t.Fatal("app performance not registered on the app command") +} diff --git a/cmd/app/slow_queries_test.go b/cmd/app/slow_queries_test.go deleted file mode 100644 index d0ac266..0000000 --- a/cmd/app/slow_queries_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package app - -import "testing" - -func TestSlowQueriesListRegistered(t *testing.T) { - for _, sub := range slowQueriesCmd.Commands() { - if sub.Name() == "list" { - return - } - } - - t.Fatal("app slow-queries missing subcommand \"list\"") -} - -func TestSlowQueriesListExposesFlags(t *testing.T) { - for _, name := range []string{"environment", "days"} { - if slowQueriesListCmd.Flags().Lookup(name) == nil { - t.Fatalf("app slow-queries list missing --%s", name) - } - } -} - -func TestSlowQueriesRegisteredOnAppCmd(t *testing.T) { - for _, sub := range Cmd.Commands() { - if sub.Name() == "slow-queries" { - return - } - } - - t.Fatal("app slow-queries not registered on the app command") -} diff --git a/plugins/major-build/skills/app-builder/SKILL.md b/plugins/major-build/skills/app-builder/SKILL.md index c6ce21b..fb1d543 100644 --- a/plugins/major-build/skills/app-builder/SKILL.md +++ b/plugins/major-build/skills/app-builder/SKILL.md @@ -88,7 +88,7 @@ Run these commands in the mounted app workspace through `mcp__plugin_major-build - `major app logs` — deployed app logs - `major app errors list` / `major app errors get ` — inspect runtime errors - `major app errors resolve ` — after committing a fix for a confirmed runtime error -- `major app slow-queries list [--environment coding-session] [--days 30]` — resource operations ranked by total time, with p95 and call site +- `major app performance list [--environment coding-session] [--days 30]` — resource operations over 500 ms p95, ranked by total time, with call site; `--all` includes faster ones - `major app errors enable` — after adding the Major error-reporter scaffolding to the repo Use each command's `--help` for filters and pagination. diff --git a/plugins/major-build/skills/debug-issue/SKILL.md b/plugins/major-build/skills/debug-issue/SKILL.md index 231d0c3..b3a4dec 100644 --- a/plugins/major-build/skills/debug-issue/SKILL.md +++ b/plugins/major-build/skills/debug-issue/SKILL.md @@ -59,7 +59,7 @@ For blank pages, error overlays, server crashes, failed route handlers, or deplo - Use `major app errors get ` for details before editing. - Prefer sourcemapped stack traces and request context from app errors over broad log searches. -When the complaint is slowness rather than failure, run `major app slow-queries list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`); open that file, and for SQL run EXPLAIN through the connector's tools before editing. +When the complaint is slowness rather than failure, run `major app performance list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`); open that file, and for SQL run EXPLAIN through the connector's tools before editing. - After fixing and committing a confirmed app error, use `major app errors resolve ` only when the issue is actually addressed. - If app errors are unavailable and the task is specifically about runtime error monitoring, add the reporter scaffolding before running `major app errors enable`. From 4315f42aa7f4b9b21a7e71ba96638ece6dd88470 Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Wed, 23 Sep 2026 13:42:37 -0700 Subject: [PATCH 3/6] fix(app): performance list help mentions --environment all; restore ListAppErrorsRequest doc comment --- clients/api/structs.go | 2 +- cmd/app/performance.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/api/structs.go b/clients/api/structs.go index 39d3b58..2cefcf8 100644 --- a/clients/api/structs.go +++ b/clients/api/structs.go @@ -558,7 +558,6 @@ type AppError struct { URL *string `json:"url,omitempty"` } -// ListAppErrorsRequest are the filters for GET /applications/:applicationId/errors // ListSlowOperationsRequest filters GET /applications/:applicationId/slow-operations type ListSlowOperationsRequest struct { ExecutionEnvironment string @@ -598,6 +597,7 @@ type ListSlowOperationsResponse struct { Operations []SlowOperation `json:"operations"` } +// ListAppErrorsRequest are the filters for GET /applications/:applicationId/errors type ListAppErrorsRequest struct { Environment string Limit int diff --git a/cmd/app/performance.go b/cmd/app/performance.go index 4ff5930..af66f16 100644 --- a/cmd/app/performance.go +++ b/cmd/app/performance.go @@ -52,8 +52,8 @@ returned unless --all is set. Sorted by total time descending.`, } func init() { - performanceListCmd.Flags().StringVar(&flagPerformanceEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, or local-dev") - performanceListCmd.Flags().IntVar(&flagPerformanceDays, "days", 0, "Aggregation window in days (1-30, default 7)") + performanceListCmd.Flags().StringVar(&flagPerformanceEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, local-dev, or all") + performanceListCmd.Flags().IntVar(&flagPerformanceDays, "days", 0, "Aggregation window in days, 1-30 (server default 7)") performanceListCmd.Flags().BoolVar(&flagPerformanceAll, "all", false, "Include operations at or under 500 ms p95") performanceCmd.AddCommand(performanceListCmd) From 3bb13e032f240f46d3d6e78314d6307a809d53b1 Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Wed, 23 Sep 2026 13:57:31 -0700 Subject: [PATCH 4/6] docs(debug-issue): give slow operations their own section instead of splitting the app-errors list --- plugins/major-build/skills/debug-issue/SKILL.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/major-build/skills/debug-issue/SKILL.md b/plugins/major-build/skills/debug-issue/SKILL.md index b3a4dec..967e910 100644 --- a/plugins/major-build/skills/debug-issue/SKILL.md +++ b/plugins/major-build/skills/debug-issue/SKILL.md @@ -58,11 +58,13 @@ For blank pages, error overlays, server crashes, failed route handlers, or deplo - Use `major app errors list` to find recent errors. - Use `major app errors get ` for details before editing. - Prefer sourcemapped stack traces and request context from app errors over broad log searches. - -When the complaint is slowness rather than failure, run `major app performance list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`); open that file, and for SQL run EXPLAIN through the connector's tools before editing. - After fixing and committing a confirmed app error, use `major app errors resolve ` only when the issue is actually addressed. - If app errors are unavailable and the task is specifically about runtime error monitoring, add the reporter scaffolding before running `major app errors enable`. +## Slow operations + +When the complaint is slowness rather than failure, run `major app performance list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`). Only operations over 500 ms p95 are returned; add `--all` to see everything, and `--environment coding-session` for preview traffic. Open the call site, and for SQL run EXPLAIN through the connector's tools before editing. + ## App logs Use logs when behavior depends on server startup, route handlers, background work, request handling, or errors that are not captured by app errors. From 3a149250c525d40c00f7461979cb32f502a94b3a Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Thu, 24 Sep 2026 16:56:17 -0700 Subject: [PATCH 5/6] feat(resource): expose invocations with optional slow filter --- cmd/app/app.go | 1 - cmd/app/performance.go | 60 ------------------- cmd/app/performance_test.go | 31 ---------- cmd/resource/invocations.go | 48 +++++++++++++++ cmd/resource/invocations_test.go | 14 +++++ cmd/resource/resource.go | 1 + .../major-build/skills/app-builder/SKILL.md | 2 +- .../major-build/skills/debug-issue/SKILL.md | 2 +- 8 files changed, 65 insertions(+), 94 deletions(-) delete mode 100644 cmd/app/performance.go delete mode 100644 cmd/app/performance_test.go create mode 100644 cmd/resource/invocations.go create mode 100644 cmd/resource/invocations_test.go diff --git a/cmd/app/app.go b/cmd/app/app.go index bdb7a27..28b64d8 100644 --- a/cmd/app/app.go +++ b/cmd/app/app.go @@ -32,7 +32,6 @@ func init() { Cmd.AddCommand(deployCmd) Cmd.AddCommand(deployStatusCmd) Cmd.AddCommand(errorsCmd) - Cmd.AddCommand(performanceCmd) Cmd.AddCommand(infoCmd) Cmd.AddCommand(listCmd) Cmd.AddCommand(logsCmd) diff --git a/cmd/app/performance.go b/cmd/app/performance.go deleted file mode 100644 index af66f16..0000000 --- a/cmd/app/performance.go +++ /dev/null @@ -1,60 +0,0 @@ -package app - -import ( - "github.com/major-technology/cli/clients/api" - "github.com/major-technology/cli/errors" - "github.com/major-technology/cli/singletons" - "github.com/major-technology/cli/utils" - "github.com/spf13/cobra" -) - -var ( - flagPerformanceEnvironment string - flagPerformanceDays int - flagPerformanceAll bool -) - -var performanceCmd = &cobra.Command{ - Use: "performance", - Short: "Inspect an application's resource-call performance", - Args: utils.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil - }, -} - -var performanceListCmd = &cobra.Command{ - Use: "list", - Short: "List resource operations over 500 ms p95, ranked by total time spent", - Long: `Aggregates every resource call the app made over the window by operation -(the invocationKey for typed clients, METHOD host/path for proxy calls) and -reports calls, p50, p95, total time, error rate, and the call site when the -query extractor matched one. Only operations whose p95 exceeds 500 ms are -returned unless --all is set. Sorted by total time descending.`, - RunE: func(cmd *cobra.Command, args []string) error { - applicationID, err := getApplicationID() - if err != nil { - return err - } - - resp, err := singletons.GetAPIClient().ListSlowOperations(applicationID, api.ListSlowOperationsRequest{ - ExecutionEnvironment: flagPerformanceEnvironment, - WindowDays: flagPerformanceDays, - IncludeAll: flagPerformanceAll, - }) - if err != nil { - return errors.WrapError("failed to list slow operations", err) - } - - return utils.WriteJSON(cmd, resp) - }, -} - -func init() { - performanceListCmd.Flags().StringVar(&flagPerformanceEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, local-dev, or all") - performanceListCmd.Flags().IntVar(&flagPerformanceDays, "days", 0, "Aggregation window in days, 1-30 (server default 7)") - performanceListCmd.Flags().BoolVar(&flagPerformanceAll, "all", false, "Include operations at or under 500 ms p95") - - performanceCmd.AddCommand(performanceListCmd) -} diff --git a/cmd/app/performance_test.go b/cmd/app/performance_test.go deleted file mode 100644 index 614d40b..0000000 --- a/cmd/app/performance_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package app - -import "testing" - -func TestPerformanceListRegistered(t *testing.T) { - for _, sub := range performanceCmd.Commands() { - if sub.Name() == "list" { - return - } - } - - t.Fatal("app performance missing subcommand \"list\"") -} - -func TestPerformanceListExposesFlags(t *testing.T) { - for _, name := range []string{"environment", "days", "all"} { - if performanceListCmd.Flags().Lookup(name) == nil { - t.Fatalf("app performance list missing --%s", name) - } - } -} - -func TestPerformanceRegisteredOnAppCmd(t *testing.T) { - for _, sub := range Cmd.Commands() { - if sub.Name() == "performance" { - return - } - } - - t.Fatal("app performance not registered on the app command") -} diff --git a/cmd/resource/invocations.go b/cmd/resource/invocations.go new file mode 100644 index 0000000..f0f066b --- /dev/null +++ b/cmd/resource/invocations.go @@ -0,0 +1,48 @@ +package resource + +import ( + "github.com/major-technology/cli/clients/api" + "github.com/major-technology/cli/errors" + "github.com/major-technology/cli/singletons" + "github.com/major-technology/cli/utils" + "github.com/spf13/cobra" +) + +var ( + flagInvocationsEnvironment string + flagInvocationsDays int + flagInvocationsSlow bool +) + +var invocationsCmd = &cobra.Command{ + Use: "invocations", + Short: "List an application's resource invocations, ranked by total time spent", + Long: `Aggregates every resource call the app made over the window by operation +(the invocationKey for typed clients, METHOD host/path for proxy calls) and +reports calls, p50, p95, total time, error rate, and the call site when the +query extractor matched one. Use --slow to show only operations over 500 ms p95. +Sorted by total time descending.`, + RunE: func(cmd *cobra.Command, args []string) error { + appInfo, err := utils.GetApplicationInfo("") + if err != nil { + return errors.WrapError("failed to identify application", err) + } + + resp, err := singletons.GetAPIClient().ListSlowOperations(appInfo.ApplicationID, api.ListSlowOperationsRequest{ + ExecutionEnvironment: flagInvocationsEnvironment, + WindowDays: flagInvocationsDays, + IncludeAll: !flagInvocationsSlow, + }) + if err != nil { + return errors.WrapError("failed to list resource invocations", err) + } + + return utils.WriteJSON(cmd, resp) + }, +} + +func init() { + invocationsCmd.Flags().StringVar(&flagInvocationsEnvironment, "environment", "", "Traffic to include: deployment (default), coding-session, local-dev, or all") + invocationsCmd.Flags().IntVar(&flagInvocationsDays, "days", 0, "Aggregation window in days, 1-30 (server default 7)") + invocationsCmd.Flags().BoolVar(&flagInvocationsSlow, "slow", false, "Only include operations over 500 ms p95") +} diff --git a/cmd/resource/invocations_test.go b/cmd/resource/invocations_test.go new file mode 100644 index 0000000..f494e62 --- /dev/null +++ b/cmd/resource/invocations_test.go @@ -0,0 +1,14 @@ +package resource + +import "testing" + +func TestInvocationsCommand(t *testing.T) { + if invocationsCmd.Parent() != Cmd { + t.Fatal("resource invocations not registered") + } + for _, name := range []string{"environment", "days", "slow"} { + if invocationsCmd.Flags().Lookup(name) == nil { + t.Fatalf("resource invocations missing --%s", name) + } + } +} diff --git a/cmd/resource/resource.go b/cmd/resource/resource.go index 1f6fe3b..d48ff63 100644 --- a/cmd/resource/resource.go +++ b/cmd/resource/resource.go @@ -23,6 +23,7 @@ func init() { Cmd.AddCommand(envCmd) Cmd.AddCommand(envListCmd) Cmd.AddCommand(listCmd) + Cmd.AddCommand(invocationsCmd) Cmd.AddCommand(addCmd) Cmd.AddCommand(removeCmd) } diff --git a/plugins/major-build/skills/app-builder/SKILL.md b/plugins/major-build/skills/app-builder/SKILL.md index fb1d543..e9d5485 100644 --- a/plugins/major-build/skills/app-builder/SKILL.md +++ b/plugins/major-build/skills/app-builder/SKILL.md @@ -88,7 +88,7 @@ Run these commands in the mounted app workspace through `mcp__plugin_major-build - `major app logs` — deployed app logs - `major app errors list` / `major app errors get ` — inspect runtime errors - `major app errors resolve ` — after committing a fix for a confirmed runtime error -- `major app performance list [--environment coding-session] [--days 30]` — resource operations over 500 ms p95, ranked by total time, with call site; `--all` includes faster ones +- `major resource invocations --slow [--environment coding-session] [--days 30]` — resource operations over 500 ms p95, ranked by total time, with call site; omit `--slow` to include faster ones - `major app errors enable` — after adding the Major error-reporter scaffolding to the repo Use each command's `--help` for filters and pagination. diff --git a/plugins/major-build/skills/debug-issue/SKILL.md b/plugins/major-build/skills/debug-issue/SKILL.md index 967e910..227f96a 100644 --- a/plugins/major-build/skills/debug-issue/SKILL.md +++ b/plugins/major-build/skills/debug-issue/SKILL.md @@ -63,7 +63,7 @@ For blank pages, error overlays, server crashes, failed route handlers, or deplo ## Slow operations -When the complaint is slowness rather than failure, run `major app performance list` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`). Only operations over 500 ms p95 are returned; add `--all` to see everything, and `--environment coding-session` for preview traffic. Open the call site, and for SQL run EXPLAIN through the connector's tools before editing. +When the complaint is slowness rather than failure, run `major resource invocations --slow` first. It ranks the app's resource operations by total time with p95 latency and the call site (`callSite.filePath:line`). The `--slow` flag includes only operations over 500 ms p95; omit it to see everything, and use `--environment coding-session` for preview traffic. Open the call site, and for SQL run EXPLAIN through the connector's tools before editing. ## App logs From 9fbe5a9ca4011209ee7cb65b8875fb8f3da8dbd8 Mon Sep 17 00:00:00 2001 From: Rishikesh Balaji Date: Fri, 25 Sep 2026 10:52:06 -0700 Subject: [PATCH 6/6] fix(resource): use invocation aggregates CLI endpoint --- clients/api/client.go | 2 +- clients/api/slow_operations_test.go | 26 ++++++++++++++++++++++++++ clients/api/structs.go | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 clients/api/slow_operations_test.go diff --git a/clients/api/client.go b/clients/api/client.go index 763ab01..dae6fb0 100644 --- a/clients/api/client.go +++ b/clients/api/client.go @@ -660,7 +660,7 @@ func (c *Client) ListSlowOperations(applicationID string, req ListSlowOperations query.Set("minP95Ms", "0") } - path := fmt.Sprintf("/applications/%s/slow-operations", applicationID) + path := fmt.Sprintf("/applications/%s/resource-invocation-aggregates", applicationID) if encoded := query.Encode(); encoded != "" { path = path + "?" + encoded } diff --git a/clients/api/slow_operations_test.go b/clients/api/slow_operations_test.go new file mode 100644 index 0000000..f5a00b9 --- /dev/null +++ b/clients/api/slow_operations_test.go @@ -0,0 +1,26 @@ +package api + +import "testing" + +func TestListSlowOperationsUsesInvocationAggregatesRoute(t *testing.T) { + for _, tc := range []struct { + name string + includeAll bool + path string + }{ + {"slow", false, "/applications/app-1/resource-invocation-aggregates?executionEnvironment=coding-session&windowDays=30"}, + {"all", true, "/applications/app-1/resource-invocation-aggregates?executionEnvironment=coding-session&minP95Ms=0&windowDays=30"}, + } { + t.Run(tc.name, func(t *testing.T) { + _, client := newTestServer(t, "GET", tc.path, 200, ListSlowOperationsResponse{Operations: []SlowOperation{}}) + _, err := client.ListSlowOperations("app-1", ListSlowOperationsRequest{ + ExecutionEnvironment: "coding-session", + WindowDays: 30, + IncludeAll: tc.includeAll, + }) + if err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/clients/api/structs.go b/clients/api/structs.go index 2cefcf8..8dfad40 100644 --- a/clients/api/structs.go +++ b/clients/api/structs.go @@ -558,7 +558,7 @@ type AppError struct { URL *string `json:"url,omitempty"` } -// ListSlowOperationsRequest filters GET /applications/:applicationId/slow-operations +// ListSlowOperationsRequest filters GET /applications/:applicationId/resource-invocation-aggregates type ListSlowOperationsRequest struct { ExecutionEnvironment string WindowDays int @@ -591,7 +591,7 @@ type SlowOperation struct { CallSite *SlowOperationCallSite `json:"callSite"` } -// ListSlowOperationsResponse represents GET /applications/:applicationId/slow-operations +// ListSlowOperationsResponse represents GET /applications/:applicationId/resource-invocation-aggregates type ListSlowOperationsResponse struct { Error *AppErrorDetail `json:"error,omitempty"` Operations []SlowOperation `json:"operations"`