Skip to content

refactor: centralize close-flow logic into shared createCloseEntityHandler factory#25628

Open
Copilot wants to merge 2 commits intomainfrom
copilot/refactor-close-issue-and-pull-request-logic
Open

refactor: centralize close-flow logic into shared createCloseEntityHandler factory#25628
Copilot wants to merge 2 commits intomainfrom
copilot/refactor-close-issue-and-pull-request-logic

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

close_issue.cjs and close_pull_request.cjs each implemented an identical ~100-line close-flow pipeline inline, while close_pull_request.cjs also duplicated checkLabelFilter, checkTitlePrefixFilter, and buildCommentBody that already existed in close_entity_helpers.cjs.

Changes

close_entity_helpers.cjs — new createCloseEntityHandler factory

Centralises the shared 11-step pipeline: max-count gating → comment body resolution → sanitization → target resolution → entity details fetch → label filter → title-prefix filter → staged-mode preview → comment posting → entity close → result construction.

Entity-specific behaviour is injected via a CloseEntityHandlerCallbacks object:

return createCloseEntityHandler(config, ISSUE_CONFIG, {
  resolveTarget(item) { /* cross-repo resolution */ },
  validateLabels(entity, n, required) { /* all-must-match for issues */ },
  buildCommentBody(sanitizedBody) { return sanitizedBody; }, // no footer for issues
  closeEntity(github, owner, repo, n, item) { /* passes state_reason */ },
  continueOnCommentError: false,
  buildSuccessResult(closed, comment, alreadyClosed) { return { number, url, title, alreadyClosed }; },
  // ...
}, githubClient);

close_issue.cjs

Reduced from ~305 lines to ~80 lines of logic. Preserves: cross-repo support, all-labels-must-match semantics, raw comment body (no footer), per-item state_reason override, fails-on-comment-error.

close_pull_request.cjs

Reduced from ~355 lines to ~90 lines of logic. Removes local duplicates of checkLabelFilter, checkTitlePrefixFilter, and buildCommentBody (now imported from helpers). Preserves: any-label-match semantics, footer-enhanced comment body, continueOnCommentError: true with commentPosted tracking.

close_pull_request.test.cjs

