Skip to content

refactor(workflow): split 420-line buildMainJob into focused helpers#45333

Merged
pelikhan merged 5 commits into
mainfrom
copilot/hippo-refactor-top-function-length
Jul 14, 2026
Merged

refactor(workflow): split 420-line buildMainJob into focused helpers#45333
pelikhan merged 5 commits into
mainfrom
copilot/hippo-refactor-top-function-length

Conversation

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

buildMainJob in compiler_main_job.go was a 420-line monolith — 7× over the 60-line largefunc threshold — mixing condition logic, dependency resolution, output declaration, env setup, and permission inference.

Changes

pkg/workflow/compiler_main_job_helpers.go (new)
Extracts 9 single-responsibility helpers from buildMainJob:

Helper Responsibility
buildMainJobCondition Job if: condition with AIC guardrail
buildMainJobDependencies needs: list + engine env content
warnBuiltinJobEnvReferences Warn on engine.env references to built-in jobs
buildMainJobCoreOutputs / addMainJobEngineErrorOutputs / buildMainJobOutputs Job outputs map
buildMainJobEnv Job-level environment variables
augmentPermissionsForDevMode / collectAgentJobScripts / buildMainJobPermissions Permission inference pipeline

pkg/workflow/compiler_main_job.go
buildMainJob body reduced from 420 lines to under 60; imports trimmed accordingly.

pkg/workflow/compiler_main_job_helpers_test.go (new)
22 targeted unit tests covering the boundaries of each helper — condition variants, dependency deduplication, output structure, env stubbing, and permission inference for dev/prod modes.

Copilot AI and others added 2 commits July 13, 2026 22:01
…unction

Split the 420-line buildMainJob function in compiler_main_job.go into
focused helpers in a new compiler_main_job_helpers.go file:

- buildMainJobCondition: job if-condition logic
- buildMainJobDependencies: needs list + engine env content
- warnBuiltinJobEnvReferences: engine.env built-in job warnings
- buildMainJobCoreOutputs: fixed output declarations
- addMainJobEngineErrorOutputs: engine error detection outputs
- buildMainJobOutputs: complete outputs map
- buildMainJobEnv: job-level env vars
- augmentPermissionsForDevMode: contents:read augmentation for dev/script mode
- collectAgentJobScripts: script gathering for permission inference
- buildMainJobPermissions: full permissions logic

The buildMainJob body is reduced from 420 lines to under 60.
All extracted helpers are each under 60 lines.

Targeted unit tests added in compiler_main_job_helpers_test.go covering
condition, dependency, output, env, and permission logic.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Split combined if-condition in warnBuiltinJobEnvReferences into two
  explicit continue-guards and use string concatenation instead of
  fmt.Sprintf for the needle construction
- Add gh-command-name assertion to the write-command error test

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor oversized validators in pkg/workflow or pkg/cli refactor(workflow): split 420-line buildMainJob into focused helpers Jul 13, 2026
Copilot AI requested a review from pelikhan July 13, 2026 22:04
@pelikhan pelikhan marked this pull request as ready for review July 14, 2026 02:42
Copilot AI review requested due to automatic review settings July 14, 2026 02:42

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 main agent job construction into focused helpers while preserving existing workflow compiler behavior.

Changes:

  • Extracts condition, dependency, output, environment, and permission logic.
  • Reduces buildMainJob to orchestration.
  • Adds targeted helper tests.
Show a summary per file
File Description
pkg/workflow/compiler_main_job.go Delegates main-job construction concerns to helpers.
pkg/workflow/compiler_main_job_helpers.go Implements extracted construction helpers.
pkg/workflow/compiler_main_job_helpers_test.go Tests helper behavior and boundaries.

Review details

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Medium

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 14, 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.

Clean, faithful refactor. Logic is preserved across all 9 extracted helpers, tests cover key branches, and the 420-to-60-line reduction is significant.

One minor nit: augmentPermissionsForDevMode is a package-level function that takes *Compiler as a parameter rather than a method receiver. This is slightly unconventional Go style and could be made a method for consistency with the rest of the helpers. Not blocking.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 28.8 AIC · ⌖ 4.27 AIC · ⊞ 4.8K

@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.

Two issues need fixing before merge

1 (high) — Vacuous test for permissions: {} skip logic (compiler_main_job_helpers_test.go:1140): The PreSteps YAML is missing the pre-steps: section key wrapper, so extractRunScriptsFromSectionYAML returns nothing, and the function exits at the empty-scripts early-return before the inference-skip branch is ever reached. The permissions: {} guard (a security-relevant boundary) goes untested. See inline comment.

2 (medium) — Silent engine-error discard in addMainJobEngineErrorOutputs (compiler_main_job_helpers.go:640): getAgenticEngine errors are swallowed without logging. Downstream jobs referencing the five error-detection outputs receive empty strings instead of the 'false' fallback, which can silently break conditional logic in safe_outputs and conclusion jobs. See inline comment.

