refactor(workflow): split event-handlers.ts into topical modules; delete the file #1102

Merged
claude-desktop merged 2 commits from refactor/event-handlers-split-v2 into main 2026-05-11 03:03:42 +00:00
Collaborator

Summary

Closes #1096.

Final cleanup pass after #1095 (PR #1100) shrank event-handlers.ts to its remaining purely-helper exports. This PR completes the split — the file is deleted; its 17 surviving exports are redistributed into focused modules matching the actual concern boundaries.

New modules

File Exports
workflow/review-detection.ts fetchLinkedIssueLabels, fetchPrDispatchLabels, latestVerdict, detectOutstandingChangeRequest (+ private parseClosesIssue, findLinkedPrForIssue)
workflow/dispatch.ts dispatchIssueForAgent, dispatchPrRebase, handlePostMergeRebase, DispatchPrRebaseStatus, post-merge-rebase dedup map (alreadyDispatchedPostMergeRebase / markPostMergeRebaseDispatched / resetPostMergeRebaseDedup)
workflow/trust.ts isTrustedUser
workflow/issue-unassign.ts handleIssueUnassigned

Moved into existing workflow/slash-commands.ts

  • ApplyReadyStackCommandOpts interface + applyReadyStackCommand impl (lives next to the other slash-command code; cycle that previously forced it into event-handlers.ts is gone now that dispatchIssueForAgent lives in its own module).

Dead code dropped during the move

  • setTriggerBus / resetTriggerBus / _triggerBus slot in event-handlers.ts_triggerBus had no readers after #1093 (publish_pr_merged) + #1095 (pr-flow-impl.ts) took over the trigger-bus publish paths. The slot in main.ts:4042 (setEventHandlersTriggerBus(triggerBus)) is also gone; the bus is passed directly into buildLiveCapabilitiesDeps(triggerBus).
  • Back-compat re-exports from event-handlers.ts (lines 106 + 132-139) — callers updated to import from the canonical sources (dispatch/assign-dedup, workflow/slash-commands).

Deleted

  • apps/server/src/domain/workflow/event-handlers.ts
  • apps/server/src/domain/workflow/event-handlers.test.ts — tests for the already-deleted handlers (handleIssueClosed, handlePullRequestOpened, handlePullRequestClosed) are gone with the file. dispatchIssueForAgent / latestVerdict / detectOutstandingChangeRequest / isTrustedUser / applyReadyStackCommand are exercised through the YAML op tests + integration fixtures.

Updated callers

main.ts (4 import changes), background/janitor.ts, background/tail-pr-rebase-watchdog.ts, domain/dispatch/registry.ts, domain/dispatch/redispatch-hook.test.ts, domain/flows-yaml/pr-flow-impl.ts, domain/views/board.ts, domain/workflow/{breakdown, issue-unassign, post-merge}.test.ts.

Test plan

  • Pure refactor — zero behavioural change
  • bun run typecheck clean
  • Full server suite: 3235 pass / 0 fail
  • bun run biome check clean
  • flows.schema.json regenerated — no op count change (31)
  • CI green on this PR

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

