-
Notifications
You must be signed in to change notification settings - Fork 295
Description
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"] = maxValueHowever, 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.gowith theupdate_issue.go,update_discussion.go,update_pull_request.go,update_release.go,update_project.gocluster — excellent generic base with specific overrides.pkg/workflow/mcp_config_utils.goproviding shared utilities for themcp_config_*.gocluster.pkg/workflow/codemod_yaml_utils.gocentralizing 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.goto a data table - Finding 4: Fix
generateMaxWithTargetConfigandgenerateAssignToAgentConfigto callgenerateMaxConfiginsafe_outputs_config_generation_helpers.go - Finding 5: Move
trimLine/startsWithfromcodemod_bash_anonymous.gotocodemod_yaml_utils.go
References:
Generated by Semantic Function Refactoring
- expires on Feb 20, 2026, 5:29 PM UTC