Overview
The file pkg/workflow/safe_outputs_handler_registry.go has grown to 1091 lines, making it difficult to maintain and test. It centers on a single giant handlerRegistry map literal (spanning lines 57-1091) containing 41 handler builder closures, plus a small set of GitHub-token resolution helper functions at the top of the file. This task involves refactoring it into smaller, focused files grouped by functional domain, with dedicated test coverage.
Current State
- File:
pkg/workflow/safe_outputs_handler_registry.go
- Size: 1091 lines
- Test Coverage: No test file found (
safe_outputs_handler_registry_test.go does not exist)
- Complexity: One monolithic
var handlerRegistry = map[string]handlerBuilder{...} literal contains 41 handler-builder closures (one per safe-output type), each independently reading its own cfg.<X> field and building a config map via the handlerConfigBuilder fluent API. There is no shared state between most closures beyond cfg and a handful of shared helpers (resolveHandlerGitHubToken, getEffectiveFooterForTemplatable, etc.), making the map trivially splittable by domain without touching call sites.
Full File Analysis
Detailed Breakdown
Helper functions (lines 1-56) - token resolution, kept as-is or moved to a small shared file:
resolveHandlerGitHubToken (L16)
resolveApproveWorkflowRunGitHubToken (L27)
resolveHandlerGitHubTokenWithStepID (L37)
handlerSupportsPerHandlerGitHubAppToken (L45)
handlerRegistry map entries (L57-1091), grouped by domain:
- Issues:
create_issue (L58), close_issue (L135), update_issue (L380), link_sub_issue (L435), set_issue_type (L1048), set_issue_field (L1071)
- Discussions:
create_discussion (L110), close_discussion (L155), update_discussion (L408)
- Labels/metadata:
add_labels (L172), remove_labels (L199), replace_label (L217), assign_milestone (L264)
- Comments:
add_comment (L88), hide_comment (L718), create_pull_request_review_comment (L464), reply_to_pull_request_review_comment (L500)
- Pull requests:
create_pull_request (L533, largest single entry ~86 lines), push_to_pull_request_branch (L619), update_pull_request (L667), merge_pull_request (L687), close_pull_request (L702), mark_pull_request_as_ready_for_review (L280), add_reviewer (L248), dismiss_pull_request_review (L315), submit_pull_request_review (L482), resolve_pull_request_review_thread (L517)
- Workflow/CI:
approve_workflow_run (L296), create_code_scanning_alert (L331), create_check_run (L345), dispatch_workflow (L733), dispatch_repository (L760), call_workflow (L780), autofix_code_scanning_alert (L948), upload_code_coverage (L937)
- Assets/artifacts:
upload_asset (L890), upload_artifact (L904)
- Projects:
create_project (L961), update_project (L980), create_project_status_update (L1036)
- Agents/assignment:
create_agent_session (L366), assign_to_agent (L866), assign_to_user (L1000), unassign_from_user (L1018)
- Diagnostics/misc:
missing_tool (L797), missing_data (L808), noop (L819), report_incomplete (L830), create_report_incomplete_issue (L841), update_release (L452)
No duplicate logic was found beyond the common newHandlerConfigBuilder()...Build() pattern, which is already correctly factored into the shared handlerConfigBuilder. The main complexity driver is sheer entry count in one map literal rather than algorithmic complexity.
Refactoring Strategy
Proposed File Splits
Split the single handlerRegistry map into several map[string]handlerBuilder partial maps defined in domain-specific files, merged into the final handlerRegistry via a small init/merge step (or by keeping one var handlerRegistry = mergeHandlerMaps(...) in the original file).
-
safe_outputs_handler_registry_issues.go
- Functions:
create_issue, close_issue, update_issue, link_sub_issue, set_issue_type, set_issue_field, add_labels, remove_labels, replace_label, assign_milestone
- Responsibility: Issue lifecycle and metadata handler builders
- Estimated LOC: ~330
-
safe_outputs_handler_registry_discussions.go
- Functions:
create_discussion, close_discussion, update_discussion
- Responsibility: Discussion lifecycle handler builders
- Estimated LOC: ~60
-
safe_outputs_handler_registry_pull_requests.go
- Functions:
create_pull_request, push_to_pull_request_branch, update_pull_request, merge_pull_request, close_pull_request, mark_pull_request_as_ready_for_review, add_reviewer, dismiss_pull_request_review, submit_pull_request_review, resolve_pull_request_review_thread, create_pull_request_review_comment, reply_to_pull_request_review_comment
- Responsibility: Pull request lifecycle and review handler builders
- Estimated LOC: ~350
-
safe_outputs_handler_registry_workflow.go
- Functions:
approve_workflow_run, create_code_scanning_alert, create_check_run, dispatch_workflow, dispatch_repository, call_workflow, autofix_code_scanning_alert, upload_code_coverage, upload_asset, upload_artifact
- Responsibility: CI/workflow-triggering and artifact/coverage handler builders
- Estimated LOC: ~180
-
safe_outputs_handler_registry_projects.go
- Functions:
create_project, update_project, create_project_status_update, assign_to_agent, assign_to_user, unassign_from_user, create_agent_session
- Responsibility: Project board and agent-assignment handler builders
- Estimated LOC: ~130
-
safe_outputs_handler_registry_misc.go
- Functions:
add_comment, hide_comment, update_release, missing_tool, missing_data, noop, report_incomplete, create_report_incomplete_issue
- Responsibility: Comment and diagnostic/no-op handler builders
- Estimated LOC: ~90
The base safe_outputs_handler_registry.go retains the token-resolution helpers (resolveHandlerGitHubToken, resolveApproveWorkflowRunGitHubToken, resolveHandlerGitHubTokenWithStepID, handlerSupportsPerHandlerGitHubAppToken) and a small combined var handlerRegistry = map[string]handlerBuilder{...} assembled from the domain maps (e.g. via maps.Copy from each partial map, or literal composition in an init()), keeping getSafeOutputHandlerByKey and other lookup call sites unchanged.
Shared Utilities
No new shared utility file is required; the existing handlerConfigBuilder already centralizes common map-building logic and should continue to be reused as-is by all split files.
Interface Abstractions
None needed - handlerBuilder is already a simple function type (func(cfg *SafeOutputsConfig) map[string]any); the split is purely file-organizational and does not require new interfaces.
Test Coverage Plan
Add dedicated test files, since none currently exist for this registry:
-
safe_outputs_handler_registry_issues_test.go
- Test cases: each issue-domain handler builder returns nil when its config field is nil; returns expected map when populated (spot-check
max, labels, github-token resolution via resolveHandlerGitHubToken with and without GitHubApp)
- Target coverage: >80%
-
safe_outputs_handler_registry_pull_requests_test.go
- Test cases:
create_pull_request full option matrix (largest entry), push_to_pull_request_branch nil/non-nil, review-comment handlers with github-token resolution
- Target coverage: >80%
-
safe_outputs_handler_registry_workflow_test.go
- Test cases:
approve_workflow_run token fallback behavior (must never silently use the default Actions token, per existing code comment), dispatch_workflow/dispatch_repository/call_workflow nil/non-nil paths
- Target coverage: >80%
-
safe_outputs_handler_registry_projects_test.go and safe_outputs_handler_registry_misc_test.go
- Test cases: nil/non-nil config paths for each remaining handler, footer/normalize_closing_keywords propagation where applicable
- Target coverage: >80%
Also add/keep a small safe_outputs_handler_registry_test.go covering the retained helpers (resolveHandlerGitHubToken, resolveApproveWorkflowRunGitHubToken, handlerSupportsPerHandlerGitHubAppToken) and a sanity test that handlerRegistry contains exactly the expected 41 keys after the split (guards against accidental entry loss during the refactor).
Implementation Guidelines
- Preserve Behavior: Ensure all existing functionality works identically
- Maintain Exports: Keep public API unchanged (exported functions/types)
- Add Tests First: Write tests for each new file before refactoring
- Incremental Changes: Split one module at a time
- Run Tests Frequently: Verify
make test-unit passes after each split
- Update Imports: Ensure all import paths are correct
- Document Changes: Add comments explaining module boundaries
Acceptance Criteria
Additional Context
- Repository Guidelines: Follow patterns in
.github/agents/developer.instructions.agent.md
- Code Organization: Prefer many small files grouped by functionality
- Testing: Match existing test patterns in
pkg/workflow/*_test.go
Priority: Medium
Effort: Medium (mechanical map-literal split across 41 entries, no behavior change; new test files are the bulk of the work)
Expected Impact: Improved maintainability, easier testing, reduced complexity in pkg/workflow
Generated by 🧹 Daily File Diet · copilot · auto · 132.7 AIC · ⌖ 4.96 AIC · ⊞ 10.2K · ◷
Overview
The file
pkg/workflow/safe_outputs_handler_registry.gohas grown to 1091 lines, making it difficult to maintain and test. It centers on a single gianthandlerRegistrymap literal (spanning lines 57-1091) containing 41 handler builder closures, plus a small set of GitHub-token resolution helper functions at the top of the file. This task involves refactoring it into smaller, focused files grouped by functional domain, with dedicated test coverage.Current State
pkg/workflow/safe_outputs_handler_registry.gosafe_outputs_handler_registry_test.godoes not exist)var handlerRegistry = map[string]handlerBuilder{...}literal contains 41 handler-builder closures (one per safe-output type), each independently reading its owncfg.<X>field and building a config map via thehandlerConfigBuilderfluent API. There is no shared state between most closures beyondcfgand a handful of shared helpers (resolveHandlerGitHubToken,getEffectiveFooterForTemplatable, etc.), making the map trivially splittable by domain without touching call sites.Full File Analysis
Detailed Breakdown
Helper functions (lines 1-56) - token resolution, kept as-is or moved to a small shared file:
resolveHandlerGitHubToken(L16)resolveApproveWorkflowRunGitHubToken(L27)resolveHandlerGitHubTokenWithStepID(L37)handlerSupportsPerHandlerGitHubAppToken(L45)handlerRegistrymap entries (L57-1091), grouped by domain:create_issue(L58),close_issue(L135),update_issue(L380),link_sub_issue(L435),set_issue_type(L1048),set_issue_field(L1071)create_discussion(L110),close_discussion(L155),update_discussion(L408)add_labels(L172),remove_labels(L199),replace_label(L217),assign_milestone(L264)add_comment(L88),hide_comment(L718),create_pull_request_review_comment(L464),reply_to_pull_request_review_comment(L500)create_pull_request(L533, largest single entry ~86 lines),push_to_pull_request_branch(L619),update_pull_request(L667),merge_pull_request(L687),close_pull_request(L702),mark_pull_request_as_ready_for_review(L280),add_reviewer(L248),dismiss_pull_request_review(L315),submit_pull_request_review(L482),resolve_pull_request_review_thread(L517)approve_workflow_run(L296),create_code_scanning_alert(L331),create_check_run(L345),dispatch_workflow(L733),dispatch_repository(L760),call_workflow(L780),autofix_code_scanning_alert(L948),upload_code_coverage(L937)upload_asset(L890),upload_artifact(L904)create_project(L961),update_project(L980),create_project_status_update(L1036)create_agent_session(L366),assign_to_agent(L866),assign_to_user(L1000),unassign_from_user(L1018)missing_tool(L797),missing_data(L808),noop(L819),report_incomplete(L830),create_report_incomplete_issue(L841),update_release(L452)No duplicate logic was found beyond the common
newHandlerConfigBuilder()...Build()pattern, which is already correctly factored into the sharedhandlerConfigBuilder. The main complexity driver is sheer entry count in one map literal rather than algorithmic complexity.Refactoring Strategy
Proposed File Splits
Split the single
handlerRegistrymap into severalmap[string]handlerBuilderpartial maps defined in domain-specific files, merged into the finalhandlerRegistryvia a small init/merge step (or by keeping onevar handlerRegistry = mergeHandlerMaps(...)in the original file).safe_outputs_handler_registry_issues.gocreate_issue,close_issue,update_issue,link_sub_issue,set_issue_type,set_issue_field,add_labels,remove_labels,replace_label,assign_milestonesafe_outputs_handler_registry_discussions.gocreate_discussion,close_discussion,update_discussionsafe_outputs_handler_registry_pull_requests.gocreate_pull_request,push_to_pull_request_branch,update_pull_request,merge_pull_request,close_pull_request,mark_pull_request_as_ready_for_review,add_reviewer,dismiss_pull_request_review,submit_pull_request_review,resolve_pull_request_review_thread,create_pull_request_review_comment,reply_to_pull_request_review_commentsafe_outputs_handler_registry_workflow.goapprove_workflow_run,create_code_scanning_alert,create_check_run,dispatch_workflow,dispatch_repository,call_workflow,autofix_code_scanning_alert,upload_code_coverage,upload_asset,upload_artifactsafe_outputs_handler_registry_projects.gocreate_project,update_project,create_project_status_update,assign_to_agent,assign_to_user,unassign_from_user,create_agent_sessionsafe_outputs_handler_registry_misc.goadd_comment,hide_comment,update_release,missing_tool,missing_data,noop,report_incomplete,create_report_incomplete_issueThe base
safe_outputs_handler_registry.goretains the token-resolution helpers (resolveHandlerGitHubToken,resolveApproveWorkflowRunGitHubToken,resolveHandlerGitHubTokenWithStepID,handlerSupportsPerHandlerGitHubAppToken) and a small combinedvar handlerRegistry = map[string]handlerBuilder{...}assembled from the domain maps (e.g. viamaps.Copyfrom each partial map, or literal composition in aninit()), keepinggetSafeOutputHandlerByKeyand other lookup call sites unchanged.Shared Utilities
No new shared utility file is required; the existing
handlerConfigBuilderalready centralizes common map-building logic and should continue to be reused as-is by all split files.Interface Abstractions
None needed -
handlerBuilderis already a simple function type (func(cfg *SafeOutputsConfig) map[string]any); the split is purely file-organizational and does not require new interfaces.Test Coverage Plan
Add dedicated test files, since none currently exist for this registry:
safe_outputs_handler_registry_issues_test.gomax,labels,github-tokenresolution viaresolveHandlerGitHubTokenwith and withoutGitHubApp)safe_outputs_handler_registry_pull_requests_test.gocreate_pull_requestfull option matrix (largest entry),push_to_pull_request_branchnil/non-nil, review-comment handlers withgithub-tokenresolutionsafe_outputs_handler_registry_workflow_test.goapprove_workflow_runtoken fallback behavior (must never silently use the default Actions token, per existing code comment),dispatch_workflow/dispatch_repository/call_workflownil/non-nil pathssafe_outputs_handler_registry_projects_test.goandsafe_outputs_handler_registry_misc_test.goAlso add/keep a small
safe_outputs_handler_registry_test.gocovering the retained helpers (resolveHandlerGitHubToken,resolveApproveWorkflowRunGitHubToken,handlerSupportsPerHandlerGitHubAppToken) and a sanity test thathandlerRegistrycontains exactly the expected 41 keys after the split (guards against accidental entry loss during the refactor).Implementation Guidelines
make test-unitpasses after each splitAcceptance Criteria
make test-unit)make lint)make build)Additional Context
.github/agents/developer.instructions.agent.mdpkg/workflow/*_test.goPriority: Medium
Effort: Medium (mechanical map-literal split across 41 entries, no behavior change; new test files are the bulk of the work)
Expected Impact: Improved maintainability, easier testing, reduced complexity in
pkg/workflow