# Scoping report — adopting upstream domain reorg `8356eb34` (#1726) as a file-move exercise Date: 2026-07-17. Repo: `/Users/adammoussa/Documents/repositories/seahaven/open-swe` (branch `dev`, clean). Strategy scoped: **fork file contents, upstream layout** — replay the reorg's *moves* onto the fork's own tree; never take upstream file contents. Merge-base with `upstream/main`: `f6c215ff`. Reorg commit sits 91 upstream commits past the merge-base. Current truthful conflict surface (`git merge-tree --write-tree --name-only HEAD upstream/main`): **140 conflicted paths** (`docs/upstream-sync/domain-reorg/merge-tree-now.txt`). --- ## 1. Move-map (`git diff-tree -r -M50 --name-status 8356eb34^ 8356eb34`) 298 entries total (raw file: `docs/upstream-sync/domain-reorg/movemap-m50.txt`): | class | count | meaning | |---|---|---| | **R100** pure renames | **150** | byte-identical moves (mostly `tests/*` → `tests//`, `ui/src/{components,lib}/agents/*` → `ui/src/features/*`, `agent/reviewer_*`/`review_style_*` → `agent/review/`) | | **R<100** rename+edit | **77** | scores R062–R099; **every sampled edit is an import-path / monkeypatch-target rewrite only** (see §Spot-checks) | | **M** in-place edits | **49** | shim-ification (`webapp.py` 2007-line reduction, `server.py` constant extraction), import rewires in `agent/tools/*`, `agent/webhooks/*`, `evals/`, `ui/src/routes/*`, config files | | **A** true adds | **21** | all thin structure: `agent/graphs/{agent,analyzer,chat,reviewer,scheduler}.py` (re-export shims), `agent/runtime/{constants,execution,sandbox}.py` (constants + delegating wrappers around `agent.server`), `agent/api/{app,health}.py`, `agent/webhooks/{common,github_routes,linear_routes,slack_routes}.py`, `agent/{providers,resources,review,runtime,graphs,api}/__init__.py` | | **D** true deletes | **1** | `ui/src/components/agents/AgentPromptBar.tsx` — a 2-line re-export shim, re-added at `ui/src/features/agents/components/AgentPromptBar.tsx` with the path retargeted (A/D pair is cosmetic, not a content loss) | Content-consolidations (real redistribution rather than move): effectively **one** — the FastAPI layer. `agent/webapp.py` (upstream pre-reorg ~2k lines) is split into `agent/webhooks/common.py` (shared dispatch/verify helpers), `agent/api/app.py` (composition), `agent/api/health.py` (`/health` + `/webhooks/run-complete`), and per-source `*_routes.py`; `webapp.py` becomes a 4-line compatibility shim (`from .api.app import app`). Secondary micro-extractions: `server.py` → `runtime/constants.py` (`DEFAULT_LLM_MODEL_ID`, `DEFAULT_LLM_MAX_TOKENS`, `DEFAULT_RECURSION_LIMIT`, `MODEL_CALL_RECURSION_LIMIT`) and `runtime/execution.py` (`graph_loaded_for_execution`); `prompt.py` → `default_prompt.md` loaded via `importlib.resources` from `agent/resources/`. The "consolidate reviewer modules" sub-commit is **not** a content merge — it is nine 1:1 module moves into `agent/review/` (R098–R100) plus import rewires. `langgraph.json` **does change**: graph entrypoints retarget from `agent.server:traced_agent` / `agent.reviewer:...` / `agent.analyzer:...` / `agent.chat:...` / `agent.scheduler:...` to `agent.graphs.:`. The targets are pure re-export shims (e.g. `agent/graphs/agent.py` = `from agent.server import get_agent, traced_agent`), so *behavior* is unchanged, but the deployment manifest strings are not. `http.app` stays `agent.webapp:app` (via the compat shim). See §Hazards (d). --- ## 2. Collision set (move-map × fork divergence) Fork divergence since merge-base: 322 paths (130 A / 188 M / 3 D / 1 R; raw: `docs/upstream-sync/domain-reorg/fork-divergence-status.txt`). Fork-added files split against upstream trees: **67 are shared** (cherry-picked, exist upstream pre-reorg → move-map applies) and **64 are TRUE fork-only** (exist nowhere upstream). Headline classification: | bucket | count | |---|---| | MECHANICAL — fork-touched, R100 pure rename → `git mv` fork's version | **57** | | MECHANICAL-trivial — moved by reorg, fork never touched → `git mv` (identical either way) | **127** | | HAND-CARRY (light) — fork-touched, R<100 → `git mv` + replicate upstream's import-rewrite pattern on fork content | **34** | | HAND-CARRY (real) — fork-modified AND reorg-modified-in-place (M×M) | **34** (≈10 substantive, rest are 1–3-line import seds) | | FORK-ONLY placement decisions | **64** files, of which **13 test files** need explicit new homes; nearly all fork-only *source* stays put (reorg does not move `agent/tools/`, `agent/utils/`, `agent/middleware/`, `agent/integrations/`, `agent/dashboard/`, `agent/webhooks/` handler modules) | | moved upstream but absent from fork (added in the 91 interim commits — no-op for us) | 9 | ### 2a. MECHANICAL (fork-touched, pure rename — the bulk) `git mv` fork's file to the new path; no content change. 57 files: - Root/docs/resources: `INSTALLATION.md → docs/INSTALLATION.md`, `default_prompt.md → agent/resources/default_prompt.md` (pairs with the `prompt.py` loader hunk — see HAND-CARRY), `agent/reviewer_findings.py → agent/review/findings.py`. - Tests (46): the full `tests/test_*.py → tests//test_*.py` set, e.g. `tests/test_dispatch.py → tests/agent/`, `tests/test_workflow_push_guard.py → tests/agent/`, `tests/test_plan_mode.py`, `tests/test_plan_review.py → tests/agent/`, `tests/test_auth_sources.py`, `tests/test_authorship.py`, `tests/test_encryption*→ tests/auth/`, `tests/test_github_app.py`, `tests/test_open_pull_request.py`, `tests/test_pr_creation_guard.py → tests/github/`, `tests/test_gateway.py`, `tests/test_proxy_auth.py`, `tests/test_local_integration.py → tests/sandbox/`, sanitize/middleware tests → `tests/middleware/`, model tests → `tests/models/`, Slack tool tests → `tests/slack/`, dashboard/team-settings tests → `tests/dashboard/`, `tests/test_completion_webhook.py → tests/webhooks/`, etc. (full list in `docs/upstream-sync/domain-reorg/cross.json`, key `mech`). - UI (10): `ui/src/lib/agents/{api,queries,types,streamMessagesToUi,sidebarFilter,messageTimestamps}.ts → ui/src/features/agents/lib/`, `ui/src/components/agents/PrHeader.tsx → ui/src/features/reviews/components/`, `MessageTimestamp.tsx`, `diffUtils.ts`. Plus the 127 fork-untouched moved files (same treatment, zero divergence risk). ### 2b. HAND-CARRY **(i) R<100 moved files fork also touched — 34 files.** Upstream's edits are exclusively path rewires; replicate the same rewires on the fork's content after `git mv`: - Python: `agent/reviewer_publish.py → agent/review/publish.py` (R099: `from .reviewer_findings import` → `from .findings import`, `from .utils.X` → `from ..utils.X`), same for `reviewer_reconcile.py`, `reviewer_trace_context.py`; reviewer test files R062–R099 retarget `webapp` → `webhook_common` / `github_webhooks` monkeypatch modules; `tests/test_e2b_integration.py → tests/sandbox/` (fork re-implemented E2B sync — keep fork content, fix imports only). - UI: 20 `.tsx/.ts` files where the only edits are `@/components/agents/...` → `@/features/agents/components/...` and `@/lib/agents/...` → `@/features/agents/lib/...` (verified zero non-import edits on the worst-looking case, `AgentThreadView.tsx` R092). Fork-diverged files in this set that must keep fork content: `PlanReview.tsx` (plan-mode #130), `WorkflowApprovalCard.tsx`, `AgentsSidebar.tsx`, `SidebarFilterMenu.tsx`, `AgentGitPanel.tsx`, `AutomationEditor.tsx`. **(ii) M×M in-place overlap — 34 files, of which the substantive ten:** | file | upstream's reorg edit | fork action | |---|---|---| | `agent/webapp.py` (fork: **2,590 lines**) | gutted to 4-line shim; content → `webhooks/common.py` + `api/app.py` + `api/health.py` + `*_routes.py` | **the big one** — split the fork's monolith the same way, carrying fork-only Jira/Confluence/Connect routes (see §3) | | `agent/server.py` | extract 4 constants → `runtime/constants.py`, `graph_loaded_for_execution` → `runtime/execution.py`; adds public aliases `get_cached_sandbox_backend` etc. | replicate extraction on fork content; fork's sync sandbox lifecycle stays in `server.py`/`utils/sandbox_state.py` untouched — `runtime/sandbox.py` shim delegates to `agent.server` (upstream's own shim does exactly this, so it wraps fork code cleanly) | | `agent/prompt.py` | `DEFAULT_PROMPT_PATH` env-only + `importlib.resources` load of `agent/resources/default_prompt.md` | apply this one hunk by hand to fork's heavily customized `prompt.py`; **verify the wheel packages the .md** (upstream did *not* touch `pyproject.toml`; `packages=["agent"]` under hatchling should carry it — build and check) | | `agent/reviewer.py` / `agent/analyzer.py` / `agent/chat.py` | import rewires to `agent.review.*` / small | sed-level on fork content | | `agent/webhooks/{github,linear,slack}.py` | `from agent import webapp` → `from . import common`; `webapp.X` → `common.X` (module-attribute style preserved so monkeypatching keeps working) | **caution:** these exist on *both* sides with different content (fork built its own split earlier). No `git mv` applies — sed the fork's files; never adopt upstream's | | `tests/conftest.py` | `from agent import webapp` → `from agent.webhooks import common as webhook_common` (auto-review fixture) | same sed on fork's conftest | | `tests/e2e/harness.py` | `from agent.webapp import app, generate_thread_id_from_slack_thread` → `from agent.api.app import app` + `from agent.utils.thread_ids import ...` | fork already has `agent/utils/thread_ids.py` — same retarget | | `ui/tsconfig.json` / `ui/eslint.config.js` | replace the 8-file `ported/` exclude lists with `src/features/agents/experiments/**` | adopt the glob (simplification, no behavior change); `ui/vite.config.ts` upstream edit is cosmetic churn on the fork-only mock-harness plugin — skip | | `langgraph.json` | entrypoints → `agent.graphs.*` | retarget fork's file **and add `ci_monitor": "agent.graphs.ci_monitor:get_ci_monitor"` shim** for the fork-only graph (upstream deleted its ci-autofix cluster; fork keeps it) | | `AGENTS.md`/`CLAUDE.md`/`README.md` | doc-path updates | fork-rewritten docs — update path references manually (CLAUDE.md architecture sections name `agent/webapp.py`, tool/test paths) | The remaining ~24 M×M files (`agent/tools/add_finding.py` and the other reviewer tools, `agent/dashboard/{eval_jobs,review_api,review_chat_api}.py`, `evals/reviewer/*`, `ui/src/routes/*.tsx`, `agent/utils/github_org_membership.py`, `agent/middleware/settle_review_check.py`) take 1–3-line import seds (`..reviewer_findings` → `..review.findings`, `@/components/agents` → `@/features/...`). **Blast-radius counts (fork tree):** 38 Python files import `reviewer_*`/`review_style_*` modules; 12 Python files import `agent.webapp`; **240 `monkeypatch.setattr(webapp, ...)` sites across 11 test files** must retarget to `webhook_common`/handler modules; 54 UI files import `@/components/agents` or `@/lib/agents`. ### 2c. FORK-ONLY placement proposals (64 true fork-only files) Upstream's domain logic: tools stay flat in `agent/tools/`; util clients in `agent/utils/`; webhook *handlers* in `agent/webhooks/.py` with HTTP routes in `agent/webhooks/_routes.py`; tests in `tests//`. **Source — stays put (no move needed):** - Atlassian tools `agent/tools/{jira_*,confluence_*}.py` (11 files) → stay (`agent/tools/` unmoved by reorg). - `agent/utils/{jira,confluence,adf,atlassian_connect,jira_project_repo_map,confluence_space_repo_map}.py` → stay. - Fork middleware, `agent/integrations/e2b.py`, dashboard additions, `.githooks/`, `.github/`, `scripts/`, `docs/upstream-sync/`, `docs/repo-conventions/`, `deploy/` → stay. **Source — new homes created by the split (part of the webapp-split commit):** - `agent/webhooks/jira.py`, `agent/webhooks/confluence.py` → stay as handler modules; **add fork-only `agent/webhooks/jira_routes.py` and `agent/webhooks/confluence_routes.py`** mirroring upstream's `github_routes.py` pattern (routes access helpers via `common.X`). - Atlassian Connect lifecycle/descriptor routes (`/connect/*`, `GET /connect/atlassian-connect.json`) → propose `agent/webhooks/confluence_routes.py` (they are Confluence-trigger plumbing) or a dedicated `agent/webhooks/connect_routes.py` if cleaner; register in fork's `agent/api/app.py`. **Tests — 13 fork-only/uncovered files:** | file | proposed home | |---|---| | `tests/test_jira_webhook_{author,corroboration,replay}.py`, `tests/test_confluence_webhook.py`, `tests/test_linear_webhook_replay.py` | `tests/webhooks/` | | `tests/test_atlassian_connect.py` | `tests/auth/` (JWT/qsh verification surface; alternative `tests/webhooks/`) | | `tests/test_jira_utils.py`, `tests/test_confluence_utils.py` | `tests/tools/` (client layer backing the Jira/Confluence tools) | | `tests/test_repo_binding_isolation.py` (TID-COLLIDE-01 guard) | `tests/sandbox/` (it exercises the sandbox/GitHub-token per-thread caches; alternative `tests/webhooks/`) | | `tests/test_auth_error_leak.py` | `tests/auth/` | | `tests/test_app_bot_identity.py` | `tests/github/` (bot authorship/PR attribution) | | `tests/test_ci_autofix.py`, `tests/test_autofix_webhook.py` | `tests/github/` (upstream deleted the impl cluster; fork keeps `agent/ci_autofix.py`, `agent/ci_monitor.py` in place) | UI fork-only: `ui/src/components/PwaUpdateToast.tsx` → `ui/src/components/` stays (not an agents-feature file); `ui/src/lib/auth-redirect*.{ts,tsx}` → stay in `ui/src/lib/` (upstream kept non-agents lib files there); `ui/src/routes/$owner.$repo.pull.$number.tsx` → stays (`routes/` not moved). `tests/e2e/**` (fork harness + specs) → stays; upstream kept `tests/e2e/` in place. --- ## 3. Consolidations / decision list for Adam 1. **FastAPI layer split (the only true consolidation) — FLAG-HUMAN, auth surface.** Fork's 2,590-line `agent/webapp.py` carries fork-only webhook auth: `verify_jira_secret` (+ optional HMAC/timestamp), Atlassian Connect JWT/qsh handling, the GitHub/Slack/Linear signature paths, token-attribution gating, TID-COLLIDE-01 thread-binding. Splitting it into `webhooks/common.py` + `api/` + `*_routes.py` moves signature-verification and dispatch code across files. Recommendation: **adopt the split** (it is behavior-preserving by construction — upstream kept module-attribute access precisely so monkeypatched tests stay valid, and the fork's webhook tests are the safety net), but treat the split commit as a sensitive change: **/sh-security-review required** (auth/webhook surface), and per the fork playbook surface the resolution choice to you before merge. No GPT-4.1 cross-family review strictly triggered (no IAM/Lambda-signature change), but the split of webhook verification code is a reasonable opt-in candidate. 2. **`langgraph.json` entrypoint retarget** — deployment-manifest change (graphs → `agent.graphs.*` shims; `http.app` unchanged). Verify the LangGraph deployment redeploys cleanly; decide whether fork-only `ci_monitor` gets a `agent/graphs/ci_monitor.py` shim (recommended, for symmetry) or keeps its old path (inconsistent). 3. **Reviewer consolidation** → `agent/review/` package: adopt. It is 9 pure moves + import rewires; fork's reviewer customizations (#1713 reconciliation, findings model, publish flow) ride along untouched. No content decision needed. 4. **CI-autofix cluster** (upstream deleted `agent/ci_autofix.py`; fork keeps it): confirm keep-baseline, and approve its post-split wiring point (its webhook triggers live in the split `github.py`/`common.py`) + test placement (`tests/github/`). 5. **UI `experiments/` adoption**: upstream renames `ported/` → `features/agents/experiments/` + `chat/` and swaps the 8-file tsconfig/eslint exclude lists for one glob. Adopt (pure simplification). Fork-diverged `DiffView.tsx`, `CloudPromptBar.tsx`, etc. keep fork content. 6. **Atlassian placements** (§2c): approve `jira_routes.py`/`confluence_routes.py` naming and where Connect lifecycle routes land — this is fork-only auth surface; your call on `connect_routes.py` vs folding into `confluence_routes.py`. 7. **Sequencing vs #1736 (bind cached GitHub tokens to users — priority security pick)**: recommend adopting the reorg **first**, because #1736 is written against `agent/webhooks/common.py`, which only exists after the split; it then lands near-clean (still gated by cross-review + /sh-security-review per its ledger row). ## 4. Hazards check (CLAUDE.md playbook) - **(a) Thin-router re-import rebinds:** post-reorg upstream is *clean* — `git grep 'from \.webhooks\.' 8356eb34 -- agent/webapp.py agent/api agent/webhooks/common.py` returns nothing; routes access handlers via `from . import github as service` + `service.X`/`common.X` module-attribute style, so the rebind hazard the playbook documents is designed out. Residual risk only if a hand-carry file drifts toward upstream *content*: `agent/webhooks/{github,linear,slack}.py` exist on both sides with different content (fork versions carry the fixes) — taking upstream's file here silently drops fork behavior. Same for `tests/conftest.py` and `tests/e2e/harness.py`. Rule for the exercise: **upstream content may only be taken for the 21 "A" structural shims**, everything else moves fork content. - **(b) Paired tests move with impls:** the map's test moves are 1:1 with unmoved impls (impl modules stay importable at old or shimmed paths), so no vertical splits arise; the 13 uncovered fork tests get the placements in §2c. Keep `tests/test_e2b_integration.py` (fork's sync E2B) as fork content when moving to `tests/sandbox/`. `tests/middleware/test_sandbox_recovery.py` → `tests/sandbox/` (fork has `tests/middleware/`? — it exists only as the upstream pre-path; fork's copy sits wherever it was cherry-picked; include in the mechanical sweep). - **(c) Tooling path references:** verified — - `Makefile`: only generic `tests/` (`TEST_FILE ?= tests/`) — unaffected; `make triage-render/-check` scripts don't reference moved paths. - `pyproject.toml`: `packages = ["agent"]`, `testpaths = ["tests"]` — both recursive, unaffected; **verify wheel includes `agent/resources/default_prompt.md`** (only real packaging check). - `.github/workflows/ci.yml`: references `tests/e2e/**` (unmoved) and `ui/` bun checks — unaffected; `reviewer-eval.yml` → `evals/` (unmoved). - `langgraph.json` — changes (see decision 2); fork-only `tests/e2e/langgraph.e2e.json` uses its own `agent_entrypoint.py` (imports `agent.server:traced_agent`, which remains valid) — no change needed. - UI: `@` alias root (`src/`) unchanged; `vite.config.ts`/`tsconfig.json`/`eslint.config.js` need only the exclude-glob swap; **bun stays** — do not import upstream's pnpm bits (fork oddly carries a `ui/pnpm-workspace.yaml`; consider deleting it in this exercise as lockfile hygiene, per playbook rule 7). - `scripts/`, `.githooks/`: no moved-path references found. - **(d) Public entrypoints:** upstream's claim "without changing its public entrypoints" is true **behaviorally, false textually** — `langgraph.json` graph strings all change to `agent.graphs.*` (verified by diff). HTTP surface (`agent.webapp:app`) and all webhook URLs unchanged. Fork must mirror the graph-string change plus its `ci_monitor` extra. ## 5. Payoff — the 20 deferred ledger rows (docs/upstream-sync/triage.md) Of the 19 deferred rows besides `8356eb34` itself: - **Clean picks after adoption (path remap was the blocker): 5** — #1732 dashboard label rendering, #1761 capitalize labels, #1744 collapse git panel, #1748 Linear issue search (test-path remap), #1742 defensive copy. - **Near-clean after adoption (small content deltas or review gates remain): 3** — **#1736 GitHub token binding** (biggest beneficiary: written against `webhooks/common.py`; auth gates still apply), #1758 sandbox-create JSON env, #1760 separate LangSmith key/endpoint (both additive to diverged `integrations/langsmith.py` — small reconciles). - **Still content reconciliation for fork-divergence reasons: 9** — #1724 (Slack NL plan approvals), #1731 (OpenAI Responses history vs #155 middleware), #1734 (Slack DMs), #1737 (reviewer diff bounds), #1741 (SSRF image fetches), #1733 (todos default-off vs fork prompt), #1735 (untagged two-party Slack), #1747 (Slack run links), #1719 (web-tool offload vs sync sandbox lifecycle). Adoption still removes their test/UI path-remap tax. - **Not path-related (product/deps decisions): 2** — #1727 (GPT-5.5 default; rides the no-OpenAI-models decision), #1745 (deepagents 0.7.0a7 alpha validation). Net: **8 of 19 rows become clean/near-clean**, and *every future upstream commit* stops needing path remaps — the standing tax on the cherry-pick pipeline disappears (recent won't-merge rows #1759/#1769 cited the layout mismatch explicitly). ## 6. Execution plan sketch (one commit per cascade class; validate ruff → `pytest --co` → unit → e2e per layer) Work on a throwaway branch off `dev`; promote as one PR with granular commits. 1. **C1 — docs/resources/assets** (~1h): `git mv` `INSTALLATION.md`/`CUSTOMIZATION.md` → `docs/`, `static/` → `assets/` (check README badge/image refs), `default_prompt.md` → `agent/resources/` + apply the `prompt.py` importlib.resources hunk + `agent/resources/__init__.py`; build wheel, confirm the .md ships. Gate: ruff + unit. 2. **C2 — `agent/review/` package** (~half day): mv 9 reviewer/style modules, rewrite the 38 importer files, add `agent/review/__init__.py` (fork's export surface). Gate: ruff + `pytest --co` + reviewer unit tests. 3. **C3 — graphs/runtime/providers shims + `langgraph.json`** (~2h): take upstream's 21 structural A-files nearly verbatim (they delegate to `agent.server` etc., which is fork code), add fork-only `agent/graphs/ci_monitor.py`, retarget `langgraph.json`. Gate: `pytest --co` + `make dev` boots all graphs. **Adam sign-off: decision 2.** 4. **C4 — FastAPI split** (~1–1.5 days, the critical commit): split fork's `webapp.py` → `webhooks/common.py` + `api/{app,health}.py` + `{github,linear,slack,jira,confluence}_routes.py` (+ Connect routes per decision 6); sed handlers `webapp.` → `common.`; retarget 240 test monkeypatch sites + `tests/conftest.py` + `tests/e2e/harness.py`; keep `webapp.py` shim. Gate: full unit + **E2E (Playwright + real LangGraph dev server)** — the only layer that catches wiring drift. **Adam sign-offs: decisions 1, 4, 6; /sh-security-review on this commit.** 5. **C5 — tests// moves** (~half day): mechanical map moves (§2a) + R<100 monkeypatch retargets not already done in C4 + the 13 fork-only placements (§2c). Gate: `pytest --co` + full unit (paths only, no logic). 6. **C6 — UI features/ moves** (~half day–1 day): `git mv` per map incl. `ported/` → `experiments|chat`, rewrite `@/` imports in the 54 importer files + moved files, tsconfig/eslint exclude glob, AgentPromptBar shim retarget, delete `ui/pnpm-workspace.yaml` (lockfile hygiene). Gate: `bunx tsc --noEmit` + bun build + Playwright e2e. **Adam sign-off: decision 5.** 7. **C7 — docs + ledger + memory** (~2h): update fork `CLAUDE.md`/`README`/`AGENTS.md` path references, flip `8356eb34` to Landed in `triage.jsonl` (+ re-triage notes on the 8 unblocked rows), update the open-swe project memory, Confluence architecture page if module map is documented there. **Effort estimate: 3–5 focused days** end-to-end, dominated by C4. Rollback story is clean: each commit is independently revertable until C4; after C4, C5/C6 are path-only. ## Spot-checks validating the classification method 1. **Mechanical** — `tests/test_dispatch.py → tests/agent/test_dispatch.py` listed `R100` in the diff-tree output (byte-identical move); fork's diverged copy moves via `git mv` untouched. 2. **Hand-carry (R<100)** — `agent/reviewer_publish.py → agent/review/publish.py` (R099): upstream's entire edit is the import block (`from .reviewer_findings import` → `from .findings import`, `from .utils.github_http import` → `from ..utils.github_http import`); no logic lines. Worst-scoring test `tests/test_pr_ready_auto_review.py` (R062): every hunk is `monkeypatch.setattr(webapp, ...)` → `monkeypatch.setattr(webhook_common, ...)` / `webapp.process_github_pr_ready` → `github_webhooks.process_github_pr_ready`. Worst-scoring fork-diverged UI file `AgentThreadView.tsx` (R092): grep of the rename diff shows 0 non-import changed lines. 3. **Consolidation** — post-reorg `agent/webapp.py` is exactly `from .api.app import app` (4 lines, verified via `git show 8356eb34:agent/webapp.py`); `agent/webhooks/github.py`'s reorg diff swaps `from agent import webapp` for `from . import common` and rewrites `webapp.X` → `common.X` with the docstring explicitly noting module-attribute access is preserved for monkeypatching. 4. **Test pairing** — `tests/conftest.py` reorg diff retargets the autouse auto-review fixture from `agent.webapp` to `agent.webhooks.common`, confirming test fixtures follow the split, not the moves. Working artifacts: `docs/upstream-sync/domain-reorg/{movemap-m50.txt, fork-divergence-status.txt, cross.json, forkonly.json, merge-tree-now.txt, head-tree.txt, upstream-pre-tree.txt, upstream-main-tree.txt}`.