You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[typist] 🔤 Typist: Go Type Consistency Analysis
#67180
I scanned roughly 1,493 non-test .go files under pkg/ (~1,436 top-level type definitions) for duplicated type definitions and untyped (interface{}/any) usage. The good news first: interface{} is essentially gone from production code — the only 9 hits are intentional "bad pattern" fixtures inside pkg/linters/*/testdata, used by this repo's own custom linters to test themselves. any is used ~2,900 times, and the overwhelming majority of that is legitimate (generics like func f[T any], and deliberately dynamic YAML/JSON frontmatter fields that are documented as "too dynamic to type").
Digging past that, though, turned up a handful of worthwhile findings. On the duplication side, RunOptions and RunWorkflowOptions in pkg/cli share 8 of 10 fields verbatim and could share a common embedded struct, and GatewayServerMetrics/GatewayToolMetrics in pkg/cli/gateway_logs_types.go re-spell a metrics shape the team already consolidated elsewhere into MCPServerStatsBase (the existing code comment literally describes fixing this exact kind of drift) — this one's a straggler that didn't get the memo. On the untyped side, the clearest win is Target any repeated 5 times in pkg/workflow/safe_outputs_azure_devops.go, even though every other Target field in the same package (CreateCheckRun.Target, UpdateDiscussions.Target) is a plain string, and tests confirm it's always a string at runtime — same story for Headers any vs. Headers map[string]string used elsewhere for the identical concept.
Full Analysis Report
Duplicated Type Definitions
Summary Statistics
Total types analyzed: ~1,436 (approximate; top-level struct/interface declarations in non-test .go files under pkg/)
8 of RunWorkflowOptions' 10 fields (Verbose, EngineOverride, RepoOverride, RefOverride, AutoMergePRs, Push, DryRun, Approve) are identical name+type matches with RunOptions.
Recommendation:
Extract a shared WorkflowRunFlags struct holding the 8 common fields and embed it in both RunOptions and RunWorkflowOptions.
Estimated effort: 1-2 hours
Benefits: One source of truth for the shared run flags; adding a new shared flag (e.g. a future --timeout) only requires touching one struct instead of two.
Cluster 2: MCP/Gateway server & tool call metrics
Type: Near duplicate Occurrences: 5 related types Impact: High — the codebase already solved this once and left one case unconsolidated
Locations:
pkg/cli/gateway_logs_types.go:85 — type GatewayServerMetrics struct {...}
pkg/cli/gateway_logs_types.go:97 — type GatewayToolMetrics struct {...}
pkg/cli/logs_models.go:218 — type MCPServerStatsBase struct {...} (the shared base others already embed)
pkg/cli/audit_report.go:254 — type MCPServerStats struct { MCPServerStatsBase; ... }
pkg/cli/audit_expanded.go:94 — type MCPServerHealthDetail struct { MCPServerStatsBase; ... }
Definition Comparison:
// logs_models.go:218 — the existing comment documents the exact problem this cluster re-introduces:// "Those types previously spelled the same concepts four different ways// (TotalCalls/ToolCalls/ToolCallCount and TotalErrors/ErrorCount)"typeMCPServerStatsBasestruct {
ServerNamestring`json:"server_name" console:"header:Server"`ToolCallCountint`json:"tool_call_count" console:"header:Tool Calls"`ErrorCountint`json:"error_count,omitempty" console:"header:Errors,omitempty"`
}
// gateway_logs_types.go:85 — re-spells the same ServerName/ToolCallCount/ErrorCount concept// instead of embedding MCPServerStatsBase:typeGatewayServerMetricsstruct {
ServerNamestringRequestCountintToolCallCountintTotalDurationfloat64ErrorCountintFilteredCountintGuardPolicyBlockedintToolsmap[string]*GatewayToolMetrics
}
Recommendation:
MCPServerStats and MCPServerHealthDetail already embed MCPServerStatsBase to avoid exactly this drift — GatewayServerMetrics (live gateway-log parsing path) is the one straggler left outside that hierarchy.
Embed MCPServerStatsBase in GatewayServerMetrics (keeping its gateway-specific fields like TotalDuration/FilteredCount/GuardPolicyBlocked alongside it).
Estimated effort: 2-3 hours (plus updating any field-rename call sites)
Benefits: Finishes the consolidation the team already started; one name per concept across the whole MCP metrics surface.
Cluster 3: WorkQueue Assignment/AssignmentClaim vs CLI "receipt" types
Type: Semantic duplicate Occurrences: 4 Impact: Medium
Locations:
pkg/workqueue/types.go:218 — type Assignment struct {DispatchID, RequestID, CommitID, PolicyEpoch, Pool, WorkerProfile string; Claims []AssignmentClaim}
pkg/workqueue/types.go:229 — type AssignmentClaim struct {Handle, ClaimID, WorkID, Work, ResultRefs}
pkg/cli/logs_work_queue_current.go:30 — type WorkQueueAssignmentReceipt struct {DispatchID, RequestID, CommitID, State string; Released bool; Claims []WorkQueueClaimReceipt}
pkg/cli/logs_work_queue_current.go:39 — type WorkQueueClaimReceipt struct {Handle, ClaimID, WorkID, State, Barrier}
pkg/cli's "receipt" types hand-duplicate ~60-70% of the field names from pkg/workqueue's core Assignment/AssignmentClaim types instead of projecting from them.
Recommendation:
Add a ToReceipt() method on workqueue.Assignment/AssignmentClaim, or factor out a shared identity sub-struct (DispatchID/RequestID/CommitID/Handle/ClaimID/WorkID), instead of re-declaring the field names by hand in pkg/cli.
Estimated effort: 2-3 hours
Benefits: Changes to the core work-queue identity fields propagate automatically to the CLI reporting view instead of needing a second hand-edit.
Cluster 4: Domain allow/block list duplication outside DomainBuckets
Type: Semantic duplicate Occurrences: 3 Impact: Medium
Locations:
pkg/cli/domain_buckets.go:13 — type DomainBuckets struct {AllowedDomains []string; BlockedDomains []string} (designed to be embedded)
pkg/cli/access_log.go:44 — type domainAnalysisWire struct {...; AllowedDomains, BlockedDomains []string}
Recommendation:
DomainBuckets already exists for exactly this purpose and is embedded elsewhere via AnalysisBase. Embed it in WorkflowDomainsDetail too.
The domainAnalysisWire case is a JSON-compat shim that must preserve specific legacy keys — that one is a reasonable, already-understood exception and lower priority.
Estimated effort: 30-60 minutes
Benefits: One definition of "what an allow/block domain pair looks like" across CLI reporting types.
pkg/scanfindings/scanfindings.go:107 — type Finding struct {RuleID, Severity, Message, File, Line, Column, Context} (the shared normalized target)
pkg/cli/zizmor.go:27 — type zizmorFinding struct {...}
pkg/cli/poutine.go:26 — type poutineFinding struct {...}
pkg/cli/grype.go:54 — type grypeFinding struct {...}
pkg/cli/runner_guard.go:23 — type runnerGuardFinding struct {...}
Each *Finding struct mirrors a distinct third-party tool's own JSON output schema (zizmor/poutine/grype/runner-guard), so merging them into one type isn't appropriate — but all four get hand-converted into scanfindings.Finding with near-identical RuleID/Severity/File/Line mapping code.
Recommendation:
Keep the tool-specific wire structs, but introduce a small shared conversion helper (or a rawScanFinding interface) so the four conversions to scanfindings.Finding stop being copy-pasted independently.
Note: AuditFinding (pkg/cli/audit_report.go:79) and SecurityFinding (pkg/workflow/markdown_security_scanner.go:63) are further semantic cousins of the same "severity + message + location" shape worth folding in if this is tackled.
Estimated effort: 2-3 hours
Benefits: New scanners only need to write a mapping function, not a parallel finding struct plus conversion.
Cluster 6: ProgressBar / SpinnerWrapper (native vs. WASM) — not actionable
These are same-named types split across Go build tags (native vs. GOOS=js WASM stub) — the standard, intentional pattern already documented in this repo's developer-code-organization skill. No change needed.
Untyped Usages
Summary Statistics
interface{} usages in production pkg/ code: 0 (9 raw matches, all inside pkg/linters/*/testdata fixture files that intentionally demonstrate the anti-pattern for the custom linters' own golden tests)
any usages: ~2,900 across 565 files — the large majority are legitimate (Go generics constraints, and dynamic YAML/JSON frontmatter fields the authors have explicitly commented as "too dynamic to type")
Untyped constants: ~596 raw const X = <literal> declarations (mostly local, test-scoped constants); a handful of production, semantically-significant numeric constants stood out
Total flagged untyped locations: ~15 concrete call-outs across 3 categories below
Category 1: any fields that are actually always one concrete type
Impact: High — these aren't "genuinely dynamic," they're just inconsistent with identically-named fields elsewhere in the same codebase
Example 1: Target any in Azure DevOps safe-output configs
Current: Target any with tag yaml:"target,omitempty", checked only via target != nil and consumed with fmt.Sprintf("%v", target)
Actual usage: Every other Target field in the same package is a plain string — e.g. CreateCheckRunConfig.Target, UpdateDiscussions.Target, assign_milestone.go's config.Target — and safe_outputs_azure_devops_test.go:74 asserts config.UpdateWorkItems.Target equals the string "42".
Suggested fix:
Targetstring`yaml:"target,omitempty"`
with the existing != nil guards changed to != "".
Benefits: Matches the convention already used for every other Target field in pkg/workflow; removes a pointless any→string round trip through %v.
Example 2: Headers any vs. Headers map[string]string
Current: Headers any with tag json:"headers,omitempty"
Actual usage: pkg/workflow/tools_parser.go:717 builds config.Headers = make(map[string]string) for the equivalent MCP-server headers concept, and tools_types.go/observability_otlp.go consume headers as either map[string]string or string elsewhere in the same package — never as arbitrary nested data.
Note: these genuinely need to stay dynamic — they're raw passthrough of arbitrary GitHub Actions step YAML (uses:/run:/with:/etc.), confirmed by compiler_main_job_helpers.go's extractRunScriptsFromSectionYAML(data.PreSteps, ...), which re-serializes them back to YAML rather than inspecting fields.
Suggested fix: not a structural retyping, just a shared alias for clarity: type RawActionSteps []any, used consistently instead of eight independent []any fields.
Benefits: Makes the "this is intentionally untyped YAML passthrough" decision visible at the type level instead of only in a comment on one of the eight fields.
Category 3: Untyped numeric/semantic constants
Impact: Low-Medium — mostly clear from context already, but a few lack units at a glance
Examples:
// pkg/workqueue/codec.go:26-27 — byte sizes with no unit in the nameconstmaxParseBytes=80<<20constmaxLineBytes=8<<20// pkg/workqueue/branch.go:22 — retry countconstmaxRetries=5// pkg/workqueue/types.go:10 — countconstMaxClaimsPerDispatch=16// pkg/linters/largefunc/largefunc.go:19 — line countconstDefaultMaxLines=60// pkg/linters/excessivefuncparams/excessivefuncparams.go:19 — param countconstDefaultMaxParams=8// pkg/validationerror/validationerror.go:21 — string lengthconstMaxValueLength=100
Most of these already have self-documenting names (MaxParseBytes, DefaultMaxLines), so the risk is low — but introducing small semantic types would remove any residual ambiguity and is a very cheap, mechanical change if the team wants it:
Recommendation: Low priority relative to Categories 1-2. Only worth doing opportunistically (e.g. next time one of these files is touched for another reason), not as a dedicated pass.
🎯 What Should We Do About This?
Here's a suggested action plan, prioritized by impact and how cheap each fix is:
Priority 1: Quick, high-confidence fixes
Target any → Target string in pkg/workflow/safe_outputs_azure_devops.go (5 fields, 1 file, tests already prove it's always a string)
Headers any → Headers map[string]string in pkg/workflow/frontmatter_types.go and pkg/parser/import_observability.go
Extract shared WorkflowRunFlags struct for RunOptions/RunWorkflowOptions in pkg/cli
Estimated effort: ~3-4 hours total Impact: High — removes real type-assertion risk and finishes work the codebase already started elsewhere
Priority 2: Finish an existing consolidation
Embed MCPServerStatsBase in GatewayServerMetrics (pkg/cli/gateway_logs_types.go) to close the gap the team's own comment already flags
Estimated effort: 2-3 hours Impact: High — architectural consistency across the MCP metrics surface
Priority 3: Medium-value cleanups
Add ToReceipt() on workqueue.Assignment/AssignmentClaim instead of hand-duplicating fields in pkg/cli's work-queue receipt types
Embed DomainBuckets in WorkflowDomainsDetail
Introduce GuardPolicyMap and RawActionSteps named type aliases for the repeated map[string]any/[]any shapes
Estimated effort: ~5-6 hours total Impact: Medium — clarity and drift-prevention, not correctness
Priority 4: Opportunistic only
Semantic types for byte-size/count constants (ByteSize, etc.) — only worth doing when already touching those files for another reason.
Implementation Checklist
Fix Target any → string in Azure DevOps safe-output configs
Fix Headers any → map[string]string in frontmatter/observability types
Extract shared WorkflowRunFlags struct for RunOptions/RunWorkflowOptions
Embed MCPServerStatsBase in GatewayServerMetrics
Add ToReceipt() projection on workqueue Assignment/AssignmentClaim
Embed DomainBuckets in WorkflowDomainsDetail
Introduce GuardPolicyMap / RawActionSteps named type aliases
Run full test suite after each change
Leave the WASM-stub ProgressBar/SpinnerWrapper duplication alone (intentional)
Analysis Metadata
Total Go Files Analyzed: ~1,493 non-test .go files under pkg/
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
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 roughly 1,493 non-test
.gofiles underpkg/(~1,436 top-level type definitions) for duplicated type definitions and untyped (interface{}/any) usage. The good news first:interface{}is essentially gone from production code — the only 9 hits are intentional "bad pattern" fixtures insidepkg/linters/*/testdata, used by this repo's own custom linters to test themselves.anyis used ~2,900 times, and the overwhelming majority of that is legitimate (generics likefunc f[T any], and deliberately dynamic YAML/JSON frontmatter fields that are documented as "too dynamic to type").Digging past that, though, turned up a handful of worthwhile findings. On the duplication side,
RunOptionsandRunWorkflowOptionsinpkg/clishare 8 of 10 fields verbatim and could share a common embedded struct, andGatewayServerMetrics/GatewayToolMetricsinpkg/cli/gateway_logs_types.gore-spell a metrics shape the team already consolidated elsewhere intoMCPServerStatsBase(the existing code comment literally describes fixing this exact kind of drift) — this one's a straggler that didn't get the memo. On the untyped side, the clearest win isTarget anyrepeated 5 times inpkg/workflow/safe_outputs_azure_devops.go, even though every otherTargetfield in the same package (CreateCheckRun.Target,UpdateDiscussions.Target) is a plainstring, and tests confirm it's always a string at runtime — same story forHeaders anyvs.Headers map[string]stringused elsewhere for the identical concept.Full Analysis Report
Duplicated Type Definitions
Summary Statistics
struct/interfacedeclarations in non-test.gofiles underpkg/)Cluster 1:
RunOptionsvsRunWorkflowOptionsType: Near duplicate
Occurrences: 2
Impact: High — same package, 80% field overlap
Locations:
pkg/cli/run_workflow_execution.go:31—type RunOptions struct {...}pkg/cli/run_interactive.go:366—type RunWorkflowOptions struct {...}Definition Comparison:
8 of
RunWorkflowOptions' 10 fields (Verbose,EngineOverride,RepoOverride,RefOverride,AutoMergePRs,Push,DryRun,Approve) are identical name+type matches withRunOptions.Recommendation:
WorkflowRunFlagsstruct holding the 8 common fields and embed it in bothRunOptionsandRunWorkflowOptions.--timeout) only requires touching one struct instead of two.Cluster 2: MCP/Gateway server & tool call metrics
Type: Near duplicate
Occurrences: 5 related types
Impact: High — the codebase already solved this once and left one case unconsolidated
Locations:
pkg/cli/gateway_logs_types.go:85—type GatewayServerMetrics struct {...}pkg/cli/gateway_logs_types.go:97—type GatewayToolMetrics struct {...}pkg/cli/logs_models.go:218—type MCPServerStatsBase struct {...}(the shared base others already embed)pkg/cli/audit_report.go:254—type MCPServerStats struct { MCPServerStatsBase; ... }pkg/cli/audit_expanded.go:94—type MCPServerHealthDetail struct { MCPServerStatsBase; ... }Definition Comparison:
Recommendation:
MCPServerStatsandMCPServerHealthDetailalready embedMCPServerStatsBaseto avoid exactly this drift —GatewayServerMetrics(live gateway-log parsing path) is the one straggler left outside that hierarchy.MCPServerStatsBaseinGatewayServerMetrics(keeping its gateway-specific fields likeTotalDuration/FilteredCount/GuardPolicyBlockedalongside it).Cluster 3: WorkQueue
Assignment/AssignmentClaimvs CLI "receipt" typesType: Semantic duplicate
Occurrences: 4
Impact: Medium
Locations:
pkg/workqueue/types.go:218—type Assignment struct {DispatchID, RequestID, CommitID, PolicyEpoch, Pool, WorkerProfile string; Claims []AssignmentClaim}pkg/workqueue/types.go:229—type AssignmentClaim struct {Handle, ClaimID, WorkID, Work, ResultRefs}pkg/cli/logs_work_queue_current.go:30—type WorkQueueAssignmentReceipt struct {DispatchID, RequestID, CommitID, State string; Released bool; Claims []WorkQueueClaimReceipt}pkg/cli/logs_work_queue_current.go:39—type WorkQueueClaimReceipt struct {Handle, ClaimID, WorkID, State, Barrier}pkg/cli's "receipt" types hand-duplicate ~60-70% of the field names frompkg/workqueue's coreAssignment/AssignmentClaimtypes instead of projecting from them.Recommendation:
ToReceipt()method onworkqueue.Assignment/AssignmentClaim, or factor out a shared identity sub-struct (DispatchID/RequestID/CommitID/Handle/ClaimID/WorkID), instead of re-declaring the field names by hand inpkg/cli.Cluster 4: Domain allow/block list duplication outside
DomainBucketsType: Semantic duplicate
Occurrences: 3
Impact: Medium
Locations:
pkg/cli/domain_buckets.go:13—type DomainBuckets struct {AllowedDomains []string; BlockedDomains []string}(designed to be embedded)pkg/cli/domains_command.go:27—type WorkflowDomainsDetail struct {Workflow, Engine string; AllowedDomains, BlockedDomains []string}pkg/cli/access_log.go:44—type domainAnalysisWire struct {...; AllowedDomains, BlockedDomains []string}Recommendation:
DomainBucketsalready exists for exactly this purpose and is embedded elsewhere viaAnalysisBase. Embed it inWorkflowDomainsDetailtoo.domainAnalysisWirecase is a JSON-compat shim that must preserve specific legacy keys — that one is a reasonable, already-understood exception and lower priority.Cluster 5: Per-tool "Finding" wrapper structs (scanner integrations)
Type: Semantic duplicate
Occurrences: 5
Impact: Low-Medium — largely justified, worth a lighter-touch fix
Locations:
pkg/scanfindings/scanfindings.go:107—type Finding struct {RuleID, Severity, Message, File, Line, Column, Context}(the shared normalized target)pkg/cli/zizmor.go:27—type zizmorFinding struct {...}pkg/cli/poutine.go:26—type poutineFinding struct {...}pkg/cli/grype.go:54—type grypeFinding struct {...}pkg/cli/runner_guard.go:23—type runnerGuardFinding struct {...}Each
*Findingstruct mirrors a distinct third-party tool's own JSON output schema (zizmor/poutine/grype/runner-guard), so merging them into one type isn't appropriate — but all four get hand-converted intoscanfindings.Findingwith near-identicalRuleID/Severity/File/Linemapping code.Recommendation:
rawScanFindinginterface) so the four conversions toscanfindings.Findingstop being copy-pasted independently.AuditFinding(pkg/cli/audit_report.go:79) andSecurityFinding(pkg/workflow/markdown_security_scanner.go:63) are further semantic cousins of the same "severity + message + location" shape worth folding in if this is tackled.Cluster 6:
ProgressBar/SpinnerWrapper(native vs. WASM) — not actionableType: Exact duplicate (name only)
Occurrences: 4
Impact: None — false positive
Locations:
pkg/console/progress.go:31,pkg/console/progress_wasm.go:9,pkg/console/spinner.go:92,pkg/console/spinner_wasm.go:12These are same-named types split across Go build tags (native vs.
GOOS=jsWASM stub) — the standard, intentional pattern already documented in this repo'sdeveloper-code-organizationskill. No change needed.Untyped Usages
Summary Statistics
interface{}usages in productionpkg/code: 0 (9 raw matches, all insidepkg/linters/*/testdatafixture files that intentionally demonstrate the anti-pattern for the custom linters' own golden tests)anyusages: ~2,900 across 565 files — the large majority are legitimate (Go generics constraints, and dynamic YAML/JSON frontmatter fields the authors have explicitly commented as "too dynamic to type")const X = <literal>declarations (mostly local, test-scoped constants); a handful of production, semantically-significant numeric constants stood outCategory 1:
anyfields that are actually always one concrete typeImpact: High — these aren't "genuinely dynamic," they're just inconsistent with identically-named fields elsewhere in the same codebase
Example 1:
Target anyin Azure DevOps safe-output configspkg/workflow/safe_outputs_azure_devops.go:40,52,57,64,70(UpdateWorkItemConfig,CommentOnWorkItemConfig,AssignWorkItemConfig,LinkWorkItemsConfig,UploadWorkItemAttachmentConfig)Target anywith tagyaml:"target,omitempty", checked only viatarget != niland consumed withfmt.Sprintf("%v", target)Targetfield in the same package is a plainstring— e.g.CreateCheckRunConfig.Target,UpdateDiscussions.Target,assign_milestone.go'sconfig.Target— andsafe_outputs_azure_devops_test.go:74assertsconfig.UpdateWorkItems.Targetequals the string"42".!= nilguards changed to!= "".Targetfield inpkg/workflow; removes a pointlessany→stringround trip through%v.Example 2:
Headers anyvs.Headers map[string]stringpkg/workflow/frontmatter_types.go:252,289,pkg/parser/import_observability.go:18Headers anywith tagjson:"headers,omitempty"pkg/workflow/tools_parser.go:717buildsconfig.Headers = make(map[string]string)for the equivalent MCP-server headers concept, andtools_types.go/observability_otlp.goconsume headers as eithermap[string]stringorstringelsewhere in the same package — never as arbitrary nested data.Headers map[string]string(matchingpkg/workflow/tools_types.go's existing header config).Category 2: Same untyped shape copy-pasted across structs (should be a named type)
Impact: Medium — not wrong, but duplicated
map[string]any/[]anyliterals hide that these are the same conceptExample:
GuardPolicies map[string]anypkg/workflow/mcp_renderer_types.go:21,94,131,pkg/workflow/mcp_config_types.go:54,pkg/workflow/tools_types.go:530map[string]anyGuardPolicies GuardPolicyMapeverywhere instead of re-typing the map literal.Example:
[]anyfor GitHub Actions step passthroughpkg/workflow/frontmatter_types.go:413-416(PreSteps,Steps,PreAgentSteps,PostSteps),pkg/workflow/threat_detection_config.go:12-13,pkg/workflow/safe_jobs.go:25,pkg/workflow/safe_outputs_config_types.go:137uses:/run:/with:/etc.), confirmed bycompiler_main_job_helpers.go'sextractRunScriptsFromSectionYAML(data.PreSteps, ...), which re-serializes them back to YAML rather than inspecting fields.type RawActionSteps []any, used consistently instead of eight independent[]anyfields.Category 3: Untyped numeric/semantic constants
Impact: Low-Medium — mostly clear from context already, but a few lack units at a glance
Examples:
Most of these already have self-documenting names (
MaxParseBytes,DefaultMaxLines), so the risk is low — but introducing small semantic types would remove any residual ambiguity and is a very cheap, mechanical change if the team wants it:Recommendation: Low priority relative to Categories 1-2. Only worth doing opportunistically (e.g. next time one of these files is touched for another reason), not as a dedicated pass.
🎯 What Should We Do About This?
Here's a suggested action plan, prioritized by impact and how cheap each fix is:
Priority 1: Quick, high-confidence fixes
Target any→Target stringinpkg/workflow/safe_outputs_azure_devops.go(5 fields, 1 file, tests already prove it's always a string)Headers any→Headers map[string]stringinpkg/workflow/frontmatter_types.goandpkg/parser/import_observability.goWorkflowRunFlagsstruct forRunOptions/RunWorkflowOptionsinpkg/cliEstimated effort: ~3-4 hours total
Impact: High — removes real type-assertion risk and finishes work the codebase already started elsewhere
Priority 2: Finish an existing consolidation
MCPServerStatsBaseinGatewayServerMetrics(pkg/cli/gateway_logs_types.go) to close the gap the team's own comment already flagsEstimated effort: 2-3 hours
Impact: High — architectural consistency across the MCP metrics surface
Priority 3: Medium-value cleanups
ToReceipt()onworkqueue.Assignment/AssignmentClaiminstead of hand-duplicating fields inpkg/cli's work-queue receipt typesDomainBucketsinWorkflowDomainsDetailGuardPolicyMapandRawActionStepsnamed type aliases for the repeatedmap[string]any/[]anyshapesEstimated effort: ~5-6 hours total
Impact: Medium — clarity and drift-prevention, not correctness
Priority 4: Opportunistic only
ByteSize, etc.) — only worth doing when already touching those files for another reason.Implementation Checklist
Target any→stringin Azure DevOps safe-output configsHeaders any→map[string]stringin frontmatter/observability typesWorkflowRunFlagsstruct forRunOptions/RunWorkflowOptionsMCPServerStatsBaseinGatewayServerMetricsToReceipt()projection on workqueueAssignment/AssignmentClaimDomainBucketsinWorkflowDomainsDetailGuardPolicyMap/RawActionStepsnamed type aliasesProgressBar/SpinnerWrapperduplication alone (intentional)Analysis Metadata
.gofiles underpkg/All reactions