Non-blocking observations
  • Dead dedup map in warnBuiltinJobEnvReferences (compiler_main_job_helpers.go:577): builtinsWarned can never be triggered because sliceutil.SortedKeys already deduplicates the slice. Carry-over from the original; safe to remove. See inline comment.
  • Double call to referencesCustomJobOutputs (compiler_main_job_helpers.go:487): same args called twice in an if/else-if; hoist into a local variable to avoid the double traversal.

The overall refactor is well-structured and the extracted helpers are properly scoped. Test coverage is thorough outside the one broken case.

🔎 Code quality review by PR Code Quality Reviewer · 59 AIC · ⌖ 4.82 AIC · ⊞ 5.4K
Comment /review to run again

Comments that could not be inline-anchored

pkg/workflow/compiler_main_job_helpers_test.go:1146

Vacuous test — exercises empty-scripts early-return, not the permission-inference skip it claims to cover.

<details>
<summary>💡 Details and fix</summary>

collectAgentJobScripts calls extractRunScriptsFromSectionYAML(data.PreSteps, &quot;pre-steps&quot;), which expects YAML wrapped under a pre-steps: key (the sibling write-command test on line 1126 even has an explicit comment about this). The value here is bare list syntax:

PreSteps: `- name: read step
  run: gh issue list`,

So `…

pkg/workflow/compiler_main_job_helpers.go:643

Silent engine-lookup failure: addMainJobEngineErrorOutputs discards the error and the caller has no way to detect the omission.

<details>
<summary>💡 Details and fix</summary>

addMainJobEngineErrorOutputs calls c.getAgenticEngine and returns early on error (line 640-641) without logging or returning the error. Its caller, buildMainJobOutputs, has no way to know that the five error-detection outputs (inference_access_error, mcp_policy_error, etc.) were silently dropped. Downstr…

pkg/workflow/compiler_main_job_helpers.go:584

Dead code: builtinsWarned dedup map is populated but its guard can never fire.

<details>
<summary>💡 Details</summary>

sliceutil.SortedKeys returns a deduplicated, sorted slice — each builtin name appears exactly once in the loop. The setutil.Contains(builtinsWarned, builtinJobName) check on line 584 is therefore always false on the single visit to each name, making the entire dedup map dead code.

This was carried over from the original implementation. Remove the map and the contai…

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 5 test(s): 5 design, 0 implementation, 0 violation(s).

📊 Metrics (5 tests)
Metric Value
Analyzed 5 (Go: 5, JS: 0)
✅ Design 5 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (40%)
Duplicate clusters 0
Inflation No (0.79x)
🚨 Violations 0
Test File Classification Assertion Count
TestBuildMainJobCondition compiler_main_job_helpers_test.go:22 Design: Condition logic (5 scenarios) 5
TestBuildMainJobDependencies compiler_main_job_helpers_test.go:79 Design: Dependency graph (6 subtests) 8
TestBuildMainJobOutputs compiler_main_job_helpers_test.go:155 Design: Output generation (4 subtests) 8
TestBuildMainJobEnv compiler_main_job_helpers_test.go:202 Design: Env var construction (5 subtests) 8
TestBuildMainJobPermissions compiler_main_job_helpers_test.go:265 Design + Error handling (3 subtests) 7

Verdict

Passed. 0% implementation tests (threshold: 30%). No guideline violations.

Key observations:

  • All 5 main tests exercise behavioral contracts, not implementation details
  • 19 nested subtests provide comprehensive scenario coverage
  • Permission inference test includes explicit error validation (write gh commands rejection)
  • Table-driven patterns ensure maintainability and consistent coverage
  • Test file (300 lines) reasonably sized relative to production code (381 lines); no inflation concern
  • Required (go/redacted):build !integration tag present on line 1 ✓
  • No forbidden mock libraries (gomock, testify/mock) ✓

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 21.6 AIC · ⌖ 10.4 AIC · ⊞ 6.8K ·
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.

✅ Test Quality Sentinel: 82/100. 0% implementation tests (threshold: 30%). All 5 test functions validate behavioral contracts with comprehensive scenario coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (694 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/45333-extract-buildjob-helpers-from-monolith.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 — 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-45333: Extract buildMainJob Helpers Into a Focused Helpers File

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

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

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., 45333-extract-buildjob-helpers-from-monolith.md for PR #45333).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 58.5 AIC · ⌖ 10.2 AIC · ⊞ 8.5K ·
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.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on two correctness issues.

📋 Key Themes & Highlights

Issues to address

  • False-positive test (compiler_main_job_helpers_test.go line 294): PreSteps is missing its "pre-steps:" YAML wrapper, so extractRunScriptsFromSectionYAML silently skips the step and the test never exercises permission inference.
  • Missing test coverage: warnBuiltinJobEnvReferences is the only helper without a test; its side-effect (IncrementWarningCount) is completely uncovered.
  • Minor consistency nit: buildMainJobCoreOutputs hardcodes "needs.activation.outputs.model" as a string literal instead of using constants.ActivationJobName.

Positive Highlights

  • ✅ Excellent decomposition — 9 single-responsibility helpers with clear, descriptive names
  • buildMainJob is now well under the 60-line threshold and easy to skim
  • ✅ Strong test coverage overall: 22 tests covering condition, dependency deduplication, outputs, env, and permissions paths
  • ✅ Imports correctly trimmed in the original file

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

// buildMainJobCoreOutputs returns the fixed output declarations that every main agent job exposes.
func buildMainJobCoreOutputs() map[string]string {
return map[string]string{
"model": "${{ needs.activation.outputs.model }}",

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] Hardcoded "needs.activation.outputs.model" — should use constants.ActivationJobName for consistency.

Every other activation reference in this file uses the constant (lines 35, 49, 209), but this key embeds the job name as a raw string literal. If the constant ever changes, this output silently breaks.

💡 Suggested fix
"model": fmt.Sprintf("${{ needs.%s.outputs.model }}", constants.ActivationJobName),

Same pattern used for artifact_prefix at line 209.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the latest commit — "model" output now uses fmt.Sprintf("${{ needs.%s.outputs.model }}", constants.ActivationJobName) for consistency with all other activation references in the file.

c := NewCompiler()
data := &WorkflowData{
Permissions: "permissions: {}",
PreSteps: `- name: read step

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] PreSteps YAML is missing the "pre-steps:" wrapper key — extractRunScriptsFromSectionYAML will silently return nil scripts, so the test never actually exercises permission inference.