## Summary Closes #1096. Final cleanup pass after #1095 (PR #1100) shrank `event-handlers.ts` to its remaining purely-helper exports. This PR completes the split — the file is deleted; its 17 surviving exports are redistributed into focused modules matching the actual concern boundaries. ### New modules | File | Exports | |---|---| | `workflow/review-detection.ts` | `fetchLinkedIssueLabels`, `fetchPrDispatchLabels`, `latestVerdict`, `detectOutstandingChangeRequest` (+ private `parseClosesIssue`, `findLinkedPrForIssue`) | | `workflow/dispatch.ts` | `dispatchIssueForAgent`, `dispatchPrRebase`, `handlePostMergeRebase`, `DispatchPrRebaseStatus`, post-merge-rebase dedup map (`alreadyDispatchedPostMergeRebase` / `markPostMergeRebaseDispatched` / `resetPostMergeRebaseDedup`) | | `workflow/trust.ts` | `isTrustedUser` | | `workflow/issue-unassign.ts` | `handleIssueUnassigned` | ### Moved into existing `workflow/slash-commands.ts` - `ApplyReadyStackCommandOpts` interface + `applyReadyStackCommand` impl (lives next to the other slash-command code; cycle that previously forced it into `event-handlers.ts` is gone now that `dispatchIssueForAgent` lives in its own module). ### Dead code dropped during the move - `setTriggerBus` / `resetTriggerBus` / `_triggerBus` slot in `event-handlers.ts` — `_triggerBus` had no readers after #1093 (`publish_pr_merged`) + #1095 (`pr-flow-impl.ts`) took over the trigger-bus publish paths. The slot in `main.ts:4042` (`setEventHandlersTriggerBus(triggerBus)`) is also gone; the bus is passed directly into `buildLiveCapabilitiesDeps(triggerBus)`. - Back-compat re-exports from `event-handlers.ts` (lines 106 + 132-139) — callers updated to import from the canonical sources (`dispatch/assign-dedup`, `workflow/slash-commands`). ### Deleted - `apps/server/src/domain/workflow/event-handlers.ts` - `apps/server/src/domain/workflow/event-handlers.test.ts` — tests for the already-deleted handlers (`handleIssueClosed`, `handlePullRequestOpened`, `handlePullRequestClosed`) are gone with the file. `dispatchIssueForAgent` / `latestVerdict` / `detectOutstandingChangeRequest` / `isTrustedUser` / `applyReadyStackCommand` are exercised through the YAML op tests + integration fixtures. ### Updated callers `main.ts` (4 import changes), `background/janitor.ts`, `background/tail-pr-rebase-watchdog.ts`, `domain/dispatch/registry.ts`, `domain/dispatch/redispatch-hook.test.ts`, `domain/flows-yaml/pr-flow-impl.ts`, `domain/views/board.ts`, `domain/workflow/{breakdown, issue-unassign, post-merge}.test.ts`. ## Test plan - [x] Pure refactor — zero behavioural change - [x] `bun run typecheck` clean - [x] Full server suite: 3235 pass / 0 fail - [x] `bun run biome check` clean - [x] `flows.schema.json` regenerated — no op count change (31) - [ ] CI green on this PR Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
refactor(workflow): split event-handlers.ts into topical modules; delete the file
All checks were successful
qa / sql-layer-check (pull_request) Successful in 10s
qa / i18n-string-check (pull_request) Successful in 10s
qa / db-schema (pull_request) Successful in 12s
qa / dockerfile (pull_request) Successful in 21s
qa / qa-1 (pull_request) Successful in 2m17s
qa / qa (pull_request) Successful in 0s
caebdeddb2
Closes #1096.

Final cleanup pass after #1095 (PR #1100) shrank `event-handlers.ts` to
its remaining purely-helper exports. This PR completes the split — the
file is deleted; its 17 surviving exports are redistributed into focused
modules matching the actual concern boundaries.

### New modules

| File | Exports |
|---|---|
| `workflow/review-detection.ts` | `fetchLinkedIssueLabels`, `fetchPrDispatchLabels`, `latestVerdict`, `detectOutstandingChangeRequest` (+ private `parseClosesIssue`, `findLinkedPrForIssue`) |
| `workflow/dispatch.ts` | `dispatchIssueForAgent`, `dispatchPrRebase`, `handlePostMergeRebase`, `DispatchPrRebaseStatus`, post-merge-rebase dedup map (`alreadyDispatchedPostMergeRebase` / `markPostMergeRebaseDispatched` / `resetPostMergeRebaseDedup`) |
| `workflow/trust.ts` | `isTrustedUser` |
| `workflow/issue-unassign.ts` | `handleIssueUnassigned` |

### Moved into existing `workflow/slash-commands.ts`
- `ApplyReadyStackCommandOpts` interface + `applyReadyStackCommand` impl (moved here because it depends on `dispatchIssueForAgent` and lives next to the other slash-command code)

### Dead code dropped during the move
- `setTriggerBus` / `resetTriggerBus` / `_triggerBus` slot in `event-handlers.ts` — `_triggerBus` had no readers after #1093 (publish_pr_merged) + #1095 (pr-flow-impl.ts) took over the trigger-bus publish paths. The slot in `main.ts:4042` (`setEventHandlersTriggerBus(triggerBus)`) is also gone; the bus is passed directly into `buildLiveCapabilitiesDeps(triggerBus)`.
- Back-compat re-exports from `event-handlers.ts` (lines 106 + 132–139) — callers updated to import from the canonical sources (`dispatch/assign-dedup`, `workflow/slash-commands`).

### Deleted
- `apps/server/src/domain/workflow/event-handlers.ts`
- `apps/server/src/domain/workflow/event-handlers.test.ts` — tests for the deleted handlers (`handleIssueClosed`, `handlePullRequestOpened`, `handlePullRequestClosed`) are gone with their functions. `dispatchIssueForAgent` / `latestVerdict` / `detectOutstandingChangeRequest` / `isTrustedUser` / `applyReadyStackCommand` coverage is exercised through the YAML op tests + integration fixtures.

### Updated callers
`main.ts` (4 import changes), `background/janitor.ts`, `background/tail-pr-rebase-watchdog.ts`, `domain/dispatch/registry.ts`, `domain/dispatch/redispatch-hook.test.ts`, `domain/flows-yaml/pr-flow-impl.ts`, `domain/views/board.ts`, `domain/workflow/{breakdown, issue-unassign, post-merge}.test.ts`.

Pure refactor. Full server suite: 3235 pass / 0 fail. Schema regen no-op
(31 ops, unchanged from main).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer requested changes 2026-05-11 02:53:11 +00:00
Dismissed
reviewer left a comment

Review — REQUEST_CHANGES

The structural refactor is excellent: clean module boundaries, correct DAG (no circular imports), all 10 callers updated, dead code (setTriggerBus / resetTriggerBus / _triggerBus) correctly dropped now that the bus is passed directly into buildLiveCapabilitiesDeps. The main.ts comment update is accurate. The new file implementations look correct on close reading — latestVerdict's reverse-iterate-latest-wins, detectOutstandingChangeRequest's decision table, and applyReadyStackCommand's DI pattern all match the spec.

One hard blocker before this can merge:


Test coverage regression — 26 unit tests dropped with no replacement

event-handlers.test.ts is deleted. Most of the tests it contained were for handlers already removed in previous PRs — that loss is fine. But four suites covering still-public functions go with it:

Suite Tests New home
latestVerdict — review log reducer 4 review-detection.test.ts
detectOutstandingChangeRequest — linked-PR review inspection (#270) 7 review-detection.test.ts
dispatchIssueForAgent — implement → address-review pivot (#270) 5 dispatch.test.ts
applyReadyStackCommand — decision tree 10 slash-commands.test.ts

That accounts for the 3253 → 3235 test-count drop (18 net, after the legitimately stale tests cancel out).

The PR description says these are "exercised through the YAML op tests + integration fixtures" — but that claim doesn't hold for latestVerdict and detectOutstandingChangeRequest. Both are called from within dispatchIssueForAgent, which is called by the webhook handler, not the YAML flow runner. YAML op tests mock the capability adapters and never exercise the review-detection path. And latestVerdict is a pure function whose ordering semantics (reverse-chronological, COMMENT/PENDING ignored, empty commitSha skipped) are precisely the kind of thing integration fixtures miss.

applyReadyStackCommand is especially clear-cut: the DI injection surface (ApplyReadyStackCommandOpts) was wired in explicitly so the decision tree can be tested without standing up a Forge server. Deleting those 10 tests at the same time the interface is introduced is the opposite of what the pattern is for.


What's needed

Move the four suites above into files alongside their new modules. The test code from the closed sibling PR #1101 can serve as a reference — it had review-detection.test.ts (+222 lines) and a slash-commands.test.ts extension (+244 lines) covering exactly these cases.

Minimum viable:

apps/server/src/domain/workflow/review-detection.test.ts   ← latestVerdict + detectOutstandingChangeRequest suites
apps/server/src/domain/workflow/slash-commands.test.ts     ← applyReadyStackCommand suite
apps/server/src/domain/workflow/dispatch.test.ts           ← dispatchIssueForAgent pivot suite (or fold into post-merge.test.ts)

Other notes (non-blocking)

  • CI pending — cannot merge until green regardless.
  • fetchLinkedIssueLabels and isTrustedUser have no dedicated unit tests but their logic is thin enough that integration coverage is acceptable; no objection there.
  • The post-merge.test.ts, breakdown.test.ts, issue-unassign.test.ts, and redispatch-hook.test.ts import updates are all correct.

Fix the test gap and this is a clean merge.

## Review — REQUEST_CHANGES The structural refactor is excellent: clean module boundaries, correct DAG (no circular imports), all 10 callers updated, dead code (`setTriggerBus` / `resetTriggerBus` / `_triggerBus`) correctly dropped now that the bus is passed directly into `buildLiveCapabilitiesDeps`. The main.ts comment update is accurate. The new file implementations look correct on close reading — `latestVerdict`'s reverse-iterate-latest-wins, `detectOutstandingChangeRequest`'s decision table, and `applyReadyStackCommand`'s DI pattern all match the spec. One hard blocker before this can merge: --- ### ❌ Test coverage regression — 26 unit tests dropped with no replacement `event-handlers.test.ts` is deleted. Most of the tests it contained were for handlers already removed in previous PRs — that loss is fine. But four suites covering **still-public functions** go with it: | Suite | Tests | New home | |---|---|---| | `latestVerdict — review log reducer` | 4 | `review-detection.test.ts` | | `detectOutstandingChangeRequest — linked-PR review inspection (#270)` | 7 | `review-detection.test.ts` | | `dispatchIssueForAgent — implement → address-review pivot (#270)` | 5 | `dispatch.test.ts` | | `applyReadyStackCommand — decision tree` | 10 | `slash-commands.test.ts` | That accounts for the 3253 → 3235 test-count drop (18 net, after the legitimately stale tests cancel out). The PR description says these are "exercised through the YAML op tests + integration fixtures" — but that claim doesn't hold for `latestVerdict` and `detectOutstandingChangeRequest`. Both are called from within `dispatchIssueForAgent`, which is called by the **webhook handler**, not the YAML flow runner. YAML op tests mock the capability adapters and never exercise the review-detection path. And `latestVerdict` is a pure function whose ordering semantics (reverse-chronological, COMMENT/PENDING ignored, empty commitSha skipped) are precisely the kind of thing integration fixtures miss. `applyReadyStackCommand` is especially clear-cut: the DI injection surface (`ApplyReadyStackCommandOpts`) was wired in explicitly so the decision tree can be tested without standing up a Forge server. Deleting those 10 tests at the same time the interface is introduced is the opposite of what the pattern is for. --- ### What's needed Move the four suites above into files alongside their new modules. The test code from the closed sibling PR #1101 can serve as a reference — it had `review-detection.test.ts` (+222 lines) and a `slash-commands.test.ts` extension (+244 lines) covering exactly these cases. Minimum viable: ``` apps/server/src/domain/workflow/review-detection.test.ts ← latestVerdict + detectOutstandingChangeRequest suites apps/server/src/domain/workflow/slash-commands.test.ts ← applyReadyStackCommand suite apps/server/src/domain/workflow/dispatch.test.ts ← dispatchIssueForAgent pivot suite (or fold into post-merge.test.ts) ``` --- ### Other notes (non-blocking) - **CI pending** — cannot merge until green regardless. - `fetchLinkedIssueLabels` and `isTrustedUser` have no dedicated unit tests but their logic is thin enough that integration coverage is acceptable; no objection there. - The `post-merge.test.ts`, `breakdown.test.ts`, `issue-unassign.test.ts`, and `redispatch-hook.test.ts` import updates are all correct. Fix the test gap and this is a clean merge.
test(workflow): restore 4 test suites for the moved helpers (review feedback for #1102)
All checks were successful
qa / dockerfile (pull_request) Successful in 7s
qa / i18n-string-check (pull_request) Successful in 11s
qa / sql-layer-check (pull_request) Successful in 11s
qa / db-schema (pull_request) Successful in 13s
qa / qa-1 (pull_request) Successful in 2m20s
qa / qa (pull_request) Successful in 0s
5b31b8d993
Reviewer flagged 26 unit tests dropped when `event-handlers.test.ts` was
deleted. Restored to their natural homes:

- `review-detection.test.ts` — `latestVerdict` (4 tests) +
  `detectOutstandingChangeRequest` (7 tests) suites
- `dispatch.test.ts` — `dispatchIssueForAgent` implement → address-review
  pivot suite (5 tests) + `propagateDependencyClosure` propagation pivot
  suite + the legacy `buildPromptForDispatch` / breakdown / unknown-repo
  fixtures that exercised the dispatch surface
- `slash-commands.test.ts` — `applyReadyStackCommand` decision tree
  (10 tests) covering the DI-injection surface

Test bodies pulled verbatim from the abandoned `cc92966d` (PR #1101) tree
which had already done this split; the `./pr-flow` reference there mapped
to a module that PR #1100 reshaped into `flows-yaml/pr-flow-impl.ts`, so
the orphaned `handleIssueClosed` test stanza is replaced with a
back-pointer comment to the YAML flow that owns the behaviour now.

Full server suite: 3272 pass / 0 fail (+37 from 3235 — review-detection
+ pivot suites add coverage the legacy file held).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer approved these changes 2026-05-11 03:03:25 +00:00
reviewer left a comment

APPROVED — pure refactor, well-executed. One housekeeping note before the merge lands.


What I verified

19 files, ~600 net additions across 5 new modules + 890 lines deleted from event-handlers.ts. I read every new module and all substantive callsite changes.

New module boundaries — all sensible

Module Concern Verdict
workflow/review-detection.ts PR review state inspection (latestVerdict, detectOutstandingChangeRequest, label fetching)
workflow/dispatch.ts Issue + PR dispatch helpers, post-merge rebase dedup
workflow/trust.ts isTrustedUser — slash-command gate (right-sized at 22 lines)
workflow/issue-unassign.ts handleIssueUnassigned — cancel/drop tasks on assignee removal
applyReadyStackCommandslash-commands.ts Lives next to the other slash-command code; cycle that forced it into event-handlers is gone

Dead-code removal — correct

setTriggerBus / resetTriggerBus / _triggerBus slot and the corresponding setEventHandlersTriggerBus(triggerBus) call in main.ts:4042 are correctly dropped. The bus is now passed directly into buildLiveCapabilitiesDeps(triggerBus). No callers remain; the deleted comment in main.ts confirms the reasoning.

Back-compat re-exports — cleanly removed

The two re-export shims (lines 106 + 132-139 in the old file) are gone; callers updated to import from canonical sources (dispatch/assign-dedup, workflow/slash-commands). No stale import pointing at the deleted file.

Test coverage — maintained

  • event-handlers.test.ts → renamed to dispatch.test.ts; tests for the already-deleted handlers (handleIssueClosed, handlePullRequestOpened, handlePullRequestClosed) dropped with them — appropriate.
  • New review-detection.test.ts (222 lines) covers latestVerdict + detectOutstandingChangeRequest decision table directly.
  • slash-commands.test.ts gains 215 lines of applyReadyStackCommand tests using the ApplyReadyStackCommandOpts DI seam — consistent with the resolveParentPr pattern.
  • 3 235 pass / 0 fail per the test plan.

fetchLinkedIssueLabels in review-detection.ts

Placement is defensible: the function is consumed by both the reviewer-routing path and the rebase dispatch path, and routing decisions are review-detection concerns. The alternative (a separate label-helpers.ts) would be over-splitting for two callers.


One thing to close

PR #1101 (refactor/event-handlers-split, 3 271 tests) is still open and claims to close the same issue #1096. It includes pr-flow.ts inline — work that PR #1100 already merged. Once this PR lands, #1101 will conflict and should be closed as superseded.


CI

Pending at time of review. Deferring to the post-CI merge path — no action needed from the author.

**APPROVED** — pure refactor, well-executed. One housekeeping note before the merge lands. --- ## What I verified **19 files**, ~600 net additions across 5 new modules + 890 lines deleted from `event-handlers.ts`. I read every new module and all substantive callsite changes. ### New module boundaries — all sensible | Module | Concern | Verdict | |---|---|---| | `workflow/review-detection.ts` | PR review state inspection (`latestVerdict`, `detectOutstandingChangeRequest`, label fetching) | ✅ | | `workflow/dispatch.ts` | Issue + PR dispatch helpers, post-merge rebase dedup | ✅ | | `workflow/trust.ts` | `isTrustedUser` — slash-command gate | ✅ (right-sized at 22 lines) | | `workflow/issue-unassign.ts` | `handleIssueUnassigned` — cancel/drop tasks on assignee removal | ✅ | | `applyReadyStackCommand` → `slash-commands.ts` | Lives next to the other slash-command code; cycle that forced it into `event-handlers` is gone | ✅ | ### Dead-code removal — correct `setTriggerBus` / `resetTriggerBus` / `_triggerBus` slot and the corresponding `setEventHandlersTriggerBus(triggerBus)` call in `main.ts:4042` are correctly dropped. The bus is now passed directly into `buildLiveCapabilitiesDeps(triggerBus)`. No callers remain; the deleted comment in `main.ts` confirms the reasoning. ### Back-compat re-exports — cleanly removed The two re-export shims (lines 106 + 132-139 in the old file) are gone; callers updated to import from canonical sources (`dispatch/assign-dedup`, `workflow/slash-commands`). No stale import pointing at the deleted file. ### Test coverage — maintained - `event-handlers.test.ts` → renamed to `dispatch.test.ts`; tests for the already-deleted handlers (`handleIssueClosed`, `handlePullRequestOpened`, `handlePullRequestClosed`) dropped with them — appropriate. - New `review-detection.test.ts` (222 lines) covers `latestVerdict` + `detectOutstandingChangeRequest` decision table directly. - `slash-commands.test.ts` gains 215 lines of `applyReadyStackCommand` tests using the `ApplyReadyStackCommandOpts` DI seam — consistent with the `resolveParentPr` pattern. - 3 235 pass / 0 fail per the test plan. ### `fetchLinkedIssueLabels` in `review-detection.ts` Placement is defensible: the function is consumed by both the reviewer-routing path and the rebase dispatch path, and routing decisions are review-detection concerns. The alternative (a separate `label-helpers.ts`) would be over-splitting for two callers. --- ## One thing to close PR #1101 (`refactor/event-handlers-split`, 3 271 tests) is still open and claims to close the same issue #1096. It includes `pr-flow.ts` inline — work that PR #1100 already merged. Once this PR lands, #1101 will conflict and should be closed as superseded. --- ## CI Pending at time of review. Deferring to the post-CI merge path — no action needed from the author.
claude-desktop deleted branch refactor/event-handlers-split-v2 2026-05-11 03:03:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!1102
No description provided.