Skip to content

[refactor] Semantic function clustering: expression detection helpers and version state duplication #27176

@github-actions

Description

@github-actions

Analysis of 711 non-test Go files across pkg/ (workflow: 343, cli: 257, parser: 40, console: 25, others: 46) using Serena semantic analysis and function inventory.

Most of the codebase is well-organized: engine polymorphism (claude/codex/gemini each implement the same interface), WASM build-constraint alternates, the actionpins adapter layer, and the safe-outputs cluster (22 files) are all clean. Two actionable refactoring opportunities stand out.


Issue 1: Scattered Expression Detection Helpers

Four functions in pkg/workflow check whether a string is a GitHub Actions expression ($\{\{ ... }}), but they live in unrelated files and have slightly different implementations:

Function File Logic Semantic
isGitHubActionsExpression dispatch_repository_validation.go strings.Contains(s, "$\{\{") contains opening marker
containsGitHubActionsExpression shell.go find $\{\{, then check }} follows contains complete expression
isGitHubExpression safe_outputs_validation.go check both markers exist and $\{\{ index < }} index contains complete expression
isExpressionString templatables.go HasPrefix("$\{\{") && HasSuffix("}}") whole string is an expression

containsGitHubActionsExpression and isGitHubExpression are near-duplicates — both return true when a string contains a properly ordered $\{\{ ... }} pair, implemented independently in two unrelated source files.

isGitHubActionsExpression is a weaker variant (only checks for $\{\{), placing it two files away from the stronger check that should replace it.

isExpressionString has a distinct semantic (the entire string is an expression) but also belongs with the others.

A dedicated file pkg/workflow/expression_patterns.go already exists and holds all expression-related regex patterns. It is the correct consolidation point.

Recommendation

Move all four functions into pkg/workflow/expression_patterns.go, rationalizing into three semantically clear helpers:

// containsExpression reports whether s contains a $\{\{ ... }} expression.
func containsExpression(s string) bool { ... }

// isExpression reports whether the entire string s is a $\{\{ ... }} expression.
func isExpression(s string) bool { ... }

// hasExpressionMarker reports whether s contains the opening $\{\{ marker (permissive check).
func hasExpressionMarker(s string) bool { ... }

Then update the four call sites:

  • dispatch_repository_validation.go (4 call sites) → hasExpressionMarker or containsExpression
  • shell.go (1 call site) → containsExpression
  • safe_outputs_validation.go (1 call site) → containsExpression
  • templatables.go (6 call sites) → isExpression

Estimated effort: 1–2 hours
Benefit: single source of truth; easier to harden expression detection uniformly

Current implementations
// dispatch_repository_validation.go — only checks opening marker
func isGitHubActionsExpression(value string) bool {
    return strings.Contains(value, "$\{\{")
}

// shell.go — finds $\{\{ then checks }} follows
func containsGitHubActionsExpression(s string) bool {
    openIdx := strings.Index(s, "$\{\{")
    if openIdx < 0 { return false }
    return strings.Contains(s[openIdx:], "}}")
}

// safe_outputs_validation.go — both markers + ordering
func isGitHubExpression(s string) bool {
    if !strings.Contains(s, "$\{\{") || !strings.Contains(s, "}}") { return false }
    openIndex := strings.Index(s, "$\{\{")
    closeIndex := strings.Index(s, "}}")
    return openIndex >= 0 && closeIndex > openIndex+3
}

// templatables.go — whole string is expression
func isExpressionString(s string) bool {
    return strings.HasPrefix(s, "$\{\{") && strings.HasSuffix(s, "}}")
}

Issue 2: Dual Version State Requires Manual Synchronization

pkg/cli/version.go and pkg/workflow/version.go each maintain their own version variable, kept in sync manually:

// pkg/cli/version.go
var version = "dev"
func GetVersion() string { return version }
func SetVersionInfo(v string) {
    version = v
    workflow.SetVersion(v) // manual sync
}

// pkg/workflow/version.go
var compilerVersion = "dev"
func GetVersion() string { return compilerVersion }
func SetVersion(v string) { compilerVersion = v }

If any code path calls workflow.SetVersion() without also calling cli.SetVersionInfo(), the two copies diverge silently. The cli.GetVersion() wrapper adds no behaviour beyond returning a local copy of a value that is already available via workflow.GetVersion().

Recommendation

Remove version from pkg/cli/version.go and have cli.GetVersion() delegate directly to workflow.GetVersion():

// pkg/cli/version.go — after refactor
func SetVersionInfo(v string) { workflow.SetVersion(v) }
func GetVersion() string      { return workflow.GetVersion() }

This eliminates the duplicated state while preserving the public API of both packages.

Estimated effort: 30 minutes
Benefit: single source of truth for version; eliminates silent drift risk


Analysis Metadata

  • Total non-test Go files analyzed: 711
  • Packages covered: workflow (343), cli (257), parser (40), console (25), agentdrain (10), stringutil (6), plus 8 smaller packages
  • Patterns confirmed as intentional (no action needed): WASM build-constraint file pairs, engine interface polymorphism (claude/codex/gemini/copilot/crush), actionpins adapter layer with type aliases, safe-outputs 22-file cluster, update-entity helper family
  • Detection method: Serena semantic analysis (LSP) + function name inventory + implementation comparison
  • Workflow run: §24627930373

Generated by Semantic Function Refactoring · ● 542.3K ·

  • expires on Apr 21, 2026, 11:35 AM UTC

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions