Skip to content

[refactor] Semantic Function Clustering Analysis: Refactoring Opportunities #16608

@github-actions

Description

@github-actions

This issue tracks refactoring opportunities identified through semantic function clustering and duplicate detection across all non-test Go files in the repository.

Analysis scope: 541 non-test .go files across pkg/ directory
Workflow run: §22150151732
Analysis date: 2026-02-18


Executive Summary

Analysis of 541 Go source files across pkg/cli/, pkg/workflow/, pkg/parser/, pkg/console/, and utility packages identified 5 significant refactoring opportunities. The codebase shows good overall organization with consistent naming conventions. Key issues are: two near-duplicate validation functions in pkg/cli, three nearly-identical batch runner functions, a data-driven opportunity with 22 permissions factory functions, minor max-value resolution duplication in config generation helpers, and two local helper functions in a codemod file that belong in the shared utilities file.

Key Metrics

Metric Value
Total Go files analyzed 541
Function clusters identified 12+
Actionable duplication findings 5
High-priority items 2
Medium-priority items 3

Finding 1 — High Priority: Near-Duplicate Validation Functions

Files: pkg/cli/compile_validation.go
Functions: CompileWorkflowWithValidation and CompileWorkflowDataWithValidation

Both functions share approximately 60 lines of identical post-compilation logic:

  • Lock file validation
  • Action SHA validation
  • Per-file zizmor execution
  • Per-file poutine execution
  • Per-file actionlint execution

The only difference between the two functions is the initial compilation step:

// CompileWorkflowWithValidation
workflowData, err := compiler.CompileWorkflow(filePath)

// CompileWorkflowDataWithValidation
workflowData, err := compiler.CompileWorkflowData(workflowData, filePath)

Recommendation: Extract the shared post-compilation logic into a private helper runValidationOnCompiledWorkflow(compiler, workflowData, filePath string, opts ValidationOpts) error. Both public functions become thin wrappers calling this helper after their respective compile step.

Impact: ~60 lines removed, single place to update validation logic going forward.


Finding 2 — High Priority: Three Near-Identical Batch Runner Functions

File: pkg/cli/compile_batch_operations.go
Functions: runBatchActionlint, runBatchZizmor, runBatchPoutine

All three functions follow the identical pattern:

func runBatch(Tool)(...) error {
    if len(input) == 0 { return nil }
    log("Running (Tool)...")
    err := Run(Tool)OnFiles(input, verbose, strict)
    if err != nil {
        if strict {
            return fmt.Errorf("...: %w", err)
        }
        console.Warning("...")
    }
    return nil
}

The only differences are the tool name string and the called function. This results in ~15-20 lines duplicated three times (~60 lines total).

Recommendation: Introduce a generic batchRunner type:

type batchToolRunner struct {
    name    string
    runFn   func([]string, bool, bool) error
}

func (r batchToolRunner) run(input []string, verbose, strict bool) error {
    if len(input) == 0 { return nil }
    // shared logic...
}

Then define each runner as a simple data declaration. For runBatchPoutine, which takes a directory instead of file slice, a variant interface would handle that case.

Impact: ~40 lines removed, consistent behavior guaranteed across all three tools.


Finding 3 — Medium Priority: 22 Permissions Factory Functions Could Be Data-Driven

File: pkg/workflow/permissions_factory.go
Functions: NewPermissions* (22 functions, lines 7–221+)

The file contains 22 factory functions, of which 17 follow an identical structure:

func NewPermissions(Name)() *Permissions {
    return NewPermissionsFromMap(map[PermissionScope]PermissionLevel{
        ScopeContents:  LevelRead,
        ScopeIssues:    LevelWrite,
        // ...
    })
}

These are all legitimate named presets, but creating a new preset currently requires copying a function body. A data-driven approach would make the presets more discoverable and prevent copy-paste errors:

var permissionPresets = map[string]map[PermissionScope]PermissionLevel{
    "ContentsRead": {ScopeContents: LevelRead},
    "ContentsReadIssuesWrite": {
        ScopeContents: LevelRead,
        ScopeIssues:   LevelWrite,
    },
    // ...
}

The existing named constructor functions can remain as thin wrappers calling NewPermissionsFromMap(permissionPresets["ContentsRead"]), giving callers type-safe access while centralizing the permission definitions.

Impact: Permission scope combinations are defined in one place, reducing risk of inconsistency across presets.


Finding 4 — Medium Priority: Max-Value Resolution Duplicated in Config Generation Helpers

File: pkg/workflow/safe_outputs_config_generation_helpers.go
Functions: generateMaxWithTargetConfig (line ~41), generateAssignToAgentConfig (line ~96)

generateMaxConfig already encapsulates the pattern:

maxValue := defaultMax
if max > 0 {
    maxValue = max
}
config["max"] = maxValue

However, generateMaxWithTargetConfig and generateAssignToAgentConfig duplicate this logic inline instead of calling generateMaxConfig and merging the result.

Recommendation: Both functions should call generateMaxConfig(max, defaultMax) to obtain the base config and then add their additional fields, matching the pattern already used by generateMaxWithAllowedLabelsConfig, generateMaxWithAllowedConfig, and others.

Impact: 6–8 lines removed, consistent behavior when the max-resolution logic changes.


Finding 5 — Low Priority: Two Local Helpers in codemod_bash_anonymous.go Should Move to Shared Utilities

File: pkg/cli/codemod_bash_anonymous.go
Functions: trimLine(s string) string, startsWith(s, prefix string) bool

codemod_yaml_utils.go is the established centralized utilities file used by all 17 codemod files. codemod_bash_anonymous.go is the only codemod file that defines private helper functions outside of this shared file. trimLine and startsWith are generic string utilities that other codemods could benefit from.

Recommendation: Move trimLine and startsWith from codemod_bash_anonymous.go to codemod_yaml_utils.go. Update codemod_bash_anonymous.go to use the shared versions.

Impact: Completes the consolidation of codemod infrastructure into a single utilities file.


Well-Organized Patterns (No Action Needed)

The following were examined and found to be well-structured:

  • pkg/workflow/update_entity_helpers.go with the update_issue.go, update_discussion.go, update_pull_request.go, update_release.go, update_project.go cluster — excellent generic base with specific overrides.
  • pkg/workflow/mcp_config_utils.go providing shared utilities for the mcp_config_*.go cluster.
  • pkg/workflow/codemod_yaml_utils.go centralizing YAML manipulation for all 17 codemod files.
  • pkg/workflow/error_aggregation.go + error_helpers.go — clean separation of error collection vs. error construction.
  • pkg/workflow/frontmatter_extraction_metadata.go, _security.go, _yaml.go — well-split extraction concerns.
  • pkg/workflow/permissions_operations.go + permissions_parser.go — clean separation of operations from parsing.
  • pkg/cli/compile_* file cluster — excellent feature-based file decomposition.

Implementation Checklist

  • Finding 1: Extract shared post-compilation validation logic in compile_validation.go
  • Finding 2: Introduce generic batch runner in compile_batch_operations.go
  • Finding 3: Convert 17 permissions presets in permissions_factory.go to a data table
  • Finding 4: Fix generateMaxWithTargetConfig and generateAssignToAgentConfig to call generateMaxConfig in safe_outputs_config_generation_helpers.go
  • Finding 5: Move trimLine/startsWith from codemod_bash_anonymous.go to codemod_yaml_utils.go

References:

Generated by Semantic Function Refactoring

  • expires on Feb 20, 2026, 5:29 PM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions