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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions pkg/cli/compile_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
package cli

import (
"cmp"
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"slices"
"strings"

"github.com/github/gh-aw/pkg/gitutil"
Expand Down Expand Up @@ -684,11 +685,11 @@ func displayBatchCompilationNotices(compiler *workflow.Compiler, config CompileC
count: count,
})
}
sort.Slice(features, func(i, j int) bool {
if features[i].count != features[j].count {
return features[i].count > features[j].count
slices.SortFunc(features, func(a, b featureCount) int {
if a.count != b.count {
return cmp.Compare(b.count, a.count)
}
return features[i].name < features[j].name
return cmp.Compare(a.name, b.name)
})

fmt.Fprintln(os.Stderr, console.FormatWarningMessageStderr("Experimental features in use:"))
Expand Down
17 changes: 9 additions & 8 deletions pkg/cli/mcp_tools_readonly.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,24 +533,25 @@ func extractShellcheckDiagnostics(stderrOutput string) []string {

lines := strings.Split(stderrOutput, "\n")
diagnostics := make([]string, 0)
current := ""
var current strings.Builder

flush := func() {
if strings.TrimSpace(current) != "" {
diagnostics = append(diagnostics, strings.TrimSpace(current))
if text := strings.TrimSpace(current.String()); text != "" {
diagnostics = append(diagnostics, text)
}
current = ""
current.Reset()
}

for _, line := range lines {
trimmed := strings.TrimSpace(line)
switch {
case strings.Contains(trimmed, "shellcheck findings in "):
flush()
current = trimmed
case current != "" && (strings.Contains(trimmed, "script:") || strings.HasPrefix(trimmed, "script ")):
current += "\n" + trimmed
case current != "" && trimmed == "":
current.WriteString(trimmed)
case current.Len() > 0 && (strings.Contains(trimmed, "script:") || strings.HasPrefix(trimmed, "script ")):
current.WriteString("\n")
current.WriteString(trimmed)
case current.Len() > 0 && trimmed == "":
flush()
}
}
Expand Down
16 changes: 8 additions & 8 deletions pkg/cli/runner_guard_activation_gate.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type runnerGuardWorkflow struct {
// dependencies, has an if: condition referencing author_association. Workflows without such
// a gate keep their findings.
func filterRunnerGuardFindings(findings []runnerGuardFinding, gitRoot string) []runnerGuardFinding {
gatedJobsByFile := make(map[string]map[string]bool)
gatedJobsByFile := make(map[string]map[string]struct{})
filtered := make([]runnerGuardFinding, 0, len(findings))

for _, finding := range findings {
Expand All @@ -53,7 +53,7 @@ func filterRunnerGuardFindings(findings []runnerGuardFinding, gitRoot string) []
gatedJobsByFile[finding.File] = gatedJobs
}

if gatedJobs[finding.JobID] {
if _, isGated := gatedJobs[finding.JobID]; isGated {
runnerGuardLog.Printf("Suppressing %s finding for gated job %q in %s", finding.RuleID, finding.JobID, finding.File)
continue
}
Expand Down Expand Up @@ -107,8 +107,8 @@ func resolveRunnerGuardFilePath(gitRoot string, file string) string {
// protected by an author_association check, either directly on the job's if: condition or
// transitively through the needs: graph. An empty set is returned when the workflow cannot
// be read or parsed, so that findings are preserved rather than silently dropped.
func authorAssociationGatedJobs(path string) map[string]bool {
gated := make(map[string]bool)
func authorAssociationGatedJobs(path string) map[string]struct{} {
gated := make(map[string]struct{})
if path == "" {
return gated
}
Expand All @@ -132,11 +132,11 @@ func authorAssociationGatedJobs(path string) map[string]bool {
for range len(workflow.Jobs) {
changed := false
for jobID, job := range workflow.Jobs {
if gated[jobID] {
if _, isGated := gated[jobID]; isGated {
continue
}
if hasAuthorAssociationCheck(job.If) || anyJobGated(gated, jobNeeds(job.Needs)) {
gated[jobID] = true
gated[jobID] = struct{}{}
changed = true
}
}
Expand All @@ -154,9 +154,9 @@ func hasAuthorAssociationCheck(condition string) bool {
}

// anyJobGated reports whether any of the named jobs is in the gated set.
func anyJobGated(gated map[string]bool, needs []string) bool {
func anyJobGated(gated map[string]struct{}, needs []string) bool {
for _, need := range needs {
if gated[need] {
if _, isGated := gated[need]; isGated {
return true
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/runner_guard_activation_gate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ func TestAuthorAssociationGatedJobs(t *testing.T) {

gated := authorAssociationGatedJobs(filepath.Join(gitRoot, ".github", "workflows", "gated.lock.yml"))

assert.True(t, gated["pre_activation"])
assert.True(t, gated["activation"])
assert.True(t, gated["agent"])
assert.True(t, gated["conclusion"])
assert.Contains(t, gated, "pre_activation")
assert.Contains(t, gated, "activation")
assert.Contains(t, gated, "agent")
assert.Contains(t, gated, "conclusion")
})

t.Run("workflow without a gate has no gated jobs", func(t *testing.T) {
Expand Down
11 changes: 7 additions & 4 deletions pkg/linters/globwalkignorederror/globwalkignorederror.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ var Analyzer = analyzerutil.New("globwalkignorederror", "reports filepath.Glob a

// checkedFuncs maps package import path to the set of function names within
// that package whose discarded error return should be flagged.
var checkedFuncs = map[string]map[string]bool{
"path/filepath": {"Glob": true},
"os": {"ReadDir": true},
var checkedFuncs = map[string]map[string]struct{}{
"path/filepath": {"Glob": {}},
"os": {"ReadDir": {}},
}

func run(pass *analysis.Pass) (any, error) {
Expand Down Expand Up @@ -72,7 +72,10 @@ func analyzeGlobWalkAssign(pass *analysis.Pass, n ast.Node, generatedFiles filec
return
}
funcs, ok := checkedFuncs[pkgName.Imported().Path()]
if !ok || !funcs[sel.Sel.Name] {
if !ok {
return
}
if _, checked := funcs[sel.Sel.Name]; !checked {
return
}
position := pass.Fset.PositionFor(call.Pos(), false)
Expand Down
24 changes: 12 additions & 12 deletions pkg/workflow/central_slash_command_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func centralRoutingCommandNames(wd *WorkflowData) []string {
return nil
}

func collectCentralCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string][]slashCommandRoute, map[string]map[string]bool) {
func collectCentralCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string][]slashCommandRoute, map[string]map[string]struct{}) {
slashRoutesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList)
labelRoutesByCommand := collectCentralLabelCommandRoutes(workflowDataList, mergedEvents)
return slashRoutesByCommand, labelRoutesByCommand, mergedEvents
Expand All @@ -128,9 +128,9 @@ func removeIfExists(path string) error {
return nil
}

func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]bool) {
func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]struct{}) {
routesByCommand := make(map[string][]slashCommandRoute)
mergedEvents := make(map[string]map[string]bool)
mergedEvents := make(map[string]map[string]struct{})

for _, wd := range workflowDataList {
commandNames := centralRoutingCommandNames(wd)
Expand All @@ -153,10 +153,10 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str
// 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)
mergedEvents[event.EventName] = make(map[string]struct{})
}
for _, t := range event.Types {
mergedEvents[event.EventName][t] = true
mergedEvents[event.EventName][t] = struct{}{}
}
}

Expand Down Expand Up @@ -200,7 +200,7 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str
return routesByCommand, mergedEvents
}

func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEvents map[string]map[string]bool) map[string][]slashCommandRoute {
func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEvents map[string]map[string]struct{}) map[string][]slashCommandRoute {
routesByLabel := make(map[string][]slashCommandRoute)

for _, wd := range workflowDataList {
Expand All @@ -223,9 +223,9 @@ func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEv

for _, eventName := range routeEvents {
if mergedEvents[eventName] == nil {
mergedEvents[eventName] = make(map[string]bool)
mergedEvents[eventName] = make(map[string]struct{})
}
mergedEvents[eventName]["labeled"] = true
mergedEvents[eventName]["labeled"] = struct{}{}
}

for _, labelName := range wd.LabelCommand {
Expand Down Expand Up @@ -342,7 +342,7 @@ func resolveCentralizedEventStatusComment(wd *WorkflowData, eventName string) bo
func buildCentralSlashCommandWorkflowYAML(
slashRoutesByCommand map[string][]slashCommandRoute,
labelRoutesByCommand map[string][]slashCommandRoute,
mergedEvents map[string]map[string]bool,
mergedEvents map[string]map[string]struct{},
runsOn string,
setupActionRef string,
helpCommands []helpCommandEntry,
Expand Down Expand Up @@ -592,7 +592,7 @@ func writeCentralRouteTypeSummary(b *strings.Builder, routesByTrigger map[string
}
}

func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[string]map[string]bool) {
func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[string]map[string]struct{}) {
b.WriteString(` permissions:
actions: write
contents: read
Expand All @@ -608,7 +608,7 @@ func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[stri
}
}

func needsPullRequestsPermission(mergedEvents map[string]map[string]bool) bool {
func needsPullRequestsPermission(mergedEvents map[string]map[string]struct{}) bool {
// issue_comment and issues events can target pull requests (issue-backed PR payloads),
// and runtime branch resolution uses pulls.get for those cases.
pullRequestEvents := []string{"issues", "issue_comment", "pull_request", "pull_request_comment", "pull_request_review_comment", "pull_request_review"}
Expand Down Expand Up @@ -710,7 +710,7 @@ func formatRunsOnSnippetForInlineValue(runsOn string) string {
return "\n" + strings.Join(lines, "\n")
}

func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]bool) {
func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]struct{}) {
eventOrder := []string{
"issues",
"issue_comment",
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/central_slash_command_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ func TestBuildHelpCommandEntries_ReservedHelpCommandName(t *testing.T) {
require.Equal(t, "help", entries[0].Command)
}

func typeSetKeys(typeSet map[string]bool) []string {
func typeSetKeys(typeSet map[string]struct{}) []string {
out := make([]string, 0, len(typeSet))
for key := range typeSet {
out = append(out, key)
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/copilot_engine_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ func buildEngineCommandScriptSetup(command string) string {
// configuration authored in-repo; preserve shell semantics and forward driver args.
scriptContent := fmt.Sprintf("#!/usr/bin/env bash\nset +o histexpand\nset -eo pipefail\n%s \"$@\"\n", command)
heredocDelimiter := "GH_AW_ENGINE_COMMAND_EOF"
for strings.Contains(scriptContent, heredocDelimiter) {
for strings.Contains(scriptContent, heredocDelimiter) { //nolint:stringsconcatloop -- trivial cold path, runs 0 times in normal operation
heredocDelimiter += "_X"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The strings.Builder here is over-engineering for a loop that runs 0 times in normal operation. The linter rule targets hot-path accumulations; this is a cold-path conflict-avoidance suffix loop where string concatenation is perfectly readable.

💡 Consider a targeted nolint instead
heredocDelimiter := "GH_AW_ENGINE_COMMAND_EOF"
for strings.Contains(scriptContent, heredocDelimiter) { (nolint/redacted):loop-concat -- trivial cold path
    heredocDelimiter += "_X"
}

This keeps the intent clear and documents why the deviation is intentional.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted to plain string concatenation with //nolint:stringsconcatloop as suggested. Commit: pushed in the latest update.

Expand Down
3 changes: 2 additions & 1 deletion pkg/workflow/copilot_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ func (p *copilotSessionJSONLParser) handleUserEntry(entry SessionEntry) {
if !ok {
continue
}
if outputSize := len(content.Content); outputSize > 0 {
if content.Content != "" {
outputSize := len(content.Content)
if toolInfo, exists := p.toolCallMap[toolName]; exists {
if outputSize > toolInfo.MaxOutputSize {
toolInfo.MaxOutputSize = outputSize
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/engine_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ func loadKnownEngineImports(download func(context.Context) ([]byte, error)) map[
for _, engine := range catalog.Engines {
id := strings.ToLower(strings.TrimSpace(engine.ID))
importPath := strings.TrimSpace(engine.Import)
if len(id) == 0 || len(importPath) == 0 {
if id == "" || importPath == "" {
continue
}
loaded[id] = knownEngineImportWithCompilerRef(importPath)
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/samples_replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func collectSampleEntries(config *SafeOutputsConfig) []SampleEntry {
args := make(map[string]any, len(sample))
var sidecars map[string]any
for k, v := range sample {
if sidecarKeys[k] {
if _, isSidecar := sidecarKeys[k]; isSidecar {
if sidecars == nil {
sidecars = make(map[string]any)
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/workflow/samples_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ const sampleRuntimeExpressionPlaceholder = "aw_sample"
// that are NOT passed to the MCP tool's `tools/call` arguments. They are stripped
// from the sample before schema validation and consumed by the replay driver
// (e.g. to pre-stage a branch + patch on disk).
var sampleSidecarFields = map[string]map[string]bool{
var sampleSidecarFields = map[string]map[string]struct{}{
"create_pull_request": {
"patch": true,
"patch": {},
},
"push_to_pull_request_branch": {
"patch": true,
"patch": {},
},
}

Expand Down Expand Up @@ -409,10 +409,10 @@ func schemaNumberAsInt(schema map[string]any, key string) (int, bool) {
// stripSidecarFields returns a shallow copy of sample with sidecar keys removed.
// The original map is never modified, even when no sidecars are configured —
// callers may mutate the returned map without affecting the caller's input.
func stripSidecarFields(sample map[string]any, sidecars map[string]bool) map[string]any {
func stripSidecarFields(sample map[string]any, sidecars map[string]struct{}) map[string]any {
out := make(map[string]any, len(sample))
for k, v := range sample {
if sidecars[k] {
if _, isSidecar := sidecars[k]; isSidecar {
continue
}
out[k] = v
Expand Down