Updated one title-prefix error assertion to match the now-shared error message format ("doesn't start with" — same format close_issue already used).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/github/gh-aw/contents/.github%2Fworkflows%2Faudit-workflows.md
    • Triggering command: /opt/hostedtoolcache/node/24.14.1/x64/bin/node /opt/hostedtoolcache/node/24.14.1/x64/bin/node --experimental-import-meta-resolve --require /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/suppress-warnings.cjs --conditions node --conditions development /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/dist/workers/forks.js (http block)
  • invalid.example.invalid
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git git conf�� user.name Test User _modules/.bin/git -M main t git init�� --bare --initial-branch=main 86_64/git -b main modules/@npmcli/agent-change.txt git (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

…ctory (#close-issue-close-pr-duplication)

- Add createCloseEntityHandler() to close_entity_helpers.cjs handling the
  common pipeline: max-count gating, comment body resolution, sanitization,
  label/title-prefix filtering, staged-mode preview, comment posting with
  configurable error handling, entity close, and success result construction
- Refactor close_issue.cjs to delegate to createCloseEntityHandler with
  issue-specific callbacks (cross-repo, all-labels-must-match, no footer,
  state_reason support)
- Refactor close_pull_request.cjs to delegate to createCloseEntityHandler,
  removing duplicate local checkLabelFilter, checkTitlePrefixFilter and
  buildCommentBody (now imported from close_entity_helpers.cjs)
- Update close_pull_request.test.cjs title-prefix error assertion to match
  the aligned error message format

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d384dacd-9154-4360-9586-b607087672e9

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor close_issue and close_pull_request handlers to reduce duplication refactor: centralize close-flow logic into shared createCloseEntityHandler factory Apr 10, 2026
Copilot AI requested a review from pelikhan April 10, 2026 12:21
@pelikhan pelikhan marked this pull request as ready for review April 10, 2026 12:27
Copilot AI review requested due to automatic review settings April 10, 2026 12:27
@github-actions github-actions bot mentioned this pull request Apr 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100

Excellent test quality

Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (100%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
should validate required title prefix actions/setup/js/close_pull_request.test.cjs:391 ✅ Design None — correctly tracks shared error message format

Change Summary

This PR modifies exactly one assertion in one test file:

- expect(result.error).toContain("does not start with required prefix");
+ expect(result.error).toContain("doesn't start with");

The change keeps the assertion synchronized with the now-shared error message format introduced by the createCloseEntityHandler factory refactor. The test continues to verify a behavioral contract: when a PR title doesn't match the required prefix, the handler returns success: false with an error message containing the expected phrase.

No new test functions were added or removed. No Go test files were changed. The single modified test is an error-path case (tests a failure scenario), carries two expect() assertions checking both success and error shape, and is a high-value design test.

Test inflation: test file changed by 1 line vs. 350 lines added/removed across production files — well within acceptable bounds.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 0 tests (no Go test files changed)
  • 🟨 JavaScript (*.test.cjs): 1 test (vitest)

Verdict

Check passed. 0% of modified tests are implementation tests (threshold: 30%). The single changed assertion correctly tracks the refactored shared error message.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · ● 434.5K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 100/100. Test quality is excellent — 0% of modified tests are implementation tests (threshold: 30%). The single changed assertion correctly tracks the refactored shared error-message format from the createCloseEntityHandler factory.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the close-flow logic for issues and pull requests by centralizing the shared pipeline into a new createCloseEntityHandler factory, reducing duplication and aligning shared validation/error behavior across handlers.

Changes:

  • Added createCloseEntityHandler to unify the close pipeline (max gating, sanitization, filters, staged-mode preview, comment+close, result shaping).
  • Refactored close_issue.cjs and close_pull_request.cjs to use the shared factory with entity-specific callbacks.
  • Updated a PR title-prefix assertion in close_pull_request.test.cjs to match the shared error message format.
Show a summary per file
File Description
actions/setup/js/close_entity_helpers.cjs Introduces createCloseEntityHandler and exports it for reuse across close handlers.
actions/setup/js/close_issue.cjs Replaces inline close pipeline with the shared factory, preserving issue-specific semantics (label matching, state_reason override, no footer).
actions/setup/js/close_pull_request.cjs Replaces inline close pipeline with the shared factory and removes duplicated helper logic in favor of shared helpers.
actions/setup/js/close_pull_request.test.cjs Adjusts test expectation for the updated, shared title-prefix error message wording.

Copilot's findings

Tip

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

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment on lines +124 to +128
buildCommentBody(sanitizedBody) {
const triggeringPRNumber = context.payload?.pull_request?.number;
const triggeringIssueNumber = context.payload?.issue?.number;
return buildCommentBody(sanitizedBody, triggeringIssueNumber, triggeringPRNumber);
},
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

createCloseEntityHandler sanitizes the comment body before invoking callbacks.buildCommentBody, but in the PR handler the callback delegates to buildCommentBody from close_entity_helpers.cjs, which sanitizes internally as well. This results in double-sanitization of the user-provided comment for PRs (potentially altering output and doing extra work). Consider making the helper buildCommentBody assume input is already sanitized (and update other call sites accordingly), or bypass it here and only apply sanitization once in the shared pipeline.

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot added the lgtm label Apr 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — great work on this refactoring! Centralising the shared 11-step close-flow pipeline into createCloseEntityHandler is a clean architectural improvement — it eliminates the ~100-line duplication between close_issue.cjs and close_pull_request.cjs and removes the redundant local copies of checkLabelFilter, checkTitlePrefixFilter, and buildCommentBody that already lived in close_entity_helpers.cjs.

The PR is well-structured, laser-focused on one thing, has a thorough description, includes a test update (and the Test Quality Sentinel gave it 100/100 🎉), adds no new dependencies, and the net change is a healthy net reduction of ~103 lines of code. This looks ready for maintainer review! ✅

Generated by Contribution Check · ● 3.2M ·

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate Code: close_issue and close_pull_request handlers reimplement the same close-flow logic

3 participants