π§ Semantic Function Clustering Analysis
Analysis of repository: github/gh-aw β 1,102 non-test Go files across pkg/
Executive Summary
Semantic clustering + Serena/grep-verified body comparison across the codebase found a small set of high-confidence, actionable refactoring opportunities. The dominant finding is a wholesale clone of the Gemini CLI engine into the Antigravity engine (five paired files, ~90β95% identical bodies), which has already begun to drift and now carries latent behavioral inconsistency. Secondary findings are localized near-duplicate render/parse helpers in pkg/cli, pkg/console, and pkg/parser.
Good news worth noting: the parse*Config safe-output family (~40 functions) is already well consolidated behind parseConfigScaffold, and provider-specific ParseLogMetrics correctly delegate to a shared parseStatsJSONLMetrics. This report focuses only on what remains.
Metrics
| Metric |
Value |
| Non-test Go files analyzed |
1,102 (focus: pkg/workflow 453, pkg/cli 357, pkg/parser 52, pkg/console 29) |
| Verified duplicate clusters |
8 |
| Files in the Antigravity/Gemini clone |
5 pairs |
| Scattered-helper patterns |
3 |
| Detection method |
Serena/gopls semantic lookup + grep signature clustering + manual body diff |
Priority 1 β High Impact: Antigravity / Gemini engine clone
The entire antigravity_* engine is a near-verbatim copy of gemini_*. Each pair differs only in engine name, args, and API-key/base-URL env-var literals. This is the single highest-value refactor.
Verified paired duplicates (click to expand)
1a. GetExecutionSteps (~200 lines, >90% identical)
pkg/workflow/antigravity_engine.go:155 β func (e *AntigravityEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) []GitHubActionStep
pkg/workflow/gemini_engine.go:157 β func (e *GeminiEngine) GetExecutionSteps(...)
Same firewall block, same non-firewall command template, same env-map construction, same filtering tail. The command template is byte-identical apart from the injected command literal.
1b. GetRequiredSecretNames (byte-for-byte apart from one literal) β confirmed by direct read
pkg/workflow/antigravity_engine.go:50 (secrets := []string{"ANTIGRAVITY_API_KEY"})
pkg/workflow/gemini_engine.go:50 (secrets := []string{"GEMINI_API_KEY"})
Both then run the identical collectCommonMCPSecrets(...) append and the same hasGitHubTool -> GITHUB_MCP_SERVER_TOKEN branch.
1c. Settings-step generators (~95% identical)
pkg/workflow/antigravity_tools.go:130 β generateAntigravitySettingsStep
pkg/workflow/gemini_tools.go:125 β generateGeminiSettingsStep
Same tools-core plumbing, same JSON config shape, same jq-merge shell script; differ only in .antigravity/.gemini dir and GH_AW_*_BASE_CONFIG env name.
1d. RenderMCPConfig + surrounding engine methods (identical bodies)
pkg/workflow/antigravity_mcp.go:13 and pkg/workflow/gemini_mcp.go:13, both delegating to renderDefaultJSONMCPConfig(...). The GetPreBundleSteps, GetDeclaredOutputFiles, GetAgentManifestFiles/PathPrefixes, and GetSecretValidationStep pairs are all clone-per-engine.
1e. β οΈ Already-diverged clone (latent bug) β computeToolsCore
pkg/workflow/antigravity_tools.go:43 computeAntigravityToolsCore
pkg/workflow/gemini_tools.go:44 computeGeminiToolsCore
Same output contract, but the copies have drifted: Antigravity factored the bash logic into appendBashTools (with "wildcard anywhere wins" semantics) while Gemini still inlines an earlier loop with different discard-on-wildcard behavior. This is a real behavioral inconsistency between two engines that should behave identically.
Recommendation: Introduce a shared embeddable base type (e.g. googleCLIEngine) carrying the common behavior, parameterized by per-engine constants (engine name, primary secret, config dir, base-config env var, CLI args). The two engines become thin wrappers. Unify computeToolsCore on the single appendBashTools helper first β that one closes an active divergence, not just duplication.
Estimated effort: 4β6 hours. Benefit: removes ~5 clone pairs and eliminates the tools-core behavioral drift.
Priority 2 β Medium Impact: localized duplicate render/parse helpers
pkg/cli findings (click to expand)
2a. Poutine output rendering copied verbatim
pkg/cli/poutine.go:275 parseAndDisplayPoutineOutput vs pkg/cli/poutine.go:385 parseAndDisplayPoutineOutputForDirectory
Identical JSON preamble plus a ~60-line per-finding rendering loop (severity defaulting, startLine := max(1, lineNum-2) context window, console.CompilerError construction). Only per-file-filter vs group-by-file differs.
β Extract renderPoutineFindings(output, findings, fileLines) used by both.
2b. Scattered "read file -> context lines -> CompilerError" helper (3+ files)
pkg/cli/poutine.go:337 & :480, pkg/cli/runner_guard.go:226, pkg/cli/zizmor.go:259
The read-source -> split-lines -> build context-around-lineNum -> emit console.CompilerError block is duplicated. Additionally the path-traversal guard (filepath.Clean -> Abs -> Rel -> reject ..) is copied nearly verbatim between poutine.go:420 and runner_guard.go:182.
β Extract extractContextLines(fileLines, lineNum) and validateFileWithinRoot(gitRoot, filePath).
2c. Firewall timeline row renderers (~95% identical)
pkg/cli/gateway_logs_timeline_render.go:218 renderFirewallNetworkAllowedRow vs :241 renderFirewallNetworkBlockedRow β differ only in event-kind constant and initial status value.
β Unify into renderFirewallNetworkRow(evt, kind, defaultStatus).
2d. TSV renderers share skeleton + identical insights footer
pkg/cli/logs_format_tsv.go:31 renderLogsTSV vs :115 renderLogsTSVVerbose β same row-normalization loop and identical # insights: observability block; verbose is a column super-set.
β Drive both from one column-descriptor list; factor the shared footer.
2e. Org report header block repeated across three commands (weakest of the CLI set)
deploy_org.go:38 renderOrgDeployReport, update_org.go:152 renderOrgPreviewReport, upgrade_org.go:116 renderOrgUpgradeReport β share the applying/dry-run header idiom; per-item bodies legitimately differ.
β Extract renderOrgReportHeader(...); keep item bodies distinct.
pkg/console & pkg/parser findings (click to expand)
2f. Two byte-size formatters (confirmed both wrap scaleBinaryBytes)
pkg/console/progress_shared.go:6 formatBytes vs pkg/console/format.go:21 FormatFileSize β differ only cosmetically (space/precision/unit-list).
β Collapse into one formatter with a small style option.
2g. Two compact-number formatters in one file
pkg/console/render.go:674 FormatNumber (k/M/B, "0" for zero) vs :727 FormatTokens (K/M, "-" for zero) β same concept, divergent conventions producing inconsistent UI output.
β Unify onto one parameterized helper (suffix case + zero placeholder + precision).
2h. Near-duplicate cron handlers
pkg/parser/schedule_fuzzy_scatter.go: handleDaily(297)/handleDailyWeekdays(287), handleHourly(322)/handleHourlyWeekdays(307) β each pair >80% identical, differing only in the cron day field (* * * vs * * 1-5).
β Parameterize each pair with a dayField string argument.
2i. Redundant shadowing wrapper
pkg/parser/schema_suggestions.go:231 FindClosestMatches only adds two log lines around stringutil.FindClosestMatches (same name/signature/doc copied verbatim from pkg/stringutil/fuzzy_match.go:19).
β Have callers use stringutil.FindClosestMatches directly, or keep only if the logging is required.
Minor twin (Priority 3): pkg/workflow/add_labels.go:18 parseAddLabelsConfig vs remove_labels.go:18 parseRemoveLabelsConfig are identical except the "add-labels"/"remove-labels" literal β candidate for a generic parseAllowBlockLabelConfig[T].
Explicitly checked and ruled out
To keep signal high, these were investigated and dropped as not problematic duplication: //go:build wasm variant files (console_wasm.go, github_wasm.go), the console.FormatXMessage family (trivial distinct public APIs), typeutil.ParseIntValue vs ConvertToInt (intentional strict-vs-lenient semantics), download_workflow.go git-archive vs sparse-clone strategies, deps_outdated.go thin wrappers, and agentdrain Save/Load inverse pairs.
Implementation Checklist
Analysis Metadata
- Total Go files analyzed: 1,102 (non-test,
pkg/)
- Verified duplicate clusters: 8
- Scattered-helper patterns: 3
- Detection method: Serena/gopls semantic analysis + naming-pattern clustering + manual body diff
- Analysis date: 2026-07-18
Generated by π§ Semantic Function Refactoring Β· 457.9 AIC Β· β 16.5 AIC Β· β 9.4K Β· β·
π§ Semantic Function Clustering Analysis
Analysis of repository: github/gh-aw β 1,102 non-test Go files across
pkg/Executive Summary
Semantic clustering + Serena/grep-verified body comparison across the codebase found a small set of high-confidence, actionable refactoring opportunities. The dominant finding is a wholesale clone of the Gemini CLI engine into the Antigravity engine (five paired files, ~90β95% identical bodies), which has already begun to drift and now carries latent behavioral inconsistency. Secondary findings are localized near-duplicate render/parse helpers in
pkg/cli,pkg/console, andpkg/parser.Good news worth noting: the
parse*Configsafe-output family (~40 functions) is already well consolidated behindparseConfigScaffold, and provider-specificParseLogMetricscorrectly delegate to a sharedparseStatsJSONLMetrics. This report focuses only on what remains.Metrics
pkg/workflow453,pkg/cli357,pkg/parser52,pkg/console29)grepsignature clustering + manual body diffPriority 1 β High Impact: Antigravity / Gemini engine clone
The entire
antigravity_*engine is a near-verbatim copy ofgemini_*. Each pair differs only in engine name, args, and API-key/base-URL env-var literals. This is the single highest-value refactor.Verified paired duplicates (click to expand)
1a.
GetExecutionSteps(~200 lines, >90% identical)pkg/workflow/antigravity_engine.go:155βfunc (e *AntigravityEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) []GitHubActionSteppkg/workflow/gemini_engine.go:157βfunc (e *GeminiEngine) GetExecutionSteps(...)Same firewall block, same non-firewall command template, same env-map construction, same filtering tail. The command template is byte-identical apart from the injected command literal.
1b.
GetRequiredSecretNames(byte-for-byte apart from one literal) β confirmed by direct readpkg/workflow/antigravity_engine.go:50(secrets := []string{"ANTIGRAVITY_API_KEY"})pkg/workflow/gemini_engine.go:50(secrets := []string{"GEMINI_API_KEY"})Both then run the identical
collectCommonMCPSecrets(...)append and the samehasGitHubTool->GITHUB_MCP_SERVER_TOKENbranch.1c. Settings-step generators (~95% identical)
pkg/workflow/antigravity_tools.go:130βgenerateAntigravitySettingsSteppkg/workflow/gemini_tools.go:125βgenerateGeminiSettingsStepSame tools-core plumbing, same JSON config shape, same
jq-merge shell script; differ only in.antigravity/.geminidir andGH_AW_*_BASE_CONFIGenv name.1d.
RenderMCPConfig+ surrounding engine methods (identical bodies)pkg/workflow/antigravity_mcp.go:13andpkg/workflow/gemini_mcp.go:13, both delegating torenderDefaultJSONMCPConfig(...). TheGetPreBundleSteps,GetDeclaredOutputFiles,GetAgentManifestFiles/PathPrefixes, andGetSecretValidationSteppairs are all clone-per-engine.1e.β οΈ Already-diverged clone (latent bug) β
computeToolsCorepkg/workflow/antigravity_tools.go:43computeAntigravityToolsCorepkg/workflow/gemini_tools.go:44computeGeminiToolsCoreSame output contract, but the copies have drifted: Antigravity factored the bash logic into
appendBashTools(with "wildcard anywhere wins" semantics) while Gemini still inlines an earlier loop with different discard-on-wildcard behavior. This is a real behavioral inconsistency between two engines that should behave identically.Recommendation: Introduce a shared embeddable base type (e.g.
googleCLIEngine) carrying the common behavior, parameterized by per-engine constants (engine name, primary secret, config dir, base-config env var, CLI args). The two engines become thin wrappers. UnifycomputeToolsCoreon the singleappendBashToolshelper first β that one closes an active divergence, not just duplication.Estimated effort: 4β6 hours. Benefit: removes ~5 clone pairs and eliminates the tools-core behavioral drift.
Priority 2 β Medium Impact: localized duplicate render/parse helpers
pkg/cli findings (click to expand)
2a. Poutine output rendering copied verbatim
pkg/cli/poutine.go:275parseAndDisplayPoutineOutputvspkg/cli/poutine.go:385parseAndDisplayPoutineOutputForDirectoryIdentical JSON preamble plus a ~60-line per-finding rendering loop (severity defaulting,
startLine := max(1, lineNum-2)context window,console.CompilerErrorconstruction). Only per-file-filter vs group-by-file differs.β Extract
renderPoutineFindings(output, findings, fileLines)used by both.2b. Scattered "read file -> context lines -> CompilerError" helper (3+ files)
pkg/cli/poutine.go:337&:480,pkg/cli/runner_guard.go:226,pkg/cli/zizmor.go:259The read-source -> split-lines -> build context-around-
lineNum-> emitconsole.CompilerErrorblock is duplicated. Additionally the path-traversal guard (filepath.Clean->Abs->Rel-> reject..) is copied nearly verbatim betweenpoutine.go:420andrunner_guard.go:182.β Extract
extractContextLines(fileLines, lineNum)andvalidateFileWithinRoot(gitRoot, filePath).2c. Firewall timeline row renderers (~95% identical)
pkg/cli/gateway_logs_timeline_render.go:218renderFirewallNetworkAllowedRowvs:241renderFirewallNetworkBlockedRowβ differ only in event-kind constant and initialstatusvalue.β Unify into
renderFirewallNetworkRow(evt, kind, defaultStatus).2d. TSV renderers share skeleton + identical insights footer
pkg/cli/logs_format_tsv.go:31renderLogsTSVvs:115renderLogsTSVVerboseβ same row-normalization loop and identical# insights:observability block; verbose is a column super-set.β Drive both from one column-descriptor list; factor the shared footer.
2e. Org report header block repeated across three commands (weakest of the CLI set)
deploy_org.go:38renderOrgDeployReport,update_org.go:152renderOrgPreviewReport,upgrade_org.go:116renderOrgUpgradeReportβ share the applying/dry-run header idiom; per-item bodies legitimately differ.β Extract
renderOrgReportHeader(...); keep item bodies distinct.pkg/console & pkg/parser findings (click to expand)
2f. Two byte-size formatters (confirmed both wrap
scaleBinaryBytes)pkg/console/progress_shared.go:6formatBytesvspkg/console/format.go:21FormatFileSizeβ differ only cosmetically (space/precision/unit-list).β Collapse into one formatter with a small style option.
2g. Two compact-number formatters in one file
pkg/console/render.go:674FormatNumber(k/M/B,"0"for zero) vs:727FormatTokens(K/M,"-"for zero) β same concept, divergent conventions producing inconsistent UI output.β Unify onto one parameterized helper (suffix case + zero placeholder + precision).
2h. Near-duplicate cron handlers
pkg/parser/schedule_fuzzy_scatter.go:handleDaily(297)/handleDailyWeekdays(287),handleHourly(322)/handleHourlyWeekdays(307) β each pair >80% identical, differing only in the cron day field (* * *vs* * 1-5).β Parameterize each pair with a
dayField stringargument.2i. Redundant shadowing wrapper
pkg/parser/schema_suggestions.go:231FindClosestMatchesonly adds two log lines aroundstringutil.FindClosestMatches(same name/signature/doc copied verbatim frompkg/stringutil/fuzzy_match.go:19).β Have callers use
stringutil.FindClosestMatchesdirectly, or keep only if the logging is required.Minor twin (Priority 3):
pkg/workflow/add_labels.go:18parseAddLabelsConfigvsremove_labels.go:18parseRemoveLabelsConfigare identical except the"add-labels"/"remove-labels"literal β candidate for a genericparseAllowBlockLabelConfig[T].Explicitly checked and ruled out
To keep signal high, these were investigated and dropped as not problematic duplication:
//go:build wasmvariant files (console_wasm.go,github_wasm.go), theconsole.FormatXMessagefamily (trivial distinct public APIs),typeutil.ParseIntValuevsConvertToInt(intentional strict-vs-lenient semantics),download_workflow.gogit-archive vs sparse-clone strategies,deps_outdated.gothin wrappers, andagentdrainSave/Load inverse pairs.Implementation Checklist
computeAntigravityToolsCore/computeGeminiToolsCoreonappendBashTools(closes active divergence) β do this firstgoogleCLIEnginebase for the Antigravity/Gemini engine clone (execution steps, secrets, settings, MCP config)renderPoutineFindings,extractContextLines,validateFileWithinRootinpkg/cliformatBytes/FormatFileSizeandFormatNumber/FormatTokensinpkg/consoleparser.FindClosestMatchesshadowcomputeToolsCorealignment)Analysis Metadata
pkg/)