From 16269ce692c8cc1aced9d324857aab65a9c04416 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 00:09:49 +0000 Subject: [PATCH 01/40] Add centralized slash command trigger strategy and generator Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/check_command_position.cjs | 36 +++ .../setup/js/check_command_position.test.cjs | 14 +- .../docs/reference/command-triggers.md | 16 +- .../docs/reference/frontmatter-full.md | 7 + docs/src/content/docs/reference/triggers.md | 1 + pkg/cli/compile_pipeline.go | 5 + pkg/cli/compile_post_processing.go | 19 ++ pkg/parser/schemas/main_workflow_schema.json | 26 +- .../central_slash_command_workflow.go | 231 ++++++++++++++++++ .../central_slash_command_workflow_test.go | 63 +++++ .../compiler_orchestrator_workflow.go | 2 +- pkg/workflow/compiler_safe_outputs.go | 20 +- pkg/workflow/compiler_safe_outputs_test.go | 39 +++ pkg/workflow/compiler_types.go | 3 +- pkg/workflow/frontmatter_extraction_yaml.go | 20 +- .../slash_command_centralized_compile_test.go | 48 ++++ pkg/workflow/tools.go | 89 ++++--- 17 files changed, 586 insertions(+), 53 deletions(-) create mode 100644 pkg/workflow/central_slash_command_workflow.go create mode 100644 pkg/workflow/central_slash_command_workflow_test.go create mode 100644 pkg/workflow/slash_command_centralized_compile_test.go diff --git a/actions/setup/js/check_command_position.cjs b/actions/setup/js/check_command_position.cjs index 349ccf1f114..e6f9402e392 100644 --- a/actions/setup/js/check_command_position.cjs +++ b/actions/setup/js/check_command_position.cjs @@ -66,6 +66,42 @@ async function main() { text = context.payload.discussion?.body || ""; } else if (eventName === "discussion_comment") { text = context.payload.comment?.body || ""; + } else if (eventName === "workflow_dispatch") { + const rawAwContext = context.payload?.inputs?.aw_context ?? ""; + let inboundCommandName = ""; + if (typeof rawAwContext === "string" && rawAwContext.trim() !== "") { + try { + const parsed = JSON.parse(rawAwContext); + if (parsed && typeof parsed === "object" && typeof parsed.command_name === "string") { + inboundCommandName = parsed.command_name.trim(); + } + } catch { + // ignore malformed aw_context and fall back to manual workflow_dispatch behavior + } + } + + if (inboundCommandName) { + if (commands.includes(inboundCommandName)) { + core.info(`✓ command_name '${inboundCommandName}' resolved from workflow_dispatch aw_context`); + core.setOutput("command_position_ok", "true"); + core.setOutput("matched_command", inboundCommandName); + } else { + core.warning(`⚠️ command_name '${inboundCommandName}' from aw_context is not in allowed commands list.`); + core.setOutput("command_position_ok", "false"); + core.setOutput("matched_command", ""); + await writeDenialSummary( + `Workflow dispatch aw_context.command_name '${inboundCommandName}' is not one of the configured commands.`, + "Ensure the centralized slash-command trigger dispatches only configured commands." + ); + } + return; + } + + // Manual workflow_dispatch without aw_context.command_name is still allowed. + core.info("workflow_dispatch without aw_context.command_name; skipping command position check"); + core.setOutput("command_position_ok", "true"); + core.setOutput("matched_command", ""); + return; } else { // For non-comment events, pass the check core.info(`Event ${eventName} does not require command position check`); diff --git a/actions/setup/js/check_command_position.test.cjs b/actions/setup/js/check_command_position.test.cjs index e71b2a5e16e..3c6f02d1638 100644 --- a/actions/setup/js/check_command_position.test.cjs +++ b/actions/setup/js/check_command_position.test.cjs @@ -76,7 +76,19 @@ const mockCore = { (mockContext.payload = {}), await eval(`(async () => { ${checkCommandPositionScript}; await main(); })()`), expect(mockCore.setOutput).toHaveBeenCalledWith("command_position_ok", "true"), - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("does not require command position check"))); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("without aw_context.command_name"))); + }), + it("should resolve command from workflow_dispatch aw_context", async () => { + process.env.GH_AW_COMMANDS = JSON.stringify(["test-bot"]); + mockContext.eventName = "workflow_dispatch"; + mockContext.payload = { + inputs: { + aw_context: JSON.stringify({ command_name: "test-bot" }), + }, + }; + await eval(`(async () => { ${checkCommandPositionScript}; await main(); })()`); + expect(mockCore.setOutput).toHaveBeenCalledWith("command_position_ok", "true"); + expect(mockCore.setOutput).toHaveBeenCalledWith("matched_command", "test-bot"); }), it("should handle pull_request event with command at start", async () => { ((process.env.GH_AW_COMMANDS = JSON.stringify(["review-bot"])), diff --git a/docs/src/content/docs/reference/command-triggers.md b/docs/src/content/docs/reference/command-triggers.md index dc64089232e..4e75de666bf 100644 --- a/docs/src/content/docs/reference/command-triggers.md +++ b/docs/src/content/docs/reference/command-triggers.md @@ -68,7 +68,21 @@ on: schedule: weekly on monday ``` -**Note**: You cannot combine `slash_command` with `issues`, `issue_comment`, or `pull_request` as they would conflict. +### Centralized trigger strategy + +Set `on.slash_command.strategy: centralized` to opt a workflow into centralized slash-command routing. +When enabled, the workflow compiles as `workflow_dispatch`-centric, and the compiler generates one +shared `agentics-slash-command-trigger.yml` workflow that listens to merged slash-command events and +dispatches matching target workflows with `aw_context`. + +```yaml wrap +on: + slash_command: + name: my-bot + strategy: centralized +``` + +**Note**: With default inline strategy, you cannot combine `slash_command` with `issues`, `issue_comment`, or `pull_request` as they would conflict. With `strategy: centralized`, non-slash events are preserved because slash matching is handled in the generated central trigger workflow. **Exception for Label-Only Events**: You CAN combine `slash_command` with `issues` or `pull_request` if those events are configured for label-only triggers (`labeled` or `unlabeled` types only). This allows workflows to respond to slash commands while also reacting to label changes. diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 4be4ea8db69..75240b5fabb 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -152,6 +152,13 @@ on: name: [] # Array items: Command name without leading slash + # Trigger compilation strategy for slash commands. + # - "inline" (default): compile comment/body listeners directly in this workflow + # - "centralized": compile this workflow as workflow_dispatch-centric and route + # slash command events via the generated central trigger workflow. + # (optional) + strategy: "centralized" + # Events where the command should be active. Default is all comment-related events # ('*'). Use GitHub Actions event names. # (optional) diff --git a/docs/src/content/docs/reference/triggers.md b/docs/src/content/docs/reference/triggers.md index fd85b18a9b6..acfa535f3b3 100644 --- a/docs/src/content/docs/reference/triggers.md +++ b/docs/src/content/docs/reference/triggers.md @@ -395,6 +395,7 @@ on: slash_command: name: investigate events: [issues, issue_comment] # Only respond in issue contexts + # strategy: centralized # Optional: route via generated central trigger workflow ``` See [Command Triggers](/gh-aw/reference/command-triggers/) for complete documentation including event filtering, context text, reactions, and examples. diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index 23e5dc72a10..fd53292f7f4 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -525,6 +525,11 @@ func runPostProcessingForDirectory( return err } } + if err := generateCentralSlashCommandWorkflowWrapper(workflowDataList, absWorkflowDir, config.Strict); err != nil { + if config.Strict { + return err + } + } } // Prune stale gh-aw-actions entries before saving diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 4746c45fd0b..7a83078092e 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -100,6 +100,25 @@ func generateMaintenanceWorkflowWrapper( return nil } +// generateCentralSlashCommandWorkflowWrapper generates a single centralized +// slash-command trigger workflow for all participating workflows. +func generateCentralSlashCommandWorkflowWrapper( + workflowDataList []*workflow.WorkflowData, + workflowsDir string, + strict bool, +) error { + compilePostProcessingLog.Print("Generating centralized slash-command workflow") + + if err := workflow.GenerateCentralSlashCommandWorkflow(workflowDataList, workflowsDir); err != nil { + if strict { + return fmt.Errorf("failed to generate centralized slash-command workflow: %w", err) + } + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to generate centralized slash-command workflow: %v", err))) + } + + return nil +} + // purgeOrphanedLockFiles removes orphaned .lock.yml files // These are lock files that exist but don't have a corresponding .md file func purgeOrphanedLockFiles(workflowsDir string, expectedLockFiles []string, verbose bool) error { diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 9c81d81df2c..e25007923a2 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -459,6 +459,11 @@ "maxItems": 25 } ] + }, + "strategy": { + "type": "string", + "description": "Slash command trigger compilation strategy. 'inline' (default) compiles direct comment listeners in this workflow. 'centralized' compiles this workflow as workflow_dispatch-centric and routes slash events via the generated central trigger workflow.", + "enum": ["inline", "centralized"] } }, "additionalProperties": false @@ -9900,9 +9905,24 @@ { "properties": { "slash_command": { - "not": { - "type": "null" - } + "allOf": [ + { + "not": { + "type": "null" + } + }, + { + "not": { + "type": "object", + "properties": { + "strategy": { + "const": "centralized" + } + }, + "required": ["strategy"] + } + } + ] } }, "required": ["slash_command"] diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go new file mode 100644 index 00000000000..f874d87b4ca --- /dev/null +++ b/pkg/workflow/central_slash_command_workflow.go @@ -0,0 +1,231 @@ +package workflow + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "slices" + "sort" + "strings" + + "github.com/github/gh-aw/pkg/logger" +) + +var centralSlashCommandWorkflowLog = logger.New("workflow:central_slash_command_workflow") + +const centralSlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" + +type slashCommandRoute struct { + Workflow string `json:"workflow"` + Events []string `json:"events"` +} + +// GenerateCentralSlashCommandWorkflow generates a single centralized slash-command trigger +// workflow for workflows that opt into on.slash_command.strategy: centralized. +// When no centralized slash-command workflows are found, any existing generated file is deleted. +func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workflowDir string) error { + centralSlashCommandWorkflowLog.Printf("Generating centralized slash-command workflow from %d workflow(s)", len(workflowDataList)) + routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) + + triggerFile := filepath.Join(workflowDir, centralSlashCommandWorkflowFilename) + if len(routesByCommand) == 0 || len(mergedEvents) == 0 { + centralSlashCommandWorkflowLog.Print("No centralized slash-command participants found") + if _, err := os.Stat(triggerFile); err == nil { + if err := os.Remove(triggerFile); err != nil { + return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) + } + } + return nil + } + + content, err := buildCentralSlashCommandWorkflowYAML(routesByCommand, mergedEvents) + if err != nil { + return err + } + + if err := os.WriteFile(triggerFile, []byte(content), 0644); err != nil { + return fmt.Errorf("failed to write centralized slash-command workflow: %w", err) + } + centralSlashCommandWorkflowLog.Printf("Wrote centralized slash-command workflow: %s", triggerFile) + return nil +} + +func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]bool) { + routesByCommand := make(map[string][]slashCommandRoute) + mergedEvents := make(map[string]map[string]bool) + + for _, wd := range workflowDataList { + if wd == nil || !wd.CommandCentralized || len(wd.Command) == 0 { + continue + } + + filteredEvents := FilterCommentEvents(wd.CommandEvents) + if len(filteredEvents) == 0 { + continue + } + + routeEvents := GetCommentEventNames(filteredEvents) + routeEvents = uniqueSorted(routeEvents) + if len(routeEvents) == 0 { + continue + } + + // Merge workflow-level subscriptions using YAML-ready GitHub event names. + for _, event := range MergeEventsForYAML(filteredEvents) { + if mergedEvents[event.EventName] == nil { + mergedEvents[event.EventName] = make(map[string]bool) + } + for _, t := range event.Types { + mergedEvents[event.EventName][t] = true + } + } + + for _, commandName := range wd.Command { + route := slashCommandRoute{ + Workflow: wd.WorkflowID, + Events: slices.Clone(routeEvents), + } + routesByCommand[commandName] = append(routesByCommand[commandName], route) + } + } + + // Stable ordering for deterministic output. + for commandName := range routesByCommand { + sort.Slice(routesByCommand[commandName], func(i, j int) bool { + return routesByCommand[commandName][i].Workflow < routesByCommand[commandName][j].Workflow + }) + } + + return routesByCommand, mergedEvents +} + +func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashCommandRoute, mergedEvents map[string]map[string]bool) (string, error) { + routesJSON, err := json.Marshal(routesByCommand) + if err != nil { + return "", fmt.Errorf("failed to marshal centralized slash-command routes: %w", err) + } + + header := GenerateWorkflowHeader("", "pkg/workflow/central_slash_command_workflow.go", "") + + var b strings.Builder + b.WriteString(header) + b.WriteString(`name: "Agentic Slash Command Trigger" + +on: +`) + writeCentralSlashEventsYAML(&b, mergedEvents) + b.WriteString(` +permissions: + actions: write + contents: read + +jobs: + route: + runs-on: ubuntu-slim + steps: + - name: Checkout repository + uses: ` + getActionPin("actions/checkout") + ` + + - name: Route slash command + uses: ` + getActionPin("actions/github-script") + ` + env: + GH_AW_SLASH_ROUTING: '` + escapeSingleQuotedYAMLString(string(routesJSON)) + `' + with: + script: | + const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); + const bodyByEvent = { + issues: context.payload?.issue?.body ?? "", + pull_request: context.payload?.pull_request?.body ?? "", + issue_comment: context.payload?.comment?.body ?? "", + pull_request_review_comment: context.payload?.comment?.body ?? "", + discussion: context.payload?.discussion?.body ?? "", + discussion_comment: context.payload?.comment?.body ?? "", + }; + + function eventIdentifier() { + if (context.eventName !== "issue_comment") { + return context.eventName; + } + return context.payload?.issue?.pull_request ? "pull_request_comment" : "issue_comment"; + } + + const text = bodyByEvent[context.eventName] ?? ""; + const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; + if (!firstWord.startsWith("/")) { + core.info("No slash command found at start of payload text; skipping dispatch."); + return; + } + + const commandName = firstWord.slice(1); + const identifier = eventIdentifier(); + const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); + if (routes.length === 0) { + core.info("No centralized routes matched command '/" + commandName + "' for event '" + identifier + "'."); + return; + } + + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + setupGlobals(core, github, context, exec, io, getOctokit); + const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); + + const ref = process.env.GITHUB_HEAD_REF ? "refs/heads/" + process.env.GITHUB_HEAD_REF : (process.env.GITHUB_REF || context.ref || "refs/heads/" + (context.payload?.repository?.default_branch || "main")); + for (const route of routes) { + const awContext = buildAwContext(); + awContext.command_name = commandName; + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: route.workflow + ".lock.yml", + ref, + inputs: { + aw_context: JSON.stringify(awContext), + }, + }); + core.info("Dispatched '" + route.workflow + "' for '/" + commandName + "'"); + } +`) + return b.String(), nil +} + +func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]bool) { + eventOrder := []string{ + "issues", + "issue_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment", + } + + for _, eventName := range eventOrder { + typeSet := mergedEvents[eventName] + if len(typeSet) == 0 { + continue + } + types := make([]string, 0, len(typeSet)) + for t := range typeSet { + types = append(types, t) + } + sort.Strings(types) + b.WriteString(" " + eventName + ":\n") + b.WriteString(" types: [" + strings.Join(types, ", ") + "]\n") + } +} + +func uniqueSorted(values []string) []string { + seen := make(map[string]bool, len(values)) + for _, v := range values { + seen[v] = true + } + result := make([]string, 0, len(seen)) + for v := range seen { + result = append(result, v) + } + sort.Strings(result) + return result +} + +func escapeSingleQuotedYAMLString(input string) string { + return strings.ReplaceAll(input, "'", "''") +} diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go new file mode 100644 index 00000000000..3fd2c5d6214 --- /dev/null +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -0,0 +1,63 @@ +//go:build !integration + +package workflow + +import ( + "os" + "path/filepath" + "testing" + + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/require" +) + +func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { + tmpDir := testutil.TempDir(t, "central-slash-workflow-test") + + data := []*WorkflowData{ + { + WorkflowID: "triage", + Command: []string{"triage"}, + CommandEvents: []string{"issue_comment"}, + CommandCentralized: true, + }, + { + WorkflowID: "review", + Command: []string{"triage"}, + CommandEvents: []string{"pull_request_comment"}, + CommandCentralized: true, + }, + } + + require.NoError(t, GenerateCentralSlashCommandWorkflow(data, tmpDir)) + + generatedPath := filepath.Join(tmpDir, centralSlashCommandWorkflowFilename) + content, err := os.ReadFile(generatedPath) + require.NoError(t, err) + text := string(content) + + require.Contains(t, text, "name: \"Agentic Slash Command Trigger\"") + require.Contains(t, text, "issue_comment:") + require.Contains(t, text, `"triage":[{"workflow":"review","events":["pull_request_comment"]},{"workflow":"triage","events":["issue_comment"]}]`) + require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) +} + +func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { + tmpDir := testutil.TempDir(t, "central-slash-workflow-delete-test") + generatedPath := filepath.Join(tmpDir, centralSlashCommandWorkflowFilename) + require.NoError(t, os.WriteFile(generatedPath, []byte("stale"), 0644)) + + data := []*WorkflowData{ + { + WorkflowID: "regular", + Command: []string{"regular"}, + CommandEvents: []string{"issue_comment"}, + CommandCentralized: false, + }, + } + + require.NoError(t, GenerateCentralSlashCommandWorkflow(data, tmpDir)) + _, err := os.Stat(generatedPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) +} diff --git a/pkg/workflow/compiler_orchestrator_workflow.go b/pkg/workflow/compiler_orchestrator_workflow.go index 40e03f2cd15..3873be7ab81 100644 --- a/pkg/workflow/compiler_orchestrator_workflow.go +++ b/pkg/workflow/compiler_orchestrator_workflow.go @@ -334,7 +334,7 @@ func (c *Compiler) extractAdditionalConfigurations( workflowData.RepoMemoryConfig = repoMemoryConfig // Extract and process mcp-scripts and safe-outputs - workflowData.Command, workflowData.CommandEvents = c.extractCommandConfig(frontmatter) + workflowData.Command, workflowData.CommandEvents, workflowData.CommandCentralized = c.extractCommandConfig(frontmatter) workflowData.LabelCommand, workflowData.LabelCommandEvents, workflowData.LabelCommandRemoveLabel = c.extractLabelCommandConfig(frontmatter) workflowData.Jobs = c.extractJobsFromFrontmatter(frontmatter) diff --git a/pkg/workflow/compiler_safe_outputs.go b/pkg/workflow/compiler_safe_outputs.go index 00937cc30f2..d6b6c2499c9 100644 --- a/pkg/workflow/compiler_safe_outputs.go +++ b/pkg/workflow/compiler_safe_outputs.go @@ -150,15 +150,19 @@ func (c *Compiler) parseOnSection(frontmatter map[string]any, workflowData *Work baseName := strings.TrimSuffix(filepath.Base(markdownPath), ".md") workflowData.Command = []string{baseName} } - // Check for conflicting events (but allow issues/pull_request with non-conflicting types: labeled/unlabeled/ready_for_review) - conflictingEvents := []string{"issues", "issue_comment", "pull_request", "pull_request_review_comment"} - for _, eventName := range conflictingEvents { - if eventValue, hasConflict := onMap[eventName]; hasConflict { - // Special case: allow issues/pull_request with non-conflicting types - if (eventName == "issues" || eventName == "pull_request") && parser.IsNonConflictingCommandEvent(eventValue) { - continue // Allow this - it doesn't conflict with command triggers + // In centralized mode slash_command no longer compiles broad comment listeners, + // so slash/non-slash event co-existence is allowed. + if !workflowData.CommandCentralized { + // Check for conflicting events (but allow issues/pull_request with non-conflicting types: labeled/unlabeled/ready_for_review) + conflictingEvents := []string{"issues", "issue_comment", "pull_request", "pull_request_review_comment"} + for _, eventName := range conflictingEvents { + if eventValue, hasConflict := onMap[eventName]; hasConflict { + // Special case: allow issues/pull_request with non-conflicting types + if (eventName == "issues" || eventName == "pull_request") && parser.IsNonConflictingCommandEvent(eventValue) { + continue // Allow this - it doesn't conflict with command triggers + } + return fmt.Errorf("cannot use 'slash_command' with '%s' in the same workflow", eventName) } - return fmt.Errorf("cannot use 'slash_command' with '%s' in the same workflow", eventName) } } diff --git a/pkg/workflow/compiler_safe_outputs_test.go b/pkg/workflow/compiler_safe_outputs_test.go index 6735e333247..b820e3135cc 100644 --- a/pkg/workflow/compiler_safe_outputs_test.go +++ b/pkg/workflow/compiler_safe_outputs_test.go @@ -21,6 +21,7 @@ func TestParseOnSection(t *testing.T) { expectedReaction string expectedLockAgent bool expectedOn string + expectedCentralized bool checkCommandEvents bool expectedOtherEvents map[string]any }{ @@ -129,6 +130,26 @@ func TestParseOnSection(t *testing.T) { markdownPath: "/path/to/test.md", expectedError: true, }, + { + name: "slash_command centralized strategy allows non-slash events", + frontmatter: map[string]any{ + "on": map[string]any{ + "slash_command": map[string]any{ + "strategy": "centralized", + }, + "issue_comment": map[string]any{ + "types": []string{"created"}, + }, + }, + }, + workflowData: &WorkflowData{CommandCentralized: true}, + markdownPath: "/path/to/test.md", + expectedError: false, + expectedCommand: []string{"test"}, + expectedReaction: "eyes", + expectedCentralized: true, + checkCommandEvents: true, + }, { name: "slash_command conflicts with issues", frontmatter: map[string]any{ @@ -277,6 +298,7 @@ func TestParseOnSection(t *testing.T) { if tt.expectedReaction != "" { assert.Equal(t, tt.expectedReaction, tt.workflowData.AIReaction, "Reaction mismatch") } + assert.Equal(t, tt.expectedCentralized, tt.workflowData.CommandCentralized, "CommandCentralized mismatch") assert.Equal(t, tt.expectedLockAgent, tt.workflowData.LockForAgent, "LockForAgent mismatch") if tt.checkCommandEvents { assert.NotNil(t, tt.workflowData.CommandOtherEvents, "CommandOtherEvents should be set") @@ -369,6 +391,23 @@ func TestCompilerMergeSafeJobsFromIncludedConfigs(t *testing.T) { } } +func TestExtractCommandConfig_CentralizedStrategy(t *testing.T) { + c := &Compiler{} + names, events, centralized := c.extractCommandConfig(map[string]any{ + "on": map[string]any{ + "slash_command": map[string]any{ + "name": "deploy", + "events": []any{"issue_comment"}, + "strategy": "centralized", + }, + }, + }) + + assert.Equal(t, []string{"deploy"}, names) + assert.Equal(t, []string{"issue_comment"}, events) + assert.True(t, centralized) +} + // TestApplyDefaultTools tests default tool application logic func TestApplyDefaultTools(t *testing.T) { tests := []struct { diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 3452d7fca06..4ba571a1fcf 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -128,7 +128,7 @@ func NewCompiler(opts ...CompilerOption) *Compiler { artifactManager: NewArtifactManager(), actionPinWarnings: make(map[string]bool), // Initialize warning cache priorManifests: make(map[string]*GHAWManifest), - gitRoot: gitRoot, // Auto-detected git root + gitRoot: gitRoot, // Auto-detected git root } // Apply functional options @@ -475,6 +475,7 @@ type WorkflowData struct { ManualApproval string // environment name for manual approval from on: section Command []string // for /command trigger support - multiple command names CommandEvents []string // events where command should be active (nil = all events) + CommandCentralized bool // when true, slash_command uses centralized dispatch routing via workflow_dispatch CommandOtherEvents map[string]any // for merging command with other events LabelCommand []string // for label-command trigger support - label names that act as commands LabelCommandEvents []string // events where label-command should be active (nil = all: issues, pull_request, discussion) diff --git a/pkg/workflow/frontmatter_extraction_yaml.go b/pkg/workflow/frontmatter_extraction_yaml.go index 13f82bc39fe..75be714a9c2 100644 --- a/pkg/workflow/frontmatter_extraction_yaml.go +++ b/pkg/workflow/frontmatter_extraction_yaml.go @@ -957,8 +957,9 @@ func (c *Compiler) extractExpressionFromIfString(ifString string) string { return ifString } -// extractCommandConfig extracts command configuration from frontmatter including name and events -func (c *Compiler) extractCommandConfig(frontmatter map[string]any) (commandNames []string, commandEvents []string) { +// extractCommandConfig extracts command configuration from frontmatter including name, events, +// and centralized routing strategy for slash_command. +func (c *Compiler) extractCommandConfig(frontmatter map[string]any) (commandNames []string, commandEvents []string, commandCentralized bool) { frontmatterLog.Print("Extracting command configuration from frontmatter") // Check new format: on.slash_command or on.slash_command.name (preferred) // Also check legacy format: on.command or on.command.name (deprecated) @@ -990,12 +991,13 @@ func (c *Compiler) extractCommandConfig(frontmatter map[string]any) (commandName // Check if command is a string (shorthand format) if commandStr, ok := commandValue.(string); ok { frontmatterLog.Printf("Extracted command name (shorthand): %s", commandStr) - return []string{commandStr}, nil // nil means default (all events) + return []string{commandStr}, nil, false // nil means default (all events) } // Check if command is a map with a name key (object format) if commandMap, ok := commandValue.(map[string]any); ok { var names []string var events []string + centralized := false if nameValue, hasName := commandMap["name"]; hasName { // Handle string or array of strings @@ -1015,14 +1017,20 @@ func (c *Compiler) extractCommandConfig(frontmatter map[string]any) (commandName events = ParseCommandEvents(eventsValue) } - frontmatterLog.Printf("Extracted command config: names=%v, events=%v", names, events) - return names, events + if strategyRaw, hasStrategy := commandMap["strategy"]; hasStrategy { + if strategy, ok := strategyRaw.(string); ok && strings.EqualFold(strings.TrimSpace(strategy), "centralized") { + centralized = true + } + } + + frontmatterLog.Printf("Extracted command config: names=%v, events=%v, centralized=%v", names, events, centralized) + return names, events, centralized } } } } - return nil, nil + return nil, nil, false } // extractLabelCommandConfig extracts the label-command configuration from frontmatter diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go new file mode 100644 index 00000000000..b630edde081 --- /dev/null +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -0,0 +1,48 @@ +//go:build !integration + +package workflow + +import ( + "os" + "path/filepath" + "testing" + + "github.com/github/gh-aw/pkg/stringutil" + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/require" +) + +func TestCompileWorkflow_SlashCommandCentralizedStrategy(t *testing.T) { + tmpDir := testutil.TempDir(t, "workflow-centralized-slash-test") + + markdownPath := filepath.Join(tmpDir, "deploy.md") + content := `--- +on: + slash_command: + name: deploy + strategy: centralized + push: + branches: [main] +tools: + github: + allowed: [list_issues] +--- + +# Deploy +` + require.NoError(t, os.WriteFile(markdownPath, []byte(content), 0644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(markdownPath)) + + lockPath := stringutil.MarkdownToLockFile(markdownPath) + lockContent, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(lockContent) + + require.Contains(t, compiled, "workflow_dispatch:") + require.Contains(t, compiled, "push:") + require.NotContains(t, compiled, "issue_comment:") + require.NotContains(t, compiled, "pull_request_review_comment:") + require.NotContains(t, compiled, "startsWith(github.event.comment.body") +} diff --git a/pkg/workflow/tools.go b/pkg/workflow/tools.go index e19d6cfa347..1fea52d1db5 100644 --- a/pkg/workflow/tools.go +++ b/pkg/workflow/tools.go @@ -82,24 +82,37 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error if isCommandTrigger { toolsLog.Print("Workflow is command trigger, configuring command events") - // Get the filtered command events based on CommandEvents field - filteredEvents := FilterCommentEvents(data.CommandEvents) + commandEventsMap := make(map[string]any) + + // In centralized slash-command mode, compile slash workflows as + // workflow_dispatch-centric targets and preserve only non-slash events. + var filteredEvents []CommentEventMapping + if data.CommandCentralized { + if len(data.CommandOtherEvents) > 0 { + maps.Copy(commandEventsMap, data.CommandOtherEvents) + } + if _, hasWorkflowDispatch := commandEventsMap["workflow_dispatch"]; !hasWorkflowDispatch { + commandEventsMap["workflow_dispatch"] = nil + } + } else { + // Get the filtered command events based on CommandEvents field + filteredEvents = FilterCommentEvents(data.CommandEvents) - // Merge events for YAML generation (combines pull_request_comment and issue_comment into issue_comment) - yamlEvents := MergeEventsForYAML(filteredEvents) + // Merge events for YAML generation (combines pull_request_comment and issue_comment into issue_comment) + yamlEvents := MergeEventsForYAML(filteredEvents) - // Build command events map from merged events - commandEventsMap := make(map[string]any) - for _, event := range yamlEvents { - commandEventsMap[event.EventName] = map[string]any{ - "types": event.Types, + // Build command events map from merged events + for _, event := range yamlEvents { + commandEventsMap[event.EventName] = map[string]any{ + "types": event.Types, + } } - } - // Check if there are other events to merge - if len(data.CommandOtherEvents) > 0 { - // Merge other events into command events - maps.Copy(commandEventsMap, data.CommandOtherEvents) + // Check if there are other events to merge + if len(data.CommandOtherEvents) > 0 { + // Merge other events into command events + maps.Copy(commandEventsMap, data.CommandOtherEvents) + } } // If label_command is also configured alongside slash_command, merge label events @@ -159,27 +172,39 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error data.On = builder.String() } - // Add conditional logic to check for command in issue content - // Use event-aware condition that only applies command checks to comment-related events - // Pass the filtered events to buildEventAwareCommandCondition - hasOtherEvents := len(data.CommandOtherEvents) > 0 - commandConditionTree, err := buildEventAwareCommandCondition(data.Command, data.CommandEvents, hasOtherEvents) - if err != nil { - return fmt.Errorf("failed to build command condition: %w", err) - } + // Add conditional logic for command workflows unless centralized mode is enabled. + if !data.CommandCentralized { + // Add conditional logic to check for command in issue content + // Use event-aware condition that only applies command checks to comment-related events + // Pass the filtered events to buildEventAwareCommandCondition + hasOtherEvents := len(data.CommandOtherEvents) > 0 + commandConditionTree, err := buildEventAwareCommandCondition(data.Command, data.CommandEvents, hasOtherEvents) + if err != nil { + return fmt.Errorf("failed to build command condition: %w", err) + } - if data.If == "" { - if len(data.LabelCommand) > 0 { - // Combine: (slash_command condition) OR (label_command condition) - // This allows the workflow to activate via either mechanism. - labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, false) - if err != nil { - return fmt.Errorf("failed to build combined label-command condition: %w", err) + if data.If == "" { + if len(data.LabelCommand) > 0 { + // Combine: (slash_command condition) OR (label_command condition) + // This allows the workflow to activate via either mechanism. + labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, false) + if err != nil { + return fmt.Errorf("failed to build combined label-command condition: %w", err) + } + combined := &OrNode{Left: commandConditionTree, Right: labelConditionTree} + data.If = RenderCondition(combined) + } else { + data.If = RenderCondition(commandConditionTree) } - combined := &OrNode{Left: commandConditionTree, Right: labelConditionTree} - data.If = RenderCondition(combined) + } + } else if data.If == "" && len(data.LabelCommand) > 0 { + // Centralized command mode bypasses slash-command content checks. + // If label_command is also configured, keep label gating logic. + labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, false) + if err != nil { + return fmt.Errorf("failed to build label-command condition: %w", err) } else { - data.If = RenderCondition(commandConditionTree) + data.If = RenderCondition(labelConditionTree) } } } else if isLabelCommandTrigger { From 03ea49e8dfa429e8afc8b3299312fc2ebcbd00bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 01:29:06 +0000 Subject: [PATCH 02/40] Make archie and cloclo slash commands centralized Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/archie.lock.yml | 52 ++++++++++-------------- .github/workflows/archie.md | 1 + .github/workflows/cloclo.lock.yml | 66 ++++++++++++++----------------- .github/workflows/cloclo.md | 1 + 4 files changed, 52 insertions(+), 68 deletions(-) diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 46c80faef31..b953209cd6f 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"25e043a10b12ae51af58407610eb74c0d4d1505510e0ae0ee2a45da4550964b7","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e427885be33ad6455b1ebed0e9281d293ee3a8f441a448a2d6c355823d8c6ec8","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,20 +57,13 @@ name: "Archie" "on": - issue_comment: - types: - - created - - edited - issues: - types: - - opened - - edited - - reopened - pull_request: - types: - - opened - - edited - - reopened + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -89,13 +82,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/archie ') || startsWith(github.event.issue.body, '/archie\n') || github.event.issue.body == '/archie') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/archie ') || startsWith(github.event.comment.body, '/archie\n') || github.event.comment.body == '/archie') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/archie ') || startsWith(github.event.comment.body, '/archie\n') || github.event.comment.body == '/archie') && github.event.issue.pull_request != null || github.event_name == 'pull_request' && (startsWith(github.event.pull_request.body, '/archie ') || startsWith(github.event.pull_request.body, '/archie\n') || github.event.pull_request.body == '/archie'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -248,20 +239,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_2e7019d9465d12fb_EOF' + cat << 'GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF' - GH_AW_PROMPT_2e7019d9465d12fb_EOF + GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_2e7019d9465d12fb_EOF' + cat << 'GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_2e7019d9465d12fb_EOF + GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_2e7019d9465d12fb_EOF' + cat << 'GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -290,12 +281,12 @@ jobs: {{/if}} - GH_AW_PROMPT_2e7019d9465d12fb_EOF + GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_2e7019d9465d12fb_EOF' + cat << 'GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF' ## Serena Code Analysis @@ -332,7 +323,7 @@ jobs: {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/archie.md}} - GH_AW_PROMPT_2e7019d9465d12fb_EOF + GH_AW_PROMPT_dcd5b34c2b20e0e6_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -559,9 +550,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_756f1bb9f50d6955_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ca474fdaea1f47af_EOF' {"add_comment":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_756f1bb9f50d6955_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_ca474fdaea1f47af_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -749,7 +740,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_0dc2d1bd00cd3635_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d281e39c2bef3170_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -809,7 +800,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_0dc2d1bd00cd3635_EOF + GH_AW_MCP_CONFIG_d281e39c2bef3170_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1433,7 +1424,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/archie ') || startsWith(github.event.issue.body, '/archie\n') || github.event.issue.body == '/archie') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/archie ') || startsWith(github.event.comment.body, '/archie\n') || github.event.comment.body == '/archie') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/archie ') || startsWith(github.event.comment.body, '/archie\n') || github.event.comment.body == '/archie') && github.event.issue.pull_request != null || github.event_name == 'pull_request' && (startsWith(github.event.pull_request.body, '/archie ') || startsWith(github.event.pull_request.body, '/archie\n') || github.event.pull_request.body == '/archie'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/archie.md b/.github/workflows/archie.md index 6c3484d3dea..cbdb5c90b0d 100644 --- a/.github/workflows/archie.md +++ b/.github/workflows/archie.md @@ -4,6 +4,7 @@ description: Generates Mermaid diagrams to visualize issue and pull request rela on: slash_command: name: archie + strategy: centralized events: [issues, issue_comment, pull_request, pull_request_comment] reaction: eyes status-comment: true diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 4d4664af30d..56183cfa238 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7580a647bab899ad36421143e1965286e78b15bbb5431b173f3c6afa33b428c2","strict":true,"agent_id":"claude"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d6432dc724de4e1204e3c0a45bbdd44e700c72795e63ab0638639ebab2b22cec","strict":true,"agent_id":"claude"} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -66,33 +66,20 @@ name: "/cloclo" "on": discussion: types: - - created - - edited - labeled - discussion_comment: - types: - - created - - edited - issue_comment: - types: - - created - - edited issues: types: - - opened - - edited - - reopened - labeled pull_request: types: - - opened - - edited - - reopened - labeled - pull_request_review_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -112,7 +99,10 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/cloclo ') || startsWith(github.event.issue.body, '/cloclo\n') || github.event.issue.body == '/cloclo') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') || github.event_name == 'pull_request' && (startsWith(github.event.pull_request.body, '/cloclo ') || startsWith(github.event.pull_request.body, '/cloclo\n') || github.event.pull_request.body == '/cloclo') || github.event_name == 'discussion' && (startsWith(github.event.discussion.body, '/cloclo ') || startsWith(github.event.discussion.body, '/cloclo\n') || github.event.discussion.body == '/cloclo') || github.event_name == 'discussion_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') || github.event_name == 'issues' && github.event.label.name == 'cloclo' || github.event_name == 'pull_request' && github.event.label.name == 'cloclo' || github.event_name == 'discussion' && github.event.label.name == 'cloclo')" + if: > + needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && github.event.label.name == 'cloclo' || + github.event_name == 'pull_request' && github.event.label.name == 'cloclo' || github.event_name == 'discussion' && + github.event.label.name == 'cloclo') runs-on: ubuntu-slim permissions: actions: read @@ -293,9 +283,9 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_ecf4dc9a517c46f0_EOF' + cat << 'GH_AW_PROMPT_a2de0c26aa2bcf08_EOF' - GH_AW_PROMPT_ecf4dc9a517c46f0_EOF + GH_AW_PROMPT_a2de0c26aa2bcf08_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" @@ -303,16 +293,16 @@ jobs: cat "${RUNNER_TEMP}/gh-aw/prompts/agentic_workflows_guide.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_ecf4dc9a517c46f0_EOF' + cat << 'GH_AW_PROMPT_a2de0c26aa2bcf08_EOF' Tools: add_comment, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_ecf4dc9a517c46f0_EOF + GH_AW_PROMPT_a2de0c26aa2bcf08_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_ecf4dc9a517c46f0_EOF' + cat << 'GH_AW_PROMPT_a2de0c26aa2bcf08_EOF' - GH_AW_PROMPT_ecf4dc9a517c46f0_EOF + GH_AW_PROMPT_a2de0c26aa2bcf08_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_ecf4dc9a517c46f0_EOF' + cat << 'GH_AW_PROMPT_a2de0c26aa2bcf08_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -341,12 +331,12 @@ jobs: {{/if}} - GH_AW_PROMPT_ecf4dc9a517c46f0_EOF + GH_AW_PROMPT_a2de0c26aa2bcf08_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_ecf4dc9a517c46f0_EOF' + cat << 'GH_AW_PROMPT_a2de0c26aa2bcf08_EOF' ## Serena Code Analysis @@ -385,7 +375,7 @@ jobs: {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/cloclo.md}} - GH_AW_PROMPT_ecf4dc9a517c46f0_EOF + GH_AW_PROMPT_a2de0c26aa2bcf08_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -712,9 +702,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_576530abb8320c80_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_5b26a7d42b804af3_EOF' {"add_comment":{"max":1},"create_pull_request":{"excluded_files":[".github/workflows/*.lock.yml"],"expires":48,"labels":["automation","cloclo"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","CLAUDE.md","AGENTS.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[cloclo] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_576530abb8320c80_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_5b26a7d42b804af3_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -947,7 +937,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_6cb18be545797135_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_e54dacca88c9d411_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "agenticworkflows": { @@ -1037,7 +1027,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_6cb18be545797135_EOF + GH_AW_MCP_CONFIG_e54dacca88c9d411_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1753,7 +1743,9 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/cloclo ') || startsWith(github.event.issue.body, '/cloclo\n') || github.event.issue.body == '/cloclo') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') || github.event_name == 'pull_request' && (startsWith(github.event.pull_request.body, '/cloclo ') || startsWith(github.event.pull_request.body, '/cloclo\n') || github.event.pull_request.body == '/cloclo') || github.event_name == 'discussion' && (startsWith(github.event.discussion.body, '/cloclo ') || startsWith(github.event.discussion.body, '/cloclo\n') || github.event.discussion.body == '/cloclo') || github.event_name == 'discussion_comment' && (startsWith(github.event.comment.body, '/cloclo ') || startsWith(github.event.comment.body, '/cloclo\n') || github.event.comment.body == '/cloclo') || github.event_name == 'issues' && github.event.label.name == 'cloclo' || github.event_name == 'pull_request' && github.event.label.name == 'cloclo' || github.event_name == 'discussion' && github.event.label.name == 'cloclo')" + if: > + github.event_name == 'issues' && github.event.label.name == 'cloclo' || github.event_name == 'pull_request' && + github.event.label.name == 'cloclo' || github.event_name == 'discussion' && github.event.label.name == 'cloclo' runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/cloclo.md b/.github/workflows/cloclo.md index 75fe83b78bd..57607e0ca11 100644 --- a/.github/workflows/cloclo.md +++ b/.github/workflows/cloclo.md @@ -2,6 +2,7 @@ on: slash_command: name: cloclo + strategy: centralized label_command: cloclo status-comment: true permissions: From f027538087d55513ac42f5a2847680b7ad27a6af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 01:57:55 +0000 Subject: [PATCH 03/40] Plan centralized router filename fix Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agentics-slash-command-trigger.yml | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .github/workflows/agentics-slash-command-trigger.yml diff --git a/.github/workflows/agentics-slash-command-trigger.yml b/.github/workflows/agentics-slash-command-trigger.yml new file mode 100644 index 00000000000..ae5136ce23b --- /dev/null +++ b/.github/workflows/agentics-slash-command-trigger.yml @@ -0,0 +1,106 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by pkg/workflow/central_slash_command_workflow.go. DO NOT EDIT. +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +name: "Agentic Slash Command Trigger" + +on: + issues: + types: [edited, opened, reopened] + issue_comment: + types: [created, edited] + pull_request: + types: [edited, opened, reopened] + pull_request_review_comment: + types: [created, edited] + discussion: + types: [created, edited] + discussion_comment: + types: [created, edited] + +permissions: + actions: write + contents: read + +jobs: + route: + runs-on: ubuntu-slim + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Route slash command + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SLASH_ROUTING: '{"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}]}' + with: + script: | + const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); + const bodyByEvent = { + issues: context.payload?.issue?.body ?? "", + pull_request: context.payload?.pull_request?.body ?? "", + issue_comment: context.payload?.comment?.body ?? "", + pull_request_review_comment: context.payload?.comment?.body ?? "", + discussion: context.payload?.discussion?.body ?? "", + discussion_comment: context.payload?.comment?.body ?? "", + }; + + function eventIdentifier() { + if (context.eventName !== "issue_comment") { + return context.eventName; + } + return context.payload?.issue?.pull_request ? "pull_request_comment" : "issue_comment"; + } + + const text = bodyByEvent[context.eventName] ?? ""; + const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; + if (!firstWord.startsWith("/")) { + core.info("No slash command found at start of payload text; skipping dispatch."); + return; + } + + const commandName = firstWord.slice(1); + const identifier = eventIdentifier(); + const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); + if (routes.length === 0) { + core.info("No centralized routes matched command '/" + commandName + "' for event '" + identifier + "'."); + return; + } + + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + setupGlobals(core, github, context, exec, io, getOctokit); + const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); + + const ref = process.env.GITHUB_HEAD_REF ? "refs/heads/" + process.env.GITHUB_HEAD_REF : (process.env.GITHUB_REF || context.ref || "refs/heads/" + (context.payload?.repository?.default_branch || "main")); + for (const route of routes) { + const awContext = buildAwContext(); + awContext.command_name = commandName; + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: route.workflow + ".lock.yml", + ref, + inputs: { + aw_context: JSON.stringify(awContext), + }, + }); + core.info("Dispatched '" + route.workflow + "' for '/" + commandName + "'"); + } From 59e84f48bf2b8093768a3786fe504f79a3b1c8f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 02:01:19 +0000 Subject: [PATCH 04/40] Rename centralized slash router workflow output Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...trigger.yml => agentic_slash_commands.yml} | 0 .../docs/reference/command-triggers.md | 2 +- .../central_slash_command_workflow.go | 27 +++++++++++++++---- .../central_slash_command_workflow_test.go | 5 ++++ 4 files changed, 28 insertions(+), 6 deletions(-) rename .github/workflows/{agentics-slash-command-trigger.yml => agentic_slash_commands.yml} (100%) diff --git a/.github/workflows/agentics-slash-command-trigger.yml b/.github/workflows/agentic_slash_commands.yml similarity index 100% rename from .github/workflows/agentics-slash-command-trigger.yml rename to .github/workflows/agentic_slash_commands.yml diff --git a/docs/src/content/docs/reference/command-triggers.md b/docs/src/content/docs/reference/command-triggers.md index 4e75de666bf..c725c0c1613 100644 --- a/docs/src/content/docs/reference/command-triggers.md +++ b/docs/src/content/docs/reference/command-triggers.md @@ -72,7 +72,7 @@ on: Set `on.slash_command.strategy: centralized` to opt a workflow into centralized slash-command routing. When enabled, the workflow compiles as `workflow_dispatch`-centric, and the compiler generates one -shared `agentics-slash-command-trigger.yml` workflow that listens to merged slash-command events and +shared `agentic_slash_commands.yml` workflow that listens to merged slash-command events and dispatches matching target workflows with `aw_context`. ```yaml wrap diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index f874d87b4ca..b1ace4bdee6 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -14,7 +14,10 @@ import ( var centralSlashCommandWorkflowLog = logger.New("workflow:central_slash_command_workflow") -const centralSlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" +const ( + centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" + legacySlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" +) type slashCommandRoute struct { Workflow string `json:"workflow"` @@ -29,12 +32,14 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) triggerFile := filepath.Join(workflowDir, centralSlashCommandWorkflowFilename) + legacyTriggerFile := filepath.Join(workflowDir, legacySlashCommandWorkflowFilename) if len(routesByCommand) == 0 || len(mergedEvents) == 0 { centralSlashCommandWorkflowLog.Print("No centralized slash-command participants found") - if _, err := os.Stat(triggerFile); err == nil { - if err := os.Remove(triggerFile); err != nil { - return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) - } + if err := removeIfExists(triggerFile); err != nil { + return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) + } + if err := removeIfExists(legacyTriggerFile); err != nil { + return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) } return nil } @@ -47,10 +52,22 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf if err := os.WriteFile(triggerFile, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write centralized slash-command workflow: %w", err) } + if err := removeIfExists(legacyTriggerFile); err != nil { + return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) + } centralSlashCommandWorkflowLog.Printf("Wrote centralized slash-command workflow: %s", triggerFile) return nil } +func removeIfExists(path string) error { + if _, err := os.Stat(path); err == nil { + return os.Remove(path) + } else if !os.IsNotExist(err) { + return err + } + return nil +} + func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]bool) { routesByCommand := make(map[string][]slashCommandRoute) mergedEvents := make(map[string]map[string]bool) diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 3fd2c5d6214..b9358d31271 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -45,7 +45,9 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { tmpDir := testutil.TempDir(t, "central-slash-workflow-delete-test") generatedPath := filepath.Join(tmpDir, centralSlashCommandWorkflowFilename) + legacyGeneratedPath := filepath.Join(tmpDir, legacySlashCommandWorkflowFilename) require.NoError(t, os.WriteFile(generatedPath, []byte("stale"), 0644)) + require.NoError(t, os.WriteFile(legacyGeneratedPath, []byte("stale"), 0644)) data := []*WorkflowData{ { @@ -60,4 +62,7 @@ func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { _, err := os.Stat(generatedPath) require.Error(t, err) require.True(t, os.IsNotExist(err)) + _, err = os.Stat(legacyGeneratedPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) } From 6f887717353a7bf4e332adeec444e553f3f3907d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 02:05:26 +0000 Subject: [PATCH 05/40] Polish centralized router filename migration logic Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/central_slash_command_workflow.go | 6 +++--- .../central_slash_command_workflow_test.go | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index b1ace4bdee6..36683bbe583 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -15,8 +15,8 @@ import ( var centralSlashCommandWorkflowLog = logger.New("workflow:central_slash_command_workflow") const ( - centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" - legacySlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" + centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" + legacyCentralSlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" ) type slashCommandRoute struct { @@ -32,7 +32,7 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) triggerFile := filepath.Join(workflowDir, centralSlashCommandWorkflowFilename) - legacyTriggerFile := filepath.Join(workflowDir, legacySlashCommandWorkflowFilename) + legacyTriggerFile := filepath.Join(workflowDir, legacyCentralSlashCommandWorkflowFilename) if len(routesByCommand) == 0 || len(mergedEvents) == 0 { centralSlashCommandWorkflowLog.Print("No centralized slash-command participants found") if err := removeIfExists(triggerFile); err != nil { diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index b9358d31271..d05785657a3 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -45,7 +45,7 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { tmpDir := testutil.TempDir(t, "central-slash-workflow-delete-test") generatedPath := filepath.Join(tmpDir, centralSlashCommandWorkflowFilename) - legacyGeneratedPath := filepath.Join(tmpDir, legacySlashCommandWorkflowFilename) + legacyGeneratedPath := filepath.Join(tmpDir, legacyCentralSlashCommandWorkflowFilename) require.NoError(t, os.WriteFile(generatedPath, []byte("stale"), 0644)) require.NoError(t, os.WriteFile(legacyGeneratedPath, []byte("stale"), 0644)) @@ -66,3 +66,17 @@ func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { require.Error(t, err) require.True(t, os.IsNotExist(err)) } + +func TestRemoveIfExists(t *testing.T) { + tmpDir := testutil.TempDir(t, "remove-if-exists-test") + existingPath := filepath.Join(tmpDir, "existing.txt") + missingPath := filepath.Join(tmpDir, "missing.txt") + + require.NoError(t, os.WriteFile(existingPath, []byte("content"), 0644)) + require.NoError(t, removeIfExists(existingPath)) + _, err := os.Stat(existingPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + + require.NoError(t, removeIfExists(missingPath)) +} From 2f2666ba94c7cae50ffde4528dc17779b29b47f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 02:13:27 +0000 Subject: [PATCH 06/40] Remove legacy centralized trigger cleanup logic Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/central_slash_command_workflow.go | 10 +--------- pkg/workflow/central_slash_command_workflow_test.go | 5 ----- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 36683bbe583..e8259d7d895 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -15,8 +15,7 @@ import ( var centralSlashCommandWorkflowLog = logger.New("workflow:central_slash_command_workflow") const ( - centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" - legacyCentralSlashCommandWorkflowFilename = "agentics-slash-command-trigger.yml" + centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" ) type slashCommandRoute struct { @@ -32,15 +31,11 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) triggerFile := filepath.Join(workflowDir, centralSlashCommandWorkflowFilename) - legacyTriggerFile := filepath.Join(workflowDir, legacyCentralSlashCommandWorkflowFilename) if len(routesByCommand) == 0 || len(mergedEvents) == 0 { centralSlashCommandWorkflowLog.Print("No centralized slash-command participants found") if err := removeIfExists(triggerFile); err != nil { return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) } - if err := removeIfExists(legacyTriggerFile); err != nil { - return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) - } return nil } @@ -52,9 +47,6 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf if err := os.WriteFile(triggerFile, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write centralized slash-command workflow: %w", err) } - if err := removeIfExists(legacyTriggerFile); err != nil { - return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) - } centralSlashCommandWorkflowLog.Printf("Wrote centralized slash-command workflow: %s", triggerFile) return nil } diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index d05785657a3..a6dec99000e 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -45,9 +45,7 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { tmpDir := testutil.TempDir(t, "central-slash-workflow-delete-test") generatedPath := filepath.Join(tmpDir, centralSlashCommandWorkflowFilename) - legacyGeneratedPath := filepath.Join(tmpDir, legacyCentralSlashCommandWorkflowFilename) require.NoError(t, os.WriteFile(generatedPath, []byte("stale"), 0644)) - require.NoError(t, os.WriteFile(legacyGeneratedPath, []byte("stale"), 0644)) data := []*WorkflowData{ { @@ -62,9 +60,6 @@ func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { _, err := os.Stat(generatedPath) require.Error(t, err) require.True(t, os.IsNotExist(err)) - _, err = os.Stat(legacyGeneratedPath) - require.Error(t, err) - require.True(t, os.IsNotExist(err)) } func TestRemoveIfExists(t *testing.T) { From df243c37c63c5089f5ec229fe2b98b9f3ba98f29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:21:30 +0000 Subject: [PATCH 07/40] Scope slash router permissions per job Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_slash_commands.yml | 6 ++++-- pkg/workflow/central_slash_command_workflow.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/agentic_slash_commands.yml b/.github/workflows/agentic_slash_commands.yml index ae5136ce23b..fcecc372644 100644 --- a/.github/workflows/agentic_slash_commands.yml +++ b/.github/workflows/agentic_slash_commands.yml @@ -37,12 +37,14 @@ on: types: [created, edited] permissions: - actions: write - contents: read + {} jobs: route: runs-on: ubuntu-slim + permissions: + actions: write + contents: read steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index e8259d7d895..3090e9dc16c 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -126,12 +126,14 @@ on: writeCentralSlashEventsYAML(&b, mergedEvents) b.WriteString(` permissions: - actions: write - contents: read + {} jobs: route: runs-on: ubuntu-slim + permissions: + actions: write + contents: read steps: - name: Checkout repository uses: ` + getActionPin("actions/checkout") + ` From a228c7dce0104866ff85af607a056d5e4d8c594b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:26:19 +0000 Subject: [PATCH 08/40] Use explicit empty top-level permissions mapping Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_slash_commands.yml | 3 +-- pkg/workflow/central_slash_command_workflow.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/agentic_slash_commands.yml b/.github/workflows/agentic_slash_commands.yml index fcecc372644..c2183bdcf65 100644 --- a/.github/workflows/agentic_slash_commands.yml +++ b/.github/workflows/agentic_slash_commands.yml @@ -36,8 +36,7 @@ on: discussion_comment: types: [created, edited] -permissions: - {} +permissions: {} jobs: route: diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 3090e9dc16c..0dee6dcaf73 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -125,8 +125,7 @@ on: `) writeCentralSlashEventsYAML(&b, mergedEvents) b.WriteString(` -permissions: - {} +permissions: {} jobs: route: From 3423a6fc3e085197edfb564f424f02cd816455f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:40:21 +0000 Subject: [PATCH 09/40] Add centralized slash router trigger generation tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../central_slash_command_workflow_test.go | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index a6dec99000e..d65ae74fc81 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -16,15 +16,21 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { data := []*WorkflowData{ { - WorkflowID: "triage", + WorkflowID: "triage-issue", Command: []string{"triage"}, - CommandEvents: []string{"issue_comment"}, + CommandEvents: []string{"issue_comment", "issues"}, CommandCentralized: true, }, { - WorkflowID: "review", + WorkflowID: "triage-pr", Command: []string{"triage"}, - CommandEvents: []string{"pull_request_comment"}, + CommandEvents: []string{"pull_request", "pull_request_comment"}, + CommandCentralized: true, + }, + { + WorkflowID: "cloclo", + Command: []string{"cloclo"}, + CommandEvents: []string{"discussion_comment"}, CommandCentralized: true, }, } @@ -37,8 +43,15 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { text := string(content) require.Contains(t, text, "name: \"Agentic Slash Command Trigger\"") + require.Contains(t, text, "permissions: {}") + require.Contains(t, text, " permissions:\n actions: write\n contents: read") + require.Contains(t, text, "issues:") require.Contains(t, text, "issue_comment:") - require.Contains(t, text, `"triage":[{"workflow":"review","events":["pull_request_comment"]},{"workflow":"triage","events":["issue_comment"]}]`) + require.Contains(t, text, "pull_request:") + require.Contains(t, text, "discussion_comment:") + require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) + require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) + require.Contains(t, text, `const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier));`) require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) } @@ -75,3 +88,46 @@ func TestRemoveIfExists(t *testing.T) { require.NoError(t, removeIfExists(missingPath)) } + +func TestCollectCentralSlashCommandRoutes_UnionizesMergedEvents(t *testing.T) { + data := []*WorkflowData{ + { + WorkflowID: "triage-issue", + Command: []string{"triage"}, + CommandEvents: []string{"issues", "issue_comment"}, + CommandCentralized: true, + }, + { + WorkflowID: "triage-pr", + Command: []string{"triage"}, + CommandEvents: []string{"pull_request", "pull_request_comment"}, + CommandCentralized: true, + }, + { + WorkflowID: "non-centralized", + Command: []string{"triage"}, + CommandEvents: []string{"discussion"}, + CommandCentralized: false, + }, + } + + routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(data) + + require.Equal(t, []slashCommandRoute{ + {Workflow: "triage-issue", Events: []string{"issue_comment", "issues"}}, + {Workflow: "triage-pr", Events: []string{"pull_request", "pull_request_comment"}}, + }, routesByCommand["triage"]) + + require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, keys(mergedEvents["issues"])) + require.ElementsMatch(t, []string{"created", "edited"}, keys(mergedEvents["issue_comment"])) + require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, keys(mergedEvents["pull_request"])) + require.NotContains(t, mergedEvents, "discussion") +} + +func keys(typeSet map[string]bool) []string { + out := make([]string, 0, len(typeSet)) + for key := range typeSet { + out = append(out, key) + } + return out +} From 5bb0d478a2cc16853db929137d2585017dbc6bb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:41:57 +0000 Subject: [PATCH 10/40] Polish centralized slash router test helper naming Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/central_slash_command_workflow_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index d65ae74fc81..36a1a01016e 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -118,13 +118,13 @@ func TestCollectCentralSlashCommandRoutes_UnionizesMergedEvents(t *testing.T) { {Workflow: "triage-pr", Events: []string{"pull_request", "pull_request_comment"}}, }, routesByCommand["triage"]) - require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, keys(mergedEvents["issues"])) - require.ElementsMatch(t, []string{"created", "edited"}, keys(mergedEvents["issue_comment"])) - require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, keys(mergedEvents["pull_request"])) + require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, typeSetKeys(mergedEvents["issues"])) + require.ElementsMatch(t, []string{"created", "edited"}, typeSetKeys(mergedEvents["issue_comment"])) + require.ElementsMatch(t, []string{"opened", "edited", "reopened"}, typeSetKeys(mergedEvents["pull_request"])) require.NotContains(t, mergedEvents, "discussion") } -func keys(typeSet map[string]bool) []string { +func typeSetKeys(typeSet map[string]bool) []string { out := make([]string, 0, len(typeSet)) for key := range typeSet { out = append(out, key) From 9e53098cf8188b4aaae0d470946bb49278ac9fef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:51:55 +0000 Subject: [PATCH 11/40] Warn when many slash commands are non-centralized Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_pipeline.go | 3 + pkg/cli/compile_post_processing.go | 33 ++++++ .../compile_post_processing_warning_test.go | 108 ++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 pkg/cli/compile_post_processing_warning_test.go diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index fd53292f7f4..3089398230b 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -346,6 +346,9 @@ func compileAllFilesInDirectory( } } + // Emit recommendation when many slash commands are present without centralized strategy. + displayCentralizedSlashCommandRecommendation(compiler, workflowDataList, config.JSONOutput) + // Get warning count from compiler stats.Warnings = compiler.GetWarningCount() diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 7a83078092e..4f235dcea31 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -236,6 +236,39 @@ func displaySafeUpdateWarnings(compiler *workflow.Compiler, jsonOutput bool) { } } +// displayCentralizedSlashCommandRecommendation warns when a repository has many +// slash commands still using non-centralized strategy. +func displayCentralizedSlashCommandRecommendation(compiler *workflow.Compiler, workflowDataList []*workflow.WorkflowData, jsonOutput bool) { + if jsonOutput { + return + } + + totalSlashCommands := 0 + nonCentralizedSlashCommands := 0 + for _, wd := range workflowDataList { + if wd == nil || len(wd.Command) == 0 { + continue + } + totalSlashCommands += len(wd.Command) + if !wd.CommandCentralized { + nonCentralizedSlashCommands += len(wd.Command) + } + } + + if totalSlashCommands < 3 || nonCentralizedSlashCommands == 0 { + return + } + + fmt.Fprintln(os.Stderr, console.FormatWarningMessage( + fmt.Sprintf( + "Detected %d slash_command entries in this repository; %d are not using centralized routing. Consider setting `on.slash_command.strategy: centralized` to reduce duplicate triggers and route through `agentic_slash_commands.yml`.", + totalSlashCommands, + nonCentralizedSlashCommands, + ), + )) + compiler.IncrementWarningCount() +} + // pruneStaleActionCacheEntries removes stale gh-aw-actions entries from the // action cache whose version does not match the compiler's current version. // This prevents actions-lock.json from accumulating entries for old compiler diff --git a/pkg/cli/compile_post_processing_warning_test.go b/pkg/cli/compile_post_processing_warning_test.go new file mode 100644 index 00000000000..da6f99210dc --- /dev/null +++ b/pkg/cli/compile_post_processing_warning_test.go @@ -0,0 +1,108 @@ +//go:build !integration + +package cli + +import ( + "bytes" + "io" + "os" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/workflow" + "github.com/stretchr/testify/require" +) + +func TestDisplayCentralizedSlashCommandRecommendation(t *testing.T) { + tests := []struct { + name string + workflows []*workflow.WorkflowData + jsonOutput bool + expectWarning bool + expectedWarnCount int + }{ + { + name: "warns when three slash commands include non centralized workflows", + workflows: []*workflow.WorkflowData{ + {Command: []string{"a"}, CommandCentralized: false}, + {Command: []string{"b"}, CommandCentralized: false}, + {Command: []string{"c"}, CommandCentralized: true}, + }, + expectWarning: true, + expectedWarnCount: 1, + }, + { + name: "does not warn when fewer than three slash commands exist", + workflows: []*workflow.WorkflowData{ + {Command: []string{"a"}, CommandCentralized: false}, + {Command: []string{"b"}, CommandCentralized: false}, + }, + expectWarning: false, + expectedWarnCount: 0, + }, + { + name: "does not warn when all slash commands are centralized", + workflows: []*workflow.WorkflowData{ + {Command: []string{"a"}, CommandCentralized: true}, + {Command: []string{"b"}, CommandCentralized: true}, + {Command: []string{"c"}, CommandCentralized: true}, + }, + expectWarning: false, + expectedWarnCount: 0, + }, + { + name: "does not warn for json output mode", + workflows: []*workflow.WorkflowData{ + {Command: []string{"a"}, CommandCentralized: false}, + {Command: []string{"b"}, CommandCentralized: false}, + {Command: []string{"c"}, CommandCentralized: false}, + }, + jsonOutput: true, + expectWarning: false, + expectedWarnCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + compiler := workflow.NewCompiler() + + stderrOutput := captureStderr(t, func() { + displayCentralizedSlashCommandRecommendation(compiler, tt.workflows, tt.jsonOutput) + }) + + if tt.expectWarning { + require.Contains(t, stderrOutput, "Consider setting `on.slash_command.strategy: centralized`") + require.Contains(t, stderrOutput, "Detected 3 slash_command entries") + } else { + require.NotContains(t, stderrOutput, "on.slash_command.strategy: centralized") + } + + require.Equal(t, tt.expectedWarnCount, compiler.GetWarningCount()) + }) + } +} + +func captureStderr(t *testing.T, fn func()) string { + t.Helper() + + oldStderr := os.Stderr + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stderr = w + t.Cleanup(func() { + os.Stderr = oldStderr + _ = r.Close() + _ = w.Close() + }) + + fn() + + require.NoError(t, w.Close()) + os.Stderr = oldStderr + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + return strings.TrimSpace(buf.String()) +} From 089bcc904739b816fb739d02ff5b945a58708d3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 03:54:01 +0000 Subject: [PATCH 12/40] Polish slash strategy warning implementation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_post_processing.go | 13 ++++---- .../compile_post_processing_warning_test.go | 31 ++----------------- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 4f235dcea31..d57cc085802 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -259,13 +259,12 @@ func displayCentralizedSlashCommandRecommendation(compiler *workflow.Compiler, w return } - fmt.Fprintln(os.Stderr, console.FormatWarningMessage( - fmt.Sprintf( - "Detected %d slash_command entries in this repository; %d are not using centralized routing. Consider setting `on.slash_command.strategy: centralized` to reduce duplicate triggers and route through `agentic_slash_commands.yml`.", - totalSlashCommands, - nonCentralizedSlashCommands, - ), - )) + msg := fmt.Sprintf( + "Detected %d slash_command entries in this repository; %d are not using centralized routing. Consider setting `on.slash_command.strategy: centralized` to reduce duplicate triggers and route through `agentic_slash_commands.yml`.", + totalSlashCommands, + nonCentralizedSlashCommands, + ) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(msg)) compiler.IncrementWarningCount() } diff --git a/pkg/cli/compile_post_processing_warning_test.go b/pkg/cli/compile_post_processing_warning_test.go index da6f99210dc..fa9ac5fd2ee 100644 --- a/pkg/cli/compile_post_processing_warning_test.go +++ b/pkg/cli/compile_post_processing_warning_test.go @@ -3,12 +3,9 @@ package cli import ( - "bytes" - "io" - "os" - "strings" "testing" + "github.com/github/gh-aw/pkg/testutil" "github.com/github/gh-aw/pkg/workflow" "github.com/stretchr/testify/require" ) @@ -67,7 +64,7 @@ func TestDisplayCentralizedSlashCommandRecommendation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { compiler := workflow.NewCompiler() - stderrOutput := captureStderr(t, func() { + stderrOutput := testutil.CaptureStderr(t, func() { displayCentralizedSlashCommandRecommendation(compiler, tt.workflows, tt.jsonOutput) }) @@ -82,27 +79,3 @@ func TestDisplayCentralizedSlashCommandRecommendation(t *testing.T) { }) } } - -func captureStderr(t *testing.T, fn func()) string { - t.Helper() - - oldStderr := os.Stderr - r, w, err := os.Pipe() - require.NoError(t, err) - os.Stderr = w - t.Cleanup(func() { - os.Stderr = oldStderr - _ = r.Close() - _ = w.Close() - }) - - fn() - - require.NoError(t, w.Close()) - os.Stderr = oldStderr - - var buf bytes.Buffer - _, err = io.Copy(&buf, r) - require.NoError(t, err) - return strings.TrimSpace(buf.String()) -} From 8a36773d3fb9d1c253edd968efbbcca33b4bb379 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 04:08:58 +0000 Subject: [PATCH 13/40] Add draft ADR-31605 for centralized slash-command routing Co-Authored-By: Claude Opus 4.7 (1M context) --- ...31605-centralized-slash-command-routing.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/adr/31605-centralized-slash-command-routing.md diff --git a/docs/adr/31605-centralized-slash-command-routing.md b/docs/adr/31605-centralized-slash-command-routing.md new file mode 100644 index 00000000000..fb9c4ae810d --- /dev/null +++ b/docs/adr/31605-centralized-slash-command-routing.md @@ -0,0 +1,97 @@ +# ADR-31605: Centralized Slash-Command Routing via Generated Agentic Router Workflow + +**Date**: 2026-05-12 +**Status**: Draft +**Deciders**: Unknown — *[TODO: PR author to confirm]* + +--- + +## Part 1 — Narrative (Human-Friendly) + +### Context + +Each slash-command workflow in this repository previously registered its own listeners for `issues`, `issue_comment`, `pull_request`, `pull_request_review_comment`, `discussion`, and `discussion_comment`. With many such workflows (e.g. `/archie`, `/cloclo`, and a growing fleet), this caused every comment, issue, or PR event to wake up many lock-files, each evaluating long `if:` expressions to decide whether its slash command matched. The duplication inflated GitHub Actions usage, made permissions sprawl across workflows, and produced large compiled `if:` predicates that were hard to read and maintain. The compiler also lacked guidance to nudge authors toward a shared router as the slash-command fleet grew. + +### Decision + +We will introduce a `centralized` strategy for `on.slash_command` and have the compiler generate a single shared router workflow at `.github/workflows/agentic_slash_commands.yml` that owns the merged set of slash-command events and dispatches matching target workflows via `workflow_dispatch` with an `aw_context` input. Participating workflows (those declaring `strategy: centralized`) compile to `workflow_dispatch`-only triggers, retaining their non-slash events (e.g. label-only triggers) but delegating slash detection to the central router. The compiler additionally emits a warning recommending `strategy: centralized` once three or more slash commands are detected and some remain non-centralized, so the convention scales as the fleet grows. + +### Alternatives Considered + +#### Alternative 1: Keep per-workflow inline listeners (status quo) + +Each slash-command workflow continues to declare its own event listeners and inline `if:` predicate. This is simple and decentralized — each workflow is self-contained — but it scales poorly: every comment/issue/PR event fans out to N workflows, each compiling N progressively longer `if:` expressions. It was rejected because the cost is visible today (duplicated runs, large generated predicates) and grows linearly with the slash-command fleet. + +#### Alternative 2: One static, hand-maintained router workflow + +Maintain `agentic_slash_commands.yml` by hand and require workflow authors to register their command in it manually. This avoids compiler complexity but reintroduces a long-running coordination problem: every new slash command requires editing a shared file, and the registry can drift from the per-workflow frontmatter. Rejected because compiler-generation of the router from frontmatter (`strategy: centralized`) preserves a single source of truth and avoids merge conflicts on the shared router. + +#### Alternative 3: Use a `repository_dispatch` or external broker + +Forward slash events to an external service (or `repository_dispatch`) that then triggers the right workflow. This decouples GitHub event listeners entirely but adds an out-of-repo dependency, new auth surface, and operational risk. Rejected because `workflow_dispatch` + a generated router stays inside GitHub Actions and requires no new infrastructure. + +### Consequences + +#### Positive +- One shared workflow (`agentic_slash_commands.yml`) handles slash-event listening for all participating commands, replacing N copies of the same listener set. +- Generated `if:` predicates on participating workflows shrink from large slash-text expressions to simple `workflow_dispatch`-gated activations, improving readability of lock files. +- Workflow-level permissions on participating lock files are reduced (e.g. dropping `issues: write` / `pull-requests: write` from activation jobs that no longer need them) because routing logic lives in the central router with its own scoped `actions: write` job permission. +- A compile-time warning nudges authors toward `strategy: centralized` once three or more slash commands exist, making the convention discoverable without a manual migration push. + +#### Negative +- Adds a new generated file (`.github/workflows/agentic_slash_commands.yml`) whose lifecycle is owned by the compiler — contributors must understand it is regenerated and not edited by hand. +- Introduces an indirection: a slash command now arrives via `workflow_dispatch` triggered by another workflow, which complicates debugging (two runs to inspect instead of one) and shifts some auth context onto `aw_context`. +- The router holds `actions: write` permission to dispatch other workflows; a bug in routing logic could dispatch the wrong workflow, so the route map and event-matching filter must remain trustworthy. +- Legacy trigger-file handling for `agentics-slash-command-trigger.yml` was removed; any external references to that file name become stale. + +#### Neutral +- Two existing workflows (`/archie`, `/cloclo`) were migrated as part of this PR; remaining slash workflows continue to use the default inline strategy until opted in. +- The router serializes inbound command resolution via `aw_context.command_name`, requiring `check_command_position.cjs` to learn a new `workflow_dispatch` code path (added in this PR with tests). +- Documentation under `docs/src/content/docs/reference/command-triggers.md` was updated to describe both strategies side-by-side. + +--- + +## Part 2 — Normative Specification (RFC 2119) + +> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +### Centralized Strategy Selection + +1. A slash-command workflow **MAY** opt into centralized routing by setting `on.slash_command.strategy: centralized` in its frontmatter. +2. The compiler **MUST** treat `strategy: centralized` as the participation flag — a workflow without that key **MUST NOT** be wired into the central router. +3. When at least one workflow opts into `strategy: centralized`, the compiler **MUST** generate exactly one router workflow file at `.github/workflows/agentic_slash_commands.yml`. +4. The generated router file **MUST** be regenerable from frontmatter alone and **MUST NOT** be hand-edited; contributors **SHOULD** treat it as compiler output. + +### Router Workflow Structure + +1. The generated router **MUST** declare `permissions: {}` at the top level (no workflow-wide permissions). +2. The router job named `route` **MUST** declare scoped job-level permissions of at minimum `actions: write` and `contents: read`, and **MUST NOT** declare broader permissions than required to dispatch participating workflows. +3. The router **MUST** listen on the **union** of slash-event types declared by participating workflows (e.g. `issues`, `issue_comment`, `pull_request`, `pull_request_review_comment`, `discussion`, `discussion_comment`) and **MUST NOT** listen on events for which no participating workflow has subscribed. +4. The router **MUST** dispatch a participating workflow only when both the command name (parsed from the first token of the payload body) and the inbound event identifier match an entry in the generated route map. +5. The router **MUST** pass an `aw_context` JSON input containing at least `command_name` to the dispatched workflow. + +### Participating Workflow Compilation + +1. A workflow with `strategy: centralized` **MUST** compile with `workflow_dispatch` as a trigger and **MUST** accept an `aw_context` string input. +2. A workflow with `strategy: centralized` **MUST NOT** re-declare slash-text matching on `issue_comment`, `pull_request_review_comment`, `discussion`, or `discussion_comment` in its compiled lock file; slash matching is the router's responsibility. +3. A workflow with `strategy: centralized` **MAY** retain non-slash listeners that do not collide with slash routing (for example, label-only triggers on `issues`, `pull_request`, or `discussion`). +4. The compiled activation `if:` predicate of a centralized workflow **MUST NOT** include slash-text inspection of payload bodies. + +### Inbound Command Resolution + +1. Setup logic processing `workflow_dispatch` events **MUST** read `command_name` from `aw_context.inputs.aw_context` JSON when present. +2. If `aw_context.command_name` is present, the setup logic **MUST** verify it is in the configured commands list and **MUST** fail the command-position check (emit a denial summary) when it is not. +3. Manual `workflow_dispatch` invocations without `aw_context.command_name` **SHOULD** pass the command-position check to preserve existing manual-run behavior. + +### Compiler Guidance + +1. The compiler **SHOULD** emit a warning recommending `strategy: centralized` when three or more slash commands are detected in the repository and at least one of them does not declare `strategy: centralized`. +2. The warning **MUST NOT** block compilation; it is advisory only. + +### Conformance + +An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance. + +--- + +*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/25712590786) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* From e471720c3f00afddc2f7cba908a20987a08d0cf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 04:23:16 +0000 Subject: [PATCH 14/40] Fix centralized slash+label gating and add regression test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../slash_command_centralized_compile_test.go | 36 +++++++++++++++++++ pkg/workflow/tools.go | 19 ++-------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go index b630edde081..e58706bc9db 100644 --- a/pkg/workflow/slash_command_centralized_compile_test.go +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -46,3 +46,39 @@ tools: require.NotContains(t, compiled, "pull_request_review_comment:") require.NotContains(t, compiled, "startsWith(github.event.comment.body") } + +func TestCompileWorkflow_SlashCommandCentralizedWithLabelCommand(t *testing.T) { + tmpDir := testutil.TempDir(t, "workflow-centralized-slash-label-test") + + markdownPath := filepath.Join(tmpDir, "triage.md") + content := `--- +on: + slash_command: + name: triage + strategy: centralized + label_command: + name: triage + events: [issues] +tools: + github: + allowed: [list_issues] +--- + +# Triage +` + require.NoError(t, os.WriteFile(markdownPath, []byte(content), 0644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(markdownPath)) + + lockPath := stringutil.MarkdownToLockFile(markdownPath) + lockContent, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(lockContent) + + require.Contains(t, compiled, "workflow_dispatch:") + require.Contains(t, compiled, "issues:") + require.Contains(t, compiled, "types:\n - labeled") + require.Contains(t, compiled, "github.event.label.name == 'triage'") + require.Contains(t, compiled, "|| !(github.event_name == 'issues')") +} diff --git a/pkg/workflow/tools.go b/pkg/workflow/tools.go index 1fea52d1db5..9dcb81d0dcf 100644 --- a/pkg/workflow/tools.go +++ b/pkg/workflow/tools.go @@ -154,22 +154,7 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error // Keep "on" quoted as it's a YAML boolean keyword data.On = yamlStr } else { - // If conversion fails, build a basic YAML string manually - var builder strings.Builder - builder.WriteString(`"on":`) - for _, event := range filteredEvents { - builder.WriteString("\n ") - builder.WriteString(event.EventName) - builder.WriteString(":\n types: [") - for i, t := range event.Types { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(t) - } - builder.WriteString("]") - } - data.On = builder.String() + return fmt.Errorf("failed to marshal command events: %w", err) } // Add conditional logic for command workflows unless centralized mode is enabled. @@ -200,7 +185,7 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error } else if data.If == "" && len(data.LabelCommand) > 0 { // Centralized command mode bypasses slash-command content checks. // If label_command is also configured, keep label gating logic. - labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, false) + labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, true) if err != nil { return fmt.Errorf("failed to build label-command condition: %w", err) } else { From dd5fb5ba535a7fc26e25e4fd47ba6fa731e7586e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 04:24:07 +0000 Subject: [PATCH 15/40] Document centralized label condition dispatch allowance Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/tools.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/workflow/tools.go b/pkg/workflow/tools.go index 9dcb81d0dcf..7466211ccd8 100644 --- a/pkg/workflow/tools.go +++ b/pkg/workflow/tools.go @@ -185,6 +185,7 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error } else if data.If == "" && len(data.LabelCommand) > 0 { // Centralized command mode bypasses slash-command content checks. // If label_command is also configured, keep label gating logic. + // hasOtherEvents=true keeps router workflow_dispatch runs eligible. labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, true) if err != nil { return fmt.Errorf("failed to build label-command condition: %w", err) From e762f4a078b3990c52c38e4d798e01a9ee0286b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 04:40:29 +0000 Subject: [PATCH 16/40] Regenerate cloclo lock with centralized dispatch gating Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/cloclo.lock.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 56183cfa238..2979817842b 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -100,9 +100,9 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && github.event.label.name == 'cloclo' || - github.event_name == 'pull_request' && github.event.label.name == 'cloclo' || github.event_name == 'discussion' && - github.event.label.name == 'cloclo') + needs.pre_activation.outputs.activated == 'true' && ((github.event_name == 'issues' || github.event_name == 'pull_request' || + github.event_name == 'discussion') && github.event.label.name == 'cloclo' || (!(github.event_name == 'issues')) && + (!(github.event_name == 'pull_request')) && (!(github.event_name == 'discussion'))) runs-on: ubuntu-slim permissions: actions: read @@ -1744,8 +1744,9 @@ jobs: pre_activation: if: > - github.event_name == 'issues' && github.event.label.name == 'cloclo' || github.event_name == 'pull_request' && - github.event.label.name == 'cloclo' || github.event_name == 'discussion' && github.event.label.name == 'cloclo' + (github.event_name == 'issues' || github.event_name == 'pull_request' || github.event_name == 'discussion') && + github.event.label.name == 'cloclo' || (!(github.event_name == 'issues')) && (!(github.event_name == 'pull_request')) && + (!(github.event_name == 'discussion')) runs-on: ubuntu-slim permissions: contents: read From 0fbf69e221b2771476ad2319847583466e4ca979 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 04:54:21 +0000 Subject: [PATCH 17/40] Rename centralized router workflow to agentic_commands Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...tic_slash_commands.yml => agentic_commands.yml} | 0 .../adr/31605-centralized-slash-command-routing.md | 10 +++++----- .../src/content/docs/reference/command-triggers.md | 2 +- pkg/cli/compile_post_processing.go | 2 +- pkg/workflow/central_slash_command_workflow.go | 14 +++++++++++++- 5 files changed, 20 insertions(+), 8 deletions(-) rename .github/workflows/{agentic_slash_commands.yml => agentic_commands.yml} (100%) diff --git a/.github/workflows/agentic_slash_commands.yml b/.github/workflows/agentic_commands.yml similarity index 100% rename from .github/workflows/agentic_slash_commands.yml rename to .github/workflows/agentic_commands.yml diff --git a/docs/adr/31605-centralized-slash-command-routing.md b/docs/adr/31605-centralized-slash-command-routing.md index fb9c4ae810d..b19259435a6 100644 --- a/docs/adr/31605-centralized-slash-command-routing.md +++ b/docs/adr/31605-centralized-slash-command-routing.md @@ -14,7 +14,7 @@ Each slash-command workflow in this repository previously registered its own lis ### Decision -We will introduce a `centralized` strategy for `on.slash_command` and have the compiler generate a single shared router workflow at `.github/workflows/agentic_slash_commands.yml` that owns the merged set of slash-command events and dispatches matching target workflows via `workflow_dispatch` with an `aw_context` input. Participating workflows (those declaring `strategy: centralized`) compile to `workflow_dispatch`-only triggers, retaining their non-slash events (e.g. label-only triggers) but delegating slash detection to the central router. The compiler additionally emits a warning recommending `strategy: centralized` once three or more slash commands are detected and some remain non-centralized, so the convention scales as the fleet grows. +We will introduce a `centralized` strategy for `on.slash_command` and have the compiler generate a single shared router workflow at `.github/workflows/agentic_commands.yml` that owns the merged set of slash-command events and dispatches matching target workflows via `workflow_dispatch` with an `aw_context` input. Participating workflows (those declaring `strategy: centralized`) compile to `workflow_dispatch`-only triggers, retaining their non-slash events (e.g. label-only triggers) but delegating slash detection to the central router. The compiler additionally emits a warning recommending `strategy: centralized` once three or more slash commands are detected and some remain non-centralized, so the convention scales as the fleet grows. ### Alternatives Considered @@ -24,7 +24,7 @@ Each slash-command workflow continues to declare its own event listeners and inl #### Alternative 2: One static, hand-maintained router workflow -Maintain `agentic_slash_commands.yml` by hand and require workflow authors to register their command in it manually. This avoids compiler complexity but reintroduces a long-running coordination problem: every new slash command requires editing a shared file, and the registry can drift from the per-workflow frontmatter. Rejected because compiler-generation of the router from frontmatter (`strategy: centralized`) preserves a single source of truth and avoids merge conflicts on the shared router. +Maintain `agentic_commands.yml` by hand and require workflow authors to register their command in it manually. This avoids compiler complexity but reintroduces a long-running coordination problem: every new slash command requires editing a shared file, and the registry can drift from the per-workflow frontmatter. Rejected because compiler-generation of the router from frontmatter (`strategy: centralized`) preserves a single source of truth and avoids merge conflicts on the shared router. #### Alternative 3: Use a `repository_dispatch` or external broker @@ -33,13 +33,13 @@ Forward slash events to an external service (or `repository_dispatch`) that then ### Consequences #### Positive -- One shared workflow (`agentic_slash_commands.yml`) handles slash-event listening for all participating commands, replacing N copies of the same listener set. +- One shared workflow (`agentic_commands.yml`) handles slash-event listening for all participating commands, replacing N copies of the same listener set. - Generated `if:` predicates on participating workflows shrink from large slash-text expressions to simple `workflow_dispatch`-gated activations, improving readability of lock files. - Workflow-level permissions on participating lock files are reduced (e.g. dropping `issues: write` / `pull-requests: write` from activation jobs that no longer need them) because routing logic lives in the central router with its own scoped `actions: write` job permission. - A compile-time warning nudges authors toward `strategy: centralized` once three or more slash commands exist, making the convention discoverable without a manual migration push. #### Negative -- Adds a new generated file (`.github/workflows/agentic_slash_commands.yml`) whose lifecycle is owned by the compiler — contributors must understand it is regenerated and not edited by hand. +- Adds a new generated file (`.github/workflows/agentic_commands.yml`) whose lifecycle is owned by the compiler — contributors must understand it is regenerated and not edited by hand. - Introduces an indirection: a slash command now arrives via `workflow_dispatch` triggered by another workflow, which complicates debugging (two runs to inspect instead of one) and shifts some auth context onto `aw_context`. - The router holds `actions: write` permission to dispatch other workflows; a bug in routing logic could dispatch the wrong workflow, so the route map and event-matching filter must remain trustworthy. - Legacy trigger-file handling for `agentics-slash-command-trigger.yml` was removed; any external references to that file name become stale. @@ -59,7 +59,7 @@ Forward slash events to an external service (or `repository_dispatch`) that then 1. A slash-command workflow **MAY** opt into centralized routing by setting `on.slash_command.strategy: centralized` in its frontmatter. 2. The compiler **MUST** treat `strategy: centralized` as the participation flag — a workflow without that key **MUST NOT** be wired into the central router. -3. When at least one workflow opts into `strategy: centralized`, the compiler **MUST** generate exactly one router workflow file at `.github/workflows/agentic_slash_commands.yml`. +3. When at least one workflow opts into `strategy: centralized`, the compiler **MUST** generate exactly one router workflow file at `.github/workflows/agentic_commands.yml`. 4. The generated router file **MUST** be regenerable from frontmatter alone and **MUST NOT** be hand-edited; contributors **SHOULD** treat it as compiler output. ### Router Workflow Structure diff --git a/docs/src/content/docs/reference/command-triggers.md b/docs/src/content/docs/reference/command-triggers.md index c725c0c1613..7b03f0b579a 100644 --- a/docs/src/content/docs/reference/command-triggers.md +++ b/docs/src/content/docs/reference/command-triggers.md @@ -72,7 +72,7 @@ on: Set `on.slash_command.strategy: centralized` to opt a workflow into centralized slash-command routing. When enabled, the workflow compiles as `workflow_dispatch`-centric, and the compiler generates one -shared `agentic_slash_commands.yml` workflow that listens to merged slash-command events and +shared `agentic_commands.yml` workflow that listens to merged slash-command events and dispatches matching target workflows with `aw_context`. ```yaml wrap diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index d57cc085802..e08f6c9bb66 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -260,7 +260,7 @@ func displayCentralizedSlashCommandRecommendation(compiler *workflow.Compiler, w } msg := fmt.Sprintf( - "Detected %d slash_command entries in this repository; %d are not using centralized routing. Consider setting `on.slash_command.strategy: centralized` to reduce duplicate triggers and route through `agentic_slash_commands.yml`.", + "Detected %d slash_command entries in this repository; %d are not using centralized routing. Consider setting `on.slash_command.strategy: centralized` to reduce duplicate triggers and route through `agentic_commands.yml`.", totalSlashCommands, nonCentralizedSlashCommands, ) diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 0dee6dcaf73..9cb9dd528f0 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -15,7 +15,8 @@ import ( var centralSlashCommandWorkflowLog = logger.New("workflow:central_slash_command_workflow") const ( - centralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" + centralSlashCommandWorkflowFilename = "agentic_commands.yml" + legacyCentralSlashCommandWorkflowFilename = "agentic_slash_commands.yml" ) type slashCommandRoute struct { @@ -31,11 +32,17 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf routesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) triggerFile := filepath.Join(workflowDir, centralSlashCommandWorkflowFilename) + legacyTriggerFile := filepath.Join(workflowDir, legacyCentralSlashCommandWorkflowFilename) if len(routesByCommand) == 0 || len(mergedEvents) == 0 { centralSlashCommandWorkflowLog.Print("No centralized slash-command participants found") if err := removeIfExists(triggerFile); err != nil { return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) } + if legacyTriggerFile != triggerFile { + if err := removeIfExists(legacyTriggerFile); err != nil { + return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) + } + } return nil } @@ -47,6 +54,11 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf if err := os.WriteFile(triggerFile, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write centralized slash-command workflow: %w", err) } + if legacyTriggerFile != triggerFile { + if err := removeIfExists(legacyTriggerFile); err != nil { + return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) + } + } centralSlashCommandWorkflowLog.Printf("Wrote centralized slash-command workflow: %s", triggerFile) return nil } From db13f8137633820375d696b05a99cc7504bc041c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 04:58:28 +0000 Subject: [PATCH 18/40] Simplify legacy centralized workflow cleanup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../central_slash_command_workflow.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 9cb9dd528f0..d490fd11772 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -38,10 +38,8 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf if err := removeIfExists(triggerFile); err != nil { return fmt.Errorf("failed to delete centralized slash-command workflow: %w", err) } - if legacyTriggerFile != triggerFile { - if err := removeIfExists(legacyTriggerFile); err != nil { - return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) - } + if err := cleanupLegacyCentralSlashCommandWorkflow(legacyTriggerFile); err != nil { + return err } return nil } @@ -54,15 +52,20 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf if err := os.WriteFile(triggerFile, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write centralized slash-command workflow: %w", err) } - if legacyTriggerFile != triggerFile { - if err := removeIfExists(legacyTriggerFile); err != nil { - return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) - } + if err := cleanupLegacyCentralSlashCommandWorkflow(legacyTriggerFile); err != nil { + return err } centralSlashCommandWorkflowLog.Printf("Wrote centralized slash-command workflow: %s", triggerFile) return nil } +func cleanupLegacyCentralSlashCommandWorkflow(path string) error { + if err := removeIfExists(path); err != nil { + return fmt.Errorf("failed to delete legacy centralized slash-command workflow: %w", err) + } + return nil +} + func removeIfExists(path string) error { if _, err := os.Stat(path); err == nil { return os.Remove(path) From 427b3c73267bcd26d56499cd8940449351faf76b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 05:13:33 +0000 Subject: [PATCH 19/40] Harden centralized slash routing and mark strategy experimental Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 51 ++++++++++++ .../docs/reference/frontmatter-full.md | 5 +- .../central_slash_command_workflow.go | 51 ++++++++++++ .../central_slash_command_workflow_test.go | 4 + pkg/workflow/compiler_validators.go | 6 ++ ...d_centralized_experimental_warning_test.go | 80 +++++++++++++++++++ 6 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 pkg/workflow/slash_command_centralized_experimental_warning_test.go diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index c2183bdcf65..dce06a9d6db 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -86,6 +86,57 @@ jobs: return; } + const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]); + const authorAssociationByEvent = { + issues: context.payload?.issue?.author_association ?? "", + pull_request: context.payload?.pull_request?.author_association ?? "", + issue_comment: context.payload?.comment?.author_association ?? "", + pull_request_review_comment: context.payload?.comment?.author_association ?? "", + discussion: context.payload?.discussion?.author_association ?? "", + discussion_comment: context.payload?.comment?.author_association ?? "", + }; + if (!(context.eventName in authorAssociationByEvent)) { + core.info("Skipping centralized dispatch for unsupported event '" + context.eventName + "'."); + return; + } + const authorAssociation = String(authorAssociationByEvent[context.eventName] ?? "").toUpperCase(); + if (!trustedAuthorAssociations.has(authorAssociation)) { + core.info("Skipping centralized dispatch due to untrusted author association '" + authorAssociation + "'."); + return; + } + + async function isForkBasedPullRequestEvent() { + if (!(identifier === "pull_request" || identifier === "pull_request_comment")) { + return false; + } + + let pullRequest = context.payload?.pull_request ?? null; + if (!pullRequest && identifier === "pull_request_comment") { + const pullNumber = context.payload?.issue?.number; + if (typeof pullNumber === "number") { + const response = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullNumber, + }); + pullRequest = response?.data ?? null; + } + } + + if (!pullRequest?.head?.repo) { + // Treat missing PR head repository as untrusted to avoid dispatching + // when fork provenance cannot be verified. + core.info("Skipping centralized dispatch because PR head repository metadata is unavailable."); + return true; + } + return pullRequest.head.repo.full_name !== pullRequest.base?.repo?.full_name; + } + + if (await isForkBasedPullRequestEvent()) { + core.info("Skipping centralized dispatch for fork-based pull_request event."); + return; + } + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); setupGlobals(core, github, context, exec, io, getOctokit); const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 75240b5fabb..1d1c7da4878 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -154,8 +154,9 @@ on: # Trigger compilation strategy for slash commands. # - "inline" (default): compile comment/body listeners directly in this workflow - # - "centralized": compile this workflow as workflow_dispatch-centric and route - # slash command events via the generated central trigger workflow. + # - "centralized" (experimental): compile this workflow as + # workflow_dispatch-centric and route slash command events via the generated + # central trigger workflow. # (optional) strategy: "centralized" diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index d490fd11772..e35db453eff 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -190,6 +190,57 @@ jobs: return; } + const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]); + const authorAssociationByEvent = { + issues: context.payload?.issue?.author_association ?? "", + pull_request: context.payload?.pull_request?.author_association ?? "", + issue_comment: context.payload?.comment?.author_association ?? "", + pull_request_review_comment: context.payload?.comment?.author_association ?? "", + discussion: context.payload?.discussion?.author_association ?? "", + discussion_comment: context.payload?.comment?.author_association ?? "", + }; + if (!(context.eventName in authorAssociationByEvent)) { + core.info("Skipping centralized dispatch for unsupported event '" + context.eventName + "'."); + return; + } + const authorAssociation = String(authorAssociationByEvent[context.eventName] ?? "").toUpperCase(); + if (!trustedAuthorAssociations.has(authorAssociation)) { + core.info("Skipping centralized dispatch due to untrusted author association '" + authorAssociation + "'."); + return; + } + + async function isForkBasedPullRequestEvent() { + if (!(identifier === "pull_request" || identifier === "pull_request_comment")) { + return false; + } + + let pullRequest = context.payload?.pull_request ?? null; + if (!pullRequest && identifier === "pull_request_comment") { + const pullNumber = context.payload?.issue?.number; + if (typeof pullNumber === "number") { + const response = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullNumber, + }); + pullRequest = response?.data ?? null; + } + } + + if (!pullRequest?.head?.repo) { + // Treat missing PR head repository as untrusted to avoid dispatching + // when fork provenance cannot be verified. + core.info("Skipping centralized dispatch because PR head repository metadata is unavailable."); + return true; + } + return pullRequest.head.repo.full_name !== pullRequest.base?.repo?.full_name; + } + + if (await isForkBasedPullRequestEvent()) { + core.info("Skipping centralized dispatch for fork-based pull_request event."); + return; + } + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); setupGlobals(core, github, context, exec, io, getOctokit); const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 36a1a01016e..3efc77ccd45 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -52,6 +52,10 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) require.Contains(t, text, `const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier));`) + require.Contains(t, text, `const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]);`) + require.Contains(t, text, `if (!trustedAuthorAssociations.has(authorAssociation)) {`) + require.Contains(t, text, `async function isForkBasedPullRequestEvent() {`) + require.Contains(t, text, `if (await isForkBasedPullRequestEvent()) {`) require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) } diff --git a/pkg/workflow/compiler_validators.go b/pkg/workflow/compiler_validators.go index 5c3a185c6b2..1c459548a51 100644 --- a/pkg/workflow/compiler_validators.go +++ b/pkg/workflow/compiler_validators.go @@ -299,6 +299,12 @@ func (c *Compiler) validateToolConfiguration(workflowData *WorkflowData, markdow c.IncrementWarningCount() } + // Emit experimental warning for centralized slash-command routing strategy + if workflowData.CommandCentralized { + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Using experimental feature: slash_command.strategy: centralized")) + c.IncrementWarningCount() + } + // Warn when slash_command and bots are both configured: if a bot listed in bots: posts // a comment that starts with the slash command text (e.g. /command-name), the // check_command_position check will pass and the bot will trigger the workflow — diff --git a/pkg/workflow/slash_command_centralized_experimental_warning_test.go b/pkg/workflow/slash_command_centralized_experimental_warning_test.go new file mode 100644 index 00000000000..569b6778ce4 --- /dev/null +++ b/pkg/workflow/slash_command_centralized_experimental_warning_test.go @@ -0,0 +1,80 @@ +//go:build integration + +package workflow + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/require" +) + +func TestSlashCommandCentralizedExperimentalWarning(t *testing.T) { + tests := []struct { + name string + content string + expectWarning bool + }{ + { + name: "centralized strategy emits warning", + content: `--- +on: + slash_command: + name: triage + strategy: centralized +--- + +# Test Workflow +`, + expectWarning: true, + }, + { + name: "inline strategy does not emit warning", + content: `--- +on: + slash_command: + name: triage +--- + +# Test Workflow +`, + expectWarning: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "slash-command-centralized-warning-test") + workflowPath := filepath.Join(tmpDir, "test-workflow.md") + require.NoError(t, os.WriteFile(workflowPath, []byte(tt.content), 0644)) + + oldStderr := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + compiler := NewCompiler() + compiler.SetStrictMode(false) + err := compiler.CompileWorkflow(workflowPath) + + w.Close() + os.Stderr = oldStderr + + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + stderrOutput := buf.String() + require.NoError(t, err) + + expected := "Using experimental feature: slash_command.strategy: centralized" + if tt.expectWarning { + require.Contains(t, stderrOutput, expected) + require.Greater(t, compiler.GetWarningCount(), 0) + } else { + require.NotContains(t, stderrOutput, expected) + } + }) + } +} From 5cc5e03328e70f62ba0969fee4f3ff298dcb6cd4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 05:28:13 +0000 Subject: [PATCH 20/40] Move centralized slash trust checks into compiled membership validation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 51 -------- actions/setup/js/check_membership.cjs | 114 ++++++++++++++---- actions/setup/js/check_membership.test.cjs | 74 +++++++++++- .../central_slash_command_workflow.go | 51 -------- .../central_slash_command_workflow_test.go | 6 +- 5 files changed, 163 insertions(+), 133 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index dce06a9d6db..c2183bdcf65 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -86,57 +86,6 @@ jobs: return; } - const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]); - const authorAssociationByEvent = { - issues: context.payload?.issue?.author_association ?? "", - pull_request: context.payload?.pull_request?.author_association ?? "", - issue_comment: context.payload?.comment?.author_association ?? "", - pull_request_review_comment: context.payload?.comment?.author_association ?? "", - discussion: context.payload?.discussion?.author_association ?? "", - discussion_comment: context.payload?.comment?.author_association ?? "", - }; - if (!(context.eventName in authorAssociationByEvent)) { - core.info("Skipping centralized dispatch for unsupported event '" + context.eventName + "'."); - return; - } - const authorAssociation = String(authorAssociationByEvent[context.eventName] ?? "").toUpperCase(); - if (!trustedAuthorAssociations.has(authorAssociation)) { - core.info("Skipping centralized dispatch due to untrusted author association '" + authorAssociation + "'."); - return; - } - - async function isForkBasedPullRequestEvent() { - if (!(identifier === "pull_request" || identifier === "pull_request_comment")) { - return false; - } - - let pullRequest = context.payload?.pull_request ?? null; - if (!pullRequest && identifier === "pull_request_comment") { - const pullNumber = context.payload?.issue?.number; - if (typeof pullNumber === "number") { - const response = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pullNumber, - }); - pullRequest = response?.data ?? null; - } - } - - if (!pullRequest?.head?.repo) { - // Treat missing PR head repository as untrusted to avoid dispatching - // when fork provenance cannot be verified. - core.info("Skipping centralized dispatch because PR head repository metadata is unavailable."); - return true; - } - return pullRequest.head.repo.full_name !== pullRequest.base?.repo?.full_name; - } - - if (await isForkBasedPullRequestEvent()) { - core.info("Skipping centralized dispatch for fork-based pull_request event."); - return; - } - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); setupGlobals(core, github, context, exec, io, getOctokit); const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); diff --git a/actions/setup/js/check_membership.cjs b/actions/setup/js/check_membership.cjs index 64b8a31030d..3cc370db928 100644 --- a/actions/setup/js/check_membership.cjs +++ b/actions/setup/js/check_membership.cjs @@ -4,25 +4,95 @@ const { parseRequiredPermissions, parseAllowedBots, checkRepositoryPermission, checkBotStatus, isAllowedBot, isConfusedDeputyAttack } = require("./check_permissions_utils.cjs"); const { writeDenialSummary } = require("./pre_activation_summary.cjs"); +function readWorkflowDispatchAwContext(payload) { + try { + const rawAwContext = payload?.inputs?.aw_context; + if (typeof rawAwContext !== "string" || rawAwContext.trim() === "") { + return null; + } + const parsed = JSON.parse(rawAwContext); + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + return null; + } + return parsed; + } catch { + return null; + } +} + async function main() { const { eventName } = context; const actor = context.actor; const { owner, repo } = context.repo; const requiredPermissions = parseRequiredPermissions(); const allowedBots = parseAllowedBots(); + let actorToValidate = actor; - // For workflow_dispatch, only skip check if "write" is in the allowed roles - // since workflow_dispatch can be triggered by users with write access + // workflow_dispatch is never treated as a trusted event. + // For centralized slash-command dispatches, validate the original triggering actor. if (eventName === "workflow_dispatch") { - const hasWriteRole = requiredPermissions.includes("write"); - if (hasWriteRole) { - core.info(`✅ Event ${eventName} does not require validation (write role allowed)`); - core.setOutput("is_team_member", "true"); - core.setOutput("result", "safe_event"); - return; + const awContext = readWorkflowDispatchAwContext(context.payload); + const commandName = typeof awContext?.command_name === "string" ? awContext.command_name.trim() : ""; + const propagatedActor = typeof awContext?.actor === "string" ? awContext.actor.trim() : ""; + + if (commandName && actor === "github-actions[bot]") { + if (!propagatedActor) { + const errorMessage = "Access denied: workflow_dispatch aw_context.actor is required for centralized slash-command dispatches."; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "config_error"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Ensure centralized slash-command dispatches include aw_context.actor."); + return; + } + + actorToValidate = propagatedActor; + core.info(`Validating centralized workflow_dispatch against originating actor '${actorToValidate}'`); + + const itemType = typeof awContext?.item_type === "string" ? awContext.item_type.trim() : ""; + const rawItemNumber = typeof awContext?.item_number === "string" ? awContext.item_number.trim() : ""; + if (itemType === "pull_request") { + const pullNumber = Number.parseInt(rawItemNumber, 10); + if (!Number.isInteger(pullNumber) || pullNumber <= 0) { + const errorMessage = "Access denied: centralized slash-command dispatch is missing a valid pull request number."; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "fork_pull_request"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Dispatch metadata is incomplete. Re-run from the original PR event."); + return; + } + + try { + const response = await github.rest.pulls.get({ + owner, + repo, + pull_number: pullNumber, + }); + const pullRequest = response?.data; + const headRepo = pullRequest?.head?.repo?.full_name; + const baseRepo = pullRequest?.base?.repo?.full_name; + if (!headRepo || !baseRepo || headRepo !== baseRepo) { + const errorMessage = "Access denied: centralized slash-command dispatch from fork-based pull requests is not allowed."; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "fork_pull_request"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Run slash-command workflows from branches in the base repository."); + return; + } + } catch (error) { + const errorMessage = `Repository permission check failed: Unable to verify pull request provenance (${error?.message ?? String(error)}).`; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "api_error"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Check the pre_activation log and ensure the workflow token can read pull request metadata."); + return; + } + } } - // If write is not allowed, continue with permission check - core.info(`Event ${eventName} requires validation (write role not allowed)`); + core.info(`Event ${eventName} requires validation`); } // skip check for other safe events @@ -56,8 +126,8 @@ async function main() { // @dependabot show (for issue_comment events) to make dependabot appear as the // actor, bypassing permission checks that rely solely on github.actor. // Reference: https://labs.boostsecurity.io/articles/weaponizing-dependabot-pwn-request-at-its-finest/ - if (isConfusedDeputyAttack(actor, eventName, context.payload)) { - const errorMessage = `Access denied: Potential confused deputy attack detected. Actor '${actor}' does not match the event author. The workflow may have been triggered indirectly via a bot command.`; + if (isConfusedDeputyAttack(actorToValidate, eventName, context.payload)) { + const errorMessage = `Access denied: Potential confused deputy attack detected. Actor '${actorToValidate}' does not match the event author. The workflow may have been triggered indirectly via a bot command.`; core.warning(errorMessage); core.setOutput("is_team_member", "false"); core.setOutput("result", "confused_deputy"); @@ -67,7 +137,7 @@ async function main() { } // Check if the actor has the required repository permissions - const result = await checkRepositoryPermission(actor, owner, repo, requiredPermissions); + const result = await checkRepositoryPermission(actorToValidate, owner, repo, requiredPermissions); if (result.authorized) { core.setOutput("is_team_member", "true"); @@ -78,23 +148,23 @@ async function main() { // Always attempt the bot allowlist fallback before giving up, so that GitHub Apps whose // actor is not a recognized GitHub user (e.g. "Copilot") are not silently denied. if (allowedBots.length > 0) { - core.info(`Checking if actor '${actor}' is in allowed bots list: ${allowedBots.join(", ")}`); + core.info(`Checking if actor '${actorToValidate}' is in allowed bots list: ${allowedBots.join(", ")}`); - if (isAllowedBot(actor, allowedBots)) { - core.info(`Actor '${actor}' is in the allowed bots list`); + if (isAllowedBot(actorToValidate, allowedBots)) { + core.info(`Actor '${actorToValidate}' is in the allowed bots list`); // Verify the bot is active/installed on the repository - const botStatus = await checkBotStatus(actor, owner, repo); + const botStatus = await checkBotStatus(actorToValidate, owner, repo); if (botStatus.isBot && botStatus.isActive) { - core.info(`✅ Bot '${actor}' is active on the repository and authorized`); + core.info(`✅ Bot '${actorToValidate}' is active on the repository and authorized`); core.setOutput("is_team_member", "true"); core.setOutput("result", "authorized_bot"); core.setOutput("user_permission", "bot"); return; } else if (botStatus.isBot && !botStatus.isActive) { - const errorMessage = `Access denied: Bot '${actor}' is not active/installed on this repository`; - core.warning(`Bot '${actor}' is in the allowed list but not active/installed on ${owner}/${repo}`); + const errorMessage = `Access denied: Bot '${actorToValidate}' is not active/installed on this repository`; + core.warning(`Bot '${actorToValidate}' is in the allowed list but not active/installed on ${owner}/${repo}`); core.setOutput("is_team_member", "false"); core.setOutput("result", "bot_not_active"); core.setOutput("user_permission", result.permission ?? "bot"); @@ -102,7 +172,7 @@ async function main() { await writeDenialSummary(errorMessage, "The bot is in the allowed list but is not installed or active on this repository. Install the GitHub App and try again."); return; } else { - core.info(`Actor '${actor}' is in allowed bots list but bot status check failed`); + core.info(`Actor '${actorToValidate}' is in allowed bots list but bot status check failed`); } } } @@ -116,7 +186,7 @@ async function main() { await writeDenialSummary(errorMessage, "The permission check failed with a GitHub API error. Check the `pre_activation` job log for details."); } else { const errorMessage = - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` + + `Access denied: User '${actorToValidate}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` + `To allow this user to run the workflow, add their role to the frontmatter. Example: roles: [${requiredPermissions.join(", ")}, ${result.permission}]`; core.setOutput("is_team_member", "false"); core.setOutput("result", "insufficient_permissions"); diff --git a/actions/setup/js/check_membership.test.cjs b/actions/setup/js/check_membership.test.cjs index 60429abea97..849b2348a69 100644 --- a/actions/setup/js/check_membership.test.cjs +++ b/actions/setup/js/check_membership.test.cjs @@ -27,6 +27,9 @@ describe("check_membership.cjs", () => { repos: { getCollaboratorPermissionLevel: vi.fn(), }, + pulls: { + get: vi.fn(), + }, }, }; @@ -126,15 +129,17 @@ describe("check_membership.cjs", () => { expect(mockCore.setOutput).toHaveBeenCalledWith("result", "safe_event"); }); - it("should skip check for workflow_dispatch when write role is allowed", async () => { + it("should validate workflow_dispatch when write role is allowed", async () => { mockContext.eventName = "workflow_dispatch"; process.env.GH_AW_REQUIRED_ROLES = "write,read"; + mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({ + data: { permission: "write" }, + }); await runScript(); - expect(mockCore.info).toHaveBeenCalledWith("✅ Event workflow_dispatch does not require validation (write role allowed)"); - expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "true"); - expect(mockCore.setOutput).toHaveBeenCalledWith("result", "safe_event"); + expect(mockCore.info).toHaveBeenCalledWith("Event workflow_dispatch requires validation"); + expect(mockGithub.rest.repos.getCollaboratorPermissionLevel).toHaveBeenCalled(); }); it("should validate workflow_dispatch when write role is not allowed", async () => { @@ -147,9 +152,68 @@ describe("check_membership.cjs", () => { await runScript(); - expect(mockCore.info).toHaveBeenCalledWith("Event workflow_dispatch requires validation (write role not allowed)"); + expect(mockCore.info).toHaveBeenCalledWith("Event workflow_dispatch requires validation"); expect(mockGithub.rest.repos.getCollaboratorPermissionLevel).toHaveBeenCalled(); }); + + it("should validate centralized workflow_dispatch using aw_context actor", async () => { + mockContext.eventName = "workflow_dispatch"; + mockContext.actor = "github-actions[bot]"; + mockContext.payload = { + inputs: { + aw_context: JSON.stringify({ + command_name: "triage", + actor: "octocat", + }), + }, + }; + process.env.GH_AW_REQUIRED_ROLES = "write"; + mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({ + data: { permission: "write" }, + }); + + await runScript(); + + expect(mockCore.info).toHaveBeenCalledWith("Validating centralized workflow_dispatch against originating actor 'octocat'"); + expect(mockGithub.rest.repos.getCollaboratorPermissionLevel).toHaveBeenCalledWith({ + owner: "testorg", + repo: "testrepo", + username: "octocat", + }); + expect(mockCore.setOutput).toHaveBeenCalledWith("result", "authorized"); + }); + + it("should deny centralized workflow_dispatch from fork-based pull requests", async () => { + mockContext.eventName = "workflow_dispatch"; + mockContext.actor = "github-actions[bot]"; + mockContext.payload = { + inputs: { + aw_context: JSON.stringify({ + command_name: "triage", + actor: "octocat", + item_type: "pull_request", + item_number: "42", + }), + }, + }; + process.env.GH_AW_REQUIRED_ROLES = "write"; + mockGithub.rest.pulls.get.mockResolvedValue({ + data: { + head: { repo: { full_name: "someone/fork" } }, + base: { repo: { full_name: "testorg/testrepo" } }, + }, + }); + + await runScript(); + + expect(mockGithub.rest.pulls.get).toHaveBeenCalledWith({ + owner: "testorg", + repo: "testrepo", + pull_number: 42, + }); + expect(mockGithub.rest.repos.getCollaboratorPermissionLevel).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("result", "fork_pull_request"); + }); }); describe("configuration validation", () => { diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index e35db453eff..d490fd11772 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -190,57 +190,6 @@ jobs: return; } - const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]); - const authorAssociationByEvent = { - issues: context.payload?.issue?.author_association ?? "", - pull_request: context.payload?.pull_request?.author_association ?? "", - issue_comment: context.payload?.comment?.author_association ?? "", - pull_request_review_comment: context.payload?.comment?.author_association ?? "", - discussion: context.payload?.discussion?.author_association ?? "", - discussion_comment: context.payload?.comment?.author_association ?? "", - }; - if (!(context.eventName in authorAssociationByEvent)) { - core.info("Skipping centralized dispatch for unsupported event '" + context.eventName + "'."); - return; - } - const authorAssociation = String(authorAssociationByEvent[context.eventName] ?? "").toUpperCase(); - if (!trustedAuthorAssociations.has(authorAssociation)) { - core.info("Skipping centralized dispatch due to untrusted author association '" + authorAssociation + "'."); - return; - } - - async function isForkBasedPullRequestEvent() { - if (!(identifier === "pull_request" || identifier === "pull_request_comment")) { - return false; - } - - let pullRequest = context.payload?.pull_request ?? null; - if (!pullRequest && identifier === "pull_request_comment") { - const pullNumber = context.payload?.issue?.number; - if (typeof pullNumber === "number") { - const response = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pullNumber, - }); - pullRequest = response?.data ?? null; - } - } - - if (!pullRequest?.head?.repo) { - // Treat missing PR head repository as untrusted to avoid dispatching - // when fork provenance cannot be verified. - core.info("Skipping centralized dispatch because PR head repository metadata is unavailable."); - return true; - } - return pullRequest.head.repo.full_name !== pullRequest.base?.repo?.full_name; - } - - if (await isForkBasedPullRequestEvent()) { - core.info("Skipping centralized dispatch for fork-based pull_request event."); - return; - } - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); setupGlobals(core, github, context, exec, io, getOctokit); const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 3efc77ccd45..def700a3604 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -52,10 +52,8 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) require.Contains(t, text, `const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier));`) - require.Contains(t, text, `const trustedAuthorAssociations = new Set(["MEMBER", "OWNER", "COLLABORATOR"]);`) - require.Contains(t, text, `if (!trustedAuthorAssociations.has(authorAssociation)) {`) - require.Contains(t, text, `async function isForkBasedPullRequestEvent() {`) - require.Contains(t, text, `if (await isForkBasedPullRequestEvent()) {`) + require.NotContains(t, text, `trustedAuthorAssociations`) + require.NotContains(t, text, `isForkBasedPullRequestEvent`) require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) } From c9609be25d2485215ecd3216ea49ff5092231218 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 05:31:44 +0000 Subject: [PATCH 21/40] Clarify centralized dispatch PR validation errors Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/check_membership.cjs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/check_membership.cjs b/actions/setup/js/check_membership.cjs index 3cc370db928..1e4ea3bcaa3 100644 --- a/actions/setup/js/check_membership.cjs +++ b/actions/setup/js/check_membership.cjs @@ -52,6 +52,15 @@ async function main() { const itemType = typeof awContext?.item_type === "string" ? awContext.item_type.trim() : ""; const rawItemNumber = typeof awContext?.item_number === "string" ? awContext.item_number.trim() : ""; if (itemType === "pull_request") { + if (!/^\d+$/.test(rawItemNumber)) { + const errorMessage = "Access denied: centralized slash-command dispatch is missing a valid pull request number."; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "fork_pull_request"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Dispatch metadata is incomplete. Re-run from the original PR event."); + return; + } const pullNumber = Number.parseInt(rawItemNumber, 10); if (!Number.isInteger(pullNumber) || pullNumber <= 0) { const errorMessage = "Access denied: centralized slash-command dispatch is missing a valid pull request number."; @@ -72,7 +81,16 @@ async function main() { const pullRequest = response?.data; const headRepo = pullRequest?.head?.repo?.full_name; const baseRepo = pullRequest?.base?.repo?.full_name; - if (!headRepo || !baseRepo || headRepo !== baseRepo) { + if (!headRepo || !baseRepo) { + const errorMessage = "Access denied: centralized slash-command dispatch pull request repository metadata is unavailable."; + core.warning(errorMessage); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "fork_pull_request"); + core.setOutput("error_message", errorMessage); + await writeDenialSummary(errorMessage, "Check the pre_activation log and ensure pull request repository metadata is present."); + return; + } + if (headRepo !== baseRepo) { const errorMessage = "Access denied: centralized slash-command dispatch from fork-based pull requests is not allowed."; core.warning(errorMessage); core.setOutput("is_team_member", "false"); From acde9af36fa5e63487a664ffc846721a5c9f497a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 13:27:33 +0000 Subject: [PATCH 22/40] Merge main and recompile lock workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/dependabot-campaign.lock.yml | 18 +++++++++---- .github/workflows/dependabot-worker.lock.yml | 25 ++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dependabot-campaign.lock.yml b/.github/workflows/dependabot-campaign.lock.yml index 2aba5e60657..1bbd297c733 100644 --- a/.github/workflows/dependabot-campaign.lock.yml +++ b/.github/workflows/dependabot-campaign.lock.yml @@ -747,9 +747,13 @@ jobs: GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.npms.io","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","binstar.org","bootstrap.pypa.io","bun.sh","cdn.jsdelivr.net","conda.anaconda.org","conda.binstar.org","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","deb.nodesource.com","deno.land","esm.sh","files.pythonhosted.org","get.pnpm.io","github.com","go.dev","golang.org","googleapis.deno.dev","googlechromelabs.github.io","goproxy.io","host.docker.internal","json-schema.org","json.schemastore.org","jsr.io","keyserver.ubuntu.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pip.pypa.io","pkg.go.dev","ppa.launchpad.net","proxy.golang.org","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.anaconda.com","repo.continuum.io","repo.yarnpkg.com","s.symcb.com","s.symcd.com","security.ubuntu.com","skimdb.npmjs.com","storage.googleapis.com","sum.golang.org","telemetry.enterprise.githubcopilot.com","telemetry.vercel.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"maxEffectiveTokens":10000000,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.npms.io","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","binstar.org","bootstrap.pypa.io","bun.sh","cdn.jsdelivr.net","conda.anaconda.org","conda.binstar.org","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","deb.nodesource.com","deno.land","esm.sh","files.pythonhosted.org","get.pnpm.io","github.com","go.dev","golang.org","googleapis.deno.dev","googlechromelabs.github.io","goproxy.io","host.docker.internal","json-schema.org","json.schemastore.org","jsr.io","keyserver.ubuntu.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pip.pypa.io","pkg.go.dev","ppa.launchpad.net","proxy.golang.org","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.anaconda.com","repo.continuum.io","repo.yarnpkg.com","s.symcb.com","s.symcd.com","security.ubuntu.com","skimdb.npmjs.com","storage.googleapis.com","sum.golang.org","telemetry.enterprise.githubcopilot.com","telemetry.vercel.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"maxRuns":100,"maxEffectiveTokens":25000000,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp://(localhost|127\.0\.0\.1)(:[0-9]+)?$ ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 @@ -1122,7 +1126,7 @@ jobs: GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "15" - GH_AW_MAX_EFFECTIVE_TOKENS: "10000000" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1265,9 +1269,13 @@ jobs: GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"maxEffectiveTokens":10000000},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"maxRuns":100,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp://(localhost|127\.0\.0\.1)(:[0-9]+)?$ ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 diff --git a/.github/workflows/dependabot-worker.lock.yml b/.github/workflows/dependabot-worker.lock.yml index 4842fe6ad34..7ff762bb9ba 100644 --- a/.github/workflows/dependabot-worker.lock.yml +++ b/.github/workflows/dependabot-worker.lock.yml @@ -852,7 +852,6 @@ jobs: # --allow-tool github # --allow-tool safeoutputs # --allow-tool shell(./gh-aw compile --dependabot) - # --allow-tool shell(cat *) # --allow-tool shell(cat) # --allow-tool shell(cd .github/workflows && npm install --package-lock-only) # --allow-tool shell(date) @@ -862,7 +861,7 @@ jobs: # --allow-tool shell(git branch:*) # --allow-tool shell(git checkout:*) # --allow-tool shell(git commit:*) - # --allow-tool shell(git diff *) + # --allow-tool shell(git diff) # --allow-tool shell(git merge:*) # --allow-tool shell(git rm:*) # --allow-tool shell(git status) @@ -875,7 +874,7 @@ jobs: # --allow-tool shell(make dependabot) # --allow-tool shell(printf) # --allow-tool shell(pwd) - # --allow-tool shell(rg *) + # --allow-tool shell(rg) # --allow-tool shell(safeoutputs:*) # --allow-tool shell(sort) # --allow-tool shell(tail) @@ -890,10 +889,14 @@ jobs: GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.npms.io","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","binstar.org","bootstrap.pypa.io","bun.sh","cdn.jsdelivr.net","conda.anaconda.org","conda.binstar.org","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","deb.nodesource.com","deno.land","esm.sh","files.pythonhosted.org","get.pnpm.io","github.com","go.dev","golang.org","googleapis.deno.dev","googlechromelabs.github.io","goproxy.io","host.docker.internal","json-schema.org","json.schemastore.org","jsr.io","keyserver.ubuntu.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pip.pypa.io","pkg.go.dev","ppa.launchpad.net","proxy.golang.org","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.anaconda.com","repo.continuum.io","repo.yarnpkg.com","s.symcb.com","s.symcd.com","security.ubuntu.com","skimdb.npmjs.com","storage.googleapis.com","sum.golang.org","telemetry.enterprise.githubcopilot.com","telemetry.vercel.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"maxEffectiveTokens":10000000,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.npms.io","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","binstar.org","bootstrap.pypa.io","bun.sh","cdn.jsdelivr.net","conda.anaconda.org","conda.binstar.org","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","deb.nodesource.com","deno.land","esm.sh","files.pythonhosted.org","get.pnpm.io","github.com","go.dev","golang.org","googleapis.deno.dev","googlechromelabs.github.io","goproxy.io","host.docker.internal","json-schema.org","json.schemastore.org","jsr.io","keyserver.ubuntu.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pip.pypa.io","pkg.go.dev","ppa.launchpad.net","proxy.golang.org","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.anaconda.com","repo.continuum.io","repo.yarnpkg.com","s.symcb.com","s.symcd.com","security.ubuntu.com","skimdb.npmjs.com","storage.googleapis.com","sum.golang.org","telemetry.enterprise.githubcopilot.com","telemetry.vercel.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"maxRuns":100,"maxEffectiveTokens":25000000,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp://(localhost|127\.0\.0\.1)(:[0-9]+)?$ ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(./gh-aw compile --dependabot)'\'' --allow-tool '\''shell(cat *)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cd .github/workflows && npm install --package-lock-only)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git diff *)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(make build)'\'' --allow-tool '\''shell(make dependabot && make build)'\'' --allow-tool '\''shell(make dependabot)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(rg *)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(./gh-aw compile --dependabot)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cd .github/workflows && npm install --package-lock-only)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git diff)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(make build)'\'' --allow-tool '\''shell(make dependabot && make build)'\'' --allow-tool '\''shell(make dependabot)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(rg)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE @@ -1242,7 +1245,7 @@ jobs: GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "30" - GH_AW_MAX_EFFECTIVE_TOKENS: "10000000" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1386,9 +1389,13 @@ jobs: GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"maxEffectiveTokens":10000000},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.43/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"maxRuns":100,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.43"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp://(localhost|127\.0\.0\.1)(:[0-9]+)?$ ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 From f436fad08b6182e770c86d77d5b0b6bc3013ced8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 14:43:44 +0000 Subject: [PATCH 23/40] Plan: address new centralized router review feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/copilot-token-audit.lock.yml | 13 +++++++------ .github/workflows/copilot-token-optimizer.lock.yml | 13 +++++++------ .github/workflows/static-analysis-report.lock.yml | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index 09e9d4165d5..5ca250697c9 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7430388914ae348ed20b0daad7f86ab4a28806a4cf32176948c289c430a73203","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"},{"repo":"github/gh-aw/actions/setup-cli","sha":"489dbab88cc78e35506b5ccbf08a4037166824ac","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -48,7 +48,6 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 # - docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 -# - github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.43 @@ -434,10 +433,11 @@ jobs: tags: localhost/gh-aw:dev build-args: | BINARY=dist/gh-aw-linux-amd64 - - name: Setup gh-aw CLI - uses: github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 - with: - version: '6b03a99' + - name: Build and install gh-aw CLI from source + run: | + gh extension remove gh-aw || true + gh extension install . + gh aw version - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -1795,3 +1795,4 @@ jobs: actions/setup sparse-checkout-cone-mode: true persist-credentials: false + diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index e6284aa67d1..f8f69e1d104 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"19a3844810750fcbbc4768d5399b51a40525dcb48b238602d8a042268a537a0e","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw/actions/setup-cli","sha":"489dbab88cc78e35506b5ccbf08a4037166824ac","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -44,7 +44,6 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.43 @@ -403,10 +402,11 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Setup gh-aw CLI - uses: github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 - with: - version: '6b03a99' + - name: Build and install gh-aw CLI from source + run: | + gh extension remove gh-aw || true + gh extension install . + gh aw version - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -1337,3 +1337,4 @@ jobs: /tmp/gh-aw/safe-output-items.jsonl /tmp/gh-aw/temporary-id-map.json if-no-files-found: ignore + diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index b1d23be3b71..a55d2225739 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"56d29caf347fba5811d2fb80f551779f75f00ca33048568e139db3a9b50f1dca","strict":true,"agent_id":"claude"} -# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"},{"repo":"github/gh-aw/actions/setup-cli","sha":"489dbab88cc78e35506b5ccbf08a4037166824ac","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -50,7 +50,6 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 # - docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 -# - github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.43 @@ -439,10 +438,11 @@ jobs: tags: localhost/gh-aw:dev build-args: | BINARY=dist/gh-aw-linux-amd64 - - name: Setup gh-aw CLI - uses: github/gh-aw/actions/setup-cli@489dbab88cc78e35506b5ccbf08a4037166824ac # v0.72.1 - with: - version: '6b03a99' + - name: Build and install gh-aw CLI from source + run: | + gh extension remove gh-aw || true + gh extension install . + gh aw version - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -1697,3 +1697,4 @@ jobs: with: key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} path: /tmp/gh-aw/cache-memory + From b47da8131dbb0cf14acb9c1fb884f692e5fce4c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 14:51:30 +0000 Subject: [PATCH 24/40] WIP: apply centralized router review feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 67 ++++++++++++++ actions/setup/js/route_slash_command.test.cjs | 84 +++++++++++++++++ .../central_slash_command_workflow.go | 91 +++++++------------ .../central_slash_command_workflow_test.go | 43 ++++++++- pkg/workflow/label_command.go | 43 +++++++++ pkg/workflow/label_command_test.go | 9 ++ .../slash_command_centralized_compile_test.go | 7 +- pkg/workflow/tools.go | 13 ++- 8 files changed, 288 insertions(+), 69 deletions(-) create mode 100644 actions/setup/js/route_slash_command.cjs create mode 100644 actions/setup/js/route_slash_command.test.cjs diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs new file mode 100644 index 00000000000..4328513ca45 --- /dev/null +++ b/actions/setup/js/route_slash_command.cjs @@ -0,0 +1,67 @@ +// @ts-check +/// + +function eventIdentifier() { + if (context.eventName !== "issue_comment") { + return context.eventName; + } + return context.payload?.issue?.pull_request ? "pull_request_comment" : "issue_comment"; +} + +function resolveBodyText() { + const bodyByEvent = { + issues: context.payload?.issue?.body ?? "", + pull_request: context.payload?.pull_request?.body ?? "", + issue_comment: context.payload?.comment?.body ?? "", + pull_request_review_comment: context.payload?.comment?.body ?? "", + discussion: context.payload?.discussion?.body ?? "", + discussion_comment: context.payload?.comment?.body ?? "", + }; + return bodyByEvent[context.eventName] ?? ""; +} + +function resolveDispatchRef() { + return process.env.GITHUB_HEAD_REF + ? `refs/heads/${process.env.GITHUB_HEAD_REF}` + : process.env.GITHUB_REF || context.ref || `refs/heads/${context.payload?.repository?.default_branch || "main"}`; +} + +async function main() { + const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); + const text = resolveBodyText(); + const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; + if (!firstWord.startsWith("/")) { + core.info("No slash command found at start of payload text; skipping dispatch."); + return; + } + + const commandName = firstWord.slice(1); + const identifier = eventIdentifier(); + const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); + if (routes.length === 0) { + core.info(`No centralized routes matched command '/${commandName}' for event '${identifier}'.`); + return; + } + + const { setupGlobals } = require("./setup_globals.cjs"); + setupGlobals(core, github, context, exec, io, getOctokit); + const { buildAwContext } = require("./aw_context.cjs"); + + const ref = resolveDispatchRef(); + for (const route of routes) { + const awContext = buildAwContext(); + awContext.command_name = commandName; + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: `${route.workflow}.lock.yml`, + ref, + inputs: { + aw_context: JSON.stringify(awContext), + }, + }); + core.info(`Dispatched '${route.workflow}' for '/${commandName}'`); + } +} + +module.exports = { main, eventIdentifier, resolveBodyText, resolveDispatchRef }; diff --git a/actions/setup/js/route_slash_command.test.cjs b/actions/setup/js/route_slash_command.test.cjs new file mode 100644 index 00000000000..98f5a0c2581 --- /dev/null +++ b/actions/setup/js/route_slash_command.test.cjs @@ -0,0 +1,84 @@ +// @ts-check +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +const globals = /** @type {any} */ global; +const { main } = require("./route_slash_command.cjs"); + +describe("route_slash_command", () => { + /** @type {{ core: any, github: any, context: any, exec: any, io: any, getOctokit: any }} */ + let savedGlobals; + /** @type {any[]} */ + let dispatchCalls; + + beforeEach(() => { + savedGlobals = { + core: globals.core, + github: globals.github, + context: globals.context, + exec: globals.exec, + io: globals.io, + getOctokit: globals.getOctokit, + }; + dispatchCalls = []; + globals.core = { + info: vi.fn(), + }; + globals.github = { + rest: { + actions: { + createWorkflowDispatch: vi.fn(async params => { + dispatchCalls.push(params); + }), + }, + }, + }; + globals.context = { + eventName: "issue_comment", + ref: "refs/heads/main", + repo: { owner: "github", repo: "gh-aw" }, + payload: { issue: {}, comment: {} }, + }; + globals.exec = {}; + globals.io = {}; + globals.getOctokit = vi.fn(); + process.env.GH_AW_SLASH_ROUTING = JSON.stringify({ + archie: [{ workflow: "archie", events: ["issue_comment", "pull_request_comment"] }], + }); + process.env.GITHUB_WORKSPACE = `${process.cwd()}`; + }); + + afterEach(() => { + globals.core = savedGlobals.core; + globals.github = savedGlobals.github; + globals.context = savedGlobals.context; + globals.exec = savedGlobals.exec; + globals.io = savedGlobals.io; + globals.getOctokit = savedGlobals.getOctokit; + delete process.env.GH_AW_SLASH_ROUTING; + delete process.env.GITHUB_WORKSPACE; + delete process.env.GITHUB_REF; + delete process.env.GITHUB_HEAD_REF; + vi.restoreAllMocks(); + }); + + it("skips dispatch when text does not start with slash command", async () => { + globals.context.payload.comment.body = "hello /archie"; + await main(); + expect(dispatchCalls).toHaveLength(0); + expect(globals.core.info).toHaveBeenCalledWith(expect.stringContaining("No slash command found")); + }); + + it("dispatches only matching command and event routes", async () => { + globals.context.payload.comment.body = "/archie please"; + await main(); + expect(dispatchCalls).toHaveLength(1); + expect(dispatchCalls[0].workflow_id).toBe("archie.lock.yml"); + }); + + it("treats issue_comment on pull requests as pull_request_comment", async () => { + globals.context.payload.issue.pull_request = { url: "https://example.test/pr/1" }; + globals.context.payload.comment.body = "/archie please"; + await main(); + expect(dispatchCalls).toHaveLength(1); + }); +}); diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index d490fd11772..339844d6cb9 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -9,6 +9,7 @@ import ( "sort" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -44,7 +45,7 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf return nil } - content, err := buildCentralSlashCommandWorkflowYAML(routesByCommand, mergedEvents) + content, err := buildCentralSlashCommandWorkflowYAML(routesByCommand, mergedEvents, resolveCentralSlashRunsOn(workflowDataList)) if err != nil { return err } @@ -124,17 +125,17 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str return routesByCommand, mergedEvents } -func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashCommandRoute, mergedEvents map[string]map[string]bool) (string, error) { +func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashCommandRoute, mergedEvents map[string]map[string]bool, runsOn string) (string, error) { routesJSON, err := json.Marshal(routesByCommand) if err != nil { return "", fmt.Errorf("failed to marshal centralized slash-command routes: %w", err) } - header := GenerateWorkflowHeader("", "pkg/workflow/central_slash_command_workflow.go", "") + header := GenerateWorkflowHeader("", "gh-aw", "Compiler version: "+GetVersion()) var b strings.Builder b.WriteString(header) - b.WriteString(`name: "Agentic Slash Command Trigger" + b.WriteString(`name: "Agentic Commands" on: `) @@ -144,7 +145,7 @@ permissions: {} jobs: route: - runs-on: ubuntu-slim + runs-on: ` + runsOn + ` permissions: actions: write contents: read @@ -158,61 +159,39 @@ jobs: GH_AW_SLASH_ROUTING: '` + escapeSingleQuotedYAMLString(string(routesJSON)) + `' with: script: | - const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); - const bodyByEvent = { - issues: context.payload?.issue?.body ?? "", - pull_request: context.payload?.pull_request?.body ?? "", - issue_comment: context.payload?.comment?.body ?? "", - pull_request_review_comment: context.payload?.comment?.body ?? "", - discussion: context.payload?.discussion?.body ?? "", - discussion_comment: context.payload?.comment?.body ?? "", - }; - - function eventIdentifier() { - if (context.eventName !== "issue_comment") { - return context.eventName; - } - return context.payload?.issue?.pull_request ? "pull_request_comment" : "issue_comment"; - } - - const text = bodyByEvent[context.eventName] ?? ""; - const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; - if (!firstWord.startsWith("/")) { - core.info("No slash command found at start of payload text; skipping dispatch."); - return; - } - - const commandName = firstWord.slice(1); - const identifier = eventIdentifier(); - const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); - if (routes.length === 0) { - core.info("No centralized routes matched command '/" + commandName + "' for event '" + identifier + "'."); - return; - } - - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); - setupGlobals(core, github, context, exec, io, getOctokit); - const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); - - const ref = process.env.GITHUB_HEAD_REF ? "refs/heads/" + process.env.GITHUB_HEAD_REF : (process.env.GITHUB_REF || context.ref || "refs/heads/" + (context.payload?.repository?.default_branch || "main")); - for (const route of routes) { - const awContext = buildAwContext(); - awContext.command_name = commandName; - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: route.workflow + ".lock.yml", - ref, - inputs: { - aw_context: JSON.stringify(awContext), - }, - }); - core.info("Dispatched '" + route.workflow + "' for '/" + commandName + "'"); - } + const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); + await main(); `) return b.String(), nil } +func resolveCentralSlashRunsOn(workflowDataList []*WorkflowData) string { + counts := map[string]int{} + for _, wd := range workflowDataList { + if wd == nil || !wd.CommandCentralized || len(wd.Command) == 0 { + continue + } + + resolved := constants.DefaultActivationJobRunnerImage + if wd.SafeOutputs != nil && strings.TrimSpace(wd.SafeOutputs.RunsOn) != "" { + resolved = strings.TrimSpace(wd.SafeOutputs.RunsOn) + } else if strings.TrimSpace(wd.RunsOnSlim) != "" { + resolved = strings.TrimSpace(wd.RunsOnSlim) + } + counts[resolved]++ + } + + best := constants.DefaultActivationJobRunnerImage + bestCount := counts[best] + for candidate, count := range counts { + if count > bestCount || (count == bestCount && candidate < best) { + best = candidate + bestCount = count + } + } + return best +} + func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]bool) { eventOrder := []string{ "issues", diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index def700a3604..45531ae5bb5 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -42,8 +42,10 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.NoError(t, err) text := string(content) - require.Contains(t, text, "name: \"Agentic Slash Command Trigger\"") + require.Contains(t, text, "name: \"Agentic Commands\"") + require.Contains(t, text, "Compiler version:") require.Contains(t, text, "permissions: {}") + require.Contains(t, text, "runs-on: ubuntu-slim") require.Contains(t, text, " permissions:\n actions: write\n contents: read") require.Contains(t, text, "issues:") require.Contains(t, text, "issue_comment:") @@ -51,7 +53,8 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.Contains(t, text, "discussion_comment:") require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) - require.Contains(t, text, `const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier));`) + require.Contains(t, text, `require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs")`) + require.NotContains(t, text, `const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}");`) require.NotContains(t, text, `trustedAuthorAssociations`) require.NotContains(t, text, `isForkBasedPullRequestEvent`) require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) @@ -126,6 +129,42 @@ func TestCollectCentralSlashCommandRoutes_UnionizesMergedEvents(t *testing.T) { require.NotContains(t, mergedEvents, "discussion") } +func TestGenerateCentralSlashCommandWorkflow_UsesCentralizedRunsOnResolution(t *testing.T) { + tmpDir := testutil.TempDir(t, "central-slash-workflow-runs-on-test") + data := []*WorkflowData{ + { + WorkflowID: "one", + Command: []string{"one"}, + CommandEvents: []string{"issue_comment"}, + CommandCentralized: true, + RunsOnSlim: "ubuntu-latest", + }, + { + WorkflowID: "two", + Command: []string{"two"}, + CommandEvents: []string{"issue_comment"}, + CommandCentralized: true, + SafeOutputs: &SafeOutputsConfig{ + RunsOn: "self-hosted", + }, + }, + { + WorkflowID: "three", + Command: []string{"three"}, + CommandEvents: []string{"issue_comment"}, + CommandCentralized: true, + SafeOutputs: &SafeOutputsConfig{ + RunsOn: "self-hosted", + }, + }, + } + + require.NoError(t, GenerateCentralSlashCommandWorkflow(data, tmpDir)) + content, err := os.ReadFile(filepath.Join(tmpDir, centralSlashCommandWorkflowFilename)) + require.NoError(t, err) + require.Contains(t, string(content), "runs-on: self-hosted") +} + func typeSetKeys(typeSet map[string]bool) []string { out := make([]string, 0, len(typeSet)) for key := range typeSet { diff --git a/pkg/workflow/label_command.go b/pkg/workflow/label_command.go index f46c3393b26..30579ed7545 100644 --- a/pkg/workflow/label_command.go +++ b/pkg/workflow/label_command.go @@ -93,3 +93,46 @@ func buildLabelCommandCondition(labelNames []string, labelCommandEvents []string Right: isNotLabelEvent, }, nil } + +// buildCentralizedLabelCommandCondition builds label-command conditions for centralized +// slash-command workflows that trigger through workflow_dispatch. +// For workflow_dispatch events, label routing checks use aw_context fields rather than +// github.event_name/github.event.label. +func buildCentralizedLabelCommandCondition(labelNames []string, labelCommandEvents []string, hasOtherEvents bool) (ConditionNode, error) { + if len(labelNames) == 0 { + return nil, errors.New("no label names provided for label-command trigger") + } + filteredEvents := FilterLabelCommandEvents(labelCommandEvents) + if len(filteredEvents) == 0 { + return nil, errors.New("no valid events specified for label-command trigger") + } + + labelExpr := BuildPropertyAccess("fromJSON(github.event.inputs.aw_context || '{}').trigger_label") + eventExpr := BuildPropertyAccess("fromJSON(github.event.inputs.aw_context || '{}').event_type") + + var labelChecks []ConditionNode + for _, labelName := range labelNames { + labelChecks = append(labelChecks, BuildEquals(labelExpr, BuildStringLiteral(labelName))) + } + labelNameMatch := BuildDisjunction(false, labelChecks...) + + var eventChecks []ConditionNode + for _, event := range filteredEvents { + eventChecks = append(eventChecks, BuildEquals(eventExpr, BuildStringLiteral(event))) + } + isLabelSourceEvent := BuildDisjunction(false, eventChecks...) + dispatchLabelCondition := &OrNode{ + Left: &AndNode{Left: isLabelSourceEvent, Right: labelNameMatch}, + Right: &NotNode{Child: isLabelSourceEvent}, + } + + dispatchCheck := BuildEventTypeEquals("workflow_dispatch") + if !hasOtherEvents { + return &AndNode{Left: dispatchCheck, Right: dispatchLabelCondition}, nil + } + + return &OrNode{ + Left: &AndNode{Left: dispatchCheck, Right: dispatchLabelCondition}, + Right: &NotNode{Child: dispatchCheck}, + }, nil +} diff --git a/pkg/workflow/label_command_test.go b/pkg/workflow/label_command_test.go index 74b7fe5ed83..41297aaad20 100644 --- a/pkg/workflow/label_command_test.go +++ b/pkg/workflow/label_command_test.go @@ -199,6 +199,15 @@ func TestBuildLabelCommandCondition(t *testing.T) { } } +func TestBuildCentralizedLabelCommandCondition(t *testing.T) { + condition, err := buildCentralizedLabelCommandCondition([]string{"cloclo"}, []string{"issues"}, false) + require.NoError(t, err) + rendered := condition.Render() + assert.Contains(t, rendered, "github.event_name == 'workflow_dispatch'") + assert.Contains(t, rendered, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") + assert.Contains(t, rendered, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo'") +} + // TestLabelCommandWorkflowCompile verifies that a workflow with label_command trigger // compiles to a valid GitHub Actions workflow with: // - label-based events (issues, pull_request, discussion) in the on: section diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go index e58706bc9db..a65f7455edc 100644 --- a/pkg/workflow/slash_command_centralized_compile_test.go +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -77,8 +77,7 @@ tools: compiled := string(lockContent) require.Contains(t, compiled, "workflow_dispatch:") - require.Contains(t, compiled, "issues:") - require.Contains(t, compiled, "types:\n - labeled") - require.Contains(t, compiled, "github.event.label.name == 'triage'") - require.Contains(t, compiled, "|| !(github.event_name == 'issues')") + require.NotContains(t, compiled, "issues:") + require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'triage'") + require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") } diff --git a/pkg/workflow/tools.go b/pkg/workflow/tools.go index 7466211ccd8..31a0cd69594 100644 --- a/pkg/workflow/tools.go +++ b/pkg/workflow/tools.go @@ -115,9 +115,9 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error } } - // If label_command is also configured alongside slash_command, merge label events - // into the existing command events map to avoid duplicate YAML keys. - if len(data.LabelCommand) > 0 { + // If label_command is also configured alongside non-centralized slash_command, merge + // label events into the existing command events map to avoid duplicate YAML keys. + if len(data.LabelCommand) > 0 && !data.CommandCentralized { labelEventNames := FilterLabelCommandEvents(data.LabelCommandEvents) for _, eventName := range labelEventNames { if existingAny, ok := commandEventsMap[eventName]; ok { @@ -183,10 +183,9 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error } } } else if data.If == "" && len(data.LabelCommand) > 0 { - // Centralized command mode bypasses slash-command content checks. - // If label_command is also configured, keep label gating logic. - // hasOtherEvents=true keeps router workflow_dispatch runs eligible. - labelConditionTree, err := buildLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, true) + // Centralized command mode compiles slash-command workflows as workflow_dispatch + // targets. Label checks for dispatches must be derived from aw_context metadata. + labelConditionTree, err := buildCentralizedLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, len(data.CommandOtherEvents) > 0) if err != nil { return fmt.Errorf("failed to build label-command condition: %w", err) } else { From e658ef419ca09573292f9f4e5c85a7732e880ca8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:05:25 +0000 Subject: [PATCH 25/40] Plan remaining PR feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 59 +++----------------------- .github/workflows/cloclo.lock.yml | 28 ++++++------ 2 files changed, 18 insertions(+), 69 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index c2183bdcf65..326886f099e 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -12,7 +12,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by pkg/workflow/central_slash_command_workflow.go. DO NOT EDIT. +# This file was automatically generated by gh-aw. DO NOT EDIT. # # To regenerate this workflow, run: # gh aw compile @@ -20,7 +20,9 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -name: "Agentic Slash Command Trigger" +# Compiler version: b47da81 +# +name: "Agentic Commands" on: issues: @@ -54,54 +56,5 @@ jobs: GH_AW_SLASH_ROUTING: '{"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}]}' with: script: | - const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); - const bodyByEvent = { - issues: context.payload?.issue?.body ?? "", - pull_request: context.payload?.pull_request?.body ?? "", - issue_comment: context.payload?.comment?.body ?? "", - pull_request_review_comment: context.payload?.comment?.body ?? "", - discussion: context.payload?.discussion?.body ?? "", - discussion_comment: context.payload?.comment?.body ?? "", - }; - - function eventIdentifier() { - if (context.eventName !== "issue_comment") { - return context.eventName; - } - return context.payload?.issue?.pull_request ? "pull_request_comment" : "issue_comment"; - } - - const text = bodyByEvent[context.eventName] ?? ""; - const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; - if (!firstWord.startsWith("/")) { - core.info("No slash command found at start of payload text; skipping dispatch."); - return; - } - - const commandName = firstWord.slice(1); - const identifier = eventIdentifier(); - const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); - if (routes.length === 0) { - core.info("No centralized routes matched command '/" + commandName + "' for event '" + identifier + "'."); - return; - } - - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); - setupGlobals(core, github, context, exec, io, getOctokit); - const { buildAwContext } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/aw_context.cjs"); - - const ref = process.env.GITHUB_HEAD_REF ? "refs/heads/" + process.env.GITHUB_HEAD_REF : (process.env.GITHUB_REF || context.ref || "refs/heads/" + (context.payload?.repository?.default_branch || "main")); - for (const route of routes) { - const awContext = buildAwContext(); - awContext.command_name = commandName; - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: route.workflow + ".lock.yml", - ref, - inputs: { - aw_context: JSON.stringify(awContext), - }, - }); - core.info("Dispatched '" + route.workflow + "' for '/" + commandName + "'"); - } + const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); + await main(); diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 8fbf91cad1e..467aa6faa72 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -64,15 +64,6 @@ name: "/cloclo" "on": - discussion: - types: - - labeled - issues: - types: - - labeled - pull_request: - types: - - labeled workflow_dispatch: inputs: aw_context: @@ -100,16 +91,19 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name == 'issues' || github.event_name == 'pull_request' || - github.event_name == 'discussion') && github.event.label.name == 'cloclo' || (!(github.event_name == 'issues')) && - (!(github.event_name == 'pull_request')) && (!(github.event_name == 'discussion'))) + needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'workflow_dispatch' && ((fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'issues' || fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request' || + fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion') && fromJSON(github.event.inputs.aw_context || + '{}').trigger_label == 'cloclo' || (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && + (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request')) && + (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'discussion')))) runs-on: ubuntu-slim permissions: actions: read contents: read discussions: write issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -1752,9 +1746,11 @@ jobs: pre_activation: if: > - (github.event_name == 'issues' || github.event_name == 'pull_request' || github.event_name == 'discussion') && - github.event.label.name == 'cloclo' || (!(github.event_name == 'issues')) && (!(github.event_name == 'pull_request')) && - (!(github.event_name == 'discussion')) + github.event_name == 'workflow_dispatch' && ((fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || + fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request' || fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'discussion') && fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo' || + (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'pull_request')) && (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion'))) runs-on: ubuntu-slim permissions: contents: read From 4ce24f3b64a1b318b340085746859e710a52409c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:11:25 +0000 Subject: [PATCH 26/40] Move slash router setupGlobals call into github-script source Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 ++ actions/setup/js/route_slash_command.cjs | 6 +----- pkg/workflow/central_slash_command_workflow.go | 2 ++ pkg/workflow/central_slash_command_workflow_test.go | 4 +++- pkg/workflow/slash_command_centralized_compile_test.go | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 326886f099e..09e99b85108 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -56,5 +56,7 @@ jobs: GH_AW_SLASH_ROUTING: '{"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}]}' with: script: | + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); await main(); diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index 4328513ca45..257698be470 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -21,9 +21,7 @@ function resolveBodyText() { } function resolveDispatchRef() { - return process.env.GITHUB_HEAD_REF - ? `refs/heads/${process.env.GITHUB_HEAD_REF}` - : process.env.GITHUB_REF || context.ref || `refs/heads/${context.payload?.repository?.default_branch || "main"}`; + return process.env.GITHUB_HEAD_REF ? `refs/heads/${process.env.GITHUB_HEAD_REF}` : process.env.GITHUB_REF || context.ref || `refs/heads/${context.payload?.repository?.default_branch || "main"}`; } async function main() { @@ -43,8 +41,6 @@ async function main() { return; } - const { setupGlobals } = require("./setup_globals.cjs"); - setupGlobals(core, github, context, exec, io, getOctokit); const { buildAwContext } = require("./aw_context.cjs"); const ref = resolveDispatchRef(); diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 339844d6cb9..ec09eca70b0 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -159,6 +159,8 @@ jobs: GH_AW_SLASH_ROUTING: '` + escapeSingleQuotedYAMLString(string(routesJSON)) + `' with: script: | + const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); await main(); `) diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 45531ae5bb5..e41a9b219e0 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -53,11 +53,13 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.Contains(t, text, "discussion_comment:") require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) + require.Contains(t, text, `require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs")`) + require.Contains(t, text, `setupGlobals(core, github, context, exec, io, getOctokit);`) require.Contains(t, text, `require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs")`) require.NotContains(t, text, `const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}");`) require.NotContains(t, text, `trustedAuthorAssociations`) require.NotContains(t, text, `isForkBasedPullRequestEvent`) - require.Contains(t, text, `workflow_id: route.workflow + ".lock.yml"`) + require.NotContains(t, text, `workflow_id: route.workflow + ".lock.yml"`) } func TestGenerateCentralSlashCommandWorkflow_DeletesWhenUnused(t *testing.T) { diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go index a65f7455edc..863a422f771 100644 --- a/pkg/workflow/slash_command_centralized_compile_test.go +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -77,7 +77,7 @@ tools: compiled := string(lockContent) require.Contains(t, compiled, "workflow_dispatch:") - require.NotContains(t, compiled, "issues:") + require.NotContains(t, compiled, "\"on\":\n issues:") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'triage'") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") } From 67d559395767e75b9459b090b36a96e5438c7297 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:15:25 +0000 Subject: [PATCH 27/40] Refine centralized slash routing setup context and tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 12 +++++++++++- .../slash_command_centralized_compile_test.go | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index 257698be470..ff6a984aa6b 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -21,7 +21,17 @@ function resolveBodyText() { } function resolveDispatchRef() { - return process.env.GITHUB_HEAD_REF ? `refs/heads/${process.env.GITHUB_HEAD_REF}` : process.env.GITHUB_REF || context.ref || `refs/heads/${context.payload?.repository?.default_branch || "main"}`; + if (process.env.GITHUB_HEAD_REF) { + return `refs/heads/${process.env.GITHUB_HEAD_REF}`; + } + + const fallbackRef = process.env.GITHUB_REF || context.ref; + if (fallbackRef) { + return fallbackRef; + } + + const defaultBranch = context.payload?.repository?.default_branch || "main"; + return `refs/heads/${defaultBranch}`; } async function main() { diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go index 863a422f771..5c2d8f552ce 100644 --- a/pkg/workflow/slash_command_centralized_compile_test.go +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -76,8 +76,9 @@ tools: require.NoError(t, err) compiled := string(lockContent) + require.Contains(t, compiled, "\"on\":\n workflow_dispatch:") require.Contains(t, compiled, "workflow_dispatch:") - require.NotContains(t, compiled, "\"on\":\n issues:") + require.NotContains(t, compiled, "\n issues:\n types:") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'triage'") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") } From 5a6c5333ddcd77fbe4fed9995278045c0b2227e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 16:47:08 +0000 Subject: [PATCH 28/40] Align centralized label conditions with aw_context event filtering Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/cloclo.lock.yml | 22 +++++++++---------- pkg/workflow/label_command.go | 12 ++-------- pkg/workflow/label_command_test.go | 2 +- .../slash_command_centralized_compile_test.go | 1 + 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 467aa6faa72..e7a2882ab22 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -91,13 +91,11 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'workflow_dispatch' && ((fromJSON(github.event.inputs.aw_context || - '{}').event_type == 'issues' || fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request' || - fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion') && fromJSON(github.event.inputs.aw_context || - '{}').trigger_label == 'cloclo' || (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && - (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request')) && - (!(fromJSON(github.event.inputs.aw_context || - '{}').event_type == 'discussion')))) + needs.pre_activation.outputs.activated == 'true' && ((fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || + fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request' || fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'discussion') && fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo' || + (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'pull_request')) && (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion'))) runs-on: ubuntu-slim permissions: actions: read @@ -1746,11 +1744,11 @@ jobs: pre_activation: if: > - github.event_name == 'workflow_dispatch' && ((fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || - fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request' || fromJSON(github.event.inputs.aw_context || - '{}').event_type == 'discussion') && fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo' || - (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || - '{}').event_type == 'pull_request')) && (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion'))) + (fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'pull_request' || fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion') && + fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo' || (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request')) && + (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'discussion')) runs-on: ubuntu-slim permissions: contents: read diff --git a/pkg/workflow/label_command.go b/pkg/workflow/label_command.go index 30579ed7545..68a2b6a5a02 100644 --- a/pkg/workflow/label_command.go +++ b/pkg/workflow/label_command.go @@ -125,14 +125,6 @@ func buildCentralizedLabelCommandCondition(labelNames []string, labelCommandEven Left: &AndNode{Left: isLabelSourceEvent, Right: labelNameMatch}, Right: &NotNode{Child: isLabelSourceEvent}, } - - dispatchCheck := BuildEventTypeEquals("workflow_dispatch") - if !hasOtherEvents { - return &AndNode{Left: dispatchCheck, Right: dispatchLabelCondition}, nil - } - - return &OrNode{ - Left: &AndNode{Left: dispatchCheck, Right: dispatchLabelCondition}, - Right: &NotNode{Child: dispatchCheck}, - }, nil + _ = hasOtherEvents + return dispatchLabelCondition, nil } diff --git a/pkg/workflow/label_command_test.go b/pkg/workflow/label_command_test.go index 41297aaad20..05025a1adc3 100644 --- a/pkg/workflow/label_command_test.go +++ b/pkg/workflow/label_command_test.go @@ -203,7 +203,7 @@ func TestBuildCentralizedLabelCommandCondition(t *testing.T) { condition, err := buildCentralizedLabelCommandCondition([]string{"cloclo"}, []string{"issues"}, false) require.NoError(t, err) rendered := condition.Render() - assert.Contains(t, rendered, "github.event_name == 'workflow_dispatch'") + assert.NotContains(t, rendered, "github.event_name") assert.Contains(t, rendered, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") assert.Contains(t, rendered, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'cloclo'") } diff --git a/pkg/workflow/slash_command_centralized_compile_test.go b/pkg/workflow/slash_command_centralized_compile_test.go index 5c2d8f552ce..7b2d7f6e82e 100644 --- a/pkg/workflow/slash_command_centralized_compile_test.go +++ b/pkg/workflow/slash_command_centralized_compile_test.go @@ -79,6 +79,7 @@ tools: require.Contains(t, compiled, "\"on\":\n workflow_dispatch:") require.Contains(t, compiled, "workflow_dispatch:") require.NotContains(t, compiled, "\n issues:\n types:") + require.NotContains(t, compiled, "github.event_name == 'workflow_dispatch'") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'triage'") require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'") } From 6a08f2cc5e121585c77c8c0c30d8a86e7fd301e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 16:51:39 +0000 Subject: [PATCH 29/40] Simplify centralized label-condition builder signature Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/label_command.go | 3 +-- pkg/workflow/label_command_test.go | 2 +- pkg/workflow/tools.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/workflow/label_command.go b/pkg/workflow/label_command.go index 68a2b6a5a02..c51b752c75b 100644 --- a/pkg/workflow/label_command.go +++ b/pkg/workflow/label_command.go @@ -98,7 +98,7 @@ func buildLabelCommandCondition(labelNames []string, labelCommandEvents []string // slash-command workflows that trigger through workflow_dispatch. // For workflow_dispatch events, label routing checks use aw_context fields rather than // github.event_name/github.event.label. -func buildCentralizedLabelCommandCondition(labelNames []string, labelCommandEvents []string, hasOtherEvents bool) (ConditionNode, error) { +func buildCentralizedLabelCommandCondition(labelNames []string, labelCommandEvents []string) (ConditionNode, error) { if len(labelNames) == 0 { return nil, errors.New("no label names provided for label-command trigger") } @@ -125,6 +125,5 @@ func buildCentralizedLabelCommandCondition(labelNames []string, labelCommandEven Left: &AndNode{Left: isLabelSourceEvent, Right: labelNameMatch}, Right: &NotNode{Child: isLabelSourceEvent}, } - _ = hasOtherEvents return dispatchLabelCondition, nil } diff --git a/pkg/workflow/label_command_test.go b/pkg/workflow/label_command_test.go index 05025a1adc3..cbd2e69c2f5 100644 --- a/pkg/workflow/label_command_test.go +++ b/pkg/workflow/label_command_test.go @@ -200,7 +200,7 @@ func TestBuildLabelCommandCondition(t *testing.T) { } func TestBuildCentralizedLabelCommandCondition(t *testing.T) { - condition, err := buildCentralizedLabelCommandCondition([]string{"cloclo"}, []string{"issues"}, false) + condition, err := buildCentralizedLabelCommandCondition([]string{"cloclo"}, []string{"issues"}) require.NoError(t, err) rendered := condition.Render() assert.NotContains(t, rendered, "github.event_name") diff --git a/pkg/workflow/tools.go b/pkg/workflow/tools.go index 31a0cd69594..bb221755c6d 100644 --- a/pkg/workflow/tools.go +++ b/pkg/workflow/tools.go @@ -185,7 +185,7 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error } else if data.If == "" && len(data.LabelCommand) > 0 { // Centralized command mode compiles slash-command workflows as workflow_dispatch // targets. Label checks for dispatches must be derived from aw_context metadata. - labelConditionTree, err := buildCentralizedLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents, len(data.CommandOtherEvents) > 0) + labelConditionTree, err := buildCentralizedLabelCommandCondition(data.LabelCommand, data.LabelCommandEvents) if err != nil { return fmt.Errorf("failed to build label-command condition: %w", err) } else { From 727775c2308523663c44cc97b860d65c30ab7ec6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:12:12 +0000 Subject: [PATCH 30/40] Plan workflow recompile follow-up Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 09e99b85108..fec320efc02 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -20,7 +20,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Compiler version: b47da81 +# Compiler version: 6a08f2c # name: "Agentic Commands" From 6281bd9de777c147696e9b60f56c2807ff41900d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:13:59 +0000 Subject: [PATCH 31/40] Recompile generated agentic commands workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index fec320efc02..6cf7d37fbbe 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -20,7 +20,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Compiler version: 6a08f2c +# Compiler version: 727775c # name: "Agentic Commands" From eb58016834498106130e3b81dd65fb83a822a257 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:23:23 +0000 Subject: [PATCH 32/40] Plan commands metadata header update Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 +- pkg/workflow/schemas/github-workflow.json | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 6cf7d37fbbe..823e6d45f83 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -20,7 +20,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Compiler version: 727775c +# Compiler version: 6281bd9-dirty # name: "Agentic Commands" diff --git a/pkg/workflow/schemas/github-workflow.json b/pkg/workflow/schemas/github-workflow.json index c2f1f432201..338994b294a 100644 --- a/pkg/workflow/schemas/github-workflow.json +++ b/pkg/workflow/schemas/github-workflow.json @@ -18,7 +18,7 @@ "properties": { "group": { "$comment": "https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-using-concurrency-to-cancel-any-in-progress-job-or-run-1", - "description": "When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled.", + "description": "When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. By default any previously pending job or workflow in the concurrency group will be canceled; this behavior can be changed with `queue`.", "type": "string" }, "cancel-in-progress": { @@ -32,6 +32,13 @@ "$ref": "#/definitions/expressionSyntax" } ] + }, + "queue": { + "$comment": "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-queueing-multiple-pending-runs", + "description": "Controls how pending jobs or workflow runs are queued within a concurrency group. With the default `single`, at most one run can be pending — additional pending runs cancel the previous one. With `max`, up to 100 runs can be pending and are processed in FIFO order. The combination of `queue: max` and `cancel-in-progress: true` is not allowed.", + "type": "string", + "enum": ["single", "max"], + "default": "single" } }, "required": ["group"], @@ -718,7 +725,7 @@ }, "concurrency": { "$comment": "https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency", - "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", + "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. By default any previously pending job or workflow in the concurrency group will be canceled; this behavior can be changed with `queue`. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", "oneOf": [ { "type": "string" @@ -921,7 +928,7 @@ }, "concurrency": { "$comment": "https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency", - "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", + "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. By default any previously pending job or workflow in the concurrency group will be canceled; this behavior can be changed with `queue`. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", "oneOf": [ { "type": "string" @@ -1780,7 +1787,7 @@ }, "concurrency": { "$comment": "https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency", - "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", + "description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \nYou can also specify concurrency at the workflow level. \nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. By default any previously pending job or workflow in the concurrency group will be canceled; this behavior can be changed with `queue`. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.", "oneOf": [ { "type": "string" From 8742d99493b0f8d60e607d058dfb1c69eebad95e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:25:50 +0000 Subject: [PATCH 33/40] Add gh-aw-commands JSON metadata header Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 3 +-- pkg/workflow/central_slash_command_workflow.go | 13 ++++++++++++- pkg/workflow/central_slash_command_workflow_test.go | 11 ++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 823e6d45f83..67e50f662b8 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,3 +1,4 @@ +# gh-aw-commands: {"compiler_version":"eb58016-dirty","schema_version":"v1"} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -20,8 +21,6 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Compiler version: 6281bd9-dirty -# name: "Agentic Commands" on: diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index ec09eca70b0..3f1b31beb15 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -131,9 +131,20 @@ func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashComm return "", fmt.Errorf("failed to marshal centralized slash-command routes: %w", err) } - header := GenerateWorkflowHeader("", "gh-aw", "Compiler version: "+GetVersion()) + commandsMetadata, err := json.Marshal(map[string]string{ + "schema_version": "v1", + "compiler_version": GetVersion(), + }) + if err != nil { + return "", fmt.Errorf("failed to marshal centralized slash-command metadata: %w", err) + } + + header := GenerateWorkflowHeader("", "gh-aw", "") var b strings.Builder + b.WriteString("# gh-aw-commands: ") + b.Write(commandsMetadata) + b.WriteString("\n") b.WriteString(header) b.WriteString(`name: "Agentic Commands" diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index e41a9b219e0..4c8509ea4fc 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -3,8 +3,10 @@ package workflow import ( + "encoding/json" "os" "path/filepath" + "strings" "testing" "github.com/github/gh-aw/pkg/testutil" @@ -41,9 +43,16 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { content, err := os.ReadFile(generatedPath) require.NoError(t, err) text := string(content) + lines := strings.Split(text, "\n") + require.NotEmpty(t, lines) + require.Contains(t, lines[0], "# gh-aw-commands: ") + metadataJSON := strings.TrimPrefix(lines[0], "# gh-aw-commands: ") + var metadata map[string]string + require.NoError(t, json.Unmarshal([]byte(metadataJSON), &metadata)) + require.Equal(t, "v1", metadata["schema_version"]) + require.NotEmpty(t, metadata["compiler_version"]) require.Contains(t, text, "name: \"Agentic Commands\"") - require.Contains(t, text, "Compiler version:") require.Contains(t, text, "permissions: {}") require.Contains(t, text, "runs-on: ubuntu-slim") require.Contains(t, text, " permissions:\n actions: write\n contents: read") From 7165207e63519104f0daa989a5487dca0c2ac6d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:29:40 +0000 Subject: [PATCH 34/40] Assert legacy compiler header is removed Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/central_slash_command_workflow_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 4c8509ea4fc..c2d9558aa64 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -53,6 +53,7 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.NotEmpty(t, metadata["compiler_version"]) require.Contains(t, text, "name: \"Agentic Commands\"") + require.NotContains(t, text, "Compiler version:") require.Contains(t, text, "permissions: {}") require.Contains(t, text, "runs-on: ubuntu-slim") require.Contains(t, text, " permissions:\n actions: write\n contents: read") From 8fc1c59575473ae5c50c1e634965bce812322a56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:34:27 +0000 Subject: [PATCH 35/40] Plan slash command logging updates Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 67e50f662b8..7f0ae8a5724 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,4 +1,4 @@ -# gh-aw-commands: {"compiler_version":"eb58016-dirty","schema_version":"v1"} +# gh-aw-commands: {"compiler_version":"7165207","schema_version":"v1"} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ From 78c41197c71371039509dc17749a28651e6bbb93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:35:15 +0000 Subject: [PATCH 36/40] Add detailed router logging for slash command dispatch Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index ff6a984aa6b..c8f708e5245 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -35,9 +35,16 @@ function resolveDispatchRef() { } async function main() { + core.info("Starting centralized slash command routing."); + core.info(`Incoming event name: '${context.eventName}'.`); + const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); + core.info(`Configured centralized commands: ${Object.keys(routeMap).length}.`); + const text = resolveBodyText(); + core.info(`Resolved payload text length: ${String(text).length}.`); const firstWord = String(text).trim().split(/\s+/)[0] ?? ""; + core.info(`First token in payload: '${firstWord || ""}'.`); if (!firstWord.startsWith("/")) { core.info("No slash command found at start of payload text; skipping dispatch."); return; @@ -45,18 +52,24 @@ async function main() { const commandName = firstWord.slice(1); const identifier = eventIdentifier(); + core.info(`Resolved command '/${commandName}' for event identifier '${identifier}'.`); + const configuredRoutes = routeMap[commandName] ?? []; + core.info(`Configured routes for '/${commandName}': ${configuredRoutes.length}.`); const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); if (routes.length === 0) { core.info(`No centralized routes matched command '/${commandName}' for event '${identifier}'.`); return; } + core.info(`Matched routes for '/${commandName}' on '${identifier}': ${routes.map(route => route.workflow).join(", ")}.`); const { buildAwContext } = require("./aw_context.cjs"); const ref = resolveDispatchRef(); + core.info(`Dispatch ref resolved to '${ref}'.`); for (const route of routes) { const awContext = buildAwContext(); awContext.command_name = commandName; + core.info(`Dispatching workflow '${route.workflow}.lock.yml' for '/${commandName}'.`); await github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, repo: context.repo.repo, @@ -68,6 +81,7 @@ async function main() { }); core.info(`Dispatched '${route.workflow}' for '/${commandName}'`); } + core.info(`Completed centralized routing for '/${commandName}'.`); } module.exports = { main, eventIdentifier, resolveBodyText, resolveDispatchRef }; From 60ad42e21353ea2b83f054b59868ccbf4b8f70cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:38:35 +0000 Subject: [PATCH 37/40] Use configured routes in slash router filter Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index c8f708e5245..de077f2ac57 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -55,7 +55,7 @@ async function main() { core.info(`Resolved command '/${commandName}' for event identifier '${identifier}'.`); const configuredRoutes = routeMap[commandName] ?? []; core.info(`Configured routes for '/${commandName}': ${configuredRoutes.length}.`); - const routes = (routeMap[commandName] ?? []).filter(route => Array.isArray(route.events) && route.events.includes(identifier)); + const routes = configuredRoutes.filter(route => Array.isArray(route.events) && route.events.includes(identifier)); if (routes.length === 0) { core.info(`No centralized routes matched command '/${commandName}' for event '${identifier}'.`); return; From 31a6d32d0c8beb0eb8b1372b612cc8bcab60daa8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:49:45 +0000 Subject: [PATCH 38/40] Add slash router payload metadata and centralize slash commands Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ace-editor.lock.yml | 46 ++++++------- .github/workflows/ace-editor.md | 1 + .github/workflows/agentic_commands.yml | 4 +- .github/workflows/approach-validator.lock.yml | 57 ++++++++-------- .github/workflows/approach-validator.md | 1 + .github/workflows/brave.lock.yml | 42 ++++++------ .github/workflows/brave.md | 1 + .github/workflows/craft.lock.yml | 46 ++++++------- .github/workflows/craft.md | 1 + .github/workflows/grumpy-reviewer.lock.yml | 66 +++++++++---------- .github/workflows/grumpy-reviewer.md | 1 + .github/workflows/mergefest.lock.yml | 46 ++++++------- .github/workflows/mergefest.md | 1 + .github/workflows/pdf-summary.lock.yml | 40 ++++------- .github/workflows/pdf-summary.md | 1 + .github/workflows/plan.lock.yml | 47 ++++++------- .github/workflows/plan.md | 1 + .github/workflows/poem-bot.lock.yml | 39 +++++------ .github/workflows/poem-bot.md | 1 + .../pr-code-quality-reviewer.lock.yml | 46 ++++++------- .github/workflows/pr-code-quality-reviewer.md | 1 + .../workflows/pr-nitpick-reviewer.lock.yml | 46 ++++++------- .github/workflows/pr-nitpick-reviewer.md | 1 + .github/workflows/security-review.lock.yml | 46 ++++++------- .github/workflows/security-review.md | 1 + .github/workflows/tidy.lock.yml | 39 +++++------ .github/workflows/tidy.md | 1 + .github/workflows/unbloat-docs.lock.yml | 41 +++++------- .github/workflows/unbloat-docs.md | 1 + .../central_slash_command_workflow.go | 39 +++++++++-- .../central_slash_command_workflow_test.go | 9 ++- 31 files changed, 359 insertions(+), 354 deletions(-) diff --git a/.github/workflows/ace-editor.lock.yml b/.github/workflows/ace-editor.lock.yml index 257c19989b5..e209d577c4c 100644 --- a/.github/workflows/ace-editor.lock.yml +++ b/.github/workflows/ace-editor.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6d5aafde7712b01923337ac46b801dfdd5e9ac02a91c9b41a9cd40ac580ef4e3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"370c523f11a9b58913c58bb6ab0362ea3b582b0204cd9a3f493b95ad2b830f11","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -53,10 +53,13 @@ name: "ACE Editor Session" "on": - issue_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -75,13 +78,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/ace ') || startsWith(github.event.comment.body, '/ace\n') || github.event.comment.body == '/ace') && github.event.issue.pull_request != null)" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -236,23 +237,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_eec6611805947a64_EOF' + cat << 'GH_AW_PROMPT_484df9ad5d20924a_EOF' - GH_AW_PROMPT_eec6611805947a64_EOF + GH_AW_PROMPT_484df9ad5d20924a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_eec6611805947a64_EOF' + cat << 'GH_AW_PROMPT_484df9ad5d20924a_EOF' Tools: create_issue - GH_AW_PROMPT_eec6611805947a64_EOF + GH_AW_PROMPT_484df9ad5d20924a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_eec6611805947a64_EOF' + cat << 'GH_AW_PROMPT_484df9ad5d20924a_EOF' - GH_AW_PROMPT_eec6611805947a64_EOF + GH_AW_PROMPT_484df9ad5d20924a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_eec6611805947a64_EOF' + cat << 'GH_AW_PROMPT_484df9ad5d20924a_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -281,13 +282,13 @@ jobs: {{/if}} - GH_AW_PROMPT_eec6611805947a64_EOF + GH_AW_PROMPT_484df9ad5d20924a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_eec6611805947a64_EOF' + cat << 'GH_AW_PROMPT_484df9ad5d20924a_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/ace-editor.md}} - GH_AW_PROMPT_eec6611805947a64_EOF + GH_AW_PROMPT_484df9ad5d20924a_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -482,9 +483,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0f4b7414d0dba99a_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d63fa8d751dcbb70_EOF' {"create_issue":{"labels":["ace-editor"],"max":1,"title_prefix":"[ace-editor]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_0f4b7414d0dba99a_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_d63fa8d751dcbb70_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -616,7 +617,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e4136b9e2e7ea1f0_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_ea272df3369fdd3f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -663,7 +664,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_e4136b9e2e7ea1f0_EOF + GH_AW_MCP_CONFIG_ea272df3369fdd3f_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1038,7 +1039,6 @@ jobs: }); pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/ace ') || startsWith(github.event.comment.body, '/ace\n') || github.event.comment.body == '/ace') && github.event.issue.pull_request != null)" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/ace-editor.md b/.github/workflows/ace-editor.md index 9ad13a0af25..94c224e7f65 100644 --- a/.github/workflows/ace-editor.md +++ b/.github/workflows/ace-editor.md @@ -3,6 +3,7 @@ name: ACE Editor Session description: Generates an ACE editor session link when invoked with /ace command on pull request comments on: slash_command: + strategy: centralized name: ace events: [pull_request_comment] strict: false diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 7f0ae8a5724..52a7ba0e9bb 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,4 +1,4 @@ -# gh-aw-commands: {"compiler_version":"7165207","schema_version":"v1"} +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"60ad42e","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","mergefest","nit","plan","poem-bot","review","security-review","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","cloclo","craft","grumpy-reviewer","mergefest","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","security-review","tidy","unbloat-docs"]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -52,7 +52,7 @@ jobs: - name: Route slash command uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_SLASH_ROUTING: '{"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}]}' + GH_AW_SLASH_ROUTING: '{"ace":[{"workflow":"ace-editor","events":["pull_request_comment"]}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"]}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"brave":[{"workflow":"brave","events":["issue_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}],"craft":[{"workflow":"craft","events":["issues"]}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"]}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"]}],"poem-bot":[{"workflow":"poem-bot","events":["issues"]}],"review":[{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"]}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"]}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"]}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"]}]}' with: script: | const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index a0e3bb4c5a6..3eb09afaefd 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"578875ad833fb057dda9d76379ec71b0d31037777d82bc02518441db2d8d6e5d","strict":true,"agent_id":"claude"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"9e79ec7966984d8bba6373164eab732d58ed682097e00df62bae6fae8f0b37c3","strict":true,"agent_id":"claude"} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,16 +57,13 @@ name: "Approach Validator" "on": - issue_comment: - types: - - created - - edited - issues: - types: - - labeled - pull_request: - types: - - labeled + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -85,13 +82,17 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/approach-validator ') || startsWith(github.event.comment.body, '/approach-validator\n') || github.event.comment.body == '/approach-validator') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/approach-validator ') || startsWith(github.event.comment.body, '/approach-validator\n') || github.event.comment.body == '/approach-validator') && github.event.issue.pull_request != null || github.event_name == 'issues' && (github.event.label.name == 'approach-proposal' || github.event.label.name == 'needs-design') || github.event_name == 'pull_request' && (github.event.label.name == 'approach-proposal' || github.event.label.name == 'needs-design'))" + if: > + needs.pre_activation.outputs.activated == 'true' && ((fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || + fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request') && (fromJSON(github.event.inputs.aw_context || + '{}').trigger_label == 'approach-proposal' || fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'needs-design') || + (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'pull_request'))) runs-on: ubuntu-slim permissions: actions: read contents: read issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -264,20 +265,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_f6efd604eccbb175_EOF' + cat << 'GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF' - GH_AW_PROMPT_f6efd604eccbb175_EOF + GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_f6efd604eccbb175_EOF' + cat << 'GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF' Tools: add_comment(max:2), add_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_f6efd604eccbb175_EOF + GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_f6efd604eccbb175_EOF' + cat << 'GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -306,18 +307,18 @@ jobs: {{/if}} - GH_AW_PROMPT_f6efd604eccbb175_EOF + GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_f6efd604eccbb175_EOF' + cat << 'GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF' {{#runtime-import .github/workflows/shared/safe-output-upload-artifact.md}} {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/approach-validator.md}} - GH_AW_PROMPT_f6efd604eccbb175_EOF + GH_AW_PROMPT_64b8ea9d5da1d3bf_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -550,9 +551,9 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs/upload-artifacts" - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1cd5b461e4d94d48_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f3ebf30c01743305_EOF' {"add_comment":{"hide_older_comments":true,"max":2},"add_labels":{"allowed":["awaiting-approach-approval","approach-approved","approach-rejected"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_artifact":{"max-size-bytes":104857600,"max-uploads":3,"retention-days":30,"skip-archive":true}} - GH_AW_SAFE_OUTPUTS_CONFIG_1cd5b461e4d94d48_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_f3ebf30c01743305_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -759,7 +760,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_bea2c9c91f5b4b9f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d68f4255db71c049_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -790,7 +791,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_bea2c9c91f5b4b9f_EOF + GH_AW_MCP_CONFIG_d68f4255db71c049_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1512,7 +1513,11 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/approach-validator ') || startsWith(github.event.comment.body, '/approach-validator\n') || github.event.comment.body == '/approach-validator') && github.event.issue.pull_request == null || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/approach-validator ') || startsWith(github.event.comment.body, '/approach-validator\n') || github.event.comment.body == '/approach-validator') && github.event.issue.pull_request != null || github.event_name == 'issues' && (github.event.label.name == 'approach-proposal' || github.event.label.name == 'needs-design') || github.event_name == 'pull_request' && (github.event.label.name == 'approach-proposal' || github.event.label.name == 'needs-design'))" + if: > + (fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues' || fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'pull_request') && (fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'approach-proposal' || + fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'needs-design') || (!(fromJSON(github.event.inputs.aw_context || + '{}').event_type == 'issues')) && (!(fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/approach-validator.md b/.github/workflows/approach-validator.md index 7b16c5f69a6..d1cba71f54d 100644 --- a/.github/workflows/approach-validator.md +++ b/.github/workflows/approach-validator.md @@ -6,6 +6,7 @@ on: names: [approach-proposal, needs-design] events: [issues, pull_request] slash_command: + strategy: centralized name: approach-validator events: [issue_comment, pull_request_comment] permissions: diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 1d88aea435c..db328ab35ba 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"dbbaf8b75dd3bdafbe8dcac6fe1280216904eb8207df077ebb08651e23b0dd22","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5863c3180ac0aaf170fe6083be2a8310cef5d121fef48c221fd21aeae6c5dff6","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["BRAVE_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"docker.io/mcp/brave-search","digest":"sha256:ca96b8acb27d8cf601a8faef86a084602cffa41d8cb18caa1e29ba4d16989d22","pinned_image":"docker.io/mcp/brave-search@sha256:ca96b8acb27d8cf601a8faef86a084602cffa41d8cb18caa1e29ba4d16989d22"},{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -56,10 +56,13 @@ name: "Brave Web Search Agent" "on": - issue_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -78,13 +81,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/brave ') || startsWith(github.event.comment.body, '/brave\n') || github.event.comment.body == '/brave') && github.event.issue.pull_request == null)" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -237,20 +238,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b28fd8abe8fe56b6_EOF' + cat << 'GH_AW_PROMPT_3b68453501dd6f11_EOF' - GH_AW_PROMPT_b28fd8abe8fe56b6_EOF + GH_AW_PROMPT_3b68453501dd6f11_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_b28fd8abe8fe56b6_EOF' + cat << 'GH_AW_PROMPT_3b68453501dd6f11_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_b28fd8abe8fe56b6_EOF + GH_AW_PROMPT_3b68453501dd6f11_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b28fd8abe8fe56b6_EOF' + cat << 'GH_AW_PROMPT_3b68453501dd6f11_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -279,18 +280,18 @@ jobs: {{/if}} - GH_AW_PROMPT_b28fd8abe8fe56b6_EOF + GH_AW_PROMPT_3b68453501dd6f11_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_b28fd8abe8fe56b6_EOF' + cat << 'GH_AW_PROMPT_3b68453501dd6f11_EOF' {{#runtime-import .github/workflows/shared/mcp/brave.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/brave.md}} - GH_AW_PROMPT_b28fd8abe8fe56b6_EOF + GH_AW_PROMPT_3b68453501dd6f11_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -515,9 +516,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4a87881f5ed92b05_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_25d0c57c08791256_EOF' {"add_comment":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_4a87881f5ed92b05_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_25d0c57c08791256_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -709,7 +710,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_62428ca1f0fed3d0_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_1bc14b4b122ee402_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "brave-search": { @@ -773,7 +774,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_62428ca1f0fed3d0_EOF + GH_AW_MCP_CONFIG_1bc14b4b122ee402_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1393,7 +1394,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/brave ') || startsWith(github.event.comment.body, '/brave\n') || github.event.comment.body == '/brave') && github.event.issue.pull_request == null)" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/brave.md b/.github/workflows/brave.md index 695fe8f3b27..74ef18a49ef 100644 --- a/.github/workflows/brave.md +++ b/.github/workflows/brave.md @@ -2,6 +2,7 @@ description: Performs web searches using Brave search engine when invoked with /brave command in issues or PRs on: slash_command: + strategy: centralized name: brave events: [issue_comment] permissions: diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 04dff130e49..387ac8bbb6b 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"647b315d95e193719dded1c53ac8c40e9afc9482b5dc15e78c39ec2cde767bbe","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d2f9af0fec608b35c7888471217fe1843f22037ba589027cc5e52ede8afba8d3","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -55,11 +55,13 @@ name: "Workflow Craft Agent" "on": - issues: - types: - - opened - - edited - - reopened + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -78,12 +80,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/craft ') || startsWith(github.event.issue.body, '/craft\n') || github.event.issue.body == '/craft'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -235,23 +236,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_aada0b6cd6fb8084_EOF' + cat << 'GH_AW_PROMPT_2d44bd4d81e20334_EOF' - GH_AW_PROMPT_aada0b6cd6fb8084_EOF + GH_AW_PROMPT_2d44bd4d81e20334_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_aada0b6cd6fb8084_EOF' + cat << 'GH_AW_PROMPT_2d44bd4d81e20334_EOF' Tools: add_comment, push_to_pull_request_branch, missing_tool, missing_data, noop - GH_AW_PROMPT_aada0b6cd6fb8084_EOF + GH_AW_PROMPT_2d44bd4d81e20334_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_aada0b6cd6fb8084_EOF' + cat << 'GH_AW_PROMPT_2d44bd4d81e20334_EOF' - GH_AW_PROMPT_aada0b6cd6fb8084_EOF + GH_AW_PROMPT_2d44bd4d81e20334_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_aada0b6cd6fb8084_EOF' + cat << 'GH_AW_PROMPT_2d44bd4d81e20334_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -280,7 +281,7 @@ jobs: {{/if}} - GH_AW_PROMPT_aada0b6cd6fb8084_EOF + GH_AW_PROMPT_2d44bd4d81e20334_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -288,12 +289,12 @@ jobs: if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_aada0b6cd6fb8084_EOF' + cat << 'GH_AW_PROMPT_2d44bd4d81e20334_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/craft.md}} - GH_AW_PROMPT_aada0b6cd6fb8084_EOF + GH_AW_PROMPT_2d44bd4d81e20334_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -520,9 +521,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7fd0064ae66ac97a_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7debd37bc8486a79_EOF' {"add_comment":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_7fd0064ae66ac97a_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_7debd37bc8486a79_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -730,7 +731,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d8bf39a78dbb4f1f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d8b7ac7de46cac63_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -761,7 +762,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_d8bf39a78dbb4f1f_EOF + GH_AW_MCP_CONFIG_d8b7ac7de46cac63_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1395,7 +1396,6 @@ jobs: } pre_activation: - if: "github.event_name == 'issues' && (startsWith(github.event.issue.body, '/craft ') || startsWith(github.event.issue.body, '/craft\n') || github.event.issue.body == '/craft')" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/craft.md b/.github/workflows/craft.md index c5c66147d5a..5d0cffbfe4f 100644 --- a/.github/workflows/craft.md +++ b/.github/workflows/craft.md @@ -2,6 +2,7 @@ description: Generates new agentic workflow markdown files based on user requests when invoked with /craft command on: slash_command: + strategy: centralized name: craft events: [issues] permissions: diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 050bfb99d30..f3a78509c90 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"23bea2711923688e67283b0f1fc2e4da195a90f857e2ba7b7121e047e97dd601","strict":true,"agent_id":"codex"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"9d02389aa226846bcccda9bc0415279f27dc508a04cd597f0eb9d89ae9cf142f","strict":true,"agent_id":"codex"} # gh-aw-manifest: {"version":1,"secrets":["CODEX_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,14 +57,13 @@ name: "Grumpy Code Reviewer 🔥" "on": - issue_comment: - types: - - created - - edited - pull_request_review_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -83,13 +82,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/grumpy ') || startsWith(github.event.comment.body, '/grumpy\n') || github.event.comment.body == '/grumpy') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/grumpy ') || startsWith(github.event.comment.body, '/grumpy\n') || github.event.comment.body == '/grumpy'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -248,20 +245,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_39fbcedd9459ea8c_EOF' + cat << 'GH_AW_PROMPT_6f47da74927b7aa3_EOF' - GH_AW_PROMPT_39fbcedd9459ea8c_EOF + GH_AW_PROMPT_6f47da74927b7aa3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_39fbcedd9459ea8c_EOF' + cat << 'GH_AW_PROMPT_6f47da74927b7aa3_EOF' Tools: create_pull_request_review_comment(max:5), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_39fbcedd9459ea8c_EOF + GH_AW_PROMPT_6f47da74927b7aa3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_39fbcedd9459ea8c_EOF' + cat << 'GH_AW_PROMPT_6f47da74927b7aa3_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -290,19 +287,19 @@ jobs: {{/if}} - GH_AW_PROMPT_39fbcedd9459ea8c_EOF + GH_AW_PROMPT_6f47da74927b7aa3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_39fbcedd9459ea8c_EOF' + cat << 'GH_AW_PROMPT_6f47da74927b7aa3_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/github-guard-policy.md}} {{#runtime-import .github/workflows/shared/pr-code-review-config.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/grumpy-reviewer.md}} - GH_AW_PROMPT_39fbcedd9459ea8c_EOF + GH_AW_PROMPT_6f47da74927b7aa3_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -519,9 +516,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_cc72f63e4152b950_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e46af0b67b311977_EOF' {"create_pull_request_review_comment":{"max":5,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_cc72f63e4152b950_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_e46af0b67b311977_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -743,7 +740,7 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e CODEX_HOME -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_dd9a8dab989d200f_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_dbaa8ab6db466e86_EOF [history] persistence = "none" @@ -770,11 +767,11 @@ jobs: [mcp_servers.safeoutputs."guard-policies".write-sink] accept = ["*"] - GH_AW_MCP_CONFIG_dd9a8dab989d200f_EOF + GH_AW_MCP_CONFIG_dbaa8ab6db466e86_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_dd9a8dab989d200f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_dbaa8ab6db466e86_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -823,11 +820,11 @@ jobs: } } } - GH_AW_MCP_CONFIG_dd9a8dab989d200f_EOF + GH_AW_MCP_CONFIG_dbaa8ab6db466e86_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_812e28467daf6039_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_c0013a2f2f45c0dd_EOF model_provider = "openai-proxy" @@ -839,7 +836,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_PERSONAL_ACCESS_TOKEN", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_812e28467daf6039_EOF + GH_AW_CODEX_SHELL_POLICY_c0013a2f2f45c0dd_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } @@ -1385,18 +1382,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e CODEX_HOME -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_d5b8f280601565e3_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_428900ed564d0c19_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_d5b8f280601565e3_EOF + GH_AW_MCP_CONFIG_428900ed564d0c19_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_924dbcc9b8a7ba20_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_407aa35746ecd309_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1407,11 +1404,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_924dbcc9b8a7ba20_EOF + GH_AW_MCP_CONFIG_407aa35746ecd309_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_d00933e1122878ea_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_28c2c5f09011972f_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1421,7 +1418,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_d00933e1122878ea_EOF + GH_AW_CODEX_SHELL_POLICY_28c2c5f09011972f_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } @@ -1506,7 +1503,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/grumpy ') || startsWith(github.event.comment.body, '/grumpy\n') || github.event.comment.body == '/grumpy') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/grumpy ') || startsWith(github.event.comment.body, '/grumpy\n') || github.event.comment.body == '/grumpy'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/grumpy-reviewer.md b/.github/workflows/grumpy-reviewer.md index 75c09da7c28..89396279100 100644 --- a/.github/workflows/grumpy-reviewer.md +++ b/.github/workflows/grumpy-reviewer.md @@ -2,6 +2,7 @@ description: "⚠️ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Performs critical code review with a focus on edge cases, potential bugs, and code quality issues" on: slash_command: + strategy: centralized name: grumpy events: [pull_request_comment, pull_request_review_comment] engine: codex diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 5e9aabddcbc..2a54852fb58 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"dddd808f28e606992cc4a49196e37ad53b279940521ac1684463d063146a8b55","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1e59cb388299dac6324e73c39f559081b07e5b3d705ba2d98fb0d08e3cec43cd","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -56,10 +56,13 @@ name: "Mergefest" "on": - issue_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -78,13 +81,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/mergefest ') || startsWith(github.event.comment.body, '/mergefest\n') || github.event.comment.body == '/mergefest') && github.event.issue.pull_request != null)" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -240,23 +241,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_55410a7f0473a4e4_EOF' + cat << 'GH_AW_PROMPT_e5875d0a04012b37_EOF' - GH_AW_PROMPT_55410a7f0473a4e4_EOF + GH_AW_PROMPT_e5875d0a04012b37_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_55410a7f0473a4e4_EOF' + cat << 'GH_AW_PROMPT_e5875d0a04012b37_EOF' Tools: push_to_pull_request_branch, missing_tool, missing_data, noop - GH_AW_PROMPT_55410a7f0473a4e4_EOF + GH_AW_PROMPT_e5875d0a04012b37_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_55410a7f0473a4e4_EOF' + cat << 'GH_AW_PROMPT_e5875d0a04012b37_EOF' - GH_AW_PROMPT_55410a7f0473a4e4_EOF + GH_AW_PROMPT_e5875d0a04012b37_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_55410a7f0473a4e4_EOF' + cat << 'GH_AW_PROMPT_e5875d0a04012b37_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -285,7 +286,7 @@ jobs: {{/if}} - GH_AW_PROMPT_55410a7f0473a4e4_EOF + GH_AW_PROMPT_e5875d0a04012b37_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -293,12 +294,12 @@ jobs: if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_55410a7f0473a4e4_EOF' + cat << 'GH_AW_PROMPT_e5875d0a04012b37_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/mergefest.md}} - GH_AW_PROMPT_55410a7f0473a4e4_EOF + GH_AW_PROMPT_e5875d0a04012b37_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -520,9 +521,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_fefaa16691f3aa98_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_eb16dc90db12aa10_EOF' {"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_fefaa16691f3aa98_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_eb16dc90db12aa10_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -706,7 +707,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_439f36d9a639d867_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_3a0203afbcaea5d1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -737,7 +738,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_439f36d9a639d867_EOF + GH_AW_MCP_CONFIG_3a0203afbcaea5d1_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1408,7 +1409,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/mergefest ') || startsWith(github.event.comment.body, '/mergefest\n') || github.event.comment.body == '/mergefest') && github.event.issue.pull_request != null)" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/mergefest.md b/.github/workflows/mergefest.md index b0981018550..4b7124d4676 100644 --- a/.github/workflows/mergefest.md +++ b/.github/workflows/mergefest.md @@ -3,6 +3,7 @@ name: Mergefest description: Automatically merges the main branch into pull request branches when invoked with /mergefest command on: slash_command: + strategy: centralized name: mergefest events: [pull_request_comment] permissions: diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index c67ff2a76ab..cb22c273a21 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"86cfe015e5b74d00b4b579cdee3237db81332ce7848f79c666939fcef84d3f05","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b3266673ee13f7a563f367e15facff38f8245763cf4cd906c263b2910fa249ff","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"mcp/markitdown","digest":"sha256:1cef3bf502503310ed0884441874ccf6cdaac20136dc1179797fa048269dc4cb","pinned_image":"mcp/markitdown@sha256:1cef3bf502503310ed0884441874ccf6cdaac20136dc1179797fa048269dc4cb"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -59,15 +59,6 @@ name: "Resource Summarizer Agent" "on": - issue_comment: - types: - - created - - edited - issues: - types: - - opened - - edited - - reopened workflow_dispatch: inputs: aw_context: @@ -102,13 +93,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && ((github.event_name == 'issue_comment' || github.event_name == 'issues') && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/summarize ') || startsWith(github.event.issue.body, '/summarize\n') || github.event.issue.body == '/summarize') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/summarize ') || startsWith(github.event.comment.body, '/summarize\n') || github.event.comment.body == '/summarize') && github.event.issue.pull_request == null) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'issues')))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -269,21 +258,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF' + cat << 'GH_AW_PROMPT_3bff113074d94d42_EOF' - GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF + GH_AW_PROMPT_3bff113074d94d42_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF' + cat << 'GH_AW_PROMPT_3bff113074d94d42_EOF' Tools: add_comment, create_discussion, missing_tool, missing_data, noop - GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF + GH_AW_PROMPT_3bff113074d94d42_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF' + cat << 'GH_AW_PROMPT_3bff113074d94d42_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -312,19 +301,19 @@ jobs: {{/if}} - GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF + GH_AW_PROMPT_3bff113074d94d42_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF' + cat << 'GH_AW_PROMPT_3bff113074d94d42_EOF' {{#runtime-import .github/workflows/shared/mcp/markitdown.md}} {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/pdf-summary.md}} - GH_AW_PROMPT_e89cb5c69ed2ea3d_EOF + GH_AW_PROMPT_3bff113074d94d42_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -576,9 +565,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ca3592545c7b7366_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4f5f6f9e4092e0b3_EOF' {"add_comment":{"max":1},"create_discussion":{"expires":24,"fallback_to_issue":true,"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_ca3592545c7b7366_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_4f5f6f9e4092e0b3_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -796,7 +785,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_52124769a2adfc71_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_23c6fc4a7264e17e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -857,7 +846,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_52124769a2adfc71_EOF + GH_AW_MCP_CONFIG_23c6fc4a7264e17e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1490,7 +1479,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && ((github.event_name == 'issue_comment' || github.event_name == 'issues') && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/summarize ') || startsWith(github.event.issue.body, '/summarize\n') || github.event.issue.body == '/summarize') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/summarize ') || startsWith(github.event.comment.body, '/summarize\n') || github.event.comment.body == '/summarize') && github.event.issue.pull_request == null) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'issues')))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pdf-summary.md b/.github/workflows/pdf-summary.md index 8b6a5fc62d5..a3dca276584 100644 --- a/.github/workflows/pdf-summary.md +++ b/.github/workflows/pdf-summary.md @@ -3,6 +3,7 @@ description: pdf summarizer on: # Command trigger - responds to /summarize mentions slash_command: + strategy: centralized name: summarize events: [issue_comment, issues] diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index d7ba8f3ac33..58b30037b90 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"887a55cc6c6e594c3f24ef3fe4daecfdba94bee6b055bb70f270cf40757a1d9d","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"026d0f78766e2ceb57211592f0964e334c5c2a6c9038cdb7d61b90084cf58bee","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -54,14 +54,13 @@ name: "Plan Command" "on": - discussion_comment: - types: - - created - - edited - issue_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -80,14 +79,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/plan ') || startsWith(github.event.comment.body, '/plan\n') || github.event.comment.body == '/plan') && github.event.issue.pull_request == null || github.event_name == 'discussion_comment' && (startsWith(github.event.comment.body, '/plan ') || startsWith(github.event.comment.body, '/plan\n') || github.event.comment.body == '/plan'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - discussions: write - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -244,20 +240,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_089bad1a80abb9ff_EOF' + cat << 'GH_AW_PROMPT_a29e2f4de291b4b6_EOF' - GH_AW_PROMPT_089bad1a80abb9ff_EOF + GH_AW_PROMPT_a29e2f4de291b4b6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_089bad1a80abb9ff_EOF' + cat << 'GH_AW_PROMPT_a29e2f4de291b4b6_EOF' Tools: create_issue(max:5), close_discussion, missing_tool, missing_data, noop - GH_AW_PROMPT_089bad1a80abb9ff_EOF + GH_AW_PROMPT_a29e2f4de291b4b6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_089bad1a80abb9ff_EOF' + cat << 'GH_AW_PROMPT_a29e2f4de291b4b6_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -286,17 +282,17 @@ jobs: {{/if}} - GH_AW_PROMPT_089bad1a80abb9ff_EOF + GH_AW_PROMPT_a29e2f4de291b4b6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_089bad1a80abb9ff_EOF' + cat << 'GH_AW_PROMPT_a29e2f4de291b4b6_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/plan.md}} - GH_AW_PROMPT_089bad1a80abb9ff_EOF + GH_AW_PROMPT_a29e2f4de291b4b6_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -516,9 +512,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b1879aea4a628246_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_69791fdf79cc167e_EOF' {"close_discussion":{"max":1},"create_issue":{"expires":48,"group":true,"labels":["plan","ai-generated","cookie"],"max":5,"title_prefix":"[plan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_b1879aea4a628246_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_69791fdf79cc167e_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -748,7 +744,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_9c51c2aca32856ed_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_02799307106636b8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -779,7 +775,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_9c51c2aca32856ed_EOF + GH_AW_MCP_CONFIG_02799307106636b8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1409,7 +1405,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/plan ') || startsWith(github.event.comment.body, '/plan\n') || github.event.comment.body == '/plan') && github.event.issue.pull_request == null || github.event_name == 'discussion_comment' && (startsWith(github.event.comment.body, '/plan ') || startsWith(github.event.comment.body, '/plan\n') || github.event.comment.body == '/plan'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/plan.md b/.github/workflows/plan.md index c7c4cac3c55..58690261c9c 100644 --- a/.github/workflows/plan.md +++ b/.github/workflows/plan.md @@ -3,6 +3,7 @@ name: Plan Command description: Generates project plans and task breakdowns when invoked with /plan command in issues or PRs on: slash_command: + strategy: centralized name: plan events: [issue_comment, discussion_comment] permissions: diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index aa138bf8cc8..d598942bd76 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e2c4f0dfc67f4f51cbee99091ba080bca77cf9164e98dc76ee1fc520c74fda2a","strict":true,"agent_id":"copilot","agent_model":"gpt-5"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5c5c0e5e9d5f891afe434dcb9605ecb1177c6c0626347193395a9f8eb0450c4a","strict":true,"agent_id":"copilot","agent_model":"gpt-5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -59,11 +59,6 @@ name: "Poem Bot - A Creative Agentic Workflow" "on": - issues: - types: - - opened - - edited - - reopened # roles: # Roles processed as role check in pre-activation job # - admin # Roles processed as role check in pre-activation job # - maintainer # Roles processed as role check in pre-activation job @@ -96,12 +91,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/poem-bot ') || startsWith(github.event.issue.body, '/poem-bot\n') || github.event.issue.body == '/poem-bot') || !(github.event_name == 'issues'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -260,25 +254,25 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_90ec2e1aca70d3cc_EOF' + cat << 'GH_AW_PROMPT_db0850897dc8fed9_EOF' - GH_AW_PROMPT_90ec2e1aca70d3cc_EOF + GH_AW_PROMPT_db0850897dc8fed9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_90ec2e1aca70d3cc_EOF' + cat << 'GH_AW_PROMPT_db0850897dc8fed9_EOF' Tools: add_comment(max:3), create_issue(max:2), update_issue(max:2), create_discussion(max:2), create_agent_session, create_pull_request, close_pull_request(max:2), create_pull_request_review_comment(max:2), add_labels(max:5), push_to_pull_request_branch, link_sub_issue(max:3), missing_tool, missing_data, noop - GH_AW_PROMPT_90ec2e1aca70d3cc_EOF + GH_AW_PROMPT_db0850897dc8fed9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_90ec2e1aca70d3cc_EOF' + cat << 'GH_AW_PROMPT_db0850897dc8fed9_EOF' - GH_AW_PROMPT_90ec2e1aca70d3cc_EOF + GH_AW_PROMPT_db0850897dc8fed9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_90ec2e1aca70d3cc_EOF' + cat << 'GH_AW_PROMPT_db0850897dc8fed9_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -307,7 +301,7 @@ jobs: {{/if}} - GH_AW_PROMPT_90ec2e1aca70d3cc_EOF + GH_AW_PROMPT_db0850897dc8fed9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -315,13 +309,13 @@ jobs: if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_90ec2e1aca70d3cc_EOF' + cat << 'GH_AW_PROMPT_db0850897dc8fed9_EOF' {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/poem-bot.md}} - GH_AW_PROMPT_90ec2e1aca70d3cc_EOF + GH_AW_PROMPT_db0850897dc8fed9_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -567,9 +561,9 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs/upload-artifacts" - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f834e6394511397e_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_cb3537d68b1f412c_EOF' {"add_comment":{"max":3,"target":"*"},"add_labels":{"allowed":["poetry","creative","automation","ai-generated","epic","haiku","sonnet","limerick"],"max":5},"close_pull_request":{"max":2,"required_labels":["poetry","automation"],"required_title_prefix":"[🎨 POETRY]","target":"*"},"create_agent_session":{"base":"main","max":1},"create_discussion":{"category":"audits","close_older_discussions":true,"expires":24,"fallback_to_issue":true,"labels":["poetry","automation","ai-generated"],"max":2,"title_prefix":"[📜 POETRY] "},"create_issue":{"expires":48,"group":true,"labels":["poetry","automation","ai-generated"],"max":2,"title_prefix":"[🎭 POEM-BOT] "},"create_pull_request":{"draft":false,"expires":48,"labels":["poetry","automation","creative-writing"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"reviewers":["copilot"],"title_prefix":"[🎨 POETRY] "},"create_pull_request_review_comment":{"max":2,"side":"RIGHT"},"create_report_incomplete_issue":{},"link_sub_issue":{"max":3,"parent_required_labels":["poetry","epic"],"parent_title_prefix":"[🎭 POEM-BOT]","sub_required_labels":["poetry"],"sub_title_prefix":"[🎭 POEM-BOT]"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"report_incomplete":{},"update_issue":{"allow_body":true,"allow_status":true,"allow_title":true,"max":2,"target":"*"},"upload_artifact":{"max-size-bytes":104857600,"max-uploads":1,"retention-days":30}} - GH_AW_SAFE_OUTPUTS_CONFIG_f834e6394511397e_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_cb3537d68b1f412c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1050,7 +1044,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_549632f40b32a334_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_90ae4e82d77594f5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -1081,7 +1075,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_549632f40b32a334_EOF + GH_AW_MCP_CONFIG_90ae4e82d77594f5_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1764,7 +1758,6 @@ jobs: } pre_activation: - if: "github.event_name == 'issues' && (startsWith(github.event.issue.body, '/poem-bot ') || startsWith(github.event.issue.body, '/poem-bot\n') || github.event.issue.body == '/poem-bot') || !(github.event_name == 'issues')" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/poem-bot.md b/.github/workflows/poem-bot.md index cdcf0c050c4..3d3ad5b203d 100644 --- a/.github/workflows/poem-bot.md +++ b/.github/workflows/poem-bot.md @@ -7,6 +7,7 @@ on: - maintainer # Command trigger - responds to /poem-bot mentions slash_command: + strategy: centralized name: poem-bot events: [issues] diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index ce6bdfb0792..3fb2598ea48 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c18ea01b6d34f3dc5d81d6a5c1cadcdad6534225f38da1392a0bf208fd613912","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d6729fad4635b3651867bf7ce32aaabcc9882db24b35a7caea43ceb9013ea041","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,17 +57,16 @@ name: "PR Code Quality Reviewer" "on": - issue_comment: - types: - - created - - edited pull_request: types: - ready_for_review - pull_request_review_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -86,7 +85,8 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review')) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'pull_request_review_comment'))) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id))" + if: > + needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) runs-on: ubuntu-slim permissions: actions: read @@ -250,20 +250,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_1d5c9b3277c6f72e_EOF' + cat << 'GH_AW_PROMPT_8c0279fb49dd3fb9_EOF' - GH_AW_PROMPT_1d5c9b3277c6f72e_EOF + GH_AW_PROMPT_8c0279fb49dd3fb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_1d5c9b3277c6f72e_EOF' + cat << 'GH_AW_PROMPT_8c0279fb49dd3fb9_EOF' Tools: create_pull_request_review_comment(max:10), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_1d5c9b3277c6f72e_EOF + GH_AW_PROMPT_8c0279fb49dd3fb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_1d5c9b3277c6f72e_EOF' + cat << 'GH_AW_PROMPT_8c0279fb49dd3fb9_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -292,12 +292,12 @@ jobs: {{/if}} - GH_AW_PROMPT_1d5c9b3277c6f72e_EOF + GH_AW_PROMPT_8c0279fb49dd3fb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_1d5c9b3277c6f72e_EOF' + cat << 'GH_AW_PROMPT_8c0279fb49dd3fb9_EOF' {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} @@ -305,7 +305,7 @@ jobs: {{#runtime-import .github/workflows/shared/pr-code-review-config.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/pr-code-quality-reviewer.md}} - GH_AW_PROMPT_1d5c9b3277c6f72e_EOF + GH_AW_PROMPT_8c0279fb49dd3fb9_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -523,9 +523,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_84a7c5b2ab212904_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_863d1eaee434fe82_EOF' {"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_84a7c5b2ab212904_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_863d1eaee434fe82_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -748,7 +748,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_1a0c4c254705b459_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_004a822449ebe2f9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -798,7 +798,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_1a0c4c254705b459_EOF + GH_AW_MCP_CONFIG_004a822449ebe2f9_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1416,7 +1416,7 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review')) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'pull_request_review_comment'))) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id))" + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pr-code-quality-reviewer.md b/.github/workflows/pr-code-quality-reviewer.md index ced7831c276..4586c621714 100644 --- a/.github/workflows/pr-code-quality-reviewer.md +++ b/.github/workflows/pr-code-quality-reviewer.md @@ -5,6 +5,7 @@ on: pull_request: types: [ready_for_review] slash_command: + strategy: centralized name: review events: [pull_request_comment, pull_request_review_comment] engine: copilot diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 977fe3b894f..ec5949c9ec6 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c94236800f17adfe450e3390fea4bebe59cb000bc39f80d372f198e18aabca91","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a7457716308b1ae1d9ed4aa5f413e5e93f182f1cff82973fa121caac63da12d9","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,14 +57,13 @@ name: "PR Nitpick Reviewer 🔍" "on": - issue_comment: - types: - - created - - edited - pull_request_review_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -83,13 +82,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/nit ') || startsWith(github.event.comment.body, '/nit\n') || github.event.comment.body == '/nit') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/nit ') || startsWith(github.event.comment.body, '/nit\n') || github.event.comment.body == '/nit'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -247,20 +244,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_1db9accd26153e71_EOF' + cat << 'GH_AW_PROMPT_1d2e5717dce668a8_EOF' - GH_AW_PROMPT_1db9accd26153e71_EOF + GH_AW_PROMPT_1d2e5717dce668a8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_1db9accd26153e71_EOF' + cat << 'GH_AW_PROMPT_1d2e5717dce668a8_EOF' Tools: create_discussion, create_pull_request_review_comment(max:10), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_1db9accd26153e71_EOF + GH_AW_PROMPT_1d2e5717dce668a8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_1db9accd26153e71_EOF' + cat << 'GH_AW_PROMPT_1d2e5717dce668a8_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -289,12 +286,12 @@ jobs: {{/if}} - GH_AW_PROMPT_1db9accd26153e71_EOF + GH_AW_PROMPT_1d2e5717dce668a8_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_1db9accd26153e71_EOF' + cat << 'GH_AW_PROMPT_1d2e5717dce668a8_EOF' {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} @@ -302,7 +299,7 @@ jobs: {{#runtime-import .github/workflows/shared/pr-code-review-config.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/pr-nitpick-reviewer.md}} - GH_AW_PROMPT_1db9accd26153e71_EOF + GH_AW_PROMPT_1d2e5717dce668a8_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -522,9 +519,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_790258275bdd5d64_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c8c5b7a57e1a5776_EOF' {"create_discussion":{"category":"audits","expires":24,"fallback_to_issue":true,"max":1,"title_prefix":"[nitpick-report] "},"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_790258275bdd5d64_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_c8c5b7a57e1a5776_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -774,7 +771,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_6906613e777e3245_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_a5bcb2b00d471f53_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -824,7 +821,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_6906613e777e3245_EOF + GH_AW_MCP_CONFIG_a5bcb2b00d471f53_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1446,7 +1443,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/nit ') || startsWith(github.event.comment.body, '/nit\n') || github.event.comment.body == '/nit') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/nit ') || startsWith(github.event.comment.body, '/nit\n') || github.event.comment.body == '/nit'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pr-nitpick-reviewer.md b/.github/workflows/pr-nitpick-reviewer.md index 2fb5a148b06..c7fbbb13f4f 100644 --- a/.github/workflows/pr-nitpick-reviewer.md +++ b/.github/workflows/pr-nitpick-reviewer.md @@ -2,6 +2,7 @@ description: "⚠️ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Provides detailed nitpicky code review focusing on style, best practices, and minor improvements" on: slash_command: + strategy: centralized name: nit events: [pull_request_comment, pull_request_review_comment] permissions: diff --git a/.github/workflows/security-review.lock.yml b/.github/workflows/security-review.lock.yml index 613865b70c1..ef0fa89c98e 100644 --- a/.github/workflows/security-review.lock.yml +++ b/.github/workflows/security-review.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b5ee0a94cfa9054fa0ddfe28c5087c23b35e2bdf1cfbdabb2f5a62c1487826cd","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6f257aa3430a6a28d7d7fbd318c28617caebfc871ad1774cb7bc2347beae9f25","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -59,14 +59,13 @@ name: "Security Review Agent 🔒" "on": - issue_comment: - types: - - created - - edited - pull_request_review_comment: - types: - - created - - edited + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} @@ -85,13 +84,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/security-review ') || startsWith(github.event.comment.body, '/security-review\n') || github.event.comment.body == '/security-review') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/security-review ') || startsWith(github.event.comment.body, '/security-review\n') || github.event.comment.body == '/security-review'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -243,21 +240,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b8421cf2a0582e37_EOF' + cat << 'GH_AW_PROMPT_5307939dd77dc999_EOF' - GH_AW_PROMPT_b8421cf2a0582e37_EOF + GH_AW_PROMPT_5307939dd77dc999_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/agentic_workflows_guide.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_b8421cf2a0582e37_EOF' + cat << 'GH_AW_PROMPT_5307939dd77dc999_EOF' Tools: create_pull_request_review_comment(max:10), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_b8421cf2a0582e37_EOF + GH_AW_PROMPT_5307939dd77dc999_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b8421cf2a0582e37_EOF' + cat << 'GH_AW_PROMPT_5307939dd77dc999_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -286,12 +283,12 @@ jobs: {{/if}} - GH_AW_PROMPT_b8421cf2a0582e37_EOF + GH_AW_PROMPT_5307939dd77dc999_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_b8421cf2a0582e37_EOF' + cat << 'GH_AW_PROMPT_5307939dd77dc999_EOF' {{#runtime-import .github/workflows/shared/security-analysis-base.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} @@ -299,7 +296,7 @@ jobs: {{#runtime-import .github/workflows/shared/pr-code-review-config.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/security-review.md}} - GH_AW_PROMPT_b8421cf2a0582e37_EOF + GH_AW_PROMPT_5307939dd77dc999_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -580,9 +577,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_87dac5dbc469a09e_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a74df72d8c31161d_EOF' {"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_87dac5dbc469a09e_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_a74df72d8c31161d_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -806,7 +803,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_091397d75440f84c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_60c8d02b44c7c117_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "agenticworkflows": { @@ -875,7 +872,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_091397d75440f84c_EOF + GH_AW_MCP_CONFIG_60c8d02b44c7c117_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1494,7 +1491,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/security-review ') || startsWith(github.event.comment.body, '/security-review\n') || github.event.comment.body == '/security-review') && github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' && (startsWith(github.event.comment.body, '/security-review ') || startsWith(github.event.comment.body, '/security-review\n') || github.event.comment.body == '/security-review'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/security-review.md b/.github/workflows/security-review.md index c44a326b5c7..dc2ea8f9dff 100644 --- a/.github/workflows/security-review.md +++ b/.github/workflows/security-review.md @@ -2,6 +2,7 @@ description: Security-focused AI agent that reviews pull requests to identify changes that could weaken security posture or extend AWF boundaries on: slash_command: + strategy: centralized name: security-review events: [pull_request_comment, pull_request_review_comment] permissions: diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 6b66e01a990..b99222f5b24 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4c054ba08af1ba87d0693ce56b1ad4142894dc7be2e7e05c6c5b766fe3a7010b","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d4bde4bf73cadef7d80425b70c0159ea0a4fdfa9571bcfc7d58aed027d25fbed","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -57,10 +57,6 @@ name: "Tidy" "on": - issue_comment: - types: - - created - - edited push: branches: - main @@ -97,13 +93,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/tidy ') || startsWith(github.event.comment.body, '/tidy\n') || github.event.comment.body == '/tidy') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -259,24 +253,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5d1b810a27c4eece_EOF' + cat << 'GH_AW_PROMPT_cb6a551224b6ba18_EOF' - GH_AW_PROMPT_5d1b810a27c4eece_EOF + GH_AW_PROMPT_cb6a551224b6ba18_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_5d1b810a27c4eece_EOF' + cat << 'GH_AW_PROMPT_cb6a551224b6ba18_EOF' Tools: create_pull_request, push_to_pull_request_branch, missing_tool, missing_data, noop - GH_AW_PROMPT_5d1b810a27c4eece_EOF + GH_AW_PROMPT_cb6a551224b6ba18_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_5d1b810a27c4eece_EOF' + cat << 'GH_AW_PROMPT_cb6a551224b6ba18_EOF' - GH_AW_PROMPT_5d1b810a27c4eece_EOF + GH_AW_PROMPT_cb6a551224b6ba18_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5d1b810a27c4eece_EOF' + cat << 'GH_AW_PROMPT_cb6a551224b6ba18_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -305,7 +299,7 @@ jobs: {{/if}} - GH_AW_PROMPT_5d1b810a27c4eece_EOF + GH_AW_PROMPT_cb6a551224b6ba18_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -313,12 +307,12 @@ jobs: if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_5d1b810a27c4eece_EOF' + cat << 'GH_AW_PROMPT_cb6a551224b6ba18_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/tidy.md}} - GH_AW_PROMPT_5d1b810a27c4eece_EOF + GH_AW_PROMPT_cb6a551224b6ba18_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -548,9 +542,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0738cd5e16c6f3be_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7b07b85aad1ec349_EOF' {"create_pull_request":{"draft":false,"expires":48,"labels":["automation","maintenance"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"reviewers":["copilot"],"title_prefix":"[tidy] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_0738cd5e16c6f3be_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_7b07b85aad1ec349_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -777,7 +771,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_2878cc75d1ddd82c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_1c7b45374fab1d91_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -808,7 +802,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_2878cc75d1ddd82c_EOF + GH_AW_MCP_CONFIG_1c7b45374fab1d91_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1467,7 +1461,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/tidy ') || startsWith(github.event.comment.body, '/tidy\n') || github.event.comment.body == '/tidy') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/tidy.md b/.github/workflows/tidy.md index 706c11a567a..15e6ea3ee89 100644 --- a/.github/workflows/tidy.md +++ b/.github/workflows/tidy.md @@ -6,6 +6,7 @@ on: - cron: 'daily around 7:00' # ~7 AM UTC workflow_dispatch: slash_command: + strategy: centralized events: [pull_request_comment] reaction: "eyes" push: diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 356c5231eeb..4271898504f 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8f769714cba8c96ed4cbfdcabe455ae4f0d00cf691d101bee1e96d0d1773fe13","strict":true,"agent_id":"claude"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d172fd355f4dc8102cd3720b53f949d95b0f8c13fa915f797e913a99d6f229b5","strict":true,"agent_id":"claude"} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.43"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.43"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -62,10 +62,6 @@ name: "Documentation Unbloat" "on": - issue_comment: - types: - - created - - edited schedule: - cron: "24 16 * * *" # skip-if-match: is:pr is:open is:draft label:doc-unbloat # Skip-if-match processed as search check in pre-activation job @@ -94,13 +90,11 @@ env: jobs: activation: needs: pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/unbloat ') || startsWith(github.event.comment.body, '/unbloat\n') || github.event.comment.body == '/unbloat') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read - issues: write - pull-requests: write outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -256,27 +250,27 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_38b6a43feeb686cf_EOF' + cat << 'GH_AW_PROMPT_bae37a7354e1d9f4_EOF' - GH_AW_PROMPT_38b6a43feeb686cf_EOF + GH_AW_PROMPT_bae37a7354e1d9f4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/playwright_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_38b6a43feeb686cf_EOF' + cat << 'GH_AW_PROMPT_bae37a7354e1d9f4_EOF' Tools: add_comment, create_pull_request, upload_asset(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_38b6a43feeb686cf_EOF + GH_AW_PROMPT_bae37a7354e1d9f4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_38b6a43feeb686cf_EOF' + cat << 'GH_AW_PROMPT_bae37a7354e1d9f4_EOF' upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs). - GH_AW_PROMPT_38b6a43feeb686cf_EOF + GH_AW_PROMPT_bae37a7354e1d9f4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_38b6a43feeb686cf_EOF' + cat << 'GH_AW_PROMPT_bae37a7354e1d9f4_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -305,9 +299,9 @@ jobs: {{/if}} - GH_AW_PROMPT_38b6a43feeb686cf_EOF + GH_AW_PROMPT_bae37a7354e1d9f4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_38b6a43feeb686cf_EOF' + cat << 'GH_AW_PROMPT_bae37a7354e1d9f4_EOF' {{#runtime-import .github/workflows/shared/docs-server-lifecycle.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} @@ -315,7 +309,7 @@ jobs: {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/unbloat-docs.md}} - GH_AW_PROMPT_38b6a43feeb686cf_EOF + GH_AW_PROMPT_bae37a7354e1d9f4_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -410,6 +404,8 @@ jobs: contents: read issues: read pull-requests: read + concurrency: + group: "gh-aw-claude-${{ github.workflow }}" env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg,.svg" @@ -582,9 +578,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_7b2970f6409813b6_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_bc445d938315710e_EOF {"add_comment":{"max":1},"create_pull_request":{"auto_merge":true,"draft":true,"expires":48,"fallback_as_issue":false,"labels":["documentation","automation","doc-unbloat"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","CLAUDE.md","AGENTS.md"],"reviewers":["copilot"],"title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg",".svg"],"branch":"assets/${GITHUB_WORKFLOW}","max":10,"max-size":10240}} - GH_AW_SAFE_OUTPUTS_CONFIG_7b2970f6409813b6_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_bc445d938315710e_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -826,7 +822,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_9c701f5cb989dfdf_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_a996d062ac58fa2a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -857,7 +853,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_9c701f5cb989dfdf_EOF + GH_AW_MCP_CONFIG_a996d062ac58fa2a_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1650,7 +1646,6 @@ jobs: } pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/unbloat ') || startsWith(github.event.comment.body, '/unbloat\n') || github.event.comment.body == '/unbloat') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/unbloat-docs.md b/.github/workflows/unbloat-docs.md index 34d32063986..989bd02f7bc 100644 --- a/.github/workflows/unbloat-docs.md +++ b/.github/workflows/unbloat-docs.md @@ -7,6 +7,7 @@ on: # Command trigger for /unbloat in PR comments slash_command: + strategy: centralized name: unbloat events: [pull_request_comment] diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 3f1b31beb15..30ca4107d4f 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -25,6 +25,14 @@ type slashCommandRoute struct { Events []string `json:"events"` } +type commandsHeaderMetadata struct { + PayloadVersion string `json:"payload_version"` + SchemaVersion string `json:"schema_version"` + Compiler string `json:"compiler_version"` + Commands []string `json:"commands"` + Workflows []string `json:"workflows"` +} + // GenerateCentralSlashCommandWorkflow generates a single centralized slash-command trigger // workflow for workflows that opt into on.slash_command.strategy: centralized. // When no centralized slash-command workflows are found, any existing generated file is deleted. @@ -131,10 +139,7 @@ func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashComm return "", fmt.Errorf("failed to marshal centralized slash-command routes: %w", err) } - commandsMetadata, err := json.Marshal(map[string]string{ - "schema_version": "v1", - "compiler_version": GetVersion(), - }) + commandsMetadata, err := json.Marshal(buildCommandsHeaderMetadata(routesByCommand)) if err != nil { return "", fmt.Errorf("failed to marshal centralized slash-command metadata: %w", err) } @@ -178,6 +183,32 @@ jobs: return b.String(), nil } +func buildCommandsHeaderMetadata(routesByCommand map[string][]slashCommandRoute) commandsHeaderMetadata { + commands := make([]string, 0, len(routesByCommand)) + workflowSet := make(map[string]bool) + for command, routes := range routesByCommand { + commands = append(commands, command) + for _, route := range routes { + if route.Workflow != "" { + workflowSet[route.Workflow] = true + } + } + } + sort.Strings(commands) + workflows := make([]string, 0, len(workflowSet)) + for workflowID := range workflowSet { + workflows = append(workflows, workflowID) + } + sort.Strings(workflows) + return commandsHeaderMetadata{ + PayloadVersion: "v1", + SchemaVersion: "v1", + Compiler: GetVersion(), + Commands: commands, + Workflows: workflows, + } +} + func resolveCentralSlashRunsOn(workflowDataList []*WorkflowData) string { counts := map[string]int{} for _, wd := range workflowDataList { diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index c2d9558aa64..65e3ed989d4 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -47,10 +47,13 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.NotEmpty(t, lines) require.Contains(t, lines[0], "# gh-aw-commands: ") metadataJSON := strings.TrimPrefix(lines[0], "# gh-aw-commands: ") - var metadata map[string]string + var metadata commandsHeaderMetadata require.NoError(t, json.Unmarshal([]byte(metadataJSON), &metadata)) - require.Equal(t, "v1", metadata["schema_version"]) - require.NotEmpty(t, metadata["compiler_version"]) + require.Equal(t, "v1", metadata.PayloadVersion) + require.Equal(t, "v1", metadata.SchemaVersion) + require.NotEmpty(t, metadata.Compiler) + require.Equal(t, []string{"cloclo", "triage"}, metadata.Commands) + require.Equal(t, []string{"cloclo", "triage-issue", "triage-pr"}, metadata.Workflows) require.Contains(t, text, "name: \"Agentic Commands\"") require.NotContains(t, text, "Compiler version:") From b6bd645e43a3191a2b8d2731286330d7bdaabbc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 18:19:01 +0000 Subject: [PATCH 39/40] Plan: address dev-mode runtime setup for agentic commands router Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 52a7ba0e9bb..df2ddb5fa57 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,4 +1,4 @@ -# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"60ad42e","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","mergefest","nit","plan","poem-bot","review","security-review","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","cloclo","craft","grumpy-reviewer","mergefest","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","security-review","tidy","unbloat-docs"]} +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"31a6d32","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","mergefest","nit","plan","poem-bot","review","security-review","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","cloclo","craft","grumpy-reviewer","mergefest","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","security-review","tidy","unbloat-docs"]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ From f1eb19f93998b49f05c82393b1b0dab5ca38991e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 18:23:04 +0000 Subject: [PATCH 40/40] Add setup runtime step to centralized commands workflow in dev mode Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 11 ++++++++--- pkg/workflow/central_slash_command_workflow.go | 16 ++++++++++++---- .../central_slash_command_workflow_test.go | 8 ++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index df2ddb5fa57..e3f3845224e 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,4 +1,4 @@ -# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"31a6d32","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","mergefest","nit","plan","poem-bot","review","security-review","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","cloclo","craft","grumpy-reviewer","mergefest","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","security-review","tidy","unbloat-docs"]} +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"b6bd645","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","mergefest","nit","plan","poem-bot","review","security-review","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","cloclo","craft","grumpy-reviewer","mergefest","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","security-review","tidy","unbloat-docs"]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -49,13 +49,18 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Scripts + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Route slash command uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SLASH_ROUTING: '{"ace":[{"workflow":"ace-editor","events":["pull_request_comment"]}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"]}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"]}],"brave":[{"workflow":"brave","events":["issue_comment"]}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"]}],"craft":[{"workflow":"craft","events":["issues"]}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"]}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"]}],"poem-bot":[{"workflow":"poem-bot","events":["issues"]}],"review":[{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"]}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"]}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"]}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"]}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"]}]}' with: script: | - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); + const { main } = require('${{ runner.temp }}/gh-aw/actions/route_slash_command.cjs'); await main(); diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 30ca4107d4f..1cfd4c4e0c7 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -53,7 +53,10 @@ func GenerateCentralSlashCommandWorkflow(workflowDataList []*WorkflowData, workf return nil } - content, err := buildCentralSlashCommandWorkflowYAML(routesByCommand, mergedEvents, resolveCentralSlashRunsOn(workflowDataList)) + actionMode := DetectActionMode(GetVersion()) + setupActionRef := ResolveSetupActionReference(actionMode, GetVersion(), "", nil) + + content, err := buildCentralSlashCommandWorkflowYAML(routesByCommand, mergedEvents, resolveCentralSlashRunsOn(workflowDataList), setupActionRef) if err != nil { return err } @@ -133,7 +136,7 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str return routesByCommand, mergedEvents } -func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashCommandRoute, mergedEvents map[string]map[string]bool, runsOn string) (string, error) { +func buildCentralSlashCommandWorkflowYAML(routesByCommand map[string][]slashCommandRoute, mergedEvents map[string]map[string]bool, runsOn string, setupActionRef string) (string, error) { routesJSON, err := json.Marshal(routesByCommand) if err != nil { return "", fmt.Errorf("failed to marshal centralized slash-command routes: %w", err) @@ -169,15 +172,20 @@ jobs: - name: Checkout repository uses: ` + getActionPin("actions/checkout") + ` + - name: Setup Scripts + uses: ` + setupActionRef + ` + with: + destination: ` + SetupActionDestination + ` + - name: Route slash command uses: ` + getActionPin("actions/github-script") + ` env: GH_AW_SLASH_ROUTING: '` + escapeSingleQuotedYAMLString(string(routesJSON)) + `' with: script: | - const { setupGlobals } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs"); + const { setupGlobals } = require('` + SetupActionDestination + `/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs"); + const { main } = require('` + SetupActionDestination + `/route_slash_command.cjs'); await main(); `) return b.String(), nil diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index 65e3ed989d4..f45bdee2693 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -15,6 +15,7 @@ import ( func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { tmpDir := testutil.TempDir(t, "central-slash-workflow-test") + t.Setenv("GH_AW_ACTION_MODE", "dev") data := []*WorkflowData{ { @@ -60,15 +61,18 @@ func TestGenerateCentralSlashCommandWorkflow_GeneratesWorkflow(t *testing.T) { require.Contains(t, text, "permissions: {}") require.Contains(t, text, "runs-on: ubuntu-slim") require.Contains(t, text, " permissions:\n actions: write\n contents: read") + require.Contains(t, text, " - name: Setup Scripts") + require.Contains(t, text, " uses: ./actions/setup") + require.Contains(t, text, " destination: ${{ runner.temp }}/gh-aw/actions") require.Contains(t, text, "issues:") require.Contains(t, text, "issue_comment:") require.Contains(t, text, "pull_request:") require.Contains(t, text, "discussion_comment:") require.Contains(t, text, `"triage":[{"workflow":"triage-issue","events":["issue_comment","issues"]},{"workflow":"triage-pr","events":["pull_request","pull_request_comment"]}]`) require.Contains(t, text, `"cloclo":[{"workflow":"cloclo","events":["discussion_comment"]}]`) - require.Contains(t, text, `require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/setup_globals.cjs")`) + require.Contains(t, text, `require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs')`) require.Contains(t, text, `setupGlobals(core, github, context, exec, io, getOctokit);`) - require.Contains(t, text, `require(process.env.GITHUB_WORKSPACE + "/actions/setup/js/route_slash_command.cjs")`) + require.Contains(t, text, `require('${{ runner.temp }}/gh-aw/actions/route_slash_command.cjs')`) require.NotContains(t, text, `const routeMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}");`) require.NotContains(t, text, `trustedAuthorAssociations`) require.NotContains(t, text, `isForkBasedPullRequestEvent`)