Skip to content

refactor(workflow,cli): eliminate 680 function-length lint findings via targeted helper extraction#47061

Merged
pelikhan merged 5 commits into
mainfrom
copilot/lint-monster-function-length-refactor-again
Jul 21, 2026
Merged

refactor(workflow,cli): eliminate 680 function-length lint findings via targeted helper extraction#47061
pelikhan merged 5 commits into
mainfrom
copilot/lint-monster-function-length-refactor-again

Conversation

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

make golint-custom reported 680 long-function findings (>60 lines) across pkg/workflow and pkg/cli, with the worst offenders ranging from 200–961 lines. This extracts cohesive logical blocks into focused private helpers, reducing all findings to zero while preserving behavior and public interfaces.

Changes

pkg/workflow/maintenance_workflow_yaml.go + new maintenance_workflow_yaml_jobs.go

  • buildMaintenanceWorkflowYAML (961 lines) → thin orchestrator delegating to per-job builders
  • New companion file holds buildMaintenanceActivityReportJob, buildMaintenanceForecastReportJob, and their sub-helpers

pkg/workflow/frontmatter_on_section_cleanup.go

  • commentOutProcessedFieldsInOnSection (697 lines) split into focused per-field helpers

pkg/workflow/side_repo_maintenance.go

  • generateSideRepoMaintenanceWorkflow (447 lines) decomposed into stage-specific builders

pkg/workflow/mcp_config_custom.go

  • getMCPConfig (209 lines) → validateMCPKnownProperties, resolveMCPType, extractMCPStdioFields, extractMCPHTTPFields, postProcessMCPConfig

pkg/workflow/repo_memory.go

  • buildPushRepoMemoryJob (204 lines) → buildPushRepoMemorySetupAndCheckoutSteps, buildPushRepoMemoryDownloadSteps, buildSinglePushRepoMemoryStep, buildPushRepoMemoryJobCondition, buildPushRepoMemoryOutputs
  • Fixed pre-existing bug: BranchName was not correctly derived from entry ID because newDefaultRepoMemoryEntry pre-populated it, causing applyRepoMemoryDefaultBranch to always exit early. Now clears BranchName before branch derivation when no explicit branch-name was set.

pkg/cli/run_workflow_execution.go

  • RunWorkflowsOnGitHub (135 lines) → validateWorkflowsForRun, executeAllWorkflowsOnce, wrapRunWithJSONOutput

pkg/cli/logs_command.go

  • NewLogsCommand (376 lines) decomposed into flag-group and handler helpers

…pkg/workflow and pkg/cli

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor long functions in pkg/workflow and pkg/cli refactor(workflow,cli): eliminate 680 function-length lint findings via targeted helper extraction Jul 21, 2026
Copilot AI requested a review from pelikhan July 21, 2026 11:48
@pelikhan
pelikhan marked this pull request as ready for review July 21, 2026 11:56
Copilot AI review requested due to automatic review settings July 21, 2026 11:56
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors large workflow and CLI functions into focused helpers while preserving public interfaces.

Changes:

  • Splits workflow YAML generation and configuration parsing into helpers.
  • Decomposes CLI workflow execution and logs command handling.
  • Corrects repo-memory branch derivation.