extractRunScriptsFromSectionYAML(data.PreSteps, "pre-steps") does yaml.Unmarshal then looks up wrapper["pre-steps"]. Without the top-level key the lookup returns nil and the gh issue list script is never parsed. The test passes vacuously.

💡 Suggested fix
PreSteps: "pre-steps:\n- name: read step\n  run: gh issue list",

Compare the working test two cases above (line ~282) which correctly wraps with "pre-steps:\n...".

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — PreSteps is now "pre-steps:\n- name: read step\n run: gh issue list" so extractRunScriptsFromSectionYAML correctly finds the top-level pre-steps key and the test genuinely exercises the inference-skip path.

// warnBuiltinJobEnvReferences emits a warning when engine.env values reference built-in
// job names in needs expressions. Built-in jobs are managed by the compiler and cannot be
// added as direct agent dependencies, so such expressions silently evaluate to empty at runtime.
func (c *Compiler) warnBuiltinJobEnvReferences(depends []string, engineEnvContent 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.

[/tdd] warnBuiltinJobEnvReferences has no test — the warning side-effect and IncrementWarningCount() call are not covered.

This is the only helper in the new file without a corresponding test case. A malformed engine.env reference to a built-in job would silently pass with zero test signal.

💡 Suggested test
func TestWarnBuiltinJobEnvReferences(t *testing.T) {
    t.Run("warns when engine.env references a built-in job not in depends", func(t *testing.T) {
        c := NewCompiler()
        // engineEnvContent references safe_outputs (a built-in not in depends)
        c.warnBuiltinJobEnvReferences([]string{}, "${{ needs.safe_outputs.outputs.foo }}")
        assert.Equal(t, 1, c.WarningCount())
    })

    t.Run("no warning when built-in is already a direct dependency", func(t *testing.T) {
        c := NewCompiler()
        c.warnBuiltinJobEnvReferences([]string{"activation"}, "${{ needs.activation.outputs.model }}")
        assert.Equal(t, 0, c.WarningCount())
    })
}

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added TestWarnBuiltinJobEnvReferences with three sub-tests: warns when a built-in job not in depends is referenced, no warning when the built-in is already a direct dependency, and no warning when engineEnvContent is empty.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot please run the pr-finisher skill, address these unresolved review comments, and rerun checks once finished:

Run: https://github.com/github/gh-aw/actions/runs/29303664684

Generated by 👨‍🍳 PR Sous Chef · 14.9 AIC · ⌖ 5.2 AIC · ⊞ 7.5K ·
Comment /souschef to run again

- Use constants.ActivationJobName in buildMainJobCoreOutputs model output
- Fix missing pre-steps: wrapper key in TestBuildMainJobPermissions
- Add TestWarnBuiltinJobEnvReferences with 3 sub-tests

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot July 14, 2026 03:52
@pelikhan pelikhan merged commit 3f740c7 into main Jul 14, 2026
42 checks passed
@pelikhan pelikhan deleted the copilot/hippo-refactor-top-function-length branch July 14, 2026 07:16
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.

[hippo] Refactor a top function-length hotspot in pkg/workflow or pkg/cli

4 participants