Repository navigation
[typist] Typist: Go Type Consistency Analysis — 2 duplicate-type clusters, 161 repeated map[string]any assertions in pkg/parser #65837
Closed
Replies: 1 comment
|
This discussion has been marked as outdated by Typist - Go Type Analysis. A newer discussion is available at Discussion #66107. |
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,426 non-test
.gofiles underpkg/(1,457 type definitions) for duplicated type definitions and weakly-typed (interface{}/any) usage. The good news first: the codebase is already disciplined about this. There's zero realinterface{}left in production code (it's fully migrated toany), and eight apparent "duplicate" structs I initially flagged (ActionPin,InputDefinition,SanitizeOptions,LogMetrics,ToolCallInfo, etc.) turned out to be intentionaltype X = pkg.Xaliases that already consolidate a single canonical definition — that's the right pattern, not a problem.After filtering out those aliases, linter test fixtures, and a couple of intentionally-documented view-model subsets, two genuine duplicate-type clusters remain, both easy, low-risk consolidations. The more interesting finding is on the untyped-usage side:
pkg/parserre-implements the samevalue.(map[string]any)type-assertion pattern 161 times across 20 files, even thoughpkg/typeutilalready ships safeLookupMap/LookupString/LookupBoolhelpers for exactly this — only one file inpkg/parseractually uses them. Consolidating onto the existing helpers would cut a lot of repeated boilerplate without needing any new types.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
.gofiles underpkg/)Cluster 1:
runtimeImportReference— near-duplicateOccurrences: 2
Impact: Medium — same concept, re-declared instead of reused;
workflowalready importsparserLocations:
pkg/parser/frontmatter_hash.go:572—type runtimeImportReference struct { path string; startLine int; endLine int }pkg/workflow/runtime_import_validation.go:38—type runtimeImportReference struct { importPath string; startLine int; endLine int }Both track the same thing — a
{{#runtime-import}}macro's file reference and line range — with 2 of 3 fields identical (startLine,endLine) and the third differing only in name (pathvsimportPath).pkg/workflow/runtime_import_validation.goalready importsgithub.com/github/gh-aw/pkg/parser, so there's no new dependency needed to consolidate.Recommendation:
parserpackage's version (e.g.RuntimeImportReference) and havepkg/workflowuse it directly instead of redeclaring an unexported copy.Cluster 2:
graderManifestEntry— near-duplicate (writer/reader split)Occurrences: 2
Impact: Medium — a reader-side struct silently depends on a writer-side struct's JSON shape with no shared source of truth
Locations:
pkg/workflow/compiler_yaml_graders.go:86— full 13-field struct (ID, Name, Description, Source, Enabled, Unit, Direction, Threshold, Max, Min, Digest, Run, Inline, Config), used to write the grader manifest.pkg/cli/audit_report_graders.go:67— 5-field subset (ID, Name, Unit, Direction, Threshold) with matching JSON tags, used to read the same manifest file back for audit reporting.Decoding JSON into a narrower struct is a legitimate Go pattern, but here there's no shared declaration (unlike the
JobInfo/JobDatapair elsewhere inpkg/cli, which has a comment explicitly documenting the subset relationship). If the writer renames or retypes a field the 5 shared ones depend on, the reader breaks silently at runtime with no compiler signal.Recommendation:
pkg/types) that both the writer's full struct and the reader embed/reuse, or havepkg/clidecode directly into (an exported version of) the writer's struct.Patterns checked and intentionally excluded (no action needed)
ActionYAMLInput,ActionPin,ContainerPin,ActionPinsData,SHAResolver(pkg/workflow/action_pins.go→pkg/actionpins),InputDefinition(pkg/workflow/inputs.go→pkg/types),SanitizeOptions(pkg/workflow/strings.go→pkg/stringutil),LogMetrics/ToolCallInfo(pkg/cli/logs_models.go→pkg/workflow). These aretype X = pkg.Xaliases, not redeclarations — exactly the pattern Cluster 1/2 above should adopt.MCPServerConfigfamily (pkg/types.BaseMCPServerConfig,pkg/workflow.MCPServerConfig,pkg/parser.RegistryMCPServerConfig) — already de-duplicated via embedding, with a comment explaining the design. No action needed.SpinnerWrapper/ProgressBar(pkg/console, wasm vs native build tags) — intentional per-platform stub pattern used throughout this codebase, not accidental duplication.JobInfo/JobData(pkg/cli) —JobDatais explicitly documented as "derived from, and intentionally much smaller than, the raw API mirrorJobInfo." Working as intended.ValidationError(pkg/validationerroris aninterface;pkg/parser.ValidationErroris a struct wrapping itsPayload) — different roles, not a duplicate.state,fakeOS,myString,myBytes,Worker— all insidepkg/linters/*/testdata/, synthetic fixtures used to exercise specific lint rules. Out of scope (test fixtures, not production duplication).Untyped Usages
Summary Statistics
interface{}usages in production code: 0 (two matches found were both in comments, e.g.pkg/cli/mcp_schema.go:121)anyusages (function params/returns, fields, maps): widespread (100+ files), majority justified.(map[string]any)across 20 files inpkg/parserCategory 1: Repeated
map[string]anytype assertions instead of existing helpersImpact: High — not a safety bug (every assertion checked uses the
, okform), but a large, verified maintenance/duplication costpkg/typeutil/lookup.goalready provides exactly this capability:Yet across
pkg/parser—yaml_import.go,schema_deprecation.go,mcp.go,schema_suggestions.go,import_field_extractor.go,import_schema_validation.go,tools_merger.go,schema_triggers.go,schema_validation.go,content_extractor.go,schema_compiler.go, and others — the samevalue, ok := x.(map[string]any)pattern is hand-written 161 times. Onlypkg/parser/frontmatter_hash.goactually imports and usespkg/typeutil.Example (
pkg/parser/mcp.go:99):could become:
Suggested fix: audit the 20 affected files and replace direct assertions with
typeutil.Lookup*calls where the shape matches (key lookup + cast); for the remainder (asserting an already-extractedanyvalue rather than a map key), consider adding a smalltypeutil.AsMap(v any) (map[string]any, bool)wrapper so all parser-layer assertions go through one place.Benefits: single place to harden nil/absent-key handling in the future; ~160 fewer near-identical lines to maintain.
Category 2:
anyfields with a small, known set of concrete typesImpact: Medium — type safety could be improved without much disruption since the real value space is already small and documented
Example 1 —
pkg/types/input_definition.go:18GetDefaultAsString()(same file, line 29) already exhaustively switches over exactly 5 concrete types (string,bool,int,int64,float64). A small discriminated wrapper type (or at minimum restricting the type switch to be the single source of truth the field's godoc points to) would let the compiler help enforce the "string, number, or boolean" contract instead of only a code comment.Example 2 —
pkg/parser/import_observability.go:16-18Two known shapes (
stringormap[string]string) are already called out in the comment. A small customUnmarshalJSONon aHeaderSpectype would make the two accepted forms explicit and testable instead of relying on call-site type switches.Category 3: Generic
anyutilities — reviewed, left as-ispkg/typeutil(ParseBool,ParseIntValue,ConvertToInt,ConvertToFloat),pkg/jsonutil.MarshalCompactNoHTMLEscape,pkg/workqueue.Branch.request, andpkg/parser.fetchRemoteFileContentall takeanyparameters that mirror stdlib conventions (json.Marshal,json.Unmarshal) or exist specifically to normalize arbitrary parsed YAML/JSON. These are intentional, narrowly-scoped generic utilities — exactly the case theinterface{}/anyguidance says to leave alone.Untyped constants — checked, no issues found
Sampled numeric constants that looked unit-ambiguous at first glance (
defaultCodeCoverageWaitForProcessingTimeout = 160,orgUpdateCoreBuffer = 500,orgUpdateCriticalConsumed = 1000) all turned out to have clear doc comments stating their units and purpose directly above the declaration. No action needed here — this is a case where comment discipline already does the job a wrapper type would otherwise provide.Refactoring Recommendations
Priority 1: Consolidate
runtimeImportReferenceSteps:
parser.runtimeImportReference(renamepath→ consistent field name, e.g.ImportPath).pkg/workflow/runtime_import_validation.goto useparser.RuntimeImportReference(already importspkg/parser).Estimated effort: ~30 minutes · Impact: Medium
Priority 2: Reduce duplicated
map[string]anyassertions inpkg/parserSteps:
typeutil.LookupMap/LookupStringPath).typeutil.AsMap(v any) (map[string]any, bool)helper for the remaining bare assertions.Estimated effort: 4-6 hours (161 call sites, mostly mechanical) · Impact: High (maintainability, consistency)
Priority 3: Share
graderManifestEntrybetween writer and readerSteps:
pkg/types(or export the workflow struct) so bothpkg/workflow/compiler_yaml_graders.goandpkg/cli/audit_report_graders.goreference one declaration.pkg/workflow/graders_workflow_integration_test.goto confirm round-tripping still works.Estimated effort: ~1 hour · Impact: Medium
Priority 4 (optional, low effort): Tighten two
anyfields with known type setsInputDefinition.Default(pkg/types/input_definition.go)observabilityImportEndpoint.Headers(pkg/parser/import_observability.go)Estimated effort: 1-2 hours combined · Impact: Low-Medium (documentation-to-compiler-enforcement upgrade)
Implementation Checklist
runtimeImportReferenceinto a single exported type inpkg/parserpkg/parser's 161 hand-rolledmap[string]anyassertions withpkg/typeutilhelpers (or a newAsMapwrapper)graderManifestEntry's common fields betweenpkg/workflowandpkg/cliInputDefinition.DefaultandobservabilityImportEndpoint.Headersas small typed wrapperspkg/parser,pkg/workflow, andpkg/clitest suites after each consolidation stepAnalysis Metadata
pkg/)pkg/+ a background type-clustering pass, cross-checked file-by-file (Serena's Go/TS/Bash language servers were unavailable in this sandbox — Go/Node were not onPATH— so symbol-level reference tracing fell back to targeted grep verification)References:
§37303246912
All reactions