Repository navigation
[typist] Typist - Go Type Consistency Analysis #66860
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-10-09T11:44:01.170Z.
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
🔤 Typist - Go Type Consistency Analysis
Analysis of repository: github/gh-aw
Executive Summary
I scanned all ~1,339 non-test struct/interface type definitions and the untyped-usage surface across
pkg/. The good news first: this codebase has already migrated essentially all rawinterface{}off to Go'sanyalias (only 2 hits remain, both in comments), and true duplicate type definitions are rare — most apparent "duplicates" turned out to be intentional, documented patterns (shared base structs, WASM build-tag stub pairs). The real opportunities are narrower but still worth doing: a handful of semantic/near-duplicate type families inpkg/cli's audit/reporting subsystem that reinvent the same shape with different names, and a systemic reliance onmap[string]any(~3,000 non-test occurrences) for parsed YAML/JSON frontmatter, where values are extracted via repeated ad-hoc type assertions instead of validated structs.The single highest-leverage fix is consolidating the
IntDelta/StringDeltafamily inaudit_comparison.gointo one genericDelta[T], and tightening the handful ofany-typed function signatures inpkg/typeutil/lookup.goandpkg/cli/token_usage_parse.gothat sit at the center of frontmatter/log parsing. Everything else is lower-urgency cleanup.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
Cluster 1: Scanner Finding/Output wrappers (zizmor / grype / poutine / runner-guard)
Type: Semantic duplicate — verified as already well-handled, downgraded to Low impact
Occurrences:
pkg/cli/zizmor.go:26—type zizmorFinding struct { Ident, Desc, URL string; Determinations struct{Severity string}; Locations []struct{...} }pkg/cli/grype.go:54—type grypeFinding struct { Vulnerability struct{ID, DataSource, Severity string; Fix struct{...}} }pkg/cli/poutine.go:25—type poutineFinding struct { RuleID, Purl string; Meta struct{Path string; Line int; Details string} }pkg/cli/runner_guard.go:23—type runnerGuardFinding struct { RuleID, Name, Severity, Description, Remediation, File, JobID string; Line int }pkg/scanfindings/scanfindings.go:107—type Finding struct { RuleID string; Severity SeverityLevel; Message string; File string; Line, Column int }Verification note: I checked this cluster by hand after the initial scan. Each scanner integration already has a
<scanner>FindingsToShared(...) []scanfindings.Findingadapter (zizmorFindingsToShared,grypeFindingsToShared,poutineFindingsToShared,runnerGuardFindingsToShared, plusgrantFindingsToShared), andpkg/cli/audit_report.goconsumes onlyscanfindings.Finding— none of the scanner-specific struct names leak into reporting. The per-scanner structs are necessary (they mirror each external tool's own JSON schema for unmarshaling), and the fan-in to one shared type is exactly the right pattern. No action needed here.Cluster 2: Two parallel run-comparison frameworks (
audit_diff.govsaudit_comparison.go)Type: Semantic duplicate
Impact: High
Occurrences:
pkg/cli/audit_diff.go:24—type DiffEntryBase struct { Status string; IsAnomaly bool; AnomalyNote string }pkg/cli/audit_diff.go:384—type AuditDiff struct { ...Run1ID, Run2ID... }pkg/cli/audit_comparison.go:21—type AuditComparisonData struct { Baseline *AuditComparisonBaseline; Delta *AuditComparisonDelta; ... }pkg/cli/audit_comparison.go:47—type AuditComparisonDelta struct { Turns AuditComparisonIntDelta; Posture AuditComparisonStringDelta; ... }Why it's a problem:
audit_diff.gocompares two runs using aRun1X/Run2X+ sharedDiffEntryBase{Status, IsAnomaly, AnomalyNote}convention.audit_comparison.gocompares a baseline vs. current run using an unrelatedBefore/After/Changedconvention with its ownDeltafamily. Same conceptual operation (diff two runs), two incompatible vocabularies in the same package.Recommendation: Converge on one comparison vocabulary — prefer the
Before/After/Changedstyle since it generalizes better — and share a single diffing helper/type family instead of maintaining two.Estimated effort: 3-4 hours. Benefits: one mental model for "comparing two runs," less duplicated diff logic.
Cluster 3:
AuditComparisonIntDelta/AuditComparisonStringDelta/AuditComparisonRouteDelta/AuditComparisonMCPFailureDeltaType: Near duplicate
Impact: Medium
Recommendation:
IntDeltaandStringDeltaare identical in shape modulo the value type. Replace with one genericDelta[T any] struct { Before, After T; Changed bool }and instantiate asDelta[int],Delta[string],Delta[*AuditComparisonRoute]. KeepMCPFailureDeltaseparate — itsNewlyPresentsemantics genuinely differ from a plain before/after comparison.Estimated effort: 1-2 hours. Benefits: one generic type instead of three hand-rolled ones; new delta kinds become a one-line instantiation.
Cluster 4: Hand-maintained "Wire" schema-mirror structs in
mcp_schema.goduplicating domain structs'MarshalJSONshapesType: Near duplicate
Impact: Medium
Occurrences (representative pairs):
pkg/cli/mcp_schema.go:239(toolUsageSummaryWire) mirrorspkg/cli/logs_report_tools.go:46(ToolUsageSummary)pkg/cli/mcp_schema.go:247(mcpServerHealthDetailWire) mirrorspkg/cli/audit_expanded.go:91(MCPServerHealthDetail, which has its own customMarshalJSON)pkg/cli/mcp_schema.go:258(mcpServerCrossRunHealthWire) mirrorspkg/cli/audit_cross_run.go:86(MCPServerCrossRunHealth)pkg/cli/mcp_schema.go:268(mcpFailureSummaryWire) mirrorspkg/cli/logs_models.go:240(MCPFailureSummary)pkg/cli/mcp_schema.go:275(domainAnalysisWireSchema) mirrorspkg/cli/access_log.go:35(DomainAnalysis)Why it's a problem: each domain struct already defines a custom
MarshalJSONwith an inline anonymous struct to produce a stable flattened JSON shape (post-embedding-refactor compatibility), andmcp_schema.goindependently redeclares that same flattened shape again just to generate a JSON Schema for MCP tool output. That's three places (domain struct, itsMarshalJSONanon struct, and the Wire struct) that must stay in sync by hand.Recommendation: Generate the JSON Schema from the real
MarshalJSONoutput (reflection-based) or introduce one source-of-truth schema struct whose tags drive both marshaling and schema generation, eliminating the Wire copy.Estimated effort: 4-6 hours (touches 5 type pairs). Benefits: removes an entire category of "forgot to update the schema mirror" bugs.
Cluster 5:
RunOptionsvsTrialOptions(workflow execution option bags)Type: Near duplicate
Impact: Medium
pkg/cli/run_workflow_execution.go:31—RunOptions{ EngineOverride, RepoOverride, RefOverride string; AutoMergePRs, Push, WaitForCompletion bool; RepeatCount int; Inputs []string; Verbose, DryRun, JSON, Approve bool }pkg/cli/trial_types.go:41—TrialOptions{ Repos TrialRepoContext; DeleteHostRepo, ForceDelete, Quiet, DryRun, JSONOutput bool; TimeoutMinutes int; TriggerContext string; RepeatCount int; AutoMergePRs bool; EngineOverride string; AppendText string; Verbose, DisableSecurityScanner bool }Recommendation: Extract a shared
CommonRunOptions{ EngineOverride string; RepeatCount int; AutoMergePRs, Verbose, DryRun bool }embedded by both, so the overlapping flags' semantics and defaults can't silently drift apart between the two commands.Estimated effort: 1-2 hours.
Lower-priority / no-action clusters (verified, listed for completeness)
ValidationError(pkg/validationerror/validationerror.gointerface vspkg/parser/validation_error.gostruct) — intentional: the parser struct embedsvalidationerror.Payloadto satisfy the shared interface. Same name across packages can look like accidental collision to newcomers; a cross-reference doc comment would help, but no structural change needed.ProgressBar/SpinnerWrapperWASM build-tag stub pairs (pkg/console/progress.go+progress_wasm.go,spinner.go+spinner_wasm.go) — deliberate platform-conditional stubs per the repo's documented WASM-stub convention.pkg/types.BaseMCPServerConfigvspkg/parserMCP config types — already a well-factored, explicitly documented shared-base embedding pattern, not duplication.Cluster/AnomalyReportvsaudit_cross_run_clusters.go'sRunCluster/ClusterPattern— same high-level idea (cluster similar items, surface patterns) applied to different domains with different algorithms (log-template mining vs. dimensional grouping). Not worth merging now; worth a sharedcluster.Analysis[T]package if a third use case appears.Argsstructs sharing aWorkflows []stringselector field — each tool needs genuinely distinct flags; only the selector field repeats. Not worth a shared type until a 5th tool needs it.Untyped Usages
Summary Statistics
interface{}usages (non-test): 2 — both in comments only; the codebase has already standardized onanyanyusages (non-test): ~4,961map[string]anyoccurrences (non-test): ~2,997pkg/linters): ~552Category 1:
anyin Function Parameters/Returns Sitting at Parsing ChokepointsImpact: High — these functions are the shared entry points for frontmatter/log parsing, so fixing them narrows the blast radius the most.
pkg/typeutil/lookup.go:9—func ParseBool(m map[string]any, key string) boolEvery caller passes parsed YAML frontmatter. Wrapping it in a named
type Frontmatter map[string]anywith typed accessor methods (f.Bool(key)) lets misuse show up at the call site instead of silently returning a zero value on type mismatch.pkg/typeutil/lookup.go:21—func LookupMap(m map[string]any, key string) (map[string]any, bool)Nested
map[string]anylookups chain indefinitely with no schema guarantee. At minimum, a named recursiveFrontmattertype documents intent and centralizes the assertion logic.pkg/cli/token_usage_parse.go:146,154—func extractUsageRecord(value any) map[string]anyandfunc usageNumericValue(parsed, usage map[string]any, keys ...string) float64value anycomes from decoding a controlled, fixed-format log record. Decoding straight into aUsageRecordstruct would move validation intojson.Unmarshaland remove the scatteredfloat64/json.Number/int/int64/stringtype switch currently needed to read the same logical field across two untyped maps.pkg/cli/mcp_add.go:167—func createMCPToolConfig(...) (map[string]any, error)The returned map is immediately serialized into YAML with a fixed key set (
type,command,env). AMCPToolConfigstruct prevents key-name typos and gets YAML marshal/unmarshal for free.pkg/cli/domains_command.go:198—func extractWorkflowDomainConfig(...) (engineID string, network *workflow.NetworkPermissions, tools map[string]any, runtimes map[string]any)Four-value return with two untyped maps is fragile to field renames; group into a
DomainConfigstruct.pkg/cli/codemod_bots.go:45—func mergeLegacyBots(onBots, topBots any) ([]string, bool)This repeats across roughly 15
codemod_*.gofiles — the frontmatter field is always astringor[]string. A single small sum-type helper used across all of them would remove the pattern everywhere at once.Category 2:
any/interface{}in Struct FieldsImpact: Medium
pkg/types/input_definition.go:18—Default any \yaml:"default,omitempty"` // Can be string, number, or boolean— the comment already documents the exact 3 legal kinds; a tagged-union (InputDefault{ Str *string; Num *float64; Bool *bool }) would let validation code pattern-match instead of type-asserting at every consumer of workflowinputs:`.pkg/parser/import_observability.go:18—Headers any \json:"headers,omitempty"`— HTTP headers are always string-keyed/valued; should just bemap[string]string`.pkg/cli/mcp_tools_privileged.go:458-459—RunID any,RunIDOrURL anywith a jsonschema tag literally stating "String or number" — a small custom-(Un)marshal union type would make this self-documenting.pkg/cli/list_workflows_command.go:29—On any \json:"on,omitempty"`for workflow trigger config, which already has a fixed shape modeled inpkg/workflow/trigger_parser.go` — reuse that type instead of a second untyped representation.Category 3:
map[string]anyHotspots (JSON/YAML-like Untyped Maps)Impact: High
pkg/parser/mcp.go— 34 occurrences in one file for MCP server definitions, which already have a well-known finite schema (stdio/http/sse, command, env, tools) validated by JSON schema elsewhere inpkg/parser/schemas. Decoding once into a struct would remove dozens of scattered.(map[string]any)assertions.pkg/parser/import_field_extractor.go— 41 occurrences; effectively hand-rolled schema validation duplicating the declarative JSON schemas inpkg/parser/schemas.pkg/cli/compile_model_validation.go:95—data.RawFrontmatter["engine"].(map[string]any)thenengine["models"].(map[string]any)— two chained assertions for a well-knownengine.modelsshape; a one-time typed decode removes the repeated assertion/ok-check boilerplate and its silent-no-op-on-mismatch failure mode.pkg/cli/codemod_min_integrity_none_bash.go:26—frontmatter["tools"].(map[string]any)→toolsMap["github"].(map[string]any)— this exact pattern repeats across most of the ~30codemod_*.gofiles; a shared typed accessor (using the existingGitHubToolConfig) would deduplicate and type-check all of them at once.Category 4: Untyped Constants (Timeouts, Levels, Modes)
Impact: Medium
pkg/cli/run_workflow_execution.go:25—const workflowCompletionWaitTimeoutMinutes = 6 * 60— a pre-multiplied bare int named "...Minutes" invites unit mistakes at every call site; preferconst workflowCompletionWaitTimeout = 6 * time.Hour.pkg/workflow/cache_integrity.go:17—const defaultCacheIntegrityLevel = "none"compared againstMinIntegrityelsewhere — should be atype CacheIntegrityLevel stringenum so arbitrary strings can't compile where only"none"/"full"are valid.pkg/constants/constants.go:306—const AWFDefaultLogLevel = "info", explicitly converted viastring(constants.AWFDefaultLogLevel)atpkg/workflow/awf_command_builder.go:531— that conversion is a tell callers already think of it as a distinct type. The same file already has the right pattern for other constants (CommandPrefix,LineLength); just not applied here.pkg/constants/constants.go:347—const DevModeGhAwImage = "localhost/gh-aw:dev"— mixing plain image-ref strings with arbitrary strings elsewhere risks passing the wrong kind of string where an image ref is expected.Refactoring Recommendations
Priority 1 — High: Consolidate the audit/comparison type families (Clusters 2 & 3)
Steps: 1) Pick the
Before/After/Changedvocabulary as the standard. 2) IntroduceDelta[T any]and replaceAuditComparisonIntDelta/AuditComparisonStringDelta. 3) Migrateaudit_diff.go'sRun1X/Run2Xfields onto the same convention. 4) Update callers and run tests.Estimated effort: 4-6 hours combined. Impact: High — one comparison vocabulary across the audit subsystem.
Priority 2 — High: Introduce a
Frontmatter/typed-config layer overmap[string]anyhotspots (Category 1 & 3 of untyped usages)Steps: 1) Start with
pkg/typeutil/lookup.go(ParseBool,LookupMap) since it's the shared entry point. 2) Add typed decode forpkg/parser/mcp.goandpkg/cli/compile_model_validation.go'sengine.modelslookup, both of which already have a JSON-schema-backed shape. 3) Extract one shared typed accessor for the repeatedfrontmatter["tools"].(map[string]any)["github"]pattern acrosscodemod_*.gofiles.Estimated effort: 6-10 hours (can be done incrementally, file by file). Impact: High — removes the largest source of silent runtime type-assertion failures.
Priority 3 — Medium: Mirror struct deduplication (Cluster 4) and
RunOptions/TrialOptions(Cluster 5)Steps: Generate
mcp_schema.go's Wire structs from the realMarshalJSONoutput instead of hand-mirroring it; extractCommonRunOptionsshared byRunOptionsandTrialOptions.Estimated effort: 5-8 hours combined. Impact: Medium.
Priority 4 — Medium: Type the semantically-meaningful constants (Category 4)
Steps: Add
CacheIntegrityLevel, convertAWFDefaultLogLevel/DevModeGhAwImageto named types following the existingCommandPrefix/LineLengthpattern already inpkg/constants/constants.go; fix the pre-multiplied timeout constant to usetime.Duration.Estimated effort: 2-3 hours. Impact: Medium — prevents unit-confusion and typo-driven bugs with low risk.
Implementation Checklist
AuditComparisonIntDelta/StringDeltainto genericDelta[T]audit_diff.goandaudit_comparison.goon one comparison vocabularymap[string]anyaccess behind a typedFrontmatteraccessor inpkg/typeutilpkg/parser/mcp.goMCP server config into a typed struct instead of repeated map assertionsfrontmatter["tools"]["github"]codemod patternmcp_schema.goWire structs with schema generation from realMarshalJSONoutputCommonRunOptionsshared byRunOptions/TrialOptionsCacheIntegrityLevel, typeAWFDefaultLogLevel/DevModeGhAwImage, fix pre-multiplied timeout constantAnalysis Metadata
anyusages, ~2,997map[string]any, ~552 untyped constantsReferences:
All reactions