open-swe/docs/upstream-sync/domain-reorg/reorg-scoping-report.md
Adam Moussa 373d888426
docs: land reorg in ledger, fix path refs, ship plan artifacts (C7)
C7 of the domain-reorg adoption (build plan docs/upstream-sync/domain-reorg/
reorg-build-plan.md, step C7):

- CLAUDE.md / AGENTS.md: retarget architecture path references to the new
  layout — graph entrypoints via agent.graphs.* shims, the agent/api/ +
  agent/webhooks/*_routes.py FastAPI split (webapp.py now a shim),
  agent/review/ package, tests/<domain>/ test paths, and the new-graph/
  test conventions. README.md already pointed at docs/ (C1) — no change.
- triage.jsonl: flip 8356eb34 (#1726) to landed on branch
  refactor/domain-reorg-adoption; add re-triage notes to the 8 unblocked
  rows (#1732/#1761/#1744/#1748/#1742 clean, #1736/#1758/#1760 near-clean).
  triage.md regenerated via make triage-render.
- Ship the plan, scoping report, move-map artifacts, and the
  domain-reorg-adoption workflow so the exercise is reproducible.

Memory + Confluence handled out-of-band (not in this commit): project
memory updated with the new module layout; Confluence check found no IT
page documents the repo module map — no Confluence change required.
2026-07-17 15:01:45 -04:00

163 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/<domain>/`, `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.<name>:<same symbol>`. 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/<domain>/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/<source>.py` with HTTP routes in `agent/webhooks/<source>_routes.py`; tests in `tests/<domain>/`.
**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/<domain>/ 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}`.