Repository navigation
[typist] Typist: Go Type Consistency Analysis #67974
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-10-13T11:43:02.293Z.
|
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 1,531 non-test
.gofiles underpkg/(1,629 top-level type declarations) for duplicated type definitions and untyped (interface{}/any/bare-literal-constant) usage. The headline finding: this codebase already has unusually strong type discipline. There's a documented internal style guide (scratchpad/go-type-patterns.md) covering semantic type aliases, and a dedicatedpkg/constantspackage already wraps job names, step IDs, artifact names, filenames, and engine names in purpose-built string types.interface{}has effectively been eliminated from production code — the only two hits in all ofpkg/are inside comments describing the concept, not actual type usage.That said, I found 7 real duplication clusters worth consolidating and a handful of untyped constants that break the project's own semantic-typing convention. The most actionable one:
pkg/cli/mcp_schema.gohand-maintains four to five "Wire" struct twins that mirror customMarshalJSONoutput on the real domain structs — a documented but fragile pattern where adding a field to the real struct silently doesn't reach the JSON-schema generator unless someone remembers to update the twin by hand. There's also one byte-for-byte exact duplicate (domainAnalysisWireSchema/domainAnalysisWire) sitting in the same package, which is a pure one-line fix.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
Cluster 1:
domainAnalysisWireSchema/domainAnalysisWireType: Exact duplicate
Occurrences: 2
Impact: High — identical struct defined twice in the same package
Locations:
pkg/cli/mcp_schema.go:275—type domainAnalysisWireSchema struct { TotalRequests int; AllowedCount int; BlockedCount int; AllowedDomains []string; BlockedDomains []string }pkg/cli/access_log.go:44—type domainAnalysisWire struct { TotalRequests int; AllowedCount int; BlockedCount int; AllowedDomains []string; BlockedDomains []string }Recommendation: Delete
domainAnalysisWireSchemaand havemcp_schema.goreferencedomainAnalysisWiredirectly for schema generation.Estimated effort: <30 minutes.
Cluster 2: "Wire" shadow structs mirroring custom
MarshalJSONoutputType: Near duplicate
Occurrences: 4 pairs
Impact: High — documented pattern, but each pair is hand-synced and will silently drift
pkg/cli/mcp_schema.gokeeps unexported*Wiretwins of several domain structs purely so the JSON-schema generator (which can't see customMarshalJSONoutput) has something to reflect over:ToolUsageSummary(pkg/cli/logs_report_tools.go:46)toolUsageSummaryWire(pkg/cli/mcp_schema.go:239)MCPServerHealthDetail(pkg/cli/audit_expanded.go:107)mcpServerHealthDetailWire(pkg/cli/mcp_schema.go:247)MCPServerCrossRunHealth(pkg/cli/audit_cross_run.go:86)mcpServerCrossRunHealthWire(pkg/cli/mcp_schema.go:258)MCPFailureSummary(pkg/cli/logs_models.go:243)mcpFailureSummaryWire(pkg/cli/mcp_schema.go:268)Recommendation: Instead of four independent hand-maintained twins, add a single helper (e.g. a
SchemaShape() anymethod on each domain type, or a shared struct-tag-driven reflector) so the wire shape is derived from — and fails to compile when it diverges from — the domain type's actualMarshalJSONoutput.Estimated effort: 3-4 hours.
Cluster 3:
graderManifestEntry(writer vs. reader)Type: Near duplicate
Occurrences: 2
Impact: Medium
Locations:
pkg/workflow/compiler_yaml_graders.go:84— full writer-side struct (ID, Name, Description, Source, Enabled, Unit, Direction, Threshold, Max, Min, Digest, Run, Inline, Config)pkg/cli/audit_report_graders.go:67— reader-side subset (ID, Name, Unit, Direction, Threshold)Both read/write the same
grader_manifest.jsonfile (seepkg/constants.GraderManifestFilename). A field added on the write side won't be visible to the cli reader unless someone manually mirrors it.Recommendation: Move the full struct to a shared location (e.g.
pkg/workflowalready owns it — export it, or move to a small shared package) and havepkg/cliimport it instead of redefining a subset.Estimated effort: 1-2 hours.
Cluster 4:
AuditOptions/auditCommandOptions/auditRunConfigType: Near duplicate
Occurrences: 3
Impact: Medium
Locations:
pkg/cli/audit.go:14—AuditOptions(public, ~15 fields: owner/repo/hostname/outputDir/verbose/parse/jsonOutput/jobID/stepNumber/format/artifactSets/experimentFilter/...)pkg/cli/audit.go:34—auditRunConfig(internal, same concepts, lower-cased fields)pkg/cli/audit_command.go:59—auditCommandOptions(CLI-flag-facing, same concepts again)Three structs carry the same audit-run configuration through three pipeline stages (raw flags → public options → internal run config) with >70% field overlap and only casing/naming differences.
Recommendation: Collapse to one struct passed through the pipeline, or make the later stages embed the earlier one and add only the fields that differ.
Estimated effort: 2-3 hours.
Cluster 5:
FirewallLogEntry/AuditLogEntryType: Semantic duplicate
Occurrences: 2
Impact: Medium
Locations:
pkg/cli/firewall_log.go:118—FirewallLogEntry(parsed from the squid-style text log:Timestamp, ClientIPPort, Domain, DestIPPort, Proto, Method, Status, Decision, URL, UserAgent)pkg/cli/firewall_policy.go:50—AuditLogEntry(parsed from the JSON audit log:Schema, Timestamp, Client, Host, Dest, Method, Status, Decision, URL)Both represent the same conceptual entity — one proxied network request observed by the sandbox firewall — just parsed from two different log formats, with ~6 of ~9-10 fields overlapping (
Timestamp, host/domain,Status,Method,Decision,URL).Recommendation: Introduce a shared normalized
ProxiedRequesttype that both format-specific parsers populate, so downstream reporting code only needs to know one shape.Estimated effort: 2-3 hours.
Cluster 6: Diagnostic-with-source-position structs
Type: Semantic duplicate
Occurrences: 3 (spanning 2 packages)
Impact: Medium
Locations:
pkg/console/console_types.go:4—ErrorPosition{File, Line, Column}+CompilerError{Position, Type, Message, Context, Hint}(console-facing error/warning rendering)pkg/parser/json_path_locator.go:23—JSONPathLocation{Line, Column, Found}(schema-validation error locations)pkg/scanfindings/scanfindings.go:107—Finding{RuleID, Severity, Message, File, Line, Column, Context}/NewFindingParams{..., FileLines}(linter/scanner findings)All three independently reinvent "a
{File, Line, Column}position attached to a message with optional source-line context" — once for compiler errors, once for JSON-path schema errors, once for scanner findings. None of them are wrong individually, but a sharedSourcePosition{Line, Column int}(optionally+File string) value type would let all three compose it instead of each redeclaring the same twointfields.Estimated effort: 2-3 hours (mostly mechanical, touches 3 packages).
Cluster 7: Wire/Display one-struct-per-presentation-layer pattern (broader)
Type: Semantic duplicate
Occurrences: 2+ (same shape as Cluster 2, different layer)
Impact: Low
pkg/cli/logs_report.go:933(mcpFailureSummaryDisplay) adds a third representation of the MCP-failure-summary shape (console/table-formatted) alongside the domain struct and its Cluster-2Wiretwin — confirming this "one struct per presentation layer" pattern is systemic acrosspkg/cli's audit/logs reporting code rather than an isolated case. Worth keeping in mind when addressing Cluster 2, since the same consolidation approach would likely cover this too.Investigated and ruled out (intentional, already well-factored — not duplicates):
types.BaseMCPServerConfigembedded by bothpkg/parser.RegistryMCPServerConfigandpkg/workflow.MCPServerConfig;pkg/parser.ValidationErrordeliberately wrappingpkg/validationerror.Payload;pkg/cli.DependencyInfoWithIndirectembeddingDependencyInfo.Untyped Usages
Summary Statistics
interface{}usages in production code: 0 (two apparent hits are both inside comments describing the concept, not real type declarations)anyusages in production code: present, but every instance found is already documented as an intentional dynamic-YAML/JSON case per the project's ownscratchpad/go-type-patterns.mdThis codebase is already unusually disciplined here.
scratchpad/go-type-patterns.mdcodifies "always useanyinstead ofinterface{}", documents whenmap[string]anyis appropriate (dynamic YAML/JSON frontmatter), and gives worked examples of converting[]anyto semantic typed slices (GitHubAllowedTools,GitHubToolsets). Thepkg/constantspackage already wraps job names, step IDs, MCP server IDs, artifact names, filenames, versions, and engine names in dedicated string types withString()/IsValid()methods.Category 1:
anystruct fields — reviewed, all justifiedanypkg/types/input_definition.go:18InputDefinition.Default anypkg/workflow/service_ports.go:85-86serviceContainerConfig.Ports anyservices:YAML allows the ports list to be strings, bare ints, or omitted — explained in the doc comment above the fieldpkg/parser/import_observability.go:16-18observabilityImportEndpoint.Headers anypkg/console/console_types.go:52-59FormField.Value anyNo changes recommended here — these match the "valid uses of
any" criteria in the team's own style guide.Category 2: Untyped numeric/string constants
Impact: Low-medium — mostly a missed opportunity for the semantic-type pattern the codebase already uses everywhere else, one real inconsistency
Examples:
Used as plain
intat the call site:pkg/workflow/role_checks.go:80(max := constants.DefaultRateLimitMax) and:87(window := constants.DefaultRateLimitWindow).Used as plain
intatpkg/parser/remote_download_file.go:92.Suggested fix (following the existing
LineLength/Versionpattern documented inscratchpad/go-type-patterns.md):One concrete inconsistency worth a quick fix: every other filename constant in
pkg/constants/constants.gois typedFilename(AgentOutputFilename,SafeOutputsFilename,TokenUsageFilename,SarifFileName, ...) except:Recommendation: add
Filenameto match its siblings — a one-line fix.What Should We Do About This?
Prioritized by effort-to-impact ratio:
domainAnalysisWireSchema(Cluster 1) and typeSafeOutputErrorsFilename Filename(Category 2). Both are single-line changes with zero behavioral risk.DefaultRateLimitMax/DefaultRateLimitWindow/MaxSymlinkDepthwith semantic types, following the existingLineLength/Versionpattern already inpkg/constants.pkg/cli/mcp_schema.gonext: tackle the Wire-struct consolidation (Clusters 2 and 7) together, since they share the same root cause (schema generator can't seeMarshalJSONoutput).graderManifestEntrydedup (Cluster 3),AuditOptionspipeline collapse (Cluster 4),FirewallLogEntry/AuditLogEntryunification (Cluster 5), and the sharedSourcePositiontype (Cluster 6) — all genuine but none urgent; good candidates for a future refactor PR rather than one-off fixes.Implementation Checklist
domainAnalysisWireSchema, reusedomainAnalysisWireinmcp_schema.goFilenametype toSafeOutputErrorsFilenameDefaultRateLimitMax,DefaultRateLimitWindow,MaxSymlinkDepthpkg/cli/mcp_schema.gograderManifestEntrystruct between writer and readerAuditOptions/auditCommandOptions/auditRunConfigtrioSourcePositiontype forconsole,parser, andscanfindingsAnalysis Metadata
pkg/)interface{}usages foundReferences: §38243234272
All reactions