Show a summary per file
File Description
pkg/workflow/side_repo_maintenance.go Decomposes side-repository workflow generation.
pkg/workflow/repo_memory.go Extracts parsing and job-building helpers.
pkg/workflow/maintenance_workflow_yaml.go Converts YAML generation into an orchestrator.
pkg/workflow/maintenance_workflow_yaml_jobs.go Adds maintenance job builders.
pkg/workflow/engine.go Decomposes engine configuration parsing.
pkg/cli/run_workflow_execution.go Extracts workflow execution stages.
pkg/cli/logs_command.go Extracts logs command setup and handling.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread pkg/workflow/side_repo_maintenance.go Outdated
Comment thread pkg/cli/run_workflow_execution.go Outdated
}
func validateLocalWorkflowForRun(workflowIdOrName string, inputs []string) error {
executionLog.Printf("Validating local workflow: %s", workflowIdOrName)
workflowFile, err := resolveWorkflowFile(workflowIdOrName, false)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: refactor(workflow,cli) — helper extraction

Overall the refactor is a solid structural improvement that achieves its lint goal. Two issues were found in the visible diff portion.

Blocking

  1. Verbose flag regression (pkg/cli/run_workflow_execution.go line 161) — resolveWorkflowFile is now called with a hardcoded false instead of opts.Verbose. Verbose-mode diagnostics during local workflow file resolution are silently dropped. Original code passed opts.Verbose correctly.

  2. Double-error return anti-pattern (executeWorkflowRun line 392) — returning (string, *WorkflowRunInfo, error, error) is non-idiomatic Go and makes call-site reasoning error-prone. Prefer a result struct or encode the non-fatal fetch error inside *WorkflowRunInfo.

Non-blocking observations
  • handleWorkflowRunInfo unused output parameter: the output string argument is accepted but never read inside the function body. Consider removing it.
  • The runInfo != nil nil-guard added to handleWorkflowRunInfo is a correctness improvement (the old code would dereference a nil pointer if getLatestWorkflowRunWithRetry returned (nil, nil)).

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 57.1 AIC · ⌖ 4.62 AIC · ⊞ 4.9K

Comment thread pkg/cli/run_workflow_execution.go Outdated
}
func validateLocalWorkflowForRun(workflowIdOrName string, inputs []string) error {
executionLog.Printf("Validating local workflow: %s", workflowIdOrName)
workflowFile, err := resolveWorkflowFile(workflowIdOrName, false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verbose flag silently dropped in validateLocalWorkflowForRun

resolveWorkflowFile is called with a hardcoded false instead of forwarding the caller's verbose flag. The original code passed opts.Verbose, so any verbose-mode diagnostics printed during workflow file resolution are now silently suppressed when this extraction path is taken.

// Before (correct)
workflowFile, err := resolveWorkflowFile(workflowIdOrName, opts.Verbose)

// After (drops verbose)
workflowFile, err := resolveWorkflowFile(workflowIdOrName, false)

Fix: add a verbose bool parameter to validateLocalWorkflowForRun (and its callers) and forward it here.

@copilot please address this.

Comment thread pkg/cli/run_workflow_execution.go Outdated

// Capture both stdout and stderr
stdout, err := cmd.Output()
func executeWorkflowRun(ctx context.Context, lockFileName string, args []string, opts RunOptions) (string, *WorkflowRunInfo, error, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unconventional double-error return signature

executeWorkflowRun returns (string, *WorkflowRunInfo, error, error) — two separate error values in the same return list. This is a Go anti-pattern that is easy to misread at the call site (callers must remember which error is fatal and which is a non-fatal run-info error).

Consider encoding the distinction with a named struct or by having the function return only the fatal error and communicating the non-fatal run-info error through the *WorkflowRunInfo itself (e.g., add a FetchErr error field). This keeps the public surface idiomatic and eliminates confusion about argument order.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (3,462 new lines in pkg/workflow and pkg/cli) but did not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/47061-function-length-lint-compliance-via-helper-extraction.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch at docs/adr/47061-function-length-lint-compliance-via-helper-extraction.md — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-47061: Function-Length Lint Compliance via Helper Extraction

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 41.3 AIC · ⌖ 9.92 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — one confirmed behavior regression

This refactor is mostly clean, but contains one correctness regression that must be fixed before merge: opts.Verbose is silently dropped when calling resolveWorkflowFile inside the new validateLocalWorkflowForRun helper (hard-coded false instead of passing opts.Verbose). Users running gh aw run --verbose will no longer get verbose output for local workflow resolution diagnostics.

Other observations (non-blocking)
  • validateWorkflowsForRun loop still calls resolveWorkflowFile(workflowName, opts.Verbose) correctly (line 1536), so the regression is isolated to the single-workflow path.
  • Dry-run verbose output format changed from "gh workflow run ..." to "gh " + strings.Join(args, " ") — functionally equivalent but slightly different surface text.
  • loadStdinLogsOptions / StdinLogsOptions does not receive cacheBefore — but this was the same in the original code, not a regression introduced here.
  • The structural decomposition in engine.go, repo_memory.go, and maintenance_workflow_yaml_jobs.go is well-executed with no observable behaviour changes.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 125.7 AIC · ⌖ 4.57 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/run_workflow_execution.go:898

Behavior regression: opts.Verbose silently dropped in local workflow validation. The refactored helper hard-codes false, breaking verbose output for users of gh aw run --verbose.

<details>
<summary>💡 Fix</summary>

The original call was:

workflowFile, err := resolveWorkflowFile(workflowIdOrName, opts.Verbose)

The refactored validateLocalWorkflowForRun hard-codes false instead:

workflowFile, err := resolveWorkflowFile(workflowIdOrName, false)

Fix: pass `verb…

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on two correctness issues and one missing regression test.

📋 Key Themes & Highlights

Issues Found

  1. Dropped error context (run_workflow_execution.go:154) — validateWorkflowForRun silently drops the "failed to validate remote workflow: " prefix on the remote-validation error path. This is a behavioral regression in user-visible error messages.

  2. MaxToolDenials guard removed for inline engine path (engine.go:311) — the original code guarded top-level max-tool-denials assignment with if topLevelMaxToolDenials != "". The new applyInlineEngineFields unconditionally overwrites it. Verify this is intentional.

  3. Bug fix has no regression test (repo_memory.go:196) — the BranchName derivation bug fix (clearing pre-populated BranchName so applyRepoMemoryDefaultBranch re-derives from the custom ID) has no unit test. The existing integration tests always supply explicit branch-name: fields, so the fixed path is untested.

Positive Highlights

  • ✅ Excellent extraction discipline: all 680 lint findings eliminated with no public API changes
  • applyReferencedEngineFields is a clean 12-helper orchestrator — easy to scan
  • ✅ Companion file pattern (maintenance_workflow_yaml_jobs.go) keeps individual file sizes manageable
  • ✅ Bug fix is well-described in the PR body and the fix logic is clear
  • workflowRunPreparation struct groups related prepareWorkflowRun state cleanly

Note on diff truncation

The pre-fetched diff was capped at 3000 lines (2 of 9 files visible). Findings from pkg/workflow/engine.go, repo_memory.go, and run_workflow_execution.go are based on reading the current file state in the repo. Files not visible in the diff (frontmatter_on_section_cleanup.go, maintenance_workflow_yaml*.go, mcp_config_custom.go, side_repo_maintenance.go) were not reviewed at line level.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 103.7 AIC · ⌖ 5.15 AIC · ⊞ 6.6K
Comment /matt to run again

Comment thread pkg/cli/run_workflow_execution.go Outdated
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Consider pushing your changes before running the workflow"))
}
}
return validateRemoteWorkflow(workflowIdOrName, opts.RepoOverride, opts.Verbose)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Dropped error context: validateWorkflowForRun now returns validateRemoteWorkflow's error directly, silently removing the "failed to validate remote workflow: " prefix that existed before this refactor.

💡 Restore the wrapper

Before:

if err := validateRemoteWorkflow(workflowIdOrName, opts.RepoOverride, opts.Verbose); err != nil {
    return fmt.Errorf("failed to validate remote workflow: %w", err)
}

After (current):

return validateRemoteWorkflow(workflowIdOrName, opts.RepoOverride, opts.Verbose)

The refactor inadvertently stripped the context message. Restore it:

if err := validateRemoteWorkflow(workflowIdOrName, opts.RepoOverride, opts.Verbose); err != nil {
    return fmt.Errorf("failed to validate remote workflow: %w", err)
}
return nil

@copilot please address this.

Comment thread pkg/workflow/engine.go
}
}
}
func applyInlineEngineFields(config *EngineConfig, engineObj map[string]any, topLevel engineTopLevelConfig) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] applyInlineEngineFields unconditionally sets MaxToolDenials = topLevel.maxToolDenials (even when it is ""), whereas the original code guarded this with if topLevelMaxToolDenials != "" { ... }. This is a behavior change: if an inline engine had MaxToolDenials set through some other path, this now overwrites it with the empty top-level value.

💡 Original guard vs current code

Original (HEAD~1):

if topLevelMaxToolDenials != "" {
    config.MaxToolDenials = topLevelMaxToolDenials
}

Current refactor (line 315):

config.MaxToolDenials = topLevel.maxToolDenials

This is consistent with how applyEngineTopLevelOverrides handles it for the referenced-engine path (applyReferencedEngineFields also unconditionally sets it via applyEngineTurnFields). But the inline path previously guarded it. Verify this is intentional and add a test if so.

@copilot please address this.

entry.ID = "default"
}
if !explicitBranchName {
entry.BranchName = "" // let applyRepoMemoryDefaultBranch derive from ID

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The bug fix (entry.BranchName = "" to let applyRepoMemoryDefaultBranch re-derive from the entry ID) has no dedicated unit test. The existing integration tests use explicit branch-name: fields, so they don't exercise the fixed code path.

💡 Missing test case

Add a unit test for parseRepoMemoryEntry with a custom id and no branch-name, verifying the branch name is derived from the custom ID rather than the workflow ID:

func TestParseRepoMemoryEntry_CustomIDDerivesBranchName(t *testing.T) {
    entry, err := parseRepoMemoryEntry(
        map[string]any{"id": "my-agent"},
        "my-workflow", "memory", false,
    )
    require.NoError(t, err)
    // Before the fix, this would produce memory/my-workflow instead of memory/my-agent
    assert.Equal(t, "memory/my-agent", entry.BranchName)
}

This pins the bug fix against regression.

@copilot please address this.

Comment thread pkg/workflow/engine.go
config.UserAgent = userAgentStr
}
}
func applyReferencedEngineFields(config *EngineConfig, engineObj map[string]any, topLevel engineTopLevelConfig) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] applyReferencedEngineFields calls 12 sub-helpers sequentially but does not document the ordering contract. Some helpers likely depend on prior state (e.g., applyEngineProviderFields may need to run before applyEngineBooleanFields which guards on IsInlineDefinition). A brief comment listing the ordering rationale would make this safe to extend.

💡 Suggested doc comment
// applyReferencedEngineFields applies all optional fields from a referenced engine object.
// Order matters: provider fields must precede boolean fields (IsInlineDefinition guard),
// and top-level overrides must run last to ensure they take precedence.
func applyReferencedEngineFields(...) {

@copilot please address this.

Comment thread pkg/workflow/engine.go
config.LLMProvider = strings.ToLower(strings.TrimSpace(providerStr))
}
}
func extractInlineEngineConfig(runtime any, engineObj map[string]any, topLevel engineTopLevelConfig) (string, *EngineConfig, string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] extractObjectEngineConfig returns 3 values (engineID, config, model) from both branches, but extractInlineEngineConfig sets config.ID from runtimeObj["id"] while extractReferencedEngineConfig returns it as the first value and sets config.ID. The two paths have different conventions for how the engine ID surfaces — this asymmetry is subtle and worth documenting or unifying.

💡 The asymmetry

Inline path: config.ID set inside extractInlineEngineConfig, returned string is also config.ID.
Referenced path: config.ID set inside extractReferencedEngineConfig, returned string is also config.ID.

They're actually symmetric, but extractInlineEngineConfig additionally derives ID from runtimeObj["id"] (a nested key) while extractReferencedEngineConfig reads it from engineObj["id"] (a top-level key). A brief comment explaining the key path difference at the extractObjectEngineConfig decision point would aid future maintainers.

@copilot please address this.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Ran the PR finisher pass and pushed 097e555.

Local validation passed (make fmt, make lint, make test-unit, make test). CI on this new HEAD is still stale until a maintainer re-triggers it.

@github-actions

Copy link
Copy Markdown
Contributor

Triage

Field Value
Category refactor
Risk medium
Score 38/100 (impact:20 urgency:10 quality:8)
Action batch_review
Batch pr-batch:lint-refactor

Rationale: Large mechanical refactor (3513 additions / 4109 deletions across 10 files) targeting lint compliance. No CI runs recorded yet. Behavior-preserving by design but scale warrants batch human review.

Generated by 🔧 PR Triage Agent · sonnet46 45.9 AIC · ⌖ 5.48 AIC · ⊞ 5.6K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

/matt

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design, /tdd, and /diagnosing-bugs — two actionable comments, no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Missing regression test for the bug fix: The applyRepoMemoryDefaultBranch early-exit fix has no dedicated test; the bug can silently recur.
  • Global stderr mutation in test: TestValidateLocalWorkflowForRun_PropagatesVerbose swaps os.Stderr without a t.Cleanup guard — leaks state if the test panics.

Positive Highlights

  • ✅ Excellent structural decomposition — buildMaintenanceWorkflowYAML (961 → thin orchestrator) and ExtractEngineConfig (monolith → layered helpers) are substantially easier to read.
  • parseNonEmptyAuthDefinition is a clean, well-named extraction that makes the empty-auth guard intent explicit.
  • ✅ New companion file maintenance_workflow_yaml_jobs.go keeps job-builder logic cohesive and discoverable.
  • ✅ ADR-47061 documents the decision and trade-offs honestly — the negatives section is particularly useful.
  • logsCommandExampleTemplate with fmt.Sprintf replaces fragile string concatenation — a nice readability win.

Diff Coverage Note

The patch was capped at 3000 lines; repo_memory.go, maintenance_workflow_yaml_jobs.go, and side_repo_maintenance.go were reviewed via direct file inspection rather than the diff.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 81.2 AIC · ⌖ 7.39 AIC · ⊞ 6.6K
Comment /matt to run again

@github-actions

Copy link
Copy Markdown
Contributor

Excellent work! 🎯 This refactoring systematically eliminates 680 long-function lint findings across pkg/workflow and pkg/cli by extracting cohesive logical blocks into focused private helpers. The approach is surgical—decomposing monolithic functions (961 lines → thin orchestrator, 697 lines → per-field helpers) while preserving public interfaces and behavior.

Key strengths:

  • Focused — single objective: lint compliance via helper extraction
  • Well-tested — test files updated with +53 new assertions covering the refactored paths
  • Documented — ADR added explaining the strategy and trade-offs
  • Bug fix included — pre-existing BranchName derivation issue in repo_memory.go now corrected
  • Clear changelog — file-by-file breakdown in the PR body makes review easier

This is ready for merge. 🚀

Generated by ✅ Contribution Check · sonnet46 54.7 AIC · ⌖ 7.29 AIC · ⊞ 6.2K ·

@pelikhan
pelikhan merged commit 46f2a2b into main Jul 21, 2026
6 checks passed
@pelikhan
pelikhan deleted the copilot/lint-monster-function-length-refactor-again branch July 21, 2026 13:33
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[lint-monster] [Code Quality] Shared function-length refactoring backlog for pkg/workflow and pkg/cli (680 findings)

3 participants