From fb8d6f950bc9ec2c64fa99ef50d7ff27a866eef2 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 12 Nov 2026 09:17:26 -0800 Subject: [PATCH] refactor(github): organize concrete feature owners (#41601) --- .github/workflows/mobile.yml | 4 +- config/flaky-test-pattern-baseline.txt | 16 +- .../process-host-direct-import-baseline.txt | 2 +- config/repo-host-read-baseline.txt | 4 +- .../vitest-node-runtime-files.mjs | 2 +- ...sconfig.test.mobile-host-declarations.json | 2 +- .../src/components/MobileMarkdownCallout.tsx | 2 +- .../mobile-markdown-links-context.ts | 2 +- .../mobile-markdown-mdast-nodes.d.ts | 2 +- mobile/src/components/mobile-markdown-tree.ts | 2 +- .../pr-sidebar/MobileLinkPrForm.tsx | 2 +- .../pr-sidebar/MobilePrTabSheets.tsx | 4 +- .../components/pr-sidebar/PRCheckDetail.tsx | 2 +- .../components/pr-sidebar/PRCommentCard.tsx | 2 +- .../pr-sidebar/PRCommentsSection.tsx | 4 +- .../pr-sidebar/PrCheckFailureRow.tsx | 2 +- .../components/pr-sidebar/PrChecksScreen.tsx | 2 +- .../pr-sidebar/PrReviewersSheet.tsx | 4 +- .../pr-sidebar/PrTabBottomAction.tsx | 4 +- .../components/pr-sidebar/PrThreadCode.tsx | 2 +- .../src/components/pr-sidebar/PrThreadRow.tsx | 4 +- .../components/pr-sidebar/PrThreadScreen.tsx | 2 +- .../components/pr-sidebar/pr-actions-state.ts | 9 +- .../pr-sidebar/pr-check-detail-content.ts | 2 +- .../pr-sidebar/pr-checks-presentation.ts | 4 +- .../src/components/pr-sidebar/pr-tab-gates.ts | 8 +- .../pr-sidebar/use-mobile-pr-polling.ts | 6 +- .../pr-sidebar/use-mobile-pr-tab.ts | 2 +- .../pr-sidebar/use-pr-check-run-details.ts | 2 +- .../components/pr-sidebar/use-pr-tab-menu.ts | 4 +- .../pr-sidebar/use-reviewer-people.ts | 6 +- .../components/use-mobile-markdown-tree.ts | 2 +- .../mobile-web-shell/bridge-host-requests.ts | 2 +- .../source-selection/smart-source-fan-out.ts | 4 +- .../smart-source-paste-intent.ts | 8 +- .../smart-source-search-requests.ts | 2 +- .../use-mobile-composer-source.ts | 2 +- .../use-smart-workspace-source.ts | 2 +- .../use-mobile-native-chat-markdown-links.ts | 4 +- .../mobile-source-control-next-step.ts | 8 +- .../mobile-source-control-primary-action.ts | 2 +- .../review/github-pr-check-reply-schema.ts | 2 +- .../review/github-pr-entity-reply-schema.ts | 13 +- .../review/github-pr-mutations.ts | 2 +- .../review/github-pr-read-reply-schema.ts | 4 +- .../source-control/review/github-pr-rpc.ts | 15 +- .../review/github-target-invocation.ts | 2 +- .../review/github-work-item-operation.ts | 4 +- .../review/mobile-diff-review-github.test.ts | 4 +- .../review/mobile-diff-review-github.ts | 6 +- .../review/mobile-pr-queries.ts | 14 +- .../review/mobile-pr-sidebar-state.ts | 12 +- .../review/pr-action-mutation-contract.ts | 2 +- .../review/pr-actions-engine.ts | 7 +- .../review/pr-ai-triage-prompt.ts | 7 +- .../review/pr-comment-actions.ts | 7 +- .../review/pr-comment-writes.ts | 4 +- .../source-control/review/pr-title-edit.ts | 2 +- .../review/use-mobile-pr-comment-actions.ts | 2 +- .../use-mobile-pr-sidebar-controller.ts | 2 +- .../review/use-mobile-pr-text-action.ts | 2 +- .../use-mobile-source-control-next-step.ts | 4 +- .../hosted-review/github-check-summary.ts | 2 +- .../hosted-review/gitlab-check-summary.ts | 2 +- .../mobile-hosted-check-status.ts | 4 +- .../mobile-task-pr-check-refresh.ts | 6 +- .../mobile-tasks-hosted-review.ts | 2 +- .../tasks/item-detail/github-task-identity.ts | 2 +- .../item-detail/mobile-tasks-item-mapping.ts | 2 +- .../mobile-tasks-reviewer-linear.ts | 2 +- .../use-mobile-tasks-item-detail-loading.tsx | 4 +- ...ile-tasks-item-detail-metadata-effects.tsx | 2 +- .../mobile-tasks-provider-detail-types.ts | 6 +- .../tasks/mobile-tasks-view-state-types.ts | 4 +- .../project-board/github-project-reference.ts | 4 +- .../github-project-repo-match.ts | 2 +- .../mobile-tasks-github-project-list.tsx | 2 +- .../mobile-tasks-github-project-pickers.tsx | 2 +- .../mobile-tasks-project-fields.ts | 11 +- .../task-project-board-reply-schema.ts | 2 +- ...e-mobile-tasks-project-loading-actions.tsx | 2 +- .../use-mobile-tasks-project-projection.tsx | 10 +- ...le-tasks-project-repository-resolution.tsx | 2 +- ...-mobile-tasks-provider-view-projection.tsx | 2 +- ...bile-tasks-workspace-and-project-state.tsx | 2 +- .../mobile-task-source-search-operations.ts | 2 +- .../tasks/task-list/mobile-tasks-options.tsx | 4 +- ...use-mobile-tasks-provider-load-actions.tsx | 4 +- .../task-provider-entity-reply-schema.ts | 4 +- .../rpc-recording/changed-page-outcomes.ts | 2 +- .../src/worktree/workspace-details-model.ts | 2 +- src/main/azure-devops/pull-request-mappers.ts | 2 +- src/main/bitbucket/client.ts | 5 +- src/main/bitbucket/pull-request-mappers.ts | 2 +- .../hosted-cli/gh-bound-account-env.ts | 4 +- .../runner-gh-account-binding.test.ts | 51 +--- ...ge-tree-object-quarantine-real-git.test.ts | 4 +- .../git/remotes/remote-name-listing.test.ts | 24 +- src/main/git/remotes/remote-name-listing.ts | 2 +- .../remotes/task-repository-eligibility.ts | 6 +- .../remotes/worktree-push-target-cleanup.ts | 2 +- src/main/gitea/pull-request-mappers.ts | 5 +- .../account-binding-required-host.test.ts | 0 .../account-binding-required-host.ts | 11 +- .../{ => accounts}/auth-diagnose.test.ts | 2 +- .../github/{ => accounts}/auth-diagnose.ts | 4 +- .../gh-account-binding-inventory.ts | 6 +- .../github/{ => accounts}/gh-account-token.ts | 8 +- .../{ => accounts}/gh-capability-state.ts | 8 +- .../github-project-account.test.ts | 48 +-- .../{ => accounts}/github-project-account.ts | 20 +- .../project-account-operations.test.ts | 121 ++------ .../{ => api}/gh-error-classification.ts | 4 +- .../github-read-response.ts} | 9 +- .../github-rest-revalidation.test.ts | 25 +- .../{ => api}/github-rest-revalidation.ts | 16 +- src/main/github/{ => api}/rate-limit.test.ts | 6 +- src/main/github/{ => api}/rate-limit.ts | 12 +- .../{ => attachments}/body-image-urls.test.ts | 0 .../{ => attachments}/body-image-urls.ts | 0 .../image-attachment-upload.ts | 11 +- .../client-pr-lookup-coalescing.test.ts | 181 ----------- .../github/client-rate-limit-block.test.ts | 102 ------- .../github/client-stack-merge-guard.test.ts | 91 ------ src/main/github/client.ts | 49 --- .../check/check-detail-field-mapping.ts | 2 +- .../check-details-admission-lifetime.test.ts | 30 +- .../check-details-admission-snapshot.test.ts | 8 +- .../check-details-capture-diagnostics.test.ts | 25 +- .../check/check-job-log-download.test.ts | 106 +------ .../client/check/check-job-log-download.ts | 11 +- .../client/check/check-job-log-execution.ts | 6 +- .../check/check-job-log-subscribers.test.ts | 4 +- .../client/check/check-job-log-tails.test.ts | 73 ----- .../client/check/check-job-log-tails.ts | 6 +- .../check/check-job-log-test-fixture.ts | 4 +- .../complete-pr-check-observation.test.ts | 8 +- .../check/complete-pr-check-observation.ts | 2 +- .../check/get-pr-check-details.test.ts} | 40 ++- .../client/check/get-pr-check-details.ts | 11 +- src/main/github/client/check/get-pr-checks.ts | 24 +- .../github/client/check/pr-check-rollup.ts | 7 +- .../check/pr-checks-response-mapping.ts | 9 +- .../github/client/check/rerun-pr-checks.ts | 16 +- .../client/check/rest-pr-check-pages.ts | 7 +- .../{ => client}/client-test-harness.ts | 20 +- .../github/{ => client}/client-test-mocks.ts | 127 +++++--- .../client/create/add-pr-review-comment.ts | 18 +- .../create-github-pull-request.test.ts} | 77 ++--- .../create/create-github-pull-request.ts | 15 +- .../client/create/pull-request-template.ts | 6 +- .../hydrate-work-item-merge-metadata.ts | 2 +- .../detect/repository-merge-metadata-cache.ts | 2 +- .../repository-merge-metadata-reuse.test.ts | 10 +- .../detect/repository-merge-metadata.ts | 13 +- .../client/fetch/authenticated-viewer.ts | 5 +- .../github/client/fetch/get-pr-comments.ts | 17 +- .../get-work-item-check-summary.test.ts} | 50 ++- .../get-work-item-details-reuse.test.ts} | 42 ++- .../get-work-item-origin-preference.test.ts} | 91 ++++-- .../fetch/get-work-item-preview-host.test.ts} | 62 +++- src/main/github/client/fetch/get-work-item.ts | 24 +- src/main/github/client/fetch/orca-star.ts | 3 +- .../client/fetch/pr-conversation-response.ts | 8 +- .../github/client/fetch/repo-slug-upstream.ts | 35 ++- .../github/client/fetch/work-item-fetch.ts | 9 +- src/main/github/client/gh-error-predicates.ts | 6 +- src/main/github/client/github-exec-scope.ts | 12 +- .../github/client/list/count-work-items.ts | 26 +- .../list-work-items-source-guard.test.ts} | 65 ++-- .../github/client/list/list-work-items.ts | 14 +- .../client/list/work-item-issue-page.ts | 8 +- .../client/list/work-item-list-request.ts | 7 +- .../github/client/list/work-item-pages.ts | 13 +- .../github/client/list/work-item-pr-page.ts | 5 +- .../client/list/work-item-search-batch.ts | 11 +- ...k-item-search-fallback-environment.test.ts | 27 ++ .../list}/work-item-search-isolation.test.ts | 10 +- .../client/list/work-item-search-page.ts | 8 +- .../lookup/branch-lookup-derived-data.ts | 7 +- .../client/lookup/branch-lookup-resolution.ts | 26 +- .../get-pr-for-branch-host-scope.test.ts} | 64 ++-- .../get-pr-for-branch-upstream-owner.test.ts} | 60 +++- .../github/client/lookup/get-pr-for-branch.ts | 2 +- .../lookup/implicit-default-branch-pr.ts | 12 +- .../pr-branch-lookup-revalidation.test.ts} | 60 +++- .../github/client/lookup/pr-branch-lookup.ts | 18 +- .../pr-for-branch-outcome-coalescing.test.ts | 77 +++++ .../client/lookup/pr-for-branch-outcome.ts | 13 +- .../github/client/lookup/pr-last-outcome.ts | 2 +- .../lookup/pr-lookup-rate-limit.test.ts | 121 ++++++++ .../client/lookup/pr-lookup-rate-limit.ts | 11 +- .../github/client/lookup/pr-number-lookup.ts | 17 +- .../lookup/pr-refresh-outcome-assembly.ts | 8 +- .../client/lookup/pr-stack-summary-cache.ts | 11 +- .../client/lookup/pull-request-lookup-data.ts | 16 +- .../lookup/pull-request-lookup-hydration.ts | 4 +- .../client/lookup/pull-request-push-target.ts | 24 +- .../pull-request-sidebar-query.test.ts | 97 +----- .../lookup/pull-request-sidebar-query.ts | 7 +- .../client/lookup/shared-pr-provider-reads.ts | 4 +- .../client/lookup/tracked-upstream-branch.ts | 4 +- .../client/lookup/tracked-upstream-cache.ts | 6 +- .../client/map/review-comment-response.ts | 4 +- .../client/map/work-item-field-coercion.ts | 4 +- src/main/github/client/map/work-item.ts | 2 +- src/main/github/client/merge/merge-pr.ts | 15 +- src/main/github/client/merge/pr-auto-merge.ts | 13 +- .../client/pull-request-lookup-candidates.ts | 10 +- .../client/update/pr-comment-reaction.ts | 13 +- src/main/github/client/update/pr-details.ts | 17 +- .../github/client/update/pr-file-viewed.ts | 11 +- src/main/github/client/update/pr-ready.ts | 16 +- src/main/github/client/update/pr-reviewers.ts | 9 +- src/main/github/client/update/pr-state.ts | 16 +- .../client/update/resolve-review-thread.ts | 9 +- .../work-item-source-selection.test.ts} | 168 +++++----- .../{ => comments}/comment-reactions.ts | 5 +- .../github/execution/github-legacy-command.ts | 5 + .../github-operation-slots.test.ts} | 2 +- .../execution/github-operation-slots.ts | 54 ++++ src/main/github/gh-capability-state.test.ts | 41 --- src/main/github/gh-utils.ts | 95 ------ .../github/github-read-execution-scope.ts | 1 - src/main/github/{ => issues}/issue-comment.ts | 17 +- src/main/github/{ => issues}/issue-create.ts | 14 +- .../{ => issues}/issue-details-query.ts | 2 +- .../{ => issues}/issue-field-options.ts | 14 +- .../github/{ => issues}/issue-timeline.ts | 11 +- src/main/github/{ => issues}/issue-update.ts | 22 +- .../issue-work-item-details-fresh.test.ts | 39 +-- .../{ => issues}/issue-work-item-details.ts | 30 +- src/main/github/{ => issues}/issues.test.ts | 151 ++------- src/main/github/{ => issues}/issues.ts | 16 +- ...fresh-coordinator-completion-order.test.ts | 197 ------------ ...resh-coordinator-foreground-pacing.test.ts | 126 -------- ...refresh-coordinator-refresh-events.test.ts | 87 ------ ...resh-coordinator-visibility-policy.test.ts | 135 --------- ...resh-coordinator-visible-follow-up.test.ts | 75 ----- .../pr-watch/pr-chat-action-recorder.test.ts | 2 +- .../pr-watch/pr-chat-action-recorder.ts | 2 +- .../github/pr-watch/pr-earlier-head-jobs.ts | 6 +- .../github/pr-watch/pr-head-check-read.ts | 11 +- .../pr-watch/pr-pending-action-resume.ts | 4 +- .../pr-watch/pr-pending-action-types.ts | 2 +- .../pr-watch/pr-push-remote-resolution.ts | 2 +- .../github/pr-watch/pr-watch-service.test.ts | 8 +- src/main/github/pr-watch/pr-watch-service.ts | 14 +- .../github/pr-watch/pr-watch-test-fixtures.ts | 2 +- src/main/github/pr-watch/pr-watch-types.ts | 4 +- .../github/project-bound-gh-requests.test.ts | 121 -------- src/main/github/project-view.ts | 41 --- src/main/github/project-view/internals.ts | 248 --------------- .../project-view/issue-comment-mutations.ts | 12 +- .../project-view/issue-type-mutation.ts | 14 +- src/main/github/project-view/mutations.ts | 26 +- .../project-argument-validation.ts | 69 +++++ .../project-error-classification.ts | 4 +- .../project-view/project-field-mutations.ts | 9 +- .../github/project-view/project-graphql.ts | 87 ++++++ .../project-view/project-host-execution.ts | 30 ++ .../project-request-host-auth.test.ts} | 15 +- src/main/github/project-view/project-rest.ts | 59 ++++ .../github/project-view/project-view-cache.ts | 4 +- .../project-view/project-view-config.test.ts | 108 ------- .../project-view/project-view-config.ts | 9 +- .../project-view/project-view-discovery.ts | 11 +- .../project-view-field-normalization.ts | 2 +- .../project-view-item-normalization.ts | 4 +- .../project-view/project-view-item-page.ts | 19 +- .../github/project-view/project-view-items.ts | 10 +- .../project-view/project-view-listing.ts | 8 +- .../project-view-reference.test.ts} | 9 +- .../project-view/project-view-reference.ts | 19 +- .../github/project-view/project-view-table.ts | 15 +- .../project-view/pull-request-mutation.ts | 10 +- .../project-view/repository-field-options.ts | 25 +- ...-item-details-body-image-adapters.test.ts} | 25 +- .../github/project-view/work-item-details.ts | 16 +- .../conflicts}/conflict-summary-cache.ts | 4 +- .../conflicts}/conflict-summary.test.ts | 24 +- .../conflicts}/conflict-summary.ts | 16 +- .../files}/pr-review-comment-lines.test.ts | 0 .../files}/pr-review-comment-lines.ts | 0 .../pull-request-detail-context.test.ts | 35 +-- .../files}/pull-request-detail-context.ts | 15 +- .../files}/pull-request-file-contents.ts | 26 +- .../files}/pull-request-file-data.ts | 18 +- .../refresh}/merged-pr-commit-membership.ts | 21 +- .../pr-mutation-review-invalidation.ts | 4 +- .../refresh}/pr-refresh-candidate-policy.ts | 12 +- ...oordinator-active-visible-priority.test.ts | 18 +- ...fresh-coordinator-alias-coalescing.test.ts | 16 +- ...fresh-coordinator-completion-order.test.ts | 58 ++++ .../pr-refresh-coordinator-test-harness.ts | 4 +- .../pr-refresh-coordinator-test-mocks.ts | 2 +- ...esh-coordinator-wsl-stopped-distro.test.ts | 23 +- .../refresh}/pr-refresh-coordinator.ts | 9 +- .../pr-refresh-error-classification.test.ts | 0 .../pr-refresh-error-classification.ts | 6 +- .../refresh}/pr-refresh-event-publisher.ts | 4 +- .../refresh}/pr-refresh-pacing.ts | 0 .../refresh}/pr-refresh-queue-drainer.ts | 11 +- .../pr-refresh-queue-growth-bound.test.ts | 62 +--- .../refresh}/pr-refresh-queue.ts | 4 +- .../refresh}/pr-refresh-rate-limit-gate.ts | 12 +- .../refresh}/pr-refresh-renderer-port.ts | 0 .../refresh}/pr-refresh-retry-state.ts | 4 +- .../pr-refresh-validation-backoff.test.ts | 14 +- .../refresh}/pr-refresh-validation-backoff.ts | 2 +- .../refresh}/pr-refresh-visibility.test.ts | 0 .../refresh}/pr-refresh-visibility.ts | 2 +- .../pr-refresh-watch-derived-data.test.ts | 29 +- .../refresh}/pr-refresh-watch-jobs.ts | 6 +- .../pull-request-changed-events.test.ts | 4 +- .../refresh}/pull-request-changed-events.ts | 6 +- .../stacks}/github-pr-stack-async-merge.ts | 16 +- .../stacks}/github-pr-stack.test.ts | 7 +- .../stacks}/github-pr-stack.ts | 20 +- .../stacks}/github-stack-api-responses.ts | 2 +- .../stacks}/stacked-pr-creation.test.ts | 21 +- .../stacks}/stacked-pr-creation.ts | 26 +- .../pr-head-tracking-ref.test.ts | 2 +- .../start-points}/pr-head-tracking-ref.ts | 12 +- .../start-points}/pr-start-point-wsl-gate.ts | 6 +- .../start-points}/pr-start-point.test.ts | 9 +- .../start-points}/pr-start-point.ts | 21 +- .../start-points}/review-head-remote.ts | 10 +- .../github-api-repository-probe.ts | 0 ...github-api-repository-remote-probe.test.ts | 32 +- .../github-api-repository-remote-probe.ts | 9 +- .../github-api-repository-validation.ts | 4 +- .../github-api-repository.test.ts | 83 +---- .../github-api-repository.ts | 32 +- .../github-enterprise-repository.test.ts | 10 +- .../github-enterprise-repository.ts | 23 +- .../github-owner-repo-selection.ts | 17 +- .../github-remote-identity-parsing.ts | 4 +- .../github-repository-client-refusal.test.ts} | 72 +++-- .../github-repository-host.ts | 2 +- .../github-repository-identity-cache.test.ts} | 10 +- ...epository-identity.fork-owner-repo.test.ts | 14 +- ...b-repository-identity.signed-cache.test.ts | 45 +-- ...repository-identity.ssh-host-alias.test.ts | 10 +- .../github-repository-identity.ts | 30 +- .../github-ssh-host-alias-resolution.ts | 8 +- .../local-git-config-signature.ts | 2 +- .../work-item-details-enterprise-host.test.ts | 116 ------- ...k-item-search-fallback-environment.test.ts | 47 --- .../work-item-details-pr-files.test.ts | 37 ++- .../work-item-details-test-lookup.ts | 5 +- .../work-item-details.test.ts | 37 ++- .../{ => work-items}/work-item-details.ts | 60 ++-- .../work-item-participants.ts | 20 +- .../work-item-search-test-harness.ts | 33 +- .../github-issue-link-title-fill.test.ts | 10 +- .../github-issue-link-title-fill.ts | 24 +- .../github-issue-link-title-wiring.ts | 22 +- .../issues/gitlab-issue-source-resolution.ts | 2 +- .../merge-request-state-mutations.ts | 2 +- .../merge-requests/merge-request-update.ts | 2 +- .../merge-requests/review-project-identity.ts | 5 +- .../projects/gitlab-project-ref-resolution.ts | 2 +- .../requests/gitlab-response-mapping.ts | 2 +- .../requests/glab-request-admission.test.ts | 2 +- src/main/host/electron-pr-refresh-renderer.ts | 2 +- src/main/ipc/repos/repo-update-handler.ts | 2 +- .../source-control/github-account-handlers.ts | 8 +- .../source-control/github-ipc-test-harness.ts | 2 +- .../github-issue-mutation-handlers.ts | 10 +- .../github-pr-mutation-handlers.ts | 2 +- .../source-control/github-pr-read-handlers.ts | 4 +- .../github-pr-refresh-handlers.ts | 8 +- .../github-pr-review-handlers.ts | 4 +- .../github-project-view-handlers.ts | 2 +- .../github-repo-access-guards.test.ts | 286 +++++++++--------- .../github-repo-routing-twin-host.test.ts | 2 +- .../ipc/source-control/github-repo-routing.ts | 4 +- .../github-work-item-handlers.ts | 12 +- .../workspace-cleanup-candidate.ts | 2 +- .../workspace-cleanup-git-evidence.ts | 2 +- .../workspace-cleanup-merge-evidence.ts | 2 +- .../workspace-cleanup-scan.ts | 2 +- src/main/ipc/worktree-remote.ts | 2 +- ...worktrees-orphan-directory-cleanup.test.ts | 23 +- .../ipc/worktrees-remove-preflight.test.ts | 23 +- .../create/register-review-base-handlers.ts | 8 +- .../removal/worktree-removal-ownership.ts | 2 +- src/main/orcad/orcad-entry.ts | 3 +- .../tracking-repos/repo-hydration.ts | 2 +- .../tracking-repos/repo-sanitization.ts | 7 +- .../tracking-repos/repo-update-operations.ts | 8 +- .../repository-icons/repo-icon-autodetect.ts | 2 +- .../construction/runtime-construction.ts | 4 +- .../runtime-github-issue-comment-commands.ts | 18 +- .../github/runtime-github-project-commands.ts | 2 +- ...untime-github-repository-query-commands.ts | 18 +- ...runtime-github-review-mutation-commands.ts | 6 +- .../runtime-github-review-query-commands.ts | 7 +- .../github/runtime-github-worktree-base.ts | 8 +- .../gitlab/runtime-gitlab-worktree-base.ts | 2 +- .../reviews/runtime-hosted-review-commands.ts | 39 +-- .../runtime-hosted-review-pr-reuse.test.ts | 17 +- .../mobile-desktop-relay.ts | 2 +- .../relay-github-authority.ts | 4 +- .../github-hosted-review-test-harness.ts | 197 ++++++------ .../runtime-repository-settings-controller.ts | 2 +- .../sidebar-inputs-feed-announcements.test.ts | 213 ------------- .../runtime-workspace-catalog-events.ts | 2 +- src/main/source-control/forge-provider.ts | 10 +- .../source-control/forge-review-mappers.ts | 2 +- .../hosted-review-base-ref-suffix.test.ts | 3 +- ...hosted-review-creation-eligibility.test.ts | 29 +- .../hosted-review-creation-git-state.ts | 2 +- ...review-creation-gitlab-self-hosted.test.ts | 12 +- .../hosted-review-creation-provider.ts | 5 +- .../hosted-review-creation.test.ts | 29 +- .../source-control/hosted-review-creation.ts | 4 +- ...sted-review-execution-host-routing.test.ts | 25 +- .../hosted-review-git-options.ts | 2 +- src/main/source-control/hosted-review.test.ts | 175 +---------- src/main/source-control/hosted-review.ts | 5 +- .../pull-request-linked-issue.ts | 2 +- .../stacked-hosted-review-creation.ts | 11 +- src/main/star-nag/agent-value-moment.ts | 2 +- src/main/star-nag/direct-star-attempt.ts | 2 +- src/main/star-nag/service-direct-star.test.ts | 63 +--- .../star-nag/service-force-show-races.test.ts | 2 +- .../star-nag/service-threshold-prompt.test.ts | 262 ---------------- src/main/star-nag/service.ts | 2 +- .../startup/main-process-runtime-service.ts | 2 +- .../api/gh-bridge-mutations-and-projects.ts | 15 +- .../gh-bridge-pull-requests-and-work-items.ts | 13 +- src/preload/api/github-account-api.ts | 2 +- src/preload/api/github-pull-request-api.ts | 16 +- src/preload/api/github-work-item-api.ts | 14 +- src/preload/api/repository-api.ts | 2 +- src/preload/api/workspace-session-api.ts | 2 +- .../components/cmd-j/task-search-results.ts | 2 +- .../worktree-checks-review-index.test.ts | 4 +- .../cmd-j/worktree-checks-review-index.ts | 2 +- .../crash-report/CrashReportDialogSurface.tsx | 2 +- .../actions/check-annotation-path.ts | 2 +- .../actions/check-run-clipboard-text.ts | 2 +- .../actions/check-run-details-fix-context.ts | 7 +- .../actions/check-run-details-fix-with-ai.ts | 5 +- .../actions/check-run-details-tab.ts | 4 +- .../panel/CheckRunAnnotations.tsx | 2 +- .../panel/CheckRunDetailsPanel.tsx | 10 +- .../check-details/panel/CheckRunJobs.tsx | 2 +- .../reading/check-details-reading-cache.ts | 5 +- .../reading/check-details-reading-fixture.ts | 2 +- .../render-tree/markdown-preview-plugins.ts | 2 +- .../use-markdown-preview-components.tsx | 2 +- .../github-checks-tab-state.test.ts | 2 +- .../src/components/github-checks-tab-state.ts | 4 +- .../conversation-tab-activity.tsx | 7 +- .../conversation-tab-comment-card.tsx | 5 +- .../conversation-tab-description.tsx | 4 +- .../conversation-tab-pr-sidebar.tsx | 6 +- .../conversation-tab-timeline.tsx | 2 +- .../discuss-item/conversation-tab.tsx | 17 +- .../discuss-item/gh-comment-composer.tsx | 4 +- .../issue-conversation-entries.ts | 2 +- .../gh-edit-section-assignees.tsx | 2 +- .../gh-edit-section-horizontal.tsx | 4 +- .../gh-edit-section-mutations.ts | 4 +- .../gh-edit-section-status-popover.tsx | 2 +- .../gh-edit-section-top-columns.tsx | 4 +- ...gh-edit-section.assignee-identity.test.tsx | 2 +- .../edit-item-fields/gh-edit-section.tsx | 4 +- .../edit-item-fields/gh-edit-task-record.ts | 2 +- .../checks-tab-actions.ts | 6 +- .../checks-tab-check-details.tsx | 2 +- .../checks-tab-check-row.tsx | 2 +- .../checks-tab-request-details.ts | 4 +- .../inspect-pull-request/checks-tab.tsx | 15 +- .../pr-files-combined-diff-body.tsx | 2 +- .../pr-files-combined-diff-load.ts | 6 +- .../pr-files-combined-diff-signature.ts | 5 +- .../pr-files-combined-diff-toolbar.tsx | 2 +- .../pr-actions-merge-menu.tsx | 4 +- .../land-pull-request/pr-actions-panel.tsx | 6 +- .../land-pull-request/pr-reviewers-panel.tsx | 6 +- .../pr-reviewers-picker-row.tsx | 2 +- .../pr-reviewers-request-actions.test.ts | 59 ---- .../pr-reviewers-request-actions.ts | 10 +- .../pr-reviewers-requested-list.tsx | 2 +- .../github-item-dialog-types.ts | 4 +- .../github-work-item-target-context.ts | 19 +- .../pr-file-content-cache.ts | 12 +- .../pr-file-viewed-change.ts | 4 +- .../use-github-item-dialog-details.test.tsx | 4 +- .../use-github-task-refresh.test.tsx | 2 +- .../use-github-task-refresh.ts | 4 +- .../use-github-work-item-details.ts | 8 +- .../work-item-details-cache.ts | 12 +- .../work-item-details-fetch-settle.ts | 18 +- .../work-item-state-badge.tsx | 2 +- .../github-item-dialog-issue-body.tsx | 6 +- .../github-item-dialog-issue-header.tsx | 4 +- .../github-item-dialog-pr-header.tsx | 2 +- .../github-item-dialog-pr-tabs.tsx | 6 +- .../github-item-dialog.task-record.test.tsx | 2 +- .../open-dialog/github-item-dialog.tsx | 2 +- .../use-github-item-dialog-link-copy.ts | 2 +- .../work-item-issue-source-indicator.tsx | 4 +- .../src/components/github-pr-merge-state.ts | 2 +- .../github-project/GhAuthErrorHelp.tsx | 4 +- .../github-project/ProjectBoard.test.tsx | 173 ----------- .../github-project/ProjectBoard.tsx | 9 +- .../github-project/ProjectBoardCard.tsx | 2 +- .../ProjectCell.test-fixture.tsx | 2 +- .../components/github-project/ProjectCell.tsx | 4 +- .../github-project/ProjectCellIdentity.tsx | 5 +- .../github-project/ProjectCellIssueType.tsx | 5 +- .../ProjectCellRepositoryEditors.tsx | 2 +- .../ProjectCellSelectionEditors.tsx | 2 +- .../ProjectCellValueEditors.tsx | 5 +- .../github-project/ProjectGroupHeader.tsx | 2 +- .../github-project/ProjectPicker.tsx | 6 +- .../github-project/ProjectPickerBrowse.tsx | 8 +- .../github-project/ProjectPickerPanels.tsx | 9 +- .../github-project/ProjectRoadmap.tsx | 9 +- .../github-project/ProjectRoadmapBar.tsx | 4 +- .../components/github-project/ProjectRow.tsx | 2 +- .../ProjectViewList.fixture.tsx | 2 +- .../github-project/ProjectViewList.tsx | 4 +- .../github-project/ProjectViewStates.tsx | 9 +- .../github-project/ProjectViewToolbar.tsx | 2 +- .../github-project/ProjectViewWrapper.tsx | 2 +- .../github-project/column-widths.ts | 2 +- .../src/components/github-project/columns.ts | 5 +- .../github-project-picker-filter.test.ts | 38 +-- .../github-project-picker-filter.ts | 4 +- .../github-project/group-sort.test.ts | 4 +- .../project-picker-browse-cache.ts | 2 +- .../github-project/project-picker-input.ts | 4 +- .../github-project/project-picker-runtime.ts | 6 +- .../project-picker-selection.ts | 2 +- .../github-project/project-row-filtering.ts | 5 +- .../github-project/project-view-error-copy.ts | 2 +- ...ew-wrapper-source-context-boundary.test.ts | 2 +- .../project-visible-table-cache.ts | 2 +- .../github-project/project-work-item.ts | 6 +- .../github-project/roadmap-tick-format.ts | 12 +- .../github-project/roadmap-zoom-preference.ts | 2 +- .../github-project/slug-dialog/Comments.tsx | 2 +- .../slug-dialog/SlugDialogBody.tsx | 2 +- .../github-project/useProjectPickerBrowse.ts | 4 +- .../github-project/useProjectRowActions.ts | 9 +- .../github-project/useProjectRowMutations.ts | 2 +- .../github-project/useProjectViewTable.ts | 8 +- .../components/github/CommentCodeContext.tsx | 6 +- .../components/github/CommentReactions.tsx | 5 +- .../github/GitHubMarkdownComposer.test.tsx | 18 +- .../github/GitHubMarkdownComposer.tsx | 6 +- .../github/IssueSourceIndicator.tsx | 4 +- .../components/github/IssueSourceSelector.tsx | 2 +- .../components/github/PRAssigneesPanel.tsx | 6 +- .../components/github/PRFilterDropdowns.tsx | 6 +- .../github-comment-image-upload-address.ts | 2 +- .../github-duplicate-issue-candidates.ts | 6 +- .../components/github/github-image-upload.ts | 2 +- .../github-markdown-composer-preview-pane.tsx | 2 +- ...ithub-pr-reviewer-candidate-filter.test.ts | 29 -- .../github-pr-reviewer-candidate-filter.ts | 2 +- .../github/github-rate-limit-display.tsx | 2 +- .../github-work-item-assignee-filter.ts | 2 +- .../github-work-item-comment-mutations.ts | 4 +- .../github/github-work-item-edit-mutations.ts | 6 +- .../github/github-work-item-identity.ts | 9 +- .../github/pr-check-presentation.ts | 2 +- .../components/github/pr-file-content-size.ts | 2 +- .../components/github/pr-file-diff-mapping.ts | 6 +- .../github/pull-request-confirmation-copy.ts | 2 +- .../github/work-item-state-badge.tsx | 2 +- .../github/work-item-state-presentation.tsx | 4 +- .../components/markdown-github-callout.tsx | 2 +- .../previews/NativeChatGitHubLinkPeek.tsx | 4 +- .../native-chat-github-link-preview.ts | 6 +- .../links/previews/native-chat-github-repo.ts | 4 +- .../NativeChatOpenedPullRequestCard.tsx | 4 +- .../new-workspace/composer-modal-data.ts | 2 +- .../github-source-consumers.test.tsx | 2 +- .../new-workspace-composer-card-props.ts | 2 +- ...smart-workspace-github-direct-link.test.ts | 2 +- .../smart-workspace-github-direct-link.ts | 4 +- .../smart-workspace-name-field-model.ts | 6 +- .../smart-workspace-repo-slug.ts | 4 +- .../use-smart-workspace-github-search.ts | 4 +- .../use-smart-workspace-name-field-actions.ts | 2 +- .../use-work-item-source-presentation.ts | 2 +- .../use-work-item-source-state.ts | 4 +- .../work-item-source-selection.ts | 2 +- .../src/components/pr-check-counts.ts | 8 +- .../pr-check-empty-details.fixture.tsx | 2 +- .../src/components/pr-check-empty-details.tsx | 4 +- .../pr-check-owner-presentation.test.tsx | 96 ------ .../src/components/pr-check-refresh.test.ts | 126 -------- .../components/pr-check-tab-actions.test.tsx | 156 ---------- .../actions/merge-actions.test.ts | 107 ------- .../actions/merge-actions.ts | 10 +- .../pull-request-page/actions/panel.tsx | 4 +- .../cache/file-content.test.ts | 71 ----- .../pull-request-page/cache/file-content.ts | 13 +- .../checks/details-request.ts | 8 +- .../pull-request-page/checks/details.tsx | 2 +- .../pull-request-page/checks/fix-dialog.tsx | 2 +- .../pull-request-page/checks/fix-launch.ts | 4 +- .../pull-request-page/checks/layouts.tsx | 4 +- .../pull-request-page/checks/refresh.ts | 6 +- .../pull-request-page/checks/rerun.ts | 6 +- .../pull-request-page/checks/row.tsx | 2 +- .../pull-request-page/checks/tab.tsx | 20 +- .../pull-request-page/comments/composer.tsx | 4 +- .../conversation/comment-card.tsx | 5 +- .../conversation/comment-group.tsx | 5 +- .../conversation/comments-list.tsx | 5 +- .../conversation/mention-error.test.tsx | 219 -------------- .../pull-request-page/conversation/reply.ts | 6 +- .../pull-request-page/conversation/tab.tsx | 14 +- .../pull-request-page/edit/issue-updates.ts | 2 +- .../pull-request-page/edit/section.tsx | 2 +- .../files/combined-diff-viewer.tsx | 2 +- .../files/inline-comments.ts | 4 +- .../pull-request-page/files/line-comment.ts | 4 +- .../pull-request-page/files/section-loader.ts | 4 +- .../pull-request-page/files/toolbar.tsx | 2 +- .../pull-request-page/mentions/options.ts | 6 +- .../pull-request-page/page-types.ts | 4 +- .../pull-request-page/page/header.tsx | 4 +- .../pull-request-page/page/surface.tsx | 2 +- .../pull-request-page/page/tabs-shell.tsx | 8 +- .../presentation/state-badge.tsx | 2 +- .../pull-request-page/reviewers/panel.tsx | 6 +- .../reviewers/picker-row.tsx | 2 +- .../pull-request-page/reviewers/picker.tsx | 2 +- .../reviewers/request-actions.ts | 10 +- .../reviewers/requested-list.tsx | 2 +- .../components/repo/repo-fork-indicator.tsx | 2 +- .../right-sidebar/activity-bar-buttons.tsx | 2 +- .../checks-panel/active-checks-status.test.ts | 2 +- .../checks-panel/active-checks-status.ts | 4 +- .../checks-list-expanded-details.test.tsx | 23 +- .../checks-panel-review-state-model.ts | 2 +- .../checks-panel/checks-panel-review.ts | 2 +- .../comments/comment-controls.tsx | 2 +- .../checks-panel/comments/comment-group.tsx | 2 +- .../checks-panel/comments/comment-row.tsx | 4 +- .../comments/pr-comment-fixing-reply-body.ts | 2 +- .../comments/pr-comments-ai-launch-ack.ts | 4 +- .../pr-comments-list-selection.test.tsx | 232 -------------- .../comments/pr-comments-list-selection.ts | 2 +- ...se-checks-panel-comment-mutations.test.tsx | 2 +- .../use-checks-panel-comment-mutations.tsx | 6 +- .../use-checks-panel-comment-resolution.tsx | 2 +- .../comments/use-comments-list-state.tsx | 2 +- .../checks-panel/gitlab-review-client.ts | 2 +- .../checks-panel/logs/check-job-log-tail.tsx | 2 +- .../logs/use-check-log-reading-position.ts | 2 +- .../presentation/check-details-model.ts | 5 +- .../presentation/check-presentation.tsx | 4 +- .../presentation/check-run-details.tsx | 4 +- .../presentation/check-run-time.tsx | 2 +- .../checks-panel/presentation/checks-list.tsx | 2 +- .../presentation/checks-panel-review-copy.ts | 2 +- .../presentation/conflict-summary.tsx | 2 +- .../presentation/triage-strip.fixture.tsx | 2 +- .../presentation/triage-strip.tsx | 4 +- .../refresh/checks-panel-async-result-key.ts | 4 +- .../checks-panel-polling-test-model.ts | 2 +- .../refresh/use-checks-detail-timer.ts | 2 +- .../refresh/use-checks-list-state.tsx | 7 +- .../use-checks-panel-manual-refresh.tsx | 2 +- .../refresh/use-checks-panel-polling.tsx | 2 +- .../checks-panel-review-creation.ts | 2 +- .../use-checks-panel-ai-queue.tsx | 2 +- .../use-checks-panel-context-state.tsx | 6 +- .../use-checks-panel-controller-state.tsx | 6 +- .../use-checks-panel-review-data.tsx | 4 +- .../use-checks-panel-review-state.tsx | 2 +- .../FolderWorkspacePrChecksPanel.tsx | 5 +- .../FolderWorkspacePrChecksRow.tsx | 5 +- .../parent-pr-checks-github-pr-cache.ts | 4 +- .../parent-pr-checks-hosted-review-cache.ts | 2 +- .../folder-checks/parent-pr-checks-refresh.ts | 4 +- .../parent-pr-checks-row-status.ts | 2 +- .../parent-pr-checks-row-types.ts | 6 +- .../parent-pr-checks-rows.test.ts | 6 +- .../folder-checks/parent-pr-checks-rows.ts | 4 +- .../provider-actions/GitHubPRStackMap.tsx | 2 +- .../provider-actions/HostedReviewActions.tsx | 6 +- .../github-pr-stack-confirmation.ts | 2 +- .../provider-actions/github-pr-stack-merge.ts | 4 +- .../github-refresh-error-copy.ts | 2 +- .../hosted-review-github-actions.ts | 2 +- .../use-hosted-review-actions.test.tsx | 2 +- .../use-hosted-review-actions.ts | 2 +- .../use-ready-hosted-review-action.ts | 2 +- .../right-sidebar-top-activity-bar.tsx | 10 +- .../listing/use-worktree-context.ts | 2 +- .../source-control-primary-action-types.ts | 2 +- .../settings/navigation/SettingsSidebar.tsx | 13 +- .../settings/repository/RepositoryPane.tsx | 2 +- .../RepositoryGitHubAccountSection.tsx | 4 +- .../components/sidebar/CommentMarkdown.tsx | 2 +- .../comment-markdown-element-renderers.tsx | 2 +- .../sidebar/comment-markdown-sanitize.ts | 2 +- .../SidebarFeedbackDialog.tsx | 2 +- .../use-worktree-card-linked-details.ts | 2 +- .../use-worktree-card-review-details.ts | 2 +- .../use-worktree-card-secondary-details.ts | 2 +- .../metadata/worktree-attachment-editing.ts | 2 +- .../metadata/worktree-card-meta-types.ts | 2 +- .../folder-workspace-card-pr-display.test.ts | 5 +- .../workspace-attachment-source-label.ts | 2 +- .../review/worktree-card-pr-display.ts | 2 +- .../review/worktree-task-state.ts | 2 +- ...ebar-list-golden-factories.test-fixture.ts | 2 +- .../status-bar/hosted-review-label.ts | 2 +- .../task-page/github/AssigneesCell.tsx | 8 +- .../components/task-page/github/Avatars.tsx | 4 +- .../task-page/github/ChecksCell.tsx | 2 +- .../task-page/github/DuplicatePicker.tsx | 2 +- .../task-page/github/IssueSelectors.tsx | 2 +- .../components/task-page/github/MergeCell.tsx | 6 +- .../task-page/github/ReviewCell.tsx | 6 +- .../task-page/github/ReviewerPicker.tsx | 2 +- .../task-page/github/StatusCell.tsx | 6 +- .../github/pr-check-hydration.test.tsx | 2 +- ...ask-page-github-confirmed-client-values.ts | 4 +- ...task-page-github-dialog-state-authority.ts | 4 +- .../task-page-github-landing-refresh-run.tsx | 2 +- .../task-page-github-quiet-refresh-run.tsx | 2 +- .../github/task-page-github-resume-cache.ts | 2 +- .../github/task-page-github-review-model.tsx | 12 +- .../task-page-github-reviewer-actions.ts | 10 +- .../github/task-page-github-status-actions.ts | 2 +- .../github/task-page-github-status-state.ts | 2 +- ...page-github-work-item-filter-membership.ts | 4 +- ...e-github-work-item-mutation-composition.ts | 8 +- ...age-github-work-item-mutation-lifecycle.ts | 2 +- ...sk-page-github-work-item-mutation-pages.ts | 6 +- ...-page-github-work-item-mutation-patches.ts | 4 +- ...page-github-work-item-mutation-registry.ts | 6 +- ...sk-page-github-work-item-mutation-types.ts | 2 +- .../task-page-github-work-item-mutations.ts | 2 +- .../task-page-github-work-item-quiet-adopt.ts | 4 +- ...-page-github-work-item-quiet-revalidate.ts | 2 +- ...sk-page-github-work-item-registry-types.ts | 4 +- .../task-page-github-work-item-status.ts | 2 +- ...e-task-page-github-cache-reconciliation.ts | 4 +- .../use-task-page-github-collection-state.ts | 2 +- .../github/use-task-page-github-detail.ts | 2 +- ...e-task-page-github-issue-creation.test.tsx | 264 ---------------- .../use-task-page-github-issue-creation.ts | 2 +- .../use-task-page-github-issue-draft.ts | 2 +- .../use-task-page-github-list-projection.ts | 2 +- .../use-task-page-pr-check-hydration.ts | 4 +- ...se-task-page-work-items-cache-selection.ts | 2 +- .../task-page-linear-jira-list-model.tsx | 2 +- .../task-page-cache-selectors.test.ts | 2 +- .../task-page/task-page-cache-selectors.ts | 2 +- .../task-page/task-page-checks-pill.ts | 2 +- .../task-page/task-page-dialog-work-item.ts | 2 +- .../task-page/task-page-draft-storage.tsx | 2 +- .../task-page/task-page-new-issue-draft.ts | 2 +- .../task-page/task-page-pr-check-summary.ts | 4 +- .../task-page/task-page-pr-delta-summary.ts | 2 +- .../task-page/task-page-source-context.tsx | 2 +- .../task-page-work-item-pagination.test.ts | 2 +- .../task-page-work-item-pagination.ts | 2 +- .../task-page-work-item-signatures.ts | 2 +- ...-task-page-source-display.test-fixture.tsx | 2 +- .../task-page/use-task-page-source-display.ts | 2 +- .../use-task-page-workspace-actions.ts | 2 +- ...worktree-jump-palette-task-search.test.tsx | 31 +- .../use-worktree-jump-palette-task-url.ts | 2 +- .../worktree-jump-palette-create-worktree.ts | 2 +- .../composer-state/async-composer-state.ts | 4 +- .../src/hooks/composer-state/async-model.ts | 4 +- .../branch-start-point-actions.ts | 2 +- .../composer-state/composer-decisions.ts | 2 +- .../composer-state/composer-source-model.ts | 2 +- .../composer-state/composer-target-store.ts | 2 +- .../composer-state/derived-composer-state.ts | 4 +- .../src/hooks/composer-state/derived-model.ts | 2 +- .../github-provider-selection.ts | 2 +- .../github-source-application.ts | 2 +- .../github-submit-resolution.ts | 9 +- .../github-submit-unresolved-message.ts | 2 +- .../linked-item-lookup-effects.ts | 2 +- .../composer-state/provider-runtime-sync.ts | 2 +- .../composer-state/source-identity-actions.ts | 2 +- .../source-selection-decisions.ts | 2 +- .../composer-state/target-store-model.ts | 2 +- .../src/hooks/use-pr-check-refresh-owner.ts | 11 +- src/renderer/src/hooks/useComposerState.ts | 2 +- .../src/hooks/useGitHubOperationScope.ts | 2 +- .../src/hooks/useGitHubRepoMetadata.ts | 8 +- ...GitHubSlugMetadata.assignee-reuse.test.tsx | 2 +- .../src/hooks/useGitHubSlugMetadata.ts | 6 +- src/renderer/src/hooks/useGitHubTaskItems.ts | 4 +- .../useTaskPageGitHubWorkItemMutation.ts | 11 +- .../lib/markdown/remark-github-callouts.ts | 2 +- ...-normalization-measurement-test-fixture.ts | 2 +- ...-details-log-normalization-test-fixture.ts | 2 +- .../check-run-details-log-normalization.ts | 2 +- .../checks/fix-checks-agent-launch-request.ts | 2 +- .../checks/pr-check-refresh-owner.ts | 6 +- .../comments/pr-comment-action-state.ts | 2 +- .../review/comments/pr-comment-reactions.ts | 2 +- .../pr-comment-thread-resolution.test.ts | 2 +- .../comments/pr-comment-thread-resolution.ts | 2 +- .../work-items/github/github-task-target.ts | 15 +- .../github/github-work-item-source-lookup.ts | 10 +- .../github/github-work-item-target-request.ts | 6 +- .../github-work-item-workspace-attachment.ts | 6 +- .../work-items/github/smart-github-submit.ts | 13 +- .../lib/workspace/catalog/repo-slug-cache.ts | 2 +- .../lib/workspace/catalog/repo-slug-index.ts | 2 +- .../search/palette/cmd-j-github-url-lookup.ts | 4 +- .../worktree-palette-task-url-match.ts | 4 +- .../github-check-details-timeout.ts | 2 +- .../repository-github-account.ts | 4 +- .../repository-icon-github.ts | 4 +- .../gitlab-job-trace-client.ts | 2 +- .../src/store/github/cache/cache-identity.ts | 19 +- .../src/store/github/cache/cache-model.ts | 6 +- .../src/store/github/checks/check-actions.ts | 7 +- .../src/store/github/checks/github-checks.ts | 8 +- .../checks/pr-check-owner-publication.test.ts | 6 +- .../github/checks/pr-check-status-cache.ts | 8 +- .../github/comments/comment-fetch-actions.ts | 2 +- .../comments/comment-mutation-actions.ts | 5 +- .../comments/conversation-comment-actions.ts | 2 +- .../github-pr-comment-read-order.test.ts | 2 +- .../comments/github-pr-comments.test.ts | 67 +--- .../store/github/comments/pr-comment-cache.ts | 5 +- .../src/store/github/github-slice-contract.ts | 21 +- .../store/github/github-slice-test-harness.ts | 4 +- .../src/store/github/issues/issue-actions.ts | 2 +- .../store/github/projects/project-actions.ts | 2 +- .../store/github/projects/project-cache.ts | 2 +- .../projects/project-field-mutations.test.ts | 4 +- .../projects/project-field-mutations.ts | 6 +- .../github/projects/project-row-actions.ts | 4 +- ...thub-linked-review-clear-ownership.test.ts | 2 +- .../github-pr-request-lifetime.test.ts | 2 +- .../pull-requests/linked-review-clear.ts | 2 +- .../pr-for-branch-ipc-fallback.ts | 2 +- .../pr-refresh-cached-candidate-fields.ts | 4 +- .../github/pull-requests/pr-result-cache.ts | 6 +- .../github/pull-requests/pr-result-routing.ts | 4 +- .../pull-requests/pull-request-actions.ts | 6 +- .../pull-requests/pull-request-execution.ts | 10 +- ...github-pr-manual-refresh-broadcast.test.ts | 87 +----- .../github-pr-refresh-states-leak.test.ts | 2 +- .../store/github/refresh/pr-refresh-state.ts | 2 +- .../github/refresh/refresh-event-actions.ts | 9 +- .../github/refresh/refresh-routing-actions.ts | 2 +- .../store/github/refresh/worktree-refresh.ts | 11 +- .../github/repositories/repository-routing.ts | 2 +- .../provider-request-coalescing.test.ts | 4 +- .../github/requests/request-coordination.ts | 13 +- .../github-review-thread-rollback.test.ts | 2 +- .../sources/github-source-lifetime.test.tsx | 2 +- .../github/sources/source-collections.ts | 4 +- .../sources/source-preference-ownership.ts | 9 +- .../github-task-record-retries.test.ts | 2 +- .../task-records/github-task-records.test.ts | 2 +- .../store/github/task-records/task-change.ts | 6 +- .../github/task-records/task-record-cache.ts | 8 +- .../github/task-records/task-record-model.ts | 8 +- .../github/task-records/task-record-owners.ts | 4 +- .../github/task-records/task-record-reads.ts | 4 +- .../github-work-items-error-envelope.test.ts | 2 +- .../github-work-items-runtime-routing.test.ts | 2 +- .../github-work-items-twin-cache-host.test.ts | 2 +- .../work-items/work-item-aggregate-actions.ts | 8 +- .../work-items/work-item-count-actions.ts | 2 +- .../work-items/work-item-fetch-actions.ts | 6 +- .../work-items/work-item-mutation-actions.ts | 4 +- .../work-items/work-item-patch-options.ts | 2 +- .../github/work-items/work-item-routing.ts | 6 +- src/renderer/src/store/repos/repo-state.ts | 2 +- src/renderer/src/store/repos/repo-update.ts | 2 +- .../editor-check-details-tab-owner.test.ts | 41 +-- .../hosted-review-cache-identity.ts | 2 +- .../hosted-review-cache-state.ts | 21 +- .../hosted-review/hosted-review-pr-cache.ts | 2 +- ...visible-hosted-review-refresh-ownership.ts | 2 +- .../slices/task-creation/new-issue-draft.ts | 2 +- .../store/slices/ui/ui-slice-contract-core.ts | 2 +- .../slices/ui/workspace-navigation-history.ts | 2 +- .../metadata/worktree-meta-state-update.ts | 2 +- ...rees-terminal-pr-exact-observation.test.ts | 2 +- ...trees-terminal-reference-admission.test.ts | 2 +- .../src/web/preload-api/web-github-api.ts | 8 +- src/shared/editor-tab-view-types.ts | 4 +- .../{ => accounts}/account-binding.test.ts | 0 .../github/{ => accounts}/account-binding.ts | 0 .../github/{ => accounts}/auth-types.ts | 0 .../github/{ => api}/api-availability.test.ts | 0 .../github/{ => api}/api-availability.ts | 0 .../github/{ => api}/rate-limit-types.ts | 0 .../{ => attachments}/image-attachment.ts | 0 .../github/{ => cache}/github-cache-key.ts | 6 +- .../{ => checks}/check-details-deadline.ts | 0 src/shared/github/{ => checks}/check-types.ts | 0 .../checks-detail-polling-policy.ts | 6 +- .../complete-pr-check-owner.test.ts | 20 +- .../{ => checks}/complete-pr-check-owner.ts | 10 +- .../complete-pr-check-test-fixture.ts | 0 .../github/{ => checks}/pr-check-head.ts | 0 .../github/{ => comments}/comment-types.ts | 2 +- .../{ => comments}/pr-comment-diff-hunk.ts | 0 .../{ => comments}/pr-comment-mutability.ts | 0 .../pr-inline-comment-placement.ts | 0 .../github/{ => markdown}/github-callouts.ts | 0 .../{ => markdown}/github-reference-links.ts | 7 +- src/shared/github/project-ref-input.test.ts | 18 -- .../project-board-columns.test.ts | 0 .../{ => projects}/project-board-columns.ts | 0 .../{ => projects}/project-content-field.ts | 0 .../{ => projects}/project-group-sort.ts | 0 .../{ => projects}/project-identity.test.ts | 7 +- .../github/{ => projects}/project-identity.ts | 2 +- .../{ => projects}/project-ref-input.ts | 2 +- .../project-relations.test-fixture.ts | 0 .../{ => projects}/project-request-types.ts | 2 +- .../{ => projects}/project-result-types.ts | 6 +- .../project-roadmap-timeline.test.ts | 0 .../project-roadmap-timeline.ts | 0 .../github/{ => projects}/project-types.ts | 0 ...ll-request-auto-merge-availability.test.ts | 0 .../pull-request-auto-merge-availability.ts | 2 +- .../merge}/pull-request-merge-methods.test.ts | 7 - .../merge}/pull-request-merge-methods.ts | 2 +- .../merge}/pull-request-merge-state.ts | 4 +- .../merge}/pull-request-stack-merge.ts | 2 +- .../pr-reviewer-display.ts | 4 +- .../{ => pull-requests}/pull-request-types.ts | 11 +- .../refresh}/linked-pr-staleness.ts | 4 +- .../refresh}/pr-branch-fallback-rule.test.ts | 0 .../refresh}/pr-branch-fallback-rule.ts | 2 +- .../pull-request-for-branch-outcome.test.ts | 9 +- .../pull-request-for-branch-outcome.ts | 2 +- .../refresh}/pull-request-refresh-types.ts | 7 +- src/shared/github/{ => repositories}/links.ts | 0 .../github/{ => repositories}/owner-slug.ts | 0 .../repository-identity-key.test.ts | 0 .../repository-identity-key.ts | 2 +- .../repositories/repository-identity.ts | 2 + .../exact-work-item-operation.ts | 4 +- .../{ => work-items}/work-item-page-errors.ts | 2 +- .../{ => work-items}/work-item-ssh-error.ts | 0 .../{ => work-items}/work-item-target.ts | 4 +- .../{ => work-items}/work-item-types.ts | 16 +- .../work-items-query-bounds.ts | 2 +- src/shared/gitlab-types.ts | 2 +- src/shared/global-settings-types.ts | 2 +- .../tools/native-chat-opened-pull-requests.ts | 2 +- src/shared/new-workspace/github-links.ts | 4 +- .../github-work-item-identity.ts | 2 +- .../smart-workspace-source-results.ts | 4 +- .../smart-workspace-url-source-results.ts | 6 +- .../new-workspace/work-item-lookup-text.ts | 2 +- src/shared/persisted-state-types.ts | 2 +- src/shared/project-host-setup-projection.ts | 5 +- src/shared/repo-types.ts | 4 +- src/shared/review-refresh-policy.ts | 2 +- .../github-pull-request-params.ts | 2 +- .../rpc-contract/github-repo-target-params.ts | 2 +- src/shared/rpc-contract/repo-update-params.ts | 2 +- src/shared/runtime-worktree-contracts.ts | 2 +- .../sidebar-list/workspace-review-facts.ts | 2 +- .../gitlab/gitlab-job-trace-check-details.ts | 2 +- .../checks/gitlab/gitlab-pipeline-checks.ts | 4 +- .../checks/pr-check-severity-order.ts | 2 +- .../checks/pr-check-status-label.ts | 2 +- .../source-control/checks/pr-check-status.ts | 7 +- .../checks/pr-check-step-status.ts | 2 +- .../source-control/checks/pr-check-time.ts | 2 +- .../checks/pr-checks-fix-prompt.test.ts | 2 +- .../checks/pr-checks-fix-prompt.ts | 2 +- .../checks/provider-check-summary.ts | 5 +- .../source-control-next-step-decision.ts | 6 +- ...e-control-primary-action-decision-types.ts | 2 +- .../hosted-review/hosted-review-contracts.ts | 4 +- .../hosted-review/hosted-review-github.ts | 4 +- .../review-comments/pr-comment-audience.ts | 2 +- .../review-comments/pr-comment-groups.ts | 2 +- .../pr-comments-resolution-prompt.ts | 2 +- .../links/terminal-github-pr-link-detector.ts | 2 +- .../work-items/task-provider-identity.ts | 2 +- .../task-source-runtime-capabilities.ts | 2 +- src/shared/workspace-review-checks.ts | 2 +- tests/e2e/github-project-board.spec.ts | 6 +- ...thub-project-provider-columns.unit.test.ts | 128 ++------ .../github-url-smart-input-transition.spec.ts | 2 +- .../helpers/pr-comments-sidebar-fixture.ts | 4 +- 1003 files changed, 4460 insertions(+), 8422 deletions(-) rename src/main/github/{ => accounts}/account-binding-required-host.test.ts (100%) rename src/main/github/{ => accounts}/account-binding-required-host.ts (86%) rename src/main/github/{ => accounts}/auth-diagnose.test.ts (97%) rename src/main/github/{ => accounts}/auth-diagnose.ts (96%) rename src/main/github/{ => accounts}/gh-account-binding-inventory.ts (93%) rename src/main/github/{ => accounts}/gh-account-token.ts (96%) rename src/main/github/{ => accounts}/gh-capability-state.ts (95%) rename src/main/github/{ => accounts}/github-project-account.test.ts (88%) rename src/main/github/{ => accounts}/github-project-account.ts (81%) rename src/main/github/{ => accounts}/project-account-operations.test.ts (72%) rename src/main/github/{ => api}/gh-error-classification.ts (95%) rename src/main/github/{mappers.ts => api/github-read-response.ts} (89%) rename src/main/github/{ => api}/github-rest-revalidation.test.ts (78%) rename src/main/github/{ => api}/github-rest-revalidation.ts (81%) rename src/main/github/{ => api}/rate-limit.test.ts (92%) rename src/main/github/{ => api}/rate-limit.ts (96%) rename src/main/github/{ => attachments}/body-image-urls.test.ts (100%) rename src/main/github/{ => attachments}/body-image-urls.ts (100%) rename src/main/github/{ => attachments}/image-attachment-upload.ts (87%) delete mode 100644 src/main/github/client-pr-lookup-coalescing.test.ts delete mode 100644 src/main/github/client-rate-limit-block.test.ts delete mode 100644 src/main/github/client-stack-merge-guard.test.ts delete mode 100644 src/main/github/client.ts delete mode 100644 src/main/github/client/check/check-job-log-tails.test.ts rename src/main/github/{ => client/check}/complete-pr-check-observation.test.ts (93%) rename src/main/github/{client-pr-check-details.test.ts => client/check/get-pr-check-details.test.ts} (80%) rename src/main/github/{ => client}/client-test-harness.ts (89%) rename src/main/github/{ => client}/client-test-mocks.ts (83%) rename src/main/github/{client-create-pr.test.ts => client/create/create-github-pull-request.test.ts} (67%) rename src/main/github/{ => client/detect}/repository-merge-metadata-reuse.test.ts (96%) rename src/main/github/{client-work-item-check-summary.test.ts => client/fetch/get-work-item-check-summary.test.ts} (77%) rename src/main/github/{work-item-details-query-reuse.test.ts => client/fetch/get-work-item-details-reuse.test.ts} (80%) rename src/main/github/{client-issue-origin-preference.test.ts => client/fetch/get-work-item-origin-preference.test.ts} (67%) rename src/main/github/{client-merge-queue-auto-merge.test.ts => client/fetch/get-work-item-preview-host.test.ts} (54%) rename src/main/github/{client-work-items.test.ts => client/list/list-work-items-source-guard.test.ts} (75%) create mode 100644 src/main/github/client/list/work-item-search-fallback-environment.test.ts rename src/main/github/{ => client/list}/work-item-search-isolation.test.ts (65%) rename src/main/github/{client-tracked-upstream-default-branch.test.ts => client/lookup/get-pr-for-branch-host-scope.test.ts} (70%) rename src/main/github/{client-tracked-upstream-fork-owner.test.ts => client/lookup/get-pr-for-branch-upstream-owner.test.ts} (72%) rename src/main/github/{client-pr-rest-revalidation.test.ts => client/lookup/pr-branch-lookup-revalidation.test.ts} (50%) create mode 100644 src/main/github/client/lookup/pr-for-branch-outcome-coalescing.test.ts create mode 100644 src/main/github/client/lookup/pr-lookup-rate-limit.test.ts rename src/main/github/{ => client/lookup}/pull-request-sidebar-query.test.ts (62%) rename src/main/github/{client-issue-source.test.ts => client/work-item-source-selection.test.ts} (69%) rename src/main/github/{ => comments}/comment-reactions.ts (95%) create mode 100644 src/main/github/execution/github-legacy-command.ts rename src/main/github/{gh-utils-concurrency.test.ts => execution/github-operation-slots.test.ts} (90%) create mode 100644 src/main/github/execution/github-operation-slots.ts delete mode 100644 src/main/github/gh-capability-state.test.ts delete mode 100644 src/main/github/gh-utils.ts delete mode 100644 src/main/github/github-read-execution-scope.ts rename src/main/github/{ => issues}/issue-comment.ts (73%) rename src/main/github/{ => issues}/issue-create.ts (86%) rename src/main/github/{ => issues}/issue-details-query.ts (96%) rename src/main/github/{ => issues}/issue-field-options.ts (81%) rename src/main/github/{ => issues}/issue-timeline.ts (92%) rename src/main/github/{ => issues}/issue-update.ts (82%) rename src/main/github/{ => issues}/issue-work-item-details-fresh.test.ts (89%) rename src/main/github/{ => issues}/issue-work-item-details.ts (89%) rename src/main/github/{ => issues}/issues.test.ts (54%) rename src/main/github/{ => issues}/issues.ts (85%) delete mode 100644 src/main/github/pr-refresh-coordinator-completion-order.test.ts delete mode 100644 src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts delete mode 100644 src/main/github/pr-refresh-coordinator-refresh-events.test.ts delete mode 100644 src/main/github/pr-refresh-coordinator-visibility-policy.test.ts delete mode 100644 src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts delete mode 100644 src/main/github/project-bound-gh-requests.test.ts delete mode 100644 src/main/github/project-view.ts delete mode 100644 src/main/github/project-view/internals.ts create mode 100644 src/main/github/project-view/project-argument-validation.ts create mode 100644 src/main/github/project-view/project-graphql.ts create mode 100644 src/main/github/project-view/project-host-execution.ts rename src/main/github/{project-view-host-auth.test.ts => project-view/project-request-host-auth.test.ts} (80%) create mode 100644 src/main/github/project-view/project-rest.ts delete mode 100644 src/main/github/project-view/project-view-config.test.ts rename src/main/github/{project-view.test.ts => project-view/project-view-reference.test.ts} (91%) rename src/main/github/{body-image-read-adapters.test.ts => project-view/work-item-details-body-image-adapters.test.ts} (83%) rename src/main/github/{ => pull-requests/conflicts}/conflict-summary-cache.ts (95%) rename src/main/github/{ => pull-requests/conflicts}/conflict-summary.test.ts (81%) rename src/main/github/{ => pull-requests/conflicts}/conflict-summary.ts (93%) rename src/main/github/{ => pull-requests/files}/pr-review-comment-lines.test.ts (100%) rename src/main/github/{ => pull-requests/files}/pr-review-comment-lines.ts (100%) rename src/main/github/{ => pull-requests/files}/pull-request-detail-context.test.ts (83%) rename src/main/github/{ => pull-requests/files}/pull-request-detail-context.ts (90%) rename src/main/github/{ => pull-requests/files}/pull-request-file-contents.ts (83%) rename src/main/github/{ => pull-requests/files}/pull-request-file-data.ts (86%) rename src/main/github/{ => pull-requests/refresh}/merged-pr-commit-membership.ts (77%) rename src/main/github/{ => pull-requests/refresh}/pr-mutation-review-invalidation.ts (63%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-candidate-policy.ts (93%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator-active-visible-priority.test.ts (82%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator-alias-coalescing.test.ts (91%) create mode 100644 src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator-test-harness.ts (75%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator-test-mocks.ts (98%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator-wsl-stopped-distro.test.ts (77%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-coordinator.ts (97%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-error-classification.test.ts (100%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-error-classification.ts (93%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-event-publisher.ts (93%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-pacing.ts (100%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-queue-drainer.ts (96%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-queue-growth-bound.test.ts (58%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-queue.ts (98%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-rate-limit-gate.ts (64%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-renderer-port.ts (100%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-retry-state.ts (90%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-validation-backoff.test.ts (69%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-validation-backoff.ts (96%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-visibility.test.ts (100%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-visibility.ts (96%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-watch-derived-data.test.ts (82%) rename src/main/github/{ => pull-requests/refresh}/pr-refresh-watch-jobs.ts (95%) rename src/main/github/{ => pull-requests/refresh}/pull-request-changed-events.test.ts (93%) rename src/main/github/{ => pull-requests/refresh}/pull-request-changed-events.ts (89%) rename src/main/github/{ => pull-requests/stacks}/github-pr-stack-async-merge.ts (86%) rename src/main/github/{ => pull-requests/stacks}/github-pr-stack.test.ts (85%) rename src/main/github/{ => pull-requests/stacks}/github-pr-stack.ts (91%) rename src/main/github/{ => pull-requests/stacks}/github-stack-api-responses.ts (93%) rename src/main/github/{ => pull-requests/stacks}/stacked-pr-creation.test.ts (88%) rename src/main/github/{ => pull-requests/stacks}/stacked-pr-creation.ts (88%) rename src/main/github/{ => pull-requests/start-points}/pr-head-tracking-ref.test.ts (91%) rename src/main/github/{ => pull-requests/start-points}/pr-head-tracking-ref.ts (83%) rename src/main/github/{ => pull-requests/start-points}/pr-start-point-wsl-gate.ts (81%) rename src/main/github/{ => pull-requests/start-points}/pr-start-point.test.ts (96%) rename src/main/github/{ => pull-requests/start-points}/pr-start-point.ts (90%) rename src/main/github/{ => pull-requests/start-points}/review-head-remote.ts (77%) rename src/main/github/{ => repositories}/github-api-repository-probe.ts (100%) rename src/main/github/{ => repositories}/github-api-repository-remote-probe.test.ts (63%) rename src/main/github/{ => repositories}/github-api-repository-remote-probe.ts (94%) rename src/main/github/{ => repositories}/github-api-repository-validation.ts (81%) rename src/main/github/{ => repositories}/github-api-repository.test.ts (59%) rename src/main/github/{ => repositories}/github-api-repository.ts (90%) rename src/main/github/{ => repositories}/github-enterprise-repository.test.ts (94%) rename src/main/github/{ => repositories}/github-enterprise-repository.ts (91%) rename src/main/github/{ => repositories}/github-owner-repo-selection.ts (83%) rename src/main/github/{ => repositories}/github-remote-identity-parsing.ts (94%) rename src/main/github/{client-pr-local-runtime.test.ts => repositories/github-repository-client-refusal.test.ts} (79%) rename src/main/github/{ => repositories}/github-repository-host.ts (84%) rename src/main/github/{gh-utils.test.ts => repositories/github-repository-identity-cache.test.ts} (93%) rename src/main/github/{ => repositories}/github-repository-identity.fork-owner-repo.test.ts (82%) rename src/main/github/{ => repositories}/github-repository-identity.signed-cache.test.ts (53%) rename src/main/github/{ => repositories}/github-repository-identity.ssh-host-alias.test.ts (91%) rename src/main/github/{ => repositories}/github-repository-identity.ts (90%) rename src/main/github/{ => repositories}/github-ssh-host-alias-resolution.ts (93%) rename src/main/github/{ => repositories}/local-git-config-signature.ts (98%) delete mode 100644 src/main/github/work-item-details-enterprise-host.test.ts delete mode 100644 src/main/github/work-item-search-fallback-environment.test.ts rename src/main/github/{ => work-items}/work-item-details-pr-files.test.ts (86%) rename src/main/github/{ => work-items}/work-item-details-test-lookup.ts (76%) rename src/main/github/{ => work-items}/work-item-details.test.ts (86%) rename src/main/github/{ => work-items}/work-item-details.ts (83%) rename src/main/github/{ => work-items}/work-item-participants.ts (91%) rename src/main/github/{ => work-items}/work-item-search-test-harness.ts (65%) rename src/main/github/{ => workspace-links}/github-issue-link-title-fill.test.ts (89%) rename src/main/github/{ => workspace-links}/github-issue-link-title-fill.ts (83%) rename src/main/github/{ => workspace-links}/github-issue-link-title-wiring.ts (78%) delete mode 100644 src/main/runtime/sidebar-inputs/sidebar-inputs-feed-announcements.test.ts delete mode 100644 src/main/star-nag/service-threshold-prompt.test.ts delete mode 100644 src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.test.ts delete mode 100644 src/renderer/src/components/github-project/ProjectBoard.test.tsx delete mode 100644 src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts delete mode 100644 src/renderer/src/components/pr-check-owner-presentation.test.tsx delete mode 100644 src/renderer/src/components/pr-check-refresh.test.ts delete mode 100644 src/renderer/src/components/pr-check-tab-actions.test.tsx delete mode 100644 src/renderer/src/components/pull-request-page/actions/merge-actions.test.ts delete mode 100644 src/renderer/src/components/pull-request-page/cache/file-content.test.ts delete mode 100644 src/renderer/src/components/pull-request-page/conversation/mention-error.test.tsx delete mode 100644 src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx delete mode 100644 src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.test.tsx rename src/shared/github/{ => accounts}/account-binding.test.ts (100%) rename src/shared/github/{ => accounts}/account-binding.ts (100%) rename src/shared/github/{ => accounts}/auth-types.ts (100%) rename src/shared/github/{ => api}/api-availability.test.ts (100%) rename src/shared/github/{ => api}/api-availability.ts (100%) rename src/shared/github/{ => api}/rate-limit-types.ts (100%) rename src/shared/github/{ => attachments}/image-attachment.ts (100%) rename src/shared/github/{ => cache}/github-cache-key.ts (95%) rename src/shared/github/{ => checks}/check-details-deadline.ts (100%) rename src/shared/github/{ => checks}/check-types.ts (100%) rename src/shared/github/{ => checks}/checks-detail-polling-policy.ts (91%) rename src/shared/github/{ => checks}/complete-pr-check-owner.test.ts (60%) rename src/shared/github/{ => checks}/complete-pr-check-owner.ts (91%) rename src/shared/github/{ => checks}/complete-pr-check-test-fixture.ts (100%) rename src/shared/github/{ => checks}/pr-check-head.ts (100%) rename src/shared/github/{ => comments}/comment-types.ts (96%) rename src/shared/github/{ => comments}/pr-comment-diff-hunk.ts (100%) rename src/shared/github/{ => comments}/pr-comment-mutability.ts (100%) rename src/shared/github/{ => comments}/pr-inline-comment-placement.ts (100%) rename src/shared/github/{ => markdown}/github-callouts.ts (100%) rename src/shared/github/{ => markdown}/github-reference-links.ts (95%) delete mode 100644 src/shared/github/project-ref-input.test.ts rename src/shared/github/{ => projects}/project-board-columns.test.ts (100%) rename src/shared/github/{ => projects}/project-board-columns.ts (100%) rename src/shared/github/{ => projects}/project-content-field.ts (100%) rename src/shared/github/{ => projects}/project-group-sort.ts (100%) rename src/shared/github/{ => projects}/project-identity.test.ts (72%) rename src/shared/github/{ => projects}/project-identity.ts (94%) rename src/shared/github/{ => projects}/project-ref-input.ts (87%) rename src/shared/github/{ => projects}/project-relations.test-fixture.ts (100%) rename src/shared/github/{ => projects}/project-request-types.ts (97%) rename src/shared/github/{ => projects}/project-result-types.ts (94%) rename src/shared/github/{ => projects}/project-roadmap-timeline.test.ts (100%) rename src/shared/github/{ => projects}/project-roadmap-timeline.ts (100%) rename src/shared/github/{ => projects}/project-types.ts (100%) rename src/shared/github/{ => pull-requests/merge}/pull-request-auto-merge-availability.test.ts (100%) rename src/shared/github/{ => pull-requests/merge}/pull-request-auto-merge-availability.ts (97%) rename src/shared/github/{ => pull-requests/merge}/pull-request-merge-methods.test.ts (81%) rename src/shared/github/{ => pull-requests/merge}/pull-request-merge-methods.ts (98%) rename src/shared/github/{ => pull-requests/merge}/pull-request-merge-state.ts (95%) rename src/shared/github/{ => pull-requests/merge}/pull-request-stack-merge.ts (97%) rename src/shared/github/{ => pull-requests}/pr-reviewer-display.ts (97%) rename src/shared/github/{ => pull-requests}/pull-request-types.ts (92%) rename src/shared/github/{ => pull-requests/refresh}/linked-pr-staleness.ts (96%) rename src/shared/github/{ => pull-requests/refresh}/pr-branch-fallback-rule.test.ts (100%) rename src/shared/github/{ => pull-requests/refresh}/pr-branch-fallback-rule.ts (97%) rename src/shared/github/{ => pull-requests/refresh}/pull-request-for-branch-outcome.test.ts (67%) rename src/shared/github/{ => pull-requests/refresh}/pull-request-for-branch-outcome.ts (93%) rename src/shared/github/{ => pull-requests/refresh}/pull-request-refresh-types.ts (94%) rename src/shared/github/{ => repositories}/links.ts (100%) rename src/shared/github/{ => repositories}/owner-slug.ts (100%) rename src/shared/github/{ => repositories}/repository-identity-key.test.ts (100%) rename src/shared/github/{ => repositories}/repository-identity-key.ts (97%) create mode 100644 src/shared/github/repositories/repository-identity.ts rename src/shared/github/{ => work-items}/exact-work-item-operation.ts (88%) rename src/shared/github/{ => work-items}/work-item-page-errors.ts (95%) rename src/shared/github/{ => work-items}/work-item-ssh-error.ts (100%) rename src/shared/github/{ => work-items}/work-item-target.ts (93%) rename src/shared/github/{ => work-items}/work-item-types.ts (89%) rename src/shared/github/{ => work-items}/work-items-query-bounds.ts (87%) diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 85ae016632d..98bfdddd0cb 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -34,7 +34,7 @@ on: - 'src/main/runtime/agent-session/agent-session-unsaved-*.ts' # The phone/host contract program's unmocked desktop sources and its declaration owner's inputs. - 'src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts' - - 'src/main/github/issue-field-options.ts' + - 'src/main/github/issues/issue-field-options.ts' - 'config/tsconfig.test.mobile-host-declarations.json' - 'config/tsconfig.node.json' - 'src/types/build-constants.d.ts' @@ -174,7 +174,7 @@ on: - 'src/main/runtime/agent-session/agent-session-unsaved-*.ts' # The phone/host contract program's unmocked desktop sources and its declaration owner's inputs. - 'src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts' - - 'src/main/github/issue-field-options.ts' + - 'src/main/github/issues/issue-field-options.ts' - 'config/tsconfig.test.mobile-host-declarations.json' - 'config/tsconfig.node.json' - 'src/types/build-constants.d.ts' diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 9521cc43b5d..c5b9024c001 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -428,19 +428,15 @@ 2 test-no-wall-clock:src/main/external-conversations/external-conversation-cancel-settlement.test.ts 3 test-no-wall-clock:src/main/external-conversations/external-conversation-cancel.test.ts 2 test-no-wall-clock:src/main/git/command-runner/host-routing/git-command-resolution-windows-checkout.test.ts -2 test-no-wall-clock:src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts +1 test-no-wall-clock:src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts 1 test-no-wall-clock:src/main/git/refs/maintenance/pack-refs-lock-ownership.test.ts 2 test-no-wall-clock:src/main/git/refs/maintenance/repo-ref-maintenance-real-git.test.ts 1 test-no-wall-clock:src/main/git/source-control/blob-absence-real-git.test.ts 2 test-no-wall-clock:src/main/git/worktrees/catalog/worktree-created-description-real-git.test.ts -3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts -8 test-no-wall-clock:src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts -5 test-no-wall-clock:src/main/github/pr-refresh-coordinator-completion-order.test.ts -3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts -3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-refresh-events.test.ts -4 test-no-wall-clock:src/main/github/pr-refresh-coordinator-visibility-policy.test.ts -3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts -4 test-no-wall-clock:src/main/github/pr-refresh-queue-growth-bound.test.ts +3 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-active-visible-priority.test.ts +8 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-alias-coalescing.test.ts +3 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts +2 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts 2 test-no-wall-clock:src/main/grok/grok-hook-stdin-no-eof.test.ts 3 test-no-wall-clock:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts 1 test-no-wall-clock:src/main/ipc/terminal-render-desync-evidence.test.ts @@ -517,8 +513,6 @@ 2 test-no-wall-clock:src/main/skills/transaction-state/skill-transaction-startup-recovery.test.ts 4 test-no-wall-clock:src/main/sqlite/sync-database.test.ts 4 test-no-wall-clock:src/main/ssh/remote-exec/ssh-remote-commands.test.ts -2 test-no-wall-clock:src/main/star-nag/service-direct-star.test.ts -3 test-no-wall-clock:src/main/star-nag/service-threshold-prompt.test.ts 1 test-no-wall-clock:src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts 1 test-no-wall-clock:src/main/startup/login-shell-environment.test.ts 2 test-no-wall-clock:src/main/startup/run-electron-vite-dev.test.ts diff --git a/config/process-host-direct-import-baseline.txt b/config/process-host-direct-import-baseline.txt index 7555a4268c5..ae7800bc30d 100644 --- a/config/process-host-direct-import-baseline.txt +++ b/config/process-host-direct-import-baseline.txt @@ -36,7 +36,7 @@ src/main/git/command-runner/process-capture/spawn-command-capture.ts src/main/git/command-runner/process-capture/spawned-command-tree-kill.ts src/main/git/command-runner/host-routing/wsl-aware-spawn.ts src/main/git/command-runner/host-routing/wsl-git-read-environment.ts -src/main/github/gh-utils.ts +src/main/github/execution/github-legacy-command.ts src/main/workspace-hooks/hooks.ts src/main/ipc/developer-permissions.ts src/main/ipc/filesystem/watcher-process/child-lifecycle/parcel-watcher-child-launch.ts diff --git a/config/repo-host-read-baseline.txt b/config/repo-host-read-baseline.txt index 3bbcc437e50..c1fbbf6e2ab 100644 --- a/config/repo-host-read-baseline.txt +++ b/config/repo-host-read-baseline.txt @@ -2,7 +2,7 @@ # This is a RATCHET: counts may only go DOWN. Resolve the host instead (see fix text). # A lower bound: destructured and differently named rows are not counted. # Prune after a fix: pnpm check:repo-host-read-ratchet --prune -1 src/main/github/account-binding-required-host.ts +1 src/main/github/accounts/account-binding-required-host.ts 3 src/main/ipc/ephemeral-vm-recipe-context.ts 2 src/main/ipc/filesystem/filesystem-source-control-ai-targets.ts 1 src/main/ipc/folder-repo-git-upgrade.ts @@ -46,7 +46,7 @@ 1 src/main/workspaces/space-analysis/workspace-space-worktree-row.ts 1 src/shared/endpoint-catalog-adoption.ts 1 src/shared/folder-workspaces/folder-workspace-execution-host.ts -1 src/shared/github/github-cache-key.ts +1 src/shared/github/cache/github-cache-key.ts 1 src/shared/new-workspace-dialog-repo.ts 1 src/shared/sidebar-list/workspace-review-facts.ts 1 src/shared/workspace-session/workspace-session-terminal-buffers.ts diff --git a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs index 0e3d97d340c..c3860f770ef 100644 --- a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs +++ b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs @@ -18,7 +18,7 @@ const NODE_RUNTIME_INCLUDE = [ 'src/main/ai-vault/parse-cache/session-parse-cache-persistence.test.ts', 'src/main/ai-vault/scope/session-scanner-scope-walk.test.ts', // The forced-probe handoff race opens only under V8's promise scheduling, which Electron main uses. - 'src/main/github/rate-limit.test.ts', + 'src/main/github/api/rate-limit.test.ts', // Electron main bridges streams with Node's implementation. 'src/main/host/electron-http-client.test.ts', 'src/main/browser/extensions/native-messaging/browser-extension-native-host-process-backpressure.test.ts', diff --git a/config/tsconfig.test.mobile-host-declarations.json b/config/tsconfig.test.mobile-host-declarations.json index 803119d23ae..cb32d2f2e77 100644 --- a/config/tsconfig.test.mobile-host-declarations.json +++ b/config/tsconfig.test.mobile-host-declarations.json @@ -17,7 +17,7 @@ "../src/main/runtime/orca-runtime.ts", "../src/main/runtime/rpc/core.ts", "../src/main/runtime/rpc/dispatch/dispatcher-request-parsing.ts", - "../src/main/github/issue-field-options.ts", + "../src/main/github/issues/issue-field-options.ts", "../src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.ts", "../src/types/build-constants.d.ts", "../src/main/native-chat/transcript-line-decoders-claude.ts", diff --git a/mobile/src/components/MobileMarkdownCallout.tsx b/mobile/src/components/MobileMarkdownCallout.tsx index d127e671329..d08ddcee680 100644 --- a/mobile/src/components/MobileMarkdownCallout.tsx +++ b/mobile/src/components/MobileMarkdownCallout.tsx @@ -7,7 +7,7 @@ import { OctagonAlert, TriangleAlert } from 'lucide-react-native' -import type { GitHubCalloutKind } from '../../../src/shared/github/github-callouts' +import type { GitHubCalloutKind } from '../../../src/shared/github/markdown/github-callouts' import { translate } from '../i18n/mobile-i18n' import { styles } from './mobile-markdown-styles' import { colors } from '../theme/mobile-theme' diff --git a/mobile/src/components/mobile-markdown-links-context.ts b/mobile/src/components/mobile-markdown-links-context.ts index 43d8279c7d4..130fbdabffd 100644 --- a/mobile/src/components/mobile-markdown-links-context.ts +++ b/mobile/src/components/mobile-markdown-links-context.ts @@ -1,7 +1,7 @@ import { createContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react' import { createNativeChatFileLinkHooks } from '../../../src/shared/native-chat/native-chat-file-link-hooks' import type { NativeChatFileLinkExistence } from '../../../src/shared/native-chat/native-chat-file-link-existence' -import type { GitHubRepoReference } from '../../../src/shared/github/github-reference-links' +import type { GitHubRepoReference } from '../../../src/shared/github/markdown/github-reference-links' /** What a chat knows about its workspace for links in its text; absent outside chat. */ export type MobileMarkdownLinks = { diff --git a/mobile/src/components/mobile-markdown-mdast-nodes.d.ts b/mobile/src/components/mobile-markdown-mdast-nodes.d.ts index a7ad5fdee08..cbb0c832a13 100644 --- a/mobile/src/components/mobile-markdown-mdast-nodes.d.ts +++ b/mobile/src/components/mobile-markdown-mdast-nodes.d.ts @@ -1,5 +1,5 @@ import type { BlockContent, DefinitionContent, Literal, Parent, PhrasingContent } from 'mdast' -import type { GitHubCalloutKind } from '../../../src/shared/github/github-callouts' +import type { GitHubCalloutKind } from '../../../src/shared/github/markdown/github-callouts' // The node types MobileMarkdown adds to mdast, registered by declaration merging. Merging needs // interfaces, which React Doctor's root rules flag; mobile's lint config ignores this file because diff --git a/mobile/src/components/mobile-markdown-tree.ts b/mobile/src/components/mobile-markdown-tree.ts index 0eca3907d49..b511ad24a87 100644 --- a/mobile/src/components/mobile-markdown-tree.ts +++ b/mobile/src/components/mobile-markdown-tree.ts @@ -3,7 +3,7 @@ import cjkFriendly from 'markdown-it-cjk-friendly' import footnote from 'markdown-it-footnote' import type { AlignType, Parent, PhrasingContent, Root, RootContent, Table } from 'mdast' import remend from 'remend' -import { forEachGitHubCallout } from '../../../src/shared/github/github-callouts' +import { forEachGitHubCallout } from '../../../src/shared/github/markdown/github-callouts' import { codeNode, type Token } from './mobile-markdown-code-nodes' import { inlineTagNode, inlineTagPairs } from './mobile-markdown-inline-html' import { readTaskMarker } from './mobile-markdown-task-items' diff --git a/mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx b/mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx index df19a803b29..03f03584590 100644 --- a/mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx +++ b/mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx @@ -8,7 +8,7 @@ import { triggerError, triggerSuccess } from '../../platform/haptics' import { parseGitHubIssueOrPRLink, parseGitHubIssueOrPRNumber -} from '../../../../src/shared/github/links' +} from '../../../../src/shared/github/repositories/links' import { linkMobilePr } from '../../source-control/mobile-pr-link' import { TextField } from '../composer-input/TextField' import { useMobileTranslation } from '../../i18n/use-mobile-translation' diff --git a/mobile/src/components/pr-sidebar/MobilePrTabSheets.tsx b/mobile/src/components/pr-sidebar/MobilePrTabSheets.tsx index 57c5d472bc8..d8c5eae6729 100644 --- a/mobile/src/components/pr-sidebar/MobilePrTabSheets.tsx +++ b/mobile/src/components/pr-sidebar/MobilePrTabSheets.tsx @@ -4,8 +4,8 @@ import { GitMerge } from 'lucide-react-native' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../i18n/use-mobile-translation' import type { Translate } from '../../i18n/mobile-i18n' -import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-request-types' -import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-request-merge-methods' +import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-requests/pull-request-types' +import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' import { KeyedBottomDrawer } from '../bottom-drawer/keyed-bottom-drawer' import { SheetHeader } from '../nav-bar/SheetHeader' import { SheetPrimaryButton } from '../SheetPrimaryButton' diff --git a/mobile/src/components/pr-sidebar/PRCheckDetail.tsx b/mobile/src/components/pr-sidebar/PRCheckDetail.tsx index 90894b5922e..7b9674d1646 100644 --- a/mobile/src/components/pr-sidebar/PRCheckDetail.tsx +++ b/mobile/src/components/pr-sidebar/PRCheckDetail.tsx @@ -1,7 +1,7 @@ import { ActivityIndicator, ScrollView, Text, View } from 'react-native' import { colors } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' -import type { PRCheckRunDetails } from '../../../../src/shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../src/shared/github/checks/check-types' import { presentCheckDetail, type CheckDetailJob } from './pr-check-detail-content' import { mobilePrSidebarStyles as styles } from './mobile-pr-sidebar-styles' import { useMobileTranslation } from '../../i18n/use-mobile-translation' diff --git a/mobile/src/components/pr-sidebar/PRCommentCard.tsx b/mobile/src/components/pr-sidebar/PRCommentCard.tsx index eea6227f480..8bf6e839b5c 100644 --- a/mobile/src/components/pr-sidebar/PRCommentCard.tsx +++ b/mobile/src/components/pr-sidebar/PRCommentCard.tsx @@ -6,7 +6,7 @@ import type { GitHubReaction, GitHubReactionContent, PRComment -} from '../../../../src/shared/github/comment-types' +} from '../../../../src/shared/github/comments/comment-types' import { colors, pressedStyle } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import type { Translate } from '../../i18n/mobile-i18n' diff --git a/mobile/src/components/pr-sidebar/PRCommentsSection.tsx b/mobile/src/components/pr-sidebar/PRCommentsSection.tsx index 9c44d335d27..18f461d6b80 100644 --- a/mobile/src/components/pr-sidebar/PRCommentsSection.tsx +++ b/mobile/src/components/pr-sidebar/PRCommentsSection.tsx @@ -1,8 +1,8 @@ import { useMemo, useState } from 'react' import { ActivityIndicator, Pressable, Text, View } from 'react-native' import { ChevronDown, ChevronRight } from 'lucide-react-native' -import type { PRState } from '../../../../src/shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { PRState } from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import type { GitHubPrRepoSlug } from '../../source-control/review/github-pr-repo-slug' import { colors, pressedStyle } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' diff --git a/mobile/src/components/pr-sidebar/PrCheckFailureRow.tsx b/mobile/src/components/pr-sidebar/PrCheckFailureRow.tsx index e4d44717d17..2d7d8416984 100644 --- a/mobile/src/components/pr-sidebar/PrCheckFailureRow.tsx +++ b/mobile/src/components/pr-sidebar/PrCheckFailureRow.tsx @@ -17,7 +17,7 @@ import type { Translate } from '../../i18n/mobile-i18n' import { openExternalLink } from '../../platform/external-link' import { useClipboardWriter } from '../../platform/clipboard' import { triggerError, triggerSuccess } from '../../platform/haptics' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' import { LIST_LEADING_SLOT, LIST_ROW_PADDING } from '../list/ListGroup' import { PullDownMenu } from '../nav-bar/PullDownMenu' import type { ActionSheetAction } from '../ActionSheetModal' diff --git a/mobile/src/components/pr-sidebar/PrChecksScreen.tsx b/mobile/src/components/pr-sidebar/PrChecksScreen.tsx index e833511fa55..b0060ae92b3 100644 --- a/mobile/src/components/pr-sidebar/PrChecksScreen.tsx +++ b/mobile/src/components/pr-sidebar/PrChecksScreen.tsx @@ -7,7 +7,7 @@ import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../i18n/use-mobile-translation' import type { Translate } from '../../i18n/mobile-i18n' import { openExternalLink } from '../../platform/external-link' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' import { isBrokenCheck } from '../../../../src/shared/source-control/checks/pr-checks-fix-prompt' import { sortChecksBySeverity } from '../../../../src/shared/source-control/checks/pr-check-severity-order' import { buildPrFixChecksPrompt } from '../../source-control/review/pr-ai-triage-prompt' diff --git a/mobile/src/components/pr-sidebar/PrReviewersSheet.tsx b/mobile/src/components/pr-sidebar/PrReviewersSheet.tsx index ff357de0f3b..0ccb982bd2f 100644 --- a/mobile/src/components/pr-sidebar/PrReviewersSheet.tsx +++ b/mobile/src/components/pr-sidebar/PrReviewersSheet.tsx @@ -5,8 +5,8 @@ import { colors, pressedStyle } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../i18n/use-mobile-translation' import type { Translate } from '../../i18n/mobile-i18n' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' -import { getGitHubPRReviewerRows } from '../../../../src/shared/github/pr-reviewer-display' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' +import { getGitHubPRReviewerRows } from '../../../../src/shared/github/pull-requests/pr-reviewer-display' import { useCalmConnectionState } from '../../transport/connection/calm-connection-state' import { classifyConnection, diff --git a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx index fd7bc73a9bf..eddd0a99bbc 100644 --- a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx +++ b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx @@ -3,8 +3,8 @@ import { GitMerge, GitPullRequest, Sparkles, type LucideIcon } from 'lucide-reac import { colors, feedback, pressedStyle } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../i18n/use-mobile-translation' -import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-request-merge-methods' -import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-request-merge-state' +import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' +import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-state' import { MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY } from '../../../../src/shared/source-control/runtime/source-control-runtime-capabilities' import { useRouteCapability } from '../../worktree/use-workspace-client' import { buildResolveConflictsPrompt } from '../../source-control/review/pr-ai-triage-prompt' diff --git a/mobile/src/components/pr-sidebar/PrThreadCode.tsx b/mobile/src/components/pr-sidebar/PrThreadCode.tsx index c35b3f6b411..7a642750765 100644 --- a/mobile/src/components/pr-sidebar/PrThreadCode.tsx +++ b/mobile/src/components/pr-sidebar/PrThreadCode.tsx @@ -1,5 +1,5 @@ import { Text, View } from 'react-native' -import type { DiffHunkLine } from '../../../../src/shared/github/pr-comment-diff-hunk' +import type { DiffHunkLine } from '../../../../src/shared/github/comments/pr-comment-diff-hunk' import { colors, radii, spacing, typography } from '../../theme/mobile-theme' import { themedStyleSheet } from '../../theme/themed-styles' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' diff --git a/mobile/src/components/pr-sidebar/PrThreadRow.tsx b/mobile/src/components/pr-sidebar/PrThreadRow.tsx index 10a6a117720..2a57abeb40b 100644 --- a/mobile/src/components/pr-sidebar/PrThreadRow.tsx +++ b/mobile/src/components/pr-sidebar/PrThreadRow.tsx @@ -15,13 +15,13 @@ import { Sparkles, Trash2 } from 'lucide-react-native' -import type { PRComment } from '../../../../src/shared/github/comment-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' import { getPRCommentGroupRoot, type PRCommentGroup } from '../../../../src/shared/source-control/review-comments/pr-comment-groups' import { isBotPRComment } from '../../../../src/shared/source-control/review-comments/pr-comment-audience' -import { threadDiffLines } from '../../../../src/shared/github/pr-comment-diff-hunk' +import { threadDiffLines } from '../../../../src/shared/github/comments/pr-comment-diff-hunk' import { colors, pressedStyle, radii, spacing, typography } from '../../theme/mobile-theme' import { themedStyleSheet } from '../../theme/themed-styles' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' diff --git a/mobile/src/components/pr-sidebar/PrThreadScreen.tsx b/mobile/src/components/pr-sidebar/PrThreadScreen.tsx index b1db7052d92..d87cf43aae6 100644 --- a/mobile/src/components/pr-sidebar/PrThreadScreen.tsx +++ b/mobile/src/components/pr-sidebar/PrThreadScreen.tsx @@ -1,6 +1,6 @@ import { ScrollView, Text, View } from 'react-native' import type { PRCommentGroup } from '../../../../src/shared/source-control/review-comments/pr-comment-groups' -import { threadDiffLines } from '../../../../src/shared/github/pr-comment-diff-hunk' +import { threadDiffLines } from '../../../../src/shared/github/comments/pr-comment-diff-hunk' import { colors, spacing, typography } from '../../theme/mobile-theme' import { themedStyleSheet } from '../../theme/themed-styles' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' diff --git a/mobile/src/components/pr-sidebar/pr-actions-state.ts b/mobile/src/components/pr-sidebar/pr-actions-state.ts index ca224703516..b2844c528ca 100644 --- a/mobile/src/components/pr-sidebar/pr-actions-state.ts +++ b/mobile/src/components/pr-sidebar/pr-actions-state.ts @@ -1,12 +1,15 @@ -import type { GitHubPRMergeMethod, PRInfo } from '../../../../src/shared/github/pull-request-types' +import type { + GitHubPRMergeMethod, + PRInfo +} from '../../../../src/shared/github/pull-requests/pull-request-types' import { resolveGitHubPRMergeState, type GitHubPRMergeStateKind -} from '../../../../src/shared/github/pull-request-merge-state' +} from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-state' import { resolveGitHubPRStackMergeDecision, type GitHubPRStackMergeBlocker -} from '../../../../src/shared/github/pull-request-stack-merge' +} from '../../../../src/shared/github/pull-requests/merge/pull-request-stack-merge' import { translate } from '../../i18n/mobile-i18n' // Copy for desktop’s merge-state kinds; the shared rule decides which one applies. diff --git a/mobile/src/components/pr-sidebar/pr-check-detail-content.ts b/mobile/src/components/pr-sidebar/pr-check-detail-content.ts index 1f226876628..d95865918e4 100644 --- a/mobile/src/components/pr-sidebar/pr-check-detail-content.ts +++ b/mobile/src/components/pr-sidebar/pr-check-detail-content.ts @@ -2,7 +2,7 @@ import type { PRCheckAnnotation, PRCheckJob, PRCheckRunDetails -} from '../../../../src/shared/github/check-types' +} from '../../../../src/shared/github/checks/check-types' import { resolveStepOutcome, summarizeJobSteps diff --git a/mobile/src/components/pr-sidebar/pr-checks-presentation.ts b/mobile/src/components/pr-sidebar/pr-checks-presentation.ts index 3b9cba6a125..ed451236639 100644 --- a/mobile/src/components/pr-sidebar/pr-checks-presentation.ts +++ b/mobile/src/components/pr-sidebar/pr-checks-presentation.ts @@ -1,5 +1,5 @@ -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' -import type { PRState } from '../../../../src/shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' +import type { PRState } from '../../../../src/shared/github/pull-requests/pull-request-types' import { getProviderChecksOutcome, summarizeProviderChecks diff --git a/mobile/src/components/pr-sidebar/pr-tab-gates.ts b/mobile/src/components/pr-sidebar/pr-tab-gates.ts index e7aa9ee321b..66d8d132760 100644 --- a/mobile/src/components/pr-sidebar/pr-tab-gates.ts +++ b/mobile/src/components/pr-sidebar/pr-tab-gates.ts @@ -1,8 +1,8 @@ -import { isCompletePRCheckOwner } from '../../../../src/shared/github/complete-pr-check-owner' +import { isCompletePRCheckOwner } from '../../../../src/shared/github/checks/complete-pr-check-owner' import { getProviderChecksOutcome } from '../../../../src/shared/source-control/checks/provider-check-summary' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' -import type { PRInfo } from '../../../../src/shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' +import type { PRInfo } from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import { groupPRComments } from '../../../../src/shared/source-control/review-comments/pr-comment-groups' import { isBotPRComment } from '../../../../src/shared/source-control/review-comments/pr-comment-audience' import { translate } from '../../i18n/mobile-i18n' diff --git a/mobile/src/components/pr-sidebar/use-mobile-pr-polling.ts b/mobile/src/components/pr-sidebar/use-mobile-pr-polling.ts index 69a6f16aae6..a0c295fd531 100644 --- a/mobile/src/components/pr-sidebar/use-mobile-pr-polling.ts +++ b/mobile/src/components/pr-sidebar/use-mobile-pr-polling.ts @@ -3,9 +3,9 @@ import { useFocusEffect } from 'expo-router' import { ChecksDetailPollingPolicy, nextChecksPollBackoffMs -} from '../../../../src/shared/github/checks-detail-polling-policy' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' -import type { PRInfo } from '../../../../src/shared/github/pull-request-types' +} from '../../../../src/shared/github/checks/checks-detail-polling-policy' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' +import type { PRInfo } from '../../../../src/shared/github/pull-requests/pull-request-types' import { useHostQuery } from '../../query/host-query' import { NO_BRANCH, diff --git a/mobile/src/components/pr-sidebar/use-mobile-pr-tab.ts b/mobile/src/components/pr-sidebar/use-mobile-pr-tab.ts index 46ea23cb2d1..795f3acc787 100644 --- a/mobile/src/components/pr-sidebar/use-mobile-pr-tab.ts +++ b/mobile/src/components/pr-sidebar/use-mobile-pr-tab.ts @@ -8,7 +8,7 @@ import { type PrSidebarData, type PrSidebarState } from '../../source-control/review/mobile-pr-sidebar-state' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import type { PrSidebarLoadOptions } from '../../source-control/review/use-mobile-pr-sidebar-controller' import { prActionSlices, diff --git a/mobile/src/components/pr-sidebar/use-pr-check-run-details.ts b/mobile/src/components/pr-sidebar/use-pr-check-run-details.ts index b8a0896d446..a418f230af8 100644 --- a/mobile/src/components/pr-sidebar/use-pr-check-run-details.ts +++ b/mobile/src/components/pr-sidebar/use-pr-check-run-details.ts @@ -1,5 +1,5 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' import { fetchPRCheckDetails } from '../../source-control/review/github-pr-rpc' import '../../source-control/review/github-pr-repo-slug' import type { GitHubPrRepoSlug } from '../../source-control/review/github-pr-repo-slug' diff --git a/mobile/src/components/pr-sidebar/use-pr-tab-menu.ts b/mobile/src/components/pr-sidebar/use-pr-tab-menu.ts index 347b93bca31..a977b03f32a 100644 --- a/mobile/src/components/pr-sidebar/use-pr-tab-menu.ts +++ b/mobile/src/components/pr-sidebar/use-pr-tab-menu.ts @@ -5,8 +5,8 @@ import { triggerError, triggerSuccess } from '../../platform/haptics' import { openMobilePrUrl } from '../mobile-pr-url' import type { ActionSheetAction } from '../ActionSheetModal' import { canEditPRTitle } from '../../source-control/review/pr-title-edit' -import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-request-merge-methods' -import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-request-merge-state' +import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' +import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-state' import { MOBILE_GITHUB_MARK_PR_DRAFT_RUNTIME_CAPABILITY, MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY diff --git a/mobile/src/components/pr-sidebar/use-reviewer-people.ts b/mobile/src/components/pr-sidebar/use-reviewer-people.ts index b26e4d23e01..8ff18cb644b 100644 --- a/mobile/src/components/pr-sidebar/use-reviewer-people.ts +++ b/mobile/src/components/pr-sidebar/use-reviewer-people.ts @@ -1,8 +1,6 @@ import { useState } from 'react' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../src/shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import { useWorkspaceExecutionHost } from '../../navigation/workspace-execution-host' import { useHostQuery } from '../../query/host-query' import { isHostNotConnectedError } from '../../query/host-query-errors' diff --git a/mobile/src/components/use-mobile-markdown-tree.ts b/mobile/src/components/use-mobile-markdown-tree.ts index 544661db1ca..9d93e59bb78 100644 --- a/mobile/src/components/use-mobile-markdown-tree.ts +++ b/mobile/src/components/use-mobile-markdown-tree.ts @@ -3,7 +3,7 @@ import { useMemo, useRef } from 'react' import { linkGitHubReferences, type GitHubRepoReference -} from '../../../src/shared/github/github-reference-links' +} from '../../../src/shared/github/markdown/github-reference-links' import { linkNativeChatFilePaths, type FileLinkExists diff --git a/mobile/src/mobile-web-shell/bridge-host-requests.ts b/mobile/src/mobile-web-shell/bridge-host-requests.ts index 24984412811..9bb367d223a 100644 --- a/mobile/src/mobile-web-shell/bridge-host-requests.ts +++ b/mobile/src/mobile-web-shell/bridge-host-requests.ts @@ -1,4 +1,4 @@ -import { classifyExactGitHubOperation } from '../../../src/shared/github/exact-work-item-operation' +import { classifyExactGitHubOperation } from '../../../src/shared/github/work-items/exact-work-item-operation' import { captureGitHubInvocation, GITHUB_TARGET_CHECK_TIMED_OUT, diff --git a/mobile/src/new-workspace/source-selection/smart-source-fan-out.ts b/mobile/src/new-workspace/source-selection/smart-source-fan-out.ts index b87f09efe4f..fde41b68b68 100644 --- a/mobile/src/new-workspace/source-selection/smart-source-fan-out.ts +++ b/mobile/src/new-workspace/source-selection/smart-source-fan-out.ts @@ -1,5 +1,5 @@ import { linearIssueReadFailureMessage } from '../../../../src/shared/integration-credential-errors' -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../src/shared/gitlab-types' import type { LinearIssue } from '../../../../src/shared/linear/issue-types' import type { BaseRefSearchResult } from '../../../../src/shared/repo-types' @@ -8,7 +8,7 @@ import { type SmartNameMode } from '../../../../src/shared/new-workspace/smart-workspace-source-results' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../src/shared/github/work-item-ssh-error' +import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../src/shared/github/work-items/work-item-ssh-error' import type { MrStateFilter } from './mobile-composer-source-types' import type { JiraIssue, JiraSiteSelection } from '../../../../src/shared/jira/jira-types' import { diff --git a/mobile/src/new-workspace/source-selection/smart-source-paste-intent.ts b/mobile/src/new-workspace/source-selection/smart-source-paste-intent.ts index 61543d9d17d..16ebcfd0652 100644 --- a/mobile/src/new-workspace/source-selection/smart-source-paste-intent.ts +++ b/mobile/src/new-workspace/source-selection/smart-source-paste-intent.ts @@ -1,11 +1,11 @@ -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../src/shared/gitlab-types' +import { normalizeGitHubLinkQuery } from '../../../../src/shared/new-workspace/github-links' import { - normalizeGitHubLinkQuery, parseGitHubIssueOrPRLink, type GitHubIssueOrPRLink, type RepoSlug -} from '../../../../src/shared/new-workspace/github-links' +} from '../../../../src/shared/github/repositories/links' import { parseGitLabIssueOrMRLink } from '../../../../src/shared/new-workspace/gitlab-links' import { isSmartWorkspaceSourceQueryWithinLimit } from '../../../../src/shared/new-workspace/smart-workspace-source-results' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' @@ -16,7 +16,7 @@ import { readGitHubWorkItemByNumber, gitlabWorkItemByPathRead } from '../../tasks/providers/mobile-task-source-search-operations' -import { githubRepoIdentityKey } from '../../../../src/shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../src/shared/github/repositories/repository-identity-key' export type PasteRepoCandidate = { id: string diff --git a/mobile/src/new-workspace/source-selection/smart-source-search-requests.ts b/mobile/src/new-workspace/source-selection/smart-source-search-requests.ts index 94264aff688..a422f5b8692 100644 --- a/mobile/src/new-workspace/source-selection/smart-source-search-requests.ts +++ b/mobile/src/new-workspace/source-selection/smart-source-search-requests.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../src/shared/gitlab-types' import type { LinearIssue } from '../../../../src/shared/linear/issue-types' import type { BaseRefSearchResult } from '../../../../src/shared/repo-types' diff --git a/mobile/src/new-workspace/source-selection/use-mobile-composer-source.ts b/mobile/src/new-workspace/source-selection/use-mobile-composer-source.ts index 46cfee81afe..d160f997839 100644 --- a/mobile/src/new-workspace/source-selection/use-mobile-composer-source.ts +++ b/mobile/src/new-workspace/source-selection/use-mobile-composer-source.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useState } from 'react' -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../src/shared/gitlab-types' import type { LinearIssue } from '../../../../src/shared/linear/issue-types' import { resolveComposerManualBranchNameChange } from '../../../../src/shared/composer-branch-selection' diff --git a/mobile/src/new-workspace/source-selection/use-smart-workspace-source.ts b/mobile/src/new-workspace/source-selection/use-smart-workspace-source.ts index ba6752c1bb2..88c896a914c 100644 --- a/mobile/src/new-workspace/source-selection/use-smart-workspace-source.ts +++ b/mobile/src/new-workspace/source-selection/use-smart-workspace-source.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../src/shared/gitlab-types' import { buildSmartWorkspaceSourceRows, diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts b/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts index d8ad6f990d2..a6a992793d9 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts @@ -4,8 +4,8 @@ import { relativePathInsideRoot } from '../../../../src/shared/cross-platform-pa import { githubReferenceRepo, type GitHubRepoReference -} from '../../../../src/shared/github/github-reference-links' -import { githubRepoIdentityKey } from '../../../../src/shared/github/repository-identity-key' +} from '../../../../src/shared/github/markdown/github-reference-links' +import { githubRepoIdentityKey } from '../../../../src/shared/github/repositories/repository-identity-key' import { createNativeChatFileLinkChecker, type NativeChatFileLinkExistence diff --git a/mobile/src/source-control/mobile-source-control-next-step.ts b/mobile/src/source-control/mobile-source-control-next-step.ts index 5cff6d51750..e0588541413 100644 --- a/mobile/src/source-control/mobile-source-control-next-step.ts +++ b/mobile/src/source-control/mobile-source-control-next-step.ts @@ -1,8 +1,8 @@ import { translate } from '../i18n/mobile-i18n' -import type { PRInfo } from '../../../src/shared/github/pull-request-types' -import { resolveGitHubPRMergeMethods } from '../../../src/shared/github/pull-request-merge-methods' -import { getGitHubPRPrimaryReviewer } from '../../../src/shared/github/pr-reviewer-display' -import type { GitHubWorkItemDetails } from '../../../src/shared/github/work-item-types' +import type { PRInfo } from '../../../src/shared/github/pull-requests/pull-request-types' +import { resolveGitHubPRMergeMethods } from '../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' +import { getGitHubPRPrimaryReviewer } from '../../../src/shared/github/pull-requests/pr-reviewer-display' +import type { GitHubWorkItemDetails } from '../../../src/shared/github/work-items/work-item-types' import type { SourceControlNextStep } from '../../../src/shared/source-control/decisions/source-control-next-step-decision' import { presentPrMerge } from '../components/pr-sidebar/pr-actions-state' import type { MobileCreatePrAction } from './mobile-create-pr-action' diff --git a/mobile/src/source-control/mobile-source-control-primary-action.ts b/mobile/src/source-control/mobile-source-control-primary-action.ts index 673a3a3c28e..7e0e77f4e1f 100644 --- a/mobile/src/source-control/mobile-source-control-primary-action.ts +++ b/mobile/src/source-control/mobile-source-control-primary-action.ts @@ -12,7 +12,7 @@ import { hasResolvableHostedReviewPushTargetLink, resolveHostedReviewPushTargetForActions } from '../../../src/shared/source-control/hosted-review/hosted-review-push-target' -import type { PRState } from '../../../src/shared/github/pull-request-types' +import type { PRState } from '../../../src/shared/github/pull-requests/pull-request-types' import type { GitPushTarget } from '../../../src/shared/worktree/types' import type { PrSidebarState } from './review/mobile-pr-sidebar-state' import type { MobileGitBranchCompareResult } from './mobile-branch-compare' diff --git a/mobile/src/source-control/review/github-pr-check-reply-schema.ts b/mobile/src/source-control/review/github-pr-check-reply-schema.ts index 91e1a30fd5d..cf6bc977975 100644 --- a/mobile/src/source-control/review/github-pr-check-reply-schema.ts +++ b/mobile/src/source-control/review/github-pr-check-reply-schema.ts @@ -5,7 +5,7 @@ import type { PRCheckJob, PRCheckRunDetails, PRCheckStep -} from '../../../../src/shared/github/check-types' +} from '../../../../src/shared/github/checks/check-types' import { prCount, prText } from './github-pr-entity-reply-schema' // Require check name; default optional details because hosts may omit fields the panel renders unguarded. diff --git a/mobile/src/source-control/review/github-pr-entity-reply-schema.ts b/mobile/src/source-control/review/github-pr-entity-reply-schema.ts index f8e94b6e966..d573c6c4035 100644 --- a/mobile/src/source-control/review/github-pr-entity-reply-schema.ts +++ b/mobile/src/source-control/review/github-pr-entity-reply-schema.ts @@ -1,16 +1,19 @@ -import { normalizePRCheckHeadSha } from '../../../../src/shared/github/pr-check-head' +import { normalizePRCheckHeadSha } from '../../../../src/shared/github/checks/pr-check-head' import * as z from 'zod' import { openEnum, salvagedOptional, salvagingArray } from '../../../../src/shared/zod-salvage' -import type { GitHubReaction, PRComment } from '../../../../src/shared/github/comment-types' +import type { + GitHubReaction, + PRComment +} from '../../../../src/shared/github/comments/comment-types' import type { GitHubAssignableUser, GitHubPRFile, GitHubPRMergeMethodSettings, GitHubPRReviewSummary, - GitHubRepositoryIdentity, ProviderCheckSummary -} from '../../../../src/shared/github/pull-request-types' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' +} from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../src/shared/github/repositories/repository-identity' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' // Require entity identity, salvage malformed list members, and default other fields for unguarded renderers. diff --git a/mobile/src/source-control/review/github-pr-mutations.ts b/mobile/src/source-control/review/github-pr-mutations.ts index 0c2ba0cd587..7c418ffc17b 100644 --- a/mobile/src/source-control/review/github-pr-mutations.ts +++ b/mobile/src/source-control/review/github-pr-mutations.ts @@ -1,4 +1,4 @@ -import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-request-types' +import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-requests/pull-request-types' import { githubPrAutoMergeSet, githubPrBodySet, diff --git a/mobile/src/source-control/review/github-pr-read-reply-schema.ts b/mobile/src/source-control/review/github-pr-read-reply-schema.ts index ce0776e0c9f..fd64231a220 100644 --- a/mobile/src/source-control/review/github-pr-read-reply-schema.ts +++ b/mobile/src/source-control/review/github-pr-read-reply-schema.ts @@ -1,10 +1,10 @@ import * as z from 'zod' import { salvagedOptional, salvagingArray } from '../../../../src/shared/zod-salvage' -import type { PRInfo } from '../../../../src/shared/github/pull-request-types' +import type { PRInfo } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../src/shared/github/work-item-types' +} from '../../../../src/shared/github/work-items/work-item-types' import type { HostedReviewInfo } from '../../../../src/shared/source-control/hosted-review/hosted-review-contracts' import { CHECK_STATUS, diff --git a/mobile/src/source-control/review/github-pr-rpc.ts b/mobile/src/source-control/review/github-pr-rpc.ts index d8140cc14f4..99e0e2b281b 100644 --- a/mobile/src/source-control/review/github-pr-rpc.ts +++ b/mobile/src/source-control/review/github-pr-rpc.ts @@ -1,16 +1,19 @@ import { GITHUB_CONNECTION_CHANGED, githubOperationResponse } from './github-target-invocation' import { captureMobilePRCheckContract } from './pr-check-observation-contract' -import { acceptPRCheckObservation } from '../../../../src/shared/github/complete-pr-check-owner' +import { acceptPRCheckObservation } from '../../../../src/shared/github/checks/complete-pr-check-owner' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import { withGitHubTargetOperation } from './github-work-item-operation' -import { requireGitHubWorkItemTargetDetails } from '../../../../src/shared/github/work-item-target' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../src/shared/github/check-types' +import { requireGitHubWorkItemTargetDetails } from '../../../../src/shared/github/work-items/work-item-target' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../src/shared/github/checks/check-types' import type { GitHubAssignableUser, - GitHubOwnerRepo, PRInfo -} from '../../../../src/shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +} from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import type { HostedReviewInfo } from '../../../../src/shared/source-control/hosted-review/hosted-review-contracts' import { refusedRpcMessageOrFallback } from '../../transport/rpc/rpc-refusal-message' import type { RpcResponse } from '../../transport/rpc/rpc-wire-types' diff --git a/mobile/src/source-control/review/github-target-invocation.ts b/mobile/src/source-control/review/github-target-invocation.ts index d36bd67fe48..ba534434a69 100644 --- a/mobile/src/source-control/review/github-target-invocation.ts +++ b/mobile/src/source-control/review/github-target-invocation.ts @@ -6,7 +6,7 @@ import type { SendRequestOptions } from '../../transport/rpc/unvalidated-rpc-req import type { RpcResponse } from '../../transport/rpc/rpc-wire-types' import { readConnectionStatusProbe } from '../../transport/connection/runtime-status-probe' import { GITHUB_EXACT_WORK_ITEM_TARGET_RUNTIME_CAPABILITY } from '../../../../src/shared/source-control/runtime/source-control-runtime-capabilities' -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../src/shared/github/work-item-target' +import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../src/shared/github/work-items/work-item-target' export const GITHUB_CONNECTION_CHANGED = 'The Orca server connection changed. Try again.' export const GITHUB_TARGET_CHECK_TIMED_OUT = 'Timed out confirming the executing Orca server.' diff --git a/mobile/src/source-control/review/github-work-item-operation.ts b/mobile/src/source-control/review/github-work-item-operation.ts index a7acc260312..01ce2fc1789 100644 --- a/mobile/src/source-control/review/github-work-item-operation.ts +++ b/mobile/src/source-control/review/github-work-item-operation.ts @@ -1,12 +1,12 @@ import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import type { SendRequestOptions } from '../../transport/rpc/unvalidated-rpc-request-port' -import type { GitHubOwnerRepo } from '../../../../src/shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import { hasReachedAppVersion } from '../../../../src/shared/app-version' import { GITHUB_EXACT_OPERATION_UNSUPPORTED, matchesGitHubWorkItemTarget, type GitHubWorkItemTarget -} from '../../../../src/shared/github/work-item-target' +} from '../../../../src/shared/github/work-items/work-item-target' import { captureGitHubInvocation, GITHUB_DESKTOP_UPDATE_REQUIRED, diff --git a/mobile/src/source-control/review/mobile-diff-review-github.test.ts b/mobile/src/source-control/review/mobile-diff-review-github.test.ts index d9562349ab7..f54b9059306 100644 --- a/mobile/src/source-control/review/mobile-diff-review-github.test.ts +++ b/mobile/src/source-control/review/mobile-diff-review-github.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { PRComment } from '../../../../src/shared/github/comment-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import { buildMobileDiffReviewQueue } from './mobile-diff-review-queue' import { buildMobileDiffReviewGitHub, canCommentOnGitHub } from './mobile-diff-review-github' diff --git a/mobile/src/source-control/review/mobile-diff-review-github.ts b/mobile/src/source-control/review/mobile-diff-review-github.ts index cf06b10ec2b..c8d5e220d0a 100644 --- a/mobile/src/source-control/review/mobile-diff-review-github.ts +++ b/mobile/src/source-control/review/mobile-diff-review-github.ts @@ -1,6 +1,6 @@ -import type { GitHubPRFile } from '../../../../src/shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' -import { getPRInlineCommentPlacement } from '../../../../src/shared/github/pr-inline-comment-placement' +import type { GitHubPRFile } from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' +import { getPRInlineCommentPlacement } from '../../../../src/shared/github/comments/pr-inline-comment-placement' import { groupPRComments, type PRCommentGroup diff --git a/mobile/src/source-control/review/mobile-pr-queries.ts b/mobile/src/source-control/review/mobile-pr-queries.ts index adec08baecd..358c53055fa 100644 --- a/mobile/src/source-control/review/mobile-pr-queries.ts +++ b/mobile/src/source-control/review/mobile-pr-queries.ts @@ -1,20 +1,20 @@ import type { QueryClient, QueryKey } from '@tanstack/react-query' -import type { PRComment } from '../../../../src/shared/github/comment-types' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' import { acceptPRCheckObservation, completePRCheckOwnerScope -} from '../../../../src/shared/github/complete-pr-check-owner' +} from '../../../../src/shared/github/checks/complete-pr-check-owner' import type { GitHubAssignableUser, - GitHubOwnerRepo, PRInfo -} from '../../../../src/shared/github/pull-request-types' +} from '../../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import { matchesGitHubWorkItemTarget, resolveGitHubWorkItemTarget -} from '../../../../src/shared/github/work-item-target' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +} from '../../../../src/shared/github/work-items/work-item-target' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import { LOCAL_EXECUTION_HOST_ID } from '../../../../src/shared/execution-host' import { worktreeIdsEqual } from '../../../../src/shared/worktree/id' import { defineHostQuery, type HostQuerySignal, type HostSignalEvent } from '../../query/host-query' diff --git a/mobile/src/source-control/review/mobile-pr-sidebar-state.ts b/mobile/src/source-control/review/mobile-pr-sidebar-state.ts index 5038ee8801d..40e8dd0c50d 100644 --- a/mobile/src/source-control/review/mobile-pr-sidebar-state.ts +++ b/mobile/src/source-control/review/mobile-pr-sidebar-state.ts @@ -1,11 +1,11 @@ import { resolveGitHubWorkItemTarget, githubWorkItemTargetKey -} from '../../../../src/shared/github/work-item-target' -import type { PRCheckDetail } from '../../../../src/shared/github/check-types' -import type { PRInfo } from '../../../../src/shared/github/pull-request-types' +} from '../../../../src/shared/github/work-items/work-item-target' +import type { PRCheckDetail } from '../../../../src/shared/github/checks/check-types' +import type { PRInfo } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { GitPushTarget } from '../../../../src/shared/worktree/types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import type { HostedReviewInfo } from '../../../../src/shared/source-control/hosted-review/hosted-review-contracts' import type { GitHubPrReadOutcome } from './github-pr-rpc' import { isGitHubPRSuppressed } from '../../../../src/shared/worktree/github-pr-suppression' @@ -13,13 +13,13 @@ import { prBranchFallbackNeedsHint, resolvePRBranchFallback, shouldAcceptMergedFallbackPR -} from '../../../../src/shared/github/pr-branch-fallback-rule' +} from '../../../../src/shared/github/pull-requests/refresh/pr-branch-fallback-rule' import { shouldApplyBranchMismatchedLinkedPRClear, shouldApplyDivergedLinkedPRClear, shouldClearBranchMismatchedLinkedOpenPR, shouldClearDivergedLinkedMergedPR -} from '../../../../src/shared/github/linked-pr-staleness' +} from '../../../../src/shared/github/pull-requests/refresh/linked-pr-staleness' import type { MobileWorktreeSummary } from '../mobile-worktree-metadata-operations' import type { WorktreeGitHubPRLink } from '../mobile-pr-link' import { translate } from '../../i18n/mobile-i18n' diff --git a/mobile/src/source-control/review/pr-action-mutation-contract.ts b/mobile/src/source-control/review/pr-action-mutation-contract.ts index 89e97dd8185..83322cf9961 100644 --- a/mobile/src/source-control/review/pr-action-mutation-contract.ts +++ b/mobile/src/source-control/review/pr-action-mutation-contract.ts @@ -1,4 +1,4 @@ -import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-request-types' +import type { GitHubPRMergeMethod } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { GitHubPrMutationOutcome } from './github-pr-mutation-outcome' import type { GitHubPrRepoSlug } from './github-pr-repo-slug' diff --git a/mobile/src/source-control/review/pr-actions-engine.ts b/mobile/src/source-control/review/pr-actions-engine.ts index 1adcd578666..d916ccbf0e2 100644 --- a/mobile/src/source-control/review/pr-actions-engine.ts +++ b/mobile/src/source-control/review/pr-actions-engine.ts @@ -1,5 +1,8 @@ -import type { GitHubPRMergeMethod, PRState } from '../../../../src/shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../../../src/shared/github/repository-identity-key' +import type { + GitHubPRMergeMethod, + PRState +} from '../../../../src/shared/github/pull-requests/pull-request-types' +import { githubRepoIdentityKey } from '../../../../src/shared/github/repositories/repository-identity-key' import { classifyPrSidebarFailure } from './mobile-pr-sidebar-state' import { createOptimisticField, type OptimisticField } from './optimistic-write-sequence' import type { GitHubPrMutationOutcome } from './github-pr-mutation-outcome' diff --git a/mobile/src/source-control/review/pr-ai-triage-prompt.ts b/mobile/src/source-control/review/pr-ai-triage-prompt.ts index d5712e1b2d5..4f74eb71aa8 100644 --- a/mobile/src/source-control/review/pr-ai-triage-prompt.ts +++ b/mobile/src/source-control/review/pr-ai-triage-prompt.ts @@ -3,12 +3,15 @@ import { collectFixChecksPromptDetails, hasGitHubCheckHandle } from '../../../../src/shared/source-control/checks/pr-checks-fix-prompt' -import type { PRInfo } from '../../../../src/shared/github/pull-request-types' +import type { PRInfo } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import { fetchPRCheckDetails } from './github-pr-rpc' import { splitWorktreeIdForFilesystem } from '../../../../src/shared/worktree/id' import { buildResolvePullRequestConflictsPrompt } from '../../../../src/shared/source-control/recovery/source-control-conflict-prompts' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../src/shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../src/shared/github/checks/check-types' // Like desktop, the agent gets the failing steps' log tails, not just check names. export async function buildFixChecksPrompt(input: { diff --git a/mobile/src/source-control/review/pr-comment-actions.ts b/mobile/src/source-control/review/pr-comment-actions.ts index 536edb7c4e1..96f02cc8d8a 100644 --- a/mobile/src/source-control/review/pr-comment-actions.ts +++ b/mobile/src/source-control/review/pr-comment-actions.ts @@ -1,6 +1,7 @@ -import type { PRComment } from '../../../../src/shared/github/comment-types' -import { isMutablePRConversationComment } from '../../../../src/shared/github/pr-comment-mutability' -import type { GitHubOwnerRepo, PRState } from '../../../../src/shared/github/pull-request-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' +import { isMutablePRConversationComment } from '../../../../src/shared/github/comments/pr-comment-mutability' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' +import type { PRState } from '../../../../src/shared/github/pull-requests/pull-request-types' // Only inline review threads have resolvable node ids; conversation comments cannot resolve. export function isResolvableComment(comment: Pick): boolean { diff --git a/mobile/src/source-control/review/pr-comment-writes.ts b/mobile/src/source-control/review/pr-comment-writes.ts index 8aacbe331f7..85f722bdb3d 100644 --- a/mobile/src/source-control/review/pr-comment-writes.ts +++ b/mobile/src/source-control/review/pr-comment-writes.ts @@ -1,6 +1,6 @@ import { hashKey, type QueryClient, type QueryKey } from '@tanstack/react-query' -import type { PRComment } from '../../../../src/shared/github/comment-types' -import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' +import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-items/work-item-types' import { defineHostMutation } from '../../query/host-mutation' import { translate } from '../../i18n/mobile-i18n' import { isLogicalClientCutoverError } from '../../transport/connection/logical-client-cutover-error' diff --git a/mobile/src/source-control/review/pr-title-edit.ts b/mobile/src/source-control/review/pr-title-edit.ts index 13dde88cf05..add4fc90b0a 100644 --- a/mobile/src/source-control/review/pr-title-edit.ts +++ b/mobile/src/source-control/review/pr-title-edit.ts @@ -1,4 +1,4 @@ -import type { PRState } from '../../../../src/shared/github/pull-request-types' +import type { PRState } from '../../../../src/shared/github/pull-requests/pull-request-types' export function canEditPRTitle(state: PRState | null | undefined): boolean { return state === 'open' || state === 'draft' diff --git a/mobile/src/source-control/review/use-mobile-pr-comment-actions.ts b/mobile/src/source-control/review/use-mobile-pr-comment-actions.ts index d1f83cd1e96..de7ca0658c3 100644 --- a/mobile/src/source-control/review/use-mobile-pr-comment-actions.ts +++ b/mobile/src/source-control/review/use-mobile-pr-comment-actions.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useState } from 'react' -import type { PRComment } from '../../../../src/shared/github/comment-types' +import type { PRComment } from '../../../../src/shared/github/comments/comment-types' import type { ConnectionState } from '../../transport/connection/connection-state' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import type { GitHubPrRepoSlug } from './github-pr-repo-slug' diff --git a/mobile/src/source-control/review/use-mobile-pr-sidebar-controller.ts b/mobile/src/source-control/review/use-mobile-pr-sidebar-controller.ts index cc7973fcca1..70f70ebe2aa 100644 --- a/mobile/src/source-control/review/use-mobile-pr-sidebar-controller.ts +++ b/mobile/src/source-control/review/use-mobile-pr-sidebar-controller.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useQueryClient, type UseQueryResult } from '@tanstack/react-query' -import type { GitHubPRFileViewedState } from '../../../../src/shared/github/pull-request-types' +import type { GitHubPRFileViewedState } from '../../../../src/shared/github/pull-requests/pull-request-types' import { useWorkspaceExecutionHost } from '../../navigation/workspace-execution-host' import { readHostQueryLive, useHostQuery } from '../../query/host-query' import { isHostNotConnectedError } from '../../query/host-query-errors' diff --git a/mobile/src/source-control/review/use-mobile-pr-text-action.ts b/mobile/src/source-control/review/use-mobile-pr-text-action.ts index 1cdbb6cea44..c77b49bc46f 100644 --- a/mobile/src/source-control/review/use-mobile-pr-text-action.ts +++ b/mobile/src/source-control/review/use-mobile-pr-text-action.ts @@ -1,6 +1,6 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { QueryClient, QueryKey } from '@tanstack/react-query' -import { githubRepoIdentityKey } from '../../../../src/shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../src/shared/github/repositories/repository-identity-key' import type { ConnectionState } from '../../transport/connection/connection-state' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import type { GitHubPrRepoSlug } from './github-pr-repo-slug' diff --git a/mobile/src/source-control/use-mobile-source-control-next-step.ts b/mobile/src/source-control/use-mobile-source-control-next-step.ts index de02dc11052..a845e1f421c 100644 --- a/mobile/src/source-control/use-mobile-source-control-next-step.ts +++ b/mobile/src/source-control/use-mobile-source-control-next-step.ts @@ -13,8 +13,8 @@ import { } from './review/pr-ai-triage-prompt' import type { PrSidebarState } from './review/mobile-pr-sidebar-state' import { buildResolveConflictsPrompt } from '../../../src/shared/source-control/recovery/source-control-conflict-prompts' -import { resolveGitHubPRMergeMethods } from '../../../src/shared/github/pull-request-merge-methods' -import type { GitHubPRMergeMethod } from '../../../src/shared/github/pull-request-types' +import { resolveGitHubPRMergeMethods } from '../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' +import type { GitHubPRMergeMethod } from '../../../src/shared/github/pull-requests/pull-request-types' import { summarizeProviderChecks } from '../../../src/shared/source-control/checks/provider-check-summary' import { getBrokenChecks } from '../../../src/shared/source-control/checks/pr-checks-fix-prompt' import { resolveSourceControlCommitAreaPrimaryActionDecision } from '../../../src/shared/source-control/decisions/source-control-primary-action-decision' diff --git a/mobile/src/tasks/hosted-review/github-check-summary.ts b/mobile/src/tasks/hosted-review/github-check-summary.ts index 352b10c9ea4..c19ea8a7a21 100644 --- a/mobile/src/tasks/hosted-review/github-check-summary.ts +++ b/mobile/src/tasks/hosted-review/github-check-summary.ts @@ -1,5 +1,5 @@ import { summarizeProviderChecks } from '../../../../src/shared/source-control/checks/provider-check-summary' -import type { ProviderCheckSummary as GitHubCheckSummary } from '../../../../src/shared/github/pull-request-types' +import type { ProviderCheckSummary as GitHubCheckSummary } from '../../../../src/shared/github/pull-requests/pull-request-types' export type GitHubCheckLike = { status: string diff --git a/mobile/src/tasks/hosted-review/gitlab-check-summary.ts b/mobile/src/tasks/hosted-review/gitlab-check-summary.ts index 396c9512d1d..b72bf8e61fd 100644 --- a/mobile/src/tasks/hosted-review/gitlab-check-summary.ts +++ b/mobile/src/tasks/hosted-review/gitlab-check-summary.ts @@ -3,7 +3,7 @@ import { gitLabPipelineJobOutcome } from '../../../../src/shared/source-control/checks/gitlab/gitlab-pipeline-checks' import { summarizeProviderChecks } from '../../../../src/shared/source-control/checks/provider-check-summary' -import type { ProviderCheckSummary } from '../../../../src/shared/github/pull-request-types' +import type { ProviderCheckSummary } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { GitLabPipeline } from '../../../../src/shared/gitlab-types' type GitLabPipelineJobLike = { status: string; allowFailure?: boolean } diff --git a/mobile/src/tasks/hosted-review/mobile-hosted-check-status.ts b/mobile/src/tasks/hosted-review/mobile-hosted-check-status.ts index 50b4cad3845..78c7fecbee8 100644 --- a/mobile/src/tasks/hosted-review/mobile-hosted-check-status.ts +++ b/mobile/src/tasks/hosted-review/mobile-hosted-check-status.ts @@ -1,9 +1,9 @@ import type { PRMergeableState, ProviderCheckSummary -} from '../../../../src/shared/github/pull-request-types' +} from '../../../../src/shared/github/pull-requests/pull-request-types' import { translate } from '../../i18n/mobile-i18n' -import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-request-merge-state' +import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-state' import { getProviderChecksLabel, getProviderChecksOutcome diff --git a/mobile/src/tasks/hosted-review/mobile-task-pr-check-refresh.ts b/mobile/src/tasks/hosted-review/mobile-task-pr-check-refresh.ts index 4f63d8833f4..044dfb615c4 100644 --- a/mobile/src/tasks/hosted-review/mobile-task-pr-check-refresh.ts +++ b/mobile/src/tasks/hosted-review/mobile-task-pr-check-refresh.ts @@ -1,6 +1,6 @@ -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' -import { matchesGitHubWorkItemTarget } from '../../../../src/shared/github/work-item-target' -import { acceptPRCheckObservation } from '../../../../src/shared/github/complete-pr-check-owner' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' +import { matchesGitHubWorkItemTarget } from '../../../../src/shared/github/work-items/work-item-target' +import { acceptPRCheckObservation } from '../../../../src/shared/github/checks/complete-pr-check-owner' import { captureMobilePRCheckContract } from '../../source-control/review/pr-check-observation-contract' import { githubWorkItemBySlugRead } from '../providers/mobile-task-source-search-operations' import { diff --git a/mobile/src/tasks/hosted-review/mobile-tasks-hosted-review.ts b/mobile/src/tasks/hosted-review/mobile-tasks-hosted-review.ts index 21ca23ad892..c9885684939 100644 --- a/mobile/src/tasks/hosted-review/mobile-tasks-hosted-review.ts +++ b/mobile/src/tasks/hosted-review/mobile-tasks-hosted-review.ts @@ -13,7 +13,7 @@ import type { GitHubPRReviewerRow, GitHubWorkItem } from '../mobile-tasks-provider-detail-types' -import { GITHUB_PR_MERGE_METHOD_I18N_KEYS } from '../../../../src/shared/github/pull-request-merge-methods' +import { GITHUB_PR_MERGE_METHOD_I18N_KEYS } from '../../../../src/shared/github/pull-requests/merge/pull-request-merge-methods' import { translate } from '../../i18n/mobile-i18n' export const LINEAR_PRIORITY_LABELS: Record = { diff --git a/mobile/src/tasks/item-detail/github-task-identity.ts b/mobile/src/tasks/item-detail/github-task-identity.ts index eaccda22c40..2b9092ba3bf 100644 --- a/mobile/src/tasks/item-detail/github-task-identity.ts +++ b/mobile/src/tasks/item-detail/github-task-identity.ts @@ -8,7 +8,7 @@ import { githubWorkItemTargetKey, resolveGitHubWorkItemTarget, matchesGitHubWorkItemTarget -} from '../../../../src/shared/github/work-item-target' +} from '../../../../src/shared/github/work-items/work-item-target' export function githubTaskKey(source: GitHubWorkItem): string { const target = resolveGitHubWorkItemTarget(source) diff --git a/mobile/src/tasks/item-detail/mobile-tasks-item-mapping.ts b/mobile/src/tasks/item-detail/mobile-tasks-item-mapping.ts index 5df36ae4332..cf0b0d4db87 100644 --- a/mobile/src/tasks/item-detail/mobile-tasks-item-mapping.ts +++ b/mobile/src/tasks/item-detail/mobile-tasks-item-mapping.ts @@ -1,7 +1,7 @@ import { getGitHubTaskKind } from '../../../../src/shared/work-items/task-github-search' import type { RpcSuccess } from '../../transport/rpc/rpc-wire-types' -import type { GitHubOwnerRepo } from '../../../../src/shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import { colors } from '../../theme/mobile-theme' import type { GitHubPreset, diff --git a/mobile/src/tasks/item-detail/mobile-tasks-reviewer-linear.ts b/mobile/src/tasks/item-detail/mobile-tasks-reviewer-linear.ts index e8a58464dd2..c8784b6815a 100644 --- a/mobile/src/tasks/item-detail/mobile-tasks-reviewer-linear.ts +++ b/mobile/src/tasks/item-detail/mobile-tasks-reviewer-linear.ts @@ -1,4 +1,4 @@ -import type { GitHubOwnerRepo } from '../../../../src/shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import { colors } from '../../theme/mobile-theme' import { getLinearPriorityLabel, diff --git a/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-loading.tsx b/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-loading.tsx index 6363efa7479..9512034d15f 100644 --- a/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-loading.tsx +++ b/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-loading.tsx @@ -3,8 +3,8 @@ import { githubTaskKey, requireGitHubTaskTarget } from './github-task-identity' import { matchesGitHubWorkItemTarget, GitHubWorkItemTargetError -} from '../../../../src/shared/github/work-item-target' -import type { ProviderCheckSummary } from '../../../../src/shared/github/pull-request-types' +} from '../../../../src/shared/github/work-items/work-item-target' +import type { ProviderCheckSummary } from '../../../../src/shared/github/pull-requests/pull-request-types' import type { GitLabWorkItem } from '../mobile-tasks-provider-detail-types' import type { ItemDetailMetadataEffectsModel } from './use-mobile-tasks-item-detail-metadata-effects' import { fetchJiraIssueDetail } from '../providers/jira/mobile-jira-task-source' diff --git a/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-metadata-effects.tsx b/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-metadata-effects.tsx index 37d80813328..c692ca0a1d7 100644 --- a/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-metadata-effects.tsx +++ b/mobile/src/tasks/item-detail/use-mobile-tasks-item-detail-metadata-effects.tsx @@ -1,4 +1,4 @@ -import type { GitHubOwnerRepo } from '../../../../src/shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../src/shared/github/repositories/repository-identity' import type { SendRequestOptions } from '../../transport/rpc/unvalidated-rpc-request-port' import { SHOW_MOBILE_DETAIL_METADATA_EDITORS, diff --git a/mobile/src/tasks/mobile-tasks-provider-detail-types.ts b/mobile/src/tasks/mobile-tasks-provider-detail-types.ts index b0b87212442..d6eada7657c 100644 --- a/mobile/src/tasks/mobile-tasks-provider-detail-types.ts +++ b/mobile/src/tasks/mobile-tasks-provider-detail-types.ts @@ -1,8 +1,6 @@ import type { ExecutionHostId } from '../../../src/shared/execution-host' -import type { - ProviderCheckSummary, - GitHubOwnerRepo -} from '../../../src/shared/github/pull-request-types' +import type { ProviderCheckSummary } from '../../../src/shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../src/shared/github/repositories/repository-identity' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { HostedReviewDecision } from '../../../src/shared/source-control/hosted-review/hosted-review-contracts' diff --git a/mobile/src/tasks/mobile-tasks-view-state-types.ts b/mobile/src/tasks/mobile-tasks-view-state-types.ts index 27800645925..e965a680089 100644 --- a/mobile/src/tasks/mobile-tasks-view-state-types.ts +++ b/mobile/src/tasks/mobile-tasks-view-state-types.ts @@ -2,8 +2,8 @@ import type { DetailComment } from './mobile-tasks-provider-detail-types' import type { TuiAgent } from '../../../src/shared/agent-launch/catalog/tui-agent' import type { TaskProvider } from '../../../src/shared/work-items/task-providers' import type { GitHubProjectSettings } from './project-board/github-project-reference' -import type { GitHubProjectIdentity as GitHubProjectRef } from '../../../src/shared/github/project-identity' -import type { GitHubProjectSortDirection } from '../../../src/shared/github/project-types' +import type { GitHubProjectIdentity as GitHubProjectRef } from '../../../src/shared/github/projects/project-identity' +import type { GitHubProjectSortDirection } from '../../../src/shared/github/projects/project-types' import type { JiraIssueFilter } from '../../../src/shared/jira/jira-types' export type GitHubTaskKind = 'issues' | 'prs' diff --git a/mobile/src/tasks/project-board/github-project-reference.ts b/mobile/src/tasks/project-board/github-project-reference.ts index b57eac4dd76..ca658df3d7e 100644 --- a/mobile/src/tasks/project-board/github-project-reference.ts +++ b/mobile/src/tasks/project-board/github-project-reference.ts @@ -1,11 +1,11 @@ import { GITHUB_OWNER_NUMBER_SHORTHAND_RE, GITHUB_OWNER_SLUG_RE -} from '../../../../src/shared/github/owner-slug' +} from '../../../../src/shared/github/repositories/owner-slug' import type { GitHubProjectIdentity, GitHubProjectIdentity as GitHubProjectRef -} from '../../../../src/shared/github/project-identity' +} from '../../../../src/shared/github/projects/project-identity' export type GitHubProjectOwnerType = GitHubProjectIdentity['ownerType'] diff --git a/mobile/src/tasks/project-board/github-project-repo-match.ts b/mobile/src/tasks/project-board/github-project-repo-match.ts index fa3c7047614..f81014be34f 100644 --- a/mobile/src/tasks/project-board/github-project-repo-match.ts +++ b/mobile/src/tasks/project-board/github-project-repo-match.ts @@ -1,7 +1,7 @@ import { githubRepoIdentityKey, isDefaultGitHubHost -} from '../../../../src/shared/github/repository-identity-key' +} from '../../../../src/shared/github/repositories/repository-identity-key' export type GitHubProjectRepoMatch = { id: string diff --git a/mobile/src/tasks/project-board/mobile-tasks-github-project-list.tsx b/mobile/src/tasks/project-board/mobile-tasks-github-project-list.tsx index 77f2803ba0a..a83e734afca 100644 --- a/mobile/src/tasks/project-board/mobile-tasks-github-project-list.tsx +++ b/mobile/src/tasks/project-board/mobile-tasks-github-project-list.tsx @@ -1,4 +1,4 @@ -import { projectContentField } from '../../../../src/shared/github/project-content-field' +import { projectContentField } from '../../../../src/shared/github/projects/project-content-field' import type { ConnectionPresentationModel } from '../screen-state/use-mobile-tasks-connection-presentation' import { translate } from '../../i18n/mobile-i18n' import { View, Text, Pressable, FlatList } from 'react-native' diff --git a/mobile/src/tasks/project-board/mobile-tasks-github-project-pickers.tsx b/mobile/src/tasks/project-board/mobile-tasks-github-project-pickers.tsx index f9b63427764..1c06924726a 100644 --- a/mobile/src/tasks/project-board/mobile-tasks-github-project-pickers.tsx +++ b/mobile/src/tasks/project-board/mobile-tasks-github-project-pickers.tsx @@ -10,7 +10,7 @@ import { githubProjectIdentityKey as githubProjectKey, toGitHubProjectIdentity, type GitHubProjectIdentity as GitHubProjectRef -} from '../../../../src/shared/github/project-identity' +} from '../../../../src/shared/github/projects/project-identity' import { PickerModal } from '../../components/PickerModal' import { ListGroup, SectionHeader, ListRow } from '../../components/list/ListGroup' import { styles } from '../mobile-tasks-legacy-styles' diff --git a/mobile/src/tasks/project-board/mobile-tasks-project-fields.ts b/mobile/src/tasks/project-board/mobile-tasks-project-fields.ts index 3be86502923..b71bb5b4745 100644 --- a/mobile/src/tasks/project-board/mobile-tasks-project-fields.ts +++ b/mobile/src/tasks/project-board/mobile-tasks-project-fields.ts @@ -1,11 +1,14 @@ import { projectContentField, projectContentFieldLabel -} from '../../../../src/shared/github/project-content-field' +} from '../../../../src/shared/github/projects/project-content-field' import { translate } from '../../i18n/mobile-i18n' -import type { GitHubProjectTable as SharedGitHubProjectTable } from '../../../../src/shared/github/project-types' -import { isIterationCurrent } from '../../../../src/shared/github/project-group-sort' -import type { ProjectGroup } from '../../../../src/shared/github/project-group-sort' +import type { GitHubProjectTable as SharedGitHubProjectTable } from '../../../../src/shared/github/projects/project-types' +import { + isIterationCurrent, + type ProjectGroup +} from '../../../../src/shared/github/projects/project-group-sort' + import type { ProjectSortOverride } from '../task-list/mobile-tasks-options' import type { GitHubProjectField, diff --git a/mobile/src/tasks/project-board/task-project-board-reply-schema.ts b/mobile/src/tasks/project-board/task-project-board-reply-schema.ts index 38248ba70fd..b8393c1e6ed 100644 --- a/mobile/src/tasks/project-board/task-project-board-reply-schema.ts +++ b/mobile/src/tasks/project-board/task-project-board-reply-schema.ts @@ -1,5 +1,5 @@ import * as z from 'zod' -import type { GitHubProjectOwnerType } from '../../../../src/shared/github/project-types' +import type { GitHubProjectOwnerType } from '../../../../src/shared/github/projects/project-types' import { hostUnionArms, salvagedOptional, salvagingArray } from '../../../../src/shared/zod-salvage' import { assignableUserListSchema, diff --git a/mobile/src/tasks/project-board/use-mobile-tasks-project-loading-actions.tsx b/mobile/src/tasks/project-board/use-mobile-tasks-project-loading-actions.tsx index cd678b2d035..110b3445046 100644 --- a/mobile/src/tasks/project-board/use-mobile-tasks-project-loading-actions.tsx +++ b/mobile/src/tasks/project-board/use-mobile-tasks-project-loading-actions.tsx @@ -11,7 +11,7 @@ import { githubProjectHost, githubProjectIdentityKey as githubProjectKey, toGitHubProjectIdentity -} from '../../../../src/shared/github/project-identity' +} from '../../../../src/shared/github/projects/project-identity' import { useCallback, useRef } from 'react' import type { GitHubProjectTable } from '../mobile-tasks-view-state-types' import { diff --git a/mobile/src/tasks/project-board/use-mobile-tasks-project-projection.tsx b/mobile/src/tasks/project-board/use-mobile-tasks-project-projection.tsx index f83968ced9e..bbeeb26f711 100644 --- a/mobile/src/tasks/project-board/use-mobile-tasks-project-projection.tsx +++ b/mobile/src/tasks/project-board/use-mobile-tasks-project-projection.tsx @@ -3,12 +3,16 @@ import { filterGitHubProjectRowsForRepos, findRepoForGitHubProjectRepository } from './github-project-repo-match' -import { githubProjectHost } from '../../../../src/shared/github/project-identity' +import { githubProjectHost } from '../../../../src/shared/github/projects/project-identity' import { hasSettledHostRepoList } from '../repositories/host-repo-list' import { isHostedTaskRepo } from '../repositories/hosted-repo-selection' import { useCallback, useMemo } from 'react' -import { groupRows, sortRows } from '../../../../src/shared/github/project-group-sort' -import type { ProjectGroup } from '../../../../src/shared/github/project-group-sort' +import { + groupRows, + sortRows, + type ProjectGroup +} from '../../../../src/shared/github/projects/project-group-sort' + import type { GitHubProjectRow } from '../mobile-tasks-view-state-types' import type { ProjectListEntry } from '../task-list/mobile-tasks-options' import type { RepoSummary } from '../mobile-tasks-provider-detail-types' diff --git a/mobile/src/tasks/project-board/use-mobile-tasks-project-repository-resolution.tsx b/mobile/src/tasks/project-board/use-mobile-tasks-project-repository-resolution.tsx index 0d350135d7b..61dfbeab98b 100644 --- a/mobile/src/tasks/project-board/use-mobile-tasks-project-repository-resolution.tsx +++ b/mobile/src/tasks/project-board/use-mobile-tasks-project-repository-resolution.tsx @@ -1,5 +1,5 @@ import type { ProjectProjectionModel } from './use-mobile-tasks-project-projection' -import { githubProjectIdentityKey as githubProjectKey } from '../../../../src/shared/github/project-identity' +import { githubProjectIdentityKey as githubProjectKey } from '../../../../src/shared/github/projects/project-identity' import { useEffect, useMemo } from 'react' import { GITHUB_REPO_CONCURRENCY } from '../task-list/mobile-tasks-options' import { diff --git a/mobile/src/tasks/project-board/use-mobile-tasks-provider-view-projection.tsx b/mobile/src/tasks/project-board/use-mobile-tasks-provider-view-projection.tsx index 46493616781..12d5af852a4 100644 --- a/mobile/src/tasks/project-board/use-mobile-tasks-provider-view-projection.tsx +++ b/mobile/src/tasks/project-board/use-mobile-tasks-provider-view-projection.tsx @@ -3,7 +3,7 @@ import { JIRA_FILTER_LABELS } from '../providers/jira/mobile-jira-issue-filters' import { jiraSiteLabel } from '../providers/jira/jira-mobile-connection' import { useMobileTranslation } from '../../i18n/use-mobile-translation' import type { PickerOption } from '../../components/PickerModal' -import { githubProjectIdentityKey as githubProjectKey } from '../../../../src/shared/github/project-identity' +import { githubProjectIdentityKey as githubProjectKey } from '../../../../src/shared/github/projects/project-identity' import { useCallback, useMemo } from 'react' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { diff --git a/mobile/src/tasks/project-board/use-mobile-tasks-workspace-and-project-state.tsx b/mobile/src/tasks/project-board/use-mobile-tasks-workspace-and-project-state.tsx index da4a7a3f033..0be43209263 100644 --- a/mobile/src/tasks/project-board/use-mobile-tasks-workspace-and-project-state.tsx +++ b/mobile/src/tasks/project-board/use-mobile-tasks-workspace-and-project-state.tsx @@ -10,7 +10,7 @@ import type { GitHubProjectSummary, GitHubProjectViewSummary } from './github-project-reference' -import type { GitHubProjectIdentity as GitHubProjectRef } from '../../../../src/shared/github/project-identity' +import type { GitHubProjectIdentity as GitHubProjectRef } from '../../../../src/shared/github/projects/project-identity' import type { PersistedTrustedOrcaHooks } from '../../../../src/shared/orca-yaml-hook-types' import type { SparsePreset } from '../../../../src/shared/worktree/create-types' import type { WorkspaceAgentChoice } from '../../new-workspace/creation/workspace-agent-selection' diff --git a/mobile/src/tasks/providers/mobile-task-source-search-operations.ts b/mobile/src/tasks/providers/mobile-task-source-search-operations.ts index 611d0829f4b..56c92661178 100644 --- a/mobile/src/tasks/providers/mobile-task-source-search-operations.ts +++ b/mobile/src/tasks/providers/mobile-task-source-search-operations.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../src/shared/github/work-items/work-item-types' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import type { SendRequestOptions } from '../../transport/rpc/unvalidated-rpc-request-port' import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' diff --git a/mobile/src/tasks/task-list/mobile-tasks-options.tsx b/mobile/src/tasks/task-list/mobile-tasks-options.tsx index 1236b9c49a3..e2132258242 100644 --- a/mobile/src/tasks/task-list/mobile-tasks-options.tsx +++ b/mobile/src/tasks/task-list/mobile-tasks-options.tsx @@ -4,8 +4,8 @@ import { colors } from '../../theme/mobile-theme' import { getLinkedWorkItemSuggestedName } from '../../new-workspace/creation/mobile-workspace-name' import type { GitHubProjectSettings } from '../project-board/github-project-reference' import type { TaskProvider } from '../../../../src/shared/work-items/task-providers' -import type { GitHubProjectSortDirection } from '../../../../src/shared/github/project-types' -import type { ProjectGroup } from '../../../../src/shared/github/project-group-sort' +import type { GitHubProjectSortDirection } from '../../../../src/shared/github/projects/project-types' +import type { ProjectGroup } from '../../../../src/shared/github/projects/project-group-sort' import type { GitHubMode, GitHubPreset, diff --git a/mobile/src/tasks/task-list/use-mobile-tasks-provider-load-actions.tsx b/mobile/src/tasks/task-list/use-mobile-tasks-provider-load-actions.tsx index a4563f66d7e..9803c3ebda6 100644 --- a/mobile/src/tasks/task-list/use-mobile-tasks-provider-load-actions.tsx +++ b/mobile/src/tasks/task-list/use-mobile-tasks-provider-load-actions.tsx @@ -1,5 +1,5 @@ import type { ClassifiedError } from '../../../../src/shared/classified-error' -import { getWorkItemPageErrorTypes } from '../../../../src/shared/github/work-item-page-errors' +import { getWorkItemPageErrorTypes } from '../../../../src/shared/github/work-items/work-item-page-errors' import type { RuntimeHydrationModel } from '../screen-state/use-mobile-tasks-runtime-hydration' import { readJiraConnection, @@ -12,7 +12,7 @@ import { getWorkItemCountedTotalPages, taskPageToGitHubApiPage } from '../../../../src/shared/work-items/work-item-pagination' -import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../src/shared/github/work-item-ssh-error' +import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../src/shared/github/work-items/work-item-ssh-error' import { type GitHubIssueSourceError, type GitHubIssueSourceFallback, diff --git a/mobile/src/tasks/task-provider-entity-reply-schema.ts b/mobile/src/tasks/task-provider-entity-reply-schema.ts index 2b6809dd2cb..b0cb46731da 100644 --- a/mobile/src/tasks/task-provider-entity-reply-schema.ts +++ b/mobile/src/tasks/task-provider-entity-reply-schema.ts @@ -1,6 +1,6 @@ -import { normalizePRCheckHeadSha } from '../../../src/shared/github/pr-check-head' +import { normalizePRCheckHeadSha } from '../../../src/shared/github/checks/pr-check-head' import * as z from 'zod' -import type { GitHubPRFile } from '../../../src/shared/github/pull-request-types' +import type { GitHubPRFile } from '../../../src/shared/github/pull-requests/pull-request-types' import { hostUnionArms, salvagedOptional, salvagingArray } from '../../../src/shared/zod-salvage' import { prCount, diff --git a/mobile/src/test-support/rpc-recording/changed-page-outcomes.ts b/mobile/src/test-support/rpc-recording/changed-page-outcomes.ts index 61f8ddd9977..64b085089e3 100644 --- a/mobile/src/test-support/rpc-recording/changed-page-outcomes.ts +++ b/mobile/src/test-support/rpc-recording/changed-page-outcomes.ts @@ -1,4 +1,4 @@ -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../src/shared/github/work-item-target' +import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../src/shared/github/work-items/work-item-target' import { GITHUB_CONNECTION_CHANGED } from '../../source-control/review/github-target-invocation' import { isKeyedObject, scriptsAbsentResultReply } from '../bridged-parity/divergence-evidence' import { rows } from './changed-bridge-outcomes' diff --git a/mobile/src/worktree/workspace-details-model.ts b/mobile/src/worktree/workspace-details-model.ts index e2f4e33dee1..a5b23629f09 100644 --- a/mobile/src/worktree/workspace-details-model.ts +++ b/mobile/src/worktree/workspace-details-model.ts @@ -1,5 +1,5 @@ import { getProviderChecksOutcome } from '../../../src/shared/source-control/checks/provider-check-summary' -import type { CheckStatus } from '../../../src/shared/github/pull-request-types' +import type { CheckStatus } from '../../../src/shared/github/pull-requests/pull-request-types' import { getWorkspaceAttachmentKey, getWorkspaceAttachmentUrlScope diff --git a/src/main/azure-devops/pull-request-mappers.ts b/src/main/azure-devops/pull-request-mappers.ts index 4ba68fce2e2..f769eaa0597 100644 --- a/src/main/azure-devops/pull-request-mappers.ts +++ b/src/main/azure-devops/pull-request-mappers.ts @@ -3,7 +3,7 @@ import type { CheckStatus, PRMergeableState, ProviderCheckSummary -} from '../../shared/github/pull-request-types' +} from '../../shared/github/pull-requests/pull-request-types' export type RawAzureDevOpsStatus = { state?: string | null diff --git a/src/main/bitbucket/client.ts b/src/main/bitbucket/client.ts index 5d3ed1d1839..e675230e792 100644 --- a/src/main/bitbucket/client.ts +++ b/src/main/bitbucket/client.ts @@ -1,4 +1,7 @@ -import type { CheckStatus, ProviderCheckSummary } from '../../shared/github/pull-request-types' +import type { + CheckStatus, + ProviderCheckSummary +} from '../../shared/github/pull-requests/pull-request-types' import { deriveBitbucketBuildStatus, summarizeBitbucketBuildStatuses, diff --git a/src/main/bitbucket/pull-request-mappers.ts b/src/main/bitbucket/pull-request-mappers.ts index bed469a3adb..2df66404ded 100644 --- a/src/main/bitbucket/pull-request-mappers.ts +++ b/src/main/bitbucket/pull-request-mappers.ts @@ -3,7 +3,7 @@ import type { CheckStatus, PRMergeableState, ProviderCheckSummary -} from '../../shared/github/pull-request-types' +} from '../../shared/github/pull-requests/pull-request-types' export type RawBitbucketPullRequest = { id?: number diff --git a/src/main/git/command-runner/hosted-cli/gh-bound-account-env.ts b/src/main/git/command-runner/hosted-cli/gh-bound-account-env.ts index d2d7766f430..cda087a5fd9 100644 --- a/src/main/git/command-runner/hosted-cli/gh-bound-account-env.ts +++ b/src/main/git/command-runner/hosted-cli/gh-bound-account-env.ts @@ -1,4 +1,4 @@ -import { normalizeGhAccountBinding } from '../../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../../shared/github/accounts/account-binding' import type { GhExecOptions } from './gh-exec-file' import { collectGhArgvHostSignals } from './gh-host-args' import type { ResolvedCommand } from '../host-routing/wsl-command-resolution' @@ -38,7 +38,7 @@ export async function resolveBoundGhExecEnv( createGhBoundAccountHostMismatchError, createGhBoundAccountUnavailableError, resolveGhAccountToken - } = await import('../../../github/gh-account-token') + } = await import('../../../github/accounts/gh-account-token') const normalized = normalizeGhAccountBinding(binding) if (!normalized) { throw createGhBoundAccountUnavailableError(binding) diff --git a/src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts b/src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts index 7846adbede5..db8f2f7a331 100644 --- a/src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts +++ b/src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts @@ -1,5 +1,5 @@ +import type * as GhAccountTokenModule from '../../../github/accounts/gh-account-token' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createFakeSpawnedChild as mockChild } from '../../../../shared/__fixtures__/fake-spawned-child' const { spawnMock, @@ -28,9 +28,7 @@ vi.mock('node:child_process', async (importOriginal) => ({ spawn: spawnMock })) -import type * as GhAccountTokenModule from '../../../github/gh-account-token' - -vi.mock('../../../github/gh-account-token', async (importOriginal) => { +vi.mock('../../../github/accounts/gh-account-token', async (importOriginal) => { const actual = await importOriginal() return { ...actual, @@ -44,24 +42,6 @@ import { ghExecFileAsync } from './gh-exec-file' import { _resetGhRateLimitBreaker, recordGhPrimaryRateLimit } from './gh-rate-limit-breaker' /** Spawns a child that settles with `stdout`, capturing the env the runner handed it. */ -function spawnSettledChild(stdout: string): { - env: () => NodeJS.ProcessEnv | undefined -} { - let capturedEnv: NodeJS.ProcessEnv | undefined - spawnMock.mockImplementation( - (_cmd: string, _args: string[], opts: { env?: NodeJS.ProcessEnv }) => { - capturedEnv = opts.env - const child = mockChild() - queueMicrotask(() => { - child.stdout?.emit('data', Buffer.from(stdout)) - child.emit('exit', 0, null) - child.emit('close', 0, null) - }) - return child - } - ) - return { env: () => capturedEnv } -} describe('ghExecFileAsync account binding', () => { beforeEach(() => { @@ -93,19 +73,6 @@ describe('ghExecFileAsync account binding', () => { _resetGhRateLimitBreaker() }) - it('skips injection for auth probes', async () => { - const spawned = spawnSettledChild('') - - await ghExecFileAsync(['auth', 'status'], { - host: 'github.com', - ghAccount: { host: 'github.com', user: 'Alice' }, - env: { ...process.env, GH_TOKEN: 'ambient' } - }) - - expect(resolveGhAccountTokenMock).not.toHaveBeenCalled() - expect(spawned.env()?.GH_TOKEN).toBe('ambient') - }) - it('fails closed when argv host signals disagree with the binding', async () => { await expect( ghExecFileAsync(['pr', 'list', '--repo', 'github.acme.com/a/b'], { @@ -129,20 +96,6 @@ describe('ghExecFileAsync account binding', () => { } ) - it('still spawns the keyring token read while the core bucket is blocked', async () => { - recordGhPrimaryRateLimit('core', Date.now() + 60_000, 'native:github.com') - const spawned = spawnSettledChild('gho_bound\n') - - const { stdout } = await ghExecFileAsync( - ['auth', 'token', '--user', 'Alice', '--hostname', 'github.com'], - { host: 'github.com' } - ) - - expect(stdout.trim()).toBe('gho_bound') - expect(spawnMock).toHaveBeenCalledTimes(1) - expect(spawned.env()).toBeDefined() - }) - it('fails a bound API call fast on a blocked bucket without resolving a token', async () => { recordGhPrimaryRateLimit('core', Date.now() + 60_000, 'native:github.com') diff --git a/src/main/git/objects/merge-tree-object-quarantine-real-git.test.ts b/src/main/git/objects/merge-tree-object-quarantine-real-git.test.ts index 1f0e35f8267..5716ff5e377 100644 --- a/src/main/git/objects/merge-tree-object-quarantine-real-git.test.ts +++ b/src/main/git/objects/merge-tree-object-quarantine-real-git.test.ts @@ -10,8 +10,8 @@ import { import { __resetPRConflictSummaryCachesForTests, getPRConflictSummary -} from '../../github/conflict-summary' -import { __resetPRConflictSummaryDerivationCachesForTests } from '../../github/conflict-summary-cache' +} from '../../github/pull-requests/conflicts/conflict-summary' +import { __resetPRConflictSummaryDerivationCachesForTests } from '../../github/pull-requests/conflicts/conflict-summary-cache' import { getLocalGitCapabilityCache } from '../command-runner/host-routing/git-capability-state' import { deleteBranchAfterWorktreeRemoval } from '../worktrees/removal/worktree-branch-removal' diff --git a/src/main/git/remotes/remote-name-listing.test.ts b/src/main/git/remotes/remote-name-listing.test.ts index f66100cf5f6..4e5561dd971 100644 --- a/src/main/git/remotes/remote-name-listing.test.ts +++ b/src/main/git/remotes/remote-name-listing.test.ts @@ -9,16 +9,12 @@ vi.mock('../command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) -vi.mock('../../github/local-git-config-signature', () => ({ +vi.mock('../../github/repositories/local-git-config-signature', () => ({ readLocalGitConfigSignature: readLocalGitConfigSignatureMock })) import { REMOTE_URL_PROBE_TIMEOUT_MS } from './remote-url-probe' -import { - _resetRemoteNameListingCache, - listCachedRemoteNames, - shouldProbeGitRemote -} from './remote-name-listing' +import { _resetRemoteNameListingCache, shouldProbeGitRemote } from './remote-name-listing' function remoteListCalls(): unknown[][] { return gitExecFileAsyncMock.mock.calls.filter( @@ -92,22 +88,6 @@ describe('cached git remote name listing', () => { expect(remoteListCalls()).toHaveLength(2) }) - it('coalesces concurrent listings onto one spawn', async () => { - gitExecFileAsyncMock.mockImplementation(async () => { - await Promise.resolve() - return { stdout: 'origin\n' } - }) - - await expect( - Promise.all([ - shouldProbeGitRemote('/repo', 'upstream'), - shouldProbeGitRemote('/repo', 'upstream'), - listCachedRemoteNames('/repo') - ]) - ).resolves.toEqual([false, false, ['origin']]) - expect(remoteListCalls()).toHaveLength(1) - }) - it('keeps host and WSL listings separate', async () => { gitExecFileAsyncMock.mockImplementation( async (_args: string[], options: { wslDistro?: string } = {}) => ({ diff --git a/src/main/git/remotes/remote-name-listing.ts b/src/main/git/remotes/remote-name-listing.ts index 03f2d723817..dfa2bb81831 100644 --- a/src/main/git/remotes/remote-name-listing.ts +++ b/src/main/git/remotes/remote-name-listing.ts @@ -1,4 +1,4 @@ -import { readLocalGitConfigSignature } from '../../github/local-git-config-signature' +import { readLocalGitConfigSignature } from '../../github/repositories/local-git-config-signature' import { runCoalescedProbe, type CoalescedProbes } from './coalesced-probe' import type { GitAdmissionTier } from '../../../shared/rpc-contract/git-admission-tier-params' import { REMOTE_URL_PROBE_TIMEOUT_MS } from './remote-url-probe' diff --git a/src/main/git/remotes/task-repository-eligibility.ts b/src/main/git/remotes/task-repository-eligibility.ts index 5141c38343d..8da4e10707a 100644 --- a/src/main/git/remotes/task-repository-eligibility.ts +++ b/src/main/git/remotes/task-repository-eligibility.ts @@ -1,7 +1,7 @@ import { parseGitRemoteVerboseOutput } from '../../../shared/git/remotes/git-remote-identity' -import { isDefaultGitHubHost } from '../../../shared/github/repository-identity-key' -import { isGitHubHostAuthenticated } from '../../github/github-enterprise-repository' -import { parseGitHubRemoteIdentity } from '../../github/github-remote-identity-parsing' +import { isDefaultGitHubHost } from '../../../shared/github/repositories/repository-identity-key' +import { isGitHubHostAuthenticated } from '../../github/repositories/github-enterprise-repository' +import { parseGitHubRemoteIdentity } from '../../github/repositories/github-remote-identity-parsing' import { getGlabKnownHosts, getGlabPartialHosts diff --git a/src/main/git/remotes/worktree-push-target-cleanup.ts b/src/main/git/remotes/worktree-push-target-cleanup.ts index 036479d8d51..0bc4c94d404 100644 --- a/src/main/git/remotes/worktree-push-target-cleanup.ts +++ b/src/main/git/remotes/worktree-push-target-cleanup.ts @@ -3,7 +3,7 @@ import type { Store } from '../../persistence/loading-store/store' import type { WorktreeMeta } from '../../../shared/worktree/meta-types' import type { GitPushTarget } from '../../../shared/worktree/types' -import { parseGitHubOwnerRepo } from '../../github/github-remote-identity-parsing' +import { parseGitHubOwnerRepo } from '../../github/repositories/github-remote-identity-parsing' import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id' import { iterateProcessOutputLines } from '../../../shared/process-output-field-scanner' diff --git a/src/main/gitea/pull-request-mappers.ts b/src/main/gitea/pull-request-mappers.ts index f48097dc183..94885a4ba12 100644 --- a/src/main/gitea/pull-request-mappers.ts +++ b/src/main/gitea/pull-request-mappers.ts @@ -1,4 +1,7 @@ -import type { CheckStatus, PRMergeableState } from '../../shared/github/pull-request-types' +import type { + CheckStatus, + PRMergeableState +} from '../../shared/github/pull-requests/pull-request-types' export type RawGiteaPullRequest = { number?: number diff --git a/src/main/github/account-binding-required-host.test.ts b/src/main/github/accounts/account-binding-required-host.test.ts similarity index 100% rename from src/main/github/account-binding-required-host.test.ts rename to src/main/github/accounts/account-binding-required-host.test.ts diff --git a/src/main/github/account-binding-required-host.ts b/src/main/github/accounts/account-binding-required-host.ts similarity index 86% rename from src/main/github/account-binding-required-host.ts rename to src/main/github/accounts/account-binding-required-host.ts index a027dc750a7..cec24af14c9 100644 --- a/src/main/github/account-binding-required-host.ts +++ b/src/main/github/accounts/account-binding-required-host.ts @@ -1,13 +1,16 @@ import { isUnresolvedSshHostAlias, foldComparableGitHubHost -} from '../../shared/git/remotes/git-remote-host-alias' -import { githubHostFromIdentityKey } from '../../shared/github/repository-identity-key' -import { rawSshTransportHost, remoteUrlUsesSshTransport } from './github-remote-identity-parsing' +} from '../../../shared/git/remotes/git-remote-host-alias' +import { githubHostFromIdentityKey } from '../../../shared/github/repositories/repository-identity-key' +import { + rawSshTransportHost, + remoteUrlUsesSshTransport +} from '../repositories/github-remote-identity-parsing' import { resolveSshConfigHostname, type SshConfigResolutionContext -} from './github-ssh-host-alias-resolution' +} from '../repositories/github-ssh-host-alias-resolution' export type SshAliasHostnameResolver = ( alias: string, diff --git a/src/main/github/auth-diagnose.test.ts b/src/main/github/accounts/auth-diagnose.test.ts similarity index 97% rename from src/main/github/auth-diagnose.test.ts rename to src/main/github/accounts/auth-diagnose.test.ts index 2852c9c4ce4..b8664916037 100644 --- a/src/main/github/auth-diagnose.test.ts +++ b/src/main/github/accounts/auth-diagnose.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest' const { ghExecFileAsyncMock } = vi.hoisted(() => ({ ghExecFileAsyncMock: vi.fn() })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: ghExecFileAsyncMock })) diff --git a/src/main/github/auth-diagnose.ts b/src/main/github/accounts/auth-diagnose.ts similarity index 96% rename from src/main/github/auth-diagnose.ts rename to src/main/github/accounts/auth-diagnose.ts index e7d6c87c760..b7ded5d90ed 100644 --- a/src/main/github/auth-diagnose.ts +++ b/src/main/github/accounts/auth-diagnose.ts @@ -1,6 +1,6 @@ // Environment tokens override keyring credentials and cannot be refreshed through gh auth refresh. -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import type { GhAuthDiagnostic, GhAuthAccount } from '../../shared/github/auth-types' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import type { GhAuthDiagnostic, GhAuthAccount } from '../../../shared/github/accounts/auth-types' // ProjectV2 and surrounding repository/organization queries require these scopes. const REQUIRED_SCOPES = ['project', 'read:org', 'repo'] as const diff --git a/src/main/github/gh-account-binding-inventory.ts b/src/main/github/accounts/gh-account-binding-inventory.ts similarity index 93% rename from src/main/github/gh-account-binding-inventory.ts rename to src/main/github/accounts/gh-account-binding-inventory.ts index 4e10b59ec8c..ad55c04b08f 100644 --- a/src/main/github/gh-account-binding-inventory.ts +++ b/src/main/github/accounts/gh-account-binding-inventory.ts @@ -6,12 +6,12 @@ import { type GhCapabilityTarget } from './gh-capability-state' import { invalidateGhAccountTokenCache, resolveGhAccountToken } from './gh-account-token' -import { normalizeGhAccountBinding } from '../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../shared/github/accounts/account-binding' import type { GhAccountBindingInventory, GhAccountBindingValidationResult -} from '../../shared/github/auth-types' -import type { LocalGitExecOptions } from './github-repository-identity' +} from '../../../shared/github/accounts/auth-types' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' function capabilityTargetFromLocalGitOptions( localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/gh-account-token.ts b/src/main/github/accounts/gh-account-token.ts similarity index 96% rename from src/main/github/gh-account-token.ts rename to src/main/github/accounts/gh-account-token.ts index fbcfdb5d84a..10c336254b0 100644 --- a/src/main/github/gh-account-token.ts +++ b/src/main/github/accounts/gh-account-token.ts @@ -1,12 +1,12 @@ // Resolve bound credentials into child environments only; never mutate process-wide auth or log tokens. -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import type { GhCaptureLifetimeHooks } from '../git/command-runner/hosted-cli/gh-capture-lifetime' -import { addWslEnvKeys } from '../../shared/wsl-env' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import type { GhCaptureLifetimeHooks } from '../../git/command-runner/hosted-cli/gh-capture-lifetime' +import { addWslEnvKeys } from '../../../shared/wsl-env' import { ghTokenEnvVarForHost, normalizeGhAccountBinding, type GhAccountBinding -} from '../../shared/github/account-binding' +} from '../../../shared/github/accounts/account-binding' import { createGhMultiAccountUnsupportedError, getGhExecutionHostKey, diff --git a/src/main/github/gh-capability-state.ts b/src/main/github/accounts/gh-capability-state.ts similarity index 95% rename from src/main/github/gh-capability-state.ts rename to src/main/github/accounts/gh-capability-state.ts index 83889216ef1..c9f98af4bf2 100644 --- a/src/main/github/gh-capability-state.ts +++ b/src/main/github/accounts/gh-capability-state.ts @@ -1,11 +1,11 @@ // Require gh >=2.40 multi-account support per execution runtime before offering bindings. -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import { parseWslUncPath } from '../../shared/wsl-paths' -import type { GhMultiAccountCapability } from '../../shared/github/auth-types' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { parseWslUncPath } from '../../../shared/wsl-paths' +import type { GhMultiAccountCapability } from '../../../shared/github/accounts/auth-types' import { createGhLogicalLifetime, type GhCaptureLifetimeHooks -} from '../git/command-runner/hosted-cli/gh-capture-lifetime' +} from '../../git/command-runner/hosted-cli/gh-capture-lifetime' export type GhCapabilityTarget = { cwd?: string diff --git a/src/main/github/github-project-account.test.ts b/src/main/github/accounts/github-project-account.test.ts similarity index 88% rename from src/main/github/github-project-account.test.ts rename to src/main/github/accounts/github-project-account.test.ts index 904d18b049c..ffeb8cec0a0 100644 --- a/src/main/github/github-project-account.test.ts +++ b/src/main/github/accounts/github-project-account.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GhUtils from './gh-utils' -import type * as GitRunner from '../git/command-runner/git-exec-file' +import type * as GhUtilsOwner1 from '../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GitRunner from '../../git/command-runner/git-exec-file' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -9,29 +9,29 @@ import { closeTestStores, makeRepo, testState -} from '../persistence/persistence-test-harness' -import { OrcaRuntimeService } from '../runtime/orca-runtime' -import { RpcDispatcher } from '../runtime/rpc/dispatcher' -import { GITHUB_REPO_WORK_ITEM_METHODS } from '../runtime/rpc/methods/source-control/github/github-repo-work-item-methods' -import { GITHUB_PULL_REQUEST_METHODS } from '../runtime/rpc/methods/source-control/github/github-pull-request-methods' -import { registerGitHubWorkItemHandlers } from '../ipc/source-control/github-work-item-handlers' -import { registerGitHubIssueMutationHandlers } from '../ipc/source-control/github-issue-mutation-handlers' -import { registerGitHubPRReviewHandlers } from '../ipc/source-control/github-pr-review-handlers' -import { ghPullRequestsAndWorkItemsApi } from '../../preload/api/gh-bridge-pull-requests-and-work-items' -import { ghMutationsAndProjectsApi } from '../../preload/api/gh-bridge-mutations-and-projects' -import type { GhAccountBinding } from '../../shared/github/account-binding' -import { RPC_PARAMS_BY_METHOD } from '../../shared/rpc-contract/rpc-params-catalog.generated' +} from '../../persistence/persistence-test-harness' +import { OrcaRuntimeService } from '../../runtime/orca-runtime' +import { RpcDispatcher } from '../../runtime/rpc/dispatcher' +import { GITHUB_REPO_WORK_ITEM_METHODS } from '../../runtime/rpc/methods/source-control/github/github-repo-work-item-methods' +import { GITHUB_PULL_REQUEST_METHODS } from '../../runtime/rpc/methods/source-control/github/github-pull-request-methods' +import { registerGitHubWorkItemHandlers } from '../../ipc/source-control/github-work-item-handlers' +import { registerGitHubIssueMutationHandlers } from '../../ipc/source-control/github-issue-mutation-handlers' +import { registerGitHubPRReviewHandlers } from '../../ipc/source-control/github-pr-review-handlers' +import { ghPullRequestsAndWorkItemsApi } from '../../../preload/api/gh-bridge-pull-requests-and-work-items' +import { ghMutationsAndProjectsApi } from '../../../preload/api/gh-bridge-mutations-and-projects' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' +import { RPC_PARAMS_BY_METHOD } from '../../../shared/rpc-contract/rpc-params-catalog.generated' const { exec, handle, invoke } = vi.hoisted(() => ({ - exec: vi.fn(), + exec: vi.fn(), handle: vi.fn<(channel: string, listener: (event: unknown, args: unknown) => unknown) => void>(), invoke: vi.fn<(channel: string, args: unknown) => Promise>() })) -vi.mock('./gh-utils', async (original) => ({ - ...(await original()), +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (original) => ({ + ...(await original()), ghExecFileAsync: exec })) -vi.mock('../git/command-runner/git-exec-file', async (original) => { +vi.mock('../../git/command-runner/git-exec-file', async (original) => { const actual = await original() return { ...actual, @@ -55,13 +55,13 @@ vi.mock('electron', () => ({ BrowserWindow: { fromId: () => null, getAllWindows: () => [] }, webContents: { fromId: () => null, getAllWebContents: () => [] } })) -vi.mock('../ipc/ui', () => ({ sendToTrustedUIRenderer: vi.fn() })) -import type { Project } from '../../shared/project-types' -import type { Repo } from '../../shared/repo-types' -import { ghRepoExecOptions, githubRepoContext } from './github-repository-identity' +vi.mock('../../ipc/ui', () => ({ sendToTrustedUIRenderer: vi.fn() })) +import type { Project } from '../../../shared/project-types' +import type { Repo } from '../../../shared/repo-types' +import { ghRepoExecOptions, githubRepoContext } from '../repositories/github-repository-identity' import { installGitHubProjectAccounts } from './github-project-account' -import { githubReadExecutionScope } from './github-read-execution-scope' -import { resolveGitHubRepoExecution } from './github-api-repository' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../source-control/code-host-read-execution-scope' +import { resolveGitHubRepoExecution } from '../repositories/github-api-repository' let release: (() => void) | undefined afterEach(() => release?.()) diff --git a/src/main/github/github-project-account.ts b/src/main/github/accounts/github-project-account.ts similarity index 81% rename from src/main/github/github-project-account.ts rename to src/main/github/accounts/github-project-account.ts index 7fb62454df4..c091d564bac 100644 --- a/src/main/github/github-project-account.ts +++ b/src/main/github/accounts/github-project-account.ts @@ -1,16 +1,16 @@ -import type { Repo } from '../../shared/repo-types' -import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-path' -import { getConnectionExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' -import { normalizeGhAccountBinding } from '../../shared/github/account-binding' -import { isGitRepoKind } from '../../shared/repo-kind' -import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id' -import { parseWslUncPath } from '../../shared/wsl-paths' +import type { Repo } from '../../../shared/repo-types' +import { normalizeRuntimePathForComparison } from '../../../shared/cross-platform-path' +import { getConnectionExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' +import { normalizeGhAccountBinding } from '../../../shared/github/accounts/account-binding' +import { isGitRepoKind } from '../../../shared/repo-kind' +import { splitWorktreeIdForFilesystem } from '../../../shared/worktree/id' +import { parseWslUncPath } from '../../../shared/wsl-paths' import { resolveLocalProjectRuntimesForRepos, type ProjectRuntimeResolutionStore -} from '../workspaces/execution-host/local-project-runtime-resolution' -import { getStoredRepoExecutionHostId } from '../workspaces/execution-host/repo-execution-host' -import type { GitHubRepoContext } from './github-repository-identity' +} from '../../workspaces/execution-host/local-project-runtime-resolution' +import { getStoredRepoExecutionHostId } from '../../workspaces/execution-host/repo-execution-host' +import type { GitHubRepoContext } from '../repositories/github-repository-identity' type AccountStore = ProjectRuntimeResolutionStore & { getRepos: () => readonly Repo[] diff --git a/src/main/github/project-account-operations.test.ts b/src/main/github/accounts/project-account-operations.test.ts similarity index 72% rename from src/main/github/project-account-operations.test.ts rename to src/main/github/accounts/project-account-operations.test.ts index b0901ecc271..30b8fe74dba 100644 --- a/src/main/github/project-account-operations.test.ts +++ b/src/main/github/accounts/project-account-operations.test.ts @@ -1,41 +1,43 @@ -import type { createGitHubPullRequest } from './client/create/create-github-pull-request' +import type { createGitHubPullRequest } from '../client/create/create-github-pull-request' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { createFakeSpawnedChild } from '../../shared/__fixtures__/fake-spawned-child' +import { createFakeSpawnedChild } from '../../../shared/__fixtures__/fake-spawned-child' import { createStore, closeTestStores, testState, makeRepo -} from '../persistence/persistence-test-harness' +} from '../../persistence/persistence-test-harness' const { spawnMock, remote } = vi.hoisted(() => ({ spawnMock: vi.fn(), remote: vi.fn() })) vi.mock('node:child_process', async (original) => ({ ...(await original()), spawn: spawnMock })) -vi.mock('./github-api-repository-remote-probe', () => ({ +vi.mock('../repositories/github-api-repository-remote-probe', () => ({ getOriginGitHubApiRepository: remote, getGitHubApiRepositoryForRemote: remote })) -vi.mock('../git/remotes/remote-name-listing', () => ({ shouldProbeGitRemote: async () => false })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../git/remotes/remote-name-listing', () => ({ + shouldProbeGitRemote: async () => false +})) +vi.mock('../api/rate-limit', () => ({ repositoryRateLimitGuard: () => ({ blocked: false }), noteRepositoryRateLimitSpend: () => {}, spendsSharedGitHubComQuota: () => false })) -vi.mock('./client/list/work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) -vi.mock('./client/lookup/pr-lookup-rate-limit', () => ({ assertRateLimitBudget: async () => {} })) +vi.mock('../client/list/work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) +vi.mock('../client/lookup/pr-lookup-rate-limit', () => ({ assertRateLimitBudget: async () => {} })) -vi.mock('../source-control/forge-provider-detection', () => ({ +vi.mock('../../source-control/forge-provider-detection', () => ({ detectHostedReviewProvider: async () => 'github', getForgeProviderForRepository: async () => ({ id: 'github', getReviewForBranch: async () => null, createReview: async (...args: Parameters) => - (await import('./client/create/create-github-pull-request')).createGitHubPullRequest(...args) + (await import('../client/create/create-github-pull-request')).createGitHubPullRequest(...args) }) })) -vi.mock('../source-control/hosted-review-creation-git-state', async (original) => ({ +vi.mock('../../source-control/hosted-review-creation-git-state', async (original) => ({ ...(await original()), getCurrentBranch: async () => 'feature', hasUncommittedChanges: async () => false, @@ -43,29 +45,29 @@ vi.mock('../source-control/hosted-review-creation-git-state', async (original) = baseRefExistsOnRemote: async () => true, getDefaultBaseRef: async () => 'parent' })) -vi.mock('../source-control/hosted-review-creation-provider', async (original) => ({ +vi.mock('../../source-control/hosted-review-creation-provider', async (original) => ({ ...(await original()), isProviderAuthenticated: async () => true })) import { installGitHubProjectAccounts } from './github-project-account' -import { getPRChecks } from './client/check/get-pr-checks' -import { rerunPRChecks } from './client/check/rerun-pr-checks' -import { getPRFileContents } from './work-item-details' -import './pr-start-point' -import '../runtime/integrations/github/runtime-github-repository-query-commands' +import { getPRChecks } from '../client/check/get-pr-checks' + +import { getPRFileContents } from '../pull-requests/files/pull-request-file-contents' +import '../pull-requests/start-points/pr-start-point' +import '../../runtime/integrations/github/runtime-github-repository-query-commands' import { clearGhAccountTokenCacheForTests } from './gh-account-token' import { clearGhCapabilityStateForTests } from './gh-capability-state' -import { _resetGhRateLimitBreaker } from '../git/command-runner/hosted-cli/gh-rate-limit-breaker' -import { getPRForBranchOutcome } from './client/lookup/pr-for-branch-outcome' -import { createStackedHostedReview } from '../source-control/stacked-hosted-review-creation' -import './pr-refresh-candidate-policy' -import { fetchPullRequestWorkItem } from './client/fetch/work-item-fetch' -import { repositoryMergeMetadataCache } from './client/detect/repository-merge-metadata-cache' -import { _resetPRStackSummaryCacheForTests } from './client/lookup/pr-stack-summary-cache' -import { _resetGitHubPRStackCacheForTests } from './github-pr-stack' -import { resetMergedPRCommitMembershipCacheForTest } from './merged-pr-commit-membership' -import './github-repository-identity' +import { _resetGhRateLimitBreaker } from '../../git/command-runner/hosted-cli/gh-rate-limit-breaker' +import { getPRForBranchOutcome } from '../client/lookup/pr-for-branch-outcome' + +import '../pull-requests/refresh/pr-refresh-candidate-policy' +import { fetchPullRequestWorkItem } from '../client/fetch/work-item-fetch' +import { repositoryMergeMetadataCache } from '../client/detect/repository-merge-metadata-cache' +import { _resetPRStackSummaryCacheForTests } from '../client/lookup/pr-stack-summary-cache' +import { _resetGitHubPRStackCacheForTests } from '../pull-requests/stacks/github-pr-stack' +import { resetMergedPRCommitMembershipCacheForTest } from '../pull-requests/refresh/merged-pr-commit-membership' +import '../repositories/github-repository-identity' const alice = { host: 'github.com', user: 'Alice' } const bob = { host: 'github.com', user: 'Bob' } @@ -220,21 +222,6 @@ it('separates unresolved callers by saved account before coalescing and returns expect(requests.map((r) => r.token)).toEqual(['Alice-token', 'Bob-token']) }) -it('settles the full rerun read/write on the initial account and uses the rebind on the next operation', async () => { - rebindDuringFirstResolution() - await expect( - rerunPRChecks('/work', 42, { prRepo: repository, failedOnly: true }, null, { ghAccount: bob }) - ).resolves.toEqual({ ok: true, count: 1 }) - expect(requests.map((r) => ({ post: r.args.includes('POST'), token: r.token }))).toEqual([ - { post: false, token: 'Alice-token' }, - { post: true, token: 'Alice-token' } - ]) - await expect( - rerunPRChecks('/work', 42, { prRepo: repository, failedOnly: true }) - ).resolves.toEqual({ ok: true, count: 1 }) - expect(requests.slice(2).map((r) => r.token)).toEqual(['Bob-token', 'Bob-token']) -}) - it('loads both file versions under one saved selection across repository resolution and future rebind', async () => { rebindDuringFirstResolution() request = (args, child, token) => @@ -262,56 +249,6 @@ it('loads both file versions under one saved selection across repository resolut }) }) -it('settles stack prepare/create/register with one saved selection after a pending rebind', async () => { - let finish: () => void = () => {} - let started: () => void = () => {} - const resolving = new Promise((resolve) => { - started = resolve - }) - remote.mockImplementationOnce( - () => - new Promise((resolve) => { - finish = () => resolve(repository) - started() - }) - ) - request = (args, child) => { - let output: unknown = [] - if (args[0] === 'pr' && args[1] === 'create') { - output = { number: 43, url: 'https://github.com/acme/widgets/pull/43' } - } else if (args.some((arg) => arg.includes('pulls?') && arg.includes('%3Aparent'))) { - output = [ - { - number: 42, - html_url: 'https://github.com/acme/widgets/pull/42', - head: { ref: 'parent' }, - base: { ref: 'main' } - } - ] - } else if (args.includes('POST')) { - output = { number: 50 } - } - queueMicrotask(() => complete(child, JSON.stringify(output))) - } - const pending = createStackedHostedReview( - '/work', - { provider: 'github', base: 'parent', head: 'feature', title: 'Change' }, - 'local' - ) - await resolving - rebind() - finish() - await expect(pending).resolves.toMatchObject({ - ok: true, - number: 43, - stackNumber: 50, - parentReview: { number: 42 } - }) - expect(requests.some((r) => r.args[0] === 'pr' && r.args[1] === 'create')).toBe(true) - expect(requests.some((r) => r.args.includes('POST'))).toBe(true) - expect(requests.every((r) => r.token === 'Alice-token')).toBe(true) -}) - it('returns each saved viewer merge preference with omitted detector scope and reuses only that viewer', async () => { request = (args, child, token) => queueMicrotask(() => diff --git a/src/main/github/gh-error-classification.ts b/src/main/github/api/gh-error-classification.ts similarity index 95% rename from src/main/github/gh-error-classification.ts rename to src/main/github/api/gh-error-classification.ts index 0167bec3fd4..ab1a21fbd15 100644 --- a/src/main/github/gh-error-classification.ts +++ b/src/main/github/api/gh-error-classification.ts @@ -1,5 +1,5 @@ -import type { ClassifiedError } from '../../shared/classified-error' -import { classifiedCodeHostError as classified } from '../../shared/code-host-failure-copy' +import type { ClassifiedError } from '../../../shared/classified-error' +import { classifiedCodeHostError as classified } from '../../../shared/code-host-failure-copy' export function classifyGhError(stderr: string): ClassifiedError { const s = stderr.toLowerCase() diff --git a/src/main/github/mappers.ts b/src/main/github/api/github-read-response.ts similarity index 89% rename from src/main/github/mappers.ts rename to src/main/github/api/github-read-response.ts index cbaecf02315..251bdd06b68 100644 --- a/src/main/github/mappers.ts +++ b/src/main/github/api/github-read-response.ts @@ -1,6 +1,5 @@ -import type { PRCheckDetail } from '../../shared/github/check-types' -import type { CheckStatus, IssueInfo, PRInfo } from '../../shared/github/pull-request-types' -import { derivePRCheckStatusFromRollup } from '../../shared/source-control/checks/pr-check-status' +import type { PRCheckDetail } from '../../../shared/github/checks/check-types' +import type { IssueInfo, PRInfo } from '../../../shared/github/pull-requests/pull-request-types' // REST check runs separate status/conclusion; gh pr checks combines them into one state. @@ -138,7 +137,3 @@ export function mapIssueInfo(data: { ...(typeof data.body === 'string' ? { description: data.body } : {}) } } - -export function deriveCheckStatus(rollup: unknown[] | null | undefined): CheckStatus { - return derivePRCheckStatusFromRollup(rollup) -} diff --git a/src/main/github/github-rest-revalidation.test.ts b/src/main/github/api/github-rest-revalidation.test.ts similarity index 78% rename from src/main/github/github-rest-revalidation.test.ts rename to src/main/github/api/github-rest-revalidation.test.ts index 2a1986314b7..299974fc318 100644 --- a/src/main/github/github-rest-revalidation.test.ts +++ b/src/main/github/api/github-rest-revalidation.test.ts @@ -2,8 +2,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { revalidateGitHubRestJson } from './github-rest-revalidation' const { gh, spend } = vi.hoisted(() => ({ gh: vi.fn(), spend: vi.fn() })) -vi.mock('./gh-utils', () => ({ ghExecFileAsync: gh })) -vi.mock('./rate-limit', () => ({ noteRepositoryRateLimitSpend: spend })) +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: gh +})) +vi.mock('./rate-limit', () => ({ + noteRepositoryRateLimitSpend: spend +})) const repository = { owner: 'acme', repo: 'widgets', host: 'github.com' } const endpoint = 'repos/acme/widgets/pulls/7' @@ -58,23 +62,6 @@ describe('GitHub REST revalidation', () => { expect(gh.mock.calls[2][0]).not.toContain('-H') }) - it('keeps credentials, hosts, runtimes, directories, and endpoints separate', async () => { - gh.mockResolvedValue(http(200, '{"number":7}', '"v1"')) - await read(repository, endpoint, options) - await read(repository, `${endpoint}/files`, options) - await read(repository, endpoint, { cwd: '/other' }) - await read(repository, endpoint, { ...options, wslDistro: 'Ubuntu' }) - await read({ ...repository, host: 'github.example.com' }, endpoint, options) - await read(repository, endpoint, { - ...options, - ghAccount: { host: 'github.com', user: 'other' } - }) - vi.stubEnv('GH_TOKEN', 'different-test-account') - await read(repository, endpoint, options) - expect(gh.mock.calls.every(([args]) => !args.includes('-H'))).toBe(true) - expect(gh.mock.calls[4][1].host).toBe('github.example.com') - }) - it('does not retain malformed or validator-free bodies and rejects ungrounded 304 responses', async () => { gh.mockResolvedValueOnce(http(200, '{"number":7}')) .mockResolvedValueOnce(http(200, 'truncated', '"bad"')) diff --git a/src/main/github/github-rest-revalidation.ts b/src/main/github/api/github-rest-revalidation.ts similarity index 81% rename from src/main/github/github-rest-revalidation.ts rename to src/main/github/api/github-rest-revalidation.ts index b381f7cb1e7..db2a79027f5 100644 --- a/src/main/github/github-rest-revalidation.ts +++ b/src/main/github/api/github-rest-revalidation.ts @@ -1,12 +1,10 @@ -import { BoundedMap } from '../../shared/bounded-map' -import { extractExecError } from '../git/command-runner/process-capture/exec-error' -import { ghExecFileAsync } from './gh-utils' -import { githubReadExecutionScope } from './github-read-execution-scope' -import { - githubHostExecOptions, - type GitHubApiRepository, - type GitHubRepoExecOptions -} from './github-api-repository' +import { BoundedMap } from '../../../shared/bounded-map' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../source-control/code-host-read-execution-scope' +import { githubHostExecOptions } from '../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../repositories/github-api-repository' import { noteRepositoryRateLimitSpend } from './rate-limit' type Representation = { etag: string; body: string } diff --git a/src/main/github/rate-limit.test.ts b/src/main/github/api/rate-limit.test.ts similarity index 92% rename from src/main/github/rate-limit.test.ts rename to src/main/github/api/rate-limit.test.ts index b99de4a6f68..8c145141539 100644 --- a/src/main/github/rate-limit.test.ts +++ b/src/main/github/api/rate-limit.test.ts @@ -4,18 +4,18 @@ const { ghExecFileAsyncMock } = vi.hoisted(() => ({ ghExecFileAsyncMock: vi.fn() })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: ghExecFileAsyncMock })) -import { acquire, release } from './gh-utils' +import { acquire, release } from '../execution/github-operation-slots' import { getRateLimit, _resetRateLimitCache } from './rate-limit' import { clearGhRateLimitBlock, notifyGhPrimaryRateLimit, type GhRateLimitBucket -} from '../git/command-runner/hosted-cli/gh-rate-limit-breaker' +} from '../../git/command-runner/hosted-cli/gh-rate-limit-breaker' // Refine only the tripping host/runtime's breaker; preserve the shared native snapshot. diff --git a/src/main/github/rate-limit.ts b/src/main/github/api/rate-limit.ts similarity index 96% rename from src/main/github/rate-limit.ts rename to src/main/github/api/rate-limit.ts index 1e95ab99636..1088a1051b0 100644 --- a/src/main/github/rate-limit.ts +++ b/src/main/github/api/rate-limit.ts @@ -3,11 +3,11 @@ import type { GetRateLimitResult, GitHubRateLimitBucket, GitHubRateLimitSnapshot -} from '../../shared/github/rate-limit-types' -import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key' -import { PrioritySemaphore } from '../../shared/priority-semaphore' -import { isWslUncPath } from '../../shared/wsl-paths' -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' +} from '../../../shared/github/api/rate-limit-types' +import { isDefaultGitHubHost } from '../../../shared/github/repositories/repository-identity-key' +import { PrioritySemaphore } from '../../../shared/priority-semaphore' +import { isWslUncPath } from '../../../shared/wsl-paths' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' import { clearGhRateLimitBlock, getGhRateLimitBlockedUntilMs, @@ -16,7 +16,7 @@ import { recordGhPrimaryRateLimit, registerGhRateLimitResetProbe, type GhRateLimitBucket -} from '../git/command-runner/hosted-cli/gh-rate-limit-breaker' +} from '../../git/command-runner/hosted-cli/gh-rate-limit-breaker' // GET /rate_limit is quota-exempt; caching avoids a subprocess per refresh. const RATE_LIMIT_CACHE_TTL_MS = 30_000 diff --git a/src/main/github/body-image-urls.test.ts b/src/main/github/attachments/body-image-urls.test.ts similarity index 100% rename from src/main/github/body-image-urls.test.ts rename to src/main/github/attachments/body-image-urls.test.ts diff --git a/src/main/github/body-image-urls.ts b/src/main/github/attachments/body-image-urls.ts similarity index 100% rename from src/main/github/body-image-urls.ts rename to src/main/github/attachments/body-image-urls.ts diff --git a/src/main/github/image-attachment-upload.ts b/src/main/github/attachments/image-attachment-upload.ts similarity index 87% rename from src/main/github/image-attachment-upload.ts rename to src/main/github/attachments/image-attachment-upload.ts index 7ee97203d9c..1bdd84ad910 100644 --- a/src/main/github/image-attachment-upload.ts +++ b/src/main/github/attachments/image-attachment-upload.ts @@ -1,6 +1,11 @@ -import { acquire, extractExecError, ghExecFileAsync, release } from './gh-utils' -import type { GitHubRepoExecution } from './github-api-repository' -import { assertGitHubImage, GitHubImageValidationError } from '../../shared/github/image-attachment' +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubRepoExecution } from '../repositories/github-api-repository' +import { + assertGitHubImage, + GitHubImageValidationError +} from '../../../shared/github/attachments/image-attachment' export async function uploadGitHubImage(args: { fileName: string diff --git a/src/main/github/client-pr-lookup-coalescing.test.ts b/src/main/github/client-pr-lookup-coalescing.test.ts deleted file mode 100644 index bc94f1a73e7..00000000000 --- a/src/main/github/client-pr-lookup-coalescing.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRRefreshOutcome } from '../../shared/github/pull-request-refresh-types' -import type { GitHubRepoContext, LocalGitExecOptions } from './github-repository-identity' -import { getRepoExecutionHostId } from '../../shared/execution-host' -import { hostedReviewInfoFromGitHubPRInfo } from '../../shared/source-control/hosted-review/hosted-review-github' -import { makePR } from './pr-refresh-coordinator-test-harness' -import { - __resetHostedReviewBranchCacheForTests, - invalidateHostedReviewBranchCache, - withHostedReviewBranchCache -} from '../source-control/hosted-review-branch-cache' - -const mocks = vi.hoisted(() => ({ resolve: vi.fn(), acquire: vi.fn(), release: vi.fn() })) -vi.mock('./gh-utils', () => ({ - acquire: mocks.acquire, - release: mocks.release, - githubRepoContext: ( - repoPath: string, - connectionId: string | null, - options: LocalGitExecOptions - ) => ({ - repoPath, - connectionId, - ...options - }), - ghRepoExecOptions: (context: GitHubRepoContext) => context -})) -vi.mock('./client/lookup/branch-lookup-resolution', () => ({ - resolvePRForBranchOutcome: mocks.resolve -})) -vi.mock('./client', async () => ({ - getPRForBranchOutcome: (await import('./client/lookup/pr-for-branch-outcome')) - .getPRForBranchOutcome, - getRepoSlug: vi.fn(async () => ({ owner: 'acme', repo: 'orca' })), - getGitHubPRLookupRateLimitBlock: vi.fn(async () => null), - createGitHubPullRequest: vi.fn() -})) -vi.mock('../gitlab/merge-requests/merge-request-lookup', () => ({ - getProjectSlug: vi.fn(async () => null) -})) -import { getPRForBranchOutcome } from './client/lookup/pr-for-branch-outcome' - -const outcome: PRRefreshOutcome = { kind: 'no-pr', fetchedAt: 1 } -beforeEach(() => { - vi.clearAllMocks() - mocks.resolve.mockReset() - __resetHostedReviewBranchCacheForTests() - mocks.acquire.mockResolvedValue(undefined) -}) -afterEach(() => { - vi.unstubAllEnvs() - vi.useRealTimers() -}) - -describe('PR lookup coalescing', () => { - it('cleans up errors so later reads can recover', async () => { - mocks.resolve.mockRejectedValueOnce(new Error('network down')).mockResolvedValue(outcome) - const results = await Promise.all([ - getPRForBranchOutcome('/repo', 'topic'), - getPRForBranchOutcome('/repo', 'topic') - ]) - expect(results.every((result) => result.kind === 'upstream-error')).toBe(true) - expect(mocks.resolve).toHaveBeenCalledTimes(1) - await expect(getPRForBranchOutcome('/repo', 'topic')).resolves.toEqual(outcome) - expect(mocks.resolve).toHaveBeenCalledTimes(2) - }) - - it.each([ - { label: 'native', connectionId: null, options: {} }, - { label: 'WSL', connectionId: null, options: { localGitExecOptions: { wslDistro: 'Ubuntu' } } }, - { label: 'SSH', connectionId: 'host / encoded', options: {} } - ])( - 'does not adopt a pre-creation no-PR read after $label invalidation', - async ({ connectionId, options }) => { - const created: PRRefreshOutcome = { kind: 'found', pr: makePR({ number: 13 }), fetchedAt: 2 } - const createdReview = hostedReviewInfoFromGitHubPRInfo(created.pr) - let finish: (value: PRRefreshOutcome) => void = () => {} - mocks.resolve - .mockImplementationOnce( - () => - new Promise((resolve) => { - finish = resolve - }) - ) - .mockResolvedValue(created) - const executionHostId = getRepoExecutionHostId({ connectionId }) - const identity = { repoPath: '/repo', executionHostId, branch: 'topic', ...options } - const lookup = async () => { - const result = await getPRForBranchOutcome( - '/repo', - 'topic', - null, - connectionId, - null, - options - ) - if (result.kind === 'upstream-error') { - throw new Error(result.message) - } - return result.kind === 'found' ? hostedReviewInfoFromGitHubPRInfo(result.pr) : null - } - const beforeCreation = withHostedReviewBranchCache(identity, { headOid: null }, lookup) - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(1)) - invalidateHostedReviewBranchCache('/repo', executionHostId) - const afterCreation = withHostedReviewBranchCache(identity, { headOid: null }, lookup) - try { - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(2)) - await expect(afterCreation).resolves.toEqual(createdReview) - } finally { - finish(outcome) - await Promise.all([beforeCreation, afterCreation]) - } - await expect( - withHostedReviewBranchCache(identity, { headOid: null }, lookup) - ).resolves.toEqual(createdReview) - expect(mocks.resolve).toHaveBeenCalledTimes(2) - } - ) -}) - -it.each([true, false])( - 'explicit host intent replaces the probe without losing its successor (old first: %s)', - async (oldFirst) => { - vi.useFakeTimers({ toFake: ['Date'] }) - vi.setSystemTime(1_000) - const old = Promise.withResolvers() - const fresh = Promise.withResolvers() - const merged: PRRefreshOutcome = { - kind: 'found', - pr: makePR({ state: 'merged' }), - fetchedAt: 2 - } - mocks.resolve.mockReturnValueOnce(old.promise).mockReturnValueOnce(fresh.promise) - const before = getPRForBranchOutcome('/repo', 'click', null, null, null) - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(1)) - const intent = { userRefresh: true } - const click = getPRForBranchOutcome('/repo', 'click', null, null, null, intent) - try { - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(2)) - if (oldFirst) { - old.resolve(outcome) - await before - } - const ordinary = getPRForBranchOutcome('/repo', 'click', null, null, null) - fresh.resolve(merged) - expect(await click).toEqual(merged) - expect(await ordinary).toEqual(merged) - expect(mocks.resolve).toHaveBeenCalledTimes(2) - } finally { - old.resolve(outcome) - fresh.resolve(merged) - await Promise.all([before, click]) - } - } -) - -it('an explicit hosted GitHub read reaches a new lower probe through the real forge adapter', async () => { - const { readHostedReviewForBranch } = await import('../source-control/hosted-review') - const old = Promise.withResolvers() - const merged: PRRefreshOutcome = { - kind: 'found', - pr: makePR({ state: 'merged', headSha: 'new-head' }), - fetchedAt: 2 - } - mocks.resolve.mockReturnValueOnce(old.promise).mockResolvedValueOnce(merged) - const before = getPRForBranchOutcome('/repo', 'adapter', null, null, null) - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(1)) - const click = readHostedReviewForBranch({ - repoPath: '/repo', - executionHostId: 'local', - branch: 'adapter', - userRefresh: true - }) - try { - await vi.waitFor(() => expect(mocks.resolve).toHaveBeenCalledTimes(2)) - expect((await click).review).toMatchObject({ state: 'merged' }) - } finally { - old.resolve(outcome) - await Promise.all([before, click]) - } -}) diff --git a/src/main/github/client-rate-limit-block.test.ts b/src/main/github/client-rate-limit-block.test.ts deleted file mode 100644 index 53c3d30b313..00000000000 --- a/src/main/github/client-rate-limit-block.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' - -const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') - return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } -}) - -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) - -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal()), - gitExecFileAsync: clientMocks.gitExecFileAsyncMock -})) - -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ - ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock -})) -vi.mock('./local-git-config-signature', () => - moduleMocks.localGitConfigSignatureModuleMock(clientMocks) -) -vi.mock('./github-enterprise-repository', async (importOriginal) => - moduleMocks.githubEnterpriseRepositoryModuleMock( - await importOriginal() - ) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => - moduleMocks.githubApiRepositoryModuleMock( - clientMocks, - await importOriginal() - ) -) - -import { getGitHubPRLookupRateLimitBlock } from './client/lookup/pr-lookup-rate-limit' -import { _resetOwnerRepoCache } from './gh-utils' -import { resetOriginRepositoryCache } from './client-test-harness' -import type { RateLimitGuardResult } from './client-test-mocks' - -const { - execFileAsyncMock, - ghExecFileAsyncMock, - getOwnerRepoMock, - getRemoteUrlForRepoMock, - gitExecFileAsyncMock, - getRateLimitMock, - rateLimitGuardMock, - noteRateLimitSpendMock -} = clientMocks - -describe('getGitHubPRLookupRateLimitBlock', () => { - beforeEach(() => { - resetOriginRepositoryCache() - execFileAsyncMock.mockReset() - ghExecFileAsyncMock.mockReset() - getOwnerRepoMock.mockReset() - getOwnerRepoMock.mockResolvedValue({ owner: 'acme', repo: 'widgets' }) - getRemoteUrlForRepoMock.mockReset() - gitExecFileAsyncMock.mockReset() - getRateLimitMock.mockReset() - getRateLimitMock.mockResolvedValue(undefined) - rateLimitGuardMock.mockReset() - rateLimitGuardMock.mockReturnValue({ blocked: false }) - noteRateLimitSpendMock.mockReset() - _resetOwnerRepoCache() - }) - - it('reports a block when either lookup bucket is exhausted', async () => { - rateLimitGuardMock.mockImplementation(((bucket: string) => - bucket === 'graphql' - ? { blocked: true, remaining: 4, limit: 5000, resetAt: 1_800_000_000 } - : { blocked: false }) as () => RateLimitGuardResult) - - await expect(getGitHubPRLookupRateLimitBlock('/repo-root')).resolves.toEqual({ - resetAt: 1_800_000_000 - }) - }) - - it('reports the latest reset when both lookup buckets are exhausted', async () => { - rateLimitGuardMock.mockImplementation(((bucket: string) => ({ - blocked: true, - remaining: 4, - limit: 5000, - // Why: core resets first, so returning it would retry into graphql's block. - resetAt: bucket === 'core' ? 1_800_000_000 : 1_800_003_600 - })) as () => RateLimitGuardResult) - - await expect(getGitHubPRLookupRateLimitBlock('/repo-root')).resolves.toEqual({ - resetAt: 1_800_003_600 - }) - }) - - it('fails open when the exempt rate-limit probe itself fails', async () => { - getRateLimitMock.mockRejectedValue(new Error('probe offline')) - - await expect(getGitHubPRLookupRateLimitBlock('/repo-root')).resolves.toBeNull() - }) -}) diff --git a/src/main/github/client-stack-merge-guard.test.ts b/src/main/github/client-stack-merge-guard.test.ts deleted file mode 100644 index f21503882c4..00000000000 --- a/src/main/github/client-stack-merge-guard.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' - -const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') - return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } -}) - -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) - -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal()), - gitExecFileAsync: clientMocks.gitExecFileAsyncMock -})) - -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ - ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock -})) -vi.mock('./local-git-config-signature', () => - moduleMocks.localGitConfigSignatureModuleMock(clientMocks) -) -vi.mock('./github-enterprise-repository', async (importOriginal) => - moduleMocks.githubEnterpriseRepositoryModuleMock( - await importOriginal() - ) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => - moduleMocks.githubApiRepositoryModuleMock( - clientMocks, - await importOriginal() - ) -) -import { mergePR } from './client/merge/merge-pr' -import { - installIndependentCheckFixtures, - resetGraphQLRateLimitGuardMocks -} from './client-test-harness' - -const metadataRequests = vi.fn() -const unexpectedChecks: string[][] = [] -afterEach(() => expect(unexpectedChecks).toEqual([])) - -describe('GitHub GraphQL rate-limit guard', () => { - beforeEach(() => { - resetGraphQLRateLimitGuardMocks(clientMocks) - installIndependentCheckFixtures(clientMocks, metadataRequests, unexpectedChecks) - }) - - afterEach(() => vi.restoreAllMocks()) - - const validStackHeadSha = 'a'.repeat(40) - const validStackBaseSha = 'b'.repeat(40) - - it('never falls back to legacy merge after an async stack merge transport failure', async () => { - metadataRequests - .mockResolvedValueOnce({ - stdout: JSON.stringify({ - number: 202, - state: 'open', - head: { ref: 'stack/api', sha: validStackHeadSha }, - base: { ref: 'stack/models', sha: 'models-sha' }, - stack: { - number: 51, - position: 2, - size: 2, - base: { ref: 'main', sha: validStackBaseSha } - } - }) - }) - .mockResolvedValueOnce({ - stdout: JSON.stringify({ data: { repository: { mergeQueue: null } } }) - }) - .mockRejectedValueOnce(new Error('socket closed after request submission')) - - await expect( - mergePR('/repo-root', 202, 'squash', undefined, { - owner: 'stablyai', - repo: 'orca', - host: 'github.com' - }) - ).resolves.toEqual({ ok: false, error: 'socket closed after request submission' }) - expect( - metadataRequests.mock.calls.some(([args]) => args[0] === 'pr' && args[1] === 'merge') - ).toBe(false) - }) -}) diff --git a/src/main/github/client.ts b/src/main/github/client.ts deleted file mode 100644 index b7a1d5bc1da..00000000000 --- a/src/main/github/client.ts +++ /dev/null @@ -1,49 +0,0 @@ -export { - _getMergeQueueCacheSizeForTests, - _resetMergeQueueCacheForTests -} from './client/detect/repository-merge-metadata-cache' -export { _resetPRStackSummaryCacheForTests } from './client/lookup/pr-stack-summary-cache' -export { - _getTrackedUpstreamBranchCacheSizesForTests, - __resetTrackedUpstreamBranchCacheForTests -} from './client/lookup/tracked-upstream-cache' -export { addPRReviewComment, addPRReviewCommentReply } from './client/create/add-pr-review-comment' -export { __resetOrcaStarCheckForTests, checkOrcaStarred, starOrca } from './client/fetch/orca-star' -export { countWorkItems } from './client/list/count-work-items' -export { createGitHubPullRequest } from './client/create/create-github-pull-request' -export { getAuthenticatedViewer } from './client/fetch/authenticated-viewer' -export { getGitHubPRLookupRateLimitBlock } from './client/lookup/pr-lookup-rate-limit' -export { getPRCheckDetails } from './client/check/get-pr-check-details' -export { getPRChecks } from './client/check/get-pr-checks' -export { getPRComments } from './client/fetch/get-pr-comments' -export { getPRForBranch } from './client/lookup/get-pr-for-branch' -export { getPRForBranchOutcome } from './client/lookup/pr-for-branch-outcome' -export { - getPullRequestPushTarget, - type PullRequestPushTarget -} from './client/lookup/pull-request-push-target' -export { getRepoSlug, getRepoUpstream } from './client/fetch/repo-slug-upstream' -export { - getWorkItem, - getWorkItemByOwnerRepo, - getWorkItemWithRepository -} from './client/fetch/get-work-item' -export { listWorkItems } from './client/list/list-work-items' -export { mergePR } from './client/merge/merge-pr' -export { removePRReviewers, requestPRReviewers } from './client/update/pr-reviewers' -export { rerunPRChecks } from './client/check/rerun-pr-checks' -export { resolveReviewThread } from './client/update/resolve-review-thread' -export { setPRAutoMerge } from './client/merge/pr-auto-merge' -export { setPRCommentReaction } from './client/update/pr-comment-reaction' -export { setPRFileViewed } from './client/update/pr-file-viewed' -export { updatePRDetails, updatePRTitle } from './client/update/pr-details' -export { markPRDraft, markPRReadyForReview } from './client/update/pr-ready' -export { updatePRState } from './client/update/pr-state' -export type { GitHubPRBranchLookupOptions } from './client/lookup/pull-request-lookup-data' -export type { MainWorkItem } from './client/map/work-item-field-coercion' -export { _resetOwnerRepoCache } from './gh-utils' -export { getIssue, listIssues } from './issues' -export { createIssue } from './issue-create' -export { updateIssue } from './issue-update' -export { addIssueComment } from './issue-comment' -export { listLabels, listAssignableUsers } from './issue-field-options' diff --git a/src/main/github/client/check/check-detail-field-mapping.ts b/src/main/github/client/check/check-detail-field-mapping.ts index a5ac7b7d5d5..cab277c6f8a 100644 --- a/src/main/github/client/check/check-detail-field-mapping.ts +++ b/src/main/github/client/check/check-detail-field-mapping.ts @@ -1,4 +1,4 @@ -import type { PRCheckRunDetails } from '../../../../shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../shared/github/checks/check-types' export function nullableString(value: unknown): string | null { return typeof value === 'string' && value.length > 0 ? value : null } diff --git a/src/main/github/client/check/check-details-admission-lifetime.test.ts b/src/main/github/client/check/check-details-admission-lifetime.test.ts index dd648e7e407..3a1750d788e 100644 --- a/src/main/github/client/check/check-details-admission-lifetime.test.ts +++ b/src/main/github/client/check/check-details-admission-lifetime.test.ts @@ -17,11 +17,13 @@ vi.mock('@orca/process-host/process-tree-termination', () => ({ signalProcessTree: signalMock, forceTerminateProcessTree: forceMock })) -vi.mock('../../github-api-repository', () => ({ resolveGitHubRepoExecution: resolverMock })) +vi.mock('../../repositories/github-api-repository', () => ({ + resolveGitHubRepoExecution: resolverMock +})) import { getPRCheckDetails } from './get-pr-check-details' import { prCheckLogTailCache } from './check-job-log-tails' -import { clearGhCapabilityStateForTests } from '../../gh-capability-state' -import { clearGhAccountTokenCacheForTests } from '../../gh-account-token' +import { clearGhCapabilityStateForTests } from '../../accounts/gh-capability-state' +import { clearGhAccountTokenCacheForTests } from '../../accounts/gh-account-token' const repo = { owner: 'acme', repo: 'widgets' } const children: ChildProcess[] = [] @@ -143,28 +145,6 @@ describe('check details existing admission ownership', () => { expect(logChildren).toHaveLength(5) }) - it('prevents the jobs stage after a swallowed annotation PID error until close', async () => { - const controller = new AbortController() - const result = getPRCheckDetails( - '/folder', - { checkRunId: 1, workflowRunId: 2 }, - undefined, - {}, - controller.signal - ) - const rejected = expect(result).rejects.toMatchObject({ name: 'AbortError' }) - await vi.advanceTimersByTimeAsync(0) - completeFakeSpawn(children[0], { stdout: '{"name":"loaded"}' }) - await vi.advanceTimersByTimeAsync(0) - children[1].emit('error', new Error('private-output')) - await vi.advanceTimersByTimeAsync(0) - expect(children).toHaveLength(2) - controller.abort() - await rejected - expect(children).toHaveLength(2) - expect(JSON.stringify(vi.mocked(console.warn).mock.calls)).not.toContain('private-output') - }) - it('retains all four creator permits through joined help and later token continuations after departure', async () => { const options: GhExecOptions = { cwd: '/folder', diff --git a/src/main/github/client/check/check-details-admission-snapshot.test.ts b/src/main/github/client/check/check-details-admission-snapshot.test.ts index b1975096fba..36d42b6a130 100644 --- a/src/main/github/client/check/check-details-admission-snapshot.test.ts +++ b/src/main/github/client/check/check-details-admission-snapshot.test.ts @@ -6,8 +6,8 @@ import { createFakeSpawnedChild } from '../../../../shared/__fixtures__/fake-spawned-child' import type { GhExecOptions } from '../../../git/command-runner/hosted-cli/gh-exec-file' -import { clearGhAccountTokenCacheForTests } from '../../gh-account-token' -import { clearGhCapabilityStateForTests } from '../../gh-capability-state' +import { clearGhAccountTokenCacheForTests } from '../../accounts/gh-account-token' +import { clearGhCapabilityStateForTests } from '../../accounts/gh-capability-state' import { getPRCheckDetails } from './get-pr-check-details' import { prCheckLogTailCache } from './check-job-log-tails' @@ -16,7 +16,9 @@ const { spawnMock, resolverMock } = vi.hoisted(() => ({ resolverMock: vi.fn() })) vi.mock('node:child_process', async (original) => ({ ...(await original()), spawn: spawnMock })) -vi.mock('../../github-api-repository', () => ({ resolveGitHubRepoExecution: resolverMock })) +vi.mock('../../repositories/github-api-repository', () => ({ + resolveGitHubRepoExecution: resolverMock +})) vi.mock('@orca/process-host', async (original) => { const actual = await original() return { diff --git a/src/main/github/client/check/check-details-capture-diagnostics.test.ts b/src/main/github/client/check/check-details-capture-diagnostics.test.ts index b794dd37fce..58400784e1c 100644 --- a/src/main/github/client/check/check-details-capture-diagnostics.test.ts +++ b/src/main/github/client/check/check-details-capture-diagnostics.test.ts @@ -10,7 +10,8 @@ import * as capture from '../../../git/command-runner/process-capture/exec-file- import { RpcDispatcher } from '../../../runtime/rpc/dispatcher' import { GITHUB_PULL_REQUEST_METHODS } from '../../../runtime/rpc/methods/source-control/github/github-pull-request-methods' import type { OrcaRuntimeService } from '../../../runtime/orca-runtime' -import * as ghUtils from '../../gh-utils' +import * as ghUtilsOwner1 from '../../execution/github-operation-slots' +import * as ghUtilsOwner2 from '../../../git/command-runner/hosted-cli/gh-exec-file' import { getPRCheckDetails } from './get-pr-check-details' import { prCheckLogTailCache } from './check-job-log-tails' @@ -22,7 +23,9 @@ const { spawnMock, resolverMock, signalMock, forceMock, captureCap } = vi.hoiste captureCap: { bytes: 4096, metadata: false } })) vi.mock('node:child_process', async (original) => ({ ...(await original()), spawn: spawnMock })) -vi.mock('../../github-api-repository', () => ({ resolveGitHubRepoExecution: resolverMock })) +vi.mock('../../repositories/github-api-repository', () => ({ + resolveGitHubRepoExecution: resolverMock +})) vi.mock('@orca/process-host/process-tree-termination', () => ({ signalProcessTree: signalMock, forceTerminateProcessTree: forceMock @@ -88,27 +91,27 @@ function serializeDiagnostics(value: unknown): string { ) } async function verifyAdmissionIsRestored(): Promise { - await Promise.all(Array.from({ length: 4 }, () => ghUtils.acquire())) + await Promise.all(Array.from({ length: 4 }, () => ghUtilsOwner1.acquire())) let fifthStarted = false - const fifth = ghUtils.acquire().then(() => { + const fifth = ghUtilsOwner1.acquire().then(() => { fifthStarted = true }) await vi.advanceTimersByTimeAsync(0) expect(fifthStarted).toBe(false) - ghUtils.release() + ghUtilsOwner1.release() await fifth for (let i = 0; i < 4; i++) { - ghUtils.release() + ghUtilsOwner1.release() } } async function verifyAdmissionIsRetained(): Promise { - await Promise.all(Array.from({ length: 3 }, () => ghUtils.acquire())) + await Promise.all(Array.from({ length: 3 }, () => ghUtilsOwner1.acquire())) const controller = new AbortController() let started = false - const waiting = ghUtils.acquire(controller.signal).then( + const waiting = ghUtilsOwner1.acquire(controller.signal).then( () => { started = true - ghUtils.release() + ghUtilsOwner1.release() }, () => {} ) @@ -117,7 +120,7 @@ async function verifyAdmissionIsRetained(): Promise { controller.abort() await waiting for (let i = 0; i < 3; i++) { - ghUtils.release() + ghUtilsOwner1.release() } } @@ -155,7 +158,7 @@ describe('real capture and gh diagnostics through check details RPC', () => { 'keeps %s raw capture fields internal and restores actual admission', async (mode) => { const captureSpy = vi.spyOn(capture, 'execFileCaptureToTermination') - const ghSpy = vi.spyOn(ghUtils, 'ghExecFileAsync') + const ghSpy = vi.spyOn(ghUtilsOwner2, 'ghExecFileAsync') if (mode === 'runner-timeout') { resolverMock.mockResolvedValue({ ownerRepo: { owner: 'acme', repo: 'widgets' }, diff --git a/src/main/github/client/check/check-job-log-download.test.ts b/src/main/github/client/check/check-job-log-download.test.ts index 8edbe29128f..d9dcf20ad3e 100644 --- a/src/main/github/client/check/check-job-log-download.test.ts +++ b/src/main/github/client/check/check-job-log-download.test.ts @@ -1,9 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import fixture from './__fixtures__/gh-api-unknown-escape-flag.json' + import { startCheckJobLogDownload } from './check-job-log-download' import type { GhCaptureLifetimeHooks } from '../../../git/command-runner/hosted-cli/gh-capture-lifetime' const { ghMock } = vi.hoisted(() => ({ ghMock: vi.fn() })) -vi.mock('../../gh-utils', () => ({ ghExecFileAsync: ghMock })) +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghMock +})) const diagnostic = 'unknown flag: --allow-escape-sequences' const repo = { owner: 'acme', repo: 'widgets' } function download(controller = new AbortController(), deadlineAt = Date.now() + 25_000) { @@ -36,92 +38,6 @@ describe('stateless captured diagnostic negotiation', () => { vi.useRealTimers() vi.restoreAllMocks() }) - it.each([ - fixture.stderr, - Buffer.from(fixture.stderr), - diagnostic, - `${diagnostic}\n`, - fixture.stderr.replaceAll('\n', '\r\n'), - fixture.stderr.replaceAll('\n', '\r'), - `USAGE changed\n${diagnostic}\nflags reordered`, - `help\n${diagnostic}`, - `${'changed usage\n'.repeat(500)}${diagnostic}`, - `${diagnostic}\n${diagnostic}`, - `HTTP 403\nDEBUG record\necho other text\n${diagnostic}\nauthentication failed`, - `unknown flag: --other\n${diagnostic}\nindependent failure` - ])('allows a complete diagnostic independently of surrounding output %#', async (stderr) => { - ghMock - .mockRejectedValueOnce(rejected({ stderr, stdout: 'nonempty output is irrelevant' })) - .mockResolvedValueOnce({ stdout: 'retrieved', stderr: '' }) - await expect(download().result).resolves.toBe('retrieved') - expect(ghMock).toHaveBeenCalledTimes(2) - expect(ghMock.mock.calls[1][0]).toEqual(['api', 'repos/acme/widgets/actions/jobs/77/logs']) - expect(ghMock.mock.calls[1][1]).toEqual(ghMock.mock.calls[0][1]) - }) - it.each([ - `prefix ${diagnostic}`, - `${diagnostic} suffix`, - ` ${diagnostic}`, - `${diagnostic} `, - diagnostic.toUpperCase(), - `\x1b[31m${diagnostic}`, - `${diagnostic}\x00`, - 'unknown flag: --other', - `${diagnostic}-suffix`, - `Error: ${diagnostic}`, - `$ printf '${diagnostic}'`, - `echo ${diagnostic}`, - `HTTP 403: ${diagnostic}`, - `DEBUG ${diagnostic}`, - JSON.stringify({ error: diagnostic }), - `"${diagnostic}"`, - 'authentication failed', - 'rate limit exceeded', - 'network unavailable' - ])('rejects framing or different text on the candidate line %#', async (stderr) => { - ghMock.mockRejectedValue(rejected({ stderr })) - await expect(download().result).rejects.toBeDefined() - expect(ghMock).toHaveBeenCalledOnce() - }) - it.each([ - { code: 0 }, - { code: undefined }, - { code: null }, - { code: '1' }, - { code: Number.NaN }, - { code: Infinity }, - { code: -Infinity }, - { stderr: undefined }, - { stderr: {} }, - { stderr: null }, - { stderr: '', stdout: diagnostic, message: diagnostic, cause: diagnostic }, - { outputTruncated: true, killed: false, signal: null }, - { code: 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' }, - { code: 'ENOBUFS' }, - { name: 'AbortError' }, - { code: 'ABORT_ERR' }, - { code: 'ERR_ABORTED' }, - { code: 'ETIMEDOUT' }, - { timedOut: true }, - { killed: true }, - { signal: 'SIGTERM' }, - { signal: false } - ])('honors typed exclusions before exact stderr %#', async (metadata) => { - ghMock.mockRejectedValue(rejected(metadata)) - await expect(download().result).rejects.toBeDefined() - expect(ghMock).toHaveBeenCalledOnce() - }) - it('does not read inherited stderr or a getter as capture evidence', async () => { - const inherited = Object.assign(Object.create({ stderr: diagnostic }), { code: 1 }) - ghMock.mockRejectedValueOnce(inherited) - await expect(download().result).rejects.toBeDefined() - const getter = vi.fn(() => diagnostic) - const error = Object.defineProperty({ code: 1 }, 'stderr', { get: getter }) - ghMock.mockRejectedValueOnce(error) - await expect(download().result).rejects.toBeDefined() - expect(getter).not.toHaveBeenCalled() - expect(ghMock).toHaveBeenCalledTimes(2) - }) it('waits positive first termination and starts nothing after cancellation', async () => { let close = (): void => {} @@ -143,19 +59,7 @@ describe('stateless captured diagnostic negotiation', () => { await call.terminated expect(ghMock).toHaveBeenCalledOnce() }) - it('does not reset the original deadline for fallback and starts the next job flagged', async () => { - ghMock - .mockImplementationOnce(async () => { - vi.setSystemTime(Date.now() + 4_000) - throw rejected({}) - }) - .mockResolvedValueOnce({ stdout: 'ok', stderr: '' }) - await expect(download().result).resolves.toBe('ok') - expect(ghMock.mock.calls[1][1].timeout).toBe(21_000) - ghMock.mockResolvedValueOnce({ stdout: 'next', stderr: '' }) - await expect(download().result).resolves.toBe('next') - expect(ghMock.mock.calls[2][0]).toContain('--allow-escape-sequences') - }) + it('ends at the second failure and rejects expiry or pre-abort without a child', async () => { ghMock.mockRejectedValue(rejected({})) await expect(download().result).rejects.toBeDefined() diff --git a/src/main/github/client/check/check-job-log-download.ts b/src/main/github/client/check/check-job-log-download.ts index b8d3e662b82..33587f83bc2 100644 --- a/src/main/github/client/check/check-job-log-download.ts +++ b/src/main/github/client/check/check-job-log-download.ts @@ -1,8 +1,11 @@ -import { ghExecFileAsync } from '../../gh-utils' -import type { GitHubApiRepository } from '../../github-api-repository' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from '../github-exec-scope' -import { createGhLogicalLifetime } from '../../../git/command-runner/hosted-cli/gh-capture-lifetime' -import type { GhCaptureLifetimeHooks } from '../../../git/command-runner/hosted-cli/gh-capture-lifetime' +import { + createGhLogicalLifetime, + type GhCaptureLifetimeHooks +} from '../../../git/command-runner/hosted-cli/gh-capture-lifetime' + import { runCheckDetailsCall } from './check-details-admission-lifetime' import { createCheckDetailsAbortError, waitForCheckDetailsResolution } from './check-details-abort' diff --git a/src/main/github/client/check/check-job-log-execution.ts b/src/main/github/client/check/check-job-log-execution.ts index 1497dc4b705..b6f72a0be0d 100644 --- a/src/main/github/client/check/check-job-log-execution.ts +++ b/src/main/github/client/check/check-job-log-execution.ts @@ -1,7 +1,7 @@ -import { normalizeGhAccountBinding } from '../../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../../shared/github/accounts/account-binding' import { parseWslPath } from '../../../wsl/filesystem/unc-path-access' -import { githubReadExecutionScope } from '../../github-read-execution-scope' -import type { GitHubApiRepository } from '../../github-api-repository' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from '../github-exec-scope' function reusableHost(host: string): 'cloud' | 'enterprise' | null { diff --git a/src/main/github/client/check/check-job-log-subscribers.test.ts b/src/main/github/client/check/check-job-log-subscribers.test.ts index 97b4113a3d2..eeaa43b1b1d 100644 --- a/src/main/github/client/check/check-job-log-subscribers.test.ts +++ b/src/main/github/client/check/check-job-log-subscribers.test.ts @@ -18,7 +18,9 @@ vi.mock('@orca/process-host/process-tree-termination', () => ({ signalProcessTree: signalMock, forceTerminateProcessTree: forceMock })) -vi.mock('../../github-api-repository', () => ({ resolveGitHubRepoExecution: resolverMock })) +vi.mock('../../repositories/github-api-repository', () => ({ + resolveGitHubRepoExecution: resolverMock +})) const children: ChildProcess[] = [] function spawn(): ChildProcess { const child = createFakeSpawnedChild(children.length + 100) diff --git a/src/main/github/client/check/check-job-log-tails.test.ts b/src/main/github/client/check/check-job-log-tails.test.ts deleted file mode 100644 index 263f705e4ad..00000000000 --- a/src/main/github/client/check/check-job-log-tails.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { ChildProcess, SpawnOptions } from 'node:child_process' -import { - completeFakeSpawn, - createFakeSpawnedChild -} from '../../../../shared/__fixtures__/fake-spawned-child' -import { attachLogFixture, failedLogJob } from './check-job-log-test-fixture' -import { prCheckLogTailCache } from './check-job-log-tails' -import { clearGhCapabilityStateForTests } from '../../gh-capability-state' -import { clearGhAccountTokenCacheForTests } from '../../gh-account-token' -const { spawnMock } = vi.hoisted(() => ({ spawnMock: vi.fn() })) -vi.mock('node:child_process', async (original) => ({ ...(await original()), spawn: spawnMock })) -const children: ChildProcess[] = [] -let raw = 'Error: retained diagnostic' -let active = 0 -let peak = 0 -function automaticSpawn(_program: string, argv: string[], _options: SpawnOptions): ChildProcess { - const child = createFakeSpawnedChild() - children.push(child) - active += 1 - peak = Math.max(peak, active) - const user = argv[argv.indexOf('--user') + 1] - const stdout = argv.includes('--help') ? '--user' : argv[0] === 'auth' ? `bound-${user}` : raw - queueMicrotask(() => { - active -= 1 - completeFakeSpawn(child, { stdout }) - }) - return child -} -describe('completed scoped excerpts through actual capture and runner', () => { - beforeEach(() => { - vi.useFakeTimers() - children.length = 0 - active = 0 - peak = 0 - raw = 'Error: retained diagnostic' - spawnMock.mockReset().mockImplementation(automaticSpawn) - prCheckLogTailCache.clear() - clearGhCapabilityStateForTests() - clearGhAccountTokenCacheForTests() - vi.spyOn(console, 'warn').mockImplementation(() => {}) - }) - afterEach(async () => { - for (const child of children) { - child.emit('close', 0, null) - } - await vi.advanceTimersByTimeAsync(0) - expect(vi.getTimerCount()).toBe(0) - vi.useRealTimers() - vi.restoreAllMocks() - }) - - it('bounds owned encoded cache content with FIFO eviction and five-job payload', async () => { - raw = '🙂'.repeat(6000) - for (let id = 1; id <= 129; id += 1) { - await attachLogFixture([failedLogJob(id)]) - } - expect(prCheckLogTailCache.size).toBe(128) - const tails = [...prCheckLogTailCache.values()].map((entry) => entry.logTail ?? '') - expect(tails.every((tail) => Buffer.byteLength(tail) <= 16 * 1024)).toBe(true) - expect(tails.reduce((bytes, tail) => bytes + Buffer.byteLength(tail), 0)).toBe(128 * 16 * 1024) - const jobs = Array.from({ length: 5 }, (_, i) => failedLogJob(i + 125)) - await attachLogFixture(jobs) - expect(jobs.reduce((bytes, job) => bytes + Buffer.byteLength(job.logTail ?? ''), 0)).toBe( - 80 * 1024 - ) - jobs[0].logTail = 'caller mutation' - await attachLogFixture([failedLogJob(125)]) - expect(spawnMock).toHaveBeenCalledTimes(129) - await attachLogFixture([failedLogJob(1)]) - expect(spawnMock).toHaveBeenCalledTimes(130) - }) -}) diff --git a/src/main/github/client/check/check-job-log-tails.ts b/src/main/github/client/check/check-job-log-tails.ts index 12baac336d1..d3070006caa 100644 --- a/src/main/github/client/check/check-job-log-tails.ts +++ b/src/main/github/client/check/check-job-log-tails.ts @@ -1,7 +1,7 @@ -import type { PRCheckRunDetails } from '../../../../shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../shared/github/checks/check-types' import { sliceCheckLogTail } from '../../../../shared/source-control/checks/logs/check-job-log-tail-slice' -import type { GitHubApiRepository } from '../../github-api-repository' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import type { GhExecOptions } from './../github-exec-scope' import { rethrowCheckDetailsAbort, waitForCheckDetailsResolution } from './check-details-abort' import { assertCheckLogActive, startCheckJobLogDownload } from './check-job-log-download' diff --git a/src/main/github/client/check/check-job-log-test-fixture.ts b/src/main/github/client/check/check-job-log-test-fixture.ts index a1ecbe4d319..50605cb696b 100644 --- a/src/main/github/client/check/check-job-log-test-fixture.ts +++ b/src/main/github/client/check/check-job-log-test-fixture.ts @@ -1,5 +1,5 @@ -import type { PRCheckJob } from '../../../../shared/github/check-types' -import type { GitHubApiRepository } from '../../github-api-repository' +import type { PRCheckJob } from '../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from '../github-exec-scope' import { snapshotCheckJobLogExecution } from './check-job-log-execution' import { attachFailedJobLogTails } from './check-job-log-tails' diff --git a/src/main/github/complete-pr-check-observation.test.ts b/src/main/github/client/check/complete-pr-check-observation.test.ts similarity index 93% rename from src/main/github/complete-pr-check-observation.test.ts rename to src/main/github/client/check/complete-pr-check-observation.test.ts index a0d7c16bc5b..0827a47b591 100644 --- a/src/main/github/complete-pr-check-observation.test.ts +++ b/src/main/github/client/check/complete-pr-check-observation.test.ts @@ -1,17 +1,17 @@ import { beforeEach, expect, it, vi } from 'vitest' const { exec } = vi.hoisted(() => ({ exec: vi.fn() })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (original) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (original) => ({ ...(await original>()), ghExecFileAsync: exec })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ noteRepositoryRateLimitSpend: vi.fn(), repositoryRateLimitGuard: () => ({ blocked: false }), spendsSharedGitHubComQuota: () => false })) -import { readCompletePRCheckObservation } from './client/check/complete-pr-check-observation' -import { summarizePRCheckRollup } from '../../shared/source-control/checks/pr-check-status' +import { readCompletePRCheckObservation } from './complete-pr-check-observation' +import { summarizePRCheckRollup } from '../../../../shared/source-control/checks/pr-check-status' const repository = { owner: 'acme', repo: 'widgets', host: 'github.com' } const sha = 'a'.repeat(40) diff --git a/src/main/github/client/check/complete-pr-check-observation.ts b/src/main/github/client/check/complete-pr-check-observation.ts index c0e5eaab08f..df07831ba88 100644 --- a/src/main/github/client/check/complete-pr-check-observation.ts +++ b/src/main/github/client/check/complete-pr-check-observation.ts @@ -1,4 +1,4 @@ -import type { GitHubApiRepository } from '../../github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from '../github-exec-scope' import { assertRateLimitBudget } from '../lookup/pr-lookup-rate-limit' import { readGraphQLPRCheckRollup } from './pr-check-rollup' diff --git a/src/main/github/client-pr-check-details.test.ts b/src/main/github/client/check/get-pr-check-details.test.ts similarity index 80% rename from src/main/github/client-pr-check-details.test.ts rename to src/main/github/client/check/get-pr-check-details.test.ts index 2ebbbf755b1..91ee402477b 100644 --- a/src/main/github/client-pr-check-details.test.ts +++ b/src/main/github/client/check/get-pr-check-details.test.ts @@ -1,5 +1,6 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GithubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubEnterpriseRepositoryModule from './github-enterprise-repository' const { ghExecFileAsyncMock, getOwnerRepoMock, rateLimitGuardMock } = vi.hoisted(() => ({ ghExecFileAsyncMock: vi.fn(), @@ -7,16 +8,21 @@ const { ghExecFileAsyncMock, getOwnerRepoMock, rateLimitGuardMock } = vi.hoisted rateLimitGuardMock: vi.fn(() => ({ blocked: false })) })) -vi.mock('./gh-utils', () => ({ - execFileAsync: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, +vi.mock('../../execution/github-legacy-command', () => ({ + execFileAsync: vi.fn() +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), githubRepoContext: (repoPath: string, connectionId?: string | null) => ({ repoPath, connectionId: connectionId ?? null }), ghRepoExecOptions: (context: { repoPath: string }) => ({ cwd: context.repoPath }), - getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: vi.fn(), getOwnerRepoForRemote: ( repoPath: string, remoteName: string, @@ -26,23 +32,29 @@ vi.mock('./gh-utils', () => ({ remoteName === 'origin' ? getOwnerRepoMock(repoPath, connectionId, localGitOptions) : Promise.resolve(null), - extractExecError: vi.fn((err: unknown) => ({ stderr: String(err), stdout: '' })), - acquire: vi.fn(), - release: vi.fn(), _resetOwnerRepoCache: vi.fn() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: getOwnerRepoMock +})) + +vi.mock('../../execution/github-operation-slots', () => ({ + acquire: vi.fn(), + release: vi.fn() +})) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: vi.fn() })) -vi.mock('./github-enterprise-repository', async (importOriginal) => ({ +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => ({ ...(await importOriginal()), isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: vi.fn(), repositoryRateLimitGuard: vi.fn(() => ({ blocked: false })), @@ -50,8 +62,8 @@ vi.mock('./rate-limit', () => ({ spendsSharedGitHubComQuota: vi.fn(() => true) })) -import { getPRCheckDetails } from './client/check/get-pr-check-details' -import { _resetOwnerRepoCache } from './gh-utils' +import { getPRCheckDetails } from './get-pr-check-details' +import { _resetOwnerRepoCache } from '../../repositories/github-repository-identity' describe('getPRCheckDetails', () => { beforeEach(() => { diff --git a/src/main/github/client/check/get-pr-check-details.ts b/src/main/github/client/check/get-pr-check-details.ts index 94cd28a040f..bd891ca392f 100644 --- a/src/main/github/client/check/get-pr-check-details.ts +++ b/src/main/github/client/check/get-pr-check-details.ts @@ -1,10 +1,13 @@ -import type { PRCheckRunDetails } from '../../../../shared/github/check-types' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import type { PRCheckRunDetails } from '../../../../shared/github/checks/check-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { GITHUB_CHECK_DETAILS_HOST_TIMEOUT_MS, GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE -} from '../../../../shared/github/check-details-deadline' +} from '../../../../shared/github/checks/check-details-deadline' import type { GhExecOptions } from './../github-exec-scope' import { nullableString, diff --git a/src/main/github/client/check/get-pr-checks.ts b/src/main/github/client/check/get-pr-checks.ts index 71190923c8f..06184e27b1e 100644 --- a/src/main/github/client/check/get-pr-checks.ts +++ b/src/main/github/client/check/get-pr-checks.ts @@ -1,18 +1,22 @@ -import { normalizePRCheckHeadSha } from '../../../../shared/github/pr-check-head' +import { normalizePRCheckHeadSha } from '../../../../shared/github/checks/pr-check-head' import { withGitHubRepoExecution, githubRepoContext, - ghRepoExecOptions -} from '../../github-repository-identity' + ghRepoExecOptions, + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' import { runCoalescedProbe, type CoalescedProbes } from '../../../git/remotes/coalesced-probe' -import { githubReadExecutionScope } from '../../github-read-execution-scope' -import type { PRCheckDetail } from '../../../../shared/github/check-types' -import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-item-ssh-error' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import type { PRCheckDetail } from '../../../../shared/github/checks/check-types' +import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-items/work-item-ssh-error' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' + import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { mapCheckStatus, mapCheckConclusion } from '../../mappers' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { mapCheckStatus, mapCheckConclusion } from '../../api/github-read-response' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import type { GhExecOptions } from './../github-exec-scope' import { assertRateLimitBudget } from './../lookup/pr-lookup-rate-limit' import { PR_CHECKS_ROLLUP_QUERY, type GraphQLPRChecksResponse } from './pr-checks-graphql-query' diff --git a/src/main/github/client/check/pr-check-rollup.ts b/src/main/github/client/check/pr-check-rollup.ts index 1a381e4b1b4..0ed6821d97a 100644 --- a/src/main/github/client/check/pr-check-rollup.ts +++ b/src/main/github/client/check/pr-check-rollup.ts @@ -1,7 +1,8 @@ import { z } from 'zod' -import { ghExecFileAsync } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import type { GhExecOptions } from '../github-exec-scope' import { isPendingApprovalSuiteWithoutRuns } from './pr-checks-response-mapping' diff --git a/src/main/github/client/check/pr-checks-response-mapping.ts b/src/main/github/client/check/pr-checks-response-mapping.ts index 97c0ed3cde5..0b7591343b5 100644 --- a/src/main/github/client/check/pr-checks-response-mapping.ts +++ b/src/main/github/client/check/pr-checks-response-mapping.ts @@ -1,12 +1,13 @@ -import { normalizePRCheckHeadSha } from '../../../../shared/github/pr-check-head' -import type { PRCheckDetail } from '../../../../shared/github/check-types' -import { githubRepositoryWebHost, type GitHubApiRepository } from '../../github-api-repository' +import { normalizePRCheckHeadSha } from '../../../../shared/github/checks/pr-check-head' +import type { PRCheckDetail } from '../../../../shared/github/checks/check-types' +import { githubRepositoryWebHost } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { mapCheckRunRESTStatus, mapCheckRunRESTConclusion, mapCommitStatusRESTStatus, mapCommitStatusRESTConclusion -} from '../../mappers' +} from '../../api/github-read-response' import type { GraphQLPRChecksResponse, GraphQLCheckRunContext, diff --git a/src/main/github/client/check/rerun-pr-checks.ts b/src/main/github/client/check/rerun-pr-checks.ts index c107e1d7ec7..98e7994d200 100644 --- a/src/main/github/client/check/rerun-pr-checks.ts +++ b/src/main/github/client/check/rerun-pr-checks.ts @@ -1,10 +1,16 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' -import type { GitHubRerunPRChecksResult } from '../../../../shared/github/check-types' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' +import { + withGitHubRepoExecution, + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' +import type { GitHubRerunPRChecksResult } from '../../../../shared/github/checks/check-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' + // Keep real error parsing available when tests mock gh-utils. import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' -import { classifyRerunChecksError } from '../../gh-error-classification' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { classifyRerunChecksError } from '../../api/gh-error-classification' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { getPRChecks } from './get-pr-checks' import { parseActionsRunId } from './check-detail-field-mapping' import { codeHostFailure, classifiedFailure } from '../../../../shared/code-host-failure-copy' diff --git a/src/main/github/client/check/rest-pr-check-pages.ts b/src/main/github/client/check/rest-pr-check-pages.ts index a8a9347b748..fa5aac9fdb6 100644 --- a/src/main/github/client/check/rest-pr-check-pages.ts +++ b/src/main/github/client/check/rest-pr-check-pages.ts @@ -1,8 +1,9 @@ import { z } from 'zod' import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' -import { ghExecFileAsync } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import type { GhExecOptions } from '../github-exec-scope' import { assertRateLimitBudget } from '../lookup/pr-lookup-rate-limit' import { isPendingApprovalSuiteWithoutRuns } from './pr-checks-response-mapping' diff --git a/src/main/github/client-test-harness.ts b/src/main/github/client/client-test-harness.ts similarity index 89% rename from src/main/github/client-test-harness.ts rename to src/main/github/client/client-test-harness.ts index 424d0dc7157..01fa7484827 100644 --- a/src/main/github/client-test-harness.ts +++ b/src/main/github/client/client-test-harness.ts @@ -1,15 +1,15 @@ import type { Mock } from 'vitest' -import { completePRCheckGraphQLResponse } from '../../shared/github/complete-pr-check-test-fixture' +import { completePRCheckGraphQLResponse } from '../../../shared/github/checks/complete-pr-check-test-fixture' import type { GitHubClientMocks } from './client-test-mocks' -import { _resetOwnerRepoCache } from './gh-utils' -import { _resetMergeQueueCacheForTests } from './client/detect/repository-merge-metadata-cache' -import { _resetPRStackSummaryCacheForTests } from './client/lookup/pr-stack-summary-cache' -import { __resetTrackedUpstreamBranchCacheForTests } from './client/lookup/tracked-upstream-cache' -import { __resetPRConflictSummaryCachesForTests } from './conflict-summary' -import { resetMergedPRCommitMembershipCacheForTest } from './merged-pr-commit-membership' -import { __resetRepoDefaultBranchCacheForTests } from '../source-control/repo-default-branch' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' -import { _resetGitHubPRStackCacheForTests } from './github-pr-stack' +import { _resetOwnerRepoCache } from '../repositories/github-repository-identity' +import { _resetMergeQueueCacheForTests } from './detect/repository-merge-metadata-cache' +import { _resetPRStackSummaryCacheForTests } from './lookup/pr-stack-summary-cache' +import { __resetTrackedUpstreamBranchCacheForTests } from './lookup/tracked-upstream-cache' +import { __resetPRConflictSummaryCachesForTests } from '../pull-requests/conflicts/conflict-summary' +import { resetMergedPRCommitMembershipCacheForTest } from '../pull-requests/refresh/merged-pr-commit-membership' +import { __resetRepoDefaultBranchCacheForTests } from '../../source-control/repo-default-branch' +import { _resetOriginGitHubApiRepositoryCache } from '../repositories/github-api-repository-remote-probe' +import { _resetGitHubPRStackCacheForTests } from '../pull-requests/stacks/github-pr-stack' export function resetOriginRepositoryCache(): void { _resetOriginGitHubApiRepositoryCache() diff --git a/src/main/github/client-test-mocks.ts b/src/main/github/client/client-test-mocks.ts similarity index 83% rename from src/main/github/client-test-mocks.ts rename to src/main/github/client/client-test-mocks.ts index f04469b3a03..d6e1b1667e8 100644 --- a/src/main/github/client-test-mocks.ts +++ b/src/main/github/client/client-test-mocks.ts @@ -1,9 +1,13 @@ +import type * as GithubApiRepositoryRemoteProbeModule from '../repositories/github-api-repository-remote-probe' import { vi, type Mock } from 'vitest' -import type { ClassifiedError } from '../../shared/classified-error' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' -import type { ghRepoExecOptions, githubRepoContext } from './github-repository-identity' +import type { ClassifiedError } from '../../../shared/classified-error' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' +import type * as GithubApiRepositoryModule from '../repositories/github-api-repository' +import type * as GitHubEnterpriseRepositoryModule from '../repositories/github-enterprise-repository' +import type { + ghRepoExecOptions, + githubRepoContext +} from '../repositories/github-repository-identity' export type RateLimitGuardResult = | { blocked: false } @@ -65,25 +69,17 @@ export function createGitHubClientMocks(): GitHubClientMocks { } } -export type GhUtilsModuleMock = { +export function githubLegacyCommandModuleMock(mocks: GitHubClientMocks): { execFileAsync: Mock - ghExecFileAsync: Mock - getOwnerRepo: Mock - getIssueOwnerRepo: Mock - getOwnerRepoForRemote: Mock - resolvePRRepositoryCandidates: Mock - getRemoteUrlForRepo: Mock - gitExecFileAsync: Mock - ghRepoExecOptions: Mock - githubRepoContext: Mock - classifyGhError: (stderr: string) => ClassifiedError - parseGitHubOwnerRepo: (remoteUrl: string) => GitHubOwnerRepo | null - acquire: Mock - release: Mock - _resetOwnerRepoCache: Mock +} { + return { + execFileAsync: mocks.execFileAsyncMock + } } -export function ghUtilsModuleMock(mocks: GitHubClientMocks): GhUtilsModuleMock { +export function githubHostedCliModuleMock(mocks: GitHubClientMocks): { + ghExecFileAsync: Mock +} { const ghExecFileAsync = vi.fn(async (...args: unknown[]) => { const result = await mocks.ghExecFileAsyncMock(...args) const argv = args[0] @@ -101,16 +97,40 @@ export function ghUtilsModuleMock(mocks: GitHubClientMocks): GhUtilsModuleMock { return result }) return { - execFileAsync: mocks.execFileAsyncMock, - ghExecFileAsync, + ghExecFileAsync + } +} + +export function githubOwnerSelectionModuleMock(mocks: GitHubClientMocks): { + getOwnerRepo: Mock + resolvePRRepositoryCandidates: Mock +} { + return { getOwnerRepo: mocks.getOwnerRepoMock, - getIssueOwnerRepo: mocks.getIssueOwnerRepoMock, + resolvePRRepositoryCandidates: mocks.resolvePRRepositoryCandidatesMock + } +} + +export function githubRepositoryIdentityModuleMock(mocks: GitHubClientMocks): { + getOwnerRepoForRemote: Mock + getRemoteUrlForRepo: Mock + ghRepoExecOptions: Mock + githubRepoContext: Mock + _resetOwnerRepoCache: Mock +} { + return { getOwnerRepoForRemote: mocks.getOwnerRepoForRemoteMock, - resolvePRRepositoryCandidates: mocks.resolvePRRepositoryCandidatesMock, getRemoteUrlForRepo: mocks.getRemoteUrlForRepoMock, - gitExecFileAsync: mocks.gitExecFileAsyncMock, ghRepoExecOptions: mocks.ghRepoExecOptionsMock, githubRepoContext: mocks.githubRepoContextMock, + _resetOwnerRepoCache: vi.fn() + } +} + +export function githubErrorClassificationModuleMock(): { + classifyGhError: (stderr: string) => ClassifiedError +} { + return { classifyGhError: (stderr: string) => { const lower = stderr.toLowerCase() if (lower.includes('not found') || stderr.includes('HTTP 404')) { @@ -123,14 +143,28 @@ export function ghUtilsModuleMock(mocks: GitHubClientMocks): GhUtilsModuleMock { return { type: 'permission_denied', message: stderr } } return { type: 'unknown', message: stderr } - }, + } + } +} + +export function githubRemoteIdentityParsingModuleMock(): { + parseGitHubOwnerRepo: (remoteUrl: string) => GitHubOwnerRepo | null +} { + return { parseGitHubOwnerRepo: (remoteUrl: string) => { const match = remoteUrl.trim().match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) return match ? { owner: match[1], repo: match[2] } : null - }, + } + } +} + +export function githubOperationSlotsModuleMock(mocks: GitHubClientMocks): { + acquire: Mock + release: Mock +} { + return { acquire: mocks.acquireMock, - release: mocks.releaseMock, - _resetOwnerRepoCache: vi.fn() + release: mocks.releaseMock } } @@ -210,6 +244,27 @@ export function githubApiRepositoryModuleMock( connectionId?: string | null, localGitOptions?: unknown ) => mocks.resolvePRRepositoryCandidatesMock(repoPath, connectionId, localGitOptions), + getIssueGitHubApiRepository: async (repoPath: string, connectionId?: string | null) => { + const slug = await mocks.getIssueOwnerRepoMock(repoPath, connectionId) + return slug ? { host: 'github.com', ...slug } : slug + }, + resolveIssueGitHubApiRepositorySource: async ( + repoPath: string, + _preference: unknown, + connectionId?: string | null + ) => { + const slug = await mocks.getIssueOwnerRepoMock(repoPath, connectionId) + return { source: slug ? { host: 'github.com', ...slug } : slug, fellBack: false } + } + } +} + +export function githubApiRepositoryRemoteProbeModuleMock( + mocks: GitHubClientMocks, + actual: typeof GithubApiRepositoryRemoteProbeModule +): typeof GithubApiRepositoryRemoteProbeModule { + return { + ...actual, getGitHubApiRepositoryForRemote: ( repoPath: string, remoteName: string, @@ -230,18 +285,6 @@ export function githubApiRepositoryModuleMock( const slug = fromRemote ?? (await mocks.getOwnerRepoMock(repoPath, connectionId, localGitOptions)) return slug ? { host: 'github.com', ...slug } : slug - }, - getIssueGitHubApiRepository: async (repoPath: string, connectionId?: string | null) => { - const slug = await mocks.getIssueOwnerRepoMock(repoPath, connectionId) - return slug ? { host: 'github.com', ...slug } : slug - }, - resolveIssueGitHubApiRepositorySource: async ( - repoPath: string, - _preference: unknown, - connectionId?: string | null - ) => { - const slug = await mocks.getIssueOwnerRepoMock(repoPath, connectionId) - return { source: slug ? { host: 'github.com', ...slug } : slug, fellBack: false } } } } diff --git a/src/main/github/client/create/add-pr-review-comment.ts b/src/main/github/client/create/add-pr-review-comment.ts index 25d298241ad..e359a3a5b6f 100644 --- a/src/main/github/client/create/add-pr-review-comment.ts +++ b/src/main/github/client/create/add-pr-review-comment.ts @@ -1,16 +1,14 @@ -import { githubBodyMediaHeaders } from '../../body-image-urls' +import { githubBodyMediaHeaders } from '../../attachments/body-image-urls' import type { GitHubCommentResult, GitHubPRReviewCommentInput -} from '../../../../shared/github/comment-types' -import { - ghExecFileAsync, - acquire, - release, - classifyGhError, - type LocalGitExecOptions -} from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +} from '../../../../shared/github/comments/comment-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import { classifyGhError } from '../../api/gh-error-classification' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { mapReviewCommentResponse } from './../map/review-comment-response' import { codeHostFailure, classifiedFailure } from '../../../../shared/code-host-failure-copy' export async function addPRReviewComment( diff --git a/src/main/github/client-create-pr.test.ts b/src/main/github/client/create/create-github-pull-request.test.ts similarity index 67% rename from src/main/github/client-create-pr.test.ts rename to src/main/github/client/create/create-github-pull-request.test.ts index 6bf761036c7..fb232381e88 100644 --- a/src/main/github/client-create-pr.test.ts +++ b/src/main/github/client/create/create-github-pull-request.test.ts @@ -1,3 +1,5 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' import { beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -24,11 +26,20 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ - execFileAsync: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, - getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: vi.fn(), +vi.mock('../../execution/github-legacy-command', () => ({ + execFileAsync: vi.fn() +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: getOwnerRepoMock +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), getOwnerRepoForRemote: getOwnerRepoForRemoteMock, githubRepoContext: vi.fn((repoPath: string, connectionId?: string | null, options = {}) => ({ ...options, @@ -41,27 +52,27 @@ vi.mock('./gh-utils', () => ({ ? {} : { cwd: context.repoPath, ...(context.wslDistro ? { wslDistro: context.wslDistro } : {}) } ), - gitExecFileAsync: vi.fn(), - extractExecError: extractExecErrorMock, - parseGitHubOwnerRepo: vi.fn(), - acquire: acquireMock, - release: releaseMock, _resetOwnerRepoCache: vi.fn() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal()), gitExecFileAsync: vi.fn() })) -vi.mock('./github-enterprise-repository', () => ({ +vi.mock('../../execution/github-operation-slots', () => ({ + acquire: acquireMock, + release: releaseMock +})) + +vi.mock('../../repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock, isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -import { createGitHubPullRequest } from './client/create/create-github-pull-request' +import { createGitHubPullRequest } from './create-github-pull-request' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../../repositories/github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() @@ -85,42 +96,6 @@ describe('createGitHubPullRequest', () => { acquireMock.mockResolvedValue(undefined) }) - it('targets the origin fork (not the upstream parent) on a fork checkout (#7331)', async () => { - // Creation targets the fork holding the unqualified head branch; reads can prefer upstream. - getOwnerRepoForRemoteMock.mockImplementation(async (_repoPath: string, remoteName: string) => - remoteName === 'origin' - ? { owner: 'fsdwen', repo: 'orca' } - : { owner: 'stablyai', repo: 'orca' } - ) - ghExecFileAsyncMock.mockResolvedValueOnce({ - stdout: JSON.stringify({ - number: 5, - url: 'https://github.com/fsdwen/orca/pull/5' - }) - }) - - await expect( - createGitHubPullRequest( - '/repo-root', - { - provider: 'github', - base: 'main', - head: 'my-branch', - title: 'Fork PR' - }, - 'local' - ) - ).resolves.toEqual({ - ok: true, - number: 5, - url: 'https://github.com/fsdwen/orca/pull/5' - }) - - const [args] = ghExecFileAsyncMock.mock.calls[0] - expect(args[args.indexOf('--repo') + 1]).toBe('fsdwen/orca') - expect(args[args.indexOf('--head') + 1]).toBe('my-branch') - }) - it('returns the existing PR when gh reports an already-open pull request', async () => { getOwnerRepoMock.mockResolvedValueOnce({ owner: 'acme', repo: 'widgets' }) ghExecFileAsyncMock diff --git a/src/main/github/client/create/create-github-pull-request.ts b/src/main/github/client/create/create-github-pull-request.ts index e92a6716cfb..33dfae27346 100644 --- a/src/main/github/client/create/create-github-pull-request.ts +++ b/src/main/github/client/create/create-github-pull-request.ts @@ -11,21 +11,22 @@ import { import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' import { - ghExecFileAsync, - acquire, - release, ghRepoExecOptions, - githubRepoContext -} from '../../gh-utils' + githubRepoContext, + withGitHubRepoExecution +} from '../../repositories/github-repository-identity' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions } from '../../../source-control/hosted-review-git-options' -import { getOriginGitHubApiRepository, githubHostExecOptions } from '../../github-api-repository' +import { getOriginGitHubApiRepository } from '../../repositories/github-api-repository-remote-probe' +import { githubHostExecOptions } from '../../repositories/github-repository-host' import { classifyCreatePRError, parseCreatePRPayload } from './create-pr-error-classification' import { findOpenPRByHeadBase, readPullRequestTemplate } from './pull-request-template' -import { withGitHubRepoExecution } from '../../github-repository-identity' + export async function createGitHubPullRequest( repoPath: string, input: CreateHostedReviewInput, diff --git a/src/main/github/client/create/pull-request-template.ts b/src/main/github/client/create/pull-request-template.ts index e1659791c74..1f8f8fe9e5a 100644 --- a/src/main/github/client/create/pull-request-template.ts +++ b/src/main/github/client/create/pull-request-template.ts @@ -1,11 +1,13 @@ import { readFile } from 'node:fs/promises' import { join } from 'node:path' -import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from '../../gh-utils' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { ghRepoExecOptions, githubRepoContext } from '../../repositories/github-repository-identity' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions } from '../../../source-control/hosted-review-git-options' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' export async function findOpenPRByHeadBase(args: { repoPath: string repo: GitHubApiRepository diff --git a/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts b/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts index f856603cc48..d7b3f51102c 100644 --- a/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts +++ b/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts @@ -1,4 +1,4 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './repository-merge-metadata' import type { MainWorkItem } from './../map/work-item-field-coercion' diff --git a/src/main/github/client/detect/repository-merge-metadata-cache.ts b/src/main/github/client/detect/repository-merge-metadata-cache.ts index 2e09fa243e0..e09ddafae5d 100644 --- a/src/main/github/client/detect/repository-merge-metadata-cache.ts +++ b/src/main/github/client/detect/repository-merge-metadata-cache.ts @@ -1,4 +1,4 @@ -import type { GitHubPRMergeMethodSettings } from '../../../../shared/github/pull-request-types' +import type { GitHubPRMergeMethodSettings } from '../../../../shared/github/pull-requests/pull-request-types' import type { CoalescedProbes } from '../../../git/remotes/coalesced-probe' export const MERGE_QUEUE_CACHE_TTL_MS = 10 * 60 * 1000 diff --git a/src/main/github/repository-merge-metadata-reuse.test.ts b/src/main/github/client/detect/repository-merge-metadata-reuse.test.ts similarity index 96% rename from src/main/github/repository-merge-metadata-reuse.test.ts rename to src/main/github/client/detect/repository-merge-metadata-reuse.test.ts index a981357ccd9..a7b8e56d7a8 100644 --- a/src/main/github/repository-merge-metadata-reuse.test.ts +++ b/src/main/github/client/detect/repository-merge-metadata-reuse.test.ts @@ -1,14 +1,16 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { exec, guard, spend } = vi.hoisted(() => ({ exec: vi.fn(), guard: vi.fn(), spend: vi.fn() })) -vi.mock('./gh-utils', () => ({ ghExecFileAsync: exec })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: exec +})) +vi.mock('../../api/rate-limit', () => ({ repositoryRateLimitGuard: guard, noteRepositoryRateLimitSpend: spend })) -import { detectRepositoryMergeMetadata } from './client/detect/repository-merge-metadata' -import { _resetMergeQueueCacheForTests } from './client/detect/repository-merge-metadata-cache' +import { detectRepositoryMergeMetadata } from './repository-merge-metadata' +import { _resetMergeQueueCacheForTests } from './repository-merge-metadata-cache' const repository = { owner: 'acme', repo: 'widgets', host: 'github.com' } const options = { cwd: '/repo' } diff --git a/src/main/github/client/detect/repository-merge-metadata.ts b/src/main/github/client/detect/repository-merge-metadata.ts index bfaad9c165f..44183eeaeda 100644 --- a/src/main/github/client/detect/repository-merge-metadata.ts +++ b/src/main/github/client/detect/repository-merge-metadata.ts @@ -1,11 +1,12 @@ import { posix, win32 } from 'node:path' -import { normalizeGitHubPRMergeMethodSettings } from '../../../../shared/github/pull-request-merge-methods' +import { normalizeGitHubPRMergeMethodSettings } from '../../../../shared/github/pull-requests/merge/pull-request-merge-methods' import { mergeCommandEnvironment } from '../../../../shared/command-environment' -import { ghExecFileAsync } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' -import { githubReadExecutionScope } from '../../github-read-execution-scope' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' import { runCoalescedProbe } from '../../../git/remotes/coalesced-probe' import { parseWslUncPath } from '../../../../shared/wsl-paths' import type { GhExecOptions } from './../github-exec-scope' diff --git a/src/main/github/client/fetch/authenticated-viewer.ts b/src/main/github/client/fetch/authenticated-viewer.ts index 92ebd0877b3..717875b9b0b 100644 --- a/src/main/github/client/fetch/authenticated-viewer.ts +++ b/src/main/github/client/fetch/authenticated-viewer.ts @@ -1,5 +1,6 @@ -import type { GitHubViewer } from '../../../../shared/github/pull-request-types' -import { ghExecFileAsync, acquire, release } from '../../gh-utils' +import type { GitHubViewer } from '../../../../shared/github/pull-requests/pull-request-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' /** Use the bounded runner so failed viewer probes return null without stranding a GitHub admission slot. */ export async function getAuthenticatedViewer(): Promise { await acquire() diff --git a/src/main/github/client/fetch/get-pr-comments.ts b/src/main/github/client/fetch/get-pr-comments.ts index 9984514b988..3dddd10d4ea 100644 --- a/src/main/github/client/fetch/get-pr-comments.ts +++ b/src/main/github/client/fetch/get-pr-comments.ts @@ -1,12 +1,15 @@ -import { GITHUB_BODY_MEDIA_TYPE } from '../../body-image-urls' -import type { PRComment } from '../../../../shared/github/comment-types' +import { GITHUB_BODY_MEDIA_TYPE } from '../../attachments/body-image-urls' +import type { PRComment } from '../../../../shared/github/comments/comment-types' import { runCoalescedProbe, type CoalescedProbes } from '../../../git/remotes/coalesced-probe' import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' -import { githubReadExecutionScope } from '../../github-read-execution-scope' -import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-item-ssh-error' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-items/work-item-ssh-error' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' import { assertRateLimitBudget } from './../lookup/pr-lookup-rate-limit' import { REVIEW_THREADS_QUERY } from './pr-review-threads-query' diff --git a/src/main/github/client-work-item-check-summary.test.ts b/src/main/github/client/fetch/get-work-item-check-summary.test.ts similarity index 77% rename from src/main/github/client-work-item-check-summary.test.ts rename to src/main/github/client/fetch/get-work-item-check-summary.test.ts index 80a9351198e..c130d488fe1 100644 --- a/src/main/github/client-work-item-check-summary.test.ts +++ b/src/main/github/client/fetch/get-work-item-check-summary.test.ts @@ -1,3 +1,4 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' const { ghExecFileAsyncMock, getOwnerRepoMock, rateLimitGuardMock } = vi.hoisted(() => ({ @@ -6,16 +7,21 @@ const { ghExecFileAsyncMock, getOwnerRepoMock, rateLimitGuardMock } = vi.hoisted rateLimitGuardMock: vi.fn(() => ({ blocked: false })) })) -vi.mock('./gh-utils', () => ({ - execFileAsync: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, +vi.mock('../../execution/github-legacy-command', () => ({ + execFileAsync: vi.fn() +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), githubRepoContext: (repoPath: string, connectionId?: string | null) => ({ repoPath, connectionId: connectionId ?? null }), ghRepoExecOptions: (context: { repoPath: string }) => ({ cwd: context.repoPath }), - getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: vi.fn(), getOwnerRepoForRemote: ( repoPath: string, remoteName: string, @@ -25,21 +31,35 @@ vi.mock('./gh-utils', () => ({ remoteName === 'origin' ? getOwnerRepoMock(repoPath, connectionId, localGitOptions) : Promise.resolve(null), - resolveIssueSource: vi.fn(), - extractExecError: vi.fn((err: unknown) => ({ stderr: String(err), stdout: '' })), + _resetOwnerRepoCache: vi.fn() +})) + +vi.mock('../../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: getOwnerRepoMock, + resolveIssueSource: vi.fn() +})) + +vi.mock('../../../git/command-runner/process-capture/exec-error', async (importOriginal) => ({ + ...(await importOriginal>()), + extractExecError: vi.fn((err: unknown) => ({ stderr: String(err), stdout: '' })) +})) + +vi.mock('../../execution/github-operation-slots', () => ({ acquire: vi.fn(), - release: vi.fn(), - _resetOwnerRepoCache: vi.fn(), + release: vi.fn() +})) + +vi.mock('../../api/gh-error-classification', () => ({ classifyGhError: (stderr: string) => ({ type: 'unknown', message: stderr }), classifyListIssuesError: (stderr: string) => ({ type: 'unknown', message: stderr }) })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: vi.fn() })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: vi.fn(), getRateLimit: vi.fn(async () => ({ ok: false, error: 'not probed in tests' })), @@ -48,10 +68,10 @@ vi.mock('./rate-limit', () => ({ spendsSharedGitHubComQuota: vi.fn(() => true) })) -import { getWorkItem } from './client/fetch/get-work-item' -import { _resetMergeQueueCacheForTests } from './client/detect/repository-merge-metadata-cache' -import { _resetOwnerRepoCache } from './gh-utils' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { getWorkItem } from './get-work-item' +import { _resetMergeQueueCacheForTests } from '../detect/repository-merge-metadata-cache' +import { _resetOwnerRepoCache } from '../../repositories/github-repository-identity' +import { _resetOriginGitHubApiRepositoryCache } from '../../repositories/github-api-repository-remote-probe' // Metadata and complete checks are independent provider responses. function mockPullRequestDetail(payload: Record): void { diff --git a/src/main/github/work-item-details-query-reuse.test.ts b/src/main/github/client/fetch/get-work-item-details-reuse.test.ts similarity index 80% rename from src/main/github/work-item-details-query-reuse.test.ts rename to src/main/github/client/fetch/get-work-item-details-reuse.test.ts index ece68e0e3ef..a1ddd312fd4 100644 --- a/src/main/github/work-item-details-query-reuse.test.ts +++ b/src/main/github/client/fetch/get-work-item-details-reuse.test.ts @@ -1,5 +1,6 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' import { beforeEach, expect, it, vi } from 'vitest' -import type { GitHubApiRepository } from './github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' const mocks = vi.hoisted(() => ({ exec: vi.fn(), @@ -7,10 +8,17 @@ const mocks = vi.hoisted(() => ({ acquire: vi.fn(), release: vi.fn() })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: mocks.exec, +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: mocks.exec +})) + +vi.mock('../../execution/github-operation-slots', () => ({ acquire: mocks.acquire, - release: mocks.release, + release: mocks.release +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), ghRepoExecOptions: (context: { repoPath: string; connectionId?: string; wslDistro?: string }) => context.connectionId ? {} @@ -19,24 +27,34 @@ vi.mock('./gh-utils', () => ({ repoPath, connectionId, ...options - }), + }) +})) + +vi.mock('../../api/gh-error-classification', () => ({ classifyGhError: () => ({ type: 'not_found' }) })) -vi.mock('./github-api-repository', () => ({ - resolveIssueGitHubApiRepositorySource: mocks.source, +vi.mock('../../repositories/github-api-repository', () => ({ + resolveIssueGitHubApiRepositorySource: mocks.source +})) + +vi.mock('../../repositories/github-repository-host', () => ({ githubHostExecOptions: (repository?: GitHubApiRepository) => ({ host: repository?.host }) })) -vi.mock('./client', async () => ({ - ...(await import('./client/fetch/get-work-item')), - getPRChecks: vi.fn(), +vi.mock('../check/get-pr-checks', async () => ({ + ...(await import('./get-work-item')), + getPRChecks: vi.fn() +})) + +vi.mock('./get-pr-comments', async () => ({ + ...(await import('./get-work-item')), getPRComments: vi.fn() })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ repositoryRateLimitGuard: () => ({ blocked: false }), noteRepositoryRateLimitSpend: vi.fn() })) -import { getWorkItemWithRepository } from './client/fetch/get-work-item' +import { getWorkItemWithRepository } from './get-work-item' const repository = { owner: 'team', repo: 'widgets', host: 'github.example.com:8443' } let issue: Record diff --git a/src/main/github/client-issue-origin-preference.test.ts b/src/main/github/client/fetch/get-work-item-origin-preference.test.ts similarity index 67% rename from src/main/github/client-issue-origin-preference.test.ts rename to src/main/github/client/fetch/get-work-item-origin-preference.test.ts index 570d7829856..3112a829da6 100644 --- a/src/main/github/client-issue-origin-preference.test.ts +++ b/src/main/github/client/fetch/get-work-item-origin-preference.test.ts @@ -1,6 +1,11 @@ +import type * as GithubLegacyCommandModule from '../../execution/github-legacy-command' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubOwnerRepoSelectionModule from '../../repositories/github-owner-repo-selection' +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GithubOperationSlotsModule from '../../execution/github-operation-slots' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GhUtils from './gh-utils' const { ghExecFileAsyncMock, @@ -26,23 +31,60 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', async () => { - const actual = await vi.importActual('./gh-utils') +vi.mock('../../execution/github-legacy-command', async () => { + const actual = await vi.importActual( + '../../execution/github-legacy-command' + ) + return { + ...actual, + execFileAsync: vi.fn() + } +}) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async () => { + const actual = await vi.importActual( + '../../../git/command-runner/hosted-cli/gh-exec-file' + ) + return { + ...actual, + ghExecFileAsync: ghExecFileAsyncMock + } +}) + +vi.mock('../../repositories/github-owner-repo-selection', async () => { + const actual = await vi.importActual( + '../../repositories/github-owner-repo-selection' + ) return { ...actual, - execFileAsync: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: getIssueOwnerRepoMock, + resolveIssueSource: resolveIssueSourceMock + } +}) + +vi.mock('../../repositories/github-repository-identity', async () => { + const actual = await vi.importActual( + '../../repositories/github-repository-identity' + ) + return { + ...actual, getOwnerRepoForRemote: getOwnerRepoForRemoteMock, - resolveIssueSource: resolveIssueSourceMock, - acquire: acquireMock, - release: releaseMock, _resetOwnerRepoCache: vi.fn() } }) -vi.mock('./rate-limit', () => ({ +vi.mock('../../execution/github-operation-slots', async () => { + const actual = await vi.importActual( + '../../execution/github-operation-slots' + ) + return { + ...actual, + acquire: acquireMock, + release: releaseMock + } +}) + +vi.mock('../../api/rate-limit', () => ({ rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: noteRateLimitSpendMock, getRateLimit: vi.fn(async () => ({ ok: false, error: 'not probed in tests' })), @@ -51,10 +93,9 @@ vi.mock('./rate-limit', () => ({ spendsSharedGitHubComQuota: () => true })) -vi.mock('./github-api-repository', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('../../repositories/github-api-repository', async (importOriginal) => { return { - ...actual, + ...(await importOriginal()), resolveIssueGitHubApiRepositorySource: ( repoPath: string, preference: unknown, @@ -63,6 +104,17 @@ vi.mock('./github-api-repository', async (importOriginal) => { ) => resolveIssueSourceMock(repoPath, preference, connectionId, localGitOptions), getIssueGitHubApiRepository: (repoPath: string, connectionId?: string | null) => getIssueOwnerRepoMock(repoPath, connectionId), + resolveGitHubApiRepositoryCandidates: ( + repoPath: string, + connectionId?: string | null, + localGitOptions?: unknown + ) => resolvePRRepositoryCandidatesMock(repoPath, connectionId, localGitOptions) + } +}) + +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => { + return { + ...(await importOriginal()), getOriginGitHubApiRepository: ( repoPath: string, connectionId?: string | null, @@ -76,17 +128,12 @@ vi.mock('./github-api-repository', async (importOriginal) => { ) => remoteName === 'origin' ? getOwnerRepoMock(repoPath, connectionId, localGitOptions) - : getOwnerRepoForRemoteMock(repoPath, remoteName, connectionId, localGitOptions), - resolveGitHubApiRepositoryCandidates: ( - repoPath: string, - connectionId?: string | null, - localGitOptions?: unknown - ) => resolvePRRepositoryCandidatesMock(repoPath, connectionId, localGitOptions) + : getOwnerRepoForRemoteMock(repoPath, remoteName, connectionId, localGitOptions) } }) -import { getWorkItem } from './client/fetch/get-work-item' -import { _resetOwnerRepoCache } from './gh-utils' +import { getWorkItem } from './get-work-item' +import { _resetOwnerRepoCache } from '../../repositories/github-repository-identity' describe('GitHub issue open-by-number origin preference', () => { beforeEach(() => { diff --git a/src/main/github/client-merge-queue-auto-merge.test.ts b/src/main/github/client/fetch/get-work-item-preview-host.test.ts similarity index 54% rename from src/main/github/client-merge-queue-auto-merge.test.ts rename to src/main/github/client/fetch/get-work-item-preview-host.test.ts index 296cadebdfe..d857a6a469e 100644 --- a/src/main/github/client-merge-queue-auto-merge.test.ts +++ b/src/main/github/client/fetch/get-work-item-preview-host.test.ts @@ -1,49 +1,79 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' + import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' + +import type * as GitHubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') + const moduleMocks = await import('../client-test-mocks') return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } }) -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) +vi.mock('../../execution/github-legacy-command', () => + moduleMocks.githubLegacyCommandModuleMock(clientMocks) +) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../repositories/github-owner-repo-selection', () => + moduleMocks.githubOwnerSelectionModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubRepositoryIdentityModuleMock(clientMocks) +})) + +vi.mock('../../api/gh-error-classification', () => + moduleMocks.githubErrorClassificationModuleMock() +) + +vi.mock('../../execution/github-operation-slots', () => + moduleMocks.githubOperationSlotsModuleMock(clientMocks) +) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: clientMocks.gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock + ...moduleMocks.githubHostedCliModuleMock(clientMocks) })) -vi.mock('../git/objects/local-git-object-quarantine', () => +vi.mock('../../../git/objects/local-git-object-quarantine', () => moduleMocks.localGitObjectQuarantineModuleMock() ) -vi.mock('./local-git-config-signature', () => +vi.mock('../../repositories/local-git-config-signature', () => moduleMocks.localGitConfigSignatureModuleMock(clientMocks) ) -vi.mock('./github-enterprise-repository', async (importOriginal) => +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => moduleMocks.githubEnterpriseRepositoryModuleMock( await importOriginal() ) ) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) +vi.mock('../../repositories/github-api-repository', async (importOriginal) => moduleMocks.githubApiRepositoryModuleMock( clientMocks, await importOriginal() ) ) -import { getWorkItemByOwnerRepo } from './client/fetch/get-work-item' + +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => + moduleMocks.githubApiRepositoryRemoteProbeModuleMock( + clientMocks, + await importOriginal() + ) +) +import { getWorkItemByOwnerRepo } from './get-work-item' import { installIndependentCheckFixtures, resetGraphQLRateLimitGuardMocks -} from './client-test-harness' +} from '../client-test-harness' const { resolvePRRepositoryCandidatesMock } = clientMocks diff --git a/src/main/github/client/fetch/get-work-item.ts b/src/main/github/client/fetch/get-work-item.ts index 3f3222036aa..7aedb398a8b 100644 --- a/src/main/github/client/fetch/get-work-item.ts +++ b/src/main/github/client/fetch/get-work-item.ts @@ -1,27 +1,27 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' -import type { IssueSourcePreference } from '../../../../shared/repo-types' import { - acquire, - release, - classifyGhError, - extractExecError, + withGitHubRepoExecution, type LocalGitExecOptions -} from '../../gh-utils' +} from '../../repositories/github-repository-identity' +import type { IssueSourcePreference } from '../../../../shared/repo-types' +import { acquire, release } from '../../execution/github-operation-slots' +import { classifyGhError } from '../../api/gh-error-classification' +import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' + import { resolveGitHubApiRepository, resolveGitHubApiRepositoryCandidates, - resolveIssueGitHubApiRepositorySource, - type GitHubApiRepository -} from '../../github-api-repository' + resolveIssueGitHubApiRepositorySource +} from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { githubRepoIdentityKey, isDefaultGitHubHost -} from '../../../../shared/github/repository-identity-key' +} from '../../../../shared/github/repositories/repository-identity-key' import type { MainWorkItem } from './../map/work-item-field-coercion' import { getIssueDetailsViaGraphQL, type CollapsedIssueDetails -} from '../../issue-work-item-details' +} from '../../issues/issue-work-item-details' import { fetchIssueWorkItem, fetchWorkItemPreview, diff --git a/src/main/github/client/fetch/orca-star.ts b/src/main/github/client/fetch/orca-star.ts index aa29d0007f1..d486b2ab667 100644 --- a/src/main/github/client/fetch/orca-star.ts +++ b/src/main/github/client/fetch/orca-star.ts @@ -1,4 +1,5 @@ -import { ghExecFileAsync, acquire, release } from '../../gh-utils' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' import { runAsDefaultWslCliUserAct } from '../../../git/command-runner/hosted-cli/default-wsl-cli-fallback' export const ORCA_REPO = 'stablyai/orca' diff --git a/src/main/github/client/fetch/pr-conversation-response.ts b/src/main/github/client/fetch/pr-conversation-response.ts index 4dd0a251d2e..0ab566f4b3d 100644 --- a/src/main/github/client/fetch/pr-conversation-response.ts +++ b/src/main/github/client/fetch/pr-conversation-response.ts @@ -1,11 +1,11 @@ -import { githubBodyImageUrls, removeFailedGitHubBodyHtml } from '../../body-image-urls' +import { githubBodyImageUrls, removeFailedGitHubBodyHtml } from '../../attachments/body-image-urls' import type { Nodes } from 'mdast' import { z } from 'zod' import remarkParse from 'remark-parse' import { unified } from 'unified' -import type { PRComment } from '../../../../shared/github/comment-types' -import { mapGraphQLReactionGroups } from '../../comment-reactions' -import { tailDiffHunk } from '../../../../shared/github/pr-comment-diff-hunk' +import type { PRComment } from '../../../../shared/github/comments/comment-types' +import { mapGraphQLReactionGroups } from '../../comments/comment-reactions' +import { tailDiffHunk } from '../../../../shared/github/comments/pr-comment-diff-hunk' import { viewerAuthorship } from '../../../source-control/comment-viewer-authorship' const commentSchema = z.object({ diff --git a/src/main/github/client/fetch/repo-slug-upstream.ts b/src/main/github/client/fetch/repo-slug-upstream.ts index 4184817e685..17f4e5cd241 100644 --- a/src/main/github/client/fetch/repo-slug-upstream.ts +++ b/src/main/github/client/fetch/repo-slug-upstream.ts @@ -1,34 +1,37 @@ +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' import { - ghExecFileAsync, - acquire, - release, getRemoteUrlForRepo, - githubRepoContext, + githubRepoContext +} from '../../repositories/github-repository-identity' +import { parseGitHubRemoteIdentity, - type OwnerRepo -} from '../../gh-utils' + gitHubSshConfigHostAlias, + effectiveGitHubRemoteHost +} from '../../repositories/github-remote-identity-parsing' +import type { + GitHubRepositoryIdentity as OwnerRepo, + GitHubRepositoryIdentity as GitHubApiRepository +} from '../../../../shared/github/repositories/repository-identity' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions } from '../../../source-control/hosted-review-git-options' import { getGitHubApiRepositoryForRemote, - getOriginGitHubApiRepository, - githubRepositorySlugArg, - resolveGitHubRepoExecution, - type GitHubApiRepository -} from '../../github-api-repository' + getOriginGitHubApiRepository +} from '../../repositories/github-api-repository-remote-probe' +import { githubRepositorySlugArg } from '../../repositories/github-repository-host' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' + import { withSharedRemoteUrlReads } from '../../../git/remotes/remote-url-probe' import { isStableMissingGitRemoteError } from '../../../git/remotes/stable-missing-git-remote-error' import { normalizeForgeWebHost, foldWwwHostAlias } from '../../../../shared/git/remotes/git-remote-host-alias' -import { - gitHubSshConfigHostAlias, - effectiveGitHubRemoteHost -} from '../../github-remote-identity-parsing' -import { resolveSshConfigHostname } from '../../github-ssh-host-alias-resolution' + +import { resolveSshConfigHostname } from '../../repositories/github-ssh-host-alias-resolution' import { hostedReviewLocalGitOptionArgs, sameOwnerRepo } from './../github-exec-scope' export async function getRepoSlug( repoPath: string, diff --git a/src/main/github/client/fetch/work-item-fetch.ts b/src/main/github/client/fetch/work-item-fetch.ts index fc03ece6b16..bb0cf7daeac 100644 --- a/src/main/github/client/fetch/work-item-fetch.ts +++ b/src/main/github/client/fetch/work-item-fetch.ts @@ -1,12 +1,13 @@ import type { IssueSourcePreference } from '../../../../shared/repo-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { classifyGhError } from '../../api/gh-error-classification' import { - ghExecFileAsync, - classifyGhError, ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions -} from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +} from '../../repositories/github-repository-identity' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { githubPRStackExecutionScope, type GhExecOptions } from './../github-exec-scope' import { resolvePullRequestLookupCandidates } from './../pull-request-lookup-candidates' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' diff --git a/src/main/github/client/gh-error-predicates.ts b/src/main/github/client/gh-error-predicates.ts index 96ed27e0ced..6e8b78fa41d 100644 --- a/src/main/github/client/gh-error-predicates.ts +++ b/src/main/github/client/gh-error-predicates.ts @@ -1,9 +1,9 @@ -import type { PRRefreshOutcome } from '../../../shared/github/pull-request-refresh-types' +import type { PRRefreshOutcome } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { classifyPRRefreshError, safePRRefreshErrorMessage -} from '../pr-refresh-error-classification' -import { classifyGhError } from '../gh-utils' +} from '../pull-requests/refresh/pr-refresh-error-classification' +import { classifyGhError } from '../api/gh-error-classification' // Keep real error parsing available when tests mock gh-utils. import { extractExecError, diff --git a/src/main/github/client/github-exec-scope.ts b/src/main/github/client/github-exec-scope.ts index 44683ecea6a..e2e0bc315d7 100644 --- a/src/main/github/client/github-exec-scope.ts +++ b/src/main/github/client/github-exec-scope.ts @@ -1,11 +1,13 @@ -import type { LocalGitExecOptions, OwnerRepo } from '../gh-utils' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../shared/github/repositories/repository-identity' import { hasHostedReviewLocalGitOptions, - getHostedReviewLocalGitOptions + getHostedReviewLocalGitOptions, + type HostedReviewExecutionOptions } from '../../source-control/hosted-review-git-options' -import type { HostedReviewExecutionOptions } from '../../source-control/hosted-review-git-options' -import type { GitHubRepoExecOptions } from '../github-api-repository' -import { githubRepoIdentityKey } from '../../../shared/github/repository-identity-key' + +import type { GitHubRepoExecOptions } from '../repositories/github-api-repository' +import { githubRepoIdentityKey } from '../../../shared/github/repositories/repository-identity-key' export type GhExecOptions = GitHubRepoExecOptions & { signal?: AbortSignal } export type HostedReviewLocalGitOptions = ReturnType diff --git a/src/main/github/client/list/count-work-items.ts b/src/main/github/client/list/count-work-items.ts index c7d75b9b5be..54423354641 100644 --- a/src/main/github/client/list/count-work-items.ts +++ b/src/main/github/client/list/count-work-items.ts @@ -1,27 +1,27 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' -import type { IssueSourcePreference } from '../../../../shared/repo-types' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../shared/github/work-items-query-bounds' -import { parseTaskQuery, type ParsedTaskQuery } from '../../../../shared/work-items/task-query' import { - ghExecFileAsync, - acquire, - release, + withGitHubRepoExecution, ghRepoExecOptions, githubRepoContext, - type LocalGitExecOptions, - type OwnerRepo -} from '../../gh-utils' + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' +import type { IssueSourcePreference } from '../../../../shared/repo-types' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../shared/github/work-items/work-items-query-bounds' +import { parseTaskQuery, type ParsedTaskQuery } from '../../../../shared/work-items/task-query' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' + +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { githubHostExecOptions } from '../../repositories/github-repository-host' import { - githubHostExecOptions, resolveIssueGitHubApiRepositorySource, type GitHubRepoExecOptions -} from '../../github-api-repository' +} from '../../repositories/github-api-repository' import { getRateLimit, noteRepositoryRateLimitSpend, repositoryRateLimitGuard, spendsSharedGitHubComQuota -} from '../../rate-limit' +} from '../../api/rate-limit' import { sameOwnerRepo } from './../github-exec-scope' import { buildWorkItemListRequest, resolvePrWorkItemSource } from './work-item-list-request' import { buildSearchQueryString, defaultOpenWorkItemQuery } from './work-item-search-query' diff --git a/src/main/github/client-work-items.test.ts b/src/main/github/client/list/list-work-items-source-guard.test.ts similarity index 75% rename from src/main/github/client-work-items.test.ts rename to src/main/github/client/list/list-work-items-source-guard.test.ts index f42be8891cf..6b90e956408 100644 --- a/src/main/github/client-work-items.test.ts +++ b/src/main/github/client/list/list-work-items-source-guard.test.ts @@ -1,8 +1,10 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' // Keep legacy REST request/failure coverage; API-boundary suites exercise the GraphQL path. -vi.mock('./client/list/work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) +vi.mock('./work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) const { execFileAsyncMock, @@ -30,9 +32,16 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ - execFileAsync: execFileAsyncMock, - ghExecFileAsync: ghExecFileAsyncMock, +vi.mock('../../execution/github-legacy-command', () => ({ + execFileAsync: execFileAsyncMock +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), githubRepoContext: ( repoPath: string, connectionId?: string | null, @@ -50,24 +59,32 @@ vi.mock('./gh-utils', () => ({ context.connectionId ? {} : { cwd: context.repoPath, ...(context.wslDistro ? { wslDistro: context.wslDistro } : {}) }, - getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: getIssueOwnerRepoMock, getOwnerRepoForRemote: getOwnerRepoForRemoteMock, - resolveIssueSource: resolveIssueSourceMock, + _resetOwnerRepoCache: vi.fn() +})) + +vi.mock('../../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: getOwnerRepoMock, + resolveIssueSource: resolveIssueSourceMock +})) + +vi.mock('../../execution/github-operation-slots', () => ({ acquire: acquireMock, - release: releaseMock, - _resetOwnerRepoCache: vi.fn(), + release: releaseMock +})) + +vi.mock('../../api/gh-error-classification', () => ({ classifyGhError: (stderr: string) => ({ type: 'unknown', message: stderr }), classifyListIssuesError: (stderr: string) => ({ type: 'unknown', message: stderr }), classifyListPrsError: (stderr: string) => ({ type: 'unknown', message: stderr }) })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: noteRateLimitSpendMock, getRateLimit: vi.fn(async () => ({ ok: false, error: 'not probed in tests' })), @@ -78,11 +95,9 @@ vi.mock('./rate-limit', () => ({ spendsSharedGitHubComQuota: () => true })) -vi.mock('./github-api-repository', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('../../repositories/github-api-repository', async (importOriginal) => { return { - ...actual, - // Bridge hosted source resolution to the suite's gh-utils fixtures. + ...(await importOriginal()), resolveIssueGitHubApiRepositorySource: ( repoPath: string, preference: unknown, @@ -90,7 +105,13 @@ vi.mock('./github-api-repository', async (importOriginal) => { localGitOptions?: unknown ) => resolveIssueSourceMock(repoPath, preference, connectionId, localGitOptions), getIssueGitHubApiRepository: (repoPath: string, connectionId?: string | null) => - getIssueOwnerRepoMock(repoPath, connectionId), + getIssueOwnerRepoMock(repoPath, connectionId) + } +}) + +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => { + return { + ...(await importOriginal()), getOriginGitHubApiRepository: ( repoPath: string, connectionId?: string | null, @@ -107,12 +128,12 @@ vi.mock('./github-api-repository', async (importOriginal) => { : getOwnerRepoForRemoteMock(repoPath, remoteName, connectionId, localGitOptions) } }) -import { listWorkItems } from './client/list/list-work-items' -import { _resetMergeQueueCacheForTests } from './client/detect/repository-merge-metadata-cache' -import { _resetOwnerRepoCache } from './gh-utils' -import { _resetRemoteNameListingCache } from '../git/remotes/remote-name-listing' +import { listWorkItems } from './list-work-items' +import { _resetMergeQueueCacheForTests } from '../detect/repository-merge-metadata-cache' +import { _resetOwnerRepoCache } from '../../repositories/github-repository-identity' +import { _resetRemoteNameListingCache } from '../../../git/remotes/remote-name-listing' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../../repositories/github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() diff --git a/src/main/github/client/list/list-work-items.ts b/src/main/github/client/list/list-work-items.ts index 61930f2ce4e..dc1ec451630 100644 --- a/src/main/github/client/list/list-work-items.ts +++ b/src/main/github/client/list/list-work-items.ts @@ -1,10 +1,14 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' -import type { ListWorkItemsResult } from '../../../../shared/github/work-item-types' +import { + withGitHubRepoExecution, + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' +import type { ListWorkItemsResult } from '../../../../shared/github/work-items/work-item-types' import type { IssueSourcePreference } from '../../../../shared/repo-types' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../shared/github/work-items-query-bounds' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../shared/github/work-items/work-items-query-bounds' import { parseTaskQuery } from '../../../../shared/work-items/task-query' -import { acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveIssueGitHubApiRepositorySource } from '../../github-api-repository' +import { acquire, release } from '../../execution/github-operation-slots' + +import { resolveIssueGitHubApiRepositorySource } from '../../repositories/github-api-repository' import type { MainWorkItem } from './../map/work-item-field-coercion' import { normalizeWorkItemPage, resolvePrWorkItemSource } from './work-item-list-request' import { listRecentWorkItems, listQueriedWorkItems } from './work-item-pages' diff --git a/src/main/github/client/list/work-item-issue-page.ts b/src/main/github/client/list/work-item-issue-page.ts index 2d66ecb9319..e90dd53e880 100644 --- a/src/main/github/client/list/work-item-issue-page.ts +++ b/src/main/github/client/list/work-item-issue-page.ts @@ -1,8 +1,10 @@ import { z } from 'zod' import type { ParsedTaskQuery } from '../../../../shared/work-items/task-query' -import { ghExecFileAsync, type LocalGitExecOptions, type OwnerRepo } from '../../gh-utils' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' -import type { GitHubRepoExecOptions } from '../../github-api-repository' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' import { fetchIssueWorkItem } from '../fetch/work-item-fetch' import { mapIssueWorkItem } from '../map/work-item' import { restIssueActor } from '../map/issue-actor' diff --git a/src/main/github/client/list/work-item-list-request.ts b/src/main/github/client/list/work-item-list-request.ts index d8ebe0592b1..51a90d79c66 100644 --- a/src/main/github/client/list/work-item-list-request.ts +++ b/src/main/github/client/list/work-item-list-request.ts @@ -1,13 +1,14 @@ import type { ClassifiedError } from '../../../../shared/classified-error' import type { IssueSourcePreference } from '../../../../shared/repo-types' import type { ParsedTaskQuery } from '../../../../shared/work-items/task-query' -import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-item-ssh-error' +import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/github/work-items/work-item-ssh-error' import { shouldProbeGitRemote } from '../../../git/remotes/remote-name-listing' -import type { LocalGitExecOptions, OwnerRepo } from '../../gh-utils' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { getGitHubApiRepositoryForRemote, getOriginGitHubApiRepository -} from '../../github-api-repository' +} from '../../repositories/github-api-repository-remote-probe' import { WORK_ITEM_PR_LIST_JSON_FIELDS, type MainWorkItem } from './../map/work-item-field-coercion' import { WORK_ITEM_NUMBER_SORT_QUALIFIER, quoteGitHubSearchValue } from './work-item-search-query' export type WorkItemListRequest = { diff --git a/src/main/github/client/list/work-item-pages.ts b/src/main/github/client/list/work-item-pages.ts index 4e85ab8195e..05a78d2c8bd 100644 --- a/src/main/github/client/list/work-item-pages.ts +++ b/src/main/github/client/list/work-item-pages.ts @@ -1,16 +1,15 @@ import type { ClassifiedError } from '../../../../shared/classified-error' -import { classifyGitHubUnavailable } from '../../../../shared/github/api-availability' +import { classifyGitHubUnavailable } from '../../../../shared/github/api/api-availability' import { parseTaskQuery, type ParsedTaskQuery } from '../../../../shared/work-items/task-query' import { sortWorkItemsByNumber } from '../../../../shared/work-items/work-item-order' +import { classifyListIssuesError, classifyListPrsError } from '../../api/gh-error-classification' import { - classifyListIssuesError, - classifyListPrsError, ghRepoExecOptions, githubRepoContext, - type LocalGitExecOptions, - type OwnerRepo -} from '../../gh-utils' -import { githubHostExecOptions } from '../../github-api-repository' + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { githubHostExecOptions } from '../../repositories/github-repository-host' import { githubPRStackExecutionScope } from './../github-exec-scope' import { hydrateWorkItemRepositoryMergeMetadata } from './../detect/hydrate-work-item-merge-metadata' import type { MainWorkItem } from './../map/work-item-field-coercion' diff --git a/src/main/github/client/list/work-item-pr-page.ts b/src/main/github/client/list/work-item-pr-page.ts index dc63d8b50aa..5bc80c7a9bb 100644 --- a/src/main/github/client/list/work-item-pr-page.ts +++ b/src/main/github/client/list/work-item-pr-page.ts @@ -1,7 +1,8 @@ import { z } from 'zod' import type { ParsedTaskQuery } from '../../../../shared/work-items/task-query' -import { ghExecFileAsync, type OwnerRepo } from '../../gh-utils' -import type { GitHubRepoExecOptions } from '../../github-api-repository' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' import { mapPullRequestWorkItem } from '../map/work-item' import type { MainWorkItem } from '../map/work-item-field-coercion' import { buildWorkItemListRequest } from './work-item-list-request' diff --git a/src/main/github/client/list/work-item-search-batch.ts b/src/main/github/client/list/work-item-search-batch.ts index d3730c27ceb..a92ba728328 100644 --- a/src/main/github/client/list/work-item-search-batch.ts +++ b/src/main/github/client/list/work-item-search-batch.ts @@ -1,12 +1,13 @@ import { z } from 'zod' -import { githubReadExecutionScope as workItemSearchScope } from '../../github-read-execution-scope' -export { githubReadExecutionScope as workItemSearchScope } from '../../github-read-execution-scope' +import { codeHostReadExecutionScope as workItemSearchScope } from '../../../source-control/code-host-read-execution-scope' + import { BoundedMap } from '../../../../shared/bounded-map' import { runCoalescedProbe, type CoalescedProbes } from '../../../git/remotes/coalesced-probe' import { createGhRateLimitBlockedError } from '../../../git/command-runner/hosted-cli/gh-rate-limit-breaker' -import { extractExecError, ghExecFileAsync } from '../../gh-utils' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' -import type { GitHubRepoExecOptions } from '../../github-api-repository' +import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' const envelopeSchema = z.object({ data: z.record(z.string(), z.unknown()).nullish(), diff --git a/src/main/github/client/list/work-item-search-fallback-environment.test.ts b/src/main/github/client/list/work-item-search-fallback-environment.test.ts new file mode 100644 index 00000000000..7514420d8b7 --- /dev/null +++ b/src/main/github/client/list/work-item-search-fallback-environment.test.ts @@ -0,0 +1,27 @@ +import { expect, it, vi } from 'vitest' +import { api, capture } from '../../work-items/work-item-search-test-harness' +import { listWorkItems } from './list-work-items' +import './count-work-items' +import metadata from '../../__fixtures__/work-item-search-metadata.json' + +it('hydrates oversized associations with the preferred page credential', async () => { + vi.stubEnv('GH_TOKEN', 'fixture-original-credential') + const row = structuredClone(metadata[0].graphql) + api.specialNodes = [{ ...row, labels: { ...row.labels, pageInfo: { hasNextPage: true } } }] + capture.mockImplementation(async (binary, args, options) => { + if (args.includes('graphql')) { + vi.stubEnv('GH_TOKEN', 'fixture-later-credential') + } + return api.capture(binary, args, options) + }) + const result = await listWorkItems('fixture/repo', 24, 'is:issue') + expect(result.items[0].labels).toHaveLength(125) + const queries = api.calls.filter( + (call) => + call.args.includes('graphql') || call.args.some((arg) => /^repos\/.+\/issues\/\d+$/.test(arg)) + ) + expect(queries.map((call) => call.fixtureCredential)).toEqual([ + 'fixture-original-credential', + 'fixture-original-credential' + ]) +}) diff --git a/src/main/github/work-item-search-isolation.test.ts b/src/main/github/client/list/work-item-search-isolation.test.ts similarity index 65% rename from src/main/github/work-item-search-isolation.test.ts rename to src/main/github/client/list/work-item-search-isolation.test.ts index 6dfd05f8a60..25929ff66e9 100644 --- a/src/main/github/work-item-search-isolation.test.ts +++ b/src/main/github/client/list/work-item-search-isolation.test.ts @@ -1,9 +1,9 @@ import { expect, it } from 'vitest' -import { api } from './work-item-search-test-harness' -import { listWorkItems } from './client/list/list-work-items' -import { countWorkItems } from './client/list/count-work-items' -import './client/list/work-item-search-page' -import '../git/command-runner/hosted-cli/gh-rate-limit-breaker' +import { api } from '../../work-items/work-item-search-test-harness' +import { listWorkItems } from './list-work-items' +import { countWorkItems } from './count-work-items' +import './work-item-search-page' +import '../../../git/command-runner/hosted-cli/gh-rate-limit-breaker' it('keeps count/list search usable when REST Search is exhausted and reports both-bucket failures', async () => { api.searchAvailable = false diff --git a/src/main/github/client/list/work-item-search-page.ts b/src/main/github/client/list/work-item-search-page.ts index b22fb90c3d5..65fb8175e8d 100644 --- a/src/main/github/client/list/work-item-search-page.ts +++ b/src/main/github/client/list/work-item-search-page.ts @@ -1,13 +1,13 @@ import { BoundedMap } from '../../../../shared/bounded-map' -import { isDefaultGitHubHost } from '../../../../shared/github/repository-identity-key' -import type { OwnerRepo } from '../../gh-utils' -import type { GitHubRepoExecOptions } from '../../github-api-repository' +import { isDefaultGitHubHost } from '../../../../shared/github/repositories/repository-identity-key' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' import { requestWorkItemSearch, - workItemSearchScope, WORK_ITEM_SEARCH_CACHE_MS, type WorkItemSearchType } from './work-item-search-batch' +import { codeHostReadExecutionScope as workItemSearchScope } from '../../../source-control/code-host-read-execution-scope' type PageInfo = { endCursor: string | null; hasNextPage: boolean } export type SearchConnection = { issueCount: number; pageInfo: PageInfo; nodes: T[] } diff --git a/src/main/github/client/lookup/branch-lookup-derived-data.ts b/src/main/github/client/lookup/branch-lookup-derived-data.ts index 538866389e3..db38754a3f4 100644 --- a/src/main/github/client/lookup/branch-lookup-derived-data.ts +++ b/src/main/github/client/lookup/branch-lookup-derived-data.ts @@ -1,6 +1,7 @@ -import { getPRConflictSummary } from '../../conflict-summary' -import type { ghRepoExecOptions, OwnerRepo } from '../../gh-utils' -import { hydrateGitHubPRStack } from '../../github-pr-stack' +import { getPRConflictSummary } from '../../pull-requests/conflicts/conflict-summary' +import type { ghRepoExecOptions } from '../../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { hydrateGitHubPRStack } from '../../pull-requests/stacks/github-pr-stack' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import { derivePullRequestMergeable, type PullRequestLookupData } from './pull-request-lookup-data' import { getCachedGitHubPRStackSummary } from './pr-stack-summary-cache' diff --git a/src/main/github/client/lookup/branch-lookup-resolution.ts b/src/main/github/client/lookup/branch-lookup-resolution.ts index e7fc0487eb9..aa09c96bbd6 100644 --- a/src/main/github/client/lookup/branch-lookup-resolution.ts +++ b/src/main/github/client/lookup/branch-lookup-resolution.ts @@ -1,17 +1,17 @@ -import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types' -import type { ghRepoExecOptions, OwnerRepo } from '../../gh-utils' -import { - isCommitPartOfMergedPR, - type MergedPRCommitMembership -} from '../../merged-pr-commit-membership' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { ghRepoExecOptions } from '../../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { isCommitPartOfMergedPR } from '../../pull-requests/refresh/merged-pr-commit-membership' +import type { MergedReviewCommitMembership as MergedPRCommitMembership } from '../../../source-control/merged-review-membership-memo' import { shouldHideNonOpenReviewOnDefaultBranch } from '../../../source-control/repo-default-branch' -import { - getGitHubApiRepositoryForRemote, - resolveGitHubApiRepositoryCandidates -} from '../../github-api-repository' +import { getGitHubApiRepositoryForRemote } from '../../repositories/github-api-repository-remote-probe' +import { resolveGitHubApiRepositoryCandidates } from '../../repositories/github-api-repository' import { exactRepositoryCandidates } from '../pull-request-lookup-candidates' -import { mapPRState } from '../../mappers' -import { ownerRepoFromPullRequestUrl } from './../github-exec-scope' +import { mapPRState } from '../../api/github-read-response' +import { + ownerRepoFromPullRequestUrl, + type HostedReviewLocalGitOptions +} from './../github-exec-scope' import { prRefreshUpstreamError } from './../gh-error-predicates' import { isMergedImplicitPR, @@ -32,7 +32,7 @@ import { createImplicitDefaultBranchPRPolicy, type ImplicitDefaultBranchPRContext } from './implicit-default-branch-pr' -import type { HostedReviewLocalGitOptions } from './../github-exec-scope' + export async function resolvePRForBranchOutcome(input: { repoPath: string branchName: string diff --git a/src/main/github/client-tracked-upstream-default-branch.test.ts b/src/main/github/client/lookup/get-pr-for-branch-host-scope.test.ts similarity index 70% rename from src/main/github/client-tracked-upstream-default-branch.test.ts rename to src/main/github/client/lookup/get-pr-for-branch-host-scope.test.ts index 01fd5e81a9f..b35dd255cf2 100644 --- a/src/main/github/client-tracked-upstream-default-branch.test.ts +++ b/src/main/github/client/lookup/get-pr-for-branch-host-scope.test.ts @@ -1,45 +1,75 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' + import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' + +import type * as GitHubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') + const moduleMocks = await import('../client-test-mocks') return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } }) -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) +vi.mock('../../execution/github-legacy-command', () => + moduleMocks.githubLegacyCommandModuleMock(clientMocks) +) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../repositories/github-owner-repo-selection', () => + moduleMocks.githubOwnerSelectionModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubRepositoryIdentityModuleMock(clientMocks) +})) + +vi.mock('../../api/gh-error-classification', () => + moduleMocks.githubErrorClassificationModuleMock() +) + +vi.mock('../../execution/github-operation-slots', () => + moduleMocks.githubOperationSlotsModuleMock(clientMocks) +) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: clientMocks.gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock + ...moduleMocks.githubHostedCliModuleMock(clientMocks) })) -vi.mock('./local-git-config-signature', () => +vi.mock('../../repositories/local-git-config-signature', () => moduleMocks.localGitConfigSignatureModuleMock(clientMocks) ) -vi.mock('./github-enterprise-repository', async (importOriginal) => +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => moduleMocks.githubEnterpriseRepositoryModuleMock( await importOriginal() ) ) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) +vi.mock('../../repositories/github-api-repository', async (importOriginal) => moduleMocks.githubApiRepositoryModuleMock( clientMocks, await importOriginal() ) ) -import { getPRForBranch } from './client/lookup/get-pr-for-branch' -import { resetPRForBranchMocks } from './client-test-harness' -import type { RestPullRequest } from './client/lookup/pull-request-lookup-data' -import type { OwnerRepo } from './gh-utils' +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => + moduleMocks.githubApiRepositoryRemoteProbeModuleMock( + clientMocks, + await importOriginal() + ) +) + +import { getPRForBranch } from './get-pr-for-branch' +import { resetPRForBranchMocks } from '../client-test-harness' +import type { RestPullRequest } from './pull-request-lookup-data' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' const { ghExecFileAsyncMock, diff --git a/src/main/github/client-tracked-upstream-fork-owner.test.ts b/src/main/github/client/lookup/get-pr-for-branch-upstream-owner.test.ts similarity index 72% rename from src/main/github/client-tracked-upstream-fork-owner.test.ts rename to src/main/github/client/lookup/get-pr-for-branch-upstream-owner.test.ts index ab6bf241224..2abf0e4c200 100644 --- a/src/main/github/client-tracked-upstream-fork-owner.test.ts +++ b/src/main/github/client/lookup/get-pr-for-branch-upstream-owner.test.ts @@ -1,43 +1,73 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' + import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' + +import type * as GitHubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') + const moduleMocks = await import('../client-test-mocks') return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } }) -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) +vi.mock('../../execution/github-legacy-command', () => + moduleMocks.githubLegacyCommandModuleMock(clientMocks) +) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../repositories/github-owner-repo-selection', () => + moduleMocks.githubOwnerSelectionModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubRepositoryIdentityModuleMock(clientMocks) +})) + +vi.mock('../../api/gh-error-classification', () => + moduleMocks.githubErrorClassificationModuleMock() +) + +vi.mock('../../execution/github-operation-slots', () => + moduleMocks.githubOperationSlotsModuleMock(clientMocks) +) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: clientMocks.gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock + ...moduleMocks.githubHostedCliModuleMock(clientMocks) })) -vi.mock('./local-git-config-signature', () => +vi.mock('../../repositories/local-git-config-signature', () => moduleMocks.localGitConfigSignatureModuleMock(clientMocks) ) -vi.mock('./github-enterprise-repository', async (importOriginal) => +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => moduleMocks.githubEnterpriseRepositoryModuleMock( await importOriginal() ) ) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) +vi.mock('../../repositories/github-api-repository', async (importOriginal) => moduleMocks.githubApiRepositoryModuleMock( clientMocks, await importOriginal() ) ) -import { getPRForBranch } from './client/lookup/get-pr-for-branch' -import { resetPRForBranchMocks } from './client-test-harness' +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => + moduleMocks.githubApiRepositoryRemoteProbeModuleMock( + clientMocks, + await importOriginal() + ) +) + +import { getPRForBranch } from './get-pr-for-branch' +import { resetPRForBranchMocks } from '../client-test-harness' const { ghExecFileAsyncMock, diff --git a/src/main/github/client/lookup/get-pr-for-branch.ts b/src/main/github/client/lookup/get-pr-for-branch.ts index 0d4a98197b8..1f29685be64 100644 --- a/src/main/github/client/lookup/get-pr-for-branch.ts +++ b/src/main/github/client/lookup/get-pr-for-branch.ts @@ -1,4 +1,4 @@ -import type { PRInfo } from '../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' import type { GitHubPRBranchLookupOptions } from './pull-request-lookup-data' import { getPRForBranchOutcome } from './pr-for-branch-outcome' /** Linked PR numbers are authoritative; fallback numbers apply only after branch lookup misses. */ diff --git a/src/main/github/client/lookup/implicit-default-branch-pr.ts b/src/main/github/client/lookup/implicit-default-branch-pr.ts index dbe3ace70c3..210df496dfa 100644 --- a/src/main/github/client/lookup/implicit-default-branch-pr.ts +++ b/src/main/github/client/lookup/implicit-default-branch-pr.ts @@ -1,11 +1,15 @@ -import type { OwnerRepo } from '../../gh-utils' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { getRemoteHeadBranchName, getRepoDefaultBranchName } from '../../../source-control/repo-default-branch' -import type { GhExecOptions, HostedReviewLocalGitOptions } from './../github-exec-scope' -import { ownerRepoFromPullRequestUrl } from './../github-exec-scope' +import { + type GhExecOptions, + type HostedReviewLocalGitOptions, + ownerRepoFromPullRequestUrl +} from './../github-exec-scope' + import type { TrackedUpstreamBranch } from './tracked-upstream-cache' import { pullRequestHeadRepository, diff --git a/src/main/github/client-pr-rest-revalidation.test.ts b/src/main/github/client/lookup/pr-branch-lookup-revalidation.test.ts similarity index 50% rename from src/main/github/client-pr-rest-revalidation.test.ts rename to src/main/github/client/lookup/pr-branch-lookup-revalidation.test.ts index 66db57f8b64..7e241c67528 100644 --- a/src/main/github/client-pr-rest-revalidation.test.ts +++ b/src/main/github/client/lookup/pr-branch-lookup-revalidation.test.ts @@ -1,44 +1,74 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' + import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitHubEnterpriseRepositoryModule from './github-enterprise-repository' + +import type * as GitHubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' const { clientMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./client-test-mocks') + const moduleMocks = await import('../client-test-mocks') return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } }) -vi.mock('./gh-utils', () => moduleMocks.ghUtilsModuleMock(clientMocks)) +vi.mock('../../execution/github-legacy-command', () => + moduleMocks.githubLegacyCommandModuleMock(clientMocks) +) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../repositories/github-owner-repo-selection', () => + moduleMocks.githubOwnerSelectionModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubRepositoryIdentityModuleMock(clientMocks) +})) + +vi.mock('../../api/gh-error-classification', () => + moduleMocks.githubErrorClassificationModuleMock() +) + +vi.mock('../../execution/github-operation-slots', () => + moduleMocks.githubOperationSlotsModuleMock(clientMocks) +) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: clientMocks.gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal()), - ghExecFileAsync: clientMocks.ghExecFileAsyncMock + ...moduleMocks.githubHostedCliModuleMock(clientMocks) })) -vi.mock('./local-git-config-signature', () => +vi.mock('../../repositories/local-git-config-signature', () => moduleMocks.localGitConfigSignatureModuleMock(clientMocks) ) -vi.mock('./github-enterprise-repository', async (importOriginal) => +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => moduleMocks.githubEnterpriseRepositoryModuleMock( await importOriginal() ) ) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) -vi.mock('./github-api-repository', async (importOriginal) => +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) +vi.mock('../../repositories/github-api-repository', async (importOriginal) => moduleMocks.githubApiRepositoryModuleMock( clientMocks, await importOriginal() ) ) -import { getRestPRForBranch } from './client/lookup/pr-branch-lookup' +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => + moduleMocks.githubApiRepositoryRemoteProbeModuleMock( + clientMocks, + await importOriginal() + ) +) -import { resetPRForBranchMocks } from './client-test-harness' +import { getRestPRForBranch } from './pr-branch-lookup' + +import { resetPRForBranchMocks } from '../client-test-harness' const { ghExecFileAsyncMock } = clientMocks diff --git a/src/main/github/client/lookup/pr-branch-lookup.ts b/src/main/github/client/lookup/pr-branch-lookup.ts index c77460698a7..dc432b33021 100644 --- a/src/main/github/client/lookup/pr-branch-lookup.ts +++ b/src/main/github/client/lookup/pr-branch-lookup.ts @@ -1,12 +1,16 @@ import { assertRateLimitBudget } from './pr-lookup-rate-limit' import { selectBranchReview } from '../../../source-control/branch-review-selection' -import { ghExecFileAsync } from '../../gh-utils' -import type { OwnerRepo, ghRepoExecOptions } from '../../gh-utils' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { + GitHubRepositoryIdentity as OwnerRepo, + GitHubRepositoryIdentity as GitHubApiRepository +} from '../../../../shared/github/repositories/repository-identity' +import type { ghRepoExecOptions } from '../../repositories/github-repository-identity' import { githubHostExecOptions, - githubRepositoryWebHost, - type GitHubApiRepository -} from '../../github-api-repository' + githubRepositoryWebHost +} from '../../repositories/github-repository-host' + import type { GhExecOptions } from './../github-exec-scope' import { isNoPullRequestError } from './../gh-error-predicates' import { @@ -22,8 +26,8 @@ import { import { getPRByNumber } from './pr-number-lookup' import { hydratePullRequestLookupData } from './pull-request-lookup-hydration' import type { PRProviderReadOptions } from './shared-pr-provider-reads' -import { revalidateGitHubRestJson } from '../../github-rest-revalidation' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { revalidateGitHubRestJson } from '../../api/github-rest-revalidation' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import { ownerRepoFromPullRequestUrl } from '../github-exec-scope' const BRANCH_PR_LOOKUP_PAGE_SIZE = 10 diff --git a/src/main/github/client/lookup/pr-for-branch-outcome-coalescing.test.ts b/src/main/github/client/lookup/pr-for-branch-outcome-coalescing.test.ts new file mode 100644 index 00000000000..06e499b401e --- /dev/null +++ b/src/main/github/client/lookup/pr-for-branch-outcome-coalescing.test.ts @@ -0,0 +1,77 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { + GitHubRepoContext, + LocalGitExecOptions +} from '../../repositories/github-repository-identity' + +import { __resetHostedReviewBranchCacheForTests } from '../../../source-control/hosted-review-branch-cache' + +const mocks = vi.hoisted(() => ({ resolve: vi.fn(), acquire: vi.fn(), release: vi.fn() })) +vi.mock('../../execution/github-operation-slots', () => ({ + acquire: mocks.acquire, + release: mocks.release +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + githubRepoContext: ( + repoPath: string, + connectionId: string | null, + options: LocalGitExecOptions + ) => ({ + repoPath, + connectionId, + ...options + }), + ghRepoExecOptions: (context: GitHubRepoContext) => context +})) +vi.mock('./branch-lookup-resolution', () => ({ + resolvePRForBranchOutcome: mocks.resolve +})) +vi.mock('./pr-for-branch-outcome', async () => ({ + getPRForBranchOutcome: (await import('./pr-for-branch-outcome')).getPRForBranchOutcome +})) + +vi.mock('../fetch/repo-slug-upstream', async () => ({ + getRepoSlug: vi.fn(async () => ({ owner: 'acme', repo: 'orca' })) +})) + +vi.mock('./pr-lookup-rate-limit', async () => ({ + getGitHubPRLookupRateLimitBlock: vi.fn(async () => null) +})) + +vi.mock('../create/create-github-pull-request', async () => ({ + createGitHubPullRequest: vi.fn() +})) +vi.mock('../../../gitlab/merge-requests/merge-request-lookup', () => ({ + getProjectSlug: vi.fn(async () => null) +})) +import { getPRForBranchOutcome } from './pr-for-branch-outcome' + +const outcome: PRRefreshOutcome = { kind: 'no-pr', fetchedAt: 1 } +beforeEach(() => { + vi.clearAllMocks() + mocks.resolve.mockReset() + __resetHostedReviewBranchCacheForTests() + mocks.acquire.mockResolvedValue(undefined) +}) +afterEach(() => { + vi.unstubAllEnvs() + vi.useRealTimers() +}) + +describe('PR lookup coalescing', () => { + it('cleans up errors so later reads can recover', async () => { + mocks.resolve.mockRejectedValueOnce(new Error('network down')).mockResolvedValue(outcome) + const results = await Promise.all([ + getPRForBranchOutcome('/repo', 'topic'), + getPRForBranchOutcome('/repo', 'topic') + ]) + expect(results.every((result) => result.kind === 'upstream-error')).toBe(true) + expect(mocks.resolve).toHaveBeenCalledTimes(1) + await expect(getPRForBranchOutcome('/repo', 'topic')).resolves.toEqual(outcome) + expect(mocks.resolve).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/main/github/client/lookup/pr-for-branch-outcome.ts b/src/main/github/client/lookup/pr-for-branch-outcome.ts index 05db157f287..a1d4bc779a5 100644 --- a/src/main/github/client/lookup/pr-for-branch-outcome.ts +++ b/src/main/github/client/lookup/pr-for-branch-outcome.ts @@ -1,13 +1,18 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' +import { + withGitHubRepoExecution, + ghRepoExecOptions, + githubRepoContext +} from '../../repositories/github-repository-identity' import { getRepoExecutionHostId } from '../../../../shared/execution-host' import { hostedReviewRepoScope, scopeGeneration } from '../../../source-control/hosted-review-scope-generations' import { runCoalescedProbe, type CoalescedProbes } from '../../../git/remotes/coalesced-probe' -import { githubReadExecutionScope } from '../../github-read-execution-scope' -import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types' -import { acquire, release, ghRepoExecOptions, githubRepoContext } from '../../gh-utils' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { acquire, release } from '../../execution/github-operation-slots' + import { hostedReviewLocalGitOptionArgs, githubPRStackExecutionScope } from './../github-exec-scope' import type { GitHubPRBranchLookupOptions } from './pull-request-lookup-data' import { prRefreshUpstreamError } from './../gh-error-predicates' diff --git a/src/main/github/client/lookup/pr-last-outcome.ts b/src/main/github/client/lookup/pr-last-outcome.ts index f5c7cb383be..7d6dec32aac 100644 --- a/src/main/github/client/lookup/pr-last-outcome.ts +++ b/src/main/github/client/lookup/pr-last-outcome.ts @@ -1,4 +1,4 @@ -import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' // A phone's re-read right after the desktop's own lookup reuses it instead of asking GitHub again. const PHONE_REUSE_MAX_AGE_MS = 30_000 diff --git a/src/main/github/client/lookup/pr-lookup-rate-limit.test.ts b/src/main/github/client/lookup/pr-lookup-rate-limit.test.ts new file mode 100644 index 00000000000..c710f135f93 --- /dev/null +++ b/src/main/github/client/lookup/pr-lookup-rate-limit.test.ts @@ -0,0 +1,121 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' +import type * as GhExecFileModule from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubApiRepositoryModule from '../../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../../repositories/github-api-repository-remote-probe' +import type * as GitExecFileModule from '../../../git/command-runner/git-exec-file' + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import type * as GitHubEnterpriseRepositoryModule from '../../repositories/github-enterprise-repository' + +const { clientMocks, moduleMocks } = await vi.hoisted(async () => { + const moduleMocks = await import('../client-test-mocks') + return { clientMocks: moduleMocks.createGitHubClientMocks(), moduleMocks } +}) + +vi.mock('../../execution/github-legacy-command', () => + moduleMocks.githubLegacyCommandModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-owner-repo-selection', () => + moduleMocks.githubOwnerSelectionModuleMock(clientMocks) +) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubRepositoryIdentityModuleMock(clientMocks) +})) + +vi.mock('../../api/gh-error-classification', () => + moduleMocks.githubErrorClassificationModuleMock() +) + +vi.mock('../../execution/github-operation-slots', () => + moduleMocks.githubOperationSlotsModuleMock(clientMocks) +) + +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal()), + gitExecFileAsync: clientMocks.gitExecFileAsyncMock +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ + ...(await importOriginal()), + ...moduleMocks.githubHostedCliModuleMock(clientMocks) +})) +vi.mock('../../repositories/local-git-config-signature', () => + moduleMocks.localGitConfigSignatureModuleMock(clientMocks) +) +vi.mock('../../repositories/github-enterprise-repository', async (importOriginal) => + moduleMocks.githubEnterpriseRepositoryModuleMock( + await importOriginal() + ) +) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(clientMocks)) +vi.mock('../../repositories/github-api-repository', async (importOriginal) => + moduleMocks.githubApiRepositoryModuleMock( + clientMocks, + await importOriginal() + ) +) + +vi.mock('../../repositories/github-api-repository-remote-probe', async (importOriginal) => + moduleMocks.githubApiRepositoryRemoteProbeModuleMock( + clientMocks, + await importOriginal() + ) +) + +import { getGitHubPRLookupRateLimitBlock } from './pr-lookup-rate-limit' +import { _resetOwnerRepoCache } from '../../repositories/github-repository-identity' +import { resetOriginRepositoryCache } from '../client-test-harness' +import type { RateLimitGuardResult } from '../client-test-mocks' + +const { + execFileAsyncMock, + ghExecFileAsyncMock, + getOwnerRepoMock, + getRemoteUrlForRepoMock, + gitExecFileAsyncMock, + getRateLimitMock, + rateLimitGuardMock, + noteRateLimitSpendMock +} = clientMocks + +describe('getGitHubPRLookupRateLimitBlock', () => { + beforeEach(() => { + resetOriginRepositoryCache() + execFileAsyncMock.mockReset() + ghExecFileAsyncMock.mockReset() + getOwnerRepoMock.mockReset() + getOwnerRepoMock.mockResolvedValue({ owner: 'acme', repo: 'widgets' }) + getRemoteUrlForRepoMock.mockReset() + gitExecFileAsyncMock.mockReset() + getRateLimitMock.mockReset() + getRateLimitMock.mockResolvedValue(undefined) + rateLimitGuardMock.mockReset() + rateLimitGuardMock.mockReturnValue({ blocked: false }) + noteRateLimitSpendMock.mockReset() + _resetOwnerRepoCache() + }) + + it('reports the latest reset when both lookup buckets are exhausted', async () => { + rateLimitGuardMock.mockImplementation(((bucket: string) => ({ + blocked: true, + remaining: 4, + limit: 5000, + // Why: core resets first, so returning it would retry into graphql's block. + resetAt: bucket === 'core' ? 1_800_000_000 : 1_800_003_600 + })) as () => RateLimitGuardResult) + + await expect(getGitHubPRLookupRateLimitBlock('/repo-root')).resolves.toEqual({ + resetAt: 1_800_003_600 + }) + }) + + it('fails open when the exempt rate-limit probe itself fails', async () => { + getRateLimitMock.mockRejectedValue(new Error('probe offline')) + + await expect(getGitHubPRLookupRateLimitBlock('/repo-root')).resolves.toBeNull() + }) +}) diff --git a/src/main/github/client/lookup/pr-lookup-rate-limit.ts b/src/main/github/client/lookup/pr-lookup-rate-limit.ts index 60d9c30d403..9f5352e0d20 100644 --- a/src/main/github/client/lookup/pr-lookup-rate-limit.ts +++ b/src/main/github/client/lookup/pr-lookup-rate-limit.ts @@ -1,11 +1,16 @@ -import { ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions } from '../../gh-utils' -import { getOriginGitHubApiRepository, type GitHubApiRepository } from '../../github-api-repository' +import { + ghRepoExecOptions, + githubRepoContext, + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' +import { getOriginGitHubApiRepository } from '../../repositories/github-api-repository-remote-probe' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { getRateLimit, repositoryRateLimitGuard, spendsSharedGitHubComQuota, type RateLimitBucketKind -} from '../../rate-limit' +} from '../../api/rate-limit' import type { GhExecOptions } from './../github-exec-scope' // REST branch lookup can fall back to GraphQL-backed gh commands; guard and debit both buckets. export const PR_BRANCH_LOOKUP_BUCKETS = ['core', 'graphql'] as const diff --git a/src/main/github/client/lookup/pr-number-lookup.ts b/src/main/github/client/lookup/pr-number-lookup.ts index c64a510aedd..db3eb3df8ec 100644 --- a/src/main/github/client/lookup/pr-number-lookup.ts +++ b/src/main/github/client/lookup/pr-number-lookup.ts @@ -1,7 +1,12 @@ -import { classifyPRRefreshError } from '../../pr-refresh-error-classification' -import { ghExecFileAsync } from '../../gh-utils' -import type { OwnerRepo, ghRepoExecOptions } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import { classifyPRRefreshError } from '../../pull-requests/refresh/pr-refresh-error-classification' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { + GitHubRepositoryIdentity as OwnerRepo, + GitHubRepositoryIdentity as GitHubApiRepository +} from '../../../../shared/github/repositories/repository-identity' +import type { ghRepoExecOptions } from '../../repositories/github-repository-identity' +import { githubHostExecOptions } from '../../repositories/github-repository-host' + import { isNoPullRequestError, isNotFoundGhError, @@ -17,8 +22,8 @@ import { } from './pull-request-lookup-data' import { hydratePullRequestLookupData } from './pull-request-lookup-hydration' import { isGitObjectId, isUsableRestStackMetadata } from './rest-stack-metadata-validation' -import { revalidateGitHubRestJson } from '../../github-rest-revalidation' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { revalidateGitHubRestJson } from '../../api/github-rest-revalidation' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import { ownerRepoFromPullRequestUrl, sameOwnerRepo } from '../github-exec-scope' import { readSharedPullRequestSidebarQuery, diff --git a/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts b/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts index 60c23b2ce56..7ccbac47fc7 100644 --- a/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts +++ b/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts @@ -2,14 +2,14 @@ import { classifyPRCheckRollupResult, summarizePRCheckRollup } from '../../../../shared/source-control/checks/pr-check-status' -import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { PRConflictSummary, PRMergeableState, GitHubPRStack -} from '../../../../shared/github/pull-request-types' -import { mapPRState } from '../../mappers' -import type { OwnerRepo } from '../../gh-utils' +} from '../../../../shared/github/pull-requests/pull-request-types' +import { mapPRState } from '../../api/github-read-response' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { PullRequestLookupData } from './pull-request-lookup-data' // Main-only: the full rollup stays off the renderer broadcast, beside the outcome it was read for. diff --git a/src/main/github/client/lookup/pr-stack-summary-cache.ts b/src/main/github/client/lookup/pr-stack-summary-cache.ts index c21baa63106..0021509620b 100644 --- a/src/main/github/client/lookup/pr-stack-summary-cache.ts +++ b/src/main/github/client/lookup/pr-stack-summary-cache.ts @@ -1,8 +1,9 @@ -import type { GitHubPRStack } from '../../../../shared/github/pull-request-types' -import type { ghRepoExecOptions } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' -import { githubReadExecutionScope } from '../../github-read-execution-scope' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import type { GitHubPRStack } from '../../../../shared/github/pull-requests/pull-request-types' +import type { ghRepoExecOptions } from '../../repositories/github-repository-identity' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { getRestPRByNumber } from './pr-number-lookup' export const PR_STACK_SUMMARY_CACHE_TTL_MS = 60_000 diff --git a/src/main/github/client/lookup/pull-request-lookup-data.ts b/src/main/github/client/lookup/pull-request-lookup-data.ts index 5691089505f..e915082f4b8 100644 --- a/src/main/github/client/lookup/pull-request-lookup-data.ts +++ b/src/main/github/client/lookup/pull-request-lookup-data.ts @@ -1,24 +1,28 @@ import { readCurrentReviewHead } from '../../../source-control/review-current-head' import { shouldHideMergedImplicitReview } from '../../../source-control/branch-review-selection' import type { - GitHubOwnerRepo, + GitHubRepositoryIdentity as GitHubOwnerRepo, + GitHubRepositoryIdentity as OwnerRepo +} from '../../../../shared/github/repositories/repository-identity' +import type { GitHubPRMergeMethodSettings, GitHubPRStack, PRMergeableState, PRReviewDecision -} from '../../../../shared/github/pull-request-types' -import { gitExecFileAsync, type OwnerRepo } from '../../gh-utils' +} from '../../../../shared/github/pull-requests/pull-request-types' +import { gitExecFileAsync } from '../../../git/command-runner/git-exec-file' + import type { GitAdmissionTier } from '../../../../shared/rpc-contract/git-admission-tier-params' import type { HostedReviewExecutionOptions } from '../../../source-control/hosted-review-git-options' -import { mapPRState } from '../../mappers' -import type { MergedPRCommitMembership } from '../../merged-pr-commit-membership' +import { mapPRState } from '../../api/github-read-response' +import type { MergedReviewCommitMembership as MergedPRCommitMembership } from '../../../source-control/merged-review-membership-memo' import { normalizePRMergeable, normalizeReviewDecision, isAutoMergeEnabled } from './../map/work-item-field-coercion' import { ownerRepoFromPullRequestUrl, sameOwnerRepo } from '../github-exec-scope' -import { githubRepositoryWebHost } from '../../github-repository-host' +import { githubRepositoryWebHost } from '../../repositories/github-repository-host' export type PullRequestLookupData = { number: number title: string diff --git a/src/main/github/client/lookup/pull-request-lookup-hydration.ts b/src/main/github/client/lookup/pull-request-lookup-hydration.ts index 4a24491569f..7ef03d7b33c 100644 --- a/src/main/github/client/lookup/pull-request-lookup-hydration.ts +++ b/src/main/github/client/lookup/pull-request-lookup-hydration.ts @@ -1,7 +1,7 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' -import { mapPRState } from '../../mappers' +import { mapPRState } from '../../api/github-read-response' import { normalizePullRequestLookupData, type PullRequestLookupData diff --git a/src/main/github/client/lookup/pull-request-push-target.ts b/src/main/github/client/lookup/pull-request-push-target.ts index be96c55dfb1..0812cb51005 100644 --- a/src/main/github/client/lookup/pull-request-push-target.ts +++ b/src/main/github/client/lookup/pull-request-push-target.ts @@ -1,21 +1,19 @@ -import { withGitHubRepoExecution } from '../../github-repository-identity' -import type { IssueSourcePreference } from '../../../../shared/repo-types' -import type { GitPushTarget } from '../../../../shared/worktree/types' import { - ghExecFileAsync, - acquire, - release, + withGitHubRepoExecution, ghRepoExecOptions, githubRepoContext, getRemoteUrlForRepo, type LocalGitExecOptions -} from '../../gh-utils' -import { - getGitHubApiRepositoryForRemote, - githubHostExecOptions, - type GitHubApiRepository -} from '../../github-api-repository' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +} from '../../repositories/github-repository-identity' +import type { IssueSourcePreference } from '../../../../shared/repo-types' +import type { GitPushTarget } from '../../../../shared/worktree/types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' + +import { getGitHubApiRepositoryForRemote } from '../../repositories/github-api-repository-remote-probe' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { isNotFoundGhError } from './../gh-error-predicates' import { resolvePullRequestLookupCandidates } from './../pull-request-lookup-candidates' export function pickPushRemoteUrl(args: { diff --git a/src/main/github/pull-request-sidebar-query.test.ts b/src/main/github/client/lookup/pull-request-sidebar-query.test.ts similarity index 62% rename from src/main/github/pull-request-sidebar-query.test.ts rename to src/main/github/client/lookup/pull-request-sidebar-query.test.ts index 68a1d887b27..1eedfb87fa6 100644 --- a/src/main/github/pull-request-sidebar-query.test.ts +++ b/src/main/github/client/lookup/pull-request-sidebar-query.test.ts @@ -1,27 +1,27 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const { exec, spend } = vi.hoisted(() => ({ exec: vi.fn(), spend: vi.fn() })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: exec })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: vi.fn() })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ noteRepositoryRateLimitSpend: spend, repositoryRateLimitGuard: () => ({ blocked: false }), spendsSharedGitHubComQuota: () => false })) -import { getPRByNumber, lookupPRByNumber } from './client/lookup/pr-number-lookup' -import { _resetPRStackSummaryCacheForTests } from './client/lookup/pr-stack-summary-cache' -import { _resetMergeQueueCacheForTests } from './client/detect/repository-merge-metadata-cache' -import { readPullRequestSidebarQuery } from './client/lookup/pull-request-sidebar-query' -import { createImplicitDefaultBranchPRPolicy } from './client/lookup/implicit-default-branch-pr' -import { deriveCheckStatus } from './mappers' +import { getPRByNumber, lookupPRByNumber } from './pr-number-lookup' +import { _resetPRStackSummaryCacheForTests } from './pr-stack-summary-cache' +import { _resetMergeQueueCacheForTests } from '../detect/repository-merge-metadata-cache' +import { readPullRequestSidebarQuery } from './pull-request-sidebar-query' +import { createImplicitDefaultBranchPRPolicy } from './implicit-default-branch-pr' +import { derivePRCheckStatusFromRollup as deriveCheckStatus } from '../../../../shared/source-control/checks/pr-check-status' const repository = { owner: 'acme', repo: 'widgets', host: 'github.com' } const options = { cwd: '/folder-workspace' } @@ -170,25 +170,6 @@ describe('sidebar PR query', () => { }) } ) - - it.each([ - { data: { repository: { pullRequest: { ...fields, number: 99 } } } }, - { - data: { repository: { pullRequest: fields } }, - errors: [{ path: ['repository', 'pullRequest', 'headRefOid'] }] - }, - { data: { repository: { pullRequest: fields } }, errors: [{ message: 'Unscoped failure' }] } - ])('falls back to the rich CLI read for an unusable response', async (envelope) => { - exec.mockResolvedValueOnce({ stdout: JSON.stringify(envelope) }).mockResolvedValueOnce({ - stdout: JSON.stringify({ ...fields, title: 'CLI fallback', statusCheckRollup: [] }) - }) - await expect( - getPRByNumber(repository, 42, options, 'local:host', undefined, { includeStackSummary: true }) - ).resolves.toMatchObject({ - title: 'CLI fallback' - }) - expect(exec.mock.calls[1][0].slice(0, 2)).toEqual(['pr', 'view']) - }) }) it.each([ @@ -225,63 +206,3 @@ it.each([ }) } ) - -it.each([1, 10])( - 'reuses combined exact hydration with independent context and suite populations at N=%i', - async (n) => { - const contextPages = 2 * n - const suitePages = 3 * n - exec.mockImplementation(async (args: string[]) => { - const contextCursor = args.find((arg) => arg.startsWith('checksCursor=')) - const suiteCursor = args.find((arg) => arg.startsWith('suitesCursor=')) - const contextIndex = contextCursor ? Number(contextCursor.split('=')[1]) : 0 - const suiteIndex = suiteCursor ? Number(suiteCursor.split('=')[1]) : 0 - const lastContext = contextIndex === contextPages - 1 - const commit = { - oid: fields.headRefOid, - ...(!suiteCursor - ? { - statusCheckRollup: { - contexts: { - nodes: [ - lastContext ? { __typename: 'StatusContext', state: 'FAILURE' } : successCheck - ], - pageInfo: { - hasNextPage: !lastContext, - endCursor: lastContext ? null : String(contextIndex + 1) - } - } - } - } - : {}), - ...(!contextCursor - ? { - checkSuites: { - nodes: [{ databaseId: suiteIndex + 1, conclusion: 'SUCCESS' }], - pageInfo: { - hasNextPage: suiteIndex < suitePages - 1, - endCursor: suiteIndex < suitePages - 1 ? String(suiteIndex + 1) : null - } - } - } - : {}) - } - return response({ ...fields, commits: { nodes: [{ commit }] } }) - }) - const known = { - ...fields, - stack: undefined, - statusCheckRollup: [], - headDefaultBranchName: 'trunk' - } - const observed = await getPRByNumber(repository, 42, options, 'local:host', known) - expect(observed).toMatchObject({ headDefaultBranchName: 'trunk', checksUnavailable: false }) - expect(observed?.statusCheckRollup).toHaveLength(contextPages) - expect(observed?.statusCheckRollup?.at(-1)).toEqual({ - __typename: 'StatusContext', - state: 'FAILURE' - }) - expect(exec).toHaveBeenCalledTimes(1 + (contextPages - 1) + (suitePages - 1)) - expect(exec.mock.calls.every(([args]) => args[0] === 'api' && args[1] === 'graphql')).toBe(true) - } -) diff --git a/src/main/github/client/lookup/pull-request-sidebar-query.ts b/src/main/github/client/lookup/pull-request-sidebar-query.ts index c8d3cfe0952..0ab2afc50ce 100644 --- a/src/main/github/client/lookup/pull-request-sidebar-query.ts +++ b/src/main/github/client/lookup/pull-request-sidebar-query.ts @@ -1,8 +1,9 @@ import { z } from 'zod' -import { ghExecFileAsync } from '../../gh-utils' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend } from '../../rate-limit' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend } from '../../api/rate-limit' import type { GhExecOptions } from '../github-exec-scope' import type { PullRequestLookupData } from './pull-request-lookup-data' import { diff --git a/src/main/github/client/lookup/shared-pr-provider-reads.ts b/src/main/github/client/lookup/shared-pr-provider-reads.ts index fd6c923e7a4..4202c2aa046 100644 --- a/src/main/github/client/lookup/shared-pr-provider-reads.ts +++ b/src/main/github/client/lookup/shared-pr-provider-reads.ts @@ -3,8 +3,8 @@ import { runCoalescedProbe, type CoalescedProbes } from '../../../git/remotes/coalesced-probe' -import type { GitHubApiRepository } from '../../github-api-repository' -import { githubReadExecutionScope } from '../../github-read-execution-scope' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' import { readCompletePRCheckObservation } from '../check/complete-pr-check-observation' import type { GhExecOptions } from '../github-exec-scope' import type { PullRequestLookupData } from './pull-request-lookup-data' diff --git a/src/main/github/client/lookup/tracked-upstream-branch.ts b/src/main/github/client/lookup/tracked-upstream-branch.ts index 9fd4a838e34..469ca691639 100644 --- a/src/main/github/client/lookup/tracked-upstream-branch.ts +++ b/src/main/github/client/lookup/tracked-upstream-branch.ts @@ -2,8 +2,8 @@ import { readGitReviewBranchSources, type GitReviewBranchSource } from '../../../../shared/git/remotes/git-review-branch-source' -import { gitExecFileAsync } from '../../gh-utils' -import { readLocalGitConfigSignature } from '../../local-git-config-signature' +import { gitExecFileAsync } from '../../../git/command-runner/git-exec-file' +import { readLocalGitConfigSignature } from '../../repositories/local-git-config-signature' import type { GitAdmissionTier } from '../../../../shared/rpc-contract/git-admission-tier-params' import { TRACKED_UPSTREAM_SNAPSHOT_CACHE_TTL_MS, diff --git a/src/main/github/client/lookup/tracked-upstream-cache.ts b/src/main/github/client/lookup/tracked-upstream-cache.ts index 7a0f37d06d7..1e3452a9151 100644 --- a/src/main/github/client/lookup/tracked-upstream-cache.ts +++ b/src/main/github/client/lookup/tracked-upstream-cache.ts @@ -1,8 +1,8 @@ import type { GitReviewBranchSource } from '../../../../shared/git/remotes/git-review-branch-source' import { splitRemoteBranchName } from '../../../../shared/git/remotes/git-remote-branch-name' -import type { OwnerRepo } from '../../gh-utils' -import { readLocalGitConfigSignature } from '../../local-git-config-signature' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import { readLocalGitConfigSignature } from '../../repositories/local-git-config-signature' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' export type TrackedUpstreamBranch = { remoteName: string branchName: string diff --git a/src/main/github/client/map/review-comment-response.ts b/src/main/github/client/map/review-comment-response.ts index f17f653ba0e..6ea746f4853 100644 --- a/src/main/github/client/map/review-comment-response.ts +++ b/src/main/github/client/map/review-comment-response.ts @@ -1,5 +1,5 @@ -import { githubBodyImageUrls } from '../../body-image-urls' -import type { PRComment } from '../../../../shared/github/comment-types' +import { githubBodyImageUrls } from '../../attachments/body-image-urls' +import type { PRComment } from '../../../../shared/github/comments/comment-types' export function mapReviewCommentResponse( data: { id?: number diff --git a/src/main/github/client/map/work-item-field-coercion.ts b/src/main/github/client/map/work-item-field-coercion.ts index 592c84b6ca1..d586e173a30 100644 --- a/src/main/github/client/map/work-item-field-coercion.ts +++ b/src/main/github/client/map/work-item-field-coercion.ts @@ -1,8 +1,8 @@ import type { PRMergeableState, PRReviewDecision -} from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +} from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { summarizeProviderChecks } from '../../../../shared/source-control/checks/provider-check-summary' // The renderer supplies repoId after IPC; main knows only the repository path. export type MainWorkItem = Omit diff --git a/src/main/github/client/map/work-item.ts b/src/main/github/client/map/work-item.ts index f2018edcf00..bb5c1218b1b 100644 --- a/src/main/github/client/map/work-item.ts +++ b/src/main/github/client/map/work-item.ts @@ -1,4 +1,4 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { authorFieldsFromUnknown, extractHeadOwnerLogin, diff --git a/src/main/github/client/merge/merge-pr.ts b/src/main/github/client/merge/merge-pr.ts index 801a736a870..12d28d225e7 100644 --- a/src/main/github/client/merge/merge-pr.ts +++ b/src/main/github/client/merge/merge-pr.ts @@ -1,9 +1,12 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' -import type { PRConflictSummary } from '../../../../shared/github/pull-request-types' -import { getPRConflictSummary } from '../../conflict-summary' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { mergeGitHubPRStack } from '../../github-pr-stack' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' +import type { PRConflictSummary } from '../../../../shared/github/pull-requests/pull-request-types' +import { getPRConflictSummary } from '../../pull-requests/conflicts/conflict-summary' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { mergeGitHubPRStack } from '../../pull-requests/stacks/github-pr-stack-async-merge' import { githubPRStackExecutionScope, type GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import type { PullRequestLookupData } from './../lookup/pull-request-lookup-data' diff --git a/src/main/github/client/merge/pr-auto-merge.ts b/src/main/github/client/merge/pr-auto-merge.ts index d0224544860..cb770163301 100644 --- a/src/main/github/client/merge/pr-auto-merge.ts +++ b/src/main/github/client/merge/pr-auto-merge.ts @@ -1,8 +1,11 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' -import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-request-types' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { classifyPullRequestUpdateError } from '../../gh-error-classification' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' +import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-requests/pull-request-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { classifyPullRequestUpdateError } from '../../api/gh-error-classification' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { githubPRStackExecutionScope, type GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import { getRestPRByNumber } from './../lookup/pr-number-lookup' diff --git a/src/main/github/client/pull-request-lookup-candidates.ts b/src/main/github/client/pull-request-lookup-candidates.ts index 05e6ca8e090..394ddb705b4 100644 --- a/src/main/github/client/pull-request-lookup-candidates.ts +++ b/src/main/github/client/pull-request-lookup-candidates.ts @@ -1,10 +1,8 @@ import type { IssueSourcePreference } from '../../../shared/repo-types' -import type { LocalGitExecOptions } from '../gh-utils' -import { - getOriginGitHubApiRepository, - resolveGitHubApiRepositoryCandidates, - type GitHubApiRepository -} from '../github-api-repository' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' +import { getOriginGitHubApiRepository } from '../repositories/github-api-repository-remote-probe' +import { resolveGitHubApiRepositoryCandidates } from '../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' export async function resolvePullRequestLookupCandidates( repoPath: string, preference: IssueSourcePreference | undefined, diff --git a/src/main/github/client/update/pr-comment-reaction.ts b/src/main/github/client/update/pr-comment-reaction.ts index 872ba463eb9..d847f34d0ed 100644 --- a/src/main/github/client/update/pr-comment-reaction.ts +++ b/src/main/github/client/update/pr-comment-reaction.ts @@ -1,8 +1,11 @@ -import type { GitHubReactionContent } from '../../../../shared/github/comment-types' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { toGraphQLReactionContent } from '../../comment-reactions' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import type { GitHubReactionContent } from '../../../../shared/github/comments/comment-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { toGraphQLReactionContent } from '../../comments/comment-reactions' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' export async function setPRCommentReaction( repoPath: string, reactionSubjectId: string, diff --git a/src/main/github/client/update/pr-details.ts b/src/main/github/client/update/pr-details.ts index 47a7c377fec..99b4e899ed5 100644 --- a/src/main/github/client/update/pr-details.ts +++ b/src/main/github/client/update/pr-details.ts @@ -1,16 +1,13 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' -import { - ghExecFileAsync, - acquire, - release, - classifyPullRequestUpdateError, - type LocalGitExecOptions -} from '../../gh-utils' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import { classifyPullRequestUpdateError } from '../../api/gh-error-classification' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' import { resolveGitHubRepoExecution, - type GitHubApiRepository, type GitHubRepoExecOptions -} from '../../github-api-repository' +} from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { codeHostFailure, classifiedFailure } from '../../../../shared/code-host-failure-copy' async function patchPullRequest( diff --git a/src/main/github/client/update/pr-file-viewed.ts b/src/main/github/client/update/pr-file-viewed.ts index 6caa223e7e2..66c54695e9c 100644 --- a/src/main/github/client/update/pr-file-viewed.ts +++ b/src/main/github/client/update/pr-file-viewed.ts @@ -1,7 +1,10 @@ -import type { GhAccountBinding } from '../../../../shared/github/account-binding' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' export async function setPRFileViewed(args: { repoPath: string connectionId?: string | null diff --git a/src/main/github/client/update/pr-ready.ts b/src/main/github/client/update/pr-ready.ts index e9aceaa068a..1c33e389039 100644 --- a/src/main/github/client/update/pr-ready.ts +++ b/src/main/github/client/update/pr-ready.ts @@ -1,12 +1,10 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' -import { - acquire, - classifyPullRequestUpdateError, - ghExecFileAsync, - release, - type LocalGitExecOptions -} from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' +import { acquire, release } from '../../execution/github-operation-slots' +import { classifyPullRequestUpdateError } from '../../api/gh-error-classification' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { codeHostFailure, classifiedFailure } from '../../../../shared/code-host-failure-copy' export function markPRReadyForReview( diff --git a/src/main/github/client/update/pr-reviewers.ts b/src/main/github/client/update/pr-reviewers.ts index 39ffa1f2420..aafba3a26f1 100644 --- a/src/main/github/client/update/pr-reviewers.ts +++ b/src/main/github/client/update/pr-reviewers.ts @@ -1,6 +1,9 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { codeHostFailure } from '../../../../shared/code-host-failure-copy' function reviewerUpdateArgs( diff --git a/src/main/github/client/update/pr-state.ts b/src/main/github/client/update/pr-state.ts index 1253fa592b7..866502b9a75 100644 --- a/src/main/github/client/update/pr-state.ts +++ b/src/main/github/client/update/pr-state.ts @@ -1,13 +1,11 @@ -import { invalidateReviewLookupsAfterPRMutation } from '../../pr-mutation-review-invalidation' +import { invalidateReviewLookupsAfterPRMutation } from '../../pull-requests/refresh/pr-mutation-review-invalidation' import type { GitHubPullRequestStateUpdate } from '../../../../shared/issue-mutation-types' -import { - ghExecFileAsync, - acquire, - release, - classifyPullRequestUpdateError, - type LocalGitExecOptions -} from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import { classifyPullRequestUpdateError } from '../../api/gh-error-classification' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { codeHostFailure, classifiedFailure } from '../../../../shared/code-host-failure-copy' export async function updatePRState( repoPath: string, diff --git a/src/main/github/client/update/resolve-review-thread.ts b/src/main/github/client/update/resolve-review-thread.ts index ad55844c9ad..e1b75999fe7 100644 --- a/src/main/github/client/update/resolve-review-thread.ts +++ b/src/main/github/client/update/resolve-review-thread.ts @@ -1,6 +1,9 @@ -import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' export async function resolveReviewThread( repoPath: string, threadId: string, diff --git a/src/main/github/client-issue-source.test.ts b/src/main/github/client/work-item-source-selection.test.ts similarity index 69% rename from src/main/github/client-issue-source.test.ts rename to src/main/github/client/work-item-source-selection.test.ts index 9e157f3a097..be8fd94011e 100644 --- a/src/main/github/client-issue-source.test.ts +++ b/src/main/github/client/work-item-source-selection.test.ts @@ -1,9 +1,14 @@ +import type * as GithubLegacyCommandModule from '../execution/github-legacy-command' +import type * as GhExecFileModule from '../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubOwnerRepoSelectionModule from '../repositories/github-owner-repo-selection' +import type * as GithubRepositoryIdentityModule from '../repositories/github-repository-identity' +import type * as GithubOperationSlotsModule from '../execution/github-operation-slots' +import type * as GithubApiRepositoryModule from '../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../repositories/github-api-repository-remote-probe' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GhUtils from './gh-utils' // Keep legacy REST request/failure coverage; API-boundary suites exercise the GraphQL path. -vi.mock('./client/list/work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) +vi.mock('./list/work-item-search-page', () => ({ usesGraphqlWorkItemSearch: () => false })) const { execFileAsyncMock, @@ -31,23 +36,60 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', async () => { - const actual = await vi.importActual('./gh-utils') +vi.mock('../execution/github-legacy-command', async () => { + const actual = await vi.importActual( + '../execution/github-legacy-command' + ) + return { + ...actual, + execFileAsync: execFileAsyncMock + } +}) + +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async () => { + const actual = await vi.importActual( + '../../git/command-runner/hosted-cli/gh-exec-file' + ) + return { + ...actual, + ghExecFileAsync: ghExecFileAsyncMock + } +}) + +vi.mock('../repositories/github-owner-repo-selection', async () => { + const actual = await vi.importActual( + '../repositories/github-owner-repo-selection' + ) return { ...actual, - execFileAsync: execFileAsyncMock, - ghExecFileAsync: ghExecFileAsyncMock, getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: getIssueOwnerRepoMock, + resolveIssueSource: resolveIssueSourceMock + } +}) + +vi.mock('../repositories/github-repository-identity', async () => { + const actual = await vi.importActual( + '../repositories/github-repository-identity' + ) + return { + ...actual, getOwnerRepoForRemote: getOwnerRepoForRemoteMock, - resolveIssueSource: resolveIssueSourceMock, - acquire: acquireMock, - release: releaseMock, _resetOwnerRepoCache: vi.fn() } }) -vi.mock('./rate-limit', () => ({ +vi.mock('../execution/github-operation-slots', async () => { + const actual = await vi.importActual( + '../execution/github-operation-slots' + ) + return { + ...actual, + acquire: acquireMock, + release: releaseMock + } +}) + +vi.mock('../api/rate-limit', () => ({ rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: noteRateLimitSpendMock, getRateLimit: vi.fn(async () => ({ ok: false, error: 'not probed in tests' })), @@ -56,11 +98,9 @@ vi.mock('./rate-limit', () => ({ spendsSharedGitHubComQuota: () => true })) -vi.mock('./github-api-repository', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('../repositories/github-api-repository', async (importOriginal) => { return { - ...actual, - // Bridge hosted source resolution to the existing gh-utils fixtures. + ...(await importOriginal()), resolveIssueGitHubApiRepositorySource: ( repoPath: string, preference: unknown, @@ -69,6 +109,17 @@ vi.mock('./github-api-repository', async (importOriginal) => { ) => resolveIssueSourceMock(repoPath, preference, connectionId, localGitOptions), getIssueGitHubApiRepository: (repoPath: string, connectionId?: string | null) => getIssueOwnerRepoMock(repoPath, connectionId), + resolveGitHubApiRepositoryCandidates: ( + repoPath: string, + connectionId?: string | null, + localGitOptions?: unknown + ) => resolvePRRepositoryCandidatesMock(repoPath, connectionId, localGitOptions) + } +}) + +vi.mock('../repositories/github-api-repository-remote-probe', async (importOriginal) => { + return { + ...(await importOriginal()), getOriginGitHubApiRepository: ( repoPath: string, connectionId?: string | null, @@ -82,17 +133,12 @@ vi.mock('./github-api-repository', async (importOriginal) => { ) => remoteName === 'origin' ? getOwnerRepoMock(repoPath, connectionId, localGitOptions) - : getOwnerRepoForRemoteMock(repoPath, remoteName, connectionId, localGitOptions), - resolveGitHubApiRepositoryCandidates: ( - repoPath: string, - connectionId?: string | null, - localGitOptions?: unknown - ) => resolvePRRepositoryCandidatesMock(repoPath, connectionId, localGitOptions) + : getOwnerRepoForRemoteMock(repoPath, remoteName, connectionId, localGitOptions) } }) -import { getWorkItem } from './client/fetch/get-work-item' -import { listWorkItems } from './client/list/list-work-items' -import { _resetOwnerRepoCache } from './gh-utils' +import { getWorkItem } from './fetch/get-work-item' +import { listWorkItems } from './list/list-work-items' +import { _resetOwnerRepoCache } from '../repositories/github-repository-identity' // Share origin fixtures while letting candidate tests opt into upstream. function mockUpstreamCandidate(upstream: { owner: string; repo: string } | null): void { @@ -163,44 +209,6 @@ describe('GitHub issue source split', () => { expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2) }) - it('pins typed PR metadata to explicit origin when upstream has the same number', async () => { - const upstream = { owner: 'stablyai', repo: 'orca', host: 'github.com' } - const origin = { owner: 'fork', repo: 'orca', host: 'github.com' } - getOwnerRepoMock.mockResolvedValue(origin) - mockUpstreamCandidate(upstream) - resolvePRRepositoryCandidatesMock.mockResolvedValue({ - candidates: [upstream, origin], - headRepo: origin - }) - ghExecFileAsyncMock.mockResolvedValueOnce({ - stdout: JSON.stringify({ - number: 42, - title: 'Origin PR', - state: 'open', - url: 'https://github.com/fork/orca/pull/42', - labels: [], - updatedAt: '2026-04-02T00:00:00Z', - author: { login: 'octocat' }, - isDraft: false, - headRefName: 'origin/fix', - baseRefName: 'main' - }) - }) - - const item = await getWorkItem('/repo-root', 42, 'pr', null, {}, 'origin') - - expect(resolvePRRepositoryCandidatesMock).not.toHaveBeenCalled() - expect(ghExecFileAsyncMock.mock.calls[0]?.[0]).toEqual( - expect.arrayContaining(['pr', 'view', '--repo', 'fork/orca']) - ) - expect( - ghExecFileAsyncMock.mock.calls.some((call) => - (call[0] as string[]).some((arg) => arg.includes('upstream/orca')) - ) - ).toBe(false) - expect(item?.prRepo).toEqual(origin) - }) - it('does not run a bare PR lookup when explicit origin identity is unresolved', async () => { getOwnerRepoMock.mockResolvedValue(null) mockUpstreamCandidate({ owner: 'stablyai', repo: 'orca' }) @@ -219,29 +227,6 @@ describe('GitHub issue source split', () => { expect(ghExecFileAsyncMock).not.toHaveBeenCalled() }) - it('does not probe a second PR repository after a non-not-found failure', async () => { - resolvePRRepositoryCandidatesMock.mockResolvedValueOnce({ - candidates: [ - { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - { owner: 'fork', repo: 'orca', host: 'github.com' } - ], - headRepo: { owner: 'fork', repo: 'orca', host: 'github.com' } - }) - ghExecFileAsyncMock - .mockRejectedValueOnce(new Error('HTTP 500: server error')) - .mockRejectedValueOnce(new Error('HTTP 500: server error')) - - await expect(getWorkItem('/repo-root', 42, 'pr')).resolves.toBeNull() - - // Only a confirmed 404 permits trying a same-number PR on another repository. - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2) - expect( - ghExecFileAsyncMock.mock.calls.some(([args]) => - (args as string[]).some((arg) => arg.includes('fork/orca')) - ) - ).toBe(false) - }) - it('raw number lookup tries upstream issue before origin PR', async () => { getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) // Non-404 upstream failures must not route to an unrelated origin PR with the same number. @@ -310,15 +295,4 @@ describe('GitHub issue source split', () => { expect(result.items.map((i) => i.id)).toEqual(['pr:42']) expect(result.errors?.issues?.type).toBe('permission_denied') }) - - it('raw number lookup does not fall through on transient upstream errors', async () => { - getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) - ghExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 500: server error')) - - const item = await getWorkItem('/repo-root', 42) - - expect(item).toBeNull() - expect(getOwnerRepoMock).not.toHaveBeenCalled() - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1) - }) }) diff --git a/src/main/github/comment-reactions.ts b/src/main/github/comments/comment-reactions.ts similarity index 95% rename from src/main/github/comment-reactions.ts rename to src/main/github/comments/comment-reactions.ts index 89ee5ee8d6b..c2d38f10667 100644 --- a/src/main/github/comment-reactions.ts +++ b/src/main/github/comments/comment-reactions.ts @@ -1,4 +1,7 @@ -import type { GitHubReaction, GitHubReactionContent } from '../../shared/github/comment-types' +import type { + GitHubReaction, + GitHubReactionContent +} from '../../../shared/github/comments/comment-types' type GitHubGraphQLReactionContent = | 'THUMBS_UP' diff --git a/src/main/github/execution/github-legacy-command.ts b/src/main/github/execution/github-legacy-command.ts new file mode 100644 index 00000000000..531c59d0911 --- /dev/null +++ b/src/main/github/execution/github-legacy-command.ts @@ -0,0 +1,5 @@ +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' + +// Repo-scoped operations need the WSL-aware runner; this legacy wrapper serves generic commands. +export const execFileAsync = promisify(execFile) diff --git a/src/main/github/gh-utils-concurrency.test.ts b/src/main/github/execution/github-operation-slots.test.ts similarity index 90% rename from src/main/github/gh-utils-concurrency.test.ts rename to src/main/github/execution/github-operation-slots.test.ts index de6389d260a..8914024c27e 100644 --- a/src/main/github/gh-utils-concurrency.test.ts +++ b/src/main/github/execution/github-operation-slots.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { acquire, release } from './gh-utils' +import { acquire, release } from './github-operation-slots' describe('GitHub concurrency', () => { it('removes aborted waiters without consuming a permit', async () => { diff --git a/src/main/github/execution/github-operation-slots.ts b/src/main/github/execution/github-operation-slots.ts new file mode 100644 index 00000000000..af63b9cc983 --- /dev/null +++ b/src/main/github/execution/github-operation-slots.ts @@ -0,0 +1,54 @@ +const MAX_CONCURRENT = 4 + +let running = 0 + +type QueueEntry = { + signal?: AbortSignal + start: () => void + reject: (error: Error) => void +} + +const queue: QueueEntry[] = [] + +function githubOperationAbortError(): Error { + const error = new Error('GitHub operation aborted') + error.name = 'AbortError' + return error +} + +export function acquire(signal?: AbortSignal): Promise { + if (signal?.aborted) { + return Promise.reject(githubOperationAbortError()) + } + if (running < MAX_CONCURRENT) { + running += 1 + return Promise.resolve() + } + return new Promise((resolve, reject) => { + const entry: QueueEntry = { + signal, + reject, + start: () => { + signal?.removeEventListener('abort', onAbort) + running += 1 + resolve() + } + } + const onAbort = (): void => { + const index = queue.indexOf(entry) + if (index === -1) { + return + } + queue.splice(index, 1) + reject(githubOperationAbortError()) + } + signal?.addEventListener('abort', onAbort, { once: true }) + queue.push(entry) + }) +} + +export function release(): void { + running -= 1 + const next = queue.shift() + next?.start() +} diff --git a/src/main/github/gh-capability-state.test.ts b/src/main/github/gh-capability-state.test.ts deleted file mode 100644 index a3cd51ec396..00000000000 --- a/src/main/github/gh-capability-state.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { ghExecFileAsyncMock } = vi.hoisted(() => ({ - ghExecFileAsyncMock: vi.fn() -})) - -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - ghExecFileAsync: ghExecFileAsyncMock -})) - -import { - clearGhCapabilityStateForTests, - getGhMultiAccountCapability, - invalidateGhMultiAccountCapability -} from './gh-capability-state' - -describe('gh-capability-state', () => { - beforeEach(() => { - clearGhCapabilityStateForTests() - ghExecFileAsyncMock.mockReset() - }) - - afterEach(() => { - clearGhCapabilityStateForTests() - }) - - it('caches per runtime and invalidates on request', async () => { - ghExecFileAsyncMock.mockResolvedValue({ - stdout: 'Usage: gh auth token [--user login]', - stderr: '' - }) - await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' }) - await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' }) - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1) - - invalidateGhMultiAccountCapability({ wslDistro: 'Ubuntu' }) - await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' }) - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2) - }) -}) diff --git a/src/main/github/gh-utils.ts b/src/main/github/gh-utils.ts deleted file mode 100644 index 25776bc0346..00000000000 --- a/src/main/github/gh-utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { execFile } from 'node:child_process' -import { promisify } from 'node:util' -import { gitExecFileAsync } from '../git/command-runner/git-exec-file' -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -// Import pure parsing outside runner so runner-mocked tests retain the real implementation. -import { - extractExecError, - parseRetryAfterMs -} from '../git/command-runner/process-capture/exec-error' - -// Repo-scoped operations need the WSL-aware runner; this legacy wrapper serves generic commands. -export const execFileAsync = promisify(execFile) -export { ghExecFileAsync, gitExecFileAsync, extractExecError, parseRetryAfterMs } -export { - classifyGhError, - classifyListIssuesError, - classifyListPrsError, - classifyPullRequestUpdateError -} from './gh-error-classification' -export { - _getOwnerRepoCacheSize, - _resetOwnerRepoCache, - getOwnerRepoForRemote, - getRemoteUrlForRepo, - ghRepoExecOptions, - githubRepoContext, - parseGitHubOwnerRepo, - parseGitHubRemoteIdentity -} from './github-repository-identity' -export type { - GitHubRemoteIdentity, - GitHubRemoteIdentityProbeOptions, - GitHubRepoContext, - LocalGitExecOptions, - OwnerRepo -} from './github-repository-identity' -export { - getIssueOwnerRepo, - getOwnerRepo, - resolveIssueSource, - resolvePRRepositoryCandidates -} from './github-owner-repo-selection' -export type { PRRepositoryCandidates, ResolvedIssueSource } from './github-owner-repo-selection' - -const MAX_CONCURRENT = 4 -let running = 0 -type QueueEntry = { - signal?: AbortSignal - start: () => void - reject: (error: Error) => void -} -const queue: QueueEntry[] = [] - -function githubOperationAbortError(): Error { - const error = new Error('GitHub operation aborted') - error.name = 'AbortError' - return error -} - -export function acquire(signal?: AbortSignal): Promise { - if (signal?.aborted) { - return Promise.reject(githubOperationAbortError()) - } - if (running < MAX_CONCURRENT) { - running += 1 - return Promise.resolve() - } - return new Promise((resolve, reject) => { - const entry: QueueEntry = { - signal, - reject, - start: () => { - signal?.removeEventListener('abort', onAbort) - running += 1 - resolve() - } - } - const onAbort = (): void => { - const index = queue.indexOf(entry) - if (index === -1) { - return - } - queue.splice(index, 1) - reject(githubOperationAbortError()) - } - signal?.addEventListener('abort', onAbort, { once: true }) - queue.push(entry) - }) -} - -export function release(): void { - running -= 1 - const next = queue.shift() - next?.start() -} diff --git a/src/main/github/github-read-execution-scope.ts b/src/main/github/github-read-execution-scope.ts deleted file mode 100644 index 0731cf4ed48..00000000000 --- a/src/main/github/github-read-execution-scope.ts +++ /dev/null @@ -1 +0,0 @@ -export { codeHostReadExecutionScope as githubReadExecutionScope } from '../source-control/code-host-read-execution-scope' diff --git a/src/main/github/issue-comment.ts b/src/main/github/issues/issue-comment.ts similarity index 73% rename from src/main/github/issue-comment.ts rename to src/main/github/issues/issue-comment.ts index 7f3c2a11f98..4e3fa1d9a2b 100644 --- a/src/main/github/issue-comment.ts +++ b/src/main/github/issues/issue-comment.ts @@ -1,13 +1,16 @@ -import { githubBodyImageUrls, githubBodyMediaHeaders } from './body-image-urls' -import type { GitHubCommentResult, PRComment } from '../../shared/github/comment-types' -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { LocalGitExecOptions, OwnerRepo } from './gh-utils' +import { githubBodyImageUrls, githubBodyMediaHeaders } from '../attachments/body-image-urls' +import type { GitHubCommentResult, PRComment } from '../../../shared/github/comments/comment-types' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../shared/github/repositories/repository-identity' import { resolveGitHubRepoExecution, resolveIssueGitHubApiRepositorySource -} from './github-api-repository' -import { acquire, classifyGhError, ghExecFileAsync, release } from './gh-utils' -import { codeHostFailure, classifiedFailure } from '../../shared/code-host-failure-copy' +} from '../repositories/github-api-repository' +import { acquire, release } from '../execution/github-operation-slots' +import { classifyGhError } from '../api/gh-error-classification' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostFailure, classifiedFailure } from '../../../shared/code-host-failure-copy' // An explicit target keeps replies bound to the conversation; otherwise use the selected source. export async function addIssueComment( diff --git a/src/main/github/issue-create.ts b/src/main/github/issues/issue-create.ts similarity index 86% rename from src/main/github/issue-create.ts rename to src/main/github/issues/issue-create.ts index 54f5bb74ac3..0efb38f3704 100644 --- a/src/main/github/issue-create.ts +++ b/src/main/github/issues/issue-create.ts @@ -1,15 +1,17 @@ import type { GitHubCreateIssueFields, GitHubCreateIssueResult -} from '../../shared/issue-mutation-types' -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { LocalGitExecOptions } from './gh-utils' +} from '../../../shared/issue-mutation-types' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' import { resolveGitHubRepoExecution, resolveIssueGitHubApiRepositorySource -} from './github-api-repository' -import { acquire, extractExecError, ghExecFileAsync, release } from './gh-utils' -import { codeHostFailure } from '../../shared/code-host-failure-copy' +} from '../repositories/github-api-repository' +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostFailure } from '../../../shared/code-host-failure-copy' function githubIssueErrorMessage(error: unknown): string { const { stderr, stdout } = extractExecError(error) diff --git a/src/main/github/issue-details-query.ts b/src/main/github/issues/issue-details-query.ts similarity index 96% rename from src/main/github/issue-details-query.ts rename to src/main/github/issues/issue-details-query.ts index 5d6921884f0..c63c42c5c3c 100644 --- a/src/main/github/issue-details-query.ts +++ b/src/main/github/issues/issue-details-query.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import type { GitHubAssignableUser } from '../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' export const ISSUE_DETAILS_QUERY = `query($owner: String!, $repo: String!, $number: Int!, $includeItem: Boolean!) { repository(owner: $owner, name: $repo) { diff --git a/src/main/github/issue-field-options.ts b/src/main/github/issues/issue-field-options.ts similarity index 81% rename from src/main/github/issue-field-options.ts rename to src/main/github/issues/issue-field-options.ts index 8e54a8a2759..5e5c362c14b 100644 --- a/src/main/github/issue-field-options.ts +++ b/src/main/github/issues/issue-field-options.ts @@ -1,12 +1,14 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' -import type { GitHubAssignableUser, GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { LocalGitExecOptions } from './gh-utils' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' import { resolveGitHubRepoExecution, resolveIssueGitHubApiRepositorySource -} from './github-api-repository' -import { acquire, ghExecFileAsync, release } from './gh-utils' +} from '../repositories/github-api-repository' +import { acquire, release } from '../execution/github-operation-slots' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' export async function listLabels( repoPath: string, diff --git a/src/main/github/issue-timeline.ts b/src/main/github/issues/issue-timeline.ts similarity index 92% rename from src/main/github/issue-timeline.ts rename to src/main/github/issues/issue-timeline.ts index 09e577fdcc2..42214855a51 100644 --- a/src/main/github/issue-timeline.ts +++ b/src/main/github/issues/issue-timeline.ts @@ -1,11 +1,12 @@ import type { GitHubIssueTimelineItem, GitHubIssueTimelineTarget -} from '../../shared/github/comment-types' -import { ghExecFileAsync } from './gh-utils' -import type { GitHubApiRepository, GitHubRepoExecOptions } from './github-api-repository' -import { githubHostExecOptions } from './github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +} from '../../../shared/github/comments/comment-types' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../repositories/github-api-repository' +import { githubHostExecOptions } from '../repositories/github-repository-host' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' const MAX_ISSUE_TIMELINE_ITEMS = 300 const GITHUB_REST_PAGE_SIZE = 100 diff --git a/src/main/github/issue-update.ts b/src/main/github/issues/issue-update.ts similarity index 82% rename from src/main/github/issue-update.ts rename to src/main/github/issues/issue-update.ts index 4a5fbe0bb63..7f5deb97994 100644 --- a/src/main/github/issue-update.ts +++ b/src/main/github/issues/issue-update.ts @@ -1,15 +1,17 @@ -import type { GitHubIssueUpdate } from '../../shared/issue-mutation-types' -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { LocalGitExecOptions } from './gh-utils' +import type { GitHubIssueUpdate } from '../../../shared/issue-mutation-types' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' import { resolveGitHubRepoExecution, - resolveIssueGitHubApiRepositorySource, - type GitHubApiRepository -} from './github-api-repository' -import { acquire, classifyGhError, ghExecFileAsync, release } from './gh-utils' -import { codeHostFailure, combinedClassifiedFailure } from '../../shared/code-host-failure-copy' -import type { ClassifiedError } from '../../shared/classified-error' -import { githubBodyImageUrls, githubBodyMediaHeaders } from './body-image-urls' + resolveIssueGitHubApiRepositorySource +} from '../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import { acquire, release } from '../execution/github-operation-slots' +import { classifyGhError } from '../api/gh-error-classification' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostFailure, combinedClassifiedFailure } from '../../../shared/code-host-failure-copy' +import type { ClassifiedError } from '../../../shared/classified-error' +import { githubBodyImageUrls, githubBodyMediaHeaders } from '../attachments/body-image-urls' export async function updateIssue( repoPath: string, diff --git a/src/main/github/issue-work-item-details-fresh.test.ts b/src/main/github/issues/issue-work-item-details-fresh.test.ts similarity index 89% rename from src/main/github/issue-work-item-details-fresh.test.ts rename to src/main/github/issues/issue-work-item-details-fresh.test.ts index 8d735c87693..6a66462e75d 100644 --- a/src/main/github/issue-work-item-details-fresh.test.ts +++ b/src/main/github/issues/issue-work-item-details-fresh.test.ts @@ -1,11 +1,16 @@ +import type * as GithubRepositoryIdentityModule from '../repositories/github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ exec: vi.fn(), guard: vi.fn() })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: mocks.exec, +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: mocks.exec +})) + +vi.mock('../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), ghRepoExecOptions: () => ({}), githubRepoContext: () => ({}) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ repositoryRateLimitGuard: mocks.guard, noteRepositoryRateLimitSpend: vi.fn() })) @@ -66,33 +71,7 @@ describe('explicit task refresh conversation reads', () => { ['me', true] ]) }) - it.each(['body', 'comments', 'timeline'] as const)( - 'fails the whole explicit read if required %s fails', - async (field) => { - mocks.exec.mockImplementation(async (args: string[]) => { - const route = args.find((arg) => arg.startsWith('repos/')) ?? '' - const actual = route.includes('/comments?') - ? 'comments' - : route.includes('/timeline?') - ? 'timeline' - : 'body' - if (actual === field) { - throw new Error('request failed') - } - return { - stdout: - actual === 'body' - ? JSON.stringify({ body: 'current' }) - : actual === 'comments' - ? '[]' - : '' - } - }) - await expect( - getIssueBodyAndComments('/repo', 42, repository, null, {}, true) - ).rejects.toThrow('request failed') - } - ) + it('retains ordinary fallback behavior but does not report an explicit rate-limited read as success', async () => { mocks.guard.mockReturnValue({ blocked: true }) expect(await getIssueBodyAndComments('/repo', 42, repository)).toEqual({ diff --git a/src/main/github/issue-work-item-details.ts b/src/main/github/issues/issue-work-item-details.ts similarity index 89% rename from src/main/github/issue-work-item-details.ts rename to src/main/github/issues/issue-work-item-details.ts index 677c628d562..8d843126e60 100644 --- a/src/main/github/issue-work-item-details.ts +++ b/src/main/github/issues/issue-work-item-details.ts @@ -2,30 +2,34 @@ import { GITHUB_BODY_MEDIA_TYPE, githubBodyImageUrls, removeFailedGitHubBodyHtml -} from './body-image-urls' -import { extractExecError } from '../git/command-runner/process-capture/exec-error' -import type { GitHubIssueTimelineItem, PRComment } from '../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../shared/github/pull-request-types' +} from '../attachments/body-image-urls' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import type { + GitHubIssueTimelineItem, + PRComment +} from '../../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' import { ISSUE_DETAILS_QUERY, issueItemSchema, type GraphQLIssueDetailsResponse } from './issue-details-query' -import { mapIssueWorkItem } from './client/map/work-item' -import { restIssueActor } from './client/map/issue-actor' -import type { MainWorkItem } from './client/map/work-item-field-coercion' +import { mapIssueWorkItem } from '../client/map/work-item' +import { restIssueActor } from '../client/map/issue-actor' +import type { MainWorkItem } from '../client/map/work-item-field-coercion' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' import { - ghExecFileAsync, ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions -} from './gh-utils' +} from '../repositories/github-repository-identity' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' +import { githubHostExecOptions } from '../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' import { getIssueTimelineItems } from './issue-timeline' -import { readViewerLogin } from './client/fetch/authenticated-viewer' -import { viewerAuthorship } from '../source-control/comment-viewer-authorship' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +import { readViewerLogin } from '../client/fetch/authenticated-viewer' +import { viewerAuthorship } from '../../source-control/comment-viewer-authorship' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' export type CollapsedIssueDetails = { item?: MainWorkItem diff --git a/src/main/github/issues.test.ts b/src/main/github/issues/issues.test.ts similarity index 54% rename from src/main/github/issues.test.ts rename to src/main/github/issues/issues.test.ts index 3beaf260cd7..ecb17adb769 100644 --- a/src/main/github/issues.test.ts +++ b/src/main/github/issues/issues.test.ts @@ -1,7 +1,9 @@ +import type * as GhExecFileModule from '../../git/command-runner/hosted-cli/gh-exec-file' +import type * as GithubOwnerRepoSelectionModule from '../repositories/github-owner-repo-selection' +import type * as GithubOperationSlotsModule from '../execution/github-operation-slots' +import type * as GithubEnterpriseRepositoryModule from '../repositories/github-enterprise-repository' +import type * as GithubApiRepositoryModule from '../repositories/github-api-repository' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GithubEnterpriseRepositoryModule from './github-enterprise-repository' -import type * as GhUtils from './gh-utils' const { ghExecFileAsyncMock, @@ -17,30 +19,49 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', async () => { - const actual = await vi.importActual('./gh-utils') +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async () => { + const actual = await vi.importActual( + '../../git/command-runner/hosted-cli/gh-exec-file' + ) + return { + ...actual, + ghExecFileAsync: ghExecFileAsyncMock + } +}) + +vi.mock('../repositories/github-owner-repo-selection', async () => { + const actual = await vi.importActual( + '../repositories/github-owner-repo-selection' + ) + return { + ...actual, + getOwnerRepo: getIssueOwnerRepoMock, + resolveIssueSource: resolveIssueSourceMock + } +}) + +vi.mock('../execution/github-operation-slots', async () => { + const actual = await vi.importActual( + '../execution/github-operation-slots' + ) return { ...actual, - ghExecFileAsync: ghExecFileAsyncMock, - getIssueOwnerRepo: getIssueOwnerRepoMock, - resolveIssueSource: resolveIssueSourceMock, acquire: acquireMock, release: releaseMock } }) -vi.mock('./github-enterprise-repository', async (importOriginal) => ({ +vi.mock('../repositories/github-enterprise-repository', async (importOriginal) => ({ ...(await importOriginal()), isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -vi.mock('./github-api-repository', async (importOriginal) => { +vi.mock('../repositories/github-api-repository', async (importOriginal) => { const actual = await importOriginal() const withDotComHost = (repo: T) => repo ? { host: 'github.com' as const, ...repo } : repo return { ...actual, - // Bridge hosted source resolution to the suite fixtures and retain explicit github.com host pinning. getIssueGitHubApiRepository: async ( repoPath: string, connectionId?: string | null, @@ -68,7 +89,7 @@ vi.mock('./github-api-repository', async (importOriginal) => { import { createIssue } from './issue-create' import { updateIssue } from './issue-update' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../repositories/github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() @@ -89,98 +110,6 @@ describe('issue source operations', () => { })) }) - it.each(['stderr', 'stdout'])('recovers a long issue body reported on %s', async (stream) => { - const imagePrefix = 'data:image/png;base64,' - const body = imagePrefix + 'x'.repeat(133596 - imagePrefix.length) - expect(body).toContain('data:image') - expect(body).toHaveLength(133596) - - getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) - ghExecFileAsyncMock - .mockRejectedValueOnce( - Object.assign(new Error('gh failed'), { - stderr: 'gh: Validation Failed (HTTP 422)', - [stream]: 'body is too long (maximum is 65536 characters)' - }) - ) - .mockResolvedValueOnce({ - stdout: JSON.stringify({ - number: 926, - html_url: 'https://github.com/stablyai/orca/issues/926' - }) - }) - .mockResolvedValueOnce({ stdout: '' }) - - await expect( - createIssue('/repo-root', 'Image issue', body, undefined, undefined, { - labels: ['bug'], - assignees: ['octo'] - }) - ).resolves.toEqual({ - ok: true, - number: 926, - url: 'https://github.com/stablyai/orca/issues/926' - }) - expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith( - 1, - ['api', '-X', 'POST', 'repos/stablyai/orca/issues', '--input', '-'], - { - cwd: '/repo-root', - host: 'github.com', - stdin: JSON.stringify({ - title: 'Image issue', - body, - labels: ['bug'], - assignees: ['octo'] - }) - } - ) - expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith( - 2, - ['api', '-X', 'POST', 'repos/stablyai/orca/issues', '--input', '-'], - { - cwd: '/repo-root', - host: 'github.com', - stdin: JSON.stringify({ - title: 'Image issue', - body: '', - labels: ['bug'], - assignees: ['octo'] - }) - } - ) - expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith( - 3, - ['api', '-X', 'PATCH', 'repos/stablyai/orca/issues/926', '--input', '-'], - { cwd: '/repo-root', host: 'github.com', stdin: JSON.stringify({ body }) } - ) - }) - - it('does not retry unrelated create failures', async () => { - getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) - ghExecFileAsyncMock.mockRejectedValueOnce(new Error('HTTP 422: assignees is invalid')) - - await expect(createIssue('/repo-root', 'Invalid issue', 'Body')).resolves.toEqual({ - ok: false, - error: 'HTTP 422: assignees is invalid' - }) - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1) - }) - - it('stops when placeholder create fails during oversized-body recovery', async () => { - const body = `data:image/png;base64,${'x'.repeat(100)}` - getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) - ghExecFileAsyncMock - .mockRejectedValueOnce(new Error('body is too long (maximum is 65536 characters)')) - .mockRejectedValueOnce(new Error('HTTP 500: create failed')) - - await expect(createIssue('/repo-root', 'Placeholder failure', body)).resolves.toEqual({ - ok: false, - error: 'HTTP 500: create failed' - }) - expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2) - }) - it('reports partial success when oversized-body patch fails', async () => { getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) ghExecFileAsyncMock @@ -202,20 +131,6 @@ describe('issue source operations', () => { }) }) - it('sends large issue bodies over stdin through the REST issue endpoint', async () => { - getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' }) - ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '' }) - const body = `Screenshot:\ndata:image/png;base64,${'A'.repeat(40_000)}` - - await expect(updateIssue('/repo-root', 924, { body })).resolves.toEqual({ - ok: true - }) - expect(ghExecFileAsyncMock).toHaveBeenCalledWith( - ['api', '-X', 'PATCH', 'repos/stablyai/orca/issues/924', '--input', '-'], - { cwd: '/repo-root', host: 'github.com', stdin: JSON.stringify({ body }) } - ) - }) - it('keeps the accepted issue body representation through a compound metadata edit', async () => { const body = '![new](https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488)' diff --git a/src/main/github/issues.ts b/src/main/github/issues/issues.ts similarity index 85% rename from src/main/github/issues.ts rename to src/main/github/issues/issues.ts index 02c6ead5c5e..41e7c553450 100644 --- a/src/main/github/issues.ts +++ b/src/main/github/issues/issues.ts @@ -1,15 +1,17 @@ -import type { ClassifiedError } from '../../shared/classified-error' -import type { IssueInfo } from '../../shared/github/pull-request-types' -import type { IssueSourcePreference } from '../../shared/repo-types' -import { mapIssueInfo } from './mappers' -import type { LocalGitExecOptions } from './gh-utils' +import type { ClassifiedError } from '../../../shared/classified-error' +import type { IssueInfo } from '../../../shared/github/pull-requests/pull-request-types' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import { mapIssueInfo } from '../api/github-read-response' +import type { LocalGitExecOptions } from '../repositories/github-repository-identity' import { getIssueGitHubApiRepository, resolveGitHubRepoExecution, resolveIssueGitHubApiRepositorySource -} from './github-api-repository' +} from '../repositories/github-api-repository' // prettier-ignore -import { ghExecFileAsync, acquire, release, classifyListIssuesError } from './gh-utils' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../execution/github-operation-slots' +import { classifyListIssuesError } from '../api/gh-error-classification' // Keep successful-empty and failed listings distinguishable; fallback toasts belong to the Tasks envelope. export type IssueListResult = { diff --git a/src/main/github/pr-refresh-coordinator-completion-order.test.ts b/src/main/github/pr-refresh-coordinator-completion-order.test.ts deleted file mode 100644 index 2eea735fbe3..00000000000 --- a/src/main/github/pr-refresh-coordinator-completion-order.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRRefreshOutcome } from '../../shared/github/pull-request-refresh-types' -import { runCoalescedProbe, type CoalescedProbes } from '../git/remotes/coalesced-probe' -import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' - -const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') - return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } -}) -vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) - -const { getPRForBranchOutcomeMock, getOriginGitHubApiRepositoryMock, sendMock } = coordinatorMocks - -function found(state: 'open' | 'closed' | 'merged'): PRRefreshOutcome { - return { kind: 'found', pr: makePR({ state, checksStatus: 'success' }), fetchedAt: Date.now() } -} - -describe('main refresh completion ordering', () => { - beforeEach(() => moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks)) - afterEach(() => vi.useRealTimers()) - - it.each([ - { older: 'open', newer: 'merged' }, - { older: 'closed', newer: 'open' } - ] as const)( - 'ignores expired coalesced $older reads after a newer $newer result', - async ({ older, newer }) => { - const reads: CoalescedProbes = new Map() - let finishOlder: (outcome: PRRefreshOutcome) => void = () => {} - const provider = vi.fn<() => Promise>() - provider - .mockImplementationOnce( - () => new Promise((resolve) => (finishOlder = resolve)) - ) - .mockImplementation(async () => found(newer)) - getPRForBranchOutcomeMock.mockImplementation(() => - runCoalescedProbe(reads, 'same-provider-lookup', provider, 120_000) - ) - const { - reportVisiblePRRefreshCandidates, - refreshPRNow, - addPRRefreshOutcomeObserver, - _getPRRefreshQueueSizeForTests - } = await import('./pr-refresh-coordinator') - const observer = vi.fn() - addPRRefreshOutcomeObserver(observer) - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - await vi.advanceTimersByTimeAsync(120_000) - await refreshPRNow(candidate) - expect(provider).toHaveBeenCalledTimes(2) - await vi.advanceTimersByTimeAsync(1_000) - finishOlder(found(older)) - await vi.advanceTimersByTimeAsync(0) - expect(observer).toHaveBeenCalledTimes(1) - expect(observer.mock.calls[0]?.[1]).toMatchObject({ pr: { state: newer } }) - expect(_getPRRefreshQueueSizeForTests()).toBe(newer === 'merged' ? 0 : 1) - await vi.advanceTimersByTimeAsync(59_000) - expect(provider).toHaveBeenCalledTimes(newer === 'merged' ? 2 : 3) - if (newer === 'merged') { - await vi.advanceTimersByTimeAsync(900_000) - expect(provider).toHaveBeenCalledTimes(2) - } - } - ) - - it.each(['network', 'rate_limited'] as const)( - 'ignores a late %s error after a newer settled result', - async (errorType) => { - let finishOlder: (outcome: PRRefreshOutcome) => void = () => {} - getPRForBranchOutcomeMock - .mockImplementationOnce( - () => new Promise((resolve) => (finishOlder = resolve)) - ) - .mockImplementation(async () => found('merged')) - const { - reportVisiblePRRefreshCandidates, - refreshPRNow, - _getPRRefreshErrorBackoffCountForTests, - _getPRRefreshQueueSizeForTests - } = await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - await refreshPRNow(candidate) - finishOlder({ - kind: 'upstream-error', - errorType, - message: 'old failure', - fetchedAt: Date.now(), - retryDisabledUntil: Date.now() + 300_000 - }) - await vi.advanceTimersByTimeAsync(0) - expect(_getPRRefreshErrorBackoffCountForTests()).toBe(0) - expect(_getPRRefreshQueueSizeForTests()).toBe(0) - await refreshPRNow(candidate) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(3) - } - ) - - it('fences older direct refresh completions without suppressing their caller result', async () => { - let finishOlder: (outcome: PRRefreshOutcome) => void = () => {} - getPRForBranchOutcomeMock - .mockImplementationOnce( - () => new Promise((resolve) => (finishOlder = resolve)) - ) - .mockImplementation(async () => found('merged')) - const { refreshPRNow, addPRRefreshOutcomeObserver } = await import('./pr-refresh-coordinator') - const observer = vi.fn() - addPRRefreshOutcomeObserver(observer) - const candidate = makeCandidate() - const older = refreshPRNow(candidate) - await vi.advanceTimersByTimeAsync(0) - await refreshPRNow(candidate) - finishOlder(found('closed')) - await expect(older).resolves.toMatchObject({ pr: { state: 'closed' } }) - expect(observer).toHaveBeenCalledTimes(1) - expect(sendMock.mock.calls.filter(([, event]) => event.outcome)).toHaveLength(2) - }) - - it('preserves a newer failure gate when an older successful lookup completes', async () => { - let finishOlder: (outcome: PRRefreshOutcome) => void = () => {} - getPRForBranchOutcomeMock - .mockImplementationOnce( - () => new Promise((resolve) => (finishOlder = resolve)) - ) - .mockImplementation(async () => ({ - kind: 'upstream-error', - errorType: 'rate_limited', - message: 'wait', - fetchedAt: Date.now(), - retryDisabledUntil: Date.now() + 300_000 - })) - const { reportVisiblePRRefreshCandidates, refreshPRNow } = - await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - await refreshPRNow(candidate) - finishOlder(found('open')) - await vi.advanceTimersByTimeAsync(60_000) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - await expect(refreshPRNow(candidate)).resolves.toMatchObject({ errorType: 'rate_limited' }) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - }) - - it('keeps a shared provider read coalesced while only the newest caller adopts it', async () => { - const reads: CoalescedProbes = new Map() - let finish: (outcome: PRRefreshOutcome) => void = () => {} - const provider = vi.fn(() => new Promise((resolve) => (finish = resolve))) - getPRForBranchOutcomeMock.mockImplementation(() => - runCoalescedProbe(reads, 'same-provider-lookup', provider, 120_000) - ) - const { reportVisiblePRRefreshCandidates, refreshPRNow, addPRRefreshOutcomeObserver } = - await import('./pr-refresh-coordinator') - const observer = vi.fn() - addPRRefreshOutcomeObserver(observer) - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - const manual = refreshPRNow(candidate) - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - expect(provider).toHaveBeenCalledTimes(1) - finish(found('merged')) - await manual - await vi.advanceTimersByTimeAsync(900_000) - expect(observer).toHaveBeenCalledTimes(1) - expect(provider).toHaveBeenCalledTimes(1) - }) - - it('does not start an older queued read when admission finishes after a newer direct read', async () => { - let finishAdmission: () => void = () => {} - getOriginGitHubApiRepositoryMock.mockImplementationOnce( - () => - new Promise((resolve) => { - finishAdmission = () => resolve(null) - }) - ) - getPRForBranchOutcomeMock.mockImplementation(async () => found('merged')) - const { reportVisiblePRRefreshCandidates, refreshPRNow } = - await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - await refreshPRNow(candidate) - finishAdmission() - await vi.advanceTimersByTimeAsync(900_000) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts b/src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts deleted file mode 100644 index d6a4dc88c29..00000000000 --- a/src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') - return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } -}) - -vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) - -import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' - -const { getPRForBranchOutcomeMock } = coordinatorMocks - -describe('foreground refresh pacing', () => { - beforeEach(() => { - moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks) - getPRForBranchOutcomeMock.mockImplementation(async () => ({ - kind: 'found', - pr: makePR({ checksStatus: 'success' }), - fetchedAt: Date.now() - })) - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('limits five selected admissions to three starts until 30 seconds, including deselected queued rows', async () => { - const { enqueuePRRefresh, reportVisiblePRRefreshCandidates } = - await import('./pr-refresh-coordinator') - const cards = Array.from({ length: 5 }, (_, index) => - makeCandidate({ - cacheKey: `/repo::feature/${index}`, - branch: `feature/${index}`, - worktreeId: `wt-${index}`, - cachedPRState: 'open', - cachedChecksStatus: 'success', - cachedHasPR: true, - cachedFetchedAt: Date.now() - }) - ) - - for (let index = 0; index < cards.length; index += 1) { - const candidates = cards.map((card, cardIndex) => ({ - ...card, - cachedFetchedAt: cardIndex === index ? null : card.cachedFetchedAt, - isSelected: cardIndex === index - })) - reportVisiblePRRefreshCandidates(candidates, index + 1, 1) - enqueuePRRefresh(candidates[index], 'visible', 80, 1) - await vi.advanceTimersByTimeAsync(0) - } - - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(3) - await vi.advanceTimersByTimeAsync(29_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(3) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock.mock.calls.map((call) => call[1])).toEqual([ - 'feature/0', - 'feature/1', - 'feature/2', - 'feature/4', - 'feature/3' - ]) - }) - - it('uses the list budget for ordinary priority-80 periodic follow-ups', async () => { - const { enqueuePRRefresh, reportVisiblePRRefreshCandidates } = - await import('./pr-refresh-coordinator') - coordinatorMocks.getAllWebContentsMock.mockReturnValue( - Array.from({ length: 5 }, (_, index) => ({ id: index + 1, isDestroyed: () => false })) - ) - const candidates = Array.from({ length: 5 }, (_, index) => - makeCandidate({ - cacheKey: `/repo::feature/${index}`, - branch: `feature/${index}`, - worktreeId: `wt-${index}`, - isSelected: true - }) - ) - // Independent windows can expose different selected rows in the same runtime. - for (const [index, candidate] of candidates.entries()) { - reportVisiblePRRefreshCandidates([candidate], 1, index + 1) - enqueuePRRefresh(candidate, 'visible', 80, index + 1) - } - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(5) - - await vi.advanceTimersByTimeAsync(60_000) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(6) - await vi.advanceTimersByTimeAsync(9_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(6) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(7) - }) - - it('keeps the explicit manual mergeability follow-up outside the foreground burst budget', async () => { - const { enqueuePRRefresh, refreshPRNow, reportVisiblePRRefreshCandidates } = - await import('./pr-refresh-coordinator') - for (let index = 0; index < 3; index += 1) { - enqueuePRRefresh( - makeCandidate({ branch: `active/${index}`, cacheKey: `/repo::active/${index}` }), - 'active', - 80, - 1 - ) - } - await vi.advanceTimersByTimeAsync(0) - const candidate = makeCandidate({ isSelected: true }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - getPRForBranchOutcomeMock.mockResolvedValue({ - kind: 'found', - pr: makePR({ mergeable: 'UNKNOWN' }), - fetchedAt: Date.now() - }) - await refreshPRNow(candidate) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(4) - await vi.advanceTimersByTimeAsync(2_500) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(5) - }) -}) diff --git a/src/main/github/pr-refresh-coordinator-refresh-events.test.ts b/src/main/github/pr-refresh-coordinator-refresh-events.test.ts deleted file mode 100644 index 175cc151a10..00000000000 --- a/src/main/github/pr-refresh-coordinator-refresh-events.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') - return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } -}) - -vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) - -import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' - -const { sendMock, getPRForBranchOutcomeMock } = coordinatorMocks - -describe('pr-refresh-coordinator', () => { - beforeEach(() => { - moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks) - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('ignores a stale visibility generation before it can replace queued work', async () => { - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - getPRForBranchOutcomeMock.mockResolvedValue({ kind: 'no-pr', fetchedAt: Date.now() }) - - reportVisiblePRRefreshCandidates( - [makeCandidate({ branch: 'feature/current', cacheKey: '/repo::feature/current' })], - 2, - 1 - ) - reportVisiblePRRefreshCandidates( - [makeCandidate({ branch: 'feature/stale', cacheKey: '/repo::feature/stale' })], - 1, - 1 - ) - await vi.advanceTimersByTimeAsync(0) - - expect(getPRForBranchOutcomeMock).toHaveBeenCalledOnce() - expect(getPRForBranchOutcomeMock.mock.calls[0]?.[1]).toBe('feature/current') - }) - - it('keeps each manual outcome on its request sequence when completions race', async () => { - const { refreshPRNow } = await import('./pr-refresh-coordinator') - let resolveFirst!: (outcome: ReturnType) => void - let resolveSecond!: (outcome: ReturnType) => void - const first = new Promise>((resolve) => { - resolveFirst = resolve - }) - const second = new Promise>((resolve) => { - resolveSecond = resolve - }) - getPRForBranchOutcomeMock - .mockImplementationOnce(async () => ({ - kind: 'found', - pr: await first, - fetchedAt: Date.now() - })) - .mockImplementationOnce(async () => ({ - kind: 'found', - pr: await second, - fetchedAt: Date.now() - })) - - const firstRefresh = refreshPRNow(makeCandidate()) - const secondRefresh = refreshPRNow(makeCandidate()) - await vi.advanceTimersByTimeAsync(0) - resolveSecond(makePR({ number: 22 })) - await secondRefresh - resolveFirst(makePR({ number: 11 })) - await firstRefresh - - const events = sendMock.mock.calls.map(([, event]) => event) - const inFlight = events.filter((event) => event.status === 'in-flight') - const outcomes = events.filter((event) => event.outcome) - const firstOutcome = outcomes.find((event) => event.outcome.pr?.number === 11) - const secondOutcome = outcomes.find((event) => event.outcome.pr?.number === 22) - expect(firstOutcome?.sequence).toBe(inFlight[0]?.sequence) - expect(secondOutcome?.sequence).toBe(inFlight[1]?.sequence) - expect(firstOutcome?.sequence).toBeLessThan(secondOutcome?.sequence) - }) -}) diff --git a/src/main/github/pr-refresh-coordinator-visibility-policy.test.ts b/src/main/github/pr-refresh-coordinator-visibility-policy.test.ts deleted file mode 100644 index b0ddb66b70e..00000000000 --- a/src/main/github/pr-refresh-coordinator-visibility-policy.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRRefreshOutcome } from '../../shared/github/pull-request-refresh-types' - -const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') - return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } -}) -vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) - -import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' -const { getPRForBranchOutcomeMock, sendMock } = coordinatorMocks -function found(state: 'open' | 'merged' = 'open'): PRRefreshOutcome { - return { kind: 'found', pr: makePR({ state, checksStatus: 'success' }), fetchedAt: Date.now() } -} - -describe('visibility-aware coordinator scheduling', () => { - beforeEach(() => { - moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks) - getPRForBranchOutcomeMock.mockImplementation(async () => found()) - }) - afterEach(() => { - vi.useRealTimers() - }) - - it('does not resurrect periodic work after every window hides during a lookup', async () => { - let resolve: (outcome: PRRefreshOutcome) => void = () => {} - getPRForBranchOutcomeMock.mockImplementationOnce( - () => - new Promise((done) => { - resolve = done - }) - ) - const { reportVisiblePRRefreshCandidates, _getPRRefreshQueueSizeForTests } = - await import('./pr-refresh-coordinator') - reportVisiblePRRefreshCandidates([makeCandidate()], 1, 1) - await vi.advanceTimersByTimeAsync(0) - reportVisiblePRRefreshCandidates([], 2, 1) - resolve(found()) - await vi.advanceTimersByTimeAsync(600_000) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - expect(_getPRRefreshQueueSizeForTests()).toBe(0) - }) - - it('retains stale PR state and error backoff across reports and hiding', async () => { - getPRForBranchOutcomeMock.mockImplementation(async () => ({ - kind: 'upstream-error', - errorType: 'network', - message: 'offline', - fetchedAt: Date.now() - })) - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ - cachedHasPR: true, - cachedPRState: 'open', - cachedHeadOid: 'old', - currentHeadOid: 'new' - }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - reportVisiblePRRefreshCandidates([], 2, 1) - reportVisiblePRRefreshCandidates([candidate], 3, 1) - reportVisiblePRRefreshCandidates([{ ...candidate, isSelected: true }], 4, 1) - await vi.advanceTimersByTimeAsync(119_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - reportVisiblePRRefreshCandidates([candidate], 5, 1) - await vi.advanceTimersByTimeAsync(119_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(3) - expect(sendMock.mock.calls.map(([, event]) => event.outcome?.kind).filter(Boolean)).toEqual([ - 'upstream-error', - 'upstream-error', - 'upstream-error' - ]) - }) - - it('keeps an in-flight head change behind the lookup failure backoff', async () => { - let resolve: (outcome: PRRefreshOutcome) => void = () => {} - getPRForBranchOutcomeMock.mockImplementationOnce( - () => - new Promise((done) => { - resolve = done - }) - ) - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const candidate = makeCandidate({ currentHeadOid: 'old' }) - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - reportVisiblePRRefreshCandidates([{ ...candidate, currentHeadOid: 'new' }], 2, 1) - resolve({ - kind: 'upstream-error', - errorType: 'network', - message: 'offline', - fetchedAt: Date.now() - }) - await vi.advanceTimersByTimeAsync(119_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - expect(getPRForBranchOutcomeMock.mock.calls[1][5]?.currentHeadOid).toBe('new') - }) - - it.each([true, false])( - 'preserves a rate-limit gate across selected reports (hidden=%s)', - async (hidden) => { - getPRForBranchOutcomeMock.mockImplementation(async () => ({ - kind: 'upstream-error', - errorType: 'rate_limited', - message: 'wait', - fetchedAt: Date.now(), - retryDisabledUntil: 301_000 - })) - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const candidate = makeCandidate() - reportVisiblePRRefreshCandidates([candidate], 1, 1) - await vi.advanceTimersByTimeAsync(0) - if (hidden) { - reportVisiblePRRefreshCandidates([], 2, 1) - } - await vi.advanceTimersByTimeAsync(10_000) - reportVisiblePRRefreshCandidates([{ ...candidate, isSelected: true }], 3, 1) - await vi.advanceTimersByTimeAsync(289_999) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) - } - ) -}) diff --git a/src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts b/src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts deleted file mode 100644 index aafff0a7311..00000000000 --- a/src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { - const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') - return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } -}) - -vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) - -import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' - -const { getPRForBranchOutcomeMock } = coordinatorMocks - -describe('pr-refresh-coordinator', () => { - beforeEach(() => { - moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks) - }) - - afterEach(() => { - vi.useRealTimers() - }) - - it('clears visible follow-ups when the owning window is destroyed', async () => { - const { - _getVisiblePRRefreshWindowCountForTests, - clearVisiblePRRefreshWindow, - reportVisiblePRRefreshCandidates - } = await import('./pr-refresh-coordinator') - getPRForBranchOutcomeMock.mockResolvedValue({ - kind: 'found', - pr: makePR({ checksStatus: 'pending', mergeable: 'MERGEABLE' }), - fetchedAt: Date.now() - }) - - reportVisiblePRRefreshCandidates([makeCandidate()], 1, 1) - await vi.runOnlyPendingTimersAsync() - - expect(_getVisiblePRRefreshWindowCountForTests()).toBe(1) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - - clearVisiblePRRefreshWindow(1) - await vi.advanceTimersByTimeAsync(90_000) - - expect(_getVisiblePRRefreshWindowCountForTests()).toBe(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - }) - - it('stops polling settled merged PRs, including repeated visibility reports', async () => { - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - getPRForBranchOutcomeMock.mockImplementation(async () => ({ - kind: 'found', - pr: makePR({ checksStatus: 'success', state: 'merged' }), - fetchedAt: Date.now() - })) - reportVisiblePRRefreshCandidates([makeCandidate()], 1, 1) - await vi.advanceTimersByTimeAsync(0) - const merged = makeCandidate({ - cachedFetchedAt: Date.now(), - cachedHasPR: true, - cachedPRState: 'merged', - cachedChecksStatus: 'success' - }) - await vi.advanceTimersByTimeAsync(31 * 60_000) - reportVisiblePRRefreshCandidates([merged], 2, 1) - await vi.advanceTimersByTimeAsync(0) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - await vi.advanceTimersByTimeAsync(24 * 60 * 60_000 - 31 * 60_000) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/main/github/pr-watch/pr-chat-action-recorder.test.ts b/src/main/github/pr-watch/pr-chat-action-recorder.test.ts index 7aefc97dac9..f01912a30d8 100644 --- a/src/main/github/pr-watch/pr-chat-action-recorder.test.ts +++ b/src/main/github/pr-watch/pr-chat-action-recorder.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import type { AgentJournalToolCallItem } from '../../../shared/agent-session/journal/agent-session-journal-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import { finishedShellCall, pushedPullRequestHead, diff --git a/src/main/github/pr-watch/pr-chat-action-recorder.ts b/src/main/github/pr-watch/pr-chat-action-recorder.ts index 461d9d94838..b3308b66742 100644 --- a/src/main/github/pr-watch/pr-chat-action-recorder.ts +++ b/src/main/github/pr-watch/pr-chat-action-recorder.ts @@ -1,5 +1,5 @@ import type { AgentJournalItemBody } from '../../../shared/agent-session/journal/agent-session-journal-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import { pullRequestsOpenedBy } from '../../../shared/native-chat/tools/native-chat-opened-pull-requests' import { nativeChatToolCallScript } from '../../../shared/native-chat/tools/native-chat-tool-call-script' import { gitPushUpdates, runsGitPush, type GitPushUpdate } from './git-push-update-lines' diff --git a/src/main/github/pr-watch/pr-earlier-head-jobs.ts b/src/main/github/pr-watch/pr-earlier-head-jobs.ts index bde0fb46cd0..fd4980ebee9 100644 --- a/src/main/github/pr-watch/pr-earlier-head-jobs.ts +++ b/src/main/github/pr-watch/pr-earlier-head-jobs.ts @@ -1,8 +1,8 @@ import type { StructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/structured-agent-session-logger' -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { JournalHostDatabase } from '../../native-chat/agent-session-journal/database/journal-host-database' -import { schedulePRWatchRead } from '../pr-refresh-coordinator' -import { prWatchQueueKey, type PRWatchJob } from '../pr-refresh-watch-jobs' +import { schedulePRWatchRead } from '../pull-requests/refresh/pr-refresh-coordinator' +import { prWatchQueueKey, type PRWatchJob } from '../pull-requests/refresh/pr-refresh-watch-jobs' import { deletePRPendingHead, readPRPendingHeads } from './pr-check-event-tables' import { readPRHeadCheckRollup } from './pr-head-check-read' import { parsePRKey } from './pr-watch-types' diff --git a/src/main/github/pr-watch/pr-head-check-read.ts b/src/main/github/pr-watch/pr-head-check-read.ts index 823e65d9a0c..ed3ac8698b4 100644 --- a/src/main/github/pr-watch/pr-head-check-read.ts +++ b/src/main/github/pr-watch/pr-head-check-read.ts @@ -1,16 +1,17 @@ -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { PR_CHECK_PAGE_LIMIT } from '../client/check/pr-check-rollup' import { readRestPRCheckRollup } from '../client/check/rest-pr-check-pages' import type { GhExecOptions } from '../client/github-exec-scope' import { assertRateLimitBudget } from '../client/lookup/pr-lookup-rate-limit' -import { ghExecFileAsync } from '../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../github-api-repository' -import { noteRepositoryRateLimitSpend } from '../rate-limit' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { githubHostExecOptions } from '../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend } from '../api/rate-limit' import { ghRepoExecOptions, githubRepoContext, withGitHubRepoExecution -} from '../github-repository-identity' +} from '../repositories/github-repository-identity' type PRRepository = { host: string; owner: string; repo: string } diff --git a/src/main/github/pr-watch/pr-pending-action-resume.ts b/src/main/github/pr-watch/pr-pending-action-resume.ts index 9f3f9fd154b..4da326c3805 100644 --- a/src/main/github/pr-watch/pr-pending-action-resume.ts +++ b/src/main/github/pr-watch/pr-pending-action-resume.ts @@ -2,8 +2,8 @@ // ones fixed at capture; a restart resumes the same row and never renews them. Each obligation is // saved as decided on its own, so a sibling still resolving never holds back or loses it. -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import { pushedPullRequestHead, type PushRemote } from './pr-chat-action-recorder' import { PRPendingActionDrain } from './pr-pending-action-drain' import type { PRPendingActionResumeDeps, PRVerifiedAction } from './pr-pending-action-types' diff --git a/src/main/github/pr-watch/pr-pending-action-types.ts b/src/main/github/pr-watch/pr-pending-action-types.ts index f2d174565f3..9f1abeb1aff 100644 --- a/src/main/github/pr-watch/pr-pending-action-types.ts +++ b/src/main/github/pr-watch/pr-pending-action-types.ts @@ -1,5 +1,5 @@ import type { StructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/structured-agent-session-logger' -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { JournalHostDatabase } from '../../native-chat/agent-session-journal/database/journal-host-database' import type { PushRemote } from './pr-chat-action-recorder' import type { PRPendingActionRow, PRPendingObligation } from './pr-pending-actions' diff --git a/src/main/github/pr-watch/pr-push-remote-resolution.ts b/src/main/github/pr-watch/pr-push-remote-resolution.ts index 6299b290c16..654fdfdb2e4 100644 --- a/src/main/github/pr-watch/pr-push-remote-resolution.ts +++ b/src/main/github/pr-watch/pr-push-remote-resolution.ts @@ -1,4 +1,4 @@ -import { classifyGitHubOwnerRepoFromRemoteUrl } from '../github-ssh-host-alias-resolution' +import { classifyGitHubOwnerRepoFromRemoteUrl } from '../repositories/github-ssh-host-alias-resolution' import type { PushRemote } from './pr-chat-action-recorder' import type { PRPendingObligation } from './pr-pending-actions' import type { PRWatchWorkspace } from './pr-watch-types' diff --git a/src/main/github/pr-watch/pr-watch-service.test.ts b/src/main/github/pr-watch/pr-watch-service.test.ts index 355747d1573..ffe1b13ee17 100644 --- a/src/main/github/pr-watch/pr-watch-service.test.ts +++ b/src/main/github/pr-watch/pr-watch-service.test.ts @@ -7,13 +7,13 @@ import { codexProviderHandle } from '../../../shared/agent-session/lifecycle/age import type { GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../../shared/github/pull-request-refresh-types' +} from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { createTrackedJournalOpener, openTestJournalHostDatabase } from '../../native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support' import { assemblePRRefreshFoundOutcome } from '../client/lookup/pr-refresh-outcome-assembly' -import type { PRWatchJob } from '../pr-refresh-watch-jobs' +import type { PRWatchJob } from '../pull-requests/refresh/pr-refresh-watch-jobs' import { readPRPendingDeliveryChatIds, readPRPendingHeads } from './pr-check-event-tables' import { readPRHeadCheckRollup } from './pr-head-check-read' import { capturePRPendingActions } from './pr-pending-actions' @@ -36,7 +36,7 @@ const queue = vi.hoisted(() => { return state }) -vi.mock('../pr-refresh-coordinator', () => ({ +vi.mock('../pull-requests/refresh/pr-refresh-coordinator', () => ({ addPRRefreshOutcomeObserver: (observer: QueueObserver) => { queue.observers.add(observer) return () => queue.observers.delete(observer) @@ -53,7 +53,7 @@ vi.mock('./pr-head-check-read', () => ({ resolvePRCommitSha: async () => null, isCommitInPRHead: async () => false })) -vi.mock('../github-api-repository', () => ({ +vi.mock('../repositories/github-api-repository', () => ({ resolveGitHubApiRepositoryCandidates: async () => ({ candidates: [{ owner: 'acme', repo: 'repo', host: 'github.com' }] }) diff --git a/src/main/github/pr-watch/pr-watch-service.ts b/src/main/github/pr-watch/pr-watch-service.ts index ba3f88fbc86..dfce5235d09 100644 --- a/src/main/github/pr-watch/pr-watch-service.ts +++ b/src/main/github/pr-watch/pr-watch-service.ts @@ -4,12 +4,12 @@ import type { GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +} from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import { pullRequestKey } from '../../../shared/native-chat/native-chat-pull-request-event' import type { JournalRow } from '../../native-chat/agent-session-journal/durable-rows/journal-row-schema' import type { AgentSessionJournal } from '../../native-chat/agent-session-journal/journal-store' -import { resolveGitHubApiRepositoryCandidates } from '../github-api-repository' +import { resolveGitHubApiRepositoryCandidates } from '../repositories/github-api-repository' import { observedPRCheckRollup } from '../client/lookup/pr-refresh-outcome-assembly' import { addPRRefreshOutcomeObserver, @@ -17,8 +17,12 @@ import { enqueuePRRefresh, schedulePRWatchRead, setPRWatchJobHooks -} from '../pr-refresh-coordinator' -import { prChecksDone, prWatchNextDueAt, type PRWatchJob } from '../pr-refresh-watch-jobs' +} from '../pull-requests/refresh/pr-refresh-coordinator' +import { + prChecksDone, + prWatchNextDueAt, + type PRWatchJob +} from '../pull-requests/refresh/pr-refresh-watch-jobs' import { deletePRMovedHeads, endPRWatchState } from './pr-check-event-tables' import { PREarlierHeadJobs } from './pr-earlier-head-jobs' import { PREventDeliveryTrigger } from './pr-event-delivery' diff --git a/src/main/github/pr-watch/pr-watch-test-fixtures.ts b/src/main/github/pr-watch/pr-watch-test-fixtures.ts index 30d9bfb87b3..07c862dff02 100644 --- a/src/main/github/pr-watch/pr-watch-test-fixtures.ts +++ b/src/main/github/pr-watch/pr-watch-test-fixtures.ts @@ -1,5 +1,5 @@ import type { AgentJournalItemBody } from '../../../shared/agent-session/journal/agent-session-journal-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import type { JournalItemRow } from '../../native-chat/agent-session-journal/durable-rows/journal-row-schema' /** An open PR; the fields a watch reads come from `fields`. */ diff --git a/src/main/github/pr-watch/pr-watch-types.ts b/src/main/github/pr-watch/pr-watch-types.ts index 1a2ea58d77c..3ddc7b38623 100644 --- a/src/main/github/pr-watch/pr-watch-types.ts +++ b/src/main/github/pr-watch/pr-watch-types.ts @@ -1,6 +1,6 @@ import type { StructuredAgentSessionLogger } from '../../native-chat/agent-session-wire/host/structured-agent-session-logger' -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import { pullRequestKey } from '../../../shared/native-chat/native-chat-pull-request-event' import type { RepoKind } from '../../../shared/repo-types' import type { JournalHostDatabase } from '../../native-chat/agent-session-journal/database/journal-host-database' diff --git a/src/main/github/project-bound-gh-requests.test.ts b/src/main/github/project-bound-gh-requests.test.ts deleted file mode 100644 index b81e9bd8f6e..00000000000 --- a/src/main/github/project-bound-gh-requests.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { createFakeSpawnedChild } from '../../shared/__fixtures__/fake-spawned-child' -import type { Repo } from '../../shared/repo-types' - -const { spawnMock, requestMock } = vi.hoisted(() => ({ spawnMock: vi.fn(), requestMock: vi.fn() })) - -vi.mock('node:child_process', async (importOriginal) => ({ - ...(await importOriginal()), - spawn: spawnMock -})) -vi.mock('./github-api-repository', () => ({ - getOriginGitHubApiRepository: vi.fn(async () => ({ - owner: 'acme', - repo: 'widgets', - host: 'github.com' - })), - githubHostExecOptions: () => ({ host: 'github.com' }) -})) - -import { _resetGhRateLimitBreaker } from '../git/command-runner/hosted-cli/gh-rate-limit-breaker' -import { installGitHubProjectAccounts } from './github-project-account' -import { createGitHubPullRequest } from './client/create/create-github-pull-request' -import { clearGhAccountTokenCacheForTests } from './gh-account-token' -import { clearGhCapabilityStateForTests } from './gh-capability-state' -import { runGraphql } from './project-view/internals' - -let releaseAccounts: (() => void) | undefined -let repo: Repo -beforeEach(() => { - _resetGhRateLimitBreaker() - clearGhAccountTokenCacheForTests() - clearGhCapabilityStateForTests() - requestMock.mockReset() - spawnMock.mockReset() - spawnMock.mockImplementation((_command, args: string[], options: { env: NodeJS.ProcessEnv }) => { - if (args[0] !== 'auth') { - return requestMock(args, options) - } - const child = createFakeSpawnedChild() - const output = args.includes('--help') ? '--user' : `${args[args.indexOf('--user') + 1]}-token` - queueMicrotask(() => complete(child, output)) - return child - }) - repo = { - id: 'work', - displayName: 'Work', - path: '/work', - badgeColor: '#000000', - addedAt: 0, - ghAccount: { host: 'github.com', user: 'Alice' } - } - releaseAccounts = installGitHubProjectAccounts({ getRepos: () => [repo] }) -}) -afterEach(() => { - releaseAccounts?.() - _resetGhRateLimitBreaker() - vi.unstubAllEnvs() -}) - -function complete(child: ReturnType, stdout: string): void { - child.stdout?.emit('data', Buffer.from(stdout)) - child.emit('exit', 0, null) - child.emit('close', 0, null) -} - -it('keeps Project View ambient even with a bound project cwd', async () => { - vi.stubEnv('GH_TOKEN', 'personal-token') - let selectedPersonal = false - requestMock.mockImplementation((_args, options: { env: NodeJS.ProcessEnv }) => { - selectedPersonal = options.env.GH_TOKEN === 'personal-token' - const child = createFakeSpawnedChild() - queueMicrotask(() => complete(child, '{"data":{"viewer":{"login":"personal"}}}')) - return child - }) - await expect( - runGraphql('query { viewer { login } }', {}, { cwd: repo.path, host: 'github.com' }) - ).resolves.toMatchObject({ ok: true, data: { viewer: { login: 'personal' } } }) - expect(selectedPersonal).toBe(true) - expect(spawnMock.mock.calls.some(([, args]) => args[0] === 'auth')).toBe(false) -}) - -it('keeps creation recovery on its original account while the next operation reads a rebind', async () => { - const tokens: (string | undefined)[] = [] - let startCreate: (finish: () => void) => void = () => {} - const started = new Promise<() => void>((resolve) => { - startCreate = resolve - }) - let firstCreate = true - requestMock.mockImplementation((args: string[], options: { env: NodeJS.ProcessEnv }) => { - tokens.push(options.env.GH_TOKEN) - const child = createFakeSpawnedChild() - if (args[0] === 'pr' && args[1] === 'create' && firstCreate) { - firstCreate = false - startCreate(() => complete(child, 'unconfirmed')) - } else { - queueMicrotask(() => - complete( - child, - JSON.stringify( - args[1] === 'list' - ? [{ number: 42, url: 'https://github.com/acme/widgets/pull/42' }] - : { number: 43, url: 'https://github.com/acme/widgets/pull/43' } - ) - ) - ) - } - return child - }) - const input = { provider: 'github', base: 'main', head: 'feature', title: 'Change' } as const - const pending = createGitHubPullRequest(repo.path, input, 'local') - const finish = await started - repo = { ...repo, ghAccount: { host: 'github.com', user: 'Bob' } } - finish() - await expect(pending).resolves.toMatchObject({ ok: true, number: 42 }) - expect(tokens).toEqual(['Alice-token', 'Alice-token']) - await expect(createGitHubPullRequest(repo.path, input, 'local')).resolves.toMatchObject({ - ok: true, - number: 43 - }) - expect(tokens.at(-1)).toBe('Bob-token') -}) diff --git a/src/main/github/project-view.ts b/src/main/github/project-view.ts deleted file mode 100644 index 3abe1bcebdd..00000000000 --- a/src/main/github/project-view.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Preserve the public import entry point across the internal module split. -export { isValidOwnerSlug, isValidRepoSlug } from './project-view/internals' -export { classifyProjectError } from './project-view/project-error-classification' -export { updateIssueBySlug } from './project-view/mutations' -export { - updateProjectItemFieldValue, - clearProjectItemFieldValue -} from './project-view/project-field-mutations' -export { updatePullRequestBySlug } from './project-view/pull-request-mutation' -export { - addIssueCommentBySlug, - updateIssueCommentBySlug, - deleteIssueCommentBySlug -} from './project-view/issue-comment-mutations' -export { - listLabelsBySlug, - listAssignableUsersBySlug, - listIssueTypesBySlug -} from './project-view/repository-field-options' -export { updateIssueTypeBySlug } from './project-view/issue-type-mutation' -export { getWorkItemDetailsBySlug } from './project-view/work-item-details' -export { - PROJECT_VIEW_OWNER_CACHE_MAX_ENTRIES, - _resetProjectViewCachesForTests, - _getProjectViewCacheSizesForTests, - _rememberProjectViewOwnerTypeForTests, - _getProjectViewOwnerTypeForTests, - _markProjectViewParentFieldRetriedForTests, - _hasProjectViewParentFieldRetriedForTests, - _markProjectViewParentFieldWarningLoggedForTests, - _hasProjectViewParentFieldWarningLoggedForTests -} from './project-view/project-view-cache' -export { - normalizeField, - normalizeFieldValue -} from './project-view/project-view-field-normalization' -export { normalizeItem } from './project-view/project-view-item-normalization' -export { getProjectViewTable } from './project-view/project-view-table' -export { listAccessibleProjects } from './project-view/project-view-discovery' -export { parseProjectPaste, resolveProjectRef } from './project-view/project-view-reference' -export { listProjectViews } from './project-view/project-view-listing' diff --git a/src/main/github/project-view/internals.ts b/src/main/github/project-view/internals.ts deleted file mode 100644 index f5a7688fa49..00000000000 --- a/src/main/github/project-view/internals.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { acquire, release } from '../gh-utils' -import { isGitHubOwnerSlug } from '../../../shared/github/owner-slug' -import { extractExecError } from '../../git/command-runner/process-capture/exec-error' -import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' -import { - repositoryRateLimitGuard, - noteRepositoryRateLimitSpend, - type RateLimitBucketKind -} from '../rate-limit' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' -import { githubProjectHost } from '../../../shared/github/project-identity' -import { isDefaultGitHubHost } from '../../../shared/github/repository-identity-key' -import { isGitHubHostAuthenticatedForGlobalCli } from '../github-enterprise-repository' -import { - classifyProjectError, - driftError, - rateLimitedError, - type GhGraphqlError -} from './project-error-classification' - -export { - acquire, - release, - extractExecError, - ghExecFileAsync, - repositoryRateLimitGuard, - noteRepositoryRateLimitSpend -} -export type { RateLimitBucketKind } - -/** Project View uses ambient login, independently of Repo.ghAccount; pin Enterprise hosts explicitly. */ -export type ProjectGhExecOptions = { cwd?: string; host?: string } - -// Owner/project calls have no repository slug, so pin their host directly. -export function projectGhExecOptions(host: string | undefined): ProjectGhExecOptions { - return { host: githubProjectHost(host) } -} - -export async function projectHostAuthenticationError( - host: string | undefined -): Promise { - const selectedHost = githubProjectHost(host) - if ( - isDefaultGitHubHost(selectedHost) || - (await isGitHubHostAuthenticatedForGlobalCli(selectedHost)) - ) { - return null - } - // Verify pasted hosts before sending ambient Enterprise credentials. - return { - type: 'auth_required', - message: `GitHub CLI is not authenticated to ${selectedHost}. Run gh auth login --hostname ${selectedHost}.`, - host: selectedHost - } -} - -// Shared owner rules differ from repo names; validate both before interpolating authenticated REST paths. -const REPO_SLUG_RE = /^[A-Za-z0-9._-]+$/ -const REPO_SLUG_RESERVED = new Set(['.', '..']) - -export function isValidOwnerSlug(value: unknown): value is string { - return isGitHubOwnerSlug(value) -} - -export function isValidRepoSlug(value: unknown): value is string { - return ( - typeof value === 'string' && - value.length > 0 && - REPO_SLUG_RE.test(value) && - !REPO_SLUG_RESERVED.has(value) - ) -} - -export function assertSlug( - value: unknown, - field: 'owner' | 'repo' -): { ok: true; slug: string } | { ok: false; error: GitHubProjectViewError } { - const valid = field === 'owner' ? isValidOwnerSlug(value) : isValidRepoSlug(value) - if (!valid) { - return { - ok: false, - error: { - type: 'validation_error', - message: `Invalid ${field}: "${String(value)}" is not a valid GitHub slug.`, - reason: 'invalid_slug', - detail: field, - value: String(value) - } - } - } - return { ok: true, slug: value as string } -} - -export function assertPositiveInt( - value: unknown, - field: string -): { ok: true; n: number } | { ok: false; error: GitHubProjectViewError } { - if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) { - return { - ok: false, - error: { - type: 'validation_error', - message: `Invalid ${field}: must be a positive integer.`, - reason: 'invalid_positive_integer', - detail: field - } - } - } - return { ok: true, n: value } -} - -export function validateSlugArgs( - owner: unknown, - repo: unknown -): { ok: true } | { ok: false; error: GitHubProjectViewError } { - const o = assertSlug(owner, 'owner') - if (!o.ok) { - return { ok: false, error: o.error } - } - const r = assertSlug(repo, 'repo') - if (!r.ok) { - return { ok: false, error: r.error } - } - return { ok: true } -} - -export type GraphqlVars = Record - -export async function runGraphql( - query: string, - vars: GraphqlVars, - exec?: ProjectGhExecOptions -): Promise< - | { ok: true; data: T } - | { ok: false; error: GitHubProjectViewError; raw: { stderr: string; stdout: string } } -> { - const authError = await projectHostAuthenticationError(exec?.host) - if (authError) { - return { ok: false, error: authError, raw: { stderr: '', stdout: '' } } - } - // Enterprise traffic has separate quota from the shared github.com snapshot. - const guard = repositoryRateLimitGuard({ host: exec?.host }, 'graphql') - if (guard.blocked) { - return { ok: false, error: rateLimitedError(guard), raw: { stderr: '', stdout: '' } } - } - // Use typed -F for numbers/booleans and literal -f for strings, including empty strings. - const args: string[] = ['api', 'graphql', '-f', `query=${query}`] - for (const [k, v] of Object.entries(vars)) { - if (typeof v === 'number' || typeof v === 'boolean') { - args.push('-F', `${k}=${String(v)}`) - } else { - args.push('-f', `${k}=${v}`) - } - } - await acquire() - noteRepositoryRateLimitSpend({ host: exec?.host }, 'graphql') - try { - const { stdout, stderr } = await ghExecFileAsync(args, { - encoding: 'utf-8', - ...(exec?.cwd ? { cwd: exec.cwd } : {}), - ...(exec?.host ? { host: exec.host } : {}) - }) - try { - const parsed: { data?: T; errors?: GhGraphqlError[] } = JSON.parse(stdout) - if (parsed.errors && parsed.errors.length > 0) { - return { - ok: false, - error: classifyProjectError(stderr, stdout, exec?.host), - raw: { stderr, stdout } - } - } - if (parsed.data === undefined) { - return { - ok: false, - error: driftError('response missing data'), - raw: { stderr, stdout } - } - } - return { ok: true, data: parsed.data } - } catch (parseErr) { - return { - ok: false, - error: driftError( - `failed to parse response (${parseErr instanceof Error ? parseErr.message : String(parseErr)})` - ), - raw: { stderr, stdout } - } - } - } catch (err) { - // Explicit streams preserve diagnostics that Error.message may truncate. - const { stderr, stdout: maybeStdout } = extractExecError(err) - return { - ok: false, - error: classifyProjectError(stderr, maybeStdout, exec?.host), - raw: { stderr, stdout: maybeStdout } - } - } finally { - release() - } -} - -export async function runRest( - args: string[], - cwd?: string, - bucket: RateLimitBucketKind = 'core', - options?: { expectEmpty?: boolean; host?: string } -): Promise<{ ok: true; data: T } | { ok: false; error: GitHubProjectViewError }> { - const authError = await projectHostAuthenticationError(options?.host) - if (authError) { - return { ok: false, error: authError } - } - // Enterprise traffic has separate quota from the shared github.com snapshot. - const guard = repositoryRateLimitGuard({ host: options?.host }, bucket) - if (guard.blocked) { - return { ok: false, error: rateLimitedError(guard) } - } - await acquire() - noteRepositoryRateLimitSpend({ host: options?.host }, bucket) - try { - const { stdout, stderr } = await ghExecFileAsync(['api', ...args], { - encoding: 'utf-8', - ...(cwd ? { cwd } : {}), - ...(options?.host ? { host: options.host } : {}) - }) - // Successful DELETE endpoints can return no body; do not misclassify empty stdout as a parse failure. - if (options?.expectEmpty && stdout.trim() === '') { - return { ok: true, data: undefined as T } - } - try { - return { ok: true, data: JSON.parse(stdout) as T } - } catch { - return { - ok: false, - error: { - type: 'unknown', - message: `Unexpected REST response: ${stderr.trim()}`, - reason: 'unexpected_rest_response', - detail: stderr.trim() - } - } - } - } catch (err) { - const { stderr, stdout: maybeStdout } = extractExecError(err) - return { ok: false, error: classifyProjectError(stderr, maybeStdout, options?.host) } - } finally { - release() - } -} diff --git a/src/main/github/project-view/issue-comment-mutations.ts b/src/main/github/project-view/issue-comment-mutations.ts index 61dd68d32b9..6e6339e4ae4 100644 --- a/src/main/github/project-view/issue-comment-mutations.ts +++ b/src/main/github/project-view/issue-comment-mutations.ts @@ -1,15 +1,17 @@ -import { githubBodyImageUrls, githubBodyMediaHeaders } from '../body-image-urls' -import { assertPositiveInt, projectGhExecOptions, runRest, validateSlugArgs } from './internals' -import type { PRComment } from '../../../shared/github/comment-types' +import { githubBodyImageUrls, githubBodyMediaHeaders } from '../attachments/body-image-urls' +import { assertPositiveInt, validateSlugArgs } from './project-argument-validation' +import { projectGhExecOptions } from './project-host-execution' +import { runRest } from './project-rest' +import type { PRComment } from '../../../shared/github/comments/comment-types' import type { GitHubProjectCommentMutationResult, GitHubProjectMutationResult -} from '../../../shared/github/project-result-types' +} from '../../../shared/github/projects/project-result-types' import type { AddIssueCommentBySlugArgs, DeleteIssueCommentBySlugArgs, UpdateIssueCommentBySlugArgs -} from '../../../shared/github/project-request-types' +} from '../../../shared/github/projects/project-request-types' type RawIssueCommentResponse = { id?: number diff --git a/src/main/github/project-view/issue-type-mutation.ts b/src/main/github/project-view/issue-type-mutation.ts index 30f9fb6c59b..f49665ff2f2 100644 --- a/src/main/github/project-view/issue-type-mutation.ts +++ b/src/main/github/project-view/issue-type-mutation.ts @@ -1,12 +1,8 @@ -import { - assertPositiveInt, - projectGhExecOptions, - runGraphql, - validateSlugArgs, - type GraphqlVars -} from './internals' -import type { GitHubProjectMutationResult } from '../../../shared/github/project-result-types' -import type { UpdateIssueTypeBySlugArgs } from '../../../shared/github/project-request-types' +import { assertPositiveInt, validateSlugArgs } from './project-argument-validation' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql, type GraphqlVars } from './project-graphql' +import type { GitHubProjectMutationResult } from '../../../shared/github/projects/project-result-types' +import type { UpdateIssueTypeBySlugArgs } from '../../../shared/github/projects/project-request-types' export async function updateIssueTypeBySlug( args: UpdateIssueTypeBySlugArgs diff --git a/src/main/github/project-view/mutations.ts b/src/main/github/project-view/mutations.ts index 4c31ac3a522..f47e9924914 100644 --- a/src/main/github/project-view/mutations.ts +++ b/src/main/github/project-view/mutations.ts @@ -1,20 +1,14 @@ -import { githubBodyImageUrls, githubBodyMediaHeaders } from '../body-image-urls' -import { - acquire, - assertPositiveInt, - extractExecError, - ghExecFileAsync, - noteRepositoryRateLimitSpend, - projectGhExecOptions, - projectHostAuthenticationError, - release, - repositoryRateLimitGuard, - runRest, - validateSlugArgs -} from './internals' +import { githubBodyImageUrls, githubBodyMediaHeaders } from '../attachments/body-image-urls' +import { acquire, release } from '../execution/github-operation-slots' +import { assertPositiveInt, validateSlugArgs } from './project-argument-validation' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' +import { projectGhExecOptions, projectHostAuthenticationError } from './project-host-execution' +import { runRest } from './project-rest' import { classifyProjectError, rateLimitedError } from './project-error-classification' -import type { GitHubProjectMutationResult } from '../../../shared/github/project-result-types' -import type { UpdateIssueBySlugArgs } from '../../../shared/github/project-request-types' +import type { GitHubProjectMutationResult } from '../../../shared/github/projects/project-result-types' +import type { UpdateIssueBySlugArgs } from '../../../shared/github/projects/project-request-types' export async function updateIssueBySlug( args: UpdateIssueBySlugArgs diff --git a/src/main/github/project-view/project-argument-validation.ts b/src/main/github/project-view/project-argument-validation.ts new file mode 100644 index 00000000000..1ec43dc7c77 --- /dev/null +++ b/src/main/github/project-view/project-argument-validation.ts @@ -0,0 +1,69 @@ +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { isGitHubOwnerSlug as isValidOwnerSlug } from '../../../shared/github/repositories/owner-slug' + +// Shared owner rules differ from repo names; validate both before interpolating authenticated REST paths. +const REPO_SLUG_RE = /^[A-Za-z0-9._-]+$/ + +const REPO_SLUG_RESERVED = new Set(['.', '..']) + +export function isValidRepoSlug(value: unknown): value is string { + return ( + typeof value === 'string' && + value.length > 0 && + REPO_SLUG_RE.test(value) && + !REPO_SLUG_RESERVED.has(value) + ) +} + +export function assertSlug( + value: unknown, + field: 'owner' | 'repo' +): { ok: true; slug: string } | { ok: false; error: GitHubProjectViewError } { + const valid = field === 'owner' ? isValidOwnerSlug(value) : isValidRepoSlug(value) + if (!valid) { + return { + ok: false, + error: { + type: 'validation_error', + message: `Invalid ${field}: "${String(value)}" is not a valid GitHub slug.`, + reason: 'invalid_slug', + detail: field, + value: String(value) + } + } + } + return { ok: true, slug: value as string } +} + +export function assertPositiveInt( + value: unknown, + field: string +): { ok: true; n: number } | { ok: false; error: GitHubProjectViewError } { + if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) { + return { + ok: false, + error: { + type: 'validation_error', + message: `Invalid ${field}: must be a positive integer.`, + reason: 'invalid_positive_integer', + detail: field + } + } + } + return { ok: true, n: value } +} + +export function validateSlugArgs( + owner: unknown, + repo: unknown +): { ok: true } | { ok: false; error: GitHubProjectViewError } { + const o = assertSlug(owner, 'owner') + if (!o.ok) { + return { ok: false, error: o.error } + } + const r = assertSlug(repo, 'repo') + if (!r.ok) { + return { ok: false, error: r.error } + } + return { ok: true } +} diff --git a/src/main/github/project-view/project-error-classification.ts b/src/main/github/project-view/project-error-classification.ts index 4b0df5977e3..f1e7b1aef42 100644 --- a/src/main/github/project-view/project-error-classification.ts +++ b/src/main/github/project-view/project-error-classification.ts @@ -1,6 +1,6 @@ import type { OptionalProjectItemField } from './project-view-cache' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' -import { githubProjectHost } from '../../../shared/github/project-identity' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' export type GhGraphqlError = { type?: string diff --git a/src/main/github/project-view/project-field-mutations.ts b/src/main/github/project-view/project-field-mutations.ts index 69130f7197b..22d2a80ae4d 100644 --- a/src/main/github/project-view/project-field-mutations.ts +++ b/src/main/github/project-view/project-field-mutations.ts @@ -1,10 +1,11 @@ -import { projectGhExecOptions, runGraphql, type GraphqlVars } from './internals' -import type { GitHubProjectFieldMutationValue } from '../../../shared/github/project-types' -import type { GitHubProjectMutationResult } from '../../../shared/github/project-result-types' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql, type GraphqlVars } from './project-graphql' +import type { GitHubProjectFieldMutationValue } from '../../../shared/github/projects/project-types' +import type { GitHubProjectMutationResult } from '../../../shared/github/projects/project-result-types' import type { ClearProjectItemFieldArgs, UpdateProjectItemFieldArgs -} from '../../../shared/github/project-request-types' +} from '../../../shared/github/projects/project-request-types' class UnknownFieldMutationKindError extends Error { constructor(kind: string) { diff --git a/src/main/github/project-view/project-graphql.ts b/src/main/github/project-view/project-graphql.ts new file mode 100644 index 00000000000..33a5c12bbb4 --- /dev/null +++ b/src/main/github/project-view/project-graphql.ts @@ -0,0 +1,87 @@ +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { repositoryRateLimitGuard, noteRepositoryRateLimitSpend } from '../api/rate-limit' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { + classifyProjectError, + driftError, + rateLimitedError, + type GhGraphqlError +} from './project-error-classification' +import { type ProjectGhExecOptions, projectHostAuthenticationError } from './project-host-execution' + +export type GraphqlVars = Record + +export async function runGraphql( + query: string, + vars: GraphqlVars, + exec?: ProjectGhExecOptions +): Promise< + | { ok: true; data: T } + | { ok: false; error: GitHubProjectViewError; raw: { stderr: string; stdout: string } } +> { + const authError = await projectHostAuthenticationError(exec?.host) + if (authError) { + return { ok: false, error: authError, raw: { stderr: '', stdout: '' } } + } + // Enterprise traffic has separate quota from the shared github.com snapshot. + const guard = repositoryRateLimitGuard({ host: exec?.host }, 'graphql') + if (guard.blocked) { + return { ok: false, error: rateLimitedError(guard), raw: { stderr: '', stdout: '' } } + } + // Use typed -F for numbers/booleans and literal -f for strings, including empty strings. + const args: string[] = ['api', 'graphql', '-f', `query=${query}`] + for (const [k, v] of Object.entries(vars)) { + if (typeof v === 'number' || typeof v === 'boolean') { + args.push('-F', `${k}=${String(v)}`) + } else { + args.push('-f', `${k}=${v}`) + } + } + await acquire() + noteRepositoryRateLimitSpend({ host: exec?.host }, 'graphql') + try { + const { stdout, stderr } = await ghExecFileAsync(args, { + encoding: 'utf-8', + ...(exec?.cwd ? { cwd: exec.cwd } : {}), + ...(exec?.host ? { host: exec.host } : {}) + }) + try { + const parsed: { data?: T; errors?: GhGraphqlError[] } = JSON.parse(stdout) + if (parsed.errors && parsed.errors.length > 0) { + return { + ok: false, + error: classifyProjectError(stderr, stdout, exec?.host), + raw: { stderr, stdout } + } + } + if (parsed.data === undefined) { + return { + ok: false, + error: driftError('response missing data'), + raw: { stderr, stdout } + } + } + return { ok: true, data: parsed.data } + } catch (parseErr) { + return { + ok: false, + error: driftError( + `failed to parse response (${parseErr instanceof Error ? parseErr.message : String(parseErr)})` + ), + raw: { stderr, stdout } + } + } + } catch (err) { + // Explicit streams preserve diagnostics that Error.message may truncate. + const { stderr, stdout: maybeStdout } = extractExecError(err) + return { + ok: false, + error: classifyProjectError(stderr, maybeStdout, exec?.host), + raw: { stderr, stdout: maybeStdout } + } + } finally { + release() + } +} diff --git a/src/main/github/project-view/project-host-execution.ts b/src/main/github/project-view/project-host-execution.ts new file mode 100644 index 00000000000..9cfa2645ed9 --- /dev/null +++ b/src/main/github/project-view/project-host-execution.ts @@ -0,0 +1,30 @@ +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' +import { isDefaultGitHubHost } from '../../../shared/github/repositories/repository-identity-key' +import { isGitHubHostAuthenticatedForGlobalCli } from '../repositories/github-enterprise-repository' + +/** Project View uses ambient login, independently of Repo.ghAccount; pin Enterprise hosts explicitly. */ +export type ProjectGhExecOptions = { cwd?: string; host?: string } + +// Owner/project calls have no repository slug, so pin their host directly. +export function projectGhExecOptions(host: string | undefined): ProjectGhExecOptions { + return { host: githubProjectHost(host) } +} + +export async function projectHostAuthenticationError( + host: string | undefined +): Promise { + const selectedHost = githubProjectHost(host) + if ( + isDefaultGitHubHost(selectedHost) || + (await isGitHubHostAuthenticatedForGlobalCli(selectedHost)) + ) { + return null + } + // Verify pasted hosts before sending ambient Enterprise credentials. + return { + type: 'auth_required', + message: `GitHub CLI is not authenticated to ${selectedHost}. Run gh auth login --hostname ${selectedHost}.`, + host: selectedHost + } +} diff --git a/src/main/github/project-view-host-auth.test.ts b/src/main/github/project-view/project-request-host-auth.test.ts similarity index 80% rename from src/main/github/project-view-host-auth.test.ts rename to src/main/github/project-view/project-request-host-auth.test.ts index f202dce4edb..cda50edfdb7 100644 --- a/src/main/github/project-view-host-auth.test.ts +++ b/src/main/github/project-view/project-request-host-auth.test.ts @@ -14,34 +14,35 @@ const { noteRepositoryRateLimitSpendMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ +vi.mock('../execution/github-operation-slots', () => ({ acquire: acquireMock, release: releaseMock })) -vi.mock('../git/command-runner/process-capture/exec-error', async (importOriginal) => ({ +vi.mock('../../git/command-runner/process-capture/exec-error', async (importOriginal) => ({ ...(await importOriginal>()), extractExecError: vi.fn(() => ({ stdout: '', stderr: '' })) })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: ghExecFileAsyncMock })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ rateLimitGuard: vi.fn(() => ({ blocked: false })), noteRateLimitSpend: vi.fn(), repositoryRateLimitGuard: vi.fn(() => ({ blocked: false })), noteRepositoryRateLimitSpend: noteRepositoryRateLimitSpendMock })) -vi.mock('./github-enterprise-repository', () => ({ +vi.mock('../repositories/github-enterprise-repository', () => ({ isGitHubHostAuthenticatedForGlobalCli: hostAuthenticatedMock })) -import { runGraphql, runRest } from './project-view/internals' -import { _resetProjectViewCachesForTests } from './project-view' +import { runGraphql } from './project-graphql' +import { runRest } from './project-rest' +import { _resetProjectViewCachesForTests } from './project-view-cache' describe('project view host authentication boundary', () => { beforeEach(() => { diff --git a/src/main/github/project-view/project-rest.ts b/src/main/github/project-view/project-rest.ts new file mode 100644 index 00000000000..4a037e66cb1 --- /dev/null +++ b/src/main/github/project-view/project-rest.ts @@ -0,0 +1,59 @@ +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { + repositoryRateLimitGuard, + noteRepositoryRateLimitSpend, + type RateLimitBucketKind +} from '../api/rate-limit' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { classifyProjectError, rateLimitedError } from './project-error-classification' +import { projectHostAuthenticationError } from './project-host-execution' + +export async function runRest( + args: string[], + cwd?: string, + bucket: RateLimitBucketKind = 'core', + options?: { expectEmpty?: boolean; host?: string } +): Promise<{ ok: true; data: T } | { ok: false; error: GitHubProjectViewError }> { + const authError = await projectHostAuthenticationError(options?.host) + if (authError) { + return { ok: false, error: authError } + } + // Enterprise traffic has separate quota from the shared github.com snapshot. + const guard = repositoryRateLimitGuard({ host: options?.host }, bucket) + if (guard.blocked) { + return { ok: false, error: rateLimitedError(guard) } + } + await acquire() + noteRepositoryRateLimitSpend({ host: options?.host }, bucket) + try { + const { stdout, stderr } = await ghExecFileAsync(['api', ...args], { + encoding: 'utf-8', + ...(cwd ? { cwd } : {}), + ...(options?.host ? { host: options.host } : {}) + }) + // Successful DELETE endpoints can return no body; do not misclassify empty stdout as a parse failure. + if (options?.expectEmpty && stdout.trim() === '') { + return { ok: true, data: undefined as T } + } + try { + return { ok: true, data: JSON.parse(stdout) as T } + } catch { + return { + ok: false, + error: { + type: 'unknown', + message: `Unexpected REST response: ${stderr.trim()}`, + reason: 'unexpected_rest_response', + detail: stderr.trim() + } + } + } + } catch (err) { + const { stderr, stdout: maybeStdout } = extractExecError(err) + return { ok: false, error: classifyProjectError(stderr, maybeStdout, options?.host) } + } finally { + release() + } +} diff --git a/src/main/github/project-view/project-view-cache.ts b/src/main/github/project-view/project-view-cache.ts index 7f1bad822cd..49858975a6d 100644 --- a/src/main/github/project-view/project-view-cache.ts +++ b/src/main/github/project-view/project-view-cache.ts @@ -1,5 +1,5 @@ -import type { GitHubProjectOwnerType } from '../../../shared/github/project-types' -import { githubProjectHost } from '../../../shared/github/project-identity' +import type { GitHubProjectOwnerType } from '../../../shared/github/projects/project-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' export const PROJECT_VIEW_OWNER_CACHE_MAX_ENTRIES = 512 diff --git a/src/main/github/project-view/project-view-config.test.ts b/src/main/github/project-view/project-view-config.test.ts deleted file mode 100644 index 2ec9dfa3081..00000000000 --- a/src/main/github/project-view/project-view-config.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { - fetchProjectViewsPage, - resetVerticalGroupByCapabilityForTests -} from './project-view-config' -import { runGraphql } from './internals' -import type * as Internals from './internals' - -vi.mock('./internals', async (importOriginal) => ({ - ...(await importOriginal()), - runGraphql: vi.fn() -})) - -const args = { - owner: 'acme', - ownerType: 'organization', - projectNumber: 1, - host: 'ghes.acme.test', - after: null -} as const - -function okPage() { - return { - ok: true as const, - data: { - organization: { - projectV2: { - id: 'PVT_1', - title: 'Plan', - url: 'https://ghes.acme.test/orgs/acme/projects/1', - views: { pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] } - } - } - } - } -} - -function unknownFieldFailure() { - return { - ok: false as const, - error: { type: 'schema_drift' as const, message: 'Could not read this project view.' }, - raw: { - stderr: '', - stdout: JSON.stringify({ - errors: [ - { - message: "Field 'verticalGroupByFields' doesn't exist on type 'ProjectV2View'" - } - ] - }) - } - } -} - -beforeEach(() => { - vi.resetAllMocks() - resetVerticalGroupByCapabilityForTests() -}) - -describe('verticalGroupByFields capability fallback', () => { - it('memoizes the incapability per host and keeps other hosts unaffected', async () => { - vi.mocked(runGraphql).mockResolvedValueOnce(unknownFieldFailure()).mockResolvedValue(okPage()) - await fetchProjectViewsPage(args) - await fetchProjectViewsPage(args) - expect(vi.mocked(runGraphql).mock.calls[2]?.[0]).not.toContain('verticalGroupByFields') - await fetchProjectViewsPage({ ...args, host: 'github.com' }) - expect(vi.mocked(runGraphql).mock.calls[3]?.[0]).toContain('verticalGroupByFields') - }) - - it('does not cache authorization or transient errors that mention the field', async () => { - vi.mocked(runGraphql) - .mockResolvedValueOnce({ - ok: false, - error: { type: 'scope_missing', message: 'Not authorized' }, - raw: { - stderr: '', - stdout: JSON.stringify({ - errors: [{ message: 'Not authorized to access verticalGroupByFields' }] - }) - } - }) - .mockResolvedValue(okPage()) - expect((await fetchProjectViewsPage(args)).ok).toBe(false) - expect(runGraphql).toHaveBeenCalledTimes(1) - await fetchProjectViewsPage(args) - expect(vi.mocked(runGraphql).mock.calls[1]?.[0]).toContain('verticalGroupByFields') - }) - - it('does not treat the field name inside partial-error DATA as incapability', async () => { - // Partial-error data can echo a healthy field name; only schema errors prove it unsupported. - vi.mocked(runGraphql).mockResolvedValueOnce({ - ok: false, - error: { type: 'schema_drift', message: 'Could not read this project view.' }, - raw: { - stderr: '', - stdout: JSON.stringify({ - data: { - organization: { projectV2: { views: { nodes: [{ verticalGroupByFields: {} }] } } } - }, - errors: [{ message: 'SAML enforcement: resource protected by organization policy' }] - }) - } - }) - const result = await fetchProjectViewsPage(args) - expect(result.ok).toBe(false) - expect(vi.mocked(runGraphql)).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/main/github/project-view/project-view-config.ts b/src/main/github/project-view/project-view-config.ts index da828e1c278..ea8296615bd 100644 --- a/src/main/github/project-view/project-view-config.ts +++ b/src/main/github/project-view/project-view-config.ts @@ -4,11 +4,12 @@ import type { GitHubProjectSort, GitHubProjectView, GitHubProjectViewLayout -} from '../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' -import { githubProjectHost } from '../../../shared/github/project-identity' +} from '../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' import { driftError, extractGraphqlErrors } from './project-error-classification' -import { projectGhExecOptions, runGraphql, type GraphqlVars } from './internals' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql, type GraphqlVars } from './project-graphql' import { normalizeField, type RawProjectV2Field } from './project-view-field-normalization' import { FIELD_CONFIG_FRAGMENT } from './project-view-query-fragments' diff --git a/src/main/github/project-view/project-view-discovery.ts b/src/main/github/project-view/project-view-discovery.ts index cfb43588b39..795e7e433b5 100644 --- a/src/main/github/project-view/project-view-discovery.ts +++ b/src/main/github/project-view/project-view-discovery.ts @@ -1,11 +1,12 @@ -import type { ListAccessibleProjectsArgs } from '../../../shared/github/project-request-types' -import type { ListAccessibleProjectsResult } from '../../../shared/github/project-result-types' +import type { ListAccessibleProjectsArgs } from '../../../shared/github/projects/project-request-types' +import type { ListAccessibleProjectsResult } from '../../../shared/github/projects/project-result-types' import type { GitHubProjectOwnerType, GitHubProjectSummary -} from '../../../shared/github/project-types' -import { githubProjectHost } from '../../../shared/github/project-identity' -import { projectGhExecOptions, runGraphql, type GraphqlVars } from './internals' +} from '../../../shared/github/projects/project-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql, type GraphqlVars } from './project-graphql' import { driftError } from './project-error-classification' import { rememberOwnerType } from './project-view-cache' diff --git a/src/main/github/project-view/project-view-field-normalization.ts b/src/main/github/project-view/project-view-field-normalization.ts index 33fc6044cd5..6adcda473c7 100644 --- a/src/main/github/project-view/project-view-field-normalization.ts +++ b/src/main/github/project-view/project-view-field-normalization.ts @@ -6,7 +6,7 @@ import type { GitHubProjectLabel, GitHubProjectSingleSelectOption, GitHubProjectUser -} from '../../../shared/github/project-types' +} from '../../../shared/github/projects/project-types' export type RawProjectV2Field = { __typename?: string diff --git a/src/main/github/project-view/project-view-item-normalization.ts b/src/main/github/project-view/project-view-item-normalization.ts index cb966e5aeda..69e03d51ae4 100644 --- a/src/main/github/project-view/project-view-item-normalization.ts +++ b/src/main/github/project-view/project-view-item-normalization.ts @@ -5,8 +5,8 @@ import type { GitHubProjectRow, GitHubProjectRowItemType, GitHubProjectUser -} from '../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' +} from '../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' import { driftError } from './project-error-classification' import { normalizeFieldValue, diff --git a/src/main/github/project-view/project-view-item-page.ts b/src/main/github/project-view/project-view-item-page.ts index 61c0c6911b7..210eb0b6573 100644 --- a/src/main/github/project-view/project-view-item-page.ts +++ b/src/main/github/project-view/project-view-item-page.ts @@ -1,16 +1,11 @@ import type { OptionalProjectItemField } from './project-view-cache' -import type { GitHubProjectOwnerType } from '../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' -import { - acquire, - extractExecError, - ghExecFileAsync, - noteRepositoryRateLimitSpend, - projectGhExecOptions, - projectHostAuthenticationError, - release, - repositoryRateLimitGuard -} from './internals' +import type { GitHubProjectOwnerType } from '../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' +import { projectGhExecOptions, projectHostAuthenticationError } from './project-host-execution' import { classifyProjectError, driftError, diff --git a/src/main/github/project-view/project-view-items.ts b/src/main/github/project-view/project-view-items.ts index 3c55cf0819c..99a66a41fbf 100644 --- a/src/main/github/project-view/project-view-items.ts +++ b/src/main/github/project-view/project-view-items.ts @@ -1,6 +1,10 @@ -import type { GitHubProjectOwnerType, GitHubProjectRow } from '../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' -import { projectGhExecOptions, runGraphql } from './internals' +import type { + GitHubProjectOwnerType, + GitHubProjectRow +} from '../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../shared/github/projects/project-result-types' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql } from './project-graphql' import { driftError, unsupportedItemFieldsFromErrors } from './project-error-classification' import { getUnsupportedItemFields, diff --git a/src/main/github/project-view/project-view-listing.ts b/src/main/github/project-view/project-view-listing.ts index aeacb4d5e69..429e9cd6734 100644 --- a/src/main/github/project-view/project-view-listing.ts +++ b/src/main/github/project-view/project-view-listing.ts @@ -1,10 +1,10 @@ -import type { ListProjectViewsArgs } from '../../../shared/github/project-request-types' -import type { ListProjectViewsResult } from '../../../shared/github/project-result-types' +import type { ListProjectViewsArgs } from '../../../shared/github/projects/project-request-types' +import type { ListProjectViewsResult } from '../../../shared/github/projects/project-result-types' import type { GitHubProjectViewLayout, GitHubProjectViewSummary -} from '../../../shared/github/project-types' -import { assertPositiveInt, assertSlug } from './internals' +} from '../../../shared/github/projects/project-types' +import { assertPositiveInt, assertSlug } from './project-argument-validation' import { fetchProjectViewsPage } from './project-view-config' export async function listProjectViews( diff --git a/src/main/github/project-view.test.ts b/src/main/github/project-view/project-view-reference.test.ts similarity index 91% rename from src/main/github/project-view.test.ts rename to src/main/github/project-view/project-view-reference.test.ts index b5dac755d7d..b6b9f5809da 100644 --- a/src/main/github/project-view.test.ts +++ b/src/main/github/project-view/project-view-reference.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { GITHUB_PROJECT_REF_INPUT_MAX_BYTES, GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR -} from '../../shared/github/project-ref-input' +} from '../../../shared/github/projects/project-ref-input' import { _getProjectViewCacheSizesForTests, _getProjectViewOwnerTypeForTests, @@ -11,10 +11,9 @@ import { _markProjectViewParentFieldRetriedForTests, _markProjectViewParentFieldWarningLoggedForTests, _rememberProjectViewOwnerTypeForTests, - _resetProjectViewCachesForTests, - parseProjectPaste, - resolveProjectRef -} from './project-view' + _resetProjectViewCachesForTests +} from './project-view-cache' +import { parseProjectPaste, resolveProjectRef } from './project-view-reference' describe('parseProjectPaste', () => { it('accepts enterprise-host URLs only when that host is provided (GHES)', () => { diff --git a/src/main/github/project-view/project-view-reference.ts b/src/main/github/project-view/project-view-reference.ts index 02815daf698..34c244d56c7 100644 --- a/src/main/github/project-view/project-view-reference.ts +++ b/src/main/github/project-view/project-view-reference.ts @@ -1,16 +1,21 @@ -import type { ResolveProjectRefArgs } from '../../../shared/github/project-request-types' -import { GITHUB_OWNER_NUMBER_SHORTHAND_RE } from '../../../shared/github/owner-slug' +import type { ResolveProjectRefArgs } from '../../../shared/github/projects/project-request-types' +import { + GITHUB_OWNER_NUMBER_SHORTHAND_RE, + isGitHubOwnerSlug as isValidOwnerSlug +} from '../../../shared/github/repositories/owner-slug' import type { GitHubProjectViewError, ResolveProjectRefResult -} from '../../../shared/github/project-result-types' -import type { GitHubProjectOwnerType } from '../../../shared/github/project-types' +} from '../../../shared/github/projects/project-result-types' +import type { GitHubProjectOwnerType } from '../../../shared/github/projects/project-types' import { GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR, isGitHubProjectRefInputTooLarge -} from '../../../shared/github/project-ref-input' -import { githubProjectHost } from '../../../shared/github/project-identity' -import { isValidOwnerSlug, projectGhExecOptions, runGraphql, type GraphqlVars } from './internals' +} from '../../../shared/github/projects/project-ref-input' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' + +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql, type GraphqlVars } from './project-graphql' import { getCachedOwnerType, rememberOwnerType } from './project-view-cache' import { ownerQueryRoot } from './project-view-config' diff --git a/src/main/github/project-view/project-view-table.ts b/src/main/github/project-view/project-view-table.ts index 8c69f7a2d30..2e295205ba8 100644 --- a/src/main/github/project-view/project-view-table.ts +++ b/src/main/github/project-view/project-view-table.ts @@ -1,9 +1,12 @@ -import type { GetProjectViewTableArgs } from '../../../shared/github/project-request-types' -import type { GetProjectViewTableResult } from '../../../shared/github/project-result-types' -import type { GitHubProjectTable } from '../../../shared/github/project-types' -import { githubProjectHost } from '../../../shared/github/project-identity' -import { isRenderableProjectViewLayout } from '../../../shared/github/project-types' -import { assertPositiveInt, assertSlug } from './internals' +import type { GetProjectViewTableArgs } from '../../../shared/github/projects/project-request-types' +import type { GetProjectViewTableResult } from '../../../shared/github/projects/project-result-types' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' +import { + type GitHubProjectTable, + isRenderableProjectViewLayout +} from '../../../shared/github/projects/project-types' + +import { assertPositiveInt, assertSlug } from './project-argument-validation' import { fetchProjectViewsPage, fetchViewFieldsContinuation, diff --git a/src/main/github/project-view/pull-request-mutation.ts b/src/main/github/project-view/pull-request-mutation.ts index def6756c126..daa30172fc1 100644 --- a/src/main/github/project-view/pull-request-mutation.ts +++ b/src/main/github/project-view/pull-request-mutation.ts @@ -1,7 +1,9 @@ -import { githubBodyImageUrls, githubBodyMediaHeaders } from '../body-image-urls' -import { assertPositiveInt, projectGhExecOptions, runRest, validateSlugArgs } from './internals' -import type { GitHubProjectMutationResult } from '../../../shared/github/project-result-types' -import type { UpdatePullRequestBySlugArgs } from '../../../shared/github/project-request-types' +import { githubBodyImageUrls, githubBodyMediaHeaders } from '../attachments/body-image-urls' +import { assertPositiveInt, validateSlugArgs } from './project-argument-validation' +import { projectGhExecOptions } from './project-host-execution' +import { runRest } from './project-rest' +import type { GitHubProjectMutationResult } from '../../../shared/github/projects/project-result-types' +import type { UpdatePullRequestBySlugArgs } from '../../../shared/github/projects/project-request-types' export async function updatePullRequestBySlug( args: UpdatePullRequestBySlugArgs diff --git a/src/main/github/project-view/repository-field-options.ts b/src/main/github/project-view/repository-field-options.ts index ce1d80cfbf6..4e796212e8a 100644 --- a/src/main/github/project-view/repository-field-options.ts +++ b/src/main/github/project-view/repository-field-options.ts @@ -1,27 +1,22 @@ -import { - acquire, - extractExecError, - ghExecFileAsync, - noteRepositoryRateLimitSpend, - projectGhExecOptions, - projectHostAuthenticationError, - release, - repositoryRateLimitGuard, - runGraphql, - validateSlugArgs -} from './internals' +import { acquire, release } from '../execution/github-operation-slots' +import { extractExecError } from '../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' +import { projectGhExecOptions, projectHostAuthenticationError } from './project-host-execution' +import { runGraphql } from './project-graphql' +import { validateSlugArgs } from './project-argument-validation' import { classifyProjectError, rateLimitedError } from './project-error-classification' -import type { GitHubAssignableUser } from '../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' import type { ListAssignableUsersBySlugResult, ListIssueTypesBySlugResult, ListLabelsBySlugResult -} from '../../../shared/github/project-result-types' +} from '../../../shared/github/projects/project-result-types' import type { ListAssignableUsersBySlugArgs, ListIssueTypesBySlugArgs, ListLabelsBySlugArgs -} from '../../../shared/github/project-request-types' +} from '../../../shared/github/projects/project-request-types' export async function listLabelsBySlug( args: ListLabelsBySlugArgs diff --git a/src/main/github/body-image-read-adapters.test.ts b/src/main/github/project-view/work-item-details-body-image-adapters.test.ts similarity index 83% rename from src/main/github/body-image-read-adapters.test.ts rename to src/main/github/project-view/work-item-details-body-image-adapters.test.ts index 45a2f92aca0..0cebd926583 100644 --- a/src/main/github/body-image-read-adapters.test.ts +++ b/src/main/github/project-view/work-item-details-body-image-adapters.test.ts @@ -1,21 +1,32 @@ +import type * as GithubRepositoryIdentityModule from '../repositories/github-repository-identity' import { beforeEach, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ exec: vi.fn(), graphql: vi.fn() })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: mocks.exec, +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: mocks.exec +})) + +vi.mock('../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), ghRepoExecOptions: () => ({}), githubRepoContext: () => ({}) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ repositoryRateLimitGuard: () => ({ blocked: false }), noteRepositoryRateLimitSpend: () => {} })) -vi.mock('./project-view/internals', () => ({ +vi.mock('./project-argument-validation', () => ({ assertPositiveInt: () => ({ ok: true }), - validateSlugArgs: () => ({ ok: true }), - projectGhExecOptions: () => ({}), + validateSlugArgs: () => ({ ok: true }) +})) + +vi.mock('./project-host-execution', () => ({ + projectGhExecOptions: () => ({}) +})) + +vi.mock('./project-graphql', () => ({ runGraphql: mocks.graphql })) -import { getWorkItemDetailsBySlug } from './project-view/work-item-details' +import { getWorkItemDetailsBySlug } from './work-item-details' const body = '![shot](https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488)' diff --git a/src/main/github/project-view/work-item-details.ts b/src/main/github/project-view/work-item-details.ts index 6323f722a13..e3f22800254 100644 --- a/src/main/github/project-view/work-item-details.ts +++ b/src/main/github/project-view/work-item-details.ts @@ -1,10 +1,12 @@ -import { githubBodyImageUrls, removeFailedGitHubBodyHtml } from '../body-image-urls' -import { assertPositiveInt, projectGhExecOptions, runGraphql, validateSlugArgs } from './internals' -import type { PRComment } from '../../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../../shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../../shared/github/work-item-types' -import type { ProjectWorkItemDetailsBySlugResult } from '../../../shared/github/project-result-types' -import type { ProjectWorkItemDetailsBySlugArgs } from '../../../shared/github/project-request-types' +import { githubBodyImageUrls, removeFailedGitHubBodyHtml } from '../attachments/body-image-urls' +import { assertPositiveInt, validateSlugArgs } from './project-argument-validation' +import { projectGhExecOptions } from './project-host-execution' +import { runGraphql } from './project-graphql' +import type { PRComment } from '../../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItemDetails } from '../../../shared/github/work-items/work-item-types' +import type { ProjectWorkItemDetailsBySlugResult } from '../../../shared/github/projects/project-result-types' +import type { ProjectWorkItemDetailsBySlugArgs } from '../../../shared/github/projects/project-request-types' type RawUser = { login?: string; name?: string | null; avatarUrl?: string | null } type RawLabel = { name?: string; color?: string } diff --git a/src/main/github/conflict-summary-cache.ts b/src/main/github/pull-requests/conflicts/conflict-summary-cache.ts similarity index 95% rename from src/main/github/conflict-summary-cache.ts rename to src/main/github/pull-requests/conflicts/conflict-summary-cache.ts index 38da459528b..84b290a8044 100644 --- a/src/main/github/conflict-summary-cache.ts +++ b/src/main/github/pull-requests/conflicts/conflict-summary-cache.ts @@ -1,5 +1,5 @@ -import type { PRConflictSummary } from '../../shared/github/pull-request-types' -import { dedupeInFlightRun } from '../runtime/concurrency/in-flight-run-dedupe' +import type { PRConflictSummary } from '../../../../shared/github/pull-requests/pull-request-types' +import { dedupeInFlightRun } from '../../../runtime/concurrency/in-flight-run-dedupe' // Throttle even manual refreshes to one base fetch per minute; GitHub recomputes mergeability asynchronously. export const CONFLICT_SUMMARY_BASE_FETCH_WINDOW_MS = 60_000 diff --git a/src/main/github/conflict-summary.test.ts b/src/main/github/pull-requests/conflicts/conflict-summary.test.ts similarity index 81% rename from src/main/github/conflict-summary.test.ts rename to src/main/github/pull-requests/conflicts/conflict-summary.test.ts index 31f20c4ceef..d1881deb6cb 100644 --- a/src/main/github/conflict-summary.test.ts +++ b/src/main/github/pull-requests/conflicts/conflict-summary.test.ts @@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const gitExecFileAsyncMock = vi.hoisted(() => vi.fn()) const scratchFolderPrefixes = vi.hoisted((): string[] => []) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) @@ -28,8 +28,8 @@ vi.mock('node:fs/promises', async (importOriginal) => { } }) -import { findGitSubcommandIndex } from '../../shared/git/commands/git-command-classification' -import { GIT_CAPABILITY_RETRY_INTERVAL_MS } from '../../shared/git/commands/git-capability-cache' +import { findGitSubcommandIndex } from '../../../../shared/git/commands/git-command-classification' + import { __resetPRConflictSummaryCachesForTests, getPRConflictSummary } from './conflict-summary' type GitResult = { stdout: string } @@ -112,24 +112,6 @@ describe('getPRConflictSummary caching', () => { await expect(summary).resolves.toEqual(expectedSummary) }) - it('makes no scratch folder for a Git that rejects merge-tree --write-tree, retries included', async () => { - // Git 2.25's exact rejection of both argument shapes. - const rejected = Object.assign(new Error('Command failed: git merge-tree --write-tree'), { - code: 129, - stdout: '', - stderr: 'usage: git merge-tree \n' - }) - mockGitDispatch({ 'merge-tree': () => Promise.reject(rejected) }) - - await expect(deriveSummary('head-oid-1')).resolves.toBeUndefined() - vi.setSystemTime(1_750_000_000_000 + GIT_CAPABILITY_RETRY_INTERVAL_MS + 1_000) - await expect(deriveSummary('head-oid-2')).resolves.toBeUndefined() - - expect(spawnCount('merge-tree')).toBe(2) - expect(scratchFolderPrefixes).toEqual([]) - expect(gitExecFileAsyncMock.mock.calls.filter(([, options]) => options?.env)).toEqual([]) - }) - it('runs the legacy merge-tree fallback against the scratch object store too', async () => { const commonDir = join( realpathSync(mkdtempSync(join(tmpdir(), 'orca-conflict-summary-'))), diff --git a/src/main/github/conflict-summary.ts b/src/main/github/pull-requests/conflicts/conflict-summary.ts similarity index 93% rename from src/main/github/conflict-summary.ts rename to src/main/github/pull-requests/conflicts/conflict-summary.ts index 81901981c8d..808ded34973 100644 --- a/src/main/github/conflict-summary.ts +++ b/src/main/github/pull-requests/conflicts/conflict-summary.ts @@ -1,20 +1,20 @@ -import type { PRConflictSummary } from '../../shared/github/pull-request-types' +import type { PRConflictSummary } from '../../../../shared/github/pull-requests/pull-request-types' import { isUnsupportedMergeTreeMergeBaseError, isUnsupportedMergeTreeWriteTreeError -} from '../../shared/git/branches/git-merge-tree-capability' -import { gitExecFileAsync } from '../git/command-runner/git-exec-file' +} from '../../../../shared/git/branches/git-merge-tree-capability' +import { gitExecFileAsync } from '../../../git/command-runner/git-exec-file' import { gitOptionsForWorktree, type GitRuntimeOptions -} from '../git/command-runner/git-runtime-options' -import { createLocalGitObjectQuarantine } from '../git/objects/local-git-object-quarantine' -import { _resetCanonicalRepoKeyCacheForTests } from '../git/repositories/canonical-repo-key' -import { RuntimeRemoteFetchController } from '../runtime/repositories/runtime-remote-fetch-controller' +} from '../../../git/command-runner/git-runtime-options' +import { createLocalGitObjectQuarantine } from '../../../git/objects/local-git-object-quarantine' +import { _resetCanonicalRepoKeyCacheForTests } from '../../../git/repositories/canonical-repo-key' +import { RuntimeRemoteFetchController } from '../../../runtime/repositories/runtime-remote-fetch-controller' import { clearGitCapabilityStateForTests, withLocalGitCapabilityCacheForExecution -} from '../git/command-runner/host-routing/git-capability-state' +} from '../../../git/command-runner/host-routing/git-capability-state' import { __resetPRConflictSummaryDerivationCachesForTests, buildConflictSummaryCacheKey, diff --git a/src/main/github/pr-review-comment-lines.test.ts b/src/main/github/pull-requests/files/pr-review-comment-lines.test.ts similarity index 100% rename from src/main/github/pr-review-comment-lines.test.ts rename to src/main/github/pull-requests/files/pr-review-comment-lines.test.ts diff --git a/src/main/github/pr-review-comment-lines.ts b/src/main/github/pull-requests/files/pr-review-comment-lines.ts similarity index 100% rename from src/main/github/pr-review-comment-lines.ts rename to src/main/github/pull-requests/files/pr-review-comment-lines.ts diff --git a/src/main/github/pull-request-detail-context.test.ts b/src/main/github/pull-requests/files/pull-request-detail-context.test.ts similarity index 83% rename from src/main/github/pull-request-detail-context.test.ts rename to src/main/github/pull-requests/files/pull-request-detail-context.test.ts index 064883e4f31..ffa4ae31a1e 100644 --- a/src/main/github/pull-request-detail-context.test.ts +++ b/src/main/github/pull-requests/files/pull-request-detail-context.test.ts @@ -1,9 +1,14 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { LocalGitExecOptions } from './gh-utils' +import type { LocalGitExecOptions } from '../../repositories/github-repository-identity' const { exec, guard, spend } = vi.hoisted(() => ({ exec: vi.fn(), guard: vi.fn(), spend: vi.fn() })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: exec, +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: exec +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), githubRepoContext: ( repoPath: string, connectionId: string | null, @@ -14,7 +19,7 @@ vi.mock('./gh-utils', () => ({ ...options }) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ repositoryRateLimitGuard: guard, noteRepositoryRateLimitSpend: spend })) @@ -123,20 +128,6 @@ describe('PR detail context requests', () => { expect(graphqlCalls()).toHaveLength(2) }) - it('keeps the three-page file bound without resending participants', async () => { - mockReads( - [1, 2, 3].map((page) => - response(undefined, { - id: 'PR_42', - files: { ...files, pageInfo: { hasNextPage: true, endCursor: `cursor-${page}` } } - }) - ) - ) - const result = await getPRDetailContext('/repo', 42, repository) - expect(result.viewedStates?.pullRequestId).toBe('PR_42') - expect(graphqlCalls()).toHaveLength(3) - }) - it('recovers participants after a failed query without retrying unavailable viewed state', async () => { mockReads([new Error('Query failed'), response()]) const result = await getPRDetailContext('/repo', 42, repository) @@ -144,12 +135,4 @@ describe('PR detail context requests', () => { expect(result.viewedStates).toBeNull() expect(graphqlCalls()).toHaveLength(2) }) - - it('loads GraphQL context with no remaining REST quota', async () => { - guard.mockImplementation((_, bucket: string) => ({ blocked: bucket === 'core' })) - mockReads([response()]) - const result = await getPRDetailContext('/repo', 42, repository) - expect(result.viewedStates?.pullRequestId).toBe('PR_42') - expect(exec).toHaveBeenCalledTimes(1) - }) }) diff --git a/src/main/github/pull-request-detail-context.ts b/src/main/github/pull-requests/files/pull-request-detail-context.ts similarity index 90% rename from src/main/github/pull-request-detail-context.ts rename to src/main/github/pull-requests/files/pull-request-detail-context.ts index f99da036473..53949f5524b 100644 --- a/src/main/github/pull-request-detail-context.ts +++ b/src/main/github/pull-requests/files/pull-request-detail-context.ts @@ -2,18 +2,19 @@ import { z } from 'zod' import type { GitHubAssignableUser, GitHubPRFileViewedState -} from '../../shared/github/pull-request-types' -import { extractExecError } from '../git/command-runner/process-capture/exec-error' +} from '../../../../shared/github/pull-requests/pull-request-types' +import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' import { - ghExecFileAsync, ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions -} from './gh-utils' +} from '../../repositories/github-repository-identity' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' -import { getWorkItemParticipants } from './work-item-participants' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' +import { getWorkItemParticipants } from '../../work-items/work-item-participants' function detailContextQuery(includeContext: boolean): string { const context = includeContext diff --git a/src/main/github/pull-request-file-contents.ts b/src/main/github/pull-requests/files/pull-request-file-contents.ts similarity index 83% rename from src/main/github/pull-request-file-contents.ts rename to src/main/github/pull-requests/files/pull-request-file-contents.ts index 1c4ffd88ba9..333a7eaed73 100644 --- a/src/main/github/pull-request-file-contents.ts +++ b/src/main/github/pull-requests/files/pull-request-file-contents.ts @@ -1,21 +1,21 @@ -import { withGitHubRepoExecution } from './github-repository-identity' -import type { GitHubPRFile, GitHubPRFileContents } from '../../shared/github/pull-request-types' import { - acquire, - ghExecFileAsync, + withGitHubRepoExecution, ghRepoExecOptions, githubRepoContext, - release, type LocalGitExecOptions -} from './gh-utils' +} from '../../repositories/github-repository-identity' +import type { + GitHubPRFile, + GitHubPRFileContents +} from '../../../../shared/github/pull-requests/pull-request-types' +import { acquire, release } from '../../execution/github-operation-slots' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' -import { - githubHostExecOptions, - resolveGitHubRepoExecution, - type GitHubApiRepository -} from './github-api-repository' -import { isMaxBufferOverflowError } from '../git/command-runner/process-capture/max-buffer-overflow' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import { resolveGitHubRepoExecution } from '../../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import { isMaxBufferOverflowError } from '../../../git/command-runner/process-capture/max-buffer-overflow' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' const GITHUB_RAW_CONTENT_MAX_BUFFER_BYTES = 8 * 1024 * 1024 diff --git a/src/main/github/pull-request-file-data.ts b/src/main/github/pull-requests/files/pull-request-file-data.ts similarity index 86% rename from src/main/github/pull-request-file-data.ts rename to src/main/github/pull-requests/files/pull-request-file-data.ts index e4406581f91..4e177475a30 100644 --- a/src/main/github/pull-request-file-data.ts +++ b/src/main/github/pull-requests/files/pull-request-file-data.ts @@ -1,11 +1,17 @@ -import { GITHUB_BODY_MEDIA_TYPE, githubBodyImageUrls } from './body-image-urls' -import type { GitHubPRFile } from '../../shared/github/pull-request-types' +import { GITHUB_BODY_MEDIA_TYPE, githubBodyImageUrls } from '../../attachments/body-image-urls' +import type { GitHubPRFile } from '../../../../shared/github/pull-requests/pull-request-types' import type { PRFileViewedStatesResult } from './pull-request-detail-context' -import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from './gh-utils' -import type { LocalGitExecOptions } from './gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { + ghRepoExecOptions, + githubRepoContext, + type LocalGitExecOptions +} from '../../repositories/github-repository-identity' + +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { getPRReviewCommentLineNumbersFromPatch } from './pr-review-comment-lines' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' const MAX_PR_FILES = 300 diff --git a/src/main/github/merged-pr-commit-membership.ts b/src/main/github/pull-requests/refresh/merged-pr-commit-membership.ts similarity index 77% rename from src/main/github/merged-pr-commit-membership.ts rename to src/main/github/pull-requests/refresh/merged-pr-commit-membership.ts index 6432356e33d..a37471dae35 100644 --- a/src/main/github/merged-pr-commit-membership.ts +++ b/src/main/github/pull-requests/refresh/merged-pr-commit-membership.ts @@ -1,18 +1,17 @@ -import { ghExecFileAsync } from './gh-utils' -import { githubReadExecutionScope } from './github-read-execution-scope' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' -import { githubHostExecOptions, type GitHubRepoExecOptions } from './github-api-repository' -import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' -import type { OwnerRepo } from './github-repository-identity' - +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' import { - MergedReviewMembershipMemo, - type MergedReviewCommitMembership -} from '../source-control/merged-review-membership-memo' + type MergedReviewCommitMembership as MergedPRCommitMembership, + MergedReviewMembershipMemo +} from '../../../source-control/merged-review-membership-memo' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../../shared/github/repositories/repository-identity' const COMMIT_PULLS_PAGE_SIZE = 100 const COMMIT_PULLS_MAX_PAGES = 5 -export type MergedPRCommitMembership = MergedReviewCommitMembership const membershipMemo = new MergedReviewMembershipMemo() export function resetMergedPRCommitMembershipCacheForTest(): void { diff --git a/src/main/github/pr-mutation-review-invalidation.ts b/src/main/github/pull-requests/refresh/pr-mutation-review-invalidation.ts similarity index 63% rename from src/main/github/pr-mutation-review-invalidation.ts rename to src/main/github/pull-requests/refresh/pr-mutation-review-invalidation.ts index d1cde03cc86..71aeef45960 100644 --- a/src/main/github/pr-mutation-review-invalidation.ts +++ b/src/main/github/pull-requests/refresh/pr-mutation-review-invalidation.ts @@ -1,5 +1,5 @@ -import { getRepoExecutionHostId } from '../../shared/execution-host' -import { invalidateHostedReviewBranchCache } from '../source-control/hosted-review-branch-cache' +import { getRepoExecutionHostId } from '../../../../shared/execution-host' +import { invalidateHostedReviewBranchCache } from '../../../source-control/hosted-review-branch-cache' // A post-mutation refresh must not join an older lookup or reuse its cached answer. export function invalidateReviewLookupsAfterPRMutation( diff --git a/src/main/github/pr-refresh-candidate-policy.ts b/src/main/github/pull-requests/refresh/pr-refresh-candidate-policy.ts similarity index 93% rename from src/main/github/pr-refresh-candidate-policy.ts rename to src/main/github/pull-requests/refresh/pr-refresh-candidate-policy.ts index 696747fad2d..c846f032ada 100644 --- a/src/main/github/pr-refresh-candidate-policy.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-candidate-policy.ts @@ -1,15 +1,15 @@ -import { shouldAcceptMergedFallbackPR } from '../../shared/github/pr-branch-fallback-rule' -import { reviewRefreshIntervalMs } from '../../shared/review-refresh-policy' +import { shouldAcceptMergedFallbackPR } from '../../../../shared/github/pull-requests/refresh/pr-branch-fallback-rule' +import { reviewRefreshIntervalMs } from '../../../../shared/review-refresh-policy' import type { GitHubPRRefreshAlias, GitHubPRRefreshCandidate, GitHubPRRefreshReason, GitHubPRRefreshSkippedReason, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import type { GitHubPRBranchLookupOptions } from './client' -import { PR_CHECK_PAGE_LIMIT } from './client/check/pr-check-rollup' -import { parsePRKey } from './pr-watch/pr-watch-types' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { GitHubPRBranchLookupOptions } from '../../client/lookup/pull-request-lookup-data' +import { PR_CHECK_PAGE_LIMIT } from '../../client/check/pr-check-rollup' +import { parsePRKey } from '../../pr-watch/pr-watch-types' export const MANUAL_MERGEABILITY_PENDING_REFRESH_MS = 2_500 export const POST_PUSH_DELAY_MS = 2_500 diff --git a/src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-active-visible-priority.test.ts similarity index 82% rename from src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator-active-visible-priority.test.ts index af419d443a6..14c258f2d5c 100644 --- a/src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-active-visible-priority.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRInfo } from '../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') @@ -7,11 +7,17 @@ const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { }) vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) +vi.mock('../../client/lookup/pr-for-branch-outcome', () => { + return { + getPRForBranchOutcome: coordinatorMocks.getPRForBranchOutcomeMock + } +}) +vi.mock('../../repositories/github-api-repository-remote-probe', () => { + return { + getOriginGitHubApiRepository: coordinatorMocks.getOriginGitHubApiRepositoryMock + } +}) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' diff --git a/src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-alias-coalescing.test.ts similarity index 91% rename from src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator-alias-coalescing.test.ts index 62bf28d8e2c..db6f0138965 100644 --- a/src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-alias-coalescing.test.ts @@ -6,11 +6,17 @@ const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { }) vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) +vi.mock('../../client/lookup/pr-for-branch-outcome', () => { + return { + getPRForBranchOutcome: coordinatorMocks.getPRForBranchOutcomeMock + } +}) +vi.mock('../../repositories/github-api-repository-remote-probe', () => { + return { + getOriginGitHubApiRepository: coordinatorMocks.getOriginGitHubApiRepositoryMock + } +}) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' diff --git a/src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts new file mode 100644 index 00000000000..f5470dd7ad2 --- /dev/null +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts @@ -0,0 +1,58 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' + +import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' + +const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { + const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') + return { coordinatorMocks: moduleMocks.createPRRefreshCoordinatorMocks(), moduleMocks } +}) +vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) +vi.mock('../../client/lookup/pr-for-branch-outcome', () => { + return { + getPRForBranchOutcome: coordinatorMocks.getPRForBranchOutcomeMock + } +}) +vi.mock('../../repositories/github-api-repository-remote-probe', () => { + return { + getOriginGitHubApiRepository: coordinatorMocks.getOriginGitHubApiRepositoryMock + } +}) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) + +const { getPRForBranchOutcomeMock } = coordinatorMocks + +function found(state: 'open' | 'closed' | 'merged'): PRRefreshOutcome { + return { kind: 'found', pr: makePR({ state, checksStatus: 'success' }), fetchedAt: Date.now() } +} + +describe('main refresh completion ordering', () => { + beforeEach(() => moduleMocks.resetPRRefreshCoordinatorMocks(coordinatorMocks)) + afterEach(() => vi.useRealTimers()) + + it('preserves a newer failure gate when an older successful lookup completes', async () => { + let finishOlder: (outcome: PRRefreshOutcome) => void = () => {} + getPRForBranchOutcomeMock + .mockImplementationOnce( + () => new Promise((resolve) => (finishOlder = resolve)) + ) + .mockImplementation(async () => ({ + kind: 'upstream-error', + errorType: 'rate_limited', + message: 'wait', + fetchedAt: Date.now(), + retryDisabledUntil: Date.now() + 300_000 + })) + const { reportVisiblePRRefreshCandidates, refreshPRNow } = + await import('./pr-refresh-coordinator') + const candidate = makeCandidate({ isSelected: true }) + reportVisiblePRRefreshCandidates([candidate], 1, 1) + await vi.advanceTimersByTimeAsync(0) + await refreshPRNow(candidate) + finishOlder(found('open')) + await vi.advanceTimersByTimeAsync(60_000) + expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) + await expect(refreshPRNow(candidate)).resolves.toMatchObject({ errorType: 'rate_limited' }) + expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/main/github/pr-refresh-coordinator-test-harness.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-harness.ts similarity index 75% rename from src/main/github/pr-refresh-coordinator-test-harness.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-harness.ts index 7ee455bd205..61206270714 100644 --- a/src/main/github/pr-refresh-coordinator-test-harness.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-harness.ts @@ -1,5 +1,5 @@ -import type { GitHubPRRefreshCandidate } from '../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../shared/github/pull-request-types' +import type { GitHubPRRefreshCandidate } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' export function makeCandidate( overrides: Partial = {} diff --git a/src/main/github/pr-refresh-coordinator-test-mocks.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-mocks.ts similarity index 98% rename from src/main/github/pr-refresh-coordinator-test-mocks.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-mocks.ts index 2414b8514a8..cc79d26e1ea 100644 --- a/src/main/github/pr-refresh-coordinator-test-mocks.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-test-mocks.ts @@ -1,6 +1,6 @@ import type { PRRefreshRendererPort } from './pr-refresh-renderer-port' import { vi, type Mock } from 'vitest' -import { isWslUncPath } from '../../shared/wsl-paths' +import { isWslUncPath } from '../../../../shared/wsl-paths' type PRRefreshWebContentsMock = { id: number diff --git a/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-wsl-stopped-distro.test.ts similarity index 77% rename from src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator-wsl-stopped-distro.test.ts index 54060557c91..ae9ffdc3d13 100644 --- a/src/main/github/pr-refresh-coordinator-wsl-stopped-distro.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator-wsl-stopped-distro.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as WslUncAccessGateModule from '../wsl/filesystem/wsl-unc-access-gate' +import type * as WslUncAccessGateModule from '../../../wsl/filesystem/wsl-unc-access-gate' const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') @@ -12,12 +12,18 @@ const gate = vi.hoisted(() => ({ })) vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) -vi.mock('../wsl/filesystem/wsl-unc-access-gate', async (importOriginal) => ({ +vi.mock('../../client/lookup/pr-for-branch-outcome', () => { + return { + getPRForBranchOutcome: coordinatorMocks.getPRForBranchOutcomeMock + } +}) +vi.mock('../../repositories/github-api-repository-remote-probe', () => { + return { + getOriginGitHubApiRepository: coordinatorMocks.getOriginGitHubApiRepositoryMock + } +}) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) +vi.mock('../../../wsl/filesystem/wsl-unc-access-gate', async (importOriginal) => ({ ...(await importOriginal()), blockedWslDistroForPath: gate.blockedWslDistroForPath, wslDistroForPath: gate.wslDistroForPath @@ -49,7 +55,8 @@ describe('PR refresh for a stopped WSL distro (E9)', () => { it("drains outside a user act that scheduled it, so a stopped distro's queue is not admitted", async () => { const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const { isWslUserActFor, runAsWslUserAct } = await import('../wsl/discovery/wsl-user-act-scope') + const { isWslUserActFor, runAsWslUserAct } = + await import('../../../wsl/discovery/wsl-user-act-scope') const drainedInsideAct: boolean[] = [] gate.blockedWslDistroForPath.mockImplementation(async () => { drainedInsideAct.push(isWslUserActFor('Ubuntu')) diff --git a/src/main/github/pr-refresh-coordinator.ts b/src/main/github/pull-requests/refresh/pr-refresh-coordinator.ts similarity index 97% rename from src/main/github/pr-refresh-coordinator.ts rename to src/main/github/pull-requests/refresh/pr-refresh-coordinator.ts index 2fbb0d816ef..231f2911d74 100644 --- a/src/main/github/pr-refresh-coordinator.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-coordinator.ts @@ -2,8 +2,8 @@ import type { GitHubPRRefreshCandidate, GitHubPRRefreshReason, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import { getPRForBranchOutcome } from './client' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { getPRForBranchOutcome } from '../../client/lookup/pr-for-branch-outcome' import { aliasFromCandidate, hostedReviewOptionArgs, @@ -29,7 +29,10 @@ import { import { prRefreshRateLimitPausedUntil } from './pr-refresh-rate-limit-gate' import { PRRefreshRetryState } from './pr-refresh-retry-state' import { PRRefreshVisibility } from './pr-refresh-visibility' -import { blockedWslDistroForPath, wslDistroForPath } from '../wsl/filesystem/wsl-unc-access-gate' +import { + blockedWslDistroForPath, + wslDistroForPath +} from '../../../wsl/filesystem/wsl-unc-access-gate' const retry = new PRRefreshRetryState() const queue = new PRRefreshQueue((key) => retry.reset(key)) diff --git a/src/main/github/pr-refresh-error-classification.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-error-classification.test.ts similarity index 100% rename from src/main/github/pr-refresh-error-classification.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-error-classification.test.ts diff --git a/src/main/github/pr-refresh-error-classification.ts b/src/main/github/pull-requests/refresh/pr-refresh-error-classification.ts similarity index 93% rename from src/main/github/pr-refresh-error-classification.ts rename to src/main/github/pull-requests/refresh/pr-refresh-error-classification.ts index f44090b5a4f..c9f2f152db4 100644 --- a/src/main/github/pr-refresh-error-classification.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-error-classification.ts @@ -1,6 +1,6 @@ -import type { PRRefreshErrorType } from '../../shared/github/pull-request-refresh-types' -import { classifyGitHubUnavailable } from '../../shared/github/api-availability' -import { extractExecError } from '../git/command-runner/process-capture/exec-error' +import type { PRRefreshErrorType } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { classifyGitHubUnavailable } from '../../../../shared/github/api/api-availability' +import { extractExecError } from '../../../git/command-runner/process-capture/exec-error' /** Classify runner failures before UI rendering; rate-limit 403s precede permissions, and repo 404s precede network heuristics. */ export function classifyPRRefreshError(err: unknown): PRRefreshErrorType { diff --git a/src/main/github/pr-refresh-event-publisher.ts b/src/main/github/pull-requests/refresh/pr-refresh-event-publisher.ts similarity index 93% rename from src/main/github/pr-refresh-event-publisher.ts rename to src/main/github/pull-requests/refresh/pr-refresh-event-publisher.ts index 8dc831b7950..28af9df1b82 100644 --- a/src/main/github/pr-refresh-event-publisher.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-event-publisher.ts @@ -5,8 +5,8 @@ import type { GitHubPRRefreshReason, GitHubPRRefreshSkippedReason, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import { recordCoalescedCrashBreadcrumb } from '../crash-reporting/breadcrumbs/crash-breadcrumb-store' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { recordCoalescedCrashBreadcrumb } from '../../../crash-reporting/breadcrumbs/crash-breadcrumb-store' import { prRefreshRenderers } from './pr-refresh-renderer-port' // `aliases`: every checkout the read stood for (one read can serve several linked to the same PR). diff --git a/src/main/github/pr-refresh-pacing.ts b/src/main/github/pull-requests/refresh/pr-refresh-pacing.ts similarity index 100% rename from src/main/github/pr-refresh-pacing.ts rename to src/main/github/pull-requests/refresh/pr-refresh-pacing.ts diff --git a/src/main/github/pr-refresh-queue-drainer.ts b/src/main/github/pull-requests/refresh/pr-refresh-queue-drainer.ts similarity index 96% rename from src/main/github/pr-refresh-queue-drainer.ts rename to src/main/github/pull-requests/refresh/pr-refresh-queue-drainer.ts index 9f77729381f..9a4273da40b 100644 --- a/src/main/github/pr-refresh-queue-drainer.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-queue-drainer.ts @@ -2,8 +2,8 @@ import type { GitHubPRRefreshAlias, GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import { getPRForBranchOutcome } from './client' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { getPRForBranchOutcome } from '../../client/lookup/pr-for-branch-outcome' import { aliasFromCandidate, freshRetryAt, @@ -20,8 +20,11 @@ import { type PRRefreshPacing, usesActiveRefreshPacing } from './pr-refresh-paci import type { PRRefreshQueue, PRRefreshQueueEntry } from './pr-refresh-queue' import { PRWatchJobRunner } from './pr-refresh-watch-jobs' import { prRefreshRateLimitPausedUntil } from './pr-refresh-rate-limit-gate' -import { blockedWslDistroForPath, wslDistroForPath } from '../wsl/filesystem/wsl-unc-access-gate' -import { outsideWslUserAct } from '../wsl/discovery/wsl-user-act-scope' +import { + blockedWslDistroForPath, + wslDistroForPath +} from '../../../wsl/filesystem/wsl-unc-access-gate' +import { outsideWslUserAct } from '../../../wsl/discovery/wsl-user-act-scope' import type { PRRefreshRetryState } from './pr-refresh-retry-state' import type { PRRefreshVisibility } from './pr-refresh-visibility' diff --git a/src/main/github/pr-refresh-queue-growth-bound.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts similarity index 58% rename from src/main/github/pr-refresh-queue-growth-bound.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts index be7cd287da6..980557b97bc 100644 --- a/src/main/github/pr-refresh-queue-growth-bound.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { GitHubPRRefreshCandidate } from '../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { const moduleMocks = await import('./pr-refresh-coordinator-test-mocks') @@ -7,11 +7,17 @@ const { coordinatorMocks, moduleMocks } = await vi.hoisted(async () => { }) vi.mock('./pr-refresh-renderer-port', () => moduleMocks.rendererPortModuleMock(coordinatorMocks)) -vi.mock('./client', () => moduleMocks.clientModuleMock(coordinatorMocks)) -vi.mock('./github-api-repository', () => - moduleMocks.githubApiRepositoryModuleMock(coordinatorMocks) -) -vi.mock('./rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) +vi.mock('../../client/lookup/pr-for-branch-outcome', () => { + return { + getPRForBranchOutcome: coordinatorMocks.getPRForBranchOutcomeMock + } +}) +vi.mock('../../repositories/github-api-repository-remote-probe', () => { + return { + getOriginGitHubApiRepository: coordinatorMocks.getOriginGitHubApiRepositoryMock + } +}) +vi.mock('../../api/rate-limit', () => moduleMocks.rateLimitModuleMock(coordinatorMocks)) import { makeCandidate, makePR } from './pr-refresh-coordinator-test-harness' @@ -69,48 +75,4 @@ describe('pr-refresh queue growth bounds', () => { const lastBroadcast = broadcasts.at(-1) expect(lastBroadcast?.[1].aliases.map((alias) => alias.cacheKey)).toEqual(['/repo::churn/2']) }) - - it('does not restore an older candidate over a fresher branch switch', async () => { - const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') - const first = makeCandidate({ - cacheKey: '/repo::churn/1', - branch: 'churn/1', - worktreeId: 'wt-churn', - linkedPRNumber: 42, - cachedFetchedAt: null - }) - const switched = makeCandidate({ - cacheKey: '/repo::churn/2', - branch: 'churn/2', - worktreeId: 'wt-churn', - linkedPRNumber: 42, - cachedFetchedAt: Date.now(), - cachedHasPR: true, - cachedPRState: 'open', - cachedChecksStatus: 'success' - }) - - let didSwitch = false - getPRForBranchOutcomeMock.mockImplementation(async () => { - if (!didSwitch) { - didSwitch = true - reportVisiblePRRefreshCandidates([switched], 2, 1) - } - return { - kind: 'found' as const, - pr: makePR({ checksStatus: 'pending' as const }), - fetchedAt: Date.now() - } - }) - - reportVisiblePRRefreshCandidates([first], 1, 1) - await vi.advanceTimersByTimeAsync(119_999) - - expect(getPRForBranchOutcomeMock.mock.calls.map((call) => call[1])).toEqual(['churn/1']) - await vi.advanceTimersByTimeAsync(1) - expect(getPRForBranchOutcomeMock.mock.calls.map((call) => call[1])).toEqual([ - 'churn/1', - 'churn/2' - ]) - }) }) diff --git a/src/main/github/pr-refresh-queue.ts b/src/main/github/pull-requests/refresh/pr-refresh-queue.ts similarity index 98% rename from src/main/github/pr-refresh-queue.ts rename to src/main/github/pull-requests/refresh/pr-refresh-queue.ts index 57d4bfcf5b6..2c315cc9d00 100644 --- a/src/main/github/pr-refresh-queue.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-queue.ts @@ -1,9 +1,9 @@ -import { REVIEW_REFRESH_COOLDOWN_MS } from '../../shared/review-refresh-policy' +import { REVIEW_REFRESH_COOLDOWN_MS } from '../../../../shared/review-refresh-policy' import type { GitHubPRRefreshAlias, GitHubPRRefreshCandidate, GitHubPRRefreshReason -} from '../../shared/github/pull-request-refresh-types' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { aliasFromCandidate, bypassesFreshnessDelay, diff --git a/src/main/github/pr-refresh-rate-limit-gate.ts b/src/main/github/pull-requests/refresh/pr-refresh-rate-limit-gate.ts similarity index 64% rename from src/main/github/pr-refresh-rate-limit-gate.ts rename to src/main/github/pull-requests/refresh/pr-refresh-rate-limit-gate.ts index 19cee2e215d..1b23645c2da 100644 --- a/src/main/github/pr-refresh-rate-limit-gate.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-rate-limit-gate.ts @@ -1,7 +1,11 @@ -import type { GitHubPRRefreshCandidate } from '../../shared/github/pull-request-refresh-types' -import { getOriginGitHubApiRepository } from './github-api-repository' -import { ghRepoExecOptions, githubRepoContext } from './gh-utils' -import { getRateLimit, repositoryRateLimitGuard, spendsSharedGitHubComQuota } from './rate-limit' +import type { GitHubPRRefreshCandidate } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { getOriginGitHubApiRepository } from '../../repositories/github-api-repository-remote-probe' +import { ghRepoExecOptions, githubRepoContext } from '../../repositories/github-repository-identity' +import { + getRateLimit, + repositoryRateLimitGuard, + spendsSharedGitHubComQuota +} from '../../api/rate-limit' const BUCKETS = ['core', 'graphql'] as const diff --git a/src/main/github/pr-refresh-renderer-port.ts b/src/main/github/pull-requests/refresh/pr-refresh-renderer-port.ts similarity index 100% rename from src/main/github/pr-refresh-renderer-port.ts rename to src/main/github/pull-requests/refresh/pr-refresh-renderer-port.ts diff --git a/src/main/github/pr-refresh-retry-state.ts b/src/main/github/pull-requests/refresh/pr-refresh-retry-state.ts similarity index 90% rename from src/main/github/pr-refresh-retry-state.ts rename to src/main/github/pull-requests/refresh/pr-refresh-retry-state.ts index b4999b9c04d..13a29365156 100644 --- a/src/main/github/pr-refresh-retry-state.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-retry-state.ts @@ -1,8 +1,8 @@ import type { GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import { lookupBackoffDelayMs } from '../source-control/hosted-review-refresh-pacing' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import { lookupBackoffDelayMs } from '../../../source-control/hosted-review-refresh-pacing' import { refreshIntervalForCandidate } from './pr-refresh-candidate-policy' export class PRRefreshRetryState { diff --git a/src/main/github/pr-refresh-validation-backoff.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.test.ts similarity index 69% rename from src/main/github/pr-refresh-validation-backoff.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.test.ts index a67436916ac..4fc489afcda 100644 --- a/src/main/github/pr-refresh-validation-backoff.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.test.ts @@ -4,7 +4,7 @@ const { recordCoalescedCrashBreadcrumbMock } = vi.hoisted(() => ({ recordCoalescedCrashBreadcrumbMock: vi.fn() })) -vi.mock('../crash-reporting/breadcrumbs/crash-breadcrumb-store', () => ({ +vi.mock('../../../crash-reporting/breadcrumbs/crash-breadcrumb-store', () => ({ recordCoalescedCrashBreadcrumb: recordCoalescedCrashBreadcrumbMock })) @@ -19,18 +19,6 @@ describe('PR refresh validation backoff', () => { recordCoalescedCrashBreadcrumbMock.mockReset() }) - it('backs off repeated automatic validation denials until the TTL expires', () => { - const identity = { - repoId: 'repo-1', - repoPath: '/workspace/missing', - reason: 'unknown-repo' as const - } - - expect(notePRRefreshValidationDenial(identity, 0)).toBe('validation-denied') - expect(notePRRefreshValidationDenial(identity, 60_000)).toBe('validation-backoff') - expect(notePRRefreshValidationDenial(identity, 5 * 60_000 + 1)).toBe('validation-denied') - }) - it('records path-safe diagnostic breadcrumbs', () => { notePRRefreshValidationDenial( { diff --git a/src/main/github/pr-refresh-validation-backoff.ts b/src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.ts similarity index 96% rename from src/main/github/pr-refresh-validation-backoff.ts rename to src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.ts index 768b65c0bc6..c4d095037de 100644 --- a/src/main/github/pr-refresh-validation-backoff.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-validation-backoff.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto' import { resolve } from 'node:path' -import { recordCoalescedCrashBreadcrumb } from '../crash-reporting/breadcrumbs/crash-breadcrumb-store' +import { recordCoalescedCrashBreadcrumb } from '../../../crash-reporting/breadcrumbs/crash-breadcrumb-store' const VALIDATION_BACKOFF_TTL_MS = 5 * 60_000 const MAX_VALIDATION_BACKOFF_ENTRIES = 256 diff --git a/src/main/github/pr-refresh-visibility.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-visibility.test.ts similarity index 100% rename from src/main/github/pr-refresh-visibility.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-visibility.test.ts diff --git a/src/main/github/pr-refresh-visibility.ts b/src/main/github/pull-requests/refresh/pr-refresh-visibility.ts similarity index 96% rename from src/main/github/pr-refresh-visibility.ts rename to src/main/github/pull-requests/refresh/pr-refresh-visibility.ts index 1ed101de164..d46cf26cefa 100644 --- a/src/main/github/pr-refresh-visibility.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-visibility.ts @@ -1,4 +1,4 @@ -import type { GitHubPRRefreshCandidate } from '../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { refreshKey } from './pr-refresh-candidate-policy' import { prRefreshRenderers } from './pr-refresh-renderer-port' diff --git a/src/main/github/pr-refresh-watch-derived-data.test.ts b/src/main/github/pull-requests/refresh/pr-refresh-watch-derived-data.test.ts similarity index 82% rename from src/main/github/pr-refresh-watch-derived-data.test.ts rename to src/main/github/pull-requests/refresh/pr-refresh-watch-derived-data.test.ts index c78f021f55e..9d47254c3bc 100644 --- a/src/main/github/pr-refresh-watch-derived-data.test.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-watch-derived-data.test.ts @@ -1,6 +1,6 @@ import { afterEach, expect, it, vi } from 'vitest' -import type { PRRefreshOutcome } from '../../shared/github/pull-request-refresh-types' -import type { PullRequestLookupData } from './client/lookup/pull-request-lookup-data' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PullRequestLookupData } from '../../client/lookup/pull-request-lookup-data' import { makeCandidate } from './pr-refresh-coordinator-test-harness' const fixture = vi.hoisted(() => ({ @@ -8,26 +8,33 @@ const fixture = vi.hoisted(() => ({ conflict: vi.fn(), stack: { number: 2, position: 1, size: 2, baseRefName: 'main', baseSha: 'base-sha' } })) -vi.mock('./github-api-repository', () => ({ +vi.mock('../../repositories/github-api-repository', () => ({ resolveGitHubApiRepositoryCandidates: async () => ({ candidates: [{ owner: 'acme', repo: 'widgets' }], headRepo: { owner: 'acme', repo: 'widgets' } - }), + }) +})) + +vi.mock('../../repositories/github-api-repository-remote-probe', () => ({ getOriginGitHubApiRepository: async () => ({ owner: 'acme', repo: 'widgets' }) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ repositoryRateLimitGuard: () => ({ blocked: false }), spendsSharedGitHubComQuota: () => false })) -vi.mock('./client/lookup/pr-number-lookup', () => ({ lookupPRByNumber: fixture.lookup })) -vi.mock('./client/lookup/pr-stack-summary-cache', () => ({ +vi.mock('../../client/lookup/pr-number-lookup', () => ({ lookupPRByNumber: fixture.lookup })) +vi.mock('../../client/lookup/pr-stack-summary-cache', () => ({ getCachedGitHubPRStackSummary: async () => fixture.stack })) -vi.mock('./github-pr-stack', () => ({ hydrateGitHubPRStack: async () => fixture.stack })) -vi.mock('./client/detect/repository-merge-metadata', () => ({ +vi.mock('../stacks/github-pr-stack', () => ({ + hydrateGitHubPRStack: async () => fixture.stack +})) +vi.mock('../../client/detect/repository-merge-metadata', () => ({ detectRepositoryMergeMetadata: async () => ({ mergeQueueRequired: true }) })) -vi.mock('./conflict-summary', () => ({ getPRConflictSummary: fixture.conflict })) +vi.mock('../conflicts/conflict-summary', () => ({ + getPRConflictSummary: fixture.conflict +})) const data: PullRequestLookupData = { number: 12, @@ -58,7 +65,7 @@ it('watches preserve provider checks while full and phone reads retain derived d const { addPRRefreshOutcomeObserver, schedulePRWatchRead } = await import('./pr-refresh-coordinator') const { getPRForBranchOutcome, readRecentPhonePROutcome } = - await import('./client/lookup/pr-for-branch-outcome') + await import('../../client/lookup/pr-for-branch-outcome') const candidate = makeCandidate({ branch: 'topic', linkedPRNumber: 12 }) const watched = Promise.withResolvers() const unsubscribe = addPRRefreshOutcomeObserver((_, outcome) => watched.resolve(outcome)) diff --git a/src/main/github/pr-refresh-watch-jobs.ts b/src/main/github/pull-requests/refresh/pr-refresh-watch-jobs.ts similarity index 95% rename from src/main/github/pr-refresh-watch-jobs.ts rename to src/main/github/pull-requests/refresh/pr-refresh-watch-jobs.ts index 861f60efcd1..f51c5f4ea79 100644 --- a/src/main/github/pr-refresh-watch-jobs.ts +++ b/src/main/github/pull-requests/refresh/pr-refresh-watch-jobs.ts @@ -1,9 +1,9 @@ import type { GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../shared/github/pull-request-types' -import { reviewRefreshIntervalMs } from '../../shared/review-refresh-policy' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' +import { reviewRefreshIntervalMs } from '../../../../shared/review-refresh-policy' import type { PRRefreshQueue, PRRefreshQueueEntry } from './pr-refresh-queue' import type { PRRefreshRetryState } from './pr-refresh-retry-state' diff --git a/src/main/github/pull-request-changed-events.test.ts b/src/main/github/pull-requests/refresh/pull-request-changed-events.test.ts similarity index 93% rename from src/main/github/pull-request-changed-events.test.ts rename to src/main/github/pull-requests/refresh/pull-request-changed-events.test.ts index 2f846d7612a..e7e56edb336 100644 --- a/src/main/github/pull-request-changed-events.test.ts +++ b/src/main/github/pull-requests/refresh/pull-request-changed-events.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { GitHubPRRefreshCandidate } from '../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../shared/github/pull-request-types' +import type { GitHubPRRefreshCandidate } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' import { pullRequestChangeObserver, type PullRequestChange } from './pull-request-changed-events' const candidate: GitHubPRRefreshCandidate = { diff --git a/src/main/github/pull-request-changed-events.ts b/src/main/github/pull-requests/refresh/pull-request-changed-events.ts similarity index 89% rename from src/main/github/pull-request-changed-events.ts rename to src/main/github/pull-requests/refresh/pull-request-changed-events.ts index a7171c3a76d..d334e7be290 100644 --- a/src/main/github/pull-request-changed-events.ts +++ b/src/main/github/pull-requests/refresh/pull-request-changed-events.ts @@ -1,6 +1,6 @@ -import type { PRRefreshOutcome } from '../../shared/github/pull-request-refresh-types' -import type { CheckStatus } from '../../shared/github/pull-request-types' -import type { RuntimeClientEvent } from '../../shared/runtime-client-events' +import type { PRRefreshOutcome } from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { CheckStatus } from '../../../../shared/github/pull-requests/pull-request-types' +import type { RuntimeClientEvent } from '../../../../shared/runtime-client-events' import type { PRRefreshOutcomeObserver } from './pr-refresh-event-publisher' export type PullRequestChange = Omit< diff --git a/src/main/github/github-pr-stack-async-merge.ts b/src/main/github/pull-requests/stacks/github-pr-stack-async-merge.ts similarity index 86% rename from src/main/github/github-pr-stack-async-merge.ts rename to src/main/github/pull-requests/stacks/github-pr-stack-async-merge.ts index 9f999484e5f..5d91a577ee8 100644 --- a/src/main/github/github-pr-stack-async-merge.ts +++ b/src/main/github/pull-requests/stacks/github-pr-stack-async-merge.ts @@ -1,12 +1,10 @@ -import type { GitHubPRMergeMethod } from '../../shared/github/pull-request-types' -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import { acquire, release } from './gh-utils' -import { - githubHostExecOptions, - type GitHubApiRepository, - type GitHubRepoExecOptions -} from './github-api-repository' -import { codeHostFailure } from '../../shared/code-host-failure-copy' +import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-requests/pull-request-types' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { acquire, release } from '../../execution/github-operation-slots' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' +import { codeHostFailure } from '../../../../shared/code-host-failure-copy' const POLL_INTERVAL_MS = 1_000 const MAX_POLLS = 180 diff --git a/src/main/github/github-pr-stack.test.ts b/src/main/github/pull-requests/stacks/github-pr-stack.test.ts similarity index 85% rename from src/main/github/github-pr-stack.test.ts rename to src/main/github/pull-requests/stacks/github-pr-stack.test.ts index 0447ca3b7df..402fca60bf5 100644 --- a/src/main/github/github-pr-stack.test.ts +++ b/src/main/github/pull-requests/stacks/github-pr-stack.test.ts @@ -6,15 +6,16 @@ const { ghExecFileAsyncMock, rateLimitGuardMock, noteRateLimitSpendMock } = vi.h noteRateLimitSpendMock: vi.fn() })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: ghExecFileAsyncMock })) -vi.mock('./rate-limit', () => ({ +vi.mock('../../api/rate-limit', () => ({ repositoryRateLimitGuard: rateLimitGuardMock, noteRepositoryRateLimitSpend: noteRateLimitSpendMock })) -import { _resetGitHubPRStackCacheForTests, mergeGitHubPRStack } from './github-pr-stack' +import { _resetGitHubPRStackCacheForTests } from './github-pr-stack' +import { mergeGitHubPRStack } from './github-pr-stack-async-merge' const repository = { owner: 'stablyai', repo: 'orca', host: 'github.com' } diff --git a/src/main/github/github-pr-stack.ts b/src/main/github/pull-requests/stacks/github-pr-stack.ts similarity index 91% rename from src/main/github/github-pr-stack.ts rename to src/main/github/pull-requests/stacks/github-pr-stack.ts index fac1742b00f..e20b8a64bb0 100644 --- a/src/main/github/github-pr-stack.ts +++ b/src/main/github/pull-requests/stacks/github-pr-stack.ts @@ -5,16 +5,14 @@ import type { PRMergeableState, PRReviewDecision, PRState -} from '../../shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import { githubReadExecutionScope } from './github-read-execution-scope' -import { - githubHostExecOptions, - type GitHubApiRepository, - type GitHubRepoExecOptions -} from './github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +} from '../../../../shared/github/pull-requests/pull-request-types' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../../source-control/code-host-read-execution-scope' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubRepoExecOptions } from '../../repositories/github-api-repository' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../api/rate-limit' const STACK_CACHE_TTL_MS = 30_000 const STACK_CACHE_MAX_ENTRIES = 256 @@ -281,5 +279,3 @@ export async function hydrateGitHubPRStack( } } } - -export { mergeGitHubPRStack } from './github-pr-stack-async-merge' diff --git a/src/main/github/github-stack-api-responses.ts b/src/main/github/pull-requests/stacks/github-stack-api-responses.ts similarity index 93% rename from src/main/github/github-stack-api-responses.ts rename to src/main/github/pull-requests/stacks/github-stack-api-responses.ts index 6d312db90d6..dc80b86ca80 100644 --- a/src/main/github/github-stack-api-responses.ts +++ b/src/main/github/pull-requests/stacks/github-stack-api-responses.ts @@ -1,4 +1,4 @@ -import type { HostedReviewSummary } from '../../shared/source-control/hosted-review/hosted-review-contracts' +import type { HostedReviewSummary } from '../../../../shared/source-control/hosted-review/hosted-review-contracts' export type NumberedHostedReviewSummary = Omit & { number: number } diff --git a/src/main/github/stacked-pr-creation.test.ts b/src/main/github/pull-requests/stacks/stacked-pr-creation.test.ts similarity index 88% rename from src/main/github/stacked-pr-creation.test.ts rename to src/main/github/pull-requests/stacks/stacked-pr-creation.test.ts index 5c7a800bf37..138059d4e61 100644 --- a/src/main/github/stacked-pr-creation.test.ts +++ b/src/main/github/pull-requests/stacks/stacked-pr-creation.test.ts @@ -1,3 +1,4 @@ +import type * as GithubRepositoryIdentityModule from '../../repositories/github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' const { ghExecFileAsyncMock, repositoryMock } = vi.hoisted(() => ({ @@ -5,10 +6,17 @@ const { ghExecFileAsyncMock, repositoryMock } = vi.hoisted(() => ({ repositoryMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ +vi.mock('../../execution/github-operation-slots', () => ({ acquire: vi.fn(), - release: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, + release: vi.fn() +})) + +vi.mock('../../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), ghRepoExecOptions: (context: { repoPath: string; connectionId?: string | null }) => context.connectionId ? {} : { cwd: context.repoPath }, githubRepoContext: ( @@ -18,8 +26,11 @@ vi.mock('./gh-utils', () => ({ ) => ({ repoPath, connectionId, localGitOptions }) })) -vi.mock('./github-api-repository', () => ({ - getOriginGitHubApiRepository: repositoryMock, +vi.mock('../../repositories/github-api-repository-remote-probe', () => ({ + getOriginGitHubApiRepository: repositoryMock +})) + +vi.mock('../../repositories/github-repository-host', () => ({ githubHostExecOptions: (repository: { host?: string }) => ({ host: repository.host }) })) diff --git a/src/main/github/stacked-pr-creation.ts b/src/main/github/pull-requests/stacks/stacked-pr-creation.ts similarity index 88% rename from src/main/github/stacked-pr-creation.ts rename to src/main/github/pull-requests/stacks/stacked-pr-creation.ts index 025b3c0cb8b..aaf54d9d4c2 100644 --- a/src/main/github/stacked-pr-creation.ts +++ b/src/main/github/pull-requests/stacks/stacked-pr-creation.ts @@ -1,24 +1,24 @@ -import type { ExecutionHostId } from '../../shared/execution-host' -import { hostedReviewSshConnectionId } from '../source-control/hosted-review-execution-host' +import type { ExecutionHostId } from '../../../../shared/execution-host' +import { hostedReviewSshConnectionId } from '../../../source-control/hosted-review-execution-host' import type { CreateStackedHostedReviewInput, CreateStackedHostedReviewResult -} from '../../shared/source-control/hosted-review/hosted-review-contracts' -import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key' +} from '../../../../shared/source-control/hosted-review/hosted-review-contracts' +import { isDefaultGitHubHost } from '../../../../shared/github/repositories/repository-identity-key' import { normalizeHostedReviewBaseRef, normalizeHostedReviewHeadRef -} from '../../shared/source-control/hosted-review/hosted-review-refs' -import { acquire, ghExecFileAsync, ghRepoExecOptions, githubRepoContext, release } from './gh-utils' -import { - getOriginGitHubApiRepository, - githubHostExecOptions, - type GitHubApiRepository -} from './github-api-repository' +} from '../../../../shared/source-control/hosted-review/hosted-review-refs' +import { acquire, release } from '../../execution/github-operation-slots' +import { ghExecFileAsync } from '../../../git/command-runner/hosted-cli/gh-exec-file' +import { ghRepoExecOptions, githubRepoContext } from '../../repositories/github-repository-identity' +import { getOriginGitHubApiRepository } from '../../repositories/github-api-repository-remote-probe' +import { githubHostExecOptions } from '../../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../../shared/github/repositories/repository-identity' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions -} from '../source-control/hosted-review-git-options' +} from '../../../source-control/hosted-review-git-options' import { parseGitHubStackPullRequests, parseGitHubStacks, @@ -26,7 +26,7 @@ import { type GitHubStackPullRequest, type NumberedHostedReviewSummary } from './github-stack-api-responses' -import { stackedReviewFailure } from '../../shared/source-control/hosted-review/stacked-hosted-review-failure-copy' +import { stackedReviewFailure } from '../../../../shared/source-control/hosted-review/stacked-hosted-review-failure-copy' type StackedPullRequestPlan = | { diff --git a/src/main/github/pr-head-tracking-ref.test.ts b/src/main/github/pull-requests/start-points/pr-head-tracking-ref.test.ts similarity index 91% rename from src/main/github/pr-head-tracking-ref.test.ts rename to src/main/github/pull-requests/start-points/pr-head-tracking-ref.test.ts index 1a49de8278d..f1680f37592 100644 --- a/src/main/github/pr-head-tracking-ref.test.ts +++ b/src/main/github/pull-requests/start-points/pr-head-tracking-ref.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' const { gitExecFileAsyncMock } = vi.hoisted(() => ({ gitExecFileAsyncMock: vi.fn() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) diff --git a/src/main/github/pr-head-tracking-ref.ts b/src/main/github/pull-requests/start-points/pr-head-tracking-ref.ts similarity index 83% rename from src/main/github/pr-head-tracking-ref.ts rename to src/main/github/pull-requests/start-points/pr-head-tracking-ref.ts index f49ce5ae2da..ce8c4739afc 100644 --- a/src/main/github/pr-head-tracking-ref.ts +++ b/src/main/github/pull-requests/start-points/pr-head-tracking-ref.ts @@ -1,17 +1,17 @@ -import { gitExecFileAsync } from '../git/command-runner/git-exec-file' +import { gitExecFileAsync } from '../../../git/command-runner/git-exec-file' import { githubPullRequestHeadLocalRef, isSafeReviewHeadFetchRemote, isValidReviewHeadNumber, REVIEW_HEAD_FETCH_TIMEOUT_MS -} from '../../shared/review-head-tracking-ref' -import { getReviewHeadRemoteComponent } from '../git/remotes/review-head-remote-identity' +} from '../../../../shared/review-head-tracking-ref' +import { getReviewHeadRemoteComponent } from '../../../git/remotes/review-head-remote-identity' import { SSH_GIT_RECONNECT_MESSAGE, SshHostNotServedError -} from '../providers/ssh-host-not-served-error' -import { getRepoRoutingConnectionId } from '../../shared/execution-host' -import type { Repo } from '../../shared/repo-types' +} from '../../../providers/ssh-host-not-served-error' +import { getRepoRoutingConnectionId } from '../../../../shared/execution-host' +import type { Repo } from '../../../../shared/repo-types' type LocalGitExecOptions = { cwd: string diff --git a/src/main/github/pr-start-point-wsl-gate.ts b/src/main/github/pull-requests/start-points/pr-start-point-wsl-gate.ts similarity index 81% rename from src/main/github/pr-start-point-wsl-gate.ts rename to src/main/github/pull-requests/start-points/pr-start-point-wsl-gate.ts index 7cd1665c67a..0bd2d02a041 100644 --- a/src/main/github/pr-start-point-wsl-gate.ts +++ b/src/main/github/pull-requests/start-points/pr-start-point-wsl-gate.ts @@ -1,10 +1,10 @@ -import { wslDistroStoppedMessage } from '../../shared/wsl-distro-not-running-refusal' -import type { ReviewStartPointFailure } from '../../shared/review-start-point-failure-copy' +import { wslDistroStoppedMessage } from '../../../../shared/wsl-distro-not-running-refusal' +import type { ReviewStartPointFailure } from '../../../../shared/review-start-point-failure-copy' import { guardWslUncPathAccess, WslDistroStoppedError, WslDistroUnverifiableError -} from '../wsl/filesystem/wsl-unc-access-gate' +} from '../../../wsl/filesystem/wsl-unc-access-gate' /** * Resolves a PR start point unless its repo's WSL distro is stopped (E9): a background push-target diff --git a/src/main/github/pr-start-point.test.ts b/src/main/github/pull-requests/start-points/pr-start-point.test.ts similarity index 96% rename from src/main/github/pr-start-point.test.ts rename to src/main/github/pull-requests/start-points/pr-start-point.test.ts index f3bab273c52..0284bf81769 100644 --- a/src/main/github/pr-start-point.test.ts +++ b/src/main/github/pull-requests/start-points/pr-start-point.test.ts @@ -5,13 +5,16 @@ const { getPullRequestPushTargetMock, getWorkItemMock } = vi.hoisted(() => ({ getWorkItemMock: vi.fn() })) -vi.mock('./client', () => ({ - getPullRequestPushTarget: getPullRequestPushTargetMock, +vi.mock('../../client/lookup/pull-request-push-target', () => ({ + getPullRequestPushTarget: getPullRequestPushTargetMock +})) + +vi.mock('../../client/fetch/get-work-item', () => ({ getWorkItem: getWorkItemMock })) import { resolveGitHubPrStartPoint } from './pr-start-point' -import { reviewHeadRemoteRefComponent } from '../../shared/review-head-tracking-ref' +import { reviewHeadRemoteRefComponent } from '../../../../shared/review-head-tracking-ref' const ORIGIN_URL = 'git@github.com:acme/orca.git' const ORIGIN_COMPONENT = reviewHeadRemoteRefComponent('origin', ORIGIN_URL) diff --git a/src/main/github/pr-start-point.ts b/src/main/github/pull-requests/start-points/pr-start-point.ts similarity index 90% rename from src/main/github/pr-start-point.ts rename to src/main/github/pull-requests/start-points/pr-start-point.ts index 9428809d5c3..7f20de2c027 100644 --- a/src/main/github/pr-start-point.ts +++ b/src/main/github/pull-requests/start-points/pr-start-point.ts @@ -1,16 +1,17 @@ -import { withGitHubRepoExecution } from './github-repository-identity' -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { GhAccountBinding } from '../../shared/github/account-binding' -import type { GitHubPrStartPoint, GitPushTarget } from '../../shared/worktree/types' -import { fetchCompareBaseRefWithLocalFallback } from '../git/remotes/compare-base-ref-fetch' -import { isMissingRemoteRefGitError } from '../../shared/git/refs/git-missing-remote-ref' -import { isTransientReviewHeadFetchError } from '../git/remotes/fetch-error-classification' -import { getPullRequestPushTarget, getWorkItem } from './client' +import { withGitHubRepoExecution } from '../../repositories/github-repository-identity' +import type { IssueSourcePreference } from '../../../../shared/repo-types' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' +import type { GitHubPrStartPoint, GitPushTarget } from '../../../../shared/worktree/types' +import { fetchCompareBaseRefWithLocalFallback } from '../../../git/remotes/compare-base-ref-fetch' +import { isMissingRemoteRefGitError } from '../../../../shared/git/refs/git-missing-remote-ref' +import { isTransientReviewHeadFetchError } from '../../../git/remotes/fetch-error-classification' +import { getPullRequestPushTarget } from '../../client/lookup/pull-request-push-target' +import { getWorkItem } from '../../client/fetch/get-work-item' import { githubPullRequestHeadLocalRef, reviewHeadRemoteRefComponent -} from '../../shared/review-head-tracking-ref' -import { reviewStartPointFailure } from '../../shared/review-start-point-failure-copy' +} from '../../../../shared/review-head-tracking-ref' +import { reviewStartPointFailure } from '../../../../shared/review-start-point-failure-copy' type GitExec = (args: string[]) => Promise<{ stdout: string; stderr: string }> diff --git a/src/main/github/review-head-remote.ts b/src/main/github/pull-requests/start-points/review-head-remote.ts similarity index 77% rename from src/main/github/review-head-remote.ts rename to src/main/github/pull-requests/start-points/review-head-remote.ts index 7089a78bec5..89c4dc1069e 100644 --- a/src/main/github/review-head-remote.ts +++ b/src/main/github/pull-requests/start-points/review-head-remote.ts @@ -1,8 +1,8 @@ -import type { IssueSourcePreference } from '../../shared/repo-types' -import type { GhAccountBinding } from '../../shared/github/account-binding' -import { pickPreferredGitRemote } from '../../shared/preferred-git-remote' -import { getDefaultRemote } from '../git/remotes/repo-remote-queries' -import { getGitHubApiRepositoryForRemote } from './github-api-repository' +import type { IssueSourcePreference } from '../../../../shared/repo-types' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' +import { pickPreferredGitRemote } from '../../../../shared/preferred-git-remote' +import { getDefaultRemote } from '../../../git/remotes/repo-remote-queries' +import { getGitHubApiRepositoryForRemote } from '../../repositories/github-api-repository-remote-probe' type GitExec = (args: string[]) => Promise<{ stdout: string; stderr: string }> diff --git a/src/main/github/github-api-repository-probe.ts b/src/main/github/repositories/github-api-repository-probe.ts similarity index 100% rename from src/main/github/github-api-repository-probe.ts rename to src/main/github/repositories/github-api-repository-probe.ts diff --git a/src/main/github/github-api-repository-remote-probe.test.ts b/src/main/github/repositories/github-api-repository-remote-probe.test.ts similarity index 63% rename from src/main/github/github-api-repository-remote-probe.test.ts rename to src/main/github/repositories/github-api-repository-remote-probe.test.ts index ecd8f8ab7a4..fe39dadd3a9 100644 --- a/src/main/github/github-api-repository-remote-probe.test.ts +++ b/src/main/github/repositories/github-api-repository-remote-probe.test.ts @@ -1,17 +1,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' +import type * as GitExecFileModule from '../../git/command-runner/git-exec-file' const { gitExecFileAsyncMock } = vi.hoisted(() => ({ gitExecFileAsyncMock: vi.fn<(args: string[]) => Promise<{ stdout: string; stderr: string }>>() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: gitExecFileAsyncMock })) -import { _resetOwnerRepoCache } from './gh-utils' -import { _resetRemoteNameListingCache } from '../git/remotes/remote-name-listing' +import { _resetOwnerRepoCache } from './github-repository-identity' +import { _resetRemoteNameListingCache } from '../../git/remotes/remote-name-listing' import { _resetOriginGitHubApiRepositoryCache, getGitHubApiRepositoryForRemote @@ -40,12 +40,6 @@ function mockRemotes(remotes: Record): void { }) } -function getUrlCalls(remoteName: string): number { - return gitExecFileAsyncMock.mock.calls.filter( - ([args]) => args[0] === 'remote' && args[1] === 'get-url' && args[2] === remoteName - ).length -} - beforeEach(() => { vi.useFakeTimers() gitExecFileAsyncMock.mockReset() @@ -59,17 +53,6 @@ afterEach(() => { }) describe('getGitHubApiRepositoryForRemote', () => { - it('does not run get-url for a non-origin remote the listing says is absent', async () => { - mockRemotes({ origin: 'https://github.com/acme/widgets.git' }) - - for (let poll = 0; poll < 3; poll += 1) { - await expect(getGitHubApiRepositoryForRemote(REPO_PATH, 'upstream')).resolves.toBeNull() - await vi.advanceTimersByTimeAsync(60_000) - } - - expect(getUrlCalls('upstream')).toBe(0) - }) - it('still resolves a configured upstream remote', async () => { mockRemotes({ origin: 'https://github.com/acme/orca.git', @@ -82,11 +65,4 @@ describe('getGitHubApiRepositoryForRemote', () => { host: 'github.com' }) }) - - it('probes origin even when the listing omits it', async () => { - mockRemotes({}) - - await expect(getGitHubApiRepositoryForRemote(REPO_PATH, 'origin')).resolves.toBeNull() - expect(getUrlCalls('origin')).toBeGreaterThan(0) - }) }) diff --git a/src/main/github/github-api-repository-remote-probe.ts b/src/main/github/repositories/github-api-repository-remote-probe.ts similarity index 94% rename from src/main/github/github-api-repository-remote-probe.ts rename to src/main/github/repositories/github-api-repository-remote-probe.ts index b7c59ed52d0..0ebec3d2603 100644 --- a/src/main/github/github-api-repository-remote-probe.ts +++ b/src/main/github/repositories/github-api-repository-remote-probe.ts @@ -1,11 +1,12 @@ -import { withGitHubRepoExecution } from './github-repository-identity' -import type { GitHubApiRepository } from './github-api-repository' -import { shouldProbeGitRemote } from '../git/remotes/remote-name-listing' import { + withGitHubRepoExecution, getOwnerRepoForRemote, type GitHubRemoteIdentityProbeOptions, type LocalGitExecOptions -} from './gh-utils' +} from './github-repository-identity' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import { shouldProbeGitRemote } from '../../git/remotes/remote-name-listing' + import { getEnterpriseGitHubRepoSlug, getEnterpriseGitHubRepoSlugForRemote diff --git a/src/main/github/github-api-repository-validation.ts b/src/main/github/repositories/github-api-repository-validation.ts similarity index 81% rename from src/main/github/github-api-repository-validation.ts rename to src/main/github/repositories/github-api-repository-validation.ts index f0edbafb865..c7eb0956367 100644 --- a/src/main/github/github-api-repository-validation.ts +++ b/src/main/github/repositories/github-api-repository-validation.ts @@ -1,5 +1,5 @@ -import { GITHUB_OWNER_SLUG_RE } from '../../shared/github/owner-slug' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +import { GITHUB_OWNER_SLUG_RE } from '../../../shared/github/repositories/owner-slug' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' export type GitHubApiRepositoryResolution = | GitHubOwnerRepo diff --git a/src/main/github/github-api-repository.test.ts b/src/main/github/repositories/github-api-repository.test.ts similarity index 59% rename from src/main/github/github-api-repository.test.ts rename to src/main/github/repositories/github-api-repository.test.ts index fb3da271755..023ff616841 100644 --- a/src/main/github/github-api-repository.test.ts +++ b/src/main/github/repositories/github-api-repository.test.ts @@ -1,6 +1,7 @@ +import type * as GithubOwnerRepoSelectionModule from './github-owner-repo-selection' +import type * as GithubRepositoryIdentityModule from './github-repository-identity' +import type * as GithubEnterpriseRepositoryModule from './github-enterprise-repository' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GitHubEnterpriseRepository from './github-enterprise-repository' -import type * as GhUtils from './gh-utils' const { getEnterpriseGitHubRepoSlugMock, @@ -16,29 +17,30 @@ const { shouldProbeGitRemoteMock: vi.fn(async () => true) })) -vi.mock('./gh-utils', async (importOriginal) => ({ - ...(await importOriginal()), - getOwnerRepo: getOwnerRepoMock, +vi.mock('./github-owner-repo-selection', async (importOriginal) => ({ + ...(await importOriginal()), + getOwnerRepo: getOwnerRepoMock +})) + +vi.mock('./github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), getOwnerRepoForRemote: getOwnerRepoForRemoteMock })) vi.mock('./github-enterprise-repository', async (importOriginal) => ({ - ...(await importOriginal()), + ...(await importOriginal()), getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock, isGitHubHostAuthenticated: isGitHubHostAuthenticatedMock })) -vi.mock('../git/remotes/remote-name-listing', () => ({ +vi.mock('../../git/remotes/remote-name-listing', () => ({ shouldProbeGitRemote: shouldProbeGitRemoteMock })) import { _resetOriginGitHubApiRepositoryCache, - getGitHubApiRepositoryForRemote, - getOriginGitHubApiRepository, - resolveGitHubApiRepository, - resolveGitHubApiRepositoryCandidates, - resolveGitHubRepoExecution -} from './github-api-repository' + getGitHubApiRepositoryForRemote +} from './github-api-repository-remote-probe' +import { resolveGitHubApiRepository, resolveGitHubRepoExecution } from './github-api-repository' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() @@ -123,59 +125,4 @@ describe('origin repository cache', () => { ) ).rejects.toThrow('GitHub repository identity is unverifiable.') }) - - it('does not reuse a tolerant SSH miss for verified candidate discovery', async () => { - const enterprise = { - owner: 'acme', - repo: 'widgets', - host: 'github.acme-corp.com' - } - getEnterpriseGitHubRepoSlugMock.mockResolvedValueOnce(null).mockResolvedValueOnce(enterprise) - - await expect(getOriginGitHubApiRepository('/remote/repo', 'ssh-1')).resolves.toBeNull() - await expect( - getGitHubApiRepositoryForRemote( - '/remote/repo', - 'origin', - 'ssh-1', - {}, - { - requireVerifiedSshProbe: true - } - ) - ).resolves.toEqual(enterprise) - expect(getEnterpriseGitHubRepoSlugMock).toHaveBeenCalledTimes(2) - }) -}) - -describe('skip missing upstream remote probes', () => { - it('observes and propagates a verified origin probe failure while listing remotes', async () => { - let releaseRemoteProbe: (value: boolean) => void = () => undefined - shouldProbeGitRemoteMock.mockReturnValue( - new Promise((resolve) => { - releaseRemoteProbe = resolve - }) - ) - const originError = new Error('origin probe failed') - getOwnerRepoForRemoteMock.mockImplementation(async (_path, remote) => { - if (remote === 'origin') { - throw originError - } - return { owner: 'stablyai', repo: 'orca' } - }) - - const resultPromise = resolveGitHubApiRepositoryCandidates('/repo') - await vi.waitFor(() => - expect(getOwnerRepoForRemoteMock).toHaveBeenCalledWith( - '/repo', - 'origin', - undefined, - {}, - { requireVerifiedSshProbe: true } - ) - ) - releaseRemoteProbe(true) - - await expect(resultPromise).rejects.toBe(originError) - }) }) diff --git a/src/main/github/github-api-repository.ts b/src/main/github/repositories/github-api-repository.ts similarity index 90% rename from src/main/github/github-api-repository.ts rename to src/main/github/repositories/github-api-repository.ts index 10177d912e0..7e33081061f 100644 --- a/src/main/github/github-api-repository.ts +++ b/src/main/github/repositories/github-api-repository.ts @@ -1,13 +1,19 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' -import { withGitHubRepoExecution } from './github-repository-identity' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import type { IssueSourcePreference } from '../../shared/repo-types' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' +import { + withGitHubRepoExecution, + ghRepoExecOptions, + githubRepoContext, + type LocalGitExecOptions +} from './github-repository-identity' + +import type { IssueSourcePreference } from '../../../shared/repo-types' import { githubRepoIdentityKey, isDefaultGitHubHost -} from '../../shared/github/repository-identity-key' -import { shouldProbeGitRemote } from '../git/remotes/remote-name-listing' -import { ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions } from './gh-utils' +} from '../../../shared/github/repositories/repository-identity-key' +import { shouldProbeGitRemote } from '../../git/remotes/remote-name-listing' + import { isGitHubHostAuthenticated } from './github-enterprise-repository' import { githubHostExecOptions } from './github-repository-host' import { @@ -19,13 +25,6 @@ import { getGitHubApiRepositoryForRemote, getOriginGitHubApiRepository } from './github-api-repository-remote-probe' - -export { - githubHostExecOptions, - githubRepositorySlugArg, - githubRepositoryWebHost -} from './github-repository-host' -export type GitHubApiRepository = GitHubOwnerRepo export type GitHubRepoExecOptions = ReturnType & { host?: string env?: NodeJS.ProcessEnv @@ -34,11 +33,6 @@ export type GitHubRepoExecution = { ownerRepo: GitHubApiRepository | null ghOptions: GitHubRepoExecOptions } -export { - _resetOriginGitHubApiRepositoryCache, - getGitHubApiRepositoryForRemote, - getOriginGitHubApiRepository -} /** Hosted mirror of getIssueOwnerRepo: issues prefer `upstream` over `origin`. */ export async function getIssueGitHubApiRepository( diff --git a/src/main/github/github-enterprise-repository.test.ts b/src/main/github/repositories/github-enterprise-repository.test.ts similarity index 94% rename from src/main/github/github-enterprise-repository.test.ts rename to src/main/github/repositories/github-enterprise-repository.test.ts index ca4770a740d..1b066f87cad 100644 --- a/src/main/github/github-enterprise-repository.test.ts +++ b/src/main/github/repositories/github-enterprise-repository.test.ts @@ -9,22 +9,22 @@ const { commandExecFileAsyncMock, ghExecFileAsyncMock, gitExecFileAsyncMock, res })) // Mock only execution so identity, runtime and auth-status parsing remain real. -vi.mock('../git/command-runner/process-capture/command-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/process-capture/command-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), commandExecFileAsync: commandExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: ghExecFileAsyncMock })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) -vi.mock('../ssh/config/ssh-g-config-resolution', () => ({ +vi.mock('../../ssh/config/ssh-g-config-resolution', () => ({ resolveWithSshG: resolveWithSshGMock })) import { @@ -33,7 +33,7 @@ import { isGitHubHostAuthenticated } from './github-enterprise-repository' import { _resetSshHostnameResolutionCache } from './github-ssh-host-alias-resolution' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository-remote-probe' function mockOriginRemote(url: string): void { gitExecFileAsyncMock.mockImplementation(async (args: string[]) => { diff --git a/src/main/github/github-enterprise-repository.ts b/src/main/github/repositories/github-enterprise-repository.ts similarity index 91% rename from src/main/github/github-enterprise-repository.ts rename to src/main/github/repositories/github-enterprise-repository.ts index d17ca6bb6c3..2f2ef2c6dfd 100644 --- a/src/main/github/github-enterprise-repository.ts +++ b/src/main/github/repositories/github-enterprise-repository.ts @@ -1,28 +1,29 @@ -import { githubReadExecutionScope } from './github-read-execution-scope' -import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +import { codeHostReadExecutionScope as githubReadExecutionScope } from '../../source-control/code-host-read-execution-scope' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions -} from '../source-control/hosted-review-git-options' -import { parseAuthStatus } from './auth-diagnose' +} from '../../source-control/hosted-review-git-options' +import { parseAuthStatus } from '../accounts/auth-diagnose' import { ghRepoExecOptions, getRemoteUrlForRepo, githubRepoContext, - parseGitHubRemoteIdentity, type LocalGitExecOptions } from './github-repository-identity' import { + parseGitHubRemoteIdentity, effectiveGitHubRemoteHost, gitHubSshConfigHostAlias } from './github-remote-identity-parsing' + import { resolveSshConfigHostname } from './github-ssh-host-alias-resolution' -import { parseWslPath } from '../wsl/filesystem/unc-path-access' -import { isStableMissingGitRemoteError } from '../git/remotes/stable-missing-git-remote-error' -import { requireReachableGitRoute } from '../providers/execution-host-provider-dispatch' -import { getConnectionExecutionHostId } from '../../shared/execution-host' -import { getPublicForgeProviderForHost } from '../../shared/git/remotes/public-forge-host' +import { parseWslPath } from '../../wsl/filesystem/unc-path-access' +import { isStableMissingGitRemoteError } from '../../git/remotes/stable-missing-git-remote-error' +import { requireReachableGitRoute } from '../../providers/execution-host-provider-dispatch' +import { getConnectionExecutionHostId } from '../../../shared/execution-host' +import { getPublicForgeProviderForHost } from '../../../shared/git/remotes/public-forge-host' export type GitHubEnterpriseRepoSlug = GitHubOwnerRepo & { host: string } diff --git a/src/main/github/github-owner-repo-selection.ts b/src/main/github/repositories/github-owner-repo-selection.ts similarity index 83% rename from src/main/github/github-owner-repo-selection.ts rename to src/main/github/repositories/github-owner-repo-selection.ts index e41aadad2d7..1d44f2b2969 100644 --- a/src/main/github/github-owner-repo-selection.ts +++ b/src/main/github/repositories/github-owner-repo-selection.ts @@ -1,11 +1,8 @@ -import type { IssueSourcePreference } from '../../shared/repo-types' -import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' -import { shouldProbeGitRemote } from '../git/remotes/remote-name-listing' -import { - getOwnerRepoForRemote, - type LocalGitExecOptions, - type OwnerRepo -} from './github-repository-identity' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import { githubRepoIdentityKey } from '../../../shared/github/repositories/repository-identity-key' +import { shouldProbeGitRemote } from '../../git/remotes/remote-name-listing' +import { getOwnerRepoForRemote, type LocalGitExecOptions } from './github-repository-identity' +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../shared/github/repositories/repository-identity' export async function getOwnerRepo( repoPath: string, @@ -28,8 +25,6 @@ export async function getOwnerRepo( return originPromise } -export const getIssueOwnerRepo = getOwnerRepo - export type PRRepositoryCandidates = { candidates: OwnerRepo[] headRepo: OwnerRepo | null @@ -103,7 +98,7 @@ export async function resolveIssueSource( } } return { - source: await getIssueOwnerRepo(repoPath, connectionId, localGitOptions), + source: await getOwnerRepo(repoPath, connectionId, localGitOptions), fellBack: false } } diff --git a/src/main/github/github-remote-identity-parsing.ts b/src/main/github/repositories/github-remote-identity-parsing.ts similarity index 94% rename from src/main/github/github-remote-identity-parsing.ts rename to src/main/github/repositories/github-remote-identity-parsing.ts index 613d9e0f1a0..6ee2eeae8f6 100644 --- a/src/main/github/github-remote-identity-parsing.ts +++ b/src/main/github/repositories/github-remote-identity-parsing.ts @@ -1,5 +1,5 @@ -import { normalizeGitHubRemoteHost } from '../../shared/git/remotes/git-remote-host-alias' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +import { normalizeGitHubRemoteHost } from '../../../shared/git/remotes/git-remote-host-alias' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' export type GitHubRemoteIdentity = GitHubOwnerRepo & { host: string } diff --git a/src/main/github/client-pr-local-runtime.test.ts b/src/main/github/repositories/github-repository-client-refusal.test.ts similarity index 79% rename from src/main/github/client-pr-local-runtime.test.ts rename to src/main/github/repositories/github-repository-client-refusal.test.ts index 1b975b67916..5b74697b056 100644 --- a/src/main/github/client-pr-local-runtime.test.ts +++ b/src/main/github/repositories/github-repository-client-refusal.test.ts @@ -1,3 +1,5 @@ +import type * as GitExecFileModule from '../../git/command-runner/git-exec-file' +import type * as GithubRepositoryIdentityModule from './github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' type RateLimitGuardResult = @@ -36,23 +38,41 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ - execFileAsync: execFileAsyncMock, - ghExecFileAsync: ghExecFileAsyncMock, - gitExecFileAsync: gitExecFileAsyncMock, +vi.mock('../execution/github-legacy-command', () => ({ + execFileAsync: execFileAsyncMock +})) + +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal()), + gitExecFileAsync: gitExecFileAsyncMock +})) + +vi.mock('../../git/command-runner/process-capture/exec-error', async (importOriginal) => ({ + ...(await importOriginal>()), extractExecError: (err: unknown) => ({ stderr: err instanceof Error ? err.message : String(err), stdout: '' - }), + }) +})) + +vi.mock('../execution/github-operation-slots', () => ({ acquire: acquireMock, - release: releaseMock, + release: releaseMock +})) + +vi.mock('./github-owner-repo-selection', () => ({ getOwnerRepo: getOwnerRepoMock, - getIssueOwnerRepo: getIssueOwnerRepoMock, - getOwnerRepoForRemote: getOwnerRepoForRemoteMock, resolvePRRepositoryCandidates: resolvePRRepositoryCandidatesMock, - resolveIssueSource: vi.fn(), - classifyGhError: (message: string) => ({ type: 'unknown', message }), - classifyListIssuesError: (message: string) => ({ type: 'unknown', message }), + resolveIssueSource: vi.fn() +})) + +vi.mock('./github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), + getOwnerRepoForRemote: getOwnerRepoForRemoteMock, ghRepoExecOptions: (context: { repoPath: string connectionId?: string | null @@ -74,9 +94,10 @@ vi.mock('./gh-utils', () => ({ _resetOwnerRepoCache: vi.fn() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../api/gh-error-classification', async (importOriginal) => ({ ...(await importOriginal>()), - gitExecFileAsync: gitExecFileAsyncMock + classifyGhError: (message: string) => ({ type: 'unknown', message }), + classifyListIssuesError: (message: string) => ({ type: 'unknown', message }) })) vi.mock('./github-enterprise-repository', () => ({ @@ -85,11 +106,11 @@ vi.mock('./github-enterprise-repository', () => ({ isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -vi.mock('./conflict-summary', () => ({ +vi.mock('../pull-requests/conflicts/conflict-summary', () => ({ getPRConflictSummary: vi.fn() })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ getRateLimit: getRateLimitMock, rateLimitGuard: rateLimitGuardMock, noteRateLimitSpend: noteRateLimitSpendMock, @@ -102,19 +123,16 @@ vi.mock('./rate-limit', () => ({ (!repository?.host || repository.host.toLowerCase() === 'github.com') && !executionOptions?.wslDistro })) -import { - addPRReviewCommentReply, - getPRChecks, - getPRComments, - mergePR, - removePRReviewers, - requestPRReviewers, - resolveReviewThread, - setPRAutoMerge, - updatePRTitle -} from './client' +import { addPRReviewCommentReply } from '../client/create/add-pr-review-comment' +import { getPRChecks } from '../client/check/get-pr-checks' +import { getPRComments } from '../client/fetch/get-pr-comments' +import { mergePR } from '../client/merge/merge-pr' +import { removePRReviewers, requestPRReviewers } from '../client/update/pr-reviewers' +import { resolveReviewThread } from '../client/update/resolve-review-thread' +import { setPRAutoMerge } from '../client/merge/pr-auto-merge' +import { updatePRTitle } from '../client/update/pr-details' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() diff --git a/src/main/github/github-repository-host.ts b/src/main/github/repositories/github-repository-host.ts similarity index 84% rename from src/main/github/github-repository-host.ts rename to src/main/github/repositories/github-repository-host.ts index 250f71d38c3..54aebb5d056 100644 --- a/src/main/github/github-repository-host.ts +++ b/src/main/github/repositories/github-repository-host.ts @@ -1,4 +1,4 @@ -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' export function githubHostExecOptions(repository: GitHubOwnerRepo | null | undefined): { host?: string diff --git a/src/main/github/gh-utils.test.ts b/src/main/github/repositories/github-repository-identity-cache.test.ts similarity index 93% rename from src/main/github/gh-utils.test.ts rename to src/main/github/repositories/github-repository-identity-cache.test.ts index 032c9411a5e..63bba89f548 100644 --- a/src/main/github/gh-utils.test.ts +++ b/src/main/github/repositories/github-repository-identity-cache.test.ts @@ -7,23 +7,23 @@ const { gitExecFileAsyncMock } = vi.hoisted(() => ({ gitExecFileAsyncMock: vi.fn() })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal>()), ghExecFileAsync: vi.fn() })) -import { _resetRemoteNameListingCache } from '../git/remotes/remote-name-listing' +import { _resetRemoteNameListingCache } from '../../git/remotes/remote-name-listing' import { _getOwnerRepoCacheSize, _resetOwnerRepoCache, - getOwnerRepo, getOwnerRepoForRemote -} from './gh-utils' +} from './github-repository-identity' +import { getOwnerRepo } from './github-owner-repo-selection' import { __resetLocalGitConfigSignatureCacheForTests } from './local-git-config-signature' function gitRemoteGetUrlCalls(remoteName: string): unknown[][] { diff --git a/src/main/github/github-repository-identity.fork-owner-repo.test.ts b/src/main/github/repositories/github-repository-identity.fork-owner-repo.test.ts similarity index 82% rename from src/main/github/github-repository-identity.fork-owner-repo.test.ts rename to src/main/github/repositories/github-repository-identity.fork-owner-repo.test.ts index 20c8bf454f3..96ab146ab3b 100644 --- a/src/main/github/github-repository-identity.fork-owner-repo.test.ts +++ b/src/main/github/repositories/github-repository-identity.fork-owner-repo.test.ts @@ -1,5 +1,5 @@ -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import type * as GhExecFileModule from '../git/command-runner/hosted-cli/gh-exec-file' +import type * as GitExecFileModule from '../../git/command-runner/git-exec-file' +import type * as GhExecFileModule from '../../git/command-runner/hosted-cli/gh-exec-file' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { gitExecFileAsyncMock, ghExecFileAsyncMock, readLocalGitConfigSignatureMock } = vi.hoisted( @@ -10,12 +10,12 @@ const { gitExecFileAsyncMock, ghExecFileAsyncMock, readLocalGitConfigSignatureMo }) ) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: gitExecFileAsyncMock })) -vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) => ({ ...(await importOriginal()), ghExecFileAsync: ghExecFileAsyncMock })) @@ -24,11 +24,11 @@ vi.mock('./local-git-config-signature', () => ({ readLocalGitConfigSignature: readLocalGitConfigSignatureMock })) -import { _resetRemoteNameListingCache } from '../git/remotes/remote-name-listing' +import { _resetRemoteNameListingCache } from '../../git/remotes/remote-name-listing' import { _resetOwnerRepoCache } from './github-repository-identity' -import { probeRepoUpstream } from './client/fetch/repo-slug-upstream' +import { probeRepoUpstream } from '../client/fetch/repo-slug-upstream' -import { acquire, release } from './gh-utils' +import { acquire, release } from '../execution/github-operation-slots' afterEach(() => vi.useRealTimers()) diff --git a/src/main/github/github-repository-identity.signed-cache.test.ts b/src/main/github/repositories/github-repository-identity.signed-cache.test.ts similarity index 53% rename from src/main/github/github-repository-identity.signed-cache.test.ts rename to src/main/github/repositories/github-repository-identity.signed-cache.test.ts index 2cd8252f856..adc4d5e20dd 100644 --- a/src/main/github/github-repository-identity.signed-cache.test.ts +++ b/src/main/github/repositories/github-repository-identity.signed-cache.test.ts @@ -1,12 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type * as GitRunner from '../git/command-runner/git-exec-file' +import type * as GitRunner from '../../git/command-runner/git-exec-file' const { gitExecFileAsyncMock, readLocalGitConfigSignatureMock } = vi.hoisted(() => ({ gitExecFileAsyncMock: vi.fn(), readLocalGitConfigSignatureMock: vi.fn<() => Promise>(async () => 'sig-1') })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: gitExecFileAsyncMock })) @@ -19,7 +19,6 @@ import { getOwnerRepoForRemote, _resetOwnerRepoCache } from './github-repository const REPO = '/tmp/signed-cache-repo' const THIRTY_SECONDS = 30_000 -const FOUR_MINUTES = 4 * 60_000 let remoteUrl = 'https://github.com/stablyai/orca.git' @@ -37,24 +36,6 @@ beforeEach(() => { }) describe('owner/repo identity cache', () => { - it('holds a signed identity past the unsigned TTL instead of re-spawning git', async () => { - vi.useFakeTimers() - await expect(getOwnerRepoForRemote(REPO, 'origin')).resolves.toEqual({ - owner: 'stablyai', - repo: 'orca' - }) - expect(remoteGetUrlCalls()).toBe(1) - - // Advance beyond unsigned TTL while remaining within the signed entry lifetime. - vi.setSystemTime(Date.now() + FOUR_MINUTES) - await expect(getOwnerRepoForRemote(REPO, 'origin')).resolves.toEqual({ - owner: 'stablyai', - repo: 'orca' - }) - expect(remoteGetUrlCalls()).toBe(1) - vi.useRealTimers() - }) - it('re-probes as soon as the git config signature changes', async () => { vi.useFakeTimers() await getOwnerRepoForRemote(REPO, 'origin') @@ -71,26 +52,4 @@ describe('owner/repo identity cache', () => { expect(remoteGetUrlCalls()).toBe(2) vi.useRealTimers() }) - - it('keeps the short TTL when no signature can be read', async () => { - vi.useFakeTimers() - readLocalGitConfigSignatureMock.mockImplementation(async () => undefined) - await getOwnerRepoForRemote(REPO, 'origin') - expect(remoteGetUrlCalls()).toBe(1) - - vi.setSystemTime(Date.now() + THIRTY_SECONDS + 1) - await getOwnerRepoForRemote(REPO, 'origin') - expect(remoteGetUrlCalls()).toBe(2) - vi.useRealTimers() - }) - - it('still coalesces concurrent lookups onto one probe', async () => { - const [first, second] = await Promise.all([ - getOwnerRepoForRemote(REPO, 'origin'), - getOwnerRepoForRemote(REPO, 'origin') - ]) - expect(first).toEqual({ owner: 'stablyai', repo: 'orca' }) - expect(second).toEqual(first) - expect(remoteGetUrlCalls()).toBe(1) - }) }) diff --git a/src/main/github/github-repository-identity.ssh-host-alias.test.ts b/src/main/github/repositories/github-repository-identity.ssh-host-alias.test.ts similarity index 91% rename from src/main/github/github-repository-identity.ssh-host-alias.test.ts rename to src/main/github/repositories/github-repository-identity.ssh-host-alias.test.ts index 5ebf41be4b7..ae219555fe9 100644 --- a/src/main/github/github-repository-identity.ssh-host-alias.test.ts +++ b/src/main/github/repositories/github-repository-identity.ssh-host-alias.test.ts @@ -1,5 +1,5 @@ -import type * as CommandExecFileModule from '../git/command-runner/process-capture/command-exec-file' -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' +import type * as CommandExecFileModule from '../../git/command-runner/process-capture/command-exec-file' +import type * as GitExecFileModule from '../../git/command-runner/git-exec-file' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -14,12 +14,12 @@ const { readLocalGitConfigSignatureMock: vi.fn(async () => 'sig-10284') })) -vi.mock('../git/command-runner/process-capture/command-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/process-capture/command-exec-file', async (importOriginal) => ({ ...(await importOriginal()), commandExecFileAsync: commandExecFileAsyncMock })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync: gitExecFileAsyncMock })) @@ -28,7 +28,7 @@ vi.mock('./local-git-config-signature', () => ({ readLocalGitConfigSignature: readLocalGitConfigSignatureMock })) -vi.mock('../ssh/config/ssh-g-config-resolution', () => ({ +vi.mock('../../ssh/config/ssh-g-config-resolution', () => ({ resolveWithSshG: resolveWithSshGMock })) diff --git a/src/main/github/github-repository-identity.ts b/src/main/github/repositories/github-repository-identity.ts similarity index 90% rename from src/main/github/github-repository-identity.ts rename to src/main/github/repositories/github-repository-identity.ts index 03a5ae8d5b4..3d382e5b8bb 100644 --- a/src/main/github/github-repository-identity.ts +++ b/src/main/github/repositories/github-repository-identity.ts @@ -1,29 +1,21 @@ +import type { GitHubRepositoryIdentity as OwnerRepo } from '../../../shared/github/repositories/repository-identity' import { AsyncLocalStorage } from 'node:async_hooks' -import { runCoalescedProbe, type CoalescedProbes } from '../git/remotes/coalesced-probe' -import { readRemoteUrl } from '../git/remotes/remote-url-probe' +import { runCoalescedProbe, type CoalescedProbes } from '../../git/remotes/coalesced-probe' +import { readRemoteUrl } from '../../git/remotes/remote-url-probe' import { ghAccountBindingsEqual, normalizeGhAccountBinding, type GhAccountBinding -} from '../../shared/github/account-binding' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +} from '../../../shared/github/accounts/account-binding' + import { readLocalGitConfigSignature } from './local-git-config-signature' -import { - parseGitHubOwnerRepo, - parseGitHubRemoteIdentity, - type GitHubRemoteIdentity -} from './github-remote-identity-parsing' + import { classifyGitHubOwnerRepoFromRemoteUrl } from './github-ssh-host-alias-resolution' -import { isStableMissingGitRemoteError } from '../git/remotes/stable-missing-git-remote-error' -import type { GitAdmissionTier } from '../../shared/rpc-contract/git-admission-tier-params' -import { requireReachableGitRoute } from '../providers/execution-host-provider-dispatch' -import { getConnectionExecutionHostId } from '../../shared/execution-host' -import { resolveGitHubProjectAccount } from './github-project-account' - -export type OwnerRepo = GitHubOwnerRepo - -export type { GitHubRemoteIdentity } -export { parseGitHubOwnerRepo, parseGitHubRemoteIdentity } +import { isStableMissingGitRemoteError } from '../../git/remotes/stable-missing-git-remote-error' +import type { GitAdmissionTier } from '../../../shared/rpc-contract/git-admission-tier-params' +import { requireReachableGitRoute } from '../../providers/execution-host-provider-dispatch' +import { getConnectionExecutionHostId } from '../../../shared/execution-host' +import { resolveGitHubProjectAccount } from '../accounts/github-project-account' export type GitHubRepoContext = { repoPath: string diff --git a/src/main/github/github-ssh-host-alias-resolution.ts b/src/main/github/repositories/github-ssh-host-alias-resolution.ts similarity index 93% rename from src/main/github/github-ssh-host-alias-resolution.ts rename to src/main/github/repositories/github-ssh-host-alias-resolution.ts index 5432a6f8c90..769c5e59cf4 100644 --- a/src/main/github/github-ssh-host-alias-resolution.ts +++ b/src/main/github/repositories/github-ssh-host-alias-resolution.ts @@ -1,7 +1,7 @@ -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import { commandExecFileAsync } from '../git/command-runner/process-capture/command-exec-file' -import { parseWslPath } from '../wsl/filesystem/unc-path-access' -import { resolveWithSshG } from '../ssh/config/ssh-g-config-resolution' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' +import { commandExecFileAsync } from '../../git/command-runner/process-capture/command-exec-file' +import { parseWslPath } from '../../wsl/filesystem/unc-path-access' +import { resolveWithSshG } from '../../ssh/config/ssh-g-config-resolution' import { gitHubSshConfigHostAlias, parseGitHubOwnerRepo, diff --git a/src/main/github/local-git-config-signature.ts b/src/main/github/repositories/local-git-config-signature.ts similarity index 98% rename from src/main/github/local-git-config-signature.ts rename to src/main/github/repositories/local-git-config-signature.ts index 7d20e2e2b66..b67d31f4ca1 100644 --- a/src/main/github/local-git-config-signature.ts +++ b/src/main/github/repositories/local-git-config-signature.ts @@ -1,7 +1,7 @@ import { readFile, stat } from 'node:fs/promises' import { homedir } from 'node:os' import { dirname, isAbsolute, join, resolve } from 'node:path' -import { runCoalescedProbe, type CoalescedProbes } from '../git/remotes/coalesced-probe' +import { runCoalescedProbe, type CoalescedProbes } from '../../git/remotes/coalesced-probe' import type { GitHubRepoContext } from './github-repository-identity' type LocalGitConfigPaths = { diff --git a/src/main/github/work-item-details-enterprise-host.test.ts b/src/main/github/work-item-details-enterprise-host.test.ts deleted file mode 100644 index e0e1647be75..00000000000 --- a/src/main/github/work-item-details-enterprise-host.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type * as WorkItemDetailsTestLookup from './work-item-details-test-lookup' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -type RateLimitGuardResult = - | { blocked: false } - | { blocked: true; remaining: number; limit: number; resetAt: number } - -const { - ghExecFileAsyncMock, - getOwnerRepoMock, - getEnterpriseGitHubRepoSlugMock, - getWorkItemMock, - getWorkItemByOwnerRepoMock, - getPRChecksMock, - getPRCommentsMock, - repositoryRateLimitGuardMock, - noteRepositoryRateLimitSpendMock -} = vi.hoisted(() => ({ - ghExecFileAsyncMock: vi.fn(), - getOwnerRepoMock: vi.fn(), - getEnterpriseGitHubRepoSlugMock: vi.fn(), - getWorkItemMock: vi.fn(), - getWorkItemByOwnerRepoMock: vi.fn(), - getPRChecksMock: vi.fn(), - getPRCommentsMock: vi.fn(), - repositoryRateLimitGuardMock: vi.fn< - ( - repository: { host?: string } | null | undefined, - bucket: 'core' | 'graphql' | 'search', - options?: { cwd?: string; host?: string; wslDistro?: string } - ) => RateLimitGuardResult - >(() => ({ blocked: false })), - noteRepositoryRateLimitSpendMock: vi.fn() -})) - -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: ghExecFileAsyncMock, - getOwnerRepo: getOwnerRepoMock, - getOwnerRepoForRemote: vi.fn(), - ghRepoExecOptions: vi.fn((context: { connectionId?: string | null; repoPath: string }) => - context.connectionId ? {} : { cwd: context.repoPath } - ), - githubRepoContext: vi.fn((repoPath: string, connectionId?: string | null) => ({ - repoPath, - connectionId: connectionId ?? null - })), - acquire: vi.fn().mockResolvedValue(undefined), - release: vi.fn() -})) - -vi.mock('./client', async () => ({ - getWorkItem: getWorkItemMock, - getWorkItemWithRepository: ( - await vi.importActual('./work-item-details-test-lookup') - ).makeWorkItemDetailsLookupMock(getWorkItemMock), - getWorkItemByOwnerRepo: getWorkItemByOwnerRepoMock, - getPRChecks: getPRChecksMock, - getPRComments: getPRCommentsMock -})) - -vi.mock('./github-enterprise-repository', () => ({ - getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock, - getEnterpriseGitHubRepoSlugForRemote: vi.fn().mockResolvedValue(null), - isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) -})) - -vi.mock('./rate-limit', () => ({ - repositoryRateLimitGuard: repositoryRateLimitGuardMock, - noteRepositoryRateLimitSpend: noteRepositoryRateLimitSpendMock -})) - -import { getWorkItemDetails } from './work-item-details' - -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' - -beforeEach(() => { - _resetOriginGitHubApiRepositoryCache() -}) - -describe('getWorkItemDetails Enterprise host routing', () => { - beforeEach(() => { - ghExecFileAsyncMock.mockReset() - getOwnerRepoMock.mockReset() - getEnterpriseGitHubRepoSlugMock.mockReset() - getEnterpriseGitHubRepoSlugMock.mockResolvedValue(null) - getWorkItemMock.mockReset() - getWorkItemByOwnerRepoMock.mockReset() - getPRChecksMock.mockReset() - getPRCommentsMock.mockReset() - repositoryRateLimitGuardMock.mockReset() - repositoryRateLimitGuardMock.mockReturnValue({ blocked: false }) - noteRepositoryRateLimitSpendMock.mockReset() - }) - - it('surfaces a found SSH PR without any default-host gh call when the repository is unresolved', async () => { - // A found PR without a resolved Enterprise host must not fall back to github.com. - getWorkItemMock.mockResolvedValueOnce({ - id: 'pr:7', - type: 'pr', - number: 7, - title: 'Unresolved Enterprise PR', - state: 'open', - url: 'https://github.acme-corp.com/team/orca/pull/7', - labels: [], - updatedAt: '2026-07-16T00:00:00Z', - author: 'pr-author' - }) - getEnterpriseGitHubRepoSlugMock.mockResolvedValue(null) - - const details = await getWorkItemDetails('/remote/repo', 7, 'pr', 'ssh-1') - - expect(details?.item.id).toBe('pr:7') - expect(getWorkItemByOwnerRepoMock).not.toHaveBeenCalled() - expect(ghExecFileAsyncMock).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/github/work-item-search-fallback-environment.test.ts b/src/main/github/work-item-search-fallback-environment.test.ts deleted file mode 100644 index 503d9f28ec7..00000000000 --- a/src/main/github/work-item-search-fallback-environment.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect, it, vi } from 'vitest' -import { api, capture } from './work-item-search-test-harness' -import { listWorkItems } from './client/list/list-work-items' -import './client/list/count-work-items' -import metadata from './__fixtures__/work-item-search-metadata.json' - -it('keeps REST fallback on the credential captured for the failed preferred search', async () => { - vi.stubEnv('GH_TOKEN', 'fixture-original-credential') - api.aliasErrorRepo = 'fixture/repo' - capture.mockImplementation(async (binary, args, options) => { - if (args.includes('graphql')) { - vi.stubEnv('GH_TOKEN', 'fixture-later-credential') - } - return api.capture(binary, args, options) - }) - expect((await listWorkItems('fixture/repo', 24, 'is:issue')).items).toHaveLength(24) - const queries = api.calls.filter( - (call) => - call.args.includes('graphql') || call.args.some((arg) => arg.startsWith('search/issues?')) - ) - expect(queries.map((call) => call.fixtureCredential)).toEqual([ - 'fixture-original-credential', - 'fixture-original-credential' - ]) -}) - -it('hydrates oversized associations with the preferred page credential', async () => { - vi.stubEnv('GH_TOKEN', 'fixture-original-credential') - const row = structuredClone(metadata[0].graphql) - api.specialNodes = [{ ...row, labels: { ...row.labels, pageInfo: { hasNextPage: true } } }] - capture.mockImplementation(async (binary, args, options) => { - if (args.includes('graphql')) { - vi.stubEnv('GH_TOKEN', 'fixture-later-credential') - } - return api.capture(binary, args, options) - }) - const result = await listWorkItems('fixture/repo', 24, 'is:issue') - expect(result.items[0].labels).toHaveLength(125) - const queries = api.calls.filter( - (call) => - call.args.includes('graphql') || call.args.some((arg) => /^repos\/.+\/issues\/\d+$/.test(arg)) - ) - expect(queries.map((call) => call.fixtureCredential)).toEqual([ - 'fixture-original-credential', - 'fixture-original-credential' - ]) -}) diff --git a/src/main/github/work-item-details-pr-files.test.ts b/src/main/github/work-items/work-item-details-pr-files.test.ts similarity index 86% rename from src/main/github/work-item-details-pr-files.test.ts rename to src/main/github/work-items/work-item-details-pr-files.test.ts index ff94be71d4e..485054a2f4d 100644 --- a/src/main/github/work-item-details-pr-files.test.ts +++ b/src/main/github/work-items/work-item-details-pr-files.test.ts @@ -1,3 +1,4 @@ +import type * as GithubRepositoryIdentityModule from '../repositories/github-repository-identity' import type * as WorkItemDetailsTestLookup from './work-item-details-test-lookup' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -39,36 +40,52 @@ const { >(() => ({ blocked: false })) })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: ghExecFileAsyncMock, +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), getOwnerRepoForRemote: getOwnerRepoMock, - getIssueOwnerRepo: vi.fn(), ghRepoExecOptions: vi.fn((context) => ({ cwd: context.repoPath })), githubRepoContext: vi.fn((repoPath, connectionId, localGitOptions) => ({ repoPath, connectionId: connectionId ?? null, ...localGitOptions - })), + })) +})) + +vi.mock('../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: vi.fn() +})) + +vi.mock('../execution/github-operation-slots', () => ({ acquire: vi.fn(), release: vi.fn() })) -vi.mock('./client', async () => ({ +vi.mock('../client/fetch/get-work-item', async () => ({ getWorkItem: getWorkItemMock, getWorkItemWithRepository: ( await vi.importActual('./work-item-details-test-lookup') ).makeWorkItemDetailsLookupMock(getWorkItemMock), - getWorkItemByOwnerRepo: vi.fn(), - getPRChecks: getPRChecksMock, + getWorkItemByOwnerRepo: vi.fn() +})) + +vi.mock('../client/check/get-pr-checks', async () => ({ + getPRChecks: getPRChecksMock +})) + +vi.mock('../client/fetch/get-pr-comments', async () => ({ getPRComments: getPRCommentsMock })) -vi.mock('./github-enterprise-repository', () => ({ +vi.mock('../repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock, isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ rateLimitGuard: vi.fn(() => ({ blocked: false })), noteRateLimitSpend: vi.fn(), repositoryRateLimitGuard: repositoryRateLimitGuardMock, @@ -76,7 +93,7 @@ vi.mock('./rate-limit', () => ({ })) import { getWorkItemDetails } from './work-item-details' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../repositories/github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() diff --git a/src/main/github/work-item-details-test-lookup.ts b/src/main/github/work-items/work-item-details-test-lookup.ts similarity index 76% rename from src/main/github/work-item-details-test-lookup.ts rename to src/main/github/work-items/work-item-details-test-lookup.ts index c2532854c35..fd76d6f7f7f 100644 --- a/src/main/github/work-item-details-test-lookup.ts +++ b/src/main/github/work-items/work-item-details-test-lookup.ts @@ -1,6 +1,5 @@ -import { resolveIssueGitHubApiRepositorySource } from './github-api-repository' -import type { getWorkItem } from './client' -import type { WorkItemRepositoryLookup } from './client/fetch/get-work-item' +import { resolveIssueGitHubApiRepositorySource } from '../repositories/github-api-repository' +import type { getWorkItem, WorkItemRepositoryLookup } from '../client/fetch/get-work-item' // Why: detail tests replace item lookup; retain its selected-source metadata. export function makeWorkItemDetailsLookupMock(lookup: typeof getWorkItem) { diff --git a/src/main/github/work-item-details.test.ts b/src/main/github/work-items/work-item-details.test.ts similarity index 86% rename from src/main/github/work-item-details.test.ts rename to src/main/github/work-items/work-item-details.test.ts index 3beee4e9554..b500e0db06c 100644 --- a/src/main/github/work-item-details.test.ts +++ b/src/main/github/work-items/work-item-details.test.ts @@ -1,3 +1,4 @@ +import type * as GithubRepositoryIdentityModule from '../repositories/github-repository-identity' import type * as WorkItemDetailsTestLookup from './work-item-details-test-lookup' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -53,40 +54,56 @@ const { releaseMock: vi.fn() })) -vi.mock('./gh-utils', () => ({ - ghExecFileAsync: ghExecFileAsyncMock, - getOwnerRepo: getOwnerRepoMock, +vi.mock('../../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../repositories/github-owner-repo-selection', () => ({ + getOwnerRepo: getOwnerRepoMock +})) + +vi.mock('../repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), getOwnerRepoForRemote: getOwnerRepoForRemoteMock, ghRepoExecOptions: ghRepoExecOptionsMock, - githubRepoContext: githubRepoContextMock, + githubRepoContext: githubRepoContextMock +})) + +vi.mock('../execution/github-operation-slots', () => ({ acquire: acquireMock, release: releaseMock })) -vi.mock('./client', async () => ({ +vi.mock('../client/fetch/get-work-item', async () => ({ getWorkItem: getWorkItemMock, getWorkItemWithRepository: ( await vi.importActual('./work-item-details-test-lookup') ).makeWorkItemDetailsLookupMock(getWorkItemMock), - getWorkItemByOwnerRepo: getWorkItemByOwnerRepoMock, - getPRChecks: getPRChecksMock, + getWorkItemByOwnerRepo: getWorkItemByOwnerRepoMock +})) + +vi.mock('../client/check/get-pr-checks', async () => ({ + getPRChecks: getPRChecksMock +})) + +vi.mock('../client/fetch/get-pr-comments', async () => ({ getPRComments: getPRCommentsMock })) -vi.mock('./github-enterprise-repository', () => ({ +vi.mock('../repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock, getEnterpriseGitHubRepoSlugForRemote: getEnterpriseGitHubRepoSlugForRemoteMock, isGitHubHostAuthenticated: vi.fn().mockResolvedValue(true) })) -vi.mock('./rate-limit', () => ({ +vi.mock('../api/rate-limit', () => ({ repositoryRateLimitGuard: repositoryRateLimitGuardMock, noteRepositoryRateLimitSpend: noteRepositoryRateLimitSpendMock })) import { getWorkItemDetails } from './work-item-details' -import { _resetOriginGitHubApiRepositoryCache } from './github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../repositories/github-api-repository-remote-probe' beforeEach(() => { _resetOriginGitHubApiRepositoryCache() diff --git a/src/main/github/work-item-details.ts b/src/main/github/work-items/work-item-details.ts similarity index 83% rename from src/main/github/work-item-details.ts rename to src/main/github/work-items/work-item-details.ts index 3db15194c9e..60eeaa99e98 100644 --- a/src/main/github/work-item-details.ts +++ b/src/main/github/work-items/work-item-details.ts @@ -1,22 +1,31 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' -import { withGitHubRepoExecution } from './github-repository-identity' -import type { PRCheckDetail } from '../../shared/github/check-types' -import type { GitHubPRFile, GitHubPRFileContents } from '../../shared/github/pull-request-types' -import type { GitHubWorkItemDetails } from '../../shared/github/work-item-types' -import type { IssueSourcePreference } from '../../shared/repo-types' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' import { - getPRChecks, - getPRComments, - getWorkItemWithRepository, - getWorkItemByOwnerRepo -} from './client' -import { acquire, release, type LocalGitExecOptions } from './gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from './github-api-repository' -import { isValidGitHubApiRepository } from './github-api-repository-validation' -import { getIssueBodyAndComments, getIssueDetailsViaGraphQL } from './issue-work-item-details' -import { getPRFiles, getPRMetadata, mergePRFileViewedStates } from './pull-request-file-data' -import { getPRDetailContext } from './pull-request-detail-context' -import { getPRFileContents as loadPRFileContents } from './pull-request-file-contents' + withGitHubRepoExecution, + type LocalGitExecOptions +} from '../repositories/github-repository-identity' +import type { PRCheckDetail } from '../../../shared/github/checks/check-types' + +import type { GitHubWorkItemDetails } from '../../../shared/github/work-items/work-item-types' +import type { IssueSourcePreference } from '../../../shared/repo-types' +import { getPRChecks } from '../client/check/get-pr-checks' +import { getPRComments } from '../client/fetch/get-pr-comments' +import { getWorkItemWithRepository, getWorkItemByOwnerRepo } from '../client/fetch/get-work-item' +import { acquire, release } from '../execution/github-operation-slots' + +import { resolveGitHubRepoExecution } from '../repositories/github-api-repository' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import { isValidGitHubApiRepository } from '../repositories/github-api-repository-validation' +import { + getIssueBodyAndComments, + getIssueDetailsViaGraphQL +} from '../issues/issue-work-item-details' +import { + getPRFiles, + getPRMetadata, + mergePRFileViewedStates +} from '../pull-requests/files/pull-request-file-data' +import { getPRDetailContext } from '../pull-requests/files/pull-request-detail-context' + import { enrichItemDisplayAvatars, getMentionParticipants, @@ -273,18 +282,3 @@ export async function getWorkItemDetails( expectedGhAccount ) } - -export async function getPRFileContents(args: { - repoPath: string - connectionId?: string | null - localGitOptions?: LocalGitExecOptions - prRepo?: GitHubApiRepository | null - prNumber: number - path: string - oldPath?: string - status: GitHubPRFile['status'] - headSha: string - baseSha: string -}): Promise { - return loadPRFileContents(args) -} diff --git a/src/main/github/work-item-participants.ts b/src/main/github/work-items/work-item-participants.ts similarity index 91% rename from src/main/github/work-item-participants.ts rename to src/main/github/work-items/work-item-participants.ts index d7bfc1f33f5..e016b557114 100644 --- a/src/main/github/work-item-participants.ts +++ b/src/main/github/work-items/work-item-participants.ts @@ -1,10 +1,16 @@ -import type { PRComment } from '../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../shared/github/work-item-types' -import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from './gh-utils' -import type { LocalGitExecOptions } from './gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' -import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +import type { PRComment } from '../../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' +import { ghExecFileAsync } from '../../git/command-runner/hosted-cli/gh-exec-file' +import { + ghRepoExecOptions, + githubRepoContext, + type LocalGitExecOptions +} from '../repositories/github-repository-identity' + +import { githubHostExecOptions } from '../repositories/github-repository-host' +import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity' +import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../api/rate-limit' const WORK_ITEM_PARTICIPANTS_QUERY = `query($owner: String!, $repo: String!, $number: Int!, $isPr: Boolean!) { repository(owner: $owner, name: $repo) { diff --git a/src/main/github/work-item-search-test-harness.ts b/src/main/github/work-items/work-item-search-test-harness.ts similarity index 65% rename from src/main/github/work-item-search-test-harness.ts rename to src/main/github/work-items/work-item-search-test-harness.ts index 1ef5aebb783..4cbc5993d06 100644 --- a/src/main/github/work-item-search-test-harness.ts +++ b/src/main/github/work-items/work-item-search-test-harness.ts @@ -1,10 +1,12 @@ +import type * as GithubApiRepositoryModule from '../repositories/github-api-repository' +import type * as GithubApiRepositoryRemoteProbeModule from '../repositories/github-api-repository-remote-probe' import { afterEach, beforeEach, vi, type Mock } from 'vitest' import { randomUUID } from 'node:crypto' import { basename } from 'node:path' -import type * as GithubApiRepositoryModule from './github-api-repository' -import type * as GitExecFileModule from '../git/command-runner/git-exec-file' -import { WorkItemSearchApi } from './__fixtures__/work-item-search-api' + +import type * as GitExecFileModule from '../../git/command-runner/git-exec-file' +import { WorkItemSearchApi } from '../__fixtures__/work-item-search-api' const { capture, @@ -17,10 +19,10 @@ const { sourceContext: { host: 'github.com', available: true } })) const gitExecFileAsync = vi.hoisted(() => vi.fn()) -vi.mock('../git/command-runner/process-capture/exec-file-capture', () => ({ +vi.mock('../../git/command-runner/process-capture/exec-file-capture', () => ({ execFileCaptureToTermination: capture })) -vi.mock('../git/command-runner/host-routing/wsl-command-resolution', () => ({ +vi.mock('../../git/command-runner/host-routing/wsl-command-resolution', () => ({ resolveCommand: (binary: string, args: string[], cwd?: string, distro?: string) => ({ binary, args, @@ -31,11 +33,11 @@ vi.mock('../git/command-runner/host-routing/wsl-command-resolution', () => ({ resolveDefaultWslCli: () => null })) -vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ +vi.mock('../../git/command-runner/git-exec-file', async (importOriginal) => ({ ...(await importOriginal()), gitExecFileAsync })) -vi.mock('./github-api-repository', async (importOriginal) => { +vi.mock('../repositories/github-api-repository', async (importOriginal) => { const actual = await importOriginal() const source = (repoPath: string) => sourceContext.available @@ -46,17 +48,28 @@ vi.mock('./github-api-repository', async (importOriginal) => { resolveIssueGitHubApiRepositorySource: async (repoPath: string) => ({ source: source(repoPath), fellBack: false - }), + }) + } +}) + +vi.mock('../repositories/github-api-repository-remote-probe', async (importOriginal) => { + const actual = await importOriginal() + const source = (repoPath: string) => + sourceContext.available + ? { owner: 'fixture', repo: basename(repoPath), host: sourceContext.host } + : null + return { + ...actual, getOriginGitHubApiRepository: async (repoPath: string) => source(repoPath), getGitHubApiRepositoryForRemote: async () => null } }) -import { _resetRateLimitCache } from './rate-limit' +import { _resetRateLimitCache } from '../api/rate-limit' import { clearGhRateLimitBlock, ghRateLimitScopeKey -} from '../git/command-runner/hosted-cli/gh-rate-limit-breaker' +} from '../../git/command-runner/hosted-cli/gh-rate-limit-breaker' export let api: WorkItemSearchApi beforeEach(() => { vi.useFakeTimers({ toFake: ['Date'] }) diff --git a/src/main/github/github-issue-link-title-fill.test.ts b/src/main/github/workspace-links/github-issue-link-title-fill.test.ts similarity index 89% rename from src/main/github/github-issue-link-title-fill.test.ts rename to src/main/github/workspace-links/github-issue-link-title-fill.test.ts index 596fe38be6f..c55a9b90c70 100644 --- a/src/main/github/github-issue-link-title-fill.test.ts +++ b/src/main/github/workspace-links/github-issue-link-title-fill.test.ts @@ -8,17 +8,17 @@ import { makeRepo, testState, writeDataFile -} from '../persistence/persistence-test-harness' -import { getWorkspaceAttachments } from '../../shared/workspace-attachments/workspace-attachments' -import { hostWorktreeTarget } from '../../shared/worktree/host-qualified-identity' +} from '../../persistence/persistence-test-harness' +import { getWorkspaceAttachments } from '../../../shared/workspace-attachments/workspace-attachments' +import { hostWorktreeTarget } from '../../../shared/worktree/host-qualified-identity' import { installGitHubIssueLinkTitleFill } from './github-issue-link-title-fill' vi.mock('electron', () => ({ app: { getPath: () => testState.dir }, safeStorage: { isEncryptionAvailable: () => false } })) -vi.mock('../telemetry/client', () => ({ track: vi.fn() })) -vi.mock('../telemetry/cohort-classifier', () => ({ getCohortAtEmit: vi.fn() })) +vi.mock('../../telemetry/client', () => ({ track: vi.fn() })) +vi.mock('../../telemetry/cohort-classifier', () => ({ getCohortAtEmit: vi.fn() })) const target = hostWorktreeTarget({ id: 'repo::/work', hostId: 'local' }) diff --git a/src/main/github/github-issue-link-title-fill.ts b/src/main/github/workspace-links/github-issue-link-title-fill.ts similarity index 83% rename from src/main/github/github-issue-link-title-fill.ts rename to src/main/github/workspace-links/github-issue-link-title-fill.ts index f3551b91b0f..b9a7b25cfa2 100644 --- a/src/main/github/github-issue-link-title-fill.ts +++ b/src/main/github/workspace-links/github-issue-link-title-fill.ts @@ -1,18 +1,18 @@ -import type { ExecutionHostId } from '../../shared/execution-host' -import type { Repo } from '../../shared/repo-types' -import type { WorktreeMeta } from '../../shared/worktree/meta-types' -import type { WorkspaceAttachment } from '../../shared/worktree/types' -import type { WorkspaceAttachmentMutation } from '../../shared/workspace-attachments/workspace-attachment-mutation' -import { getWorkspaceAttachments } from '../../shared/workspace-attachments/workspace-attachments' -import { WORKSPACE_ATTACHMENT_NUMBER_SLOTS } from '../../shared/workspace-attachments/workspace-attachment-legacy' -import { getWorkspaceReferenceIdentity } from '../../shared/workspace-attachments/workspace-reference-identity' +import type { ExecutionHostId } from '../../../shared/execution-host' +import type { Repo } from '../../../shared/repo-types' +import type { WorktreeMeta } from '../../../shared/worktree/meta-types' +import type { WorkspaceAttachment } from '../../../shared/worktree/types' +import type { WorkspaceAttachmentMutation } from '../../../shared/workspace-attachments/workspace-attachment-mutation' +import { getWorkspaceAttachments } from '../../../shared/workspace-attachments/workspace-attachments' +import { WORKSPACE_ATTACHMENT_NUMBER_SLOTS } from '../../../shared/workspace-attachments/workspace-attachment-legacy' +import { getWorkspaceReferenceIdentity } from '../../../shared/workspace-attachments/workspace-reference-identity' import { hostWorktreeTarget, type HostWorktreeTarget -} from '../../shared/worktree/host-qualified-identity' -import { getRepoIdFromWorktreeId } from '../../shared/worktree/id' -import type { WorktreeMetaWriteListener } from '../persistence/loading-store/profile-writing/write-scheduling' -import { findRepoRowForHostScopedWrite } from '../persistence/tracking-repos/repo-update-operations' +} from '../../../shared/worktree/host-qualified-identity' +import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id' +import type { WorktreeMetaWriteListener } from '../../persistence/loading-store/profile-writing/write-scheduling' +import { findRepoRowForHostScopedWrite } from '../../persistence/tracking-repos/repo-update-operations' export type GitHubIssueLinkTitleFillStore = { onWorktreeMetaWritten: (listener: WorktreeMetaWriteListener) => () => void diff --git a/src/main/github/github-issue-link-title-wiring.ts b/src/main/github/workspace-links/github-issue-link-title-wiring.ts similarity index 78% rename from src/main/github/github-issue-link-title-wiring.ts rename to src/main/github/workspace-links/github-issue-link-title-wiring.ts index 0c22857efcf..d9a4220f764 100644 --- a/src/main/github/github-issue-link-title-wiring.ts +++ b/src/main/github/workspace-links/github-issue-link-title-wiring.ts @@ -1,18 +1,18 @@ -import type { Repo } from '../../shared/repo-types' -import { composeWorktreeHostIdentity } from '../../shared/worktree/host-qualified-identity' -import { getRepoIdFromWorktreeId } from '../../shared/worktree/id' +import type { Repo } from '../../../shared/repo-types' +import { composeWorktreeHostIdentity } from '../../../shared/worktree/host-qualified-identity' +import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id' import { workspaceMetadataSetRow, type WorkspaceMetadataSet -} from '../../shared/worktree/workspace-metadata-set' -import type { WorkspaceAttachment } from '../../shared/worktree/types' -import { getRepoRoutingConnectionId } from '../../shared/execution-host' -import type { Store } from '../persistence/loading-store/store' -import { getLocalProjectGhExecOptions } from '../workspaces/execution-host/project-runtime-git-options' -import { getIssue } from './issues' -import { getWorkItemByOwnerRepo } from './client/fetch/get-work-item' +} from '../../../shared/worktree/workspace-metadata-set' +import type { WorkspaceAttachment } from '../../../shared/worktree/types' +import { getRepoRoutingConnectionId } from '../../../shared/execution-host' +import type { Store } from '../../persistence/loading-store/store' +import { getLocalProjectGhExecOptions } from '../../workspaces/execution-host/project-runtime-git-options' +import { getIssue } from '../issues/issues' +import { getWorkItemByOwnerRepo } from '../client/fetch/get-work-item' import { installGitHubIssueLinkTitleFill } from './github-issue-link-title-fill' -import { readGitHubRepoUnlessWslStopped } from '../ipc/source-control/github-repo-routing' +import { readGitHubRepoUnlessWslStopped } from '../../ipc/source-control/github-repo-routing' const GITHUB_ISSUE_PATH = /^\/([^/]+)\/([^/]+)\/issues\/(\d+)$/ diff --git a/src/main/gitlab/issues/gitlab-issue-source-resolution.ts b/src/main/gitlab/issues/gitlab-issue-source-resolution.ts index 51ea83f57c1..22a799e5ff2 100644 --- a/src/main/gitlab/issues/gitlab-issue-source-resolution.ts +++ b/src/main/gitlab/issues/gitlab-issue-source-resolution.ts @@ -1,6 +1,6 @@ import { listCachedRemoteNames, shouldProbeGitRemote } from '../../git/remotes/remote-name-listing' import type { IssueSourcePreference } from '../../../shared/repo-types' -import { readLocalGitConfigSignature } from '../../github/local-git-config-signature' +import { readLocalGitConfigSignature } from '../../github/repositories/local-git-config-signature' import { getProjectRefForRemoteWithSignature } from '../projects/gitlab-project-ref-resolution' import type { LocalGitExecOptions } from '../projects/gitlab-known-host-probe' import type { ProjectRef } from '../projects/project-ref-parser' diff --git a/src/main/gitlab/merge-requests/merge-request-state-mutations.ts b/src/main/gitlab/merge-requests/merge-request-state-mutations.ts index f573bd83486..7a044923083 100644 --- a/src/main/gitlab/merge-requests/merge-request-state-mutations.ts +++ b/src/main/gitlab/merge-requests/merge-request-state-mutations.ts @@ -6,7 +6,7 @@ import type { LocalGitExecOptions } from '../projects/gitlab-known-host-probe' import type { ProjectRef } from '../projects/project-ref-parser' import { withProjectRef } from './merge-request-project-resolution' import { codeHostFailure } from '../../../shared/code-host-failure-copy' -import { invalidateReviewLookupsAfterPRMutation } from '../../github/pr-mutation-review-invalidation' +import { invalidateReviewLookupsAfterPRMutation } from '../../github/pull-requests/refresh/pr-mutation-review-invalidation' // Why: glab's MRCheckErrors refuses a no-op close/reopen with these fixed texts; every other // failure, including "has already been merged", must surface. diff --git a/src/main/gitlab/merge-requests/merge-request-update.ts b/src/main/gitlab/merge-requests/merge-request-update.ts index 0b6862004ba..6384692890b 100644 --- a/src/main/gitlab/merge-requests/merge-request-update.ts +++ b/src/main/gitlab/merge-requests/merge-request-update.ts @@ -11,7 +11,7 @@ import { stripGitLabDraftTitlePrefix } from './merge-request-draft-title' import { withProjectRef } from './merge-request-project-resolution' import { tryMarkMRReady } from './merge-request-ready' import { codeHostFailure, classifiedFailure } from '../../../shared/code-host-failure-copy' -import { invalidateReviewLookupsAfterPRMutation } from '../../github/pr-mutation-review-invalidation' +import { invalidateReviewLookupsAfterPRMutation } from '../../github/pull-requests/refresh/pr-mutation-review-invalidation' export async function updateMR( repoPath: string, diff --git a/src/main/gitlab/merge-requests/review-project-identity.ts b/src/main/gitlab/merge-requests/review-project-identity.ts index babb85d7926..de8026c692f 100644 --- a/src/main/gitlab/merge-requests/review-project-identity.ts +++ b/src/main/gitlab/merge-requests/review-project-identity.ts @@ -1,12 +1,11 @@ import { runCoalescedProbe, type CoalescedProbes } from '../../git/remotes/coalesced-probe' import { codeHostReadExecutionScope } from '../../source-control/code-host-read-execution-scope' -import { readLocalGitConfigSignature } from '../../github/local-git-config-signature' +import { readLocalGitConfigSignature } from '../../github/repositories/local-git-config-signature' import { glabExecFileAsync } from '../../git/command-runner/hosted-cli/glab-exec-file' import { glabHostnameArgs, glabRepoExecOptions } from '../projects/gitlab-project-ref-resolution' import type { LocalGitExecOptions } from '../projects/gitlab-known-host-probe' -import type { ProjectRef } from '../projects/project-ref-parser' import { encodedProject } from '../projects/project-path-encoding' -import { normalizeGitLabHost } from '../projects/project-ref-parser' +import { type ProjectRef, normalizeGitLabHost } from '../projects/project-ref-parser' type ProjectIdentity = { id: number; path: string } type CachedIdentity = { value: ProjectIdentity; expiresAt: number; configSignature?: string } diff --git a/src/main/gitlab/projects/gitlab-project-ref-resolution.ts b/src/main/gitlab/projects/gitlab-project-ref-resolution.ts index 26f419c84a6..b2628c77d13 100644 --- a/src/main/gitlab/projects/gitlab-project-ref-resolution.ts +++ b/src/main/gitlab/projects/gitlab-project-ref-resolution.ts @@ -3,7 +3,7 @@ import type { GitAdmissionTier } from '../../../shared/rpc-contract/git-admissio import { addWslEnvKeys } from '../../../shared/wsl-env' import { isTransientGitProbeError, readRemoteUrl } from '../../git/remotes/remote-url-probe' import { NEGATIVE_ENTRY_TTL_MS } from '../../git/remotes/remote-ref-probe-cache' -import { readLocalGitConfigSignature } from '../../github/local-git-config-signature' +import { readLocalGitConfigSignature } from '../../github/repositories/local-git-config-signature' import { clearProjectRefInFlight, runProjectRefProbeOnce } from './project-ref-inflight' import { _resetGlabUnauthenticatedHosts, diff --git a/src/main/gitlab/requests/gitlab-response-mapping.ts b/src/main/gitlab/requests/gitlab-response-mapping.ts index 0eb432e7a08..578b255d175 100644 --- a/src/main/gitlab/requests/gitlab-response-mapping.ts +++ b/src/main/gitlab/requests/gitlab-response-mapping.ts @@ -1,5 +1,5 @@ import { gitLabPipelineJobOutcome } from '../../../shared/source-control/checks/gitlab/gitlab-pipeline-checks' -import type { CheckStatus } from '../../../shared/github/pull-request-types' +import type { CheckStatus } from '../../../shared/github/pull-requests/pull-request-types' import type { GitLabIssueInfo, GitLabWorkItem, MRInfo, MRState } from '../../../shared/gitlab-types' import { summarizeProviderChecks } from '../../../shared/source-control/checks/provider-check-summary' diff --git a/src/main/gitlab/requests/glab-request-admission.test.ts b/src/main/gitlab/requests/glab-request-admission.test.ts index 6846251a84a..fb086d75f78 100644 --- a/src/main/gitlab/requests/glab-request-admission.test.ts +++ b/src/main/gitlab/requests/glab-request-admission.test.ts @@ -18,7 +18,7 @@ vi.mock('../../git/command-runner/hosted-cli/glab-exec-file', async (importOrigi glabExecFileAsync: glabExecFileAsyncMock })) -vi.mock('../../github/local-git-config-signature', () => ({ +vi.mock('../../github/repositories/local-git-config-signature', () => ({ readLocalGitConfigSignature: readLocalGitConfigSignatureMock })) import { _resetProjectRefCache, getProjectRef } from '../projects/gitlab-project-ref-resolution' diff --git a/src/main/host/electron-pr-refresh-renderer.ts b/src/main/host/electron-pr-refresh-renderer.ts index b29a4fd5f74..279e14b8ffb 100644 --- a/src/main/host/electron-pr-refresh-renderer.ts +++ b/src/main/host/electron-pr-refresh-renderer.ts @@ -1,5 +1,5 @@ import { webContents } from 'electron' -import type { PRRefreshRendererPort } from '../github/pr-refresh-renderer-port' +import type { PRRefreshRendererPort } from '../github/pull-requests/refresh/pr-refresh-renderer-port' import { sendToTrustedUIRenderer } from '../ipc/ui' export const electronPRRefreshRenderer: PRRefreshRendererPort = { diff --git a/src/main/ipc/repos/repo-update-handler.ts b/src/main/ipc/repos/repo-update-handler.ts index 9956b0c3830..1705f7a6dea 100644 --- a/src/main/ipc/repos/repo-update-handler.ts +++ b/src/main/ipc/repos/repo-update-handler.ts @@ -14,7 +14,7 @@ import { sanitizeRepoIcon } from '../../../shared/repo-icon' import { normalizeGhAccountBinding, type GhAccountBinding -} from '../../../shared/github/account-binding' +} from '../../../shared/github/accounts/account-binding' import { normalizeRepoSourceControlAiOverrides } from '../../../shared/source-control/ai/settings/source-control-ai-repo-settings' import { diff --git a/src/main/ipc/source-control/github-account-handlers.ts b/src/main/ipc/source-control/github-account-handlers.ts index 6c7ea9aa0aa..57bba84d257 100644 --- a/src/main/ipc/source-control/github-account-handlers.ts +++ b/src/main/ipc/source-control/github-account-handlers.ts @@ -1,14 +1,14 @@ import { ipcMain } from 'electron' import { appStarSourceSchema } from '../../../shared/gh-star-source' -import { resolveGhAccountRequiredHost } from '../../github/account-binding-required-host' -import { diagnoseGhAuth } from '../../github/auth-diagnose' +import { resolveGhAccountRequiredHost } from '../../github/accounts/account-binding-required-host' +import { diagnoseGhAuth } from '../../github/accounts/auth-diagnose' import { checkOrcaStarred, starOrca } from '../../github/client/fetch/orca-star' import { getAuthenticatedViewer } from '../../github/client/fetch/authenticated-viewer' import { listGhAccountBindingInventory, validateGhAccountBinding -} from '../../github/gh-account-binding-inventory' -import { getRateLimit } from '../../github/rate-limit' +} from '../../github/accounts/gh-account-binding-inventory' +import { getRateLimit } from '../../github/api/rate-limit' import type { Store } from '../../persistence/loading-store/store' import { getCohortAtEmit } from '../../telemetry/cohort-classifier' import { track } from '../../telemetry/client' diff --git a/src/main/ipc/source-control/github-ipc-test-harness.ts b/src/main/ipc/source-control/github-ipc-test-harness.ts index 7089875262b..6b501ccd03a 100644 --- a/src/main/ipc/source-control/github-ipc-test-harness.ts +++ b/src/main/ipc/source-control/github-ipc-test-harness.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import { clearPRRefreshValidationBackoffForTests } from '../../github/pr-refresh-validation-backoff' +import { clearPRRefreshValidationBackoffForTests } from '../../github/pull-requests/refresh/pr-refresh-validation-backoff' import { type GitHubIpcMocks, listGitHubIpcMockFns } from './github-ipc-module-mocks' const ORIGINAL_PLATFORM = process.platform diff --git a/src/main/ipc/source-control/github-issue-mutation-handlers.ts b/src/main/ipc/source-control/github-issue-mutation-handlers.ts index 161693b50ef..6bcf7afad82 100644 --- a/src/main/ipc/source-control/github-issue-mutation-handlers.ts +++ b/src/main/ipc/source-control/github-issue-mutation-handlers.ts @@ -1,12 +1,12 @@ -import type { GhAccountBinding } from '../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' import { OptionalGhAccountExpectation } from '../../../shared/rpc-contract/github-repo-target-params' import { ipcMain } from 'electron' -import type { GitHubOwnerRepo } from '../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' import type { GitHubIssueUpdate } from '../../../shared/issue-mutation-types' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' -import { addIssueComment } from '../../github/issue-comment' -import { listAssignableUsers, listLabels } from '../../github/issue-field-options' -import { updateIssue } from '../../github/issue-update' +import { addIssueComment } from '../../github/issues/issue-comment' +import { listAssignableUsers, listLabels } from '../../github/issues/issue-field-options' +import { updateIssue } from '../../github/issues/issue-update' import type { Store } from '../../persistence/loading-store/store' import { assertRegisteredGitHubRepo, diff --git a/src/main/ipc/source-control/github-pr-mutation-handlers.ts b/src/main/ipc/source-control/github-pr-mutation-handlers.ts index 4cafab2daf9..b7407b358d5 100644 --- a/src/main/ipc/source-control/github-pr-mutation-handlers.ts +++ b/src/main/ipc/source-control/github-pr-mutation-handlers.ts @@ -1,5 +1,5 @@ import { ipcMain } from 'electron' -import type { GitHubOwnerRepo } from '../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' import type { GitHubPullRequestStateUpdate } from '../../../shared/issue-mutation-types' import { markPRReadyForReview } from '../../github/client/update/pr-ready' import { mergePR } from '../../github/client/merge/merge-pr' diff --git a/src/main/ipc/source-control/github-pr-read-handlers.ts b/src/main/ipc/source-control/github-pr-read-handlers.ts index 9ecb2bfc438..72650b65903 100644 --- a/src/main/ipc/source-control/github-pr-read-handlers.ts +++ b/src/main/ipc/source-control/github-pr-read-handlers.ts @@ -1,6 +1,6 @@ import { ipcMain } from 'electron' -import type { GitHubReactionContent } from '../../../shared/github/comment-types' -import type { GitHubOwnerRepo } from '../../../shared/github/pull-request-types' +import type { GitHubReactionContent } from '../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' import { getPRCheckDetails } from '../../github/client/check/get-pr-check-details' import { getPRChecks } from '../../github/client/check/get-pr-checks' diff --git a/src/main/ipc/source-control/github-pr-refresh-handlers.ts b/src/main/ipc/source-control/github-pr-refresh-handlers.ts index 7ddae33d2f8..6fd54453d72 100644 --- a/src/main/ipc/source-control/github-pr-refresh-handlers.ts +++ b/src/main/ipc/source-control/github-pr-refresh-handlers.ts @@ -5,7 +5,7 @@ import type { GitHubPRRefreshEnqueueResult, GitHubPRRefreshReason, PRRefreshOutcome -} from '../../../shared/github/pull-request-refresh-types' +} from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { Repo } from '../../../shared/repo-types' import type { OrcaRuntimeService } from '../../runtime/orca-runtime' import { getPRForBranch } from '../../github/client/lookup/get-pr-for-branch' @@ -17,9 +17,9 @@ import { reportVisiblePRRefreshCandidates, addPRRefreshOutcomeObserver, setPRRefreshRegistrationCheck -} from '../../github/pr-refresh-coordinator' -import { setPRRefreshRendererPort } from '../../github/pr-refresh-renderer-port' -import { pullRequestChangeObserver } from '../../github/pull-request-changed-events' +} from '../../github/pull-requests/refresh/pr-refresh-coordinator' +import { setPRRefreshRendererPort } from '../../github/pull-requests/refresh/pr-refresh-renderer-port' +import { pullRequestChangeObserver } from '../../github/pull-requests/refresh/pull-request-changed-events' import { electronPRRefreshRenderer } from '../../host/electron-pr-refresh-renderer' import type { Store } from '../../persistence/loading-store/store' import type { StatsCollector } from '../../stats/collector' diff --git a/src/main/ipc/source-control/github-pr-review-handlers.ts b/src/main/ipc/source-control/github-pr-review-handlers.ts index 6e7adb3e12a..c3cc1e865b9 100644 --- a/src/main/ipc/source-control/github-pr-review-handlers.ts +++ b/src/main/ipc/source-control/github-pr-review-handlers.ts @@ -1,7 +1,7 @@ -import type { GhAccountBinding } from '../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' import { OptionalGhAccountExpectation } from '../../../shared/rpc-contract/github-repo-target-params' import { ipcMain } from 'electron' -import type { GitHubOwnerRepo } from '../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' import { addPRReviewComment, diff --git a/src/main/ipc/source-control/github-project-view-handlers.ts b/src/main/ipc/source-control/github-project-view-handlers.ts index ac0247be431..f56d9b6f6a6 100644 --- a/src/main/ipc/source-control/github-project-view-handlers.ts +++ b/src/main/ipc/source-control/github-project-view-handlers.ts @@ -18,7 +18,7 @@ import type { UpdateProjectItemFieldArgs, UpdatePullRequestBySlugArgs, WakeWslDistroArg -} from '../../../shared/github/project-request-types' +} from '../../../shared/github/projects/project-request-types' import { addIssueCommentBySlug, deleteIssueCommentBySlug, diff --git a/src/main/ipc/source-control/github-repo-access-guards.test.ts b/src/main/ipc/source-control/github-repo-access-guards.test.ts index 36550af5e22..5ade4e1d7a2 100644 --- a/src/main/ipc/source-control/github-repo-access-guards.test.ts +++ b/src/main/ipc/source-control/github-repo-access-guards.test.ts @@ -1,28 +1,28 @@ -import type * as OrcaStarModule from '../../github/client/fetch/orca-star' -import type * as AuthenticatedViewerModule from '../../github/client/fetch/authenticated-viewer' -import type * as IssueCommentModule from '../../github/issue-comment' -import type * as IssueFieldOptionsModule from '../../github/issue-field-options' -import type * as IssueUpdateModule from '../../github/issue-update' -import type * as PrReadyModule from '../../github/client/update/pr-ready' -import type * as MergePrModule from '../../github/client/merge/merge-pr' -import type * as PrReviewersModule from '../../github/client/update/pr-reviewers' -import type * as RerunPrChecksModule from '../../github/client/check/rerun-pr-checks' -import type * as PrAutoMergeModule from '../../github/client/merge/pr-auto-merge' -import type * as PrStateModule from '../../github/client/update/pr-state' -import type * as PrDetailsModule from '../../github/client/update/pr-details' -import type * as GetPrCheckDetailsModule from '../../github/client/check/get-pr-check-details' -import type * as GetPrChecksModule from '../../github/client/check/get-pr-checks' -import type * as GetPrCommentsModule from '../../github/client/fetch/get-pr-comments' -import type * as ResolveReviewThreadModule from '../../github/client/update/resolve-review-thread' -import type * as PrCommentReactionModule from '../../github/client/update/pr-comment-reaction' import type * as GetPrForBranchModule from '../../github/client/lookup/get-pr-for-branch' -import type * as AddPrReviewCommentModule from '../../github/client/create/add-pr-review-comment' -import type * as PrFileViewedModule from '../../github/client/update/pr-file-viewed' -import type * as CountWorkItemsModule from '../../github/client/list/count-work-items' -import type * as IssueCreateModule from '../../github/issue-create' -import type * as IssuesModule from '../../github/issues' +import type * as IssuesModule from '../../github/issues/issues' import type * as GetWorkItemModule from '../../github/client/fetch/get-work-item' import type * as ListWorkItemsModule from '../../github/client/list/list-work-items' +import type * as CountWorkItemsModule from '../../github/client/list/count-work-items' +import type * as IssueCreateModule from '../../github/issues/issue-create' +import type * as IssueUpdateModule from '../../github/issues/issue-update' +import type * as IssueCommentModule from '../../github/issues/issue-comment' +import type * as IssueFieldOptionsModule from '../../github/issues/issue-field-options' +import type * as AuthenticatedViewerModule from '../../github/client/fetch/authenticated-viewer' +import type * as GetPrChecksModule from '../../github/client/check/get-pr-checks' +import type * as GetPrCheckDetailsModule from '../../github/client/check/get-pr-check-details' +import type * as GetPrCommentsModule from '../../github/client/fetch/get-pr-comments' +import type * as PrCommentReactionModule from '../../github/client/update/pr-comment-reaction' +import type * as ResolveReviewThreadModule from '../../github/client/update/resolve-review-thread' +import type * as PrFileViewedModule from '../../github/client/update/pr-file-viewed' +import type * as AddPrReviewCommentModule from '../../github/client/create/add-pr-review-comment' +import type * as PrDetailsModule from '../../github/client/update/pr-details' +import type * as MergePrModule from '../../github/client/merge/merge-pr' +import type * as PrAutoMergeModule from '../../github/client/merge/pr-auto-merge' +import type * as PrStateModule from '../../github/client/update/pr-state' +import type * as PrReadyModule from '../../github/client/update/pr-ready' +import type * as RerunPrChecksModule from '../../github/client/check/rerun-pr-checks' +import type * as PrReviewersModule from '../../github/client/update/pr-reviewers' +import type * as OrcaStarModule from '../../github/client/fetch/orca-star' import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = await vi.hoisted(async () => { @@ -31,125 +31,12 @@ const mocks = await vi.hoisted(async () => { }) vi.mock('electron', () => mocks.electron) -vi.mock('../../github/client', () => mocks.client) - -vi.mock('../../github/client/fetch/orca-star', async (importOriginal) => ({ - ...(await importOriginal()), - checkOrcaStarred: mocks.client.checkOrcaStarred, - starOrca: mocks.client.starOrca -})) - -vi.mock('../../github/client/fetch/authenticated-viewer', async (importOriginal) => ({ - ...(await importOriginal()), - getAuthenticatedViewer: mocks.client.getAuthenticatedViewer -})) - -vi.mock('../../github/issue-comment', async (importOriginal) => ({ - ...(await importOriginal()), - addIssueComment: mocks.client.addIssueComment -})) - -vi.mock('../../github/issue-field-options', async (importOriginal) => ({ - ...(await importOriginal()), - listAssignableUsers: mocks.client.listAssignableUsers, - listLabels: mocks.client.listLabels -})) - -vi.mock('../../github/issue-update', async (importOriginal) => ({ - ...(await importOriginal()), - updateIssue: mocks.client.updateIssue -})) - -vi.mock('../../github/client/update/pr-ready', async (importOriginal) => ({ - ...(await importOriginal()), - markPRReadyForReview: mocks.client.markPRReadyForReview -})) - -vi.mock('../../github/client/merge/merge-pr', async (importOriginal) => ({ - ...(await importOriginal()), - mergePR: mocks.client.mergePR -})) - -vi.mock('../../github/client/update/pr-reviewers', async (importOriginal) => ({ - ...(await importOriginal()), - removePRReviewers: mocks.client.removePRReviewers, - requestPRReviewers: mocks.client.requestPRReviewers -})) - -vi.mock('../../github/client/check/rerun-pr-checks', async (importOriginal) => ({ - ...(await importOriginal()), - rerunPRChecks: mocks.client.rerunPRChecks -})) - -vi.mock('../../github/client/merge/pr-auto-merge', async (importOriginal) => ({ - ...(await importOriginal()), - setPRAutoMerge: mocks.client.setPRAutoMerge -})) - -vi.mock('../../github/client/update/pr-state', async (importOriginal) => ({ - ...(await importOriginal()), - updatePRState: mocks.client.updatePRState -})) - -vi.mock('../../github/client/update/pr-details', async (importOriginal) => ({ - ...(await importOriginal()), - updatePRTitle: mocks.client.updatePRTitle -})) - -vi.mock('../../github/client/check/get-pr-check-details', async (importOriginal) => ({ - ...(await importOriginal()), - getPRCheckDetails: mocks.client.getPRCheckDetails -})) - -vi.mock('../../github/client/check/get-pr-checks', async (importOriginal) => ({ - ...(await importOriginal()), - getPRChecks: mocks.client.getPRChecks -})) - -vi.mock('../../github/client/fetch/get-pr-comments', async (importOriginal) => ({ - ...(await importOriginal()), - getPRComments: mocks.client.getPRComments -})) - -vi.mock('../../github/client/fetch/repo-slug-upstream', () => ({})) - -vi.mock('../../github/client/update/resolve-review-thread', async (importOriginal) => ({ - ...(await importOriginal()), - resolveReviewThread: mocks.client.resolveReviewThread -})) - -vi.mock('../../github/client/update/pr-comment-reaction', async (importOriginal) => ({ - ...(await importOriginal()), - setPRCommentReaction: mocks.client.setPRCommentReaction -})) - vi.mock('../../github/client/lookup/get-pr-for-branch', async (importOriginal) => ({ ...(await importOriginal()), getPRForBranch: mocks.client.getPRForBranch })) -vi.mock('../../github/client/create/add-pr-review-comment', async (importOriginal) => ({ - ...(await importOriginal()), - addPRReviewComment: mocks.client.addPRReviewComment, - addPRReviewCommentReply: mocks.client.addPRReviewCommentReply -})) - -vi.mock('../../github/client/update/pr-file-viewed', async (importOriginal) => ({ - ...(await importOriginal()), - setPRFileViewed: mocks.client.setPRFileViewed -})) - -vi.mock('../../github/client/list/count-work-items', async (importOriginal) => ({ - ...(await importOriginal()), - countWorkItems: mocks.client.countWorkItems -})) - -vi.mock('../../github/issue-create', async (importOriginal) => ({ - ...(await importOriginal()), - createIssue: mocks.client.createIssue -})) - -vi.mock('../../github/issues', async (importOriginal) => ({ +vi.mock('../../github/issues/issues', async (importOriginal) => ({ ...(await importOriginal()), getIssue: mocks.client.getIssue, listIssues: mocks.client.listIssues @@ -165,8 +52,131 @@ vi.mock('../../github/client/list/list-work-items', async (importOriginal) => ({ ...(await importOriginal()), listWorkItems: mocks.client.listWorkItems })) -vi.mock('../../github/work-item-details', () => mocks.workItemDetails) -vi.mock('../../github/pr-refresh-coordinator', () => mocks.prRefresh) + +vi.mock('../../github/client/list/count-work-items', async (importOriginal) => ({ + ...(await importOriginal()), + countWorkItems: mocks.client.countWorkItems +})) + +vi.mock('../../github/issues/issue-create', async (importOriginal) => ({ + ...(await importOriginal()), + createIssue: mocks.client.createIssue +})) + +vi.mock('../../github/issues/issue-update', async (importOriginal) => ({ + ...(await importOriginal()), + updateIssue: mocks.client.updateIssue +})) + +vi.mock('../../github/issues/issue-comment', async (importOriginal) => ({ + ...(await importOriginal()), + addIssueComment: mocks.client.addIssueComment +})) + +vi.mock('../../github/issues/issue-field-options', async (importOriginal) => ({ + ...(await importOriginal()), + listLabels: mocks.client.listLabels, + listAssignableUsers: mocks.client.listAssignableUsers +})) + +vi.mock('../../github/client/fetch/authenticated-viewer', async (importOriginal) => ({ + ...(await importOriginal()), + getAuthenticatedViewer: mocks.client.getAuthenticatedViewer +})) + +vi.mock('../../github/client/check/get-pr-checks', async (importOriginal) => ({ + ...(await importOriginal()), + getPRChecks: mocks.client.getPRChecks +})) + +vi.mock('../../github/client/check/get-pr-check-details', async (importOriginal) => ({ + ...(await importOriginal()), + getPRCheckDetails: mocks.client.getPRCheckDetails +})) + +vi.mock('../../github/client/fetch/get-pr-comments', async (importOriginal) => ({ + ...(await importOriginal()), + getPRComments: mocks.client.getPRComments +})) + +vi.mock('../../github/client/update/pr-comment-reaction', async (importOriginal) => ({ + ...(await importOriginal()), + setPRCommentReaction: mocks.client.setPRCommentReaction +})) + +vi.mock('../../github/client/update/resolve-review-thread', async (importOriginal) => ({ + ...(await importOriginal()), + resolveReviewThread: mocks.client.resolveReviewThread +})) + +vi.mock('../../github/client/update/pr-file-viewed', async (importOriginal) => ({ + ...(await importOriginal()), + setPRFileViewed: mocks.client.setPRFileViewed +})) + +vi.mock('../../github/client/create/add-pr-review-comment', async (importOriginal) => ({ + ...(await importOriginal()), + addPRReviewComment: mocks.client.addPRReviewComment, + addPRReviewCommentReply: mocks.client.addPRReviewCommentReply +})) + +vi.mock('../../github/client/update/pr-details', async (importOriginal) => ({ + ...(await importOriginal()), + updatePRTitle: mocks.client.updatePRTitle +})) + +vi.mock('../../github/client/merge/merge-pr', async (importOriginal) => ({ + ...(await importOriginal()), + mergePR: mocks.client.mergePR +})) + +vi.mock('../../github/client/merge/pr-auto-merge', async (importOriginal) => ({ + ...(await importOriginal()), + setPRAutoMerge: mocks.client.setPRAutoMerge +})) + +vi.mock('../../github/client/update/pr-state', async (importOriginal) => ({ + ...(await importOriginal()), + updatePRState: mocks.client.updatePRState +})) + +vi.mock('../../github/client/update/pr-ready', async (importOriginal) => ({ + ...(await importOriginal()), + markPRReadyForReview: mocks.client.markPRReadyForReview, + markPRDraft: mocks.client.markPRDraft +})) + +vi.mock('../../github/client/check/rerun-pr-checks', async (importOriginal) => ({ + ...(await importOriginal()), + rerunPRChecks: mocks.client.rerunPRChecks +})) + +vi.mock('../../github/client/update/pr-reviewers', async (importOriginal) => ({ + ...(await importOriginal()), + requestPRReviewers: mocks.client.requestPRReviewers, + removePRReviewers: mocks.client.removePRReviewers +})) + +vi.mock('../../github/client/fetch/orca-star', async (importOriginal) => ({ + ...(await importOriginal()), + checkOrcaStarred: mocks.client.checkOrcaStarred, + starOrca: mocks.client.starOrca +})) + +vi.mock('../../github/client/fetch/repo-slug-upstream', () => ({})) + +vi.mock('../../github/work-items/work-item-details', () => { + return { + getWorkItemDetails: mocks.workItemDetails.getWorkItemDetails + } +}) + +vi.mock('../../github/pull-requests/files/pull-request-file-contents', () => { + return { + getPRFileContents: mocks.workItemDetails.getPRFileContents + } +}) +vi.mock('../../github/pull-requests/refresh/pr-refresh-coordinator', () => mocks.prRefresh) vi.mock('../../telemetry/client', () => mocks.telemetry) vi.mock('../../telemetry/cohort-classifier', () => mocks.cohort) vi.mock('../ui', () => mocks.ui) diff --git a/src/main/ipc/source-control/github-repo-routing-twin-host.test.ts b/src/main/ipc/source-control/github-repo-routing-twin-host.test.ts index a0ab3c86c12..7f5d6964ba0 100644 --- a/src/main/ipc/source-control/github-repo-routing-twin-host.test.ts +++ b/src/main/ipc/source-control/github-repo-routing-twin-host.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { Repo } from '../../../shared/repo-types' import type { Store } from '../../persistence/loading-store/store' diff --git a/src/main/ipc/source-control/github-repo-routing.ts b/src/main/ipc/source-control/github-repo-routing.ts index ee6037c0304..815b3b7fadb 100644 --- a/src/main/ipc/source-control/github-repo-routing.ts +++ b/src/main/ipc/source-control/github-repo-routing.ts @@ -3,13 +3,13 @@ import { getRepoExecutionHostId } from '../../../shared/execution-host' import type { GitHubPRRefreshCandidate, GitHubPRRefreshEnqueueResult -} from '../../../shared/github/pull-request-refresh-types' +} from '../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { Repo } from '../../../shared/repo-types' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' import { notePRRefreshValidationDenial, type PRRefreshValidationDenialReason -} from '../../github/pr-refresh-validation-backoff' +} from '../../github/pull-requests/refresh/pr-refresh-validation-backoff' import type { Store } from '../../persistence/loading-store/store' import { getLocalProjectGhExecOptions, diff --git a/src/main/ipc/source-control/github-work-item-handlers.ts b/src/main/ipc/source-control/github-work-item-handlers.ts index d6eef4b6c6f..8c3f333cfce 100644 --- a/src/main/ipc/source-control/github-work-item-handlers.ts +++ b/src/main/ipc/source-control/github-work-item-handlers.ts @@ -1,15 +1,17 @@ -import type { GhAccountBinding } from '../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' import { OptionalGhAccountExpectation } from '../../../shared/rpc-contract/github-repo-target-params' import { ipcMain } from 'electron' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../shared/github/pull-requests/pull-request-types' import type { GitHubCreateIssueFields } from '../../../shared/issue-mutation-types' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' import { countWorkItems } from '../../github/client/list/count-work-items' -import { createIssue } from '../../github/issue-create' -import { getIssue, listIssues } from '../../github/issues' +import { createIssue } from '../../github/issues/issue-create' +import { getIssue, listIssues } from '../../github/issues/issues' import { getWorkItem, getWorkItemByOwnerRepo } from '../../github/client/fetch/get-work-item' import { listWorkItems } from '../../github/client/list/list-work-items' -import { getPRFileContents, getWorkItemDetails } from '../../github/work-item-details' +import { getPRFileContents } from '../../github/pull-requests/files/pull-request-file-contents' +import { getWorkItemDetails } from '../../github/work-items/work-item-details' import type { Store } from '../../persistence/loading-store/store' import { dispatchWorkItem, type WorkItemArgs } from './github-work-item-args' import { diff --git a/src/main/ipc/workspace-cleanup/workspace-cleanup-candidate.ts b/src/main/ipc/workspace-cleanup/workspace-cleanup-candidate.ts index 3c4f006f471..a0e8e2c70b3 100644 --- a/src/main/ipc/workspace-cleanup/workspace-cleanup-candidate.ts +++ b/src/main/ipc/workspace-cleanup/workspace-cleanup-candidate.ts @@ -2,7 +2,7 @@ import { isFolderRepo } from '../../../shared/repo-kind' import type { Repo } from '../../../shared/repo-types' import type { Worktree } from '../../../shared/worktree/types' import { getWorktreeExecutionHostId } from '../../../shared/execution-host' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import type { PRCacheView } from '../../../shared/sidebar-list/workspace-review-facts' import { createWorkspaceCleanupFingerprint, diff --git a/src/main/ipc/workspace-cleanup/workspace-cleanup-git-evidence.ts b/src/main/ipc/workspace-cleanup/workspace-cleanup-git-evidence.ts index 0c786bcab1e..9b261fa40b1 100644 --- a/src/main/ipc/workspace-cleanup/workspace-cleanup-git-evidence.ts +++ b/src/main/ipc/workspace-cleanup/workspace-cleanup-git-evidence.ts @@ -3,7 +3,7 @@ import { gitExecFileAsync } from '../../git/command-runner/git-exec-file' import type { GitStatusResult } from '../../../shared/git/status/git-status-types' import type { Repo } from '../../../shared/repo-types' import type { Worktree } from '../../../shared/worktree/types' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import type { PRCacheView } from '../../../shared/sidebar-list/workspace-review-facts' import { isWorkspaceCleanupHeadLanded, diff --git a/src/main/ipc/workspace-cleanup/workspace-cleanup-merge-evidence.ts b/src/main/ipc/workspace-cleanup/workspace-cleanup-merge-evidence.ts index 3685f654bc3..a545738d355 100644 --- a/src/main/ipc/workspace-cleanup/workspace-cleanup-merge-evidence.ts +++ b/src/main/ipc/workspace-cleanup/workspace-cleanup-merge-evidence.ts @@ -1,7 +1,7 @@ import { gitExecFileAsync } from '../../git/command-runner/git-exec-file' import { resolveDefaultBaseRefViaExec } from '../../../shared/git/refs/git-default-base-ref' import { localBranchHasNoUnmergedChanges } from '../../git/refs/resolution/local-branch-merge-proof' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../shared/repo-types' import { readWorkspaceReviewPR, diff --git a/src/main/ipc/workspace-cleanup/workspace-cleanup-scan.ts b/src/main/ipc/workspace-cleanup/workspace-cleanup-scan.ts index 76702db0f0b..ff4e089035e 100644 --- a/src/main/ipc/workspace-cleanup/workspace-cleanup-scan.ts +++ b/src/main/ipc/workspace-cleanup/workspace-cleanup-scan.ts @@ -1,6 +1,6 @@ import type { RuntimeStore } from '../../runtime/construction/runtime-store-contract' import { isFolderRepo } from '../../../shared/repo-kind' -import type { PRInfo } from '../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../shared/github/pull-requests/pull-request-types' import type { PRCacheView } from '../../../shared/sidebar-list/workspace-review-facts' import { readWorktreeMetaForHost } from '../../persistence/host-qualified-worktree-meta' import type { Repo } from '../../../shared/repo-types' diff --git a/src/main/ipc/worktree-remote.ts b/src/main/ipc/worktree-remote.ts index 0a90ecee34c..879d3fb20bc 100644 --- a/src/main/ipc/worktree-remote.ts +++ b/src/main/ipc/worktree-remote.ts @@ -28,7 +28,7 @@ import type { Worktree, WorktreeHeadIdentity } from '../../shared/worktree/types' -import { getPRForBranch } from '../github/client' +import { getPRForBranch } from '../github/client/lookup/get-pr-for-branch' import { addWorktree } from '../git/worktrees/creation/worktree-add' import { addSparseWorktree } from '../git/worktrees/creation/worktree-sparse-add' import type { diff --git a/src/main/ipc/worktrees-orphan-directory-cleanup.test.ts b/src/main/ipc/worktrees-orphan-directory-cleanup.test.ts index a587bb63165..711938f49ff 100644 --- a/src/main/ipc/worktrees-orphan-directory-cleanup.test.ts +++ b/src/main/ipc/worktrees-orphan-directory-cleanup.test.ts @@ -99,12 +99,23 @@ vi.mock('../git/repositories/git-username', async (importOriginal) => ({ resolveLocalGitUsername: (await import('./worktrees-test-module-mocks')) .resolveLocalGitUsernameMock })) -vi.mock('../github/client', async () => - (await import('./worktrees-test-module-mocks')).githubClientModuleMock() -) -vi.mock('../github/client/lookup/get-pr-for-branch', async () => - (await import('./worktrees-test-module-mocks')).githubClientModuleMock() -) +vi.mock('../github/client/lookup/get-pr-for-branch', async () => ({ + getPRForBranch: (await import('./worktrees-test-module-mocks')).getPRForBranchMock +})) + +vi.mock('../github/client/fetch/get-work-item', async () => { + return { + getWorkItem: (await import('./worktrees-test-module-mocks')).getWorkItemMock + } +}) + +vi.mock('../github/client/lookup/pull-request-push-target', async () => { + return { + getPullRequestPushTarget: (await import('./worktrees-test-module-mocks')) + .getPullRequestPushTargetMock + } +}) + vi.mock('../source-control/hosted-review', async () => (await import('./worktrees-test-module-mocks')).hostedReviewModuleMock() ) diff --git a/src/main/ipc/worktrees-remove-preflight.test.ts b/src/main/ipc/worktrees-remove-preflight.test.ts index abfda1ba765..07258baad9a 100644 --- a/src/main/ipc/worktrees-remove-preflight.test.ts +++ b/src/main/ipc/worktrees-remove-preflight.test.ts @@ -98,12 +98,23 @@ vi.mock('../git/repositories/git-username', async (importOriginal) => ({ resolveLocalGitUsername: (await import('./worktrees-test-module-mocks')) .resolveLocalGitUsernameMock })) -vi.mock('../github/client', async () => - (await import('./worktrees-test-module-mocks')).githubClientModuleMock() -) -vi.mock('../github/client/lookup/get-pr-for-branch', async () => - (await import('./worktrees-test-module-mocks')).githubClientModuleMock() -) +vi.mock('../github/client/lookup/get-pr-for-branch', async () => ({ + getPRForBranch: (await import('./worktrees-test-module-mocks')).getPRForBranchMock +})) + +vi.mock('../github/client/fetch/get-work-item', async () => { + return { + getWorkItem: (await import('./worktrees-test-module-mocks')).getWorkItemMock + } +}) + +vi.mock('../github/client/lookup/pull-request-push-target', async () => { + return { + getPullRequestPushTarget: (await import('./worktrees-test-module-mocks')) + .getPullRequestPushTargetMock + } +}) + vi.mock('../source-control/hosted-review', async () => (await import('./worktrees-test-module-mocks')).hostedReviewModuleMock() ) diff --git a/src/main/ipc/worktrees/create/register-review-base-handlers.ts b/src/main/ipc/worktrees/create/register-review-base-handlers.ts index 7697581d73b..a17f07d01dc 100644 --- a/src/main/ipc/worktrees/create/register-review-base-handlers.ts +++ b/src/main/ipc/worktrees/create/register-review-base-handlers.ts @@ -15,10 +15,10 @@ import { import { fetchPrHeadTrackingRef, fetchGitHubPullRequestHeadRef -} from '../../../github/pr-head-tracking-ref' -import { resolveGitHubPrStartPoint } from '../../../github/pr-start-point' -import { resolvePrStartPointUnlessWslStopped } from '../../../github/pr-start-point-wsl-gate' -import { resolveGitHubReviewHeadRemote } from '../../../github/review-head-remote' +} from '../../../github/pull-requests/start-points/pr-head-tracking-ref' +import { resolveGitHubPrStartPoint } from '../../../github/pull-requests/start-points/pr-start-point' +import { resolvePrStartPointUnlessWslStopped } from '../../../github/pull-requests/start-points/pr-start-point-wsl-gate' +import { resolveGitHubReviewHeadRemote } from '../../../github/pull-requests/start-points/review-head-remote' import type { WorktreeIpcContext } from '../worktree-ipc-context' import { reviewStartPointFailure } from '../../../../shared/review-start-point-failure-copy' diff --git a/src/main/ipc/worktrees/removal/worktree-removal-ownership.ts b/src/main/ipc/worktrees/removal/worktree-removal-ownership.ts index b5bfea14861..700c6081443 100644 --- a/src/main/ipc/worktrees/removal/worktree-removal-ownership.ts +++ b/src/main/ipc/worktrees/removal/worktree-removal-ownership.ts @@ -10,7 +10,7 @@ import { hasWorktreeRemovalRepoOwnerOnOtherHost } from '../../../workspaces/remo import { getRepoIdFromWorktreeId } from '../../../../shared/worktree/id' import { agentHookServer } from '../../../agent-hooks/server/agent-hook-server' import { removeWorktreeMetaRow } from '../../../persistence/host-qualified-worktree-meta' -import { pruneWorktreePRRefreshAliases } from '../../../github/pr-refresh-coordinator' +import { pruneWorktreePRRefreshAliases } from '../../../github/pull-requests/refresh/pr-refresh-coordinator' import { dropRemovedWorktreeProcessState, pruneRemovedWorktreeSnapshots diff --git a/src/main/orcad/orcad-entry.ts b/src/main/orcad/orcad-entry.ts index cc41e68d196..f1aeccad01e 100644 --- a/src/main/orcad/orcad-entry.ts +++ b/src/main/orcad/orcad-entry.ts @@ -226,7 +226,8 @@ async function activateOrcadRuntime( paneGridsForShutdown = runtime accountLaunch?.attachToRuntime(runtime) - const { wireGitHubIssueLinkTitleFill } = await import('../github/github-issue-link-title-wiring') + const { wireGitHubIssueLinkTitleFill } = + await import('../github/workspace-links/github-issue-link-title-wiring') registerCleanup(wireGitHubIssueLinkTitleFill(profileStore, runtime)) const { installOrcadEmulatorBridge } = await import('./orcad-emulator-bridge') installOrcadEmulatorBridge(runtime, registerCleanup) diff --git a/src/main/persistence/tracking-repos/repo-hydration.ts b/src/main/persistence/tracking-repos/repo-hydration.ts index b18e73bc9de..9cdfca481aa 100644 --- a/src/main/persistence/tracking-repos/repo-hydration.ts +++ b/src/main/persistence/tracking-repos/repo-hydration.ts @@ -3,7 +3,7 @@ import { getRepoExecutionHostId } from '../../../shared/execution-host' import { getDefaultRepoHookSettings } from '../../../shared/constants' import { isFolderRepo } from '../../../shared/repo-kind' import { sanitizeRepoIcon } from '../../../shared/repo-icon' -import { normalizeGhAccountBinding } from '../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../shared/github/accounts/account-binding' import { normalizeJiraProjectReference } from '../../../shared/jira/jira-project-reference' import { normalizeRepoSourceControlAiOverrides } from '../../../shared/source-control/ai/settings/source-control-ai-repo-settings' import { normalizeOrcadRetainedMarker } from '../../../shared/orcad-retained-marker' diff --git a/src/main/persistence/tracking-repos/repo-sanitization.ts b/src/main/persistence/tracking-repos/repo-sanitization.ts index c5c493577d1..b60232e78bb 100644 --- a/src/main/persistence/tracking-repos/repo-sanitization.ts +++ b/src/main/persistence/tracking-repos/repo-sanitization.ts @@ -4,9 +4,12 @@ import { editorFormatterSchema } from '../../../shared/editor-formatter' import type { GitRemoteIdentity } from '../../../shared/git/remotes/git-remote-identity' import { normalizeRepoBadgeColor } from '../../../shared/repo-badge-color' import { sanitizeRepoIcon } from '../../../shared/repo-icon' -import { normalizeGhAccountBinding } from '../../../shared/github/account-binding' +import { + normalizeGhAccountBinding, + type GhAccountBinding +} from '../../../shared/github/accounts/account-binding' import { normalizeJiraProjectReference } from '../../../shared/jira/jira-project-reference' -import type { GhAccountBinding } from '../../../shared/github/account-binding' + import { sanitizeAgentWorkspaceTrustDecision } from '../../../shared/agent-launch/permissions/agent-workspace-trust-decision' import { normalizeCustomWorktreeVisibilitySources, diff --git a/src/main/persistence/tracking-repos/repo-update-operations.ts b/src/main/persistence/tracking-repos/repo-update-operations.ts index a78dddf854b..d6c4b75abb7 100644 --- a/src/main/persistence/tracking-repos/repo-update-operations.ts +++ b/src/main/persistence/tracking-repos/repo-update-operations.ts @@ -1,14 +1,14 @@ import type { PersistedState } from '../../../shared/persisted-state-types' import type { Repo } from '../../../shared/repo-types' -import type { ExecutionHostId } from '../../../shared/execution-host' +import { type ExecutionHostId, getRepoExecutionHostId } from '../../../shared/execution-host' import type { RepoHostTarget } from '../../../shared/repo-host-identity' -import { getRepoExecutionHostId } from '../../../shared/execution-host' + import { isLegacyRepoForExternalWorktreeVisibility } from '../../../shared/external-worktree-visibility' import { normalizeRepoSourceControlAiOverrides } from '../../../shared/source-control/ai/settings/source-control-ai-repo-settings' import { normalizeRepoWorkspaceCleanupPolicy } from '../../../shared/workspace-cleanup/workspace-cleanup-policy' import { normalizeWorktreeVisibilitySourcePreferences } from '../../../shared/worktree/visibility-sources' -import type { GhAccountBinding } from '../../../shared/github/account-binding' -import { invalidateGhAccountTokenCache } from '../../github/gh-account-token' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' +import { invalidateGhAccountTokenCache } from '../../github/accounts/gh-account-token' import { sanitizeRepoUpdatesForPersistence } from './repo-sanitization' import { isFolderRepo } from '../../../shared/repo-kind' import { findExtraFolderWorkspaceIds } from '../../workspaces/catalog/folder-workspace-meta-presence' diff --git a/src/main/repository-icons/repo-icon-autodetect.ts b/src/main/repository-icons/repo-icon-autodetect.ts index 393672a9ef9..4d585178aeb 100644 --- a/src/main/repository-icons/repo-icon-autodetect.ts +++ b/src/main/repository-icons/repo-icon-autodetect.ts @@ -1,6 +1,6 @@ import { readFile, stat } from 'node:fs/promises' import type { ExecutionHostId } from '../../shared/execution-host' -import type { GitHubRepositoryIdentity } from '../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../shared/github/repositories/repository-identity' import type { RepoKind } from '../../shared/repo-types' import { faviconUrlFromWebsite, diff --git a/src/main/runtime/construction/runtime-construction.ts b/src/main/runtime/construction/runtime-construction.ts index ccedc1d4b2a..f00f5e2ed0e 100644 --- a/src/main/runtime/construction/runtime-construction.ts +++ b/src/main/runtime/construction/runtime-construction.ts @@ -14,7 +14,7 @@ import type { ProjectExecutionRuntimeResolution } from '../../../shared/executio import { OrcaRuntimeWithLinearCommands } from '../integrations/linear/linear-commands' import type { RuntimeStore } from './runtime-store-contract' -import { installGitHubProjectAccounts } from '../../github/github-project-account' +import { installGitHubProjectAccounts } from '../../github/accounts/github-project-account' import type { WorkspaceSleepPlatform } from '../workspaces/sleep/workspace-sleep-platform' import type { StatsCollector } from '../../stats/collector' @@ -56,7 +56,7 @@ import { createEphemeralAgentSessionClaimSigner } from '../agent-session/ownersh import { registerConptyDa1OverrideInstaller } from '../terminal/output/terminal-model-query-authority' import { registerTerminalViewAttributesApplier } from '../terminal/view/terminal-view-attribute-store' import { RuntimeMachineName } from '../hosts/platform-probe/runtime-machine-name' -import { setPRConflictSummaryRemoteFetches } from '../../github/conflict-summary' +import { setPRConflictSummaryRemoteFetches } from '../../github/pull-requests/conflicts/conflict-summary' import { DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT } from '../../../shared/terminal/replay/terminal-scrollback-policy' // oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- SAFETY: The constructor installs every checked canonical surface. diff --git a/src/main/runtime/integrations/github/runtime-github-issue-comment-commands.ts b/src/main/runtime/integrations/github/runtime-github-issue-comment-commands.ts index 75fd1e209a5..acaa09e4c2c 100644 --- a/src/main/runtime/integrations/github/runtime-github-issue-comment-commands.ts +++ b/src/main/runtime/integrations/github/runtime-github-issue-comment-commands.ts @@ -1,27 +1,27 @@ -import '../../../github/rate-limit' +import '../../../github/api/rate-limit' import type { GitHubCreateIssueFields, GitHubIssueUpdate } from '../../../../shared/issue-mutation-types' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' -import type { GitHubPRReviewCommentInput } from '../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubPRReviewCommentInput } from '../../../../shared/github/comments/comment-types' import type { Repo } from '../../../../shared/repo-types' import type { LocalProjectGhExecOptions } from '../../../workspaces/execution-host/project-runtime-git-options' -import { addIssueComment } from '../../../github/issue-comment' +import { addIssueComment } from '../../../github/issues/issue-comment' import { addPRReviewComment, addPRReviewCommentReply } from '../../../github/client/create/add-pr-review-comment' -import { createIssue } from '../../../github/issue-create' -import { updateIssue } from '../../../github/issue-update' +import { createIssue } from '../../../github/issues/issue-create' +import { updateIssue } from '../../../github/issues/issue-update' import { getRepoRoutingConnectionId } from '../../../../shared/execution-host' import type { GitHubAttachmentTarget } from '../../../../shared/rpc-contract/github-attachment-params' -import { uploadGitHubImage } from '../../../github/image-attachment-upload' +import { uploadGitHubImage } from '../../../github/attachments/image-attachment-upload' import { resolveGitHubRepoExecution, resolveIssueGitHubApiRepositorySource -} from '../../../github/github-api-repository' -import { projectGhExecOptions } from '../../../github/project-view/internals' +} from '../../../github/repositories/github-api-repository' +import { projectGhExecOptions } from '../../../github/project-view/project-host-execution' import { runAsDefaultWslCliUserAct } from '../../../git/command-runner/hosted-cli/default-wsl-cli-fallback' type LocalGitArgs = [] | [LocalProjectGhExecOptions] diff --git a/src/main/runtime/integrations/github/runtime-github-project-commands.ts b/src/main/runtime/integrations/github/runtime-github-project-commands.ts index 6ca90af4da3..60b9d7872e9 100644 --- a/src/main/runtime/integrations/github/runtime-github-project-commands.ts +++ b/src/main/runtime/integrations/github/runtime-github-project-commands.ts @@ -37,7 +37,7 @@ import type { UpdateIssueTypeBySlugArgs, UpdateProjectItemFieldArgs, UpdatePullRequestBySlugArgs -} from '../../../../shared/github/project-request-types' +} from '../../../../shared/github/projects/project-request-types' export class RuntimeGitHubProjectCommands { listGitHubProjects(args?: ListAccessibleProjectsArgs) { diff --git a/src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts b/src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts index 9f940082a59..8381eff8594 100644 --- a/src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts +++ b/src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts @@ -1,21 +1,21 @@ -import type { GhAccountBinding } from '../../../../shared/github/account-binding' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' -import type { ListWorkItemsResult } from '../../../../shared/github/work-item-types' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { ListWorkItemsResult } from '../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../shared/repo-types' import type { LocalProjectGhExecOptions } from '../../../workspaces/execution-host/project-runtime-git-options' import { countWorkItems } from '../../../github/client/list/count-work-items' import { getWorkItem, getWorkItemByOwnerRepo } from '../../../github/client/fetch/get-work-item' -import { listAssignableUsers, listLabels } from '../../../github/issue-field-options' -import { listIssues } from '../../../github/issues' +import { listAssignableUsers, listLabels } from '../../../github/issues/issue-field-options' +import { listIssues } from '../../../github/issues/issues' import { listWorkItems } from '../../../github/client/list/list-work-items' import type { MainWorkItem } from '../../../github/client/map/work-item-field-coercion' import { listGhAccountBindingInventory, validateGhAccountBinding -} from '../../../github/gh-account-binding-inventory' -import { getRateLimit } from '../../../github/rate-limit' -import { resolveGhAccountRequiredHost } from '../../../github/account-binding-required-host' -import { getWorkItemDetails } from '../../../github/work-item-details' +} from '../../../github/accounts/gh-account-binding-inventory' +import { getRateLimit } from '../../../github/api/rate-limit' +import { resolveGhAccountRequiredHost } from '../../../github/accounts/account-binding-required-host' +import { getWorkItemDetails } from '../../../github/work-items/work-item-details' import { getRepoRoutingConnectionId } from '../../../../shared/execution-host' type LocalGitArgs = [] | [LocalProjectGhExecOptions] diff --git a/src/main/runtime/integrations/github/runtime-github-review-mutation-commands.ts b/src/main/runtime/integrations/github/runtime-github-review-mutation-commands.ts index 74678a15ac0..6949c22e576 100644 --- a/src/main/runtime/integrations/github/runtime-github-review-mutation-commands.ts +++ b/src/main/runtime/integrations/github/runtime-github-review-mutation-commands.ts @@ -1,7 +1,7 @@ -import type { GhAccountBinding } from '../../../../shared/github/account-binding' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { GitHubPullRequestStateUpdate } from '../../../../shared/issue-mutation-types' -import type { GitHubReactionContent } from '../../../../shared/github/comment-types' +import type { GitHubReactionContent } from '../../../../shared/github/comments/comment-types' import type { Repo } from '../../../../shared/repo-types' import type { LocalProjectGhExecOptions } from '../../../workspaces/execution-host/project-runtime-git-options' import { mergePR } from '../../../github/client/merge/merge-pr' diff --git a/src/main/runtime/integrations/github/runtime-github-review-query-commands.ts b/src/main/runtime/integrations/github/runtime-github-review-query-commands.ts index 36504aa8e11..48e4118088f 100644 --- a/src/main/runtime/integrations/github/runtime-github-review-query-commands.ts +++ b/src/main/runtime/integrations/github/runtime-github-review-query-commands.ts @@ -1,11 +1,12 @@ -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../shared/repo-types' import type { LocalProjectGhExecOptions } from '../../../workspaces/execution-host/project-runtime-git-options' -import { getIssue } from '../../../github/issues' +import { getIssue } from '../../../github/issues/issues' import { getPRCheckDetails } from '../../../github/client/check/get-pr-check-details' import { getPRChecks } from '../../../github/client/check/get-pr-checks' import { getPRComments } from '../../../github/client/fetch/get-pr-comments' -import { getPRFileContents } from '../../../github/work-item-details' +import { getPRFileContents } from '../../../github/pull-requests/files/pull-request-file-contents' import { getRepoRoutingConnectionId } from '../../../../shared/execution-host' type LocalGitArgs = [] | [LocalProjectGhExecOptions] diff --git a/src/main/runtime/integrations/github/runtime-github-worktree-base.ts b/src/main/runtime/integrations/github/runtime-github-worktree-base.ts index 4e41d8f86b2..2ed65b7763c 100644 --- a/src/main/runtime/integrations/github/runtime-github-worktree-base.ts +++ b/src/main/runtime/integrations/github/runtime-github-worktree-base.ts @@ -3,10 +3,10 @@ import type { Repo } from '../../../../shared/repo-types' import { fetchPrHeadTrackingRef, fetchGitHubPullRequestHeadRef -} from '../../../github/pr-head-tracking-ref' -import { resolveGitHubPrStartPoint } from '../../../github/pr-start-point' -import { resolvePrStartPointUnlessWslStopped } from '../../../github/pr-start-point-wsl-gate' -import { resolveGitHubReviewHeadRemote } from '../../../github/review-head-remote' +} from '../../../github/pull-requests/start-points/pr-head-tracking-ref' +import { resolveGitHubPrStartPoint } from '../../../github/pull-requests/start-points/pr-start-point' +import { resolvePrStartPointUnlessWslStopped } from '../../../github/pull-requests/start-points/pr-start-point-wsl-gate' +import { resolveGitHubReviewHeadRemote } from '../../../github/pull-requests/start-points/review-head-remote' import { gitExecFileAsync } from '../../../git/command-runner/git-exec-file' import { getLocalProjectGitExecOptions, diff --git a/src/main/runtime/integrations/gitlab/runtime-gitlab-worktree-base.ts b/src/main/runtime/integrations/gitlab/runtime-gitlab-worktree-base.ts index b996fd2513e..2ac4f5b6d59 100644 --- a/src/main/runtime/integrations/gitlab/runtime-gitlab-worktree-base.ts +++ b/src/main/runtime/integrations/gitlab/runtime-gitlab-worktree-base.ts @@ -16,7 +16,7 @@ import type { ProjectRuntimeResolutionStore } from '../../../workspaces/executio import { resolveRuntimeGitLabForkMrBase } from './runtime-gitlab-fork-mr-base' import { resolveRuntimeGitLabIssueSourceRemote } from './runtime-gitlab-issue-source-remote' import { reviewStartPointFailure } from '../../../../shared/review-start-point-failure-copy' -import { resolvePrStartPointUnlessWslStopped } from '../../../github/pr-start-point-wsl-gate' +import { resolvePrStartPointUnlessWslStopped } from '../../../github/pull-requests/start-points/pr-start-point-wsl-gate' export type RuntimeGitLabWorktreeBaseArgs = { repoSelector: string diff --git a/src/main/runtime/integrations/reviews/runtime-hosted-review-commands.ts b/src/main/runtime/integrations/reviews/runtime-hosted-review-commands.ts index 47bfa21315c..63a4debdc77 100644 --- a/src/main/runtime/integrations/reviews/runtime-hosted-review-commands.ts +++ b/src/main/runtime/integrations/reviews/runtime-hosted-review-commands.ts @@ -1,29 +1,30 @@ -import type { - CreateHostedReviewInput, - CreateHostedReviewResult, - CreateStackedHostedReviewInput, - CreateStackedHostedReviewResult, - HostedReviewCreationEligibility, - HostedReviewCreationEligibilityArgs, - HostedReviewForBranchReply -} from '../../../../shared/source-control/hosted-review/hosted-review-contracts' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' -import type { - GitHubPRRefreshReason, - PRRefreshOutcome -} from '../../../../shared/github/pull-request-refresh-types' -import type { Repo } from '../../../../shared/repo-types' -import type { DeviceScope } from '../../../../shared/runtime-session-contracts' import { getPRForBranchOutcome, readRecentPhonePROutcome } from '../../../github/client/lookup/pr-for-branch-outcome' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { + GitHubPRRefreshReason, + PRRefreshOutcome +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { Repo } from '../../../../shared/repo-types' +import type { DeviceScope } from '../../../../shared/runtime-session-contracts' import { getRepoSlug, getRepoUpstream } from '../../../github/client/fetch/repo-slug-upstream' +import { admissionTierForRefreshReason } from '../../../github/pull-requests/refresh/pr-refresh-candidate-policy' import type { GitHubPRBranchLookupOptions } from '../../../github/client/lookup/pull-request-lookup-data' -import { admissionTierForRefreshReason } from '../../../github/pr-refresh-candidate-policy' -import { runHostedPRRead } from '../../../github/pr-refresh-coordinator' +import { runHostedPRRead } from '../../../github/pull-requests/refresh/pr-refresh-coordinator' +import { + type CreateHostedReviewInput, + type CreateHostedReviewResult, + type CreateStackedHostedReviewInput, + type CreateStackedHostedReviewResult, + type HostedReviewCreationEligibility, + type HostedReviewCreationEligibilityArgs, + type HostedReviewForBranchReply, + hostedReviewForBranchReply +} from '../../../../shared/source-control/hosted-review/hosted-review-contracts' import type { GitAdmissionTier } from '../../../../shared/rpc-contract/git-admission-tier-params' -import { hostedReviewForBranchReply } from '../../../../shared/source-control/hosted-review/hosted-review-contracts' + import { readHostedReviewForBranch } from '../../../source-control/hosted-review' import { getStoredRepoExecutionHostId } from '../../../workspaces/execution-host/repo-execution-host' import { hostedReviewSshConnectionId } from '../../../source-control/hosted-review-execution-host' diff --git a/src/main/runtime/integrations/reviews/runtime-hosted-review-pr-reuse.test.ts b/src/main/runtime/integrations/reviews/runtime-hosted-review-pr-reuse.test.ts index 00ec6e32707..caa4161c0a1 100644 --- a/src/main/runtime/integrations/reviews/runtime-hosted-review-pr-reuse.test.ts +++ b/src/main/runtime/integrations/reviews/runtime-hosted-review-pr-reuse.test.ts @@ -1,20 +1,25 @@ +import type * as GithubRepositoryIdentityModule from '../../../github/repositories/github-repository-identity' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { GitHubPRRefreshReason, PRRefreshOutcome -} from '../../../../shared/github/pull-request-refresh-types' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { GitHubRepoContext, LocalGitExecOptions -} from '../../../github/github-repository-identity' +} from '../../../github/repositories/github-repository-identity' import type { Repo } from '../../../../shared/repo-types' import type { DeviceScope } from '../../../../shared/runtime-session-contracts' -import { makePR } from '../../../github/pr-refresh-coordinator-test-harness' +import { makePR } from '../../../github/pull-requests/refresh/pr-refresh-coordinator-test-harness' const mocks = vi.hoisted(() => ({ resolve: vi.fn() })) -vi.mock('../../../github/gh-utils', () => ({ +vi.mock('../../../github/execution/github-operation-slots', () => ({ acquire: vi.fn(async () => {}), - release: vi.fn(), + release: vi.fn() +})) + +vi.mock('../../../github/repositories/github-repository-identity', async (importOriginal) => ({ + ...(await importOriginal()), githubRepoContext: ( repoPath: string, connectionId: string | null, @@ -25,7 +30,7 @@ vi.mock('../../../github/gh-utils', () => ({ vi.mock('../../../github/client/lookup/branch-lookup-resolution', () => ({ resolvePRForBranchOutcome: mocks.resolve })) -vi.mock('../../../github/pr-refresh-coordinator', () => ({ +vi.mock('../../../github/pull-requests/refresh/pr-refresh-coordinator', () => ({ runHostedPRRead: (_candidate: unknown, read: () => Promise) => read() })) import { RuntimeHostedReviewCommands } from './runtime-hosted-review-commands' diff --git a/src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.ts b/src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.ts index fa30a6d765a..98f605f6a72 100644 --- a/src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.ts +++ b/src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.ts @@ -1,7 +1,7 @@ import { PHONE_RELAY_CONNECT_BUDGET_MS } from '../../../shared/remote-runtime/recipe-vm-wake' import { verifyRelayedGitHubAuthority } from './relay-github-authority' import { createRelayedBrowserTab } from './relayed-browser-tab-create' -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../shared/github/work-item-target' +import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../shared/github/work-items/work-item-target' import { pairedRuntimeConnectionFromOffer } from '../../../shared/remote-runtime/pairing/paired-runtime-connection' import { randomUUID } from 'node:crypto' import type { RpcRequest } from '../rpc/core' diff --git a/src/main/runtime/mobile-desktop-relay/relay-github-authority.ts b/src/main/runtime/mobile-desktop-relay/relay-github-authority.ts index 552b91f0a99..cd28d4cb32a 100644 --- a/src/main/runtime/mobile-desktop-relay/relay-github-authority.ts +++ b/src/main/runtime/mobile-desktop-relay/relay-github-authority.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'node:crypto' import { z } from 'zod' -import { classifyExactGitHubOperation } from '../../../shared/github/exact-work-item-operation' -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../shared/github/work-item-target' +import { classifyExactGitHubOperation } from '../../../shared/github/work-items/exact-work-item-operation' +import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../shared/github/work-items/work-item-target' import { GITHUB_EXACT_DETAILS_READ_INTENT_RUNTIME_CAPABILITY } from '../../../shared/work-items/task-source-runtime-capabilities' import { GITHUB_EXACT_WORK_ITEM_TARGET_RUNTIME_CAPABILITY } from '../../../shared/source-control/runtime/source-control-runtime-capabilities' import type { RemoteRuntimePassthroughSocket } from '../../../shared/remote-runtime/remote-runtime-passthrough-socket' diff --git a/src/main/runtime/orca-runtime-test-mocks/github-hosted-review-test-harness.ts b/src/main/runtime/orca-runtime-test-mocks/github-hosted-review-test-harness.ts index 916703fc1d8..464f41685a3 100644 --- a/src/main/runtime/orca-runtime-test-mocks/github-hosted-review-test-harness.ts +++ b/src/main/runtime/orca-runtime-test-mocks/github-hosted-review-test-harness.ts @@ -1,3 +1,29 @@ +import type * as RepoSlugUpstreamModule from '../../github/client/fetch/repo-slug-upstream' +import type * as GetPrForBranchModule from '../../github/client/lookup/get-pr-for-branch' +import type * as PrForBranchOutcomeModule from '../../github/client/lookup/pr-for-branch-outcome' +import type * as GetWorkItemModule from '../../github/client/fetch/get-work-item' +import type * as PullRequestPushTargetModule from '../../github/client/lookup/pull-request-push-target' +import type * as GetPrChecksModule from '../../github/client/check/get-pr-checks' +import type * as RerunPrChecksModule from '../../github/client/check/rerun-pr-checks' +import type * as GetPrCheckDetailsModule from '../../github/client/check/get-pr-check-details' +import type * as GetPrCommentsModule from '../../github/client/fetch/get-pr-comments' +import type * as ResolveReviewThreadModule from '../../github/client/update/resolve-review-thread' +import type * as PrFileViewedModule from '../../github/client/update/pr-file-viewed' +import type * as PrDetailsModule from '../../github/client/update/pr-details' +import type * as MergePrModule from '../../github/client/merge/merge-pr' +import type * as PrAutoMergeModule from '../../github/client/merge/pr-auto-merge' +import type * as PrStateModule from '../../github/client/update/pr-state' +import type * as PrReviewersModule from '../../github/client/update/pr-reviewers' +import type * as AddPrReviewCommentModule from '../../github/client/create/add-pr-review-comment' +import type * as IssuesModule from '../../github/issues/issues' +import type * as ListWorkItemsModule from '../../github/client/list/list-work-items' +import type * as CountWorkItemsModule from '../../github/client/list/count-work-items' +import type * as IssueCreateModule from '../../github/issues/issue-create' +import type * as IssueUpdateModule from '../../github/issues/issue-update' +import type * as IssueCommentModule from '../../github/issues/issue-comment' +import type * as IssueFieldOptionsModule from '../../github/issues/issue-field-options' +import type * as WorkItemDetailsModule from '../../github/work-items/work-item-details' +import type * as PullRequestFileContentsModule from '../../github/pull-requests/files/pull-request-file-contents' import { vi, type Mock } from 'vitest' // GitHub and hosted-review mocks shared by runtime specs through setup.spec. @@ -105,176 +131,147 @@ vi.mock('../../source-control/hosted-review', () => ({ })) vi.mock('../../github/client/fetch/repo-slug-upstream', async (importOriginal) => ({ - ...(await importOriginal>()), + ...(await importOriginal()), getRepoSlug: getRepoSlugMock, getRepoUpstream: getRepoUpstreamMock })) -vi.mock('../../github/client', async (importOriginal) => { - const actual = (await importOriginal()) as Record - return { - ...actual, - getPRForBranch: getPRForBranchMock, - getPRForBranchOutcome: getPRForBranchOutcomeMock, - getRepoSlug: getRepoSlugMock, - getRepoUpstream: getRepoUpstreamMock, - getWorkItem: getGitHubWorkItemMock, - getPullRequestPushTarget: getPullRequestPushTargetMock, - getWorkItemByOwnerRepo: getGitHubWorkItemByOwnerRepoMock, - getPRChecks: getGitHubPRChecksMock, - rerunPRChecks: rerunGitHubPRChecksMock, - getPRCheckDetails: getGitHubPRCheckDetailsMock, - getPRComments: getGitHubPRCommentsMock, - resolveReviewThread: resolveGitHubReviewThreadMock, - setPRFileViewed: setGitHubPRFileViewedMock, - updatePRTitle: updateGitHubPRTitleMock, - updatePRDetails: updateGitHubPRDetailsMock, - mergePR: mergeGitHubPRMock, - setPRAutoMerge: setGitHubPRAutoMergeMock, - updatePRState: updateGitHubPRStateMock, - requestPRReviewers: requestGitHubPRReviewersMock, - removePRReviewers: removeGitHubPRReviewersMock, - addPRReviewComment: addGitHubPRReviewCommentMock, - addPRReviewCommentReply: addGitHubPRReviewCommentReplyMock, - listIssues: listGitHubIssuesMock, - listWorkItems: listGitHubWorkItemsMock, - countWorkItems: countGitHubWorkItemsMock, - getIssue: getIssueMock, - createIssue: createGitHubIssueMock, - updateIssue: updateGitHubIssueMock, - addIssueComment: addGitHubIssueCommentMock, - listLabels: listGitHubLabelsMock, - listAssignableUsers: listGitHubAssignableUsersMock - } -}) +vi.mock('../../github/client/lookup/get-pr-for-branch', async (importOriginal) => ({ + ...(await importOriginal()), + getPRForBranch: getPRForBranchMock +})) -vi.mock('../../github/work-item-details', async (importOriginal) => { - const actual = (await importOriginal()) as Record - return { - ...actual, - getWorkItemDetails: getGitHubWorkItemDetailsMock, - getPRFileContents: getGitHubPRFileContentsMock - } -}) - -vi.mock('../../github/issues', async (importOriginal) => { - const actual = (await importOriginal()) as Record - return { - ...actual, - getIssue: getIssueMock, - listIssues: listGitHubIssuesMock - } -}) - -vi.mock(import('../../github/client/lookup/pr-for-branch-outcome'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/lookup/pr-for-branch-outcome', async (importOriginal) => ({ + ...(await importOriginal()), getPRForBranchOutcome: getPRForBranchOutcomeMock })) -vi.mock(import('../../github/client/fetch/get-work-item'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/fetch/get-work-item', async (importOriginal) => ({ + ...(await importOriginal()), getWorkItem: getGitHubWorkItemMock, getWorkItemByOwnerRepo: getGitHubWorkItemByOwnerRepoMock })) -vi.mock(import('../../github/client/check/get-pr-checks'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/lookup/pull-request-push-target', async (importOriginal) => { + return { + ...(await importOriginal()), + getPullRequestPushTarget: getPullRequestPushTargetMock + } +}) + +vi.mock('../../github/client/check/get-pr-checks', async (importOriginal) => ({ + ...(await importOriginal()), getPRChecks: getGitHubPRChecksMock })) -vi.mock(import('../../github/client/check/rerun-pr-checks'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/check/rerun-pr-checks', async (importOriginal) => ({ + ...(await importOriginal()), rerunPRChecks: rerunGitHubPRChecksMock })) -vi.mock(import('../../github/client/check/get-pr-check-details'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/check/get-pr-check-details', async (importOriginal) => ({ + ...(await importOriginal()), getPRCheckDetails: getGitHubPRCheckDetailsMock })) -vi.mock(import('../../github/client/fetch/get-pr-comments'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/fetch/get-pr-comments', async (importOriginal) => ({ + ...(await importOriginal()), getPRComments: getGitHubPRCommentsMock })) -vi.mock(import('../../github/client/update/resolve-review-thread'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/update/resolve-review-thread', async (importOriginal) => ({ + ...(await importOriginal()), resolveReviewThread: resolveGitHubReviewThreadMock })) -vi.mock(import('../../github/client/update/pr-file-viewed'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/update/pr-file-viewed', async (importOriginal) => ({ + ...(await importOriginal()), setPRFileViewed: setGitHubPRFileViewedMock })) -vi.mock(import('../../github/client/update/pr-details'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/update/pr-details', async (importOriginal) => ({ + ...(await importOriginal()), updatePRTitle: updateGitHubPRTitleMock, updatePRDetails: updateGitHubPRDetailsMock })) -vi.mock(import('../../github/client/merge/merge-pr'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/merge/merge-pr', async (importOriginal) => ({ + ...(await importOriginal()), mergePR: mergeGitHubPRMock })) -vi.mock(import('../../github/client/merge/pr-auto-merge'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/merge/pr-auto-merge', async (importOriginal) => ({ + ...(await importOriginal()), setPRAutoMerge: setGitHubPRAutoMergeMock })) -vi.mock(import('../../github/client/update/pr-state'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/update/pr-state', async (importOriginal) => ({ + ...(await importOriginal()), updatePRState: updateGitHubPRStateMock })) -vi.mock(import('../../github/client/update/pr-reviewers'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/update/pr-reviewers', async (importOriginal) => ({ + ...(await importOriginal()), requestPRReviewers: requestGitHubPRReviewersMock, removePRReviewers: removeGitHubPRReviewersMock })) -vi.mock(import('../../github/client/create/add-pr-review-comment'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/create/add-pr-review-comment', async (importOriginal) => ({ + ...(await importOriginal()), addPRReviewComment: addGitHubPRReviewCommentMock, addPRReviewCommentReply: addGitHubPRReviewCommentReplyMock })) -vi.mock(import('../../github/client/list/list-work-items'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/issues/issues', async (importOriginal) => ({ + ...(await importOriginal()), + listIssues: listGitHubIssuesMock, + getIssue: getIssueMock +})) + +vi.mock('../../github/client/list/list-work-items', async (importOriginal) => ({ + ...(await importOriginal()), listWorkItems: listGitHubWorkItemsMock })) -vi.mock(import('../../github/client/list/count-work-items'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/client/list/count-work-items', async (importOriginal) => ({ + ...(await importOriginal()), countWorkItems: countGitHubWorkItemsMock })) -vi.mock(import('../../github/issue-create'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/issues/issue-create', async (importOriginal) => ({ + ...(await importOriginal()), createIssue: createGitHubIssueMock })) -vi.mock(import('../../github/issue-update'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/issues/issue-update', async (importOriginal) => ({ + ...(await importOriginal()), updateIssue: updateGitHubIssueMock })) -vi.mock(import('../../github/issue-comment'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/issues/issue-comment', async (importOriginal) => ({ + ...(await importOriginal()), addIssueComment: addGitHubIssueCommentMock })) -vi.mock(import('../../github/issue-field-options'), async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('../../github/issues/issue-field-options', async (importOriginal) => ({ + ...(await importOriginal()), listLabels: listGitHubLabelsMock, listAssignableUsers: listGitHubAssignableUsersMock })) -vi.mock(import('../../github/client/lookup/get-pr-for-branch'), async (importOriginal) => ({ - ...(await importOriginal()), - getPRForBranch: getPRForBranchMock -})) +vi.mock('../../github/work-items/work-item-details', async (importOriginal) => { + return { + ...(await importOriginal()), + getWorkItemDetails: getGitHubWorkItemDetailsMock + } +}) + +vi.mock('../../github/pull-requests/files/pull-request-file-contents', async (importOriginal) => { + return { + ...(await importOriginal()), + getPRFileContents: getGitHubPRFileContentsMock + } +}) export { createHostedReviewMock, diff --git a/src/main/runtime/repositories/runtime-repository-settings-controller.ts b/src/main/runtime/repositories/runtime-repository-settings-controller.ts index 8f81f1bcb82..b312766d2f2 100644 --- a/src/main/runtime/repositories/runtime-repository-settings-controller.ts +++ b/src/main/runtime/repositories/runtime-repository-settings-controller.ts @@ -3,7 +3,7 @@ import { isRepoHidden } from '../../../shared/hidden-projects' import { commitProvenRepoKindChange } from '../../workspaces/catalog/folder-repo-git-conversion' import type { Repo } from '../../../shared/repo-types' import type { RuntimeRepoSelector } from '../workspaces/catalog/runtime-repo-selector' -import type { GhAccountBinding } from '../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../shared/github/accounts/account-binding' import { invalidateAuthorizedRootsCache } from '../../ipc/worktrees/removal/registered-worktree-roots-cache' import { prepareLocalWorktreeRootForRepo } from '../../workspaces/creation/worktree-root-preparation' import type { RuntimeStore } from '../construction/runtime-store-contract' diff --git a/src/main/runtime/sidebar-inputs/sidebar-inputs-feed-announcements.test.ts b/src/main/runtime/sidebar-inputs/sidebar-inputs-feed-announcements.test.ts deleted file mode 100644 index 05287e24028..00000000000 --- a/src/main/runtime/sidebar-inputs/sidebar-inputs-feed-announcements.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -// Exercise every domain's real store writers and announcements (design §9.4); phone reads must not re-read domains. -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { RuntimeClientEvent } from '../../../shared/runtime-client-events' -import type { Repo } from '../../../shared/repo-types' -import type { PRInfo } from '../../../shared/github/pull-request-types' -import type { WslHostService } from '../../wsl/host-lifecycle/wsl-host-service' -import { setWslHostService } from '../../wsl/host-lifecycle/wsl-host-service-instance' -import { getGitHubPRCacheKeyCandidates } from '../../../shared/github/github-cache-key' -import { toRuntimeExecutionHostId } from '../../../shared/execution-host' -import { - getWorktreeHostIdentity, - hostWorktreeTarget -} from '../../../shared/worktree/host-qualified-identity' -import { - prGroupOfReviewFact, - WorkspaceReviewFactsSnapshotSchema, - type WorkspaceReviewFact -} from '../../../shared/sidebar-list/workspace-review-facts' -import { fixture } from '../../persistence/loading-store/profile-writing/profile-state-delayed-authority-fixture' -import { ProducerRevision } from './producer-revision' -import { SidebarInputsFeed } from './sidebar-inputs-feed' -import { - sidebarInputsFeedDomains, - type SidebarInputsFeedSources -} from './sidebar-inputs-feed-domains' -import { TestPhone } from './sidebar-inputs-feed.test-fixture' - -const repo: Repo = { id: 'repo-1', path: '/r', displayName: 'r', badgeColor: 'blue', addedAt: 1 } -const worktree = { - id: 'repo-1::/r/a', - repoId: repo.id, - hostId: 'local', - branch: 'refs/heads/feature', - linkedPR: null -} as const - -async function host() { - const { store } = await fixture() - store.addRepo(repo) - const clientEvents = new Set<(event: RuntimeClientEvent) => void>() - const temporal = new Set<() => void>() - const wslListeners = new Set<() => void>() - const revisions = { - activity: new ProducerRevision(), - paired: new ProducerRevision(), - creations: new ProducerRevision(), - rows: new ProducerRevision(), - detected: new ProducerRevision(), - liveContent: new ProducerRevision() - } - // A WSL registry that only announces; this host has no WSL hosts. - const wsl = { - getStatuses: () => [], - onStatusesChanged: (listener: () => void) => { - wslListeners.add(listener) - return () => wslListeners.delete(listener) - } - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the desktop domain reads only these two members. - setWslHostService(wsl as unknown as WslHostService) - const sources: SidebarInputsFeedSources = { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the domains read only the members stubbed here. - facts: { - workspaceActivityFacts: { revision: revisions.activity }, - getWorkspaceActivityFacts: () => ({ revision: 0, facts: {} }), - getSidebarTemporalSnapshot: () => ({ sessionId: null, retention: null }), - onSidebarTemporalChange: (listener: () => void) => { - temporal.add(listener) - return () => temporal.delete(listener) - }, - detectedCheckouts: { - revision: revisions.detected, - snapshot: () => ({}), - watch: (listener: () => void) => revisions.detected.onChange(listener) - }, - workspaceLiveContent: { revision: revisions.liveContent, snapshot: () => ({}) } - } as unknown as SidebarInputsFeedSources['facts'], - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: as above. - pairedServerRows: { - revision: revisions.paired, - snapshot: () => ({ servers: [] }), - catalogs: () => [] - } as unknown as SidebarInputsFeedSources['pairedServerRows'], - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: as above. - workspaceLifecycle: { - revision: revisions.creations, - creationsSnapshot: () => ({ creations: {} }), - hints: () => ({}) - } as unknown as SidebarInputsFeedSources['workspaceLifecycle'], - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: as above; one cached worktree, already scanned. - workspaceRows: { - revision: revisions.rows, - snapshot: () => ({ revision: 0, rows: {} }), - resolvedWorktrees: () => [worktree], - ready: async () => {} - } as unknown as SidebarInputsFeedSources['workspaceRows'], - repoIcons: { idsByRepo: () => ({}) }, - store: () => store, - onClientEvent: (listener) => { - clientEvents.add(listener) - return () => clientEvents.delete(listener) - } - } - const emit = (event: RuntimeClientEvent): void => - clientEvents.forEach((listener) => listener(event)) - const catalogEvent = (): void => emit({ type: 'reposChanged' }) - // Every public writer of what each domain reads. - const writers: Record void>> = { - activity: { - 'activity producer': () => revisions.activity.bump(), - 'paired server rows': () => revisions.paired.bump(), - // Only a paired server's partition holds activity (its browser and chat tabs). - 'saved session': () => - store.patchWorkspaceSession({ activeTabId: 'tab-1' }, toRuntimeExecutionHostId('env-1')) - }, - temporal: { 'retention owner': () => temporal.forEach((listener) => listener()) }, - creations: { 'lifecycle queue': () => revisions.creations.bump() }, - inProgress: { 'lifecycle queue': () => revisions.creations.bump() }, - workspaces: { 'workspace rows producer': () => revisions.rows.bump() }, - detected: { 'detected checkouts producer': () => revisions.detected.bump() }, - liveContent: { 'live content producer': () => revisions.liveContent.bump() }, - view: { 'ui write': () => store.updateUI({ groupBy: 'pr-status' }) }, - pairedServerHosts: { 'paired server rows': () => revisions.paired.bump() }, - pairedServerRows: { 'paired server rows': () => revisions.paired.bump() }, - desktop: { - 'catalog event': catalogEvent, - 'ui write': () => store.updateUI({ workspaceHostOrder: ['local'] }), - 'settings write': () => - store.updateSettings({ - showPinnedWorktreesInGroups: !store.getSettings().showPinnedWorktreesInGroups - }), - 'saved session': () => store.patchWorkspaceSession({ activeWorktreeId: worktree.id }), - 'pin write': () => - store.setWorktreeMeta(hostWorktreeTarget({ id: worktree.id, hostId: 'local' }), { - isPinned: true - }), - 'WSL status': () => wslListeners.forEach((listener) => listener()) - }, - projectCatalog: { - 'catalog event': catalogEvent, - 'paired server rows': () => revisions.paired.bump(), - 'ui write': () => store.updateUI({ projectOrderBy: 'recent' }), - 'full save': () => - store.setWorktreeLineage(hostWorktreeTarget({ id: worktree.id, hostId: 'local' }), { - worktreeId: worktree.id, - worktreeInstanceId: 'a-1', - parentWorktreeId: 'repo-1::/r/p', - parentWorktreeInstanceId: 'p-1', - origin: 'manual', - capture: { source: 'manual-action', confidence: 'explicit' }, - createdAt: 1 - }) - }, - repoIcons: { 'catalog event': catalogEvent }, - review: { - 'PR cache save': () => store.setGitHubCache({ ...store.getGitHubCache(), pr: {} }), - 'workspace rows producer': () => revisions.rows.bump(), - 'paired server rows': () => revisions.paired.bump(), - 'catalog event': catalogEvent - } - } - return { store, sources, writers } -} - -function feedReviewFact(value: unknown): WorkspaceReviewFact | null { - const parsed = WorkspaceReviewFactsSnapshotSchema.shape.facts.valueType.safeParse(value) - return parsed.success ? parsed.data : null -} - -beforeEach(() => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) -}) - -afterEach(() => { - setWslHostService(null) - vi.restoreAllMocks() -}) - -describe('sidebar inputs feed announcements', () => { - it('follows a PR cache save on a quiet desktop into the phone’s PR grouping', async () => { - const { store, sources } = await host() - const events: { generation: string; revision: number }[] = [] - const feed = new SidebarInputsFeed( - () => sidebarInputsFeedDomains(sources), - (event) => events.push(event) - ) - store.updateUI({ groupBy: 'pr-status' }) - const phone = new TestPhone(feed, 'conn-1') - phone.refresh() - await phone.settle() - expect(phone.view().review).toBeUndefined() - - // Nothing else happens on this desktop: no catalog event, no phone read, no metadata write. - const before = events.length - const [key] = getGitHubPRCacheKeyCandidates(repo, 'feature') - store.setGitHubCache({ - ...store.getGitHubCache(), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: grouping reads only number and state. - pr: { [key]: { data: { number: 7, state: 'merged' } as PRInfo, fetchedAt: 1 } } - }) - await vi.waitFor(() => expect(events.length).toBeGreaterThan(before)) - const notice = events.at(-1) - if (!notice) { - throw new Error('the PR cache save announced nothing') - } - phone.notice(notice) - await phone.settle() - const fact = phone.view().review?.[getWorktreeHostIdentity(worktree)] - expect(fact).toEqual({ provider: 'github', reviewState: 'merged' }) - expect(prGroupOfReviewFact(feedReviewFact(fact))).toBe('done') - phone.disconnect() - }) -}) diff --git a/src/main/runtime/workspaces/catalog/runtime-workspace-catalog-events.ts b/src/main/runtime/workspaces/catalog/runtime-workspace-catalog-events.ts index c32fb362212..7ea84a93972 100644 --- a/src/main/runtime/workspaces/catalog/runtime-workspace-catalog-events.ts +++ b/src/main/runtime/workspaces/catalog/runtime-workspace-catalog-events.ts @@ -5,7 +5,7 @@ import { import type { Repo } from '../../../../shared/repo-types' import { normalizeRuntimePathForComparison } from '../../../../shared/cross-platform-path' import { composeWorktreeId } from '../../../../shared/worktree/id' -import type { PullRequestChange } from '../../../github/pull-request-changed-events' +import type { PullRequestChange } from '../../../github/pull-requests/refresh/pull-request-changed-events' import { OrcaRuntimeWithGetStatus } from '../../hosts/runtime-status' export abstract class RuntimeWorkspaceCatalogEvents extends OrcaRuntimeWithGetStatus { diff --git a/src/main/source-control/forge-provider.ts b/src/main/source-control/forge-provider.ts index cf4f4b9ba1a..46ed21b53f7 100644 --- a/src/main/source-control/forge-provider.ts +++ b/src/main/source-control/forge-provider.ts @@ -23,12 +23,10 @@ import { getGiteaRepoSlug } from '../gitea/client' import { createGiteaPullRequest } from '../gitea/pull-request-creation' -import { - createGitHubPullRequest, - getGitHubPRLookupRateLimitBlock, - getPRForBranchOutcome, - getRepoSlug -} from '../github/client' +import { createGitHubPullRequest } from '../github/client/create/create-github-pull-request' +import { getGitHubPRLookupRateLimitBlock } from '../github/client/lookup/pr-lookup-rate-limit' +import { getPRForBranchOutcome } from '../github/client/lookup/pr-for-branch-outcome' +import { getRepoSlug } from '../github/client/fetch/repo-slug-upstream' import { getMergeRequest, getMergeRequestForBranchOrThrow, diff --git a/src/main/source-control/forge-review-mappers.ts b/src/main/source-control/forge-review-mappers.ts index 7a3cede98a7..be4598a8070 100644 --- a/src/main/source-control/forge-review-mappers.ts +++ b/src/main/source-control/forge-review-mappers.ts @@ -1,6 +1,6 @@ import type { HostedReviewInfo } from '../../shared/source-control/hosted-review/hosted-review-contracts' import { hostedReviewInfoFromGitHubPRInfo } from '../../shared/source-control/hosted-review/hosted-review-github' -import type { PRInfo } from '../../shared/github/pull-request-types' +import type { PRInfo } from '../../shared/github/pull-requests/pull-request-types' import type { MRInfo } from '../../shared/gitlab-types' import type { AzureDevOpsPullRequestInfo } from '../azure-devops/pull-request-mappers' import type { BitbucketPullRequestInfo } from '../bitbucket/pull-request-mappers' diff --git a/src/main/source-control/hosted-review-base-ref-suffix.test.ts b/src/main/source-control/hosted-review-base-ref-suffix.test.ts index 7de6024d0da..d07f256ac84 100644 --- a/src/main/source-control/hosted-review-base-ref-suffix.test.ts +++ b/src/main/source-control/hosted-review-base-ref-suffix.test.ts @@ -4,7 +4,8 @@ const { gitExecFileAsyncMock } = vi.hoisted(() => ({ gitExecFileAsyncMock: vi.fn() })) -vi.mock('../github/gh-utils', () => ({ +vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) diff --git a/src/main/source-control/hosted-review-creation-eligibility.test.ts b/src/main/source-control/hosted-review-creation-eligibility.test.ts index e672df3c077..ce8bdcb15fe 100644 --- a/src/main/source-control/hosted-review-creation-eligibility.test.ts +++ b/src/main/source-control/hosted-review-creation-eligibility.test.ts @@ -38,13 +38,19 @@ const { getEnterpriseGitHubRepoSlugMock: vi.fn() })) -vi.mock('../github/client', () => ({ - createGitHubPullRequest: createGitHubPullRequestMock, - getRepoSlug: getRepoSlugMock, +vi.mock('../github/client/create/create-github-pull-request', () => ({ + createGitHubPullRequest: createGitHubPullRequestMock +})) + +vi.mock('../github/client/fetch/repo-slug-upstream', () => ({ + getRepoSlug: getRepoSlugMock +})) + +vi.mock('../github/client/lookup/get-pr-for-branch', () => ({ getPRForBranch: vi.fn() })) -vi.mock('../github/github-enterprise-repository', () => ({ +vi.mock('../github/repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock })) @@ -86,10 +92,17 @@ vi.mock('../gitea/pull-request-creation', () => ({ isGiteaReviewCreationAuthenticated: isGiteaReviewCreationAuthenticatedMock })) -vi.mock('../github/gh-utils', () => ({ +vi.mock('../github/execution/github-operation-slots', () => ({ acquire: vi.fn(), - release: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, + release: vi.fn() +})) + +vi.mock('../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) @@ -116,7 +129,7 @@ vi.mock('./hosted-review', () => ({ import { createHostedReview, getHostedReviewCreationEligibility } from './hosted-review-creation' import { baseRefExistsOnRemote } from './hosted-review-creation-git-state' -import { _resetOriginGitHubApiRepositoryCache } from '../github/github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../github/repositories/github-api-repository-remote-probe' const LOCAL_REPO_ARGS = { executionHostId: 'local' as const, repoPath: '/repo' } diff --git a/src/main/source-control/hosted-review-creation-git-state.ts b/src/main/source-control/hosted-review-creation-git-state.ts index 57d32686ca1..c00cddeba72 100644 --- a/src/main/source-control/hosted-review-creation-git-state.ts +++ b/src/main/source-control/hosted-review-creation-git-state.ts @@ -5,7 +5,7 @@ import { } from '../../shared/source-control/hosted-review/hosted-review-refs' import { isSafeGitRefName } from '../../shared/git/status/git-status-upstream-ref' import type { GitUpstreamStatus } from '../../shared/git/status/git-status-types' -import { gitExecFileAsync } from '../github/gh-utils' +import { gitExecFileAsync } from '../git/command-runner/git-exec-file' import { isShowRefNoMatchError } from '../../shared/git/refs/git-show-ref-no-match' import { probeExactRefsViaForEachRef } from '../git/refs/resolution/exact-ref-probe' import { gitOptionalLocksDisabledEnv } from '../git/command-runner/git-environment/git-process-env' diff --git a/src/main/source-control/hosted-review-creation-gitlab-self-hosted.test.ts b/src/main/source-control/hosted-review-creation-gitlab-self-hosted.test.ts index 00ae0c91879..d6da312b510 100644 --- a/src/main/source-control/hosted-review-creation-gitlab-self-hosted.test.ts +++ b/src/main/source-control/hosted-review-creation-gitlab-self-hosted.test.ts @@ -35,9 +35,15 @@ vi.mock('../git/command-runner/hosted-cli/gh-exec-file', async (importOriginal) ghExecFileAsync: ghExecFileAsyncMock })) -vi.mock('../github/client', () => ({ - createGitHubPullRequest: vi.fn(), - getRepoSlug: getRepoSlugMock, +vi.mock('../github/client/create/create-github-pull-request', () => ({ + createGitHubPullRequest: vi.fn() +})) + +vi.mock('../github/client/fetch/repo-slug-upstream', () => ({ + getRepoSlug: getRepoSlugMock +})) + +vi.mock('../github/client/lookup/get-pr-for-branch', () => ({ getPRForBranch: vi.fn() })) diff --git a/src/main/source-control/hosted-review-creation-provider.ts b/src/main/source-control/hosted-review-creation-provider.ts index f54d913b457..caa45a48742 100644 --- a/src/main/source-control/hosted-review-creation-provider.ts +++ b/src/main/source-control/hosted-review-creation-provider.ts @@ -4,8 +4,9 @@ import type { HostedReviewCreationProvider } from '../../shared/source-control/h import { isAzureDevOpsReviewCreationAuthenticated } from '../azure-devops/pull-request-creation' import { isBitbucketReviewCreationAuthenticated } from '../bitbucket/pull-request-creation' import { isGiteaReviewCreationAuthenticated } from '../gitea/pull-request-creation' -import { getEnterpriseGitHubRepoSlug } from '../github/github-enterprise-repository' -import { acquire, ghExecFileAsync, release } from '../github/gh-utils' +import { getEnterpriseGitHubRepoSlug } from '../github/repositories/github-enterprise-repository' +import { acquire, release } from '../github/execution/github-operation-slots' +import { ghExecFileAsync } from '../git/command-runner/hosted-cli/gh-exec-file' import { getProjectSlug } from '../gitlab/merge-requests/merge-request-lookup' import { acquire as acquireGlab, diff --git a/src/main/source-control/hosted-review-creation.test.ts b/src/main/source-control/hosted-review-creation.test.ts index ec8ca06b16e..441add3fa9d 100644 --- a/src/main/source-control/hosted-review-creation.test.ts +++ b/src/main/source-control/hosted-review-creation.test.ts @@ -38,13 +38,19 @@ const { getEnterpriseGitHubRepoSlugMock: vi.fn() })) -vi.mock('../github/client', () => ({ - createGitHubPullRequest: createGitHubPullRequestMock, - getRepoSlug: getRepoSlugMock, +vi.mock('../github/client/create/create-github-pull-request', () => ({ + createGitHubPullRequest: createGitHubPullRequestMock +})) + +vi.mock('../github/client/fetch/repo-slug-upstream', () => ({ + getRepoSlug: getRepoSlugMock +})) + +vi.mock('../github/client/lookup/get-pr-for-branch', () => ({ getPRForBranch: vi.fn() })) -vi.mock('../github/github-enterprise-repository', () => ({ +vi.mock('../github/repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock })) @@ -86,10 +92,17 @@ vi.mock('../gitea/pull-request-creation', () => ({ isGiteaReviewCreationAuthenticated: isGiteaReviewCreationAuthenticatedMock })) -vi.mock('../github/gh-utils', () => ({ +vi.mock('../github/execution/github-operation-slots', () => ({ acquire: vi.fn(), - release: vi.fn(), - ghExecFileAsync: ghExecFileAsyncMock, + release: vi.fn() +})) + +vi.mock('../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal>()), gitExecFileAsync: gitExecFileAsyncMock })) @@ -117,7 +130,7 @@ vi.mock('./hosted-review', () => ({ import { createHostedReview } from './hosted-review-creation' -import { _resetOriginGitHubApiRepositoryCache } from '../github/github-api-repository' +import { _resetOriginGitHubApiRepositoryCache } from '../github/repositories/github-api-repository-remote-probe' // Isolate module-level repository identities between tests. beforeEach(() => { diff --git a/src/main/source-control/hosted-review-creation.ts b/src/main/source-control/hosted-review-creation.ts index 07caa8f26b8..ba2bc22a976 100644 --- a/src/main/source-control/hosted-review-creation.ts +++ b/src/main/source-control/hosted-review-creation.ts @@ -8,8 +8,8 @@ import type { } from '../../shared/source-control/hosted-review/hosted-review-contracts' import { supportsHostedReviewCreation } from '../../shared/source-control/hosted-review/hosted-review-creation-providers' import { normalizeHostedReviewBaseRef } from '../../shared/source-control/hosted-review/hosted-review-refs' -import { getRepoSlug } from '../github/client' -import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key' +import { getRepoSlug } from '../github/client/fetch/repo-slug-upstream' +import { isDefaultGitHubHost } from '../../shared/github/repositories/repository-identity-key' import { detectHostedReviewProvider, getForgeProviderForRepository diff --git a/src/main/source-control/hosted-review-execution-host-routing.test.ts b/src/main/source-control/hosted-review-execution-host-routing.test.ts index 2dd8c014ebe..0d6b3596358 100644 --- a/src/main/source-control/hosted-review-execution-host-routing.test.ts +++ b/src/main/source-control/hosted-review-execution-host-routing.test.ts @@ -53,9 +53,16 @@ const { assertRemoteUrlReadableMock: vi.fn() })) -vi.mock('../github/gh-utils', () => ({ - gitExecFileAsync: gitExecFileAsyncMock, - ghExecFileAsync: ghExecFileAsyncMock, +vi.mock('../git/command-runner/git-exec-file', async (importOriginal) => ({ + ...(await importOriginal>()), + gitExecFileAsync: gitExecFileAsyncMock +})) + +vi.mock('../git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: ghExecFileAsyncMock +})) + +vi.mock('../github/execution/github-operation-slots', () => ({ acquire: vi.fn(async () => {}), release: vi.fn() })) @@ -117,15 +124,15 @@ vi.mock('../github/client/lookup/pr-for-branch-outcome', () => ({ getPRForBranchOutcome: getPRForBranchOutcomeMock })) -vi.mock('../github/client', () => ({ - getRepoSlug: getRepoSlugMock, - getRepoUpstream: getRepoUpstreamMock, - getPRForBranchOutcome: getPRForBranchOutcomeMock, - getGitHubPRLookupRateLimitBlock: vi.fn(async () => null), +vi.mock('../github/client/lookup/pr-lookup-rate-limit', () => ({ + getGitHubPRLookupRateLimitBlock: vi.fn(async () => null) +})) + +vi.mock('../github/client/create/create-github-pull-request', () => ({ createGitHubPullRequest: createGitHubPullRequestMock })) -vi.mock('../github/github-enterprise-repository', () => ({ +vi.mock('../github/repositories/github-enterprise-repository', () => ({ getEnterpriseGitHubRepoSlug: getEnterpriseGitHubRepoSlugMock })) diff --git a/src/main/source-control/hosted-review-git-options.ts b/src/main/source-control/hosted-review-git-options.ts index 289691c6f99..54e6aa7bf00 100644 --- a/src/main/source-control/hosted-review-git-options.ts +++ b/src/main/source-control/hosted-review-git-options.ts @@ -1,5 +1,5 @@ import type { GitAdmissionTier } from '../../shared/rpc-contract/git-admission-tier-params' -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' export type HostedReviewLocalGitOptions = { wslDistro?: string diff --git a/src/main/source-control/hosted-review.test.ts b/src/main/source-control/hosted-review.test.ts index f61125e3a6f..6d7036953ae 100644 --- a/src/main/source-control/hosted-review.test.ts +++ b/src/main/source-control/hosted-review.test.ts @@ -33,10 +33,19 @@ vi.mock('../gitlab/merge-requests/merge-request-lookup', () => ({ getMergeRequest: vi.fn() })) -vi.mock('../github/client', () => ({ - getRepoSlug: getRepoSlugMock, - getPRForBranchOutcome: getPRForBranchOutcomeMock, - getGitHubPRLookupRateLimitBlock: vi.fn(async () => null), +vi.mock('../github/client/fetch/repo-slug-upstream', () => ({ + getRepoSlug: getRepoSlugMock +})) + +vi.mock('../github/client/lookup/pr-for-branch-outcome', () => ({ + getPRForBranchOutcome: getPRForBranchOutcomeMock +})) + +vi.mock('../github/client/lookup/pr-lookup-rate-limit', () => ({ + getGitHubPRLookupRateLimitBlock: vi.fn(async () => null) +})) + +vi.mock('../github/client/create/create-github-pull-request', () => ({ createGitHubPullRequest: vi.fn() })) @@ -80,54 +89,6 @@ describe('getHostedReviewForBranch', () => { __resetHostedReviewBranchCacheForTests() }) - it('refreshes a self-managed GitLab MR through the provider while old metadata is pending', async () => { - getProjectSlugMock.mockResolvedValue({ host: 'gitlab.example.test', path: 'g/p' }) - const old = { - number: 7, - title: 'MR', - state: 'opened', - url: 'https://gitlab.example.test/g/p/-/merge_requests/7', - pipelineStatus: 'success', - updatedAt: '2026-05-10T00:00:00.000Z', - mergeable: 'MERGEABLE' - } - let finishOld: (value: typeof old) => void = () => {} - const pending = new Promise((resolve) => { - finishOld = resolve - }) - let started!: () => void - const requestStarted = new Promise((resolve) => { - started = resolve - }) - getMergeRequestForBranchMock.mockImplementation( - ( - _repo: string, - _branch: string, - _linked: unknown, - connectionId?: string | null, - options?: { userRefresh?: boolean } - ) => { - started() - if (connectionId !== 'ssh-1') { - return Promise.resolve(null) - } - return options?.userRefresh ? Promise.resolve({ ...old, state: 'merged' }) : pending - } - ) - const input = { - repoPath: '/repo', - executionHostId: 'ssh:ssh-1', - branch: 'feature', - force: true - } as const - const before = getHostedReviewForBranch(input) - await requestStarted - const click = getHostedReviewForBranch({ ...input, userRefresh: true }) - finishOld(old) - await before - await expect(click).resolves.toMatchObject({ provider: 'gitlab', state: 'merged', number: 7 }) - }) - it('maps GitLab merge requests into the hosted review surface', async () => { getProjectSlugMock.mockResolvedValue({ host: 'gitlab.com', path: 'g/p' }) const mr = { @@ -165,114 +126,4 @@ describe('getHostedReviewForBranch', () => { expect(getProjectSlugMock).toHaveBeenCalledWith('/repo', 'ssh-1') expect(getPRForBranchOutcomeMock).not.toHaveBeenCalled() }) - - it('falls through to GitHub when origin is not GitLab', async () => { - getProjectSlugMock.mockResolvedValue(null) - getRepoSlugMock.mockResolvedValue({ owner: 'o', repo: 'r' }) - getPRForBranchOutcomeMock.mockResolvedValue({ - kind: 'found', - fetchedAt: 1, - pr: { - number: 3, - title: 'GitHub branch', - state: 'open', - url: 'https://github.com/o/r/pull/3', - checksStatus: 'pending', - updatedAt: '2026-05-10T00:00:00.000Z', - mergeable: 'UNKNOWN' - } - }) - - await expect( - getHostedReviewForBranch({ - executionHostId: 'local', - repoPath: '/repo', - branch: 'feature', - linkedGitHubPR: 3 - }) - ).resolves.toMatchObject({ - provider: 'github', - number: 3, - status: 'pending' - }) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledWith('/repo', 'feature', 3, null, null, { - currentHeadOid: null - }) - }) - - it('routes local WSL project branch lookup through provider detection and the selected provider', async () => { - getProjectSlugMock.mockResolvedValue(null) - getRepoSlugMock.mockResolvedValue(null) - getBitbucketRepoSlugMock.mockResolvedValue({ workspace: 'team', repoSlug: 'orca' }) - getBitbucketPullRequestForBranchMock.mockResolvedValue({ - number: 22, - title: 'Bitbucket WSL branch', - state: 'open', - url: 'https://bitbucket.org/team/orca/pull-requests/22', - status: 'pending', - updatedAt: '2026-06-16T00:00:00.000Z', - mergeable: 'UNKNOWN' - }) - - await expect( - getHostedReviewForBranch({ - executionHostId: 'local', - repoPath: '/repo', - branch: 'feature/wsl', - linkedBitbucketPR: 22, - localGitExecOptions: { wslDistro: 'Ubuntu' } - }) - ).resolves.toMatchObject({ - provider: 'bitbucket', - number: 22, - status: 'pending' - }) - - const executionOptions = { localGitExecOptions: { wslDistro: 'Ubuntu' } } - expect(getProjectSlugMock).toHaveBeenCalledWith('/repo', null, executionOptions) - expect(getRepoSlugMock).toHaveBeenCalledWith('/repo', null, executionOptions) - expect(getBitbucketRepoSlugMock).toHaveBeenCalledWith('/repo', null, executionOptions) - expect(getBitbucketPullRequestForBranchMock).toHaveBeenCalledWith( - '/repo', - 'feature/wsl', - 22, - null, - executionOptions - ) - }) - - it('uses fallback GitHub PR when branch is empty', async () => { - getProjectSlugMock.mockResolvedValue(null) - getRepoSlugMock.mockResolvedValue({ owner: 'o', repo: 'r' }) - getPRForBranchOutcomeMock.mockResolvedValue({ - kind: 'found', - fetchedAt: 1, - pr: { - number: 42, - title: 'Detached GitHub branch', - state: 'open', - url: 'https://github.com/o/r/pull/42', - checksStatus: 'success', - updatedAt: '2026-05-10T00:00:00.000Z', - mergeable: 'MERGEABLE' - } - }) - - await expect( - getHostedReviewForBranch({ - executionHostId: 'local', - repoPath: '/repo', - branch: '', - fallbackGitHubPR: 42 - }) - ).resolves.toMatchObject({ - provider: 'github', - number: 42, - status: 'success' - }) - expect(getPRForBranchOutcomeMock).toHaveBeenCalledWith('/repo', '', null, null, 42, { - acceptMergedFallbackPR: true, - currentHeadOid: null - }) - }) }) diff --git a/src/main/source-control/hosted-review.ts b/src/main/source-control/hosted-review.ts index 8dd813873b7..8bd616faa04 100644 --- a/src/main/source-control/hosted-review.ts +++ b/src/main/source-control/hosted-review.ts @@ -1,4 +1,7 @@ -import { githubRepoContext, withGitHubRepoExecution } from '../github/github-repository-identity' +import { + githubRepoContext, + withGitHubRepoExecution +} from '../github/repositories/github-repository-identity' import type { ExecutionHostId } from '../../shared/execution-host' import { cachedHostedReviewOutcome, diff --git a/src/main/source-control/pull-request-linked-issue.ts b/src/main/source-control/pull-request-linked-issue.ts index 0b7c102f69b..5efbbbc4ecd 100644 --- a/src/main/source-control/pull-request-linked-issue.ts +++ b/src/main/source-control/pull-request-linked-issue.ts @@ -3,7 +3,7 @@ import type { PullRequestLinkedIssue } from '../../shared/source-control/ai/gene import { isLinkedIssueNumber } from '../../shared/source-control/ai/recipes/source-control-ai-action-variables' import type { WorkspaceLinkedItem } from '../../shared/worktree/types' import type { GitRuntimeOptions } from '../git/command-runner/git-runtime-options' -import { getIssue as getGitHubIssue } from '../github/issues' +import { getIssue as getGitHubIssue } from '../github/issues/issues' import { getIssue as getGitLabIssue } from '../gitlab/issues/gitlab-issue-queries' export type PullRequestLinkedIssueMeta = { diff --git a/src/main/source-control/stacked-hosted-review-creation.ts b/src/main/source-control/stacked-hosted-review-creation.ts index 3c6aa158c99..47b9713a1b4 100644 --- a/src/main/source-control/stacked-hosted-review-creation.ts +++ b/src/main/source-control/stacked-hosted-review-creation.ts @@ -1,6 +1,9 @@ -import { withGitHubRepoExecution } from '../github/github-repository-identity' +import { withGitHubRepoExecution } from '../github/repositories/github-repository-identity' import { hostedReviewSshConnectionId } from './hosted-review-execution-host' -import { getHostedReviewLocalGitOptions } from './hosted-review-git-options' +import { + getHostedReviewLocalGitOptions, + type HostedReviewExecutionOptions +} from './hosted-review-git-options' import type { ExecutionHostId } from '../../shared/execution-host' import type { CreateStackedHostedReviewInput, @@ -10,9 +13,9 @@ import type { import { prepareGitHubStackedPullRequest, registerGitHubStackedPullRequest -} from '../github/stacked-pr-creation' +} from '../github/pull-requests/stacks/stacked-pr-creation' import { createHostedReview } from './hosted-review-creation' -import type { HostedReviewExecutionOptions } from './hosted-review-git-options' + import { stackedReviewFailure } from '../../shared/source-control/hosted-review/stacked-hosted-review-failure-copy' export async function createStackedHostedReview( diff --git a/src/main/star-nag/agent-value-moment.ts b/src/main/star-nag/agent-value-moment.ts index 52c4909a45a..5171860b9dc 100644 --- a/src/main/star-nag/agent-value-moment.ts +++ b/src/main/star-nag/agent-value-moment.ts @@ -1,5 +1,5 @@ import { app } from 'electron' -import { checkOrcaStarred } from '../github/client' +import { checkOrcaStarred } from '../github/client/fetch/orca-star' import type { Store } from '../persistence/loading-store/store' import type { StarNagPromptMode } from '../../shared/star-nag-telemetry' diff --git a/src/main/star-nag/direct-star-attempt.ts b/src/main/star-nag/direct-star-attempt.ts index 18a22aad6f5..802b43c6b6a 100644 --- a/src/main/star-nag/direct-star-attempt.ts +++ b/src/main/star-nag/direct-star-attempt.ts @@ -1,4 +1,4 @@ -import { starOrca } from '../github/client' +import { starOrca } from '../github/client/fetch/orca-star' import { track } from '../telemetry/client' import { getCohortAtEmit } from '../telemetry/cohort-classifier' import { type StarNagPromptSession, trackStarNagSessionOutcome } from './prompt-session-telemetry' diff --git a/src/main/star-nag/service-direct-star.test.ts b/src/main/star-nag/service-direct-star.test.ts index e694b26557d..fcec053d665 100644 --- a/src/main/star-nag/service-direct-star.test.ts +++ b/src/main/star-nag/service-direct-star.test.ts @@ -30,7 +30,7 @@ vi.mock('electron', () => ({ } })) -vi.mock('../github/client', () => ({ +vi.mock('../github/client/fetch/orca-star', () => ({ checkOrcaStarred: mocks.checkOrcaStarredMock, starOrca: mocks.starOrcaMock })) @@ -84,30 +84,6 @@ describe('StarNagService', () => { expect(ui.starNagCompleted).toBe(true) }) - it('records failed direct star after dismissal without clearing the cooldown or re-showing', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const deferredStar = createDeferred() - starOrcaMock.mockReturnValue(deferredStar.promise) - const { service, ui } = createHarness() - - service.registerIpcHandlers() - getIpcHandler('star-nag:forceShow')() - const starPromise = getIpcHandler('star-nag:starOrca')() - getIpcHandler('star-nag:dismiss')() - - deferredStar.resolve(false) - await expect(starPromise).resolves.toBe(false) - - expect(trackMock).toHaveBeenCalledWith( - 'star_nag_outcome', - expect.objectContaining({ outcome: 'direct_star_failed', mode: 'gh' }) - ) - expect(ui.starNagCompleted).toBeUndefined() - expect(ui.starNagDeferredUntil).toBeGreaterThan(Date.now()) - expect(window.webContents.send).toHaveBeenCalledTimes(1) - }) - it('clears the in-flight direct-star guard after thrown attempts so the user can retry', async () => { const window = createWindow() browserWindowMock.getAllWindows.mockReturnValue([window]) @@ -124,41 +100,4 @@ describe('StarNagService', () => { expect(starOrcaMock).toHaveBeenCalledTimes(2) expect(ui.starNagCompleted).toBe(true) }) - - it('records failed direct star before web fallback and guards duplicate in-flight attempts', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const deferredStar = createDeferred() - starOrcaMock.mockReturnValue(deferredStar.promise) - const { service, ui } = createHarness() - - service.registerIpcHandlers() - getIpcHandler('star-nag:forceShow')() - const starFromNag = getIpcHandler('star-nag:starOrca') - const first = starFromNag() - const second = starFromNag() - - deferredStar.resolve(false) - await expect(first).resolves.toBe(false) - await expect(second).resolves.toBe(false) - - const starAttempts = trackMock.mock.calls.filter( - ([name, payload]) => - name === 'star_nag_outcome' && (payload as { outcome?: string }).outcome === 'star_clicked' - ) - expect(starAttempts).toHaveLength(1) - expect(trackMock).toHaveBeenCalledWith( - 'star_nag_outcome', - expect.objectContaining({ outcome: 'direct_star_failed', mode: 'gh' }) - ) - - getIpcHandler('star-nag:openWeb')() - - expect(trackMock).toHaveBeenCalledWith( - 'star_nag_outcome', - expect.objectContaining({ outcome: 'opened_repo', mode: 'web' }) - ) - expect(ui.starNagCompleted).toBeUndefined() - expect(ui.starNagDeferredUntil).toBeGreaterThan(Date.now()) - }) }) diff --git a/src/main/star-nag/service-force-show-races.test.ts b/src/main/star-nag/service-force-show-races.test.ts index cae9f744075..06891c5170d 100644 --- a/src/main/star-nag/service-force-show-races.test.ts +++ b/src/main/star-nag/service-force-show-races.test.ts @@ -32,7 +32,7 @@ vi.mock('electron', () => ({ } })) -vi.mock('../github/client', () => ({ +vi.mock('../github/client/fetch/orca-star', () => ({ checkOrcaStarred: mocks.checkOrcaStarredMock, starOrca: mocks.starOrcaMock })) diff --git a/src/main/star-nag/service-threshold-prompt.test.ts b/src/main/star-nag/service-threshold-prompt.test.ts deleted file mode 100644 index e9e2bec9b8c..00000000000 --- a/src/main/star-nag/service-threshold-prompt.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { STAR_NAG_INITIAL_THRESHOLD } from '../../shared/constants' -import { - createHarness, - createIpcHandlerLookup, - createWindow, - flushAsyncWork, - resetStarNagMocks, - type TestWindow -} from './service-test-harness' - -const mocks = vi.hoisted(() => ({ - appMock: { - getVersion: vi.fn(() => '1.2.3') - }, - browserWindowMock: { - getAllWindows: vi.fn<() => TestWindow[]>(() => []) - }, - checkOrcaStarredMock: vi.fn(), - starOrcaMock: vi.fn(), - trackMock: vi.fn(), - getCohortAtEmitMock: vi.fn(() => ({ nth_repo_added: 3 })), - ipcMainHandleMock: vi.fn() -})) - -vi.mock('electron', () => ({ - app: mocks.appMock, - BrowserWindow: mocks.browserWindowMock, - ipcMain: { - handle: mocks.ipcMainHandleMock - } -})) - -vi.mock('../github/client', () => ({ - checkOrcaStarred: mocks.checkOrcaStarredMock, - starOrca: mocks.starOrcaMock -})) - -vi.mock('../telemetry/client', () => ({ - track: mocks.trackMock -})) - -vi.mock('../telemetry/cohort-classifier', () => ({ - getCohortAtEmit: mocks.getCohortAtEmitMock -})) - -const { browserWindowMock, checkOrcaStarredMock, trackMock } = mocks -const getIpcHandler = createIpcHandlerLookup(mocks.ipcMainHandleMock) - -describe('StarNagService', () => { - let consoleInfoMock: ReturnType - - beforeEach(() => { - resetStarNagMocks(mocks) - consoleInfoMock = vi.spyOn(console, 'info').mockImplementation(() => undefined) - }) - - afterEach(() => { - consoleInfoMock.mockRestore() - }) - - it('preserves the counter and dismissal backoff after an app update', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const { service, emitAgentStarted, store, ui } = createHarness({ - starNagAppVersion: '1.2.2', - starNagBaselineAgents: 10, - starNagNextThreshold: 70, - starNagDeferredUntil: 1 - }) - - service.start() - expect(store.updateUI).not.toHaveBeenCalled() - expect(ui.starNagDeferredUntil).toBe(1) - emitAgentStarted(79) - await flushAsyncWork() - expect(window.webContents.send).not.toHaveBeenCalled() - - emitAgentStarted(80) - await flushAsyncWork() - expect(window.webContents.send).toHaveBeenCalledTimes(1) - expect(trackMock).toHaveBeenCalledWith( - 'star_nag_outcome', - expect.objectContaining({ outcome: 'shown', threshold: 70, agents_since_baseline: 70 }) - ) - }) - - it.each([undefined, null])('initializes a missing baseline (%s) only once', async (baseline) => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const { service, emitAgentStarted, ui } = createHarness({ - starNagAppVersion: undefined, - starNagBaselineAgents: baseline, - starNagNextThreshold: undefined - }) - - service.start() - expect(ui.starNagBaselineAgents).toBe(45) - expect(ui.starNagNextThreshold).toBe(STAR_NAG_INITIAL_THRESHOLD) - service.stop() - mocks.appMock.getVersion.mockReturnValue('1.2.4') - service.start() - emitAgentStarted(79) - await flushAsyncWork() - expect(window.webContents.send).not.toHaveBeenCalled() - emitAgentStarted(80) - await flushAsyncWork() - expect(window.webContents.send).toHaveBeenCalledTimes(1) - }) - - it('logs a threshold exposure exactly once while the card remains visible', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const { service, emitAgentStarted } = createHarness() - - service.start() - emitAgentStarted(45) - await flushAsyncWork() - emitAgentStarted(46) - - expect(window.webContents.send).toHaveBeenCalledTimes(1) - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { - mode: 'gh', - surface: 'card' - }) - expect(consoleInfoMock).toHaveBeenCalledTimes(1) - expect(consoleInfoMock).toHaveBeenCalledWith({ - event: 'star_nag_shown', - app_version: '1.2.3', - threshold: STAR_NAG_INITIAL_THRESHOLD, - agents_since_baseline: 35, - source: 'threshold' - }) - }) - - it('shows the browser fallback when checkOrcaStarred cannot determine star state', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - checkOrcaStarredMock.mockResolvedValue(null) - const { service, emitAgentStarted } = createHarness() - - service.start() - emitAgentStarted(45) - await flushAsyncWork() - - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { - mode: 'web', - surface: 'card' - }) - expect(trackMock).toHaveBeenCalledWith('star_nag_outcome', { - outcome: 'shown', - source: 'threshold', - mode: 'web', - threshold: STAR_NAG_INITIAL_THRESHOLD, - agents_since_baseline: 35, - agents_since_baseline_bucket: '35-69', - nth_repo_added: 3 - }) - expect(consoleInfoMock).toHaveBeenCalledWith({ - event: 'star_nag_shown', - app_version: '1.2.3', - threshold: STAR_NAG_INITIAL_THRESHOLD, - agents_since_baseline: 35, - source: 'threshold' - }) - }) - - it('does not log a threshold exposure when checkOrcaStarred returns true', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - checkOrcaStarredMock.mockResolvedValue(true) - const { service, emitAgentStarted } = createHarness() - - service.start() - emitAgentStarted(45) - await flushAsyncWork() - - expect(window.webContents.send).not.toHaveBeenCalled() - expect(consoleInfoMock).not.toHaveBeenCalled() - }) - - it('does not block a later real prompt after crossing the threshold with no window', async () => { - const { service, emitAgentStarted } = createHarness() - - service.start() - emitAgentStarted(45) - await flushAsyncWork() - - expect(consoleInfoMock).not.toHaveBeenCalled() - expect(trackMock).not.toHaveBeenCalled() - - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - emitAgentStarted(46) - await flushAsyncWork() - - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { - mode: 'gh', - surface: 'card' - }) - expect(consoleInfoMock).toHaveBeenCalledWith({ - event: 'star_nag_shown', - app_version: '1.2.3', - threshold: STAR_NAG_INITIAL_THRESHOLD, - agents_since_baseline: 36, - source: 'threshold' - }) - }) - - it('logs dismissal with doubled next_threshold and advances backoff for the active session', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const { service, emitAgentStarted, ui } = createHarness() - - service.start() - service.registerIpcHandlers() - emitAgentStarted(45) - await flushAsyncWork() - getIpcHandler('star-nag:dismiss')() - - expect(consoleInfoMock).toHaveBeenLastCalledWith({ - event: 'star_nag_dismissed', - app_version: '1.2.3', - threshold: STAR_NAG_INITIAL_THRESHOLD, - agents_since_baseline: 35, - source: 'threshold', - next_threshold: STAR_NAG_INITIAL_THRESHOLD * 2 - }) - expect(ui.starNagNextThreshold).toBe(STAR_NAG_INITIAL_THRESHOLD * 2) - expect(ui.starNagBaselineAgents).toBe(45) - expect(ui.starNagDeferredUntil).toBeGreaterThan(Date.now()) - }) - - it('preserves an active cooldown and backoff after an app update', async () => { - const window = createWindow() - browserWindowMock.getAllWindows.mockReturnValue([window]) - const { service, emitAgentStarted, store, ui } = createHarness({ - starNagAppVersion: '1.2.2', - starNagNextThreshold: 70, - starNagDeferredUntil: Date.now() + 3 * 24 * 60 * 60 * 1000 - }) - - service.start() - emitAgentStarted(80) - await flushAsyncWork() - - expect(checkOrcaStarredMock).not.toHaveBeenCalled() - expect(window.webContents.send).not.toHaveBeenCalled() - expect(trackMock).not.toHaveBeenCalled() - expect(store.updateUI).not.toHaveBeenCalled() - - ui.starNagDeferredUntil = Date.now() - 1 - emitAgentStarted(81) - await flushAsyncWork() - - expect(window.webContents.send).toHaveBeenCalledTimes(1) - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { - mode: 'gh', - surface: 'card' - }) - }) -}) diff --git a/src/main/star-nag/service.ts b/src/main/star-nag/service.ts index 253e18e828c..ab076140f36 100644 --- a/src/main/star-nag/service.ts +++ b/src/main/star-nag/service.ts @@ -1,6 +1,6 @@ import { BrowserWindow, ipcMain } from 'electron' import { STAR_NAG_INITIAL_THRESHOLD } from '../../shared/constants' -import { checkOrcaStarred } from '../github/client' +import { checkOrcaStarred } from '../github/client/fetch/orca-star' import type { Store } from '../persistence/loading-store/store' import type { StatsCollector } from '../stats/collector' import { track } from '../telemetry/client' diff --git a/src/main/startup/main-process-runtime-service.ts b/src/main/startup/main-process-runtime-service.ts index 3bb76f1a7da..b8d37abc190 100644 --- a/src/main/startup/main-process-runtime-service.ts +++ b/src/main/startup/main-process-runtime-service.ts @@ -1,5 +1,5 @@ import { wireRecipeWorkspaceRemoval } from '../ephemeral-vm/recipe-workspace-removal-wiring' -import { wireGitHubIssueLinkTitleFill } from '../github/github-issue-link-title-wiring' +import { wireGitHubIssueLinkTitleFill } from '../github/workspace-links/github-issue-link-title-wiring' import { applySessionSearchSettingsChange, installChildSessionSearchService diff --git a/src/preload/api/gh-bridge-mutations-and-projects.ts b/src/preload/api/gh-bridge-mutations-and-projects.ts index a057485be6d..ecc75e119e9 100644 --- a/src/preload/api/gh-bridge-mutations-and-projects.ts +++ b/src/preload/api/gh-bridge-mutations-and-projects.ts @@ -1,13 +1,14 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' import { ipcRenderer } from 'electron' -import type { GitHubCommentResult } from '../../shared/github/comment-types' -import type { GitHubAssignableUser, GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import type { GetRateLimitResult } from '../../shared/github/rate-limit-types' +import type { GitHubCommentResult } from '../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../shared/github/repositories/repository-identity' +import type { GetRateLimitResult } from '../../shared/github/api/rate-limit-types' import type { GhAccountBindingInventory, GhAccountBindingValidationResult, GhAuthDiagnostic -} from '../../shared/github/auth-types' +} from '../../shared/github/accounts/auth-types' import type { TaskSourceContext } from '../../shared/work-items/task-source-context' import type { GetProjectViewTableResult, @@ -20,7 +21,7 @@ import type { ListProjectViewsResult, ProjectWorkItemDetailsBySlugResult, ResolveProjectRefResult -} from '../../shared/github/project-result-types' +} from '../../shared/github/projects/project-result-types' import type { AddIssueCommentBySlugArgs, ClearProjectItemFieldArgs, @@ -38,7 +39,7 @@ import type { UpdateIssueTypeBySlugArgs, UpdatePullRequestBySlugArgs, UpdateProjectItemFieldArgs -} from '../../shared/github/project-request-types' +} from '../../shared/github/projects/project-request-types' import type { AppStarSource } from '../../shared/gh-star-source' import type { PreloadApi } from '../api-types' diff --git a/src/preload/api/gh-bridge-pull-requests-and-work-items.ts b/src/preload/api/gh-bridge-pull-requests-and-work-items.ts index 5d57a2c2dda..9c9eaa5efa0 100644 --- a/src/preload/api/gh-bridge-pull-requests-and-work-items.ts +++ b/src/preload/api/gh-bridge-pull-requests-and-work-items.ts @@ -1,13 +1,16 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' import { ipcRenderer } from 'electron' -import type { GitHubReactionContent } from '../../shared/github/comment-types' +import type { GitHubReactionContent } from '../../shared/github/comments/comment-types' import type { GitHubPRRefreshCandidate, GitHubPRRefreshEvent, GitHubPRRefreshReason -} from '../../shared/github/pull-request-refresh-types' -import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' -import type { GitHubWorkItem, ListWorkItemsResult } from '../../shared/github/work-item-types' +} from '../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../shared/github/repositories/repository-identity' +import type { + GitHubWorkItem, + ListWorkItemsResult +} from '../../shared/github/work-items/work-item-types' import type { GitHubCreateIssueResult } from '../../shared/issue-mutation-types' import type { TaskSourceContext } from '../../shared/work-items/task-source-context' import type { PreloadApi } from '../api-types' diff --git a/src/preload/api/github-account-api.ts b/src/preload/api/github-account-api.ts index fe219d55065..4fe0a42066b 100644 --- a/src/preload/api/github-account-api.ts +++ b/src/preload/api/github-account-api.ts @@ -1,7 +1,7 @@ import type { GhAccountBindingInventory, GhAccountBindingValidationResult -} from '../../shared/github/auth-types' +} from '../../shared/github/accounts/auth-types' export type GithubAccountApi = { listBindableAccounts: (args: { diff --git a/src/preload/api/github-pull-request-api.ts b/src/preload/api/github-pull-request-api.ts index 38c8208a861..3284d2b8565 100644 --- a/src/preload/api/github-pull-request-api.ts +++ b/src/preload/api/github-pull-request-api.ts @@ -1,29 +1,29 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' import type { AppStarSource } from '../../shared/gh-star-source' -import type { GhAuthDiagnostic } from '../../shared/github/auth-types' +import type { GhAuthDiagnostic } from '../../shared/github/accounts/auth-types' import type { TaskSourceContext } from '../../shared/work-items/task-source-context' -import type { PRCheckDetail, PRCheckRunDetails } from '../../shared/github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../shared/github/checks/check-types' import type { GitHubCommentResult, GitHubPRReviewCommentInput, GitHubReactionContent, PRComment -} from '../../shared/github/comment-types' +} from '../../shared/github/comments/comment-types' import type { GitHubPRRefreshCandidate, GitHubPRRefreshEnqueueResult, GitHubPRRefreshEvent, GitHubPRRefreshReason, PRRefreshOutcome -} from '../../shared/github/pull-request-refresh-types' +} from '../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents, GitHubViewer, PRInfo -} from '../../shared/github/pull-request-types' -import type { GetRateLimitResult } from '../../shared/github/rate-limit-types' +} from '../../shared/github/pull-requests/pull-request-types' +import type { GetRateLimitResult } from '../../shared/github/api/rate-limit-types' import type { GitHubRepoSelectorArgs } from './github-work-item-api' export type GithubPullRequestApi = { diff --git a/src/preload/api/github-work-item-api.ts b/src/preload/api/github-work-item-api.ts index f4ce0fc4d66..e130bae0ff4 100644 --- a/src/preload/api/github-work-item-api.ts +++ b/src/preload/api/github-work-item-api.ts @@ -1,4 +1,4 @@ -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' import type { GetProjectViewTableResult, GitHubProjectCommentMutationResult, @@ -10,7 +10,7 @@ import type { ListProjectViewsResult, ProjectWorkItemDetailsBySlugResult, ResolveProjectRefResult -} from '../../shared/github/project-result-types' +} from '../../shared/github/projects/project-result-types' import type { AddIssueCommentBySlugArgs, ClearProjectItemFieldArgs, @@ -28,19 +28,19 @@ import type { UpdateIssueTypeBySlugArgs, UpdateProjectItemFieldArgs, UpdatePullRequestBySlugArgs -} from '../../shared/github/project-request-types' +} from '../../shared/github/projects/project-request-types' import type { TaskSourceContext } from '../../shared/work-items/task-source-context' -import type { GitHubCommentResult } from '../../shared/github/comment-types' +import type { GitHubCommentResult } from '../../shared/github/comments/comment-types' import type { GitHubAssignableUser, - GitHubOwnerRepo, IssueInfo -} from '../../shared/github/pull-request-types' +} from '../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../shared/github/repositories/repository-identity' import type { GitHubWorkItem, GitHubWorkItemDetails, ListWorkItemsResult -} from '../../shared/github/work-item-types' +} from '../../shared/github/work-items/work-item-types' import type { GitHubCreateIssueResult, GitHubIssueUpdate } from '../../shared/issue-mutation-types' export type GitHubRepoSelectorArgs = { diff --git a/src/preload/api/repository-api.ts b/src/preload/api/repository-api.ts index 0ccfef7c6c3..f8f23e4aaca 100644 --- a/src/preload/api/repository-api.ts +++ b/src/preload/api/repository-api.ts @@ -1,7 +1,7 @@ import type { RepoCreateFailure } from '../../shared/repo-create-failure-copy' import type { CloneTargetArgs, CloneTargetCheck } from '../../shared/git/clone/clone-target-check' import type { ExecutionHostId } from '../../shared/execution-host' -import type { GhAccountBinding } from '../../shared/github/account-binding' +import type { GhAccountBinding } from '../../shared/github/accounts/account-binding' import type { NestedRepoScanResult, ProjectGroup, diff --git a/src/preload/api/workspace-session-api.ts b/src/preload/api/workspace-session-api.ts index 34fb8f6019a..dc49809d2c4 100644 --- a/src/preload/api/workspace-session-api.ts +++ b/src/preload/api/workspace-session-api.ts @@ -1,4 +1,4 @@ -import type { IssueInfo, PRInfo } from '../../shared/github/pull-request-types' +import type { IssueInfo, PRInfo } from '../../shared/github/pull-requests/pull-request-types' import type { WorkspaceSessionPatch, WorkspaceSessionState diff --git a/src/renderer/src/components/cmd-j/task-search-results.ts b/src/renderer/src/components/cmd-j/task-search-results.ts index 4e4e452ff30..b673e94a50f 100644 --- a/src/renderer/src/components/cmd-j/task-search-results.ts +++ b/src/renderer/src/components/cmd-j/task-search-results.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue } from '../../../../shared/jira/jira-types' import type { LinearIssue } from '../../../../shared/linear/issue-types' diff --git a/src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts b/src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts index 86ee88c75bd..82ee68d87fe 100644 --- a/src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts +++ b/src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' -import type { PRInfo } from '../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../shared/repo-types' import type { Worktree } from '../../../../shared/worktree/types' -import { getGitHubPRCacheKey } from '../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../shared/github/cache/github-cache-key' import { getRepoHostIdentity } from '../../../../shared/repo-host-identity' import { buildWorktreeChecksReviewIndex } from './worktree-checks-review-index' diff --git a/src/renderer/src/components/cmd-j/worktree-checks-review-index.ts b/src/renderer/src/components/cmd-j/worktree-checks-review-index.ts index 84cd07fe5f8..997367b67a6 100644 --- a/src/renderer/src/components/cmd-j/worktree-checks-review-index.ts +++ b/src/renderer/src/components/cmd-j/worktree-checks-review-index.ts @@ -1,6 +1,6 @@ import { resolveWorktreeBranchLabel } from '@/lib/workspace/display/worktree-default-display-name' import { resolvePaletteRepoForWorktree } from '@/lib/workspace/search/palette-repo-resolution' -import { getGitHubPRCacheKey } from '../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-review-cache-identity' import type { AppState } from '@/store/types' import type { HostedReviewInfo } from '../../../../shared/source-control/hosted-review/hosted-review-contracts' diff --git a/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx b/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx index af459c293e1..2894f7f9391 100644 --- a/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx +++ b/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx @@ -20,7 +20,7 @@ import { type CrashReportDiagnosticBundle, type CrashReportRecord } from '../../../../shared/crash-reporting' -import type { GitHubViewer } from '../../../../shared/github/pull-request-types' +import type { GitHubViewer } from '../../../../shared/github/pull-requests/pull-request-types' import { translate } from '@/i18n/i18n' import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' import { diff --git a/src/renderer/src/components/editor/check-details/actions/check-annotation-path.ts b/src/renderer/src/components/editor/check-details/actions/check-annotation-path.ts index 6a14cfec5d5..98420538af3 100644 --- a/src/renderer/src/components/editor/check-details/actions/check-annotation-path.ts +++ b/src/renderer/src/components/editor/check-details/actions/check-annotation-path.ts @@ -1,4 +1,4 @@ -import type { PRCheckAnnotation } from '../../../../../../shared/github/check-types' +import type { PRCheckAnnotation } from '../../../../../../shared/github/checks/check-types' import { relativePathInsideRoot, resolveRuntimePath diff --git a/src/renderer/src/components/editor/check-details/actions/check-run-clipboard-text.ts b/src/renderer/src/components/editor/check-details/actions/check-run-clipboard-text.ts index c7b1ccd964a..cf21f548083 100644 --- a/src/renderer/src/components/editor/check-details/actions/check-run-clipboard-text.ts +++ b/src/renderer/src/components/editor/check-details/actions/check-run-clipboard-text.ts @@ -2,7 +2,7 @@ import type { PRCheckAnnotation, PRCheckJob, PRCheckRunDetails -} from '../../../../../../shared/github/check-types' +} from '../../../../../../shared/github/checks/check-types' export function formatCheckRunOutputForClipboard( details: Pick diff --git a/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-context.ts b/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-context.ts index 2251d45722c..cfe4de317db 100644 --- a/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-context.ts +++ b/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-context.ts @@ -6,11 +6,14 @@ import { import { hostedReviewInfoFromGitHubPRInfo as gitHubPRToChecksPanelReview } from '../../../../../../shared/source-control/hosted-review/hosted-review-github' import { getWorktreeGitIdentityDisplay } from '../../../../../../shared/worktree/git-identity-display' import { useAppStore } from '@/store/index' -import { getGitHubPRCacheKey } from '../../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-review-cache-identity' import { findFirstIndexedWorktree } from '@/lib/workspace/ownership/worktree-runtime-owner-index' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import type { Repo } from '../../../../../../shared/repo-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-with-ai.ts b/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-with-ai.ts index bb6ecb75dc3..c5a93ddac3e 100644 --- a/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-with-ai.ts +++ b/src/renderer/src/components/editor/check-details/actions/check-run-details-fix-with-ai.ts @@ -24,7 +24,10 @@ import type { SourceControlActionRecipe, SourceControlLaunchActionId } from '../../../../../../shared/source-control/ai/recipes/source-control-ai-actions' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { selectWorktreesByRepo } from '@/store/workspaces/catalog/workspace-record-fields' diff --git a/src/renderer/src/components/editor/check-details/actions/check-run-details-tab.ts b/src/renderer/src/components/editor/check-details/actions/check-run-details-tab.ts index 493888e2eda..8832c04de25 100644 --- a/src/renderer/src/components/editor/check-details/actions/check-run-details-tab.ts +++ b/src/renderer/src/components/editor/check-details/actions/check-run-details-tab.ts @@ -1,7 +1,7 @@ import type { GitLabProjectRef } from '../../../../../../shared/gitlab-types' import type { OpenCheckRunDetailsState } from '../../../../../../shared/editor-tab-view-types' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' -import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/repositories/repository-identity' export type CheckRunDetailsTabPatch = Pick< OpenCheckRunDetailsState, diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunAnnotations.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunAnnotations.tsx index a3929be2bca..78a1adc908b 100644 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunAnnotations.tsx +++ b/src/renderer/src/components/editor/check-details/panel/CheckRunAnnotations.tsx @@ -1,7 +1,7 @@ import React from 'react' import { ExternalLink } from 'lucide-react' import { useTranslate } from '@/i18n/use-translate' -import type { PRCheckAnnotation } from '../../../../../../shared/github/check-types' +import type { PRCheckAnnotation } from '../../../../../../shared/github/checks/check-types' import { openAnnotationLocation } from '../actions/check-annotation-open' import { getOpenableAnnotationLine } from '../actions/check-annotation-path' import { formatAnnotationsForClipboard } from '../actions/check-run-clipboard-text' diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.tsx index cf4ac05046a..fa1feb73714 100644 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.tsx +++ b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.tsx @@ -3,7 +3,7 @@ import { ExternalLink, RefreshCw } from 'lucide-react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import CommentMarkdown from '@/components/sidebar/CommentMarkdown' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type * as GitHubChecks from '../../../../../../shared/github/checks/check-types' import { SourceControlFixSplitButton } from '@/components/right-sidebar/source-control/panel/source-control-fix-split-button' import { getIntlLocale, translate } from '@/i18n/i18n' import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' @@ -37,8 +37,8 @@ function formatCheckTimestamp( type CheckStatusLike = { gitlabAllowedFailure?: boolean - status: PRCheckDetail['status'] | (string & {}) | null | undefined - conclusion: PRCheckDetail['conclusion'] | (string & {}) | null | undefined + status: GitHubChecks.PRCheckDetail['status'] | (string & {}) | null | undefined + conclusion: GitHubChecks.PRCheckDetail['conclusion'] | (string & {}) | null | undefined } function getCheckStatusLabel(check: CheckStatusLike, t: Translate = translate): string { @@ -94,8 +94,8 @@ export function CheckRunDetailsPanel({ viewStateScopeId, contextKey }: { - check: PRCheckDetail - details: PRCheckRunDetails | null + check: GitHubChecks.PRCheckDetail + details: GitHubChecks.PRCheckRunDetails | null loading: boolean error: string | null openUrl: string | null | undefined diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx index dabe9cce32e..71d70bf2851 100644 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx +++ b/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx @@ -16,7 +16,7 @@ import type { PRCheckDetail, PRCheckJob, PRCheckStep -} from '../../../../../../shared/github/check-types' +} from '../../../../../../shared/github/checks/check-types' import { resolveStepOutcome, summarizeJobSteps, diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts index 7d66f3b1106..1ed1ccb6b0f 100644 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts +++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts @@ -1,4 +1,7 @@ -import type { PRCheckJob, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckJob, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import type { OpenCheckRunDetailsState } from '../../../../../../shared/editor-tab-view-types' import { scrollTopCache, diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-fixture.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-fixture.ts index 0a111abe258..2165c1c6c0c 100644 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-fixture.ts +++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-fixture.ts @@ -5,7 +5,7 @@ import type { PRCheckDetail, PRCheckJob, PRCheckRunDetails -} from '../../../../../../shared/github/check-types' +} from '../../../../../../shared/github/checks/check-types' import { createCheckDetailsReadingOwner } from './check-details-reading-owner' import { checkJobIdentities, diff --git a/src/renderer/src/components/editor/markdown-preview/render-tree/markdown-preview-plugins.ts b/src/renderer/src/components/editor/markdown-preview/render-tree/markdown-preview-plugins.ts index ec0a86aa6a5..1b6b3d99793 100644 --- a/src/renderer/src/components/editor/markdown-preview/render-tree/markdown-preview-plugins.ts +++ b/src/renderer/src/components/editor/markdown-preview/render-tree/markdown-preview-plugins.ts @@ -10,7 +10,7 @@ import remarkFrontmatter from 'remark-frontmatter' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import { remarkMarkdownDocLinks } from '../../markdown/documents/markdown-doc-links' -import { GITHUB_CALLOUT_SANITIZE_ATTRIBUTE } from '../../../../../../shared/github/github-callouts' +import { GITHUB_CALLOUT_SANITIZE_ATTRIBUTE } from '../../../../../../shared/github/markdown/github-callouts' import { remarkGitHubCallouts } from '@/lib/markdown/remark-github-callouts' import { remarkAutolinkLiteralProseBoundary } from '@/lib/markdown/remark-autolink-literal-prose-boundary' diff --git a/src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx b/src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx index a6fcf5a4d1a..eb464566287 100644 --- a/src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx +++ b/src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx @@ -24,7 +24,7 @@ import type { MarkdownPreviewViewport } from '../viewport/use-markdown-preview-v import { useLocalImageSrc } from '../../document-images/useLocalImageSrc' import { documentResourceAccess } from '@/lib/files/access/local-file-access' import { MarkdownGitHubCallout } from '@/components/markdown-github-callout' -import { readGitHubCalloutKind } from '../../../../../../shared/github/github-callouts' +import { readGitHubCalloutKind } from '../../../../../../shared/github/markdown/github-callouts' import { translate } from '@/i18n/i18n' function hasMermaidCodeClass(value: unknown): boolean { diff --git a/src/renderer/src/components/github-checks-tab-state.test.ts b/src/renderer/src/components/github-checks-tab-state.test.ts index 8fc781ff5ca..ab9d871b373 100644 --- a/src/renderer/src/components/github-checks-tab-state.test.ts +++ b/src/renderer/src/components/github-checks-tab-state.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../shared/github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../../shared/github/checks/check-types' import { beginGitHubChecksTabDetails, createGitHubChecksTabState, diff --git a/src/renderer/src/components/github-checks-tab-state.ts b/src/renderer/src/components/github-checks-tab-state.ts index ff3f3965f67..0e20bd0a932 100644 --- a/src/renderer/src/components/github-checks-tab-state.ts +++ b/src/renderer/src/components/github-checks-tab-state.ts @@ -1,5 +1,5 @@ -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../shared/github/check-types' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../../shared/github/checks/check-types' export type CheckDetailsLoadState = { requestId?: number diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-activity.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-activity.tsx index 40186674a97..81fea6a328d 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-activity.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-activity.tsx @@ -7,9 +7,10 @@ import { } from '@/lib/source-control/review/comments/pr-comment-audience-labels' import type { PRCommentAudienceFilter } from '../../../../../shared/source-control/review-comments/pr-comment-audience' import type { PRCommentGroup } from '../../../../../shared/source-control/review-comments/pr-comment-groups' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { useTranslate } from '@/i18n/use-translate' import type { IssueConversationEntry } from './issue-conversation-entries' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-comment-card.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-comment-card.tsx index 666d6d3f4e1..635d9470d28 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-comment-card.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-comment-card.tsx @@ -12,8 +12,9 @@ import { import { formatRelativeTime } from '@/components/github/work-item-state-presentation' import { CommentCodeContext } from '@/components/github/CommentCodeContext' import { CommentReactions } from '@/components/github/CommentReactions' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { translate } from '@/i18n/i18n' import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-description.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-description.tsx index feb4035058c..8cf8e23b47c 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-description.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-description.tsx @@ -6,8 +6,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import { GitHubMarkdownComposer } from '@/components/github/GitHubMarkdownComposer' import { formatRelativeTime } from '@/components/github/work-item-state-presentation' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' export function ConversationTabDescription({ diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx index 11105c1f4d9..c6e0fbc0bac 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx @@ -1,10 +1,10 @@ import React from 'react' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { PRAssigneesPanel } from '@/components/github/PRAssigneesPanel' import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-timeline.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-timeline.tsx index ce23a6c9c7f..f03295e8dd0 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-timeline.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-timeline.tsx @@ -4,7 +4,7 @@ import { formatRelativeTime } from '@/components/github/work-item-state-presenta import type { GitHubIssueTimelineItem, GitHubIssueTimelineTarget -} from '../../../../../shared/github/comment-types' +} from '../../../../../shared/github/comments/comment-types' import { Trans } from 'react-i18next' import { i18n, translate } from '@/i18n/i18n' import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx index a4a60784110..e7e6083eea7 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx @@ -26,23 +26,24 @@ import { resolveGitHubBodyDraft, shouldSyncGitHubBodyDraft } from '@/components/github-body-draft-state' -import type { GitHubIssueTimelineItem, PRComment } from '../../../../../shared/github/comment-types' +import type { + GitHubIssueTimelineItem, + PRComment +} from '../../../../../shared/github/comments/comment-types' import type { GitHubAssignableUser, GitHubPRFile -} from '../../../../../shared/github/pull-request-types' +} from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import { - parseOwnerRepoFromItemUrl, - resolvePullRequestRepo -} from '@/components/github/github-work-item-identity' +import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' import { addIssueCommentForRepo, addPRReviewCommentReplyForRepo diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/gh-comment-composer.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/gh-comment-composer.tsx index 177ea106106..c2f5a4712da 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/gh-comment-composer.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/gh-comment-composer.tsx @@ -12,8 +12,8 @@ import { hasBoundedCommentBodyText } from '@/lib/source-control/review/comments/comment-body-submit-state' import { addIssueCommentForRepo } from '@/components/github/github-work-item-comment-mutations' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/issue-conversation-entries.ts b/src/renderer/src/components/github-item-dialog/discuss-item/issue-conversation-entries.ts index aa1cb917067..d32ae5c8068 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/issue-conversation-entries.ts +++ b/src/renderer/src/components/github-item-dialog/discuss-item/issue-conversation-entries.ts @@ -2,7 +2,7 @@ import type { GitHubIssueTimelineItem, GitHubIssueTimelineTarget, PRComment -} from '../../../../../shared/github/comment-types' +} from '../../../../../shared/github/comments/comment-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-assignees.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-assignees.tsx index 8663ba4f222..9a3e243dda0 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-assignees.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-assignees.tsx @@ -2,7 +2,7 @@ import React from 'react' import { ChevronDown, LoaderCircle, Pencil } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { cn } from '@/lib/class-names' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { useTranslate } from '@/i18n/use-translate' const checkIcon = ( diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-horizontal.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-horizontal.tsx index c24059a2430..644262111fa 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-horizontal.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-horizontal.tsx @@ -8,8 +8,8 @@ import { DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageGitHubCloseAction } from '@/components/task-page/github/task-page-github-status-actions' import { useTranslate } from '@/i18n/use-translate' import { GHEditSectionStatusPopover } from './gh-edit-section-status-popover' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts index 52524b38168..a1ac4285b77 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts @@ -8,8 +8,8 @@ import { import { beginEdit, isTrackedEdit } from './gh-edit-task-record' import { assertTaskPageGitHubDialogStateAuthority } from '@/components/task-page/github/task-page-github-dialog-state-authority' import { runIssueUpdate } from '@/components/github/github-work-item-edit-mutations' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-status-popover.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-status-popover.tsx index 7ddd6801e1f..99e9fbf6c70 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-status-popover.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-status-popover.tsx @@ -14,7 +14,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { cn } from '@/lib/class-names' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageGitHubCloseAction } from '@/components/task-page/github/task-page-github-status-actions' import { getStateLabel } from '@/components/github/work-item-state-presentation' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-top-columns.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-top-columns.tsx index b6693e443c3..eec2c5c370e 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-top-columns.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-top-columns.tsx @@ -1,7 +1,7 @@ import React from 'react' import { FolderKanban } from 'lucide-react' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageGitHubCloseAction } from '@/components/task-page/github/task-page-github-status-actions' import { useTranslate } from '@/i18n/use-translate' import { GHEditSectionStatusPopover } from './gh-edit-section-status-popover' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.assignee-identity.test.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.assignee-identity.test.tsx index 91abdca191b..3dcd00abfeb 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.assignee-identity.test.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.assignee-identity.test.tsx @@ -3,7 +3,7 @@ import { act, useState } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' import { GHEditSection } from './gh-edit-section' import { resetTaskPageGitHubMutationRegistryForTests } from '@/components/task-page/github/task-page-github-work-item-mutation-registry' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx index 33995bb59e0..605f411c3c2 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx @@ -4,7 +4,7 @@ import { useAppStore } from '@/store' import { useRepoLabels, useRepoAssignees } from '@/hooks/useGitHubRepoMetadata' import { useImmediateMutation } from '@/hooks/useIssueMetadata' import { useRepoLabelsBySlug, useRepoAssigneesBySlug } from '@/hooks/useGitHubSlugMetadata' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { getTaskPageGitHubDuplicateCandidates, @@ -12,7 +12,7 @@ import { validateTaskPageGitHubDuplicateTarget, type TaskPageGitHubCloseAction } from '@/components/task-page/github/task-page-github-status-actions' -import { parseOwnerRepoFromItemUrl } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-task-record.ts b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-task-record.ts index b2a4d780105..054b5c0fb83 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-task-record.ts +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-task-record.ts @@ -1,7 +1,7 @@ import { useAppStore } from '@/store' import { getWorkItemRepo } from '@/lib/work-items/work-item-repo-row' import { untrackedTaskOperation, type TaskChange } from '@/store/github/task-records/task-change' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' /** Whether a task record will own an edit of this item; otherwise main's untracked paths apply. */ diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-actions.ts b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-actions.ts index b1813f82765..1b27a82e42c 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-actions.ts +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-actions.ts @@ -14,9 +14,9 @@ import { } from '@/lib/work-items/github/github-source-runtime-context' import { startFixChecksAgent } from '@/lib/source-control/checks/fix-checks-agent-launch' import { buildFixBrokenChecksPrompt } from '../../../../../shared/source-control/checks/pr-checks-fix-prompt' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { translate } from '@/i18n/i18n' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-details.tsx b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-details.tsx index b97b9eb9775..39e24675de2 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-details.tsx +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-details.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button' import { cn } from '@/lib/class-names' import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { getCheckConclusion } from '@/components/pr-check-counts' import { diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-row.tsx b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-row.tsx index afc42a26e06..5b773778245 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-row.tsx +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-check-row.tsx @@ -7,7 +7,7 @@ import { checkPresentationKey } from '@/components/right-sidebar/checks-panel/presentation/check-presentation' import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { getCheckDetailsKey, getCheckStatusLabel } from '@/components/github/pr-check-presentation' import { ChecksTabCheckDetails } from './checks-tab-check-details' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-request-details.ts b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-request-details.ts index c9877e753da..da621a970be 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-request-details.ts +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab-request-details.ts @@ -5,14 +5,14 @@ import { normalizeCheckRunDetailsLogTails } from '@/lib/source-control/checks/ch import { GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE, isGitHubCheckDetailsTimeout -} from '../../../../../shared/github/check-details-deadline' +} from '../../../../../shared/github/checks/check-details-deadline' import { beginGitHubChecksTabDetails, settleGitHubChecksTabDetails, type CheckDetailsLoadState } from '@/components/github-checks-tab-state' import { getGitHubRuntimeRepoId } from '@/lib/work-items/github/github-source-runtime-context' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { translate } from '@/i18n/i18n' import type { ChecksTabActionContext } from './checks-tab-actions' diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab.tsx b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab.tsx index 2fff5e79b84..3df12a57f73 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab.tsx +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/checks-tab.tsx @@ -1,5 +1,5 @@ import { PRCheckEmptyDetails } from '@/components/pr-check-empty-details' -import { isCompletePRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import { isCompletePRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import { usePRCheckRefreshOwner } from '@/hooks/use-pr-check-refresh-owner' import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import { LoaderCircle } from 'lucide-react' @@ -16,14 +16,17 @@ import { getGitHubSourceRuntimeHost } from '@/lib/work-items/github/github-source-runtime-context' import { getBrokenChecks } from '../../../../../shared/source-control/checks/pr-checks-fix-prompt' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' +} from '../../../../../shared/github/work-items/work-item-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' + import { sortChecksBySeverity } from '../../../../../shared/source-control/checks/pr-check-severity-order' import { getCheckCountChips, diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-body.tsx b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-body.tsx index f170a2cc0e0..7ea324349c3 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-body.tsx +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-body.tsx @@ -12,7 +12,7 @@ import type { DecoratedDiffComment } from '@/components/diff-comments/decorated- import { CombinedDiffFileTree } from '../../editor/combined-diff/browse-files/combined-diff-file-tree' import type { DiffSection } from '@/components/editor/diff/sections/diff-section-types' import type { CombinedDiffFileTreeEntry } from '../../editor/combined-diff/resolve-changes/combined-diff-section-identity' -import type { GitHubPRFile } from '../../../../../shared/github/pull-request-types' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitBranchChangeEntry } from '../../../../../shared/git/diff/git-diff-compare-types' import { PRFilesCombinedDiffToolbar } from './pr-files-combined-diff-toolbar' diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-load.ts b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-load.ts index 24c0e89f9c3..f68d67db41c 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-load.ts +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-load.ts @@ -2,7 +2,7 @@ import type React from 'react' import { toast } from 'sonner' import type { DiffSection } from '@/components/editor/diff/sections/diff-section-types' import { addPRReviewCommentForRepo } from '@/components/github/github-work-item-comment-mutations' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { removeDiffSectionMeasuredHeight } from '@/components/editor/diff/sections/diff-section-height-cache' import { getStoredTextDiffContent, @@ -13,11 +13,11 @@ import { getPRFileDiffResult } from '@/components/github/pr-file-diff-mapping' import type { GitDiffResult } from '../../../../../shared/git/diff/git-diff-compare-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../../shared/github/pull-request-types' +} from '../../../../../shared/github/pull-requests/pull-request-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { translate } from '@/i18n/i18n' import { loadPRFileContents } from '../load-item-details/pr-file-content-cache' diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-signature.ts b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-signature.ts index 602e54b0f16..d23e5cccc69 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-signature.ts +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-signature.ts @@ -1,5 +1,6 @@ -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' /** Identifies one diff set: the PR revision plus the file shapes rendered for it. */ export function getPRFilesCombinedDiffSignature({ diff --git a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-toolbar.tsx b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-toolbar.tsx index 3f3a18d3350..e6aa2aef3aa 100644 --- a/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-toolbar.tsx +++ b/src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-toolbar.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' import { isPRFileViewed } from '@/components/github/pr-file-content-size' -import type { GitHubPRFile } from '../../../../../shared/github/pull-request-types' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' export function PRFilesCombinedDiffToolbar({ files, diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-merge-menu.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-merge-menu.tsx index e586b018f7b..786d0e728e1 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-merge-menu.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-merge-menu.tsx @@ -11,8 +11,8 @@ import { } from '@/components/ui/dropdown-menu' import { cn } from '@/lib/class-names' import type { presentGitHubPRMergeState } from '@/components/github-pr-merge-state' -import type { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods' -import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-request-types' +import type { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' +import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-requests/pull-request-types' import { useTranslate } from '@/i18n/use-translate' import { getPullRequestMergeMethodLabel } from '@/components/github/pull-request-confirmation-copy' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx index 5486c01fc38..25b2f8e94ea 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx @@ -12,9 +12,9 @@ import { getGitHubRuntimeRepoId } from '@/lib/work-items/github/github-source-runtime-context' import { presentGitHubPRMergeState } from '@/components/github-pr-merge-state' -import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods' -import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' +import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { useTranslate } from '@/i18n/use-translate' import { diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx index b2c0d13782e..d1f077173ef 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx @@ -6,13 +6,13 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { useAppStore } from '@/store' import { useRepoAssignees } from '@/hooks/useGitHubRepoMetadata' import { useRepoAssigneesBySlug } from '@/hooks/useGitHubSlugMetadata' -import { getGitHubPRReviewerRows } from '../../../../../shared/github/pr-reviewer-display' +import { getGitHubPRReviewerRows } from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { filterGitHubPRReviewerCandidates, getGitHubPRReviewerQueryState } from '@/components/github/github-pr-reviewer-candidate-filter' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { useTranslate } from '@/i18n/use-translate' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-picker-row.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-picker-row.tsx index c1090e5039b..9aeb7b70186 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-picker-row.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-picker-row.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Check } from 'lucide-react' import { cn } from '@/lib/class-names' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { useTranslate } from '@/i18n/use-translate' export function PRReviewerPickerRow({ diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.test.ts b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.test.ts deleted file mode 100644 index 61f7a1db2a6..00000000000 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import { requestPRReviewers } from './pr-reviewers-request-actions' - -const toastMocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn() })) - -vi.mock('sonner', () => ({ toast: toastMocks })) - -const item: GitHubWorkItem = { - id: 'pr:7', - type: 'pr', - number: 7, - repoId: 'repo-1', - title: 'PR 7', - state: 'open', - url: 'https://github.com/acme/widgets/pull/7', - labels: [], - updatedAt: '2026-11-01T00:00:00Z', - author: null -} - -describe('GitHub dialog reviewer actions', () => { - afterEach(() => { - vi.unstubAllGlobals() - vi.clearAllMocks() - }) - - it('rereads after a returned failure without applying the reviewer patch', async () => { - const requestReviewers = vi.fn().mockResolvedValue({ ok: false, error: 'Connection lost' }) - vi.stubGlobal('window', { api: { gh: { requestPRReviewers: requestReviewers } } }) - const events: string[] = [] - const patchWorkItem = vi.fn() - - await requestPRReviewers({ - submitting: false, - requestedLogins: ['octocat'], - reviewerInput: '', - selectedReviewerLogins: new Set(), - localReviewRequests: [], - ownerTarget: { kind: 'local' }, - repoPath: '/repo', - item, - reviewRepo: null, - reviewerPanelMountedRef: { current: true }, - reviewerCandidates: [], - setSubmitting: (value) => events.push(`submitting:${value}`), - setLocalReviewRequests: vi.fn(), - setReviewerInput: vi.fn(), - patchWorkItem, - onReviewersRequested: vi.fn(), - onMutated: () => events.push('reread') - }) - - expect(requestReviewers).toHaveBeenCalledTimes(1) - expect(toastMocks.error).toHaveBeenCalledWith('Connection lost') - expect(patchWorkItem).not.toHaveBeenCalled() - expect(events).toEqual(['submitting:true', 'submitting:false', 'reread']) - }) -}) diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.ts b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.ts index f5a64a990cc..de5e9dddabf 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.ts +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.ts @@ -5,14 +5,12 @@ import { getGitHubRuntimeRepoId } from '@/lib/work-items/github/github-source-ru import { normalizeGitHubReviewerLogins, parseGitHubReviewerInputLogins -} from '../../../../../shared/github/pr-reviewer-display' +} from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { notifyWorkItemDetailsMutation } from '@/components/github/github-work-item-comment-mutations' import { buildRequestedReviewUsers } from '@/components/github/work-item-state-presentation' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-requested-list.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-requested-list.tsx index 20518cdc352..8ebd6d50cc5 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-requested-list.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-requested-list.tsx @@ -3,7 +3,7 @@ import { LoaderCircle, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ReviewerAvatar } from '@/components/github/work-item-state-presentation' -import type { GitHubPRReviewerRow } from '../../../../../shared/github/pr-reviewer-display' +import type { GitHubPRReviewerRow } from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { useTranslate } from '@/i18n/use-translate' export function PRReviewersRequestedList({ diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts b/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts index ff208c06f10..6f2189793fb 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts @@ -1,5 +1,5 @@ -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { GitHubWorkItemProjectOrigin, diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/github-work-item-target-context.ts b/src/renderer/src/components/github-item-dialog/load-item-details/github-work-item-target-context.ts index e23c482ae78..ab51837d560 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/github-work-item-target-context.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/github-work-item-target-context.ts @@ -1,17 +1,20 @@ import { useGitHubOperationOwner } from '@/hooks/useGitHubOperationScope' import { canUseGitHubRepoContext } from '@/lib/work-items/github/github-source-runtime-context' +import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { - resolvePullRequestRepo, - parseOwnerRepoFromItemUrl -} from '@/components/github/github-work-item-identity' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' + parseGitHubItemRepository as parseOwnerRepoFromItemUrl, + type GitHubWorkItemTarget +} from '../../../../../shared/github/work-items/work-item-target' +import { + getTaskSourceCacheScope, + type TaskSourceContext +} from '../../../../../shared/work-items/task-source-context' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import { getWorkItemDetailsCacheKey } from './work-item-details-cache' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' + import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import { createContext, useContext, useMemo } from 'react' -import type { GitHubWorkItemTarget } from '../../../../../shared/github/work-item-target' export const GitHubWorkItemTargetContext = createContext<{ resource: GitHubWorkItemTarget diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-content-cache.ts b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-content-cache.ts index b5a88077926..492e39147e6 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-content-cache.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-content-cache.ts @@ -7,14 +7,16 @@ import { PR_FILE_CONTENT_CACHE_MAX_BYTES, getRetainedPRFileContentsByteCount } from '@/components/github/pr-file-content-size' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../../shared/github/pull-request-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' +} from '../../../../../shared/github/pull-requests/pull-request-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' const PR_FILE_CONTENT_CACHE_MAX = 64 type PRFileContentCacheEntry = { diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts index 957aa2f5b20..549aa234ee5 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts @@ -1,10 +1,10 @@ import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' -import type { GhAccountBinding } from '../../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../../shared/github/accounts/account-binding' import { toast } from 'sonner' import { setPRFileViewedForRepo } from '@/components/github/github-work-item-comment-mutations' import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { translate } from '@/i18n/i18n' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { GitHubItemDialogProjectOrigin } from './github-item-dialog-types' import { diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.test.tsx b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.test.tsx index d7f3ca07d00..d68b9bdd646 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.test.tsx +++ b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.test.tsx @@ -3,11 +3,11 @@ import { act, cleanup, render, screen, waitFor } from '@testing-library/react' import React, { useState } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import { requestPRReviewers } from '../land-pull-request/pr-reviewers-request-actions' import { useGitHubWorkItemDetails } from './use-github-work-item-details' import { diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.test.tsx b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.test.tsx index 04016d00642..7e654de9c35 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.test.tsx +++ b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.test.tsx @@ -15,7 +15,7 @@ import type { Repo } from '../../../../../shared/repo-types' import type { GitHubWorkItemDetails, GitHubWorkItem -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import { buildTaskSourceContextFromRepo } from '../../../../../shared/work-items/task-source-context' import { useGitHubTaskRefresh } from './use-github-task-refresh' diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.ts b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.ts index ea74f808637..c20e0d770df 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.ts @@ -19,8 +19,8 @@ import { } from 'react' import { useAppStore } from '@/store' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { workItemDetailsCache } from './work-item-details-cache' diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-work-item-details.ts b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-work-item-details.ts index 9a170dc3946..baddfcbec95 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-work-item-details.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-work-item-details.ts @@ -1,10 +1,10 @@ -import { projectPRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import { projectPRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import { useAppStore } from '@/store' import { useGitHubItemTargetContext } from './github-work-item-target-context' import { GitHubWorkItemTargetError, matchesGitHubWorkItemTarget -} from '../../../../../shared/github/work-item-target' +} from '../../../../../shared/github/work-items/work-item-target' import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react' import { useGitHubTaskItems } from '@/hooks/useGitHubTaskItems' import { useGitHubTaskRefresh } from './use-github-task-refresh' @@ -13,11 +13,11 @@ import { normalizeItemDialogTab, type ItemDialogTab } from '@/components/github/github-work-item-identity' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { GitHubWorkItemDetailsArgs } from './github-item-dialog-types' import { invalidateWorkItemDetailsCacheByMatch, diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-cache.ts b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-cache.ts index 83141776a9c..a52ae8268d7 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-cache.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-cache.ts @@ -1,15 +1,15 @@ -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import type { GitHubAssignableUser, - GitHubOwnerRepo, GitHubPRFileViewedState -} from '../../../../../shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +} from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { onGitHubWorkItemDetailsCacheMutation } from '@/lib/work-items/github/github-work-item-details-cache-events' diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-fetch-settle.ts b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-fetch-settle.ts index 731bef192ed..a04549394cd 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-fetch-settle.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-details-fetch-settle.ts @@ -1,24 +1,24 @@ -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { WorkItemDetailsCacheEntry } from './work-item-details-cache' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { GitHubWorkItemTargetError, matchesGitHubWorkItemTarget, requireGitHubWorkItemTargetDetails, type GitHubWorkItemTarget -} from '../../../../../shared/github/work-item-target' -import type { - GitHubWorkItem, - GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' -import type { GitHubTaskRecordCache } from '@/store/github/task-records/task-record-cache' -import type { TaskBinding } from '@/store/github/task-records/task-record-model' +} from '../../../../../shared/github/work-items/work-item-target' import { githubTaskRpcOptions, type GitHubTaskTarget } from '@/lib/work-items/github/github-task-target' +import type { + GitHubWorkItem, + GitHubWorkItemDetails +} from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubTaskRecordCache } from '@/store/github/task-records/task-record-cache' +import type { TaskBinding } from '@/store/github/task-records/task-record-model' import { useAppStore } from '@/store' import { lookupGitHubWorkItemDetailsForSource } from '@/lib/work-items/github/github-work-item-source-lookup' import { + type WorkItemDetailsCacheEntry, WORK_ITEM_DETAILS_UNAVAILABLE_MESSAGE, WORK_ITEM_DETAILS_FRESH_MS, markUserActionWorkItemDetailsRead, diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-state-badge.tsx b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-state-badge.tsx index 5b427b634f0..33bd755cb78 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/work-item-state-badge.tsx +++ b/src/renderer/src/components/github-item-dialog/load-item-details/work-item-state-badge.tsx @@ -3,7 +3,7 @@ import { getWorkItemStateTone, WorkItemStateBadge as SharedWorkItemStateBadge } from '@/components/github/work-item-state-badge' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' // Closed issues stay neutral; reserve red for unmerged closed PRs. export function getStateTone(item: GitHubWorkItem): string { diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx index 87fb38916f4..2263f9fc86f 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx @@ -1,10 +1,10 @@ import React from 'react' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { ConversationTab } from '../discuss-item/conversation-tab' import { GHEditSection } from '../edit-item-fields/gh-edit-section' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-header.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-header.tsx index ab80dac88ee..f6adef6bf73 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-header.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-header.tsx @@ -22,12 +22,12 @@ import { } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/class-names' -import { parseOwnerRepoFromItemUrl } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' import { formatRelativeTime } from '@/components/github/work-item-state-presentation' import { Trans } from 'react-i18next' import { i18n } from '@/i18n/i18n' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { WorkItemIssueSourceIndicator } from './work-item-issue-source-indicator' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-header.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-header.tsx index 6910e66fad9..a80af051d4d 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-header.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-header.tsx @@ -13,7 +13,7 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { formatRelativeTime } from '@/components/github/work-item-state-presentation' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { WorkItemIssueSourceIndicator } from './work-item-issue-source-indicator' import { WorkItemStateBadge } from '../load-item-details/work-item-state-badge' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx index e5aef3265ae..625d8815f6a 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx @@ -8,12 +8,12 @@ import { } from '@/components/github/github-work-item-identity' import { canUseGitHubRepoContext } from '@/lib/work-items/github/github-source-runtime-context' import { useTranslate } from '@/i18n/use-translate' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { ChecksTab } from '../inspect-pull-request/checks-tab' import { ConversationTab } from '../discuss-item/conversation-tab' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx index 480cb0623e3..894820099f8 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx @@ -11,7 +11,7 @@ import { } from '@/store/github/github-slice-test-harness' import type { AppState } from '@/store/types' import type { Repo } from '../../../../../shared/repo-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { buildTaskSourceContextFromRepo } from '../../../../../shared/work-items/task-source-context' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.tsx index 2234ccdefa8..c78e6756e50 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.tsx @@ -8,7 +8,7 @@ import { findGithubIssueWorkspaceAttachment } from '@/lib/work-items/github/gith import { activateAndRevealWorktree } from '@/lib/workspace/activation/worktree-activation' import { getWorktreeAttachmentLabel } from '@/lib/workspace/display/worktree-attachment-label' import { translate } from '@/i18n/i18n' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitHubItemDialogProps } from '../load-item-details/github-item-dialog-types' import { GitHubItemDialogIssueBody } from './github-item-dialog-issue-body' import { GitHubItemDialogIssueHeader } from './github-item-dialog-issue-header' diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/use-github-item-dialog-link-copy.ts b/src/renderer/src/components/github-item-dialog/open-dialog/use-github-item-dialog-link-copy.ts index 16ba6e28fd8..2afddf23769 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/use-github-item-dialog-link-copy.ts +++ b/src/renderer/src/components/github-item-dialog/open-dialog/use-github-item-dialog-link-copy.ts @@ -7,7 +7,7 @@ import { resolveGitHubLinkCopyState } from '@/components/github-link-copy-state' import { translate } from '@/i18n/i18n' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' export function useGitHubItemDialogLinkCopy(workItem: GitHubWorkItem | null): { linkCopied: boolean diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/work-item-issue-source-indicator.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/work-item-issue-source-indicator.tsx index 78c676aabe3..b98db53fe62 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/work-item-issue-source-indicator.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/work-item-issue-source-indicator.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from 'react' import { useAppStore } from '@/store' import IssueSourceIndicator, { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator' -import { parseOwnerRepoFromItemUrl } from '@/components/github/github-work-item-identity' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import { PER_REPO_FETCH_LIMIT } from '../../../../../shared/work-items/work-item-pagination' // Recover the PR slug from this repo’s cache only; sibling forks may share an issue source but differ in PR origin. diff --git a/src/renderer/src/components/github-pr-merge-state.ts b/src/renderer/src/components/github-pr-merge-state.ts index 7ce9a68e807..71b7e1375cb 100644 --- a/src/renderer/src/components/github-pr-merge-state.ts +++ b/src/renderer/src/components/github-pr-merge-state.ts @@ -4,7 +4,7 @@ import { type GitHubPRAutoMergeActionIntent, type GitHubPRMergeStateInput, type GitHubPRMergeStateKind -} from '../../../shared/github/pull-request-merge-state' +} from '../../../shared/github/pull-requests/merge/pull-request-merge-state' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx b/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx index 27fa361d19c..3e7009286fe 100644 --- a/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx +++ b/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react' import { Copy, ExternalLink, RotateCw } from 'lucide-react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' -import type { GhAuthDiagnostic } from '../../../../shared/github/auth-types' +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' +import type { GhAuthDiagnostic } from '../../../../shared/github/accounts/auth-types' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' import { projectViewErrorMessage } from './project-view-error-copy' diff --git a/src/renderer/src/components/github-project/ProjectBoard.test.tsx b/src/renderer/src/components/github-project/ProjectBoard.test.tsx deleted file mode 100644 index 5efd3405d6b..00000000000 --- a/src/renderer/src/components/github-project/ProjectBoard.test.tsx +++ /dev/null @@ -1,173 +0,0 @@ -// @vitest-environment happy-dom - -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import ProjectBoard from './ProjectBoard' -import type { - GitHubProjectField, - GitHubProjectFieldValue, - GitHubProjectRow, - GitHubProjectTable -} from '../../../../shared/github/project-types' - -const STATUS_FIELD: GitHubProjectField = { - kind: 'single-select', - id: 'f_status', - name: 'Status', - dataType: 'SINGLE_SELECT', - options: [ - { id: 'opt_todo', name: 'Todo', color: 'GREEN' }, - { id: 'opt_done', name: 'Done', color: 'PURPLE' } - ] -} -const TITLE_FIELD: GitHubProjectField = { - kind: 'field', - id: 'f_title', - name: 'Title', - dataType: 'TITLE' -} - -function row( - id: string, - title: string, - values: GitHubProjectFieldValue[], - itemType: GitHubProjectRow['itemType'] = 'ISSUE' -): GitHubProjectRow { - const fieldValuesByFieldId: Record = {} - for (const value of values) { - fieldValuesByFieldId[value.fieldId] = value - } - return { - id, - itemType, - content: { - number: itemType === 'DRAFT_ISSUE' || itemType === 'REDACTED' ? null : 7, - title, - body: null, - url: 'https://github.com/o/r/issues/7', - state: 'OPEN', - stateReason: null, - isDraft: null, - repository: 'o/r', - assignees: [], - labels: [], - parentIssue: null, - issueType: null - }, - fieldValuesByFieldId, - updatedAt: '2026-09-01T00:00:00Z', - position: 0 - } -} - -function table(fields: GitHubProjectField[], rows: GitHubProjectRow[]): GitHubProjectTable { - return { - project: { - id: 'PVT_1', - owner: 'o', - ownerType: 'user', - number: 1, - title: 'Project', - url: 'https://github.com/users/o/projects/1' - }, - selectedView: { - id: 'PVTV_1', - number: 1, - name: 'Board', - layout: 'BOARD_LAYOUT', - filter: '', - fields, - groupByFields: [], - sortByFields: [], - verticalGroupByFields: fields.filter((field) => field.kind === 'single-select') - }, - rows, - totalCount: rows.length, - parentFieldDropped: false - } -} - -const status = (optionId: string, name: string): GitHubProjectFieldValue => ({ - kind: 'single-select', - fieldId: 'f_status', - optionId, - name, - color: '' -}) - -function dragData(rowId: string): { dataTransfer: Partial } { - const store: Record = { 'application/x-orca-project-row': rowId } - return { - dataTransfer: { - types: Object.keys(store), - getData: (type: string) => store[type] ?? '', - setData: (type: string, value: string) => { - store[type] = value - }, - dropEffect: 'move', - effectAllowed: 'move' - } - } -} - -afterEach(cleanup) - -describe('ProjectBoard', () => { - it('moves a card on drop via onEditField and skips no-op drops', () => { - const onEditField = vi.fn() - const boardTable = table( - [TITLE_FIELD, STATUS_FIELD], - [row('r1', 'Ship it', [status('opt_todo', 'Todo')])] - ) - render(list} />) - fireEvent.drop(screen.getByTestId('board-column-opt_done'), dragData('r1')) - expect(onEditField).toHaveBeenCalledWith(boardTable.rows[0], 'f_status', { - kind: 'single-select', - optionId: 'opt_done' - }) - onEditField.mockClear() - fireEvent.drop(screen.getByTestId('board-column-opt_todo'), dragData('r1')) - expect(onEditField).not.toHaveBeenCalled() - }) - - it('commits a drop even when the preload stops propagation at document capture', () => { - const preloadDrop = (event: Event) => { - event.preventDefault() - event.stopPropagation() - } - document.addEventListener('drop', preloadDrop, true) - try { - const onEditField = vi.fn() - render( - - ) - fireEvent.drop(screen.getByTestId('board-column-opt_done'), dragData('r1')) - expect(onEditField).toHaveBeenCalledOnce() - } finally { - document.removeEventListener('drop', preloadDrop, true) - } - }) - - it('ignores restricted and unknown payloads and deleted-option drops', () => { - const onEditField = vi.fn() - render( - list} - /> - ) - fireEvent.drop(screen.getByTestId('board-column-opt_done'), dragData('r1')) - fireEvent.drop(screen.getByTestId('board-column-opt_done'), dragData('unknown')) - fireEvent.drop(screen.getByTestId('board-column-deleted'), dragData('r2')) - expect(onEditField).not.toHaveBeenCalled() - expect(screen.getByLabelText('Restricted item').getAttribute('draggable')).toBe('false') - }) -}) diff --git a/src/renderer/src/components/github-project/ProjectBoard.tsx b/src/renderer/src/components/github-project/ProjectBoard.tsx index 5c0c73f5cf5..ee621c888cd 100644 --- a/src/renderer/src/components/github-project/ProjectBoard.tsx +++ b/src/renderer/src/components/github-project/ProjectBoard.tsx @@ -8,13 +8,16 @@ import { buildBoardColumns, resolveBoardColumnField, type ProjectBoardColumn -} from '../../../../shared/github/project-board-columns' -import { EMPTY_PROJECT_GROUP_KEY, sortRows } from '../../../../shared/github/project-group-sort' +} from '../../../../shared/github/projects/project-board-columns' +import { + EMPTY_PROJECT_GROUP_KEY, + sortRows +} from '../../../../shared/github/projects/project-group-sort' import type { GitHubProjectFieldMutationValue, GitHubProjectRow, GitHubProjectTable -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' const COLUMN_WIDTH_PX = 272 const CARD_DRAG_MIME = 'application/x-orca-project-row' diff --git a/src/renderer/src/components/github-project/ProjectBoardCard.tsx b/src/renderer/src/components/github-project/ProjectBoardCard.tsx index 963f40cfbfe..03399a491ab 100644 --- a/src/renderer/src/components/github-project/ProjectBoardCard.tsx +++ b/src/renderer/src/components/github-project/ProjectBoardCard.tsx @@ -2,7 +2,7 @@ import React from 'react' import { FileText, GitPullRequest, Lock } from 'lucide-react' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' type Props = { row: GitHubProjectRow diff --git a/src/renderer/src/components/github-project/ProjectCell.test-fixture.tsx b/src/renderer/src/components/github-project/ProjectCell.test-fixture.tsx index f9b1d6d74bb..0188e79f95d 100644 --- a/src/renderer/src/components/github-project/ProjectCell.test-fixture.tsx +++ b/src/renderer/src/components/github-project/ProjectCell.test-fixture.tsx @@ -1,5 +1,5 @@ import { renderToStaticMarkup } from 'react-dom/server' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' import ProjectCell from './ProjectCell' export function projectRelationCellText(row: GitHubProjectRow): string[] { diff --git a/src/renderer/src/components/github-project/ProjectCell.tsx b/src/renderer/src/components/github-project/ProjectCell.tsx index 8fa34f8c2d0..a06ccefa334 100644 --- a/src/renderer/src/components/github-project/ProjectCell.tsx +++ b/src/renderer/src/components/github-project/ProjectCell.tsx @@ -3,14 +3,14 @@ import React from 'react' import { projectContentField, projectContentFieldLabel -} from '../../../../shared/github/project-content-field' +} from '../../../../shared/github/projects/project-content-field' import { TYPE_FIELD_DATA_TYPE } from './columns' import type { GitHubIssueType, GitHubProjectField, GitHubProjectFieldMutationValue, GitHubProjectRow -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import { useTranslate } from '@/i18n/use-translate' import { ProjectTitleCell, ProjectTypeCell } from './ProjectCellIdentity' import { ProjectAssigneesCell, ProjectLabelsCell } from './ProjectCellRepositoryEditors' diff --git a/src/renderer/src/components/github-project/ProjectCellIdentity.tsx b/src/renderer/src/components/github-project/ProjectCellIdentity.tsx index f71b0b9d8aa..c58cd9e7f79 100644 --- a/src/renderer/src/components/github-project/ProjectCellIdentity.tsx +++ b/src/renderer/src/components/github-project/ProjectCellIdentity.tsx @@ -2,7 +2,10 @@ import type { RuntimeClientTarget } from '../../../../shared/runtime-client-targ import React from 'react' import { FileText, GitPullRequest, Lock } from 'lucide-react' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubIssueType, GitHubProjectRow } from '../../../../shared/github/project-types' +import type { + GitHubIssueType, + GitHubProjectRow +} from '../../../../shared/github/projects/project-types' import { ProjectIssueTypeCell } from './ProjectCellIssueType' export function ProjectTitleCell({ diff --git a/src/renderer/src/components/github-project/ProjectCellIssueType.tsx b/src/renderer/src/components/github-project/ProjectCellIssueType.tsx index 73e08ffb789..a5058a0c082 100644 --- a/src/renderer/src/components/github-project/ProjectCellIssueType.tsx +++ b/src/renderer/src/components/github-project/ProjectCellIssueType.tsx @@ -8,7 +8,10 @@ import { runtimeTargetForRepoOwner } from '@/lib/workspace/ownership/repo-runtim import { gitHubSlugApiForTarget } from '@/runtime/repository-github/github-slug-owner-api' import { useAppStore } from '@/store/index' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubIssueType, GitHubProjectRow } from '../../../../shared/github/project-types' +import type { + GitHubIssueType, + GitHubProjectRow +} from '../../../../shared/github/projects/project-types' import { chipStyle, colorHex, singleSelectChipColors } from './project-cell-chip-colors' export function ProjectIssueTypeCell({ diff --git a/src/renderer/src/components/github-project/ProjectCellRepositoryEditors.tsx b/src/renderer/src/components/github-project/ProjectCellRepositoryEditors.tsx index 87d7bc5fb17..7fcc755bcb4 100644 --- a/src/renderer/src/components/github-project/ProjectCellRepositoryEditors.tsx +++ b/src/renderer/src/components/github-project/ProjectCellRepositoryEditors.tsx @@ -4,7 +4,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { useRepoAssigneesBySlug, useRepoLabelsBySlug } from '@/hooks/useGitHubSlugMetadata' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' import { EmptyProjectCell, ProjectLabelChip, ProjectUserChip } from './ProjectCellValueEditors' type RepositoryEditorProps = { diff --git a/src/renderer/src/components/github-project/ProjectCellSelectionEditors.tsx b/src/renderer/src/components/github-project/ProjectCellSelectionEditors.tsx index 22241ee2a1b..6d33932901c 100644 --- a/src/renderer/src/components/github-project/ProjectCellSelectionEditors.tsx +++ b/src/renderer/src/components/github-project/ProjectCellSelectionEditors.tsx @@ -6,7 +6,7 @@ import type { GitHubProjectField, GitHubProjectFieldMutationValue, GitHubProjectRow -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import { chipStyle, colorHex, singleSelectChipColors } from './project-cell-chip-colors' import { EmptyProjectCell } from './ProjectCellValueEditors' diff --git a/src/renderer/src/components/github-project/ProjectCellValueEditors.tsx b/src/renderer/src/components/github-project/ProjectCellValueEditors.tsx index 953861adb2e..3f1e35ce1c7 100644 --- a/src/renderer/src/components/github-project/ProjectCellValueEditors.tsx +++ b/src/renderer/src/components/github-project/ProjectCellValueEditors.tsx @@ -2,7 +2,10 @@ import { ImeInput } from '@/lib/text-input/ime/ime-text-field' import React, { useState } from 'react' import { Plus } from 'lucide-react' import { Input } from '@/components/ui/input' -import type { GitHubProjectLabel, GitHubProjectUser } from '../../../../shared/github/project-types' +import type { + GitHubProjectLabel, + GitHubProjectUser +} from '../../../../shared/github/projects/project-types' import { chipStyle, labelChipColors } from './project-cell-chip-colors' export function EmptyProjectCell({ label }: { label: string }): React.JSX.Element { diff --git a/src/renderer/src/components/github-project/ProjectGroupHeader.tsx b/src/renderer/src/components/github-project/ProjectGroupHeader.tsx index 476b6dae28c..38aa008295f 100644 --- a/src/renderer/src/components/github-project/ProjectGroupHeader.tsx +++ b/src/renderer/src/components/github-project/ProjectGroupHeader.tsx @@ -5,7 +5,7 @@ import { EMPTY_PROJECT_GROUP_KEY, isIterationCurrent, type ProjectGroup -} from '../../../../shared/github/project-group-sort' +} from '../../../../shared/github/projects/project-group-sort' import { useTranslate } from '@/i18n/use-translate' type Props = { diff --git a/src/renderer/src/components/github-project/ProjectPicker.tsx b/src/renderer/src/components/github-project/ProjectPicker.tsx index 771ba42b70b..68209b2296b 100644 --- a/src/renderer/src/components/github-project/ProjectPicker.tsx +++ b/src/renderer/src/components/github-project/ProjectPicker.tsx @@ -11,16 +11,16 @@ import type { GitHubProjectOwnerType, GitHubProjectSettings, GitHubProjectViewSummary -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import { GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR, hasBoundedGitHubProjectRefInputText, isGitHubProjectRefInputTooLarge -} from '../../../../shared/github/project-ref-input' +} from '../../../../shared/github/projects/project-ref-input' import { githubProjectHost, githubProjectIdentityKey -} from '../../../../shared/github/project-identity' +} from '../../../../shared/github/projects/project-identity' import { filterGitHubProjectPickerProjects } from './github-project-picker-filter' import { ProjectPickerBrowse } from './ProjectPickerBrowse' import { ProjectViewPickStep } from './ProjectPickerPanels' diff --git a/src/renderer/src/components/github-project/ProjectPickerBrowse.tsx b/src/renderer/src/components/github-project/ProjectPickerBrowse.tsx index 25c2f382fcc..c3490e48fd0 100644 --- a/src/renderer/src/components/github-project/ProjectPickerBrowse.tsx +++ b/src/renderer/src/components/github-project/ProjectPickerBrowse.tsx @@ -6,13 +6,13 @@ import { useTranslate } from '@/i18n/use-translate' import type { GitHubProjectSettings, GitHubProjectSummary -} from '../../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' -import { isGitHubProjectRefInputTooLarge } from '../../../../shared/github/project-ref-input' +} from '../../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' +import { isGitHubProjectRefInputTooLarge } from '../../../../shared/github/projects/project-ref-input' import { githubProjectHost, githubProjectIdentityKey -} from '../../../../shared/github/project-identity' +} from '../../../../shared/github/projects/project-identity' import type { ProjectPickerChoice } from './project-picker-selection' import { ProjectPickerError, diff --git a/src/renderer/src/components/github-project/ProjectPickerPanels.tsx b/src/renderer/src/components/github-project/ProjectPickerPanels.tsx index 464ab0650e3..750fcb91204 100644 --- a/src/renderer/src/components/github-project/ProjectPickerPanels.tsx +++ b/src/renderer/src/components/github-project/ProjectPickerPanels.tsx @@ -4,9 +4,12 @@ import { GhAuthErrorHelp } from './GhAuthErrorHelp' import { projectViewErrorMessage } from './project-view-error-copy' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' -import { isRenderableProjectViewLayout } from '../../../../shared/github/project-types' -import type { GitHubProjectViewSummary } from '../../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' +import { + isRenderableProjectViewLayout, + type GitHubProjectViewSummary +} from '../../../../shared/github/projects/project-types' + +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' export function ProjectPickerSection({ label, diff --git a/src/renderer/src/components/github-project/ProjectRoadmap.tsx b/src/renderer/src/components/github-project/ProjectRoadmap.tsx index 514f56ea222..793fd7b9a2e 100644 --- a/src/renderer/src/components/github-project/ProjectRoadmap.tsx +++ b/src/renderer/src/components/github-project/ProjectRoadmap.tsx @@ -10,7 +10,7 @@ import { ProjectTitleCell } from './ProjectCellIdentity' import { ProjectItemsEmptyState } from './ProjectViewStates' import { formatRoadmapTick } from './roadmap-tick-format' import { loadRoadmapZoom, saveRoadmapZoom } from './roadmap-zoom-preference' -import { groupRows, sortRows } from '../../../../shared/github/project-group-sort' +import { groupRows, sortRows } from '../../../../shared/github/projects/project-group-sort' import { buildRoadmapTicks, getRoadmapSpan, @@ -20,8 +20,11 @@ import { type RoadmapSpan, type RoadmapTick, type RoadmapZoom -} from '../../../../shared/github/project-roadmap-timeline' -import type { GitHubProjectRow, GitHubProjectTable } from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-roadmap-timeline' +import type { + GitHubProjectRow, + GitHubProjectTable +} from '../../../../shared/github/projects/project-types' const LABEL_WIDTH_PX = 280 const LANE_HEIGHT_PX = 36 diff --git a/src/renderer/src/components/github-project/ProjectRoadmapBar.tsx b/src/renderer/src/components/github-project/ProjectRoadmapBar.tsx index f2300ff0be5..f9b71b3323f 100644 --- a/src/renderer/src/components/github-project/ProjectRoadmapBar.tsx +++ b/src/renderer/src/components/github-project/ProjectRoadmapBar.tsx @@ -5,8 +5,8 @@ import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { chipStyle, labelChipColors, singleSelectChipColors } from './project-cell-chip-colors' import { formatRoadmapSpan } from './roadmap-tick-format' -import type { RoadmapSpan } from '../../../../shared/github/project-roadmap-timeline' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' +import type { RoadmapSpan } from '../../../../shared/github/projects/project-roadmap-timeline' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' const MIN_BAR_WIDTH_PX = 24 diff --git a/src/renderer/src/components/github-project/ProjectRow.tsx b/src/renderer/src/components/github-project/ProjectRow.tsx index a6c2b143fdf..82286730bf3 100644 --- a/src/renderer/src/components/github-project/ProjectRow.tsx +++ b/src/renderer/src/components/github-project/ProjectRow.tsx @@ -12,7 +12,7 @@ import type { GitHubProjectField, GitHubProjectFieldMutationValue, GitHubProjectRow as GitHubProjectRowType -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import { useTranslate } from '@/i18n/use-translate' const PROJECT_FROZEN_COLUMN_SURFACE_CLASS = diff --git a/src/renderer/src/components/github-project/ProjectViewList.fixture.tsx b/src/renderer/src/components/github-project/ProjectViewList.fixture.tsx index 25ee789b631..7d4dbfa149c 100644 --- a/src/renderer/src/components/github-project/ProjectViewList.fixture.tsx +++ b/src/renderer/src/components/github-project/ProjectViewList.fixture.tsx @@ -1,5 +1,5 @@ import { defineFixture } from '../../component-fixtures/component-fixture' -import { projectRelationsTableFixture } from '../../../../shared/github/project-relations.test-fixture' +import { projectRelationsTableFixture } from '../../../../shared/github/projects/project-relations.test-fixture' import ProjectViewList from './ProjectViewList' const table = projectRelationsTableFixture() diff --git a/src/renderer/src/components/github-project/ProjectViewList.tsx b/src/renderer/src/components/github-project/ProjectViewList.tsx index db4d21f4008..b801c630114 100644 --- a/src/renderer/src/components/github-project/ProjectViewList.tsx +++ b/src/renderer/src/components/github-project/ProjectViewList.tsx @@ -6,7 +6,7 @@ import ColumnResizeHandle from './ColumnResizeHandle' import ProjectGroupHeader from './ProjectGroupHeader' import ProjectRow from './ProjectRow' import { ProjectItemsEmptyState } from './ProjectViewStates' -import { groupRows, sortRows } from '../../../../shared/github/project-group-sort' +import { groupRows, sortRows } from '../../../../shared/github/projects/project-group-sort' import { getAvailableColumns, loadHiddenColumns, saveHiddenColumns } from './columns' import { ACTION_COLUMN_WIDTH, @@ -22,7 +22,7 @@ import type { GitHubProjectRow, GitHubProjectSortDirection, GitHubProjectTable -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github-project/ProjectViewStates.tsx b/src/renderer/src/components/github-project/ProjectViewStates.tsx index 18edcac2a32..f5a623e7e92 100644 --- a/src/renderer/src/components/github-project/ProjectViewStates.tsx +++ b/src/renderer/src/components/github-project/ProjectViewStates.tsx @@ -6,9 +6,12 @@ import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { GhAuthErrorHelp } from './GhAuthErrorHelp' import { projectViewErrorMessage } from './project-view-error-copy' -import { isRenderableProjectViewLayout } from '../../../../shared/github/project-types' -import type { GitHubProjectViewSummary } from '../../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' +import { + isRenderableProjectViewLayout, + type GitHubProjectViewSummary +} from '../../../../shared/github/projects/project-types' + +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' const ORCA_FEATURE_REQUEST_URL = 'https://github.com/stablyai/orca/issues/new' diff --git a/src/renderer/src/components/github-project/ProjectViewToolbar.tsx b/src/renderer/src/components/github-project/ProjectViewToolbar.tsx index 169834c5993..5284c45faea 100644 --- a/src/renderer/src/components/github-project/ProjectViewToolbar.tsx +++ b/src/renderer/src/components/github-project/ProjectViewToolbar.tsx @@ -6,7 +6,7 @@ import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import ProjectPicker from './ProjectPicker' import type { ProjectViewTableState } from './useProjectViewTable' -import { githubProjectHost } from '../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' import { addAppKeyListener, removeAppKeyListener } from '@/lib/keyboard/app-keyboard-router' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/github-project/ProjectViewWrapper.tsx b/src/renderer/src/components/github-project/ProjectViewWrapper.tsx index 31bfc095261..f66a81aa705 100644 --- a/src/renderer/src/components/github-project/ProjectViewWrapper.tsx +++ b/src/renderer/src/components/github-project/ProjectViewWrapper.tsx @@ -20,7 +20,7 @@ import { useProjectViewTable } from './useProjectViewTable' import type { GitHubProjectFieldMutationValue, GitHubProjectRow -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import { taskSourceRuntimeTarget } from '@/lib/work-items/task-source-runtime-target' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/github-project/column-widths.ts b/src/renderer/src/components/github-project/column-widths.ts index 1c069c3196b..1fd6966769a 100644 --- a/src/renderer/src/components/github-project/column-widths.ts +++ b/src/renderer/src/components/github-project/column-widths.ts @@ -1,5 +1,5 @@ // Keep cosmetic widths per device in localStorage; frozen columns use pixels, later columns use fr weights. -import type { GitHubProjectField } from '../../../../shared/github/project-types' +import type { GitHubProjectField } from '../../../../shared/github/projects/project-types' const STORAGE_KEY = 'orca.githubProject.columnWidths' diff --git a/src/renderer/src/components/github-project/columns.ts b/src/renderer/src/components/github-project/columns.ts index 404a7ace6c4..d261c4185c1 100644 --- a/src/renderer/src/components/github-project/columns.ts +++ b/src/renderer/src/components/github-project/columns.ts @@ -1,6 +1,9 @@ import { translate } from '@/i18n/i18n' // Store per-device visibility locally without modifying GitHub’s field definition or syncing cosmetic settings. -import type { GitHubProjectField, GitHubProjectView } from '../../../../shared/github/project-types' +import type { + GitHubProjectField, + GitHubProjectView +} from '../../../../shared/github/projects/project-types' export const TYPE_FIELD_ID = '__type__' export const TYPE_FIELD_DATA_TYPE = '__TYPE__' diff --git a/src/renderer/src/components/github-project/github-project-picker-filter.test.ts b/src/renderer/src/components/github-project/github-project-picker-filter.test.ts index d76807afd20..fe3323eb8d9 100644 --- a/src/renderer/src/components/github-project/github-project-picker-filter.test.ts +++ b/src/renderer/src/components/github-project/github-project-picker-filter.test.ts @@ -1,10 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { GitHubProjectSummary } from '../../../../shared/github/project-types' -import { - GITHUB_PROJECT_PICKER_QUERY_MAX_BYTES, - filterGitHubProjectPickerProjects, - isGitHubProjectPickerQueryTooLarge -} from './github-project-picker-filter' +import type { GitHubProjectSummary } from '../../../../shared/github/projects/project-types' +import { filterGitHubProjectPickerProjects } from './github-project-picker-filter' function project( owner: string, @@ -39,34 +35,4 @@ describe('github-project-picker-filter', () => { }) ).toEqual([enterprise]) }) - - it('rejects oversized pasted project searches before reading project metadata', () => { - const oversizedQuery = 'secret-project-picker-search'.repeat( - GITHUB_PROJECT_PICKER_QUERY_MAX_BYTES - ) - const candidate = { - get owner(): string { - throw new Error('oversized project searches must not scan owners') - }, - get ownerType(): GitHubProjectSummary['ownerType'] { - throw new Error('oversized project searches must not build project keys') - }, - get number(): number { - throw new Error('oversized project searches must not scan numbers') - }, - get title(): string { - throw new Error('oversized project searches must not scan titles') - } - } as GitHubProjectSummary - - expect(isGitHubProjectPickerQueryTooLarge(oversizedQuery)).toBe(true) - expect( - filterGitHubProjectPickerProjects({ - projects: [candidate], - pinned: [], - recent: [], - query: oversizedQuery - }) - ).toEqual([]) - }) }) diff --git a/src/renderer/src/components/github-project/github-project-picker-filter.ts b/src/renderer/src/components/github-project/github-project-picker-filter.ts index fcee171db82..7fbb8196e5a 100644 --- a/src/renderer/src/components/github-project/github-project-picker-filter.ts +++ b/src/renderer/src/components/github-project/github-project-picker-filter.ts @@ -2,8 +2,8 @@ import { isClipboardTextByteLengthOverLimit } from '../../../../shared/clipboard import type { GitHubProjectSettings, GitHubProjectSummary -} from '../../../../shared/github/project-types' -import { githubProjectIdentityKey } from '../../../../shared/github/project-identity' +} from '../../../../shared/github/projects/project-types' +import { githubProjectIdentityKey } from '../../../../shared/github/projects/project-identity' export const GITHUB_PROJECT_PICKER_QUERY_MAX_BYTES = 2 * 1024 diff --git a/src/renderer/src/components/github-project/group-sort.test.ts b/src/renderer/src/components/github-project/group-sort.test.ts index 6736ee3127a..a5d939a808f 100644 --- a/src/renderer/src/components/github-project/group-sort.test.ts +++ b/src/renderer/src/components/github-project/group-sort.test.ts @@ -5,8 +5,8 @@ import type { GitHubProjectSort, GitHubProjectTable, GitHubProjectView -} from '../../../../shared/github/project-types' -import { sortRows, groupRows } from '../../../../shared/github/project-group-sort' +} from '../../../../shared/github/projects/project-types' +import { sortRows, groupRows } from '../../../../shared/github/projects/project-group-sort' const singleSelectField: GitHubProjectField = { kind: 'single-select', diff --git a/src/renderer/src/components/github-project/project-picker-browse-cache.ts b/src/renderer/src/components/github-project/project-picker-browse-cache.ts index e3f35acf76b..1d0d2255c16 100644 --- a/src/renderer/src/components/github-project/project-picker-browse-cache.ts +++ b/src/renderer/src/components/github-project/project-picker-browse-cache.ts @@ -1,4 +1,4 @@ -import type { GitHubProjectSummary } from '../../../../shared/github/project-types' +import type { GitHubProjectSummary } from '../../../../shared/github/projects/project-types' export const PROJECT_PICKER_BROWSE_CACHE_TTL_MS = 5 * 60_000 export const PROJECT_PICKER_BROWSE_CACHE_MAX_ENTRIES = 32 diff --git a/src/renderer/src/components/github-project/project-picker-input.ts b/src/renderer/src/components/github-project/project-picker-input.ts index e6cd4457f8d..8263823fe7c 100644 --- a/src/renderer/src/components/github-project/project-picker-input.ts +++ b/src/renderer/src/components/github-project/project-picker-input.ts @@ -1,8 +1,8 @@ import { GITHUB_OWNER_NUMBER_SHORTHAND_RE, GITHUB_OWNER_SLUG_RE -} from '../../../../shared/github/owner-slug' -import { isGitHubProjectRefInputTooLarge } from '../../../../shared/github/project-ref-input' +} from '../../../../shared/github/repositories/owner-slug' +import { isGitHubProjectRefInputTooLarge } from '../../../../shared/github/projects/project-ref-input' export function parseProjectInput( input: string diff --git a/src/renderer/src/components/github-project/project-picker-runtime.ts b/src/renderer/src/components/github-project/project-picker-runtime.ts index 32106c4f38e..4dbb66552a3 100644 --- a/src/renderer/src/components/github-project/project-picker-runtime.ts +++ b/src/renderer/src/components/github-project/project-picker-runtime.ts @@ -1,12 +1,12 @@ import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' -import { githubProjectHost } from '../../../../shared/github/project-identity' -import type { GitHubProjectOwnerType } from '../../../../shared/github/project-types' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' +import type { GitHubProjectOwnerType } from '../../../../shared/github/projects/project-types' import type { ListAccessibleProjectsResult, ListProjectViewsResult, ResolveProjectRefResult -} from '../../../../shared/github/project-result-types' +} from '../../../../shared/github/projects/project-result-types' /** `target` is the board's row-less source host, never the focused server. */ export function getProjectPickerRuntimeScope(target: RuntimeClientTarget, host: string): string { diff --git a/src/renderer/src/components/github-project/project-picker-selection.ts b/src/renderer/src/components/github-project/project-picker-selection.ts index 052543b8551..c9b3e316913 100644 --- a/src/renderer/src/components/github-project/project-picker-selection.ts +++ b/src/renderer/src/components/github-project/project-picker-selection.ts @@ -1,4 +1,4 @@ -import type { GitHubProjectOwnerType } from '../../../../shared/github/project-types' +import type { GitHubProjectOwnerType } from '../../../../shared/github/projects/project-types' export type ResolvedProjectSelection = { owner: string diff --git a/src/renderer/src/components/github-project/project-row-filtering.ts b/src/renderer/src/components/github-project/project-row-filtering.ts index b75c2390a8f..d1d3daa7491 100644 --- a/src/renderer/src/components/github-project/project-row-filtering.ts +++ b/src/renderer/src/components/github-project/project-row-filtering.ts @@ -1,4 +1,7 @@ -import type { GitHubProjectRow, GitHubProjectTable } from '../../../../shared/github/project-types' +import type { + GitHubProjectRow, + GitHubProjectTable +} from '../../../../shared/github/projects/project-types' import type { Repo } from '../../../../shared/repo-types' /** Origin matches own the slug through their own remote; upstream matches are diff --git a/src/renderer/src/components/github-project/project-view-error-copy.ts b/src/renderer/src/components/github-project/project-view-error-copy.ts index ab02f2cb3a9..c19ef66cbf4 100644 --- a/src/renderer/src/components/github-project/project-view-error-copy.ts +++ b/src/renderer/src/components/github-project/project-view-error-copy.ts @@ -1,6 +1,6 @@ import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' type ProjectViewErrorCopyContext = { /** Host of the failing surface, used when an older host omits `error.host`. */ diff --git a/src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts b/src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts index 1c89046df56..458db571d10 100644 --- a/src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts +++ b/src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' describe('ProjectViewWrapper GitHub source context boundary', () => { it('builds project work items with a host-pinned repository identity', async () => { diff --git a/src/renderer/src/components/github-project/project-visible-table-cache.ts b/src/renderer/src/components/github-project/project-visible-table-cache.ts index e3dc0772aa0..088487c43bb 100644 --- a/src/renderer/src/components/github-project/project-visible-table-cache.ts +++ b/src/renderer/src/components/github-project/project-visible-table-cache.ts @@ -1,4 +1,4 @@ -import type { GitHubProjectTable } from '../../../../shared/github/project-types' +import type { GitHubProjectTable } from '../../../../shared/github/projects/project-types' export type CachedVisibleProjectTable = { cacheKey: string diff --git a/src/renderer/src/components/github-project/project-work-item.ts b/src/renderer/src/components/github-project/project-work-item.ts index 7b31f1e1156..845b2007f10 100644 --- a/src/renderer/src/components/github-project/project-work-item.ts +++ b/src/renderer/src/components/github-project/project-work-item.ts @@ -1,6 +1,6 @@ -import { githubProjectHost } from '../../../../shared/github/project-identity' -import type { GitHubProjectRow } from '../../../../shared/github/project-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' +import type { GitHubProjectRow } from '../../../../shared/github/projects/project-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { getRepoExecutionHostId } from '../../../../shared/execution-host' import type { RepoHostTarget } from '../../../../shared/repo-host-identity' diff --git a/src/renderer/src/components/github-project/roadmap-tick-format.ts b/src/renderer/src/components/github-project/roadmap-tick-format.ts index 016c57bd26f..570192b53d9 100644 --- a/src/renderer/src/components/github-project/roadmap-tick-format.ts +++ b/src/renderer/src/components/github-project/roadmap-tick-format.ts @@ -1,9 +1,9 @@ -import type { - RoadmapSpan, - RoadmapTick, - RoadmapZoom -} from '../../../../shared/github/project-roadmap-timeline' -import { ROADMAP_DAY_MS } from '../../../../shared/github/project-roadmap-timeline' +import { + type RoadmapSpan, + type RoadmapTick, + type RoadmapZoom, + ROADMAP_DAY_MS +} from '../../../../shared/github/projects/project-roadmap-timeline' export type RoadmapTickLabel = { label: string; sublabel: string | null } diff --git a/src/renderer/src/components/github-project/roadmap-zoom-preference.ts b/src/renderer/src/components/github-project/roadmap-zoom-preference.ts index c739f8c5330..76f3732b616 100644 --- a/src/renderer/src/components/github-project/roadmap-zoom-preference.ts +++ b/src/renderer/src/components/github-project/roadmap-zoom-preference.ts @@ -1,5 +1,5 @@ // Keep per-device zoom local instead of syncing cosmetic state or writing debounced settings. -import type { RoadmapZoom } from '../../../../shared/github/project-roadmap-timeline' +import type { RoadmapZoom } from '../../../../shared/github/projects/project-roadmap-timeline' const STORAGE_KEY = 'orca.githubProject.roadmapZoom' const DEFAULT_ZOOM: RoadmapZoom = 'month' diff --git a/src/renderer/src/components/github-project/slug-dialog/Comments.tsx b/src/renderer/src/components/github-project/slug-dialog/Comments.tsx index 34898a7015e..15ea662dd75 100644 --- a/src/renderer/src/components/github-project/slug-dialog/Comments.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/Comments.tsx @@ -9,7 +9,7 @@ import { getCommentBodySubmitState, hasBoundedCommentBodyText } from '@/lib/source-control/review/comments/comment-body-submit-state' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { useTranslate } from '@/i18n/use-translate' import { projectViewErrorMessage } from '../project-view-error-copy' diff --git a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx index f95c579188f..a9b0c7b4dc4 100644 --- a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx @@ -8,7 +8,7 @@ import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import { useAppStore } from '@/store' import { useGitHubTaskProjectTable } from '@/hooks/useGitHubTaskItems' import { gitHubSlugApiForTarget } from '@/runtime/repository-github/github-slug-owner-api' -import type { GitHubWorkItemDetails } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItemDetails } from '../../../../../shared/github/work-items/work-item-types' import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' import { LabelsEditor } from './LabelsEditor' import { AssigneesEditor } from './AssigneesEditor' diff --git a/src/renderer/src/components/github-project/useProjectPickerBrowse.ts b/src/renderer/src/components/github-project/useProjectPickerBrowse.ts index 22787ab8952..9e5ab1a9436 100644 --- a/src/renderer/src/components/github-project/useProjectPickerBrowse.ts +++ b/src/renderer/src/components/github-project/useProjectPickerBrowse.ts @@ -2,8 +2,8 @@ import { translate } from '@/i18n/i18n' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import { useCallback, useLayoutEffect, useRef, useState } from 'react' import { useMountedRef } from '@/hooks/useMountedRef' -import type { GitHubProjectSummary } from '../../../../shared/github/project-types' -import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' +import type { GitHubProjectSummary } from '../../../../shared/github/projects/project-types' +import type { GitHubProjectViewError } from '../../../../shared/github/projects/project-result-types' import { getProjectPickerBrowseCacheEntry, peekProjectPickerBrowseCacheEntry, diff --git a/src/renderer/src/components/github-project/useProjectRowActions.ts b/src/renderer/src/components/github-project/useProjectRowActions.ts index 4bc7b7a9e66..27090023de3 100644 --- a/src/renderer/src/components/github-project/useProjectRowActions.ts +++ b/src/renderer/src/components/github-project/useProjectRowActions.ts @@ -5,9 +5,12 @@ import { launchWorkItemDirect } from '@/lib/workspace/creation/work-item/launch- import { useAppStore } from '@/store/index' import { translate } from '@/i18n/i18n' import { buildTaskSourceContextFromRepo } from '../../../../shared/work-items/task-source-context' -import { githubProjectHost } from '../../../../shared/github/project-identity' -import type { GitHubProjectRow, GitHubProjectTable } from '../../../../shared/github/project-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' +import type { + GitHubProjectRow, + GitHubProjectTable +} from '../../../../shared/github/projects/project-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' import { resolveMissingRepoProjectDialogState, diff --git a/src/renderer/src/components/github-project/useProjectRowMutations.ts b/src/renderer/src/components/github-project/useProjectRowMutations.ts index ccb1574f7dd..a29912326c9 100644 --- a/src/renderer/src/components/github-project/useProjectRowMutations.ts +++ b/src/renderer/src/components/github-project/useProjectRowMutations.ts @@ -6,7 +6,7 @@ import type { GitHubIssueType, GitHubProjectFieldMutationValue, GitHubProjectRow -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' export function useProjectRowMutations(currentCacheKey: string | null) { const patchProjectIssueOrPr = useAppStore((state) => state.patchProjectIssueOrPr) diff --git a/src/renderer/src/components/github-project/useProjectViewTable.ts b/src/renderer/src/components/github-project/useProjectViewTable.ts index c6b727b8f68..73e88bea8f1 100644 --- a/src/renderer/src/components/github-project/useProjectViewTable.ts +++ b/src/renderer/src/components/github-project/useProjectViewTable.ts @@ -8,17 +8,16 @@ import { useAppStore } from '@/store/index' import { projectViewCacheKey, projectViewSourceScope } from '@/store/github/cache/cache-identity' import type { GitHubProjectSettings, - GitHubProjectTable, GitHubProjectViewSummary -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import type { GetProjectViewTableResult, GitHubProjectViewError -} from '../../../../shared/github/project-result-types' +} from '../../../../shared/github/projects/project-result-types' import { githubProjectHost, githubProjectIdentityKey -} from '../../../../shared/github/project-identity' +} from '../../../../shared/github/projects/project-identity' import type { ResolvedProjectSelection } from './project-picker-selection' import { listProjectViewsForRuntime } from './project-picker-runtime' import { filterProjectTableRowsBySelectedRepos } from './project-row-filtering' @@ -270,4 +269,3 @@ const EMPTY_PROJECT_SETTINGS: GitHubProjectSettings = { } export type ProjectViewTableState = ReturnType -export type ProjectViewTable = GitHubProjectTable diff --git a/src/renderer/src/components/github/CommentCodeContext.tsx b/src/renderer/src/components/github/CommentCodeContext.tsx index b8ec1cb817e..cb6b7c89130 100644 --- a/src/renderer/src/components/github/CommentCodeContext.tsx +++ b/src/renderer/src/components/github/CommentCodeContext.tsx @@ -16,12 +16,12 @@ import { import { getPrCommentCodeContext } from '@/components/github/pr-comment-code-context' import { getPRFileContentsRenderLimit } from '@/components/github/pr-file-diff-mapping' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import type { PRComment } from '../../../../shared/github/comment-types' +import type { PRComment } from '../../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../shared/github/pull-request-types' +} from '../../../../shared/github/pull-requests/pull-request-types' const MonacoCodeExcerpt = lazy(() => import('@/components/editor/MonacoCodeExcerpt')) diff --git a/src/renderer/src/components/github/CommentReactions.tsx b/src/renderer/src/components/github/CommentReactions.tsx index 5d3d89e7979..cf31113966a 100644 --- a/src/renderer/src/components/github/CommentReactions.tsx +++ b/src/renderer/src/components/github/CommentReactions.tsx @@ -7,7 +7,10 @@ import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' import { cn } from '@/lib/class-names' import { GITHUB_REACTION_ORDER } from '@/lib/source-control/review/comments/pr-comment-reactions' -import type { GitHubReaction, GitHubReactionContent } from '../../../../shared/github/comment-types' +import type { + GitHubReaction, + GitHubReactionContent +} from '../../../../shared/github/comments/comment-types' const REACTION_EMOJI: Record = { '+1': '👍', diff --git a/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx b/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx index 317f12b3967..30d264b7dd2 100644 --- a/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx +++ b/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx @@ -7,7 +7,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { TooltipProvider } from '@/components/ui/tooltip' import { GHCommentComposer } from '@/components/github-item-dialog/discuss-item/gh-comment-composer' import type { AppState } from '@/store/types' -import type { GitHubCommentResult, PRComment } from '../../../../shared/github/comment-types' +import type { + GitHubCommentResult, + PRComment +} from '../../../../shared/github/comments/comment-types' import { GitHubMarkdownComposer } from './GitHubMarkdownComposer' import type { GitHubImageUploadAddress } from './github-image-upload' @@ -146,19 +149,6 @@ describe('GitHubMarkdownComposer submission', () => { } ) - it('does not report editability changes as draft edits', async () => { - const onChange = vi.fn() - const props = { value: 'Draft comment', onChange, placeholder: 'Add a comment…' } - const view = render() - await waitFor(() => expect(view.container.querySelector('.tiptap')).not.toBeNull()) - onChange.mockClear() - - view.rerender() - view.rerender() - - expect(onChange).not.toHaveBeenCalled() - }) - it('accepts an external nonempty value while editability changes', async () => { const onChange = vi.fn() const props = { value: 'Original description', onChange, placeholder: 'Description' } diff --git a/src/renderer/src/components/github/GitHubMarkdownComposer.tsx b/src/renderer/src/components/github/GitHubMarkdownComposer.tsx index 270cb9d1066..458bc8aab13 100644 --- a/src/renderer/src/components/github/GitHubMarkdownComposer.tsx +++ b/src/renderer/src/components/github/GitHubMarkdownComposer.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useEditor } from '@tiptap/react' -import type { Editor } from '@tiptap/react' +import { useEditor, type Editor } from '@tiptap/react' + import Placeholder from '@tiptap/extension-placeholder' import { Paperclip } from 'lucide-react' import { cn } from '@/lib/class-names' @@ -25,7 +25,7 @@ import { } from '@/components/github/github-markdown-composer-tabbar' import { GitHubMarkdownImageInput } from './github-markdown-image-input' import { useImageInput } from '@/components/github/use-image-input' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { useTranslate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' import { diff --git a/src/renderer/src/components/github/IssueSourceIndicator.tsx b/src/renderer/src/components/github/IssueSourceIndicator.tsx index cf5a0656ccb..8225564b619 100644 --- a/src/renderer/src/components/github/IssueSourceIndicator.tsx +++ b/src/renderer/src/components/github/IssueSourceIndicator.tsx @@ -1,5 +1,5 @@ import React from 'react' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel' import { Trans } from 'react-i18next' import { i18n, translate } from '@/i18n/i18n' @@ -8,7 +8,7 @@ import { cn } from '@/lib/class-names' import { githubRepoIdentityKey, isDefaultGitHubHost -} from '../../../../shared/github/repository-identity-key' +} from '../../../../shared/github/repositories/repository-identity-key' export type IssueSourceIndicatorProps = { /** Null while unresolved or when the repo has no GitHub remote. */ diff --git a/src/renderer/src/components/github/IssueSourceSelector.tsx b/src/renderer/src/components/github/IssueSourceSelector.tsx index 10566d85704..3b3b3c90af7 100644 --- a/src/renderer/src/components/github/IssueSourceSelector.tsx +++ b/src/renderer/src/components/github/IssueSourceSelector.tsx @@ -1,5 +1,5 @@ import React from 'react' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { IssueSourcePreference } from '../../../../shared/repo-types' import { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' diff --git a/src/renderer/src/components/github/PRAssigneesPanel.tsx b/src/renderer/src/components/github/PRAssigneesPanel.tsx index 5257594e6c3..0168d1ba79c 100644 --- a/src/renderer/src/components/github/PRAssigneesPanel.tsx +++ b/src/renderer/src/components/github/PRAssigneesPanel.tsx @@ -11,15 +11,15 @@ import { useRepoAssignees } from '@/hooks/useGitHubRepoMetadata' import { useRepoAssigneesBySlug } from '@/hooks/useGitHubSlugMetadata' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { - parseOwnerRepoFromItemUrl, resolvePullRequestRepo, type GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../shared/github/work-items/work-item-target' import { runIssueUpdate } from '@/components/github/github-work-item-edit-mutations' import { ReviewerAvatar } from '@/components/github/work-item-state-presentation' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' export function PRAssigneesPanel({ item, diff --git a/src/renderer/src/components/github/PRFilterDropdowns.tsx b/src/renderer/src/components/github/PRFilterDropdowns.tsx index a37b7ce9427..20f0513f530 100644 --- a/src/renderer/src/components/github/PRFilterDropdowns.tsx +++ b/src/renderer/src/components/github/PRFilterDropdowns.tsx @@ -14,10 +14,8 @@ import { type PRFilterChange, type SectionKey } from '@/components/github/PRFilterSections' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import type { ParsedTaskQuery } from '../../../../shared/work-items/task-query' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/github/github-comment-image-upload-address.ts b/src/renderer/src/components/github/github-comment-image-upload-address.ts index d732709ac49..39685607b3a 100644 --- a/src/renderer/src/components/github/github-comment-image-upload-address.ts +++ b/src/renderer/src/components/github/github-comment-image-upload-address.ts @@ -1,6 +1,6 @@ import type { GitHubImageUploadAddress } from './github-image-upload' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { getGitHubRuntimeRepoId, getGitHubSourceRuntimeHost diff --git a/src/renderer/src/components/github/github-duplicate-issue-candidates.ts b/src/renderer/src/components/github/github-duplicate-issue-candidates.ts index 03bcc73826b..1f1130fe36a 100644 --- a/src/renderer/src/components/github/github-duplicate-issue-candidates.ts +++ b/src/renderer/src/components/github/github-duplicate-issue-candidates.ts @@ -1,11 +1,11 @@ import { useMemo } from 'react' -import { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { useShallow } from 'zustand/react/shallow' import { useAppStore } from '@/store' import type { AppState } from '@/store/types' import { getGitHubWorkItemRowHostId } from '@/store/github/work-items/work-item-routing' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' // Reuse the empty result to avoid allocating on every store write while the picker is closed. const NO_DUPLICATE_CANDIDATES: readonly GitHubWorkItem[] = [] diff --git a/src/renderer/src/components/github/github-image-upload.ts b/src/renderer/src/components/github/github-image-upload.ts index 4ce0ffc463b..292475381ac 100644 --- a/src/renderer/src/components/github/github-image-upload.ts +++ b/src/renderer/src/components/github/github-image-upload.ts @@ -4,7 +4,7 @@ import type { RuntimeStatus } from '../../../../shared/runtime-types' import { assertGitHubImage, GITHUB_IMAGE_ATTACHMENT_CAPABILITY -} from '../../../../shared/github/image-attachment' +} from '../../../../shared/github/attachments/image-attachment' import { CLIPBOARD_IMAGE_UPLOAD_CHUNK_BASE64_CHARS } from '../../../../shared/rpc-contract/clipboard-params' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { getRuntimeEnvironmentRevision } from '@/runtime/rpc/environment-revisions/runtime-environment-revision' diff --git a/src/renderer/src/components/github/github-markdown-composer-preview-pane.tsx b/src/renderer/src/components/github/github-markdown-composer-preview-pane.tsx index 2d540b2eca7..0db9e1b4e04 100644 --- a/src/renderer/src/components/github/github-markdown-composer-preview-pane.tsx +++ b/src/renderer/src/components/github/github-markdown-composer-preview-pane.tsx @@ -1,5 +1,5 @@ import CommentMarkdown from '@/components/sidebar/CommentMarkdown' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { useTranslate } from '@/i18n/use-translate' export function GitHubMarkdownComposerPreviewPane({ diff --git a/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts b/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts deleted file mode 100644 index e149c9ce28d..00000000000 --- a/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' -import { - GITHUB_PR_REVIEWER_QUERY_MAX_BYTES, - filterGitHubPRReviewerCandidates, - getGitHubPRReviewerQueryState -} from './github-pr-reviewer-candidate-filter' - -describe('github-pr-reviewer-candidate-filter', () => { - it('rejects oversized pasted reviewer queries before reading candidate metadata', () => { - const oversizedQuery = 'secret-reviewer-query'.repeat(GITHUB_PR_REVIEWER_QUERY_MAX_BYTES) - const candidate = { - get login(): string { - throw new Error('oversized reviewer queries must not scan logins') - }, - get name(): string { - throw new Error('oversized reviewer queries must not scan names') - }, - avatarUrl: '' - } as GitHubAssignableUser - - expect( - filterGitHubPRReviewerCandidates({ - candidates: [candidate], - queryState: getGitHubPRReviewerQueryState(oversizedQuery) - }) - ).toEqual([]) - }) -}) diff --git a/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.ts b/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.ts index 4f6037da7b0..18d4c38e578 100644 --- a/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.ts +++ b/src/renderer/src/components/github/github-pr-reviewer-candidate-filter.ts @@ -1,5 +1,5 @@ import { isClipboardTextByteLengthOverLimit } from '../../../../shared/clipboard-text' -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' export type GitHubPRReviewerQueryState = { query: string diff --git a/src/renderer/src/components/github/github-rate-limit-display.tsx b/src/renderer/src/components/github/github-rate-limit-display.tsx index 20d62f8c759..95bc4272742 100644 --- a/src/renderer/src/components/github/github-rate-limit-display.tsx +++ b/src/renderer/src/components/github/github-rate-limit-display.tsx @@ -8,7 +8,7 @@ import { isWslDistroNotRunningRefusal } from '../../../../shared/wsl-distro-not- import type { GetRateLimitResult, GitHubRateLimitSnapshot -} from '../../../../shared/github/rate-limit-types' +} from '../../../../shared/github/api/rate-limit-types' import { getProviderRateLimitScope } from '@/components/provider-accounts/provider-account-scope' import { ProviderHostScopeControl } from '@/components/provider-accounts/ProviderHostScopeControl' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/github/github-work-item-assignee-filter.ts b/src/renderer/src/components/github/github-work-item-assignee-filter.ts index 7d30ce248b1..29f4cd03429 100644 --- a/src/renderer/src/components/github/github-work-item-assignee-filter.ts +++ b/src/renderer/src/components/github/github-work-item-assignee-filter.ts @@ -1,4 +1,4 @@ -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' import { isGitHubWorkItemOptionFilterQueryTooLarge } from './github-work-item-option-filter-bounds' export function filterGitHubWorkItemAssignees( diff --git a/src/renderer/src/components/github/github-work-item-comment-mutations.ts b/src/renderer/src/components/github/github-work-item-comment-mutations.ts index 0fc458701cf..d1a0f5882a6 100644 --- a/src/renderer/src/components/github/github-work-item-comment-mutations.ts +++ b/src/renderer/src/components/github/github-work-item-comment-mutations.ts @@ -1,6 +1,6 @@ import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import type { RuntimeStatus } from '../../../../shared/runtime-types' -import type { GhAccountBinding } from '../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' import { GITHUB_ACCOUNT_EXPECTATION_RUNTIME_CAPABILITY } from '../../../../shared/work-items/task-source-runtime-capabilities' import { getRuntimeEnvironmentConnectionGeneration } from '@/store/slices/runtime-status/runtime-status-connection-generation' import { RUNTIME_CONNECTION_CHANGED_MESSAGE } from '@/web/web-runtime-request-registry' @@ -11,7 +11,7 @@ import { getGitHubSourceRuntimeHost } from '@/lib/work-items/github/github-source-runtime-context' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' export function addIssueCommentForRepo(args: { repoId?: string diff --git a/src/renderer/src/components/github/github-work-item-edit-mutations.ts b/src/renderer/src/components/github/github-work-item-edit-mutations.ts index 91ddcd150b4..62b5e38a6ae 100644 --- a/src/renderer/src/components/github/github-work-item-edit-mutations.ts +++ b/src/renderer/src/components/github/github-work-item-edit-mutations.ts @@ -15,10 +15,10 @@ import { type GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { notifyWorkItemDetailsMutation } from '@/components/github/github-work-item-comment-mutations' -import { githubProjectHost } from '../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' export async function runIssueUpdate(args: { diff --git a/src/renderer/src/components/github/github-work-item-identity.ts b/src/renderer/src/components/github/github-work-item-identity.ts index de6463e26cc..db01e04c6e0 100644 --- a/src/renderer/src/components/github/github-work-item-identity.ts +++ b/src/renderer/src/components/github/github-work-item-identity.ts @@ -1,9 +1,8 @@ -import { githubProjectHost } from '../../../../shared/github/project-identity' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import { githubProjectHost } from '../../../../shared/github/projects/project-identity' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' -import { parseGitHubItemRepository } from '../../../../shared/github/work-item-target' -export { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../shared/github/work-item-target' +import { parseGitHubItemRepository } from '../../../../shared/github/work-items/work-item-target' /** Project rows may belong to another repo; route mutations by their slug instead of the active workspace. */ export type GitHubWorkItemProjectOrigin = { diff --git a/src/renderer/src/components/github/pr-check-presentation.ts b/src/renderer/src/components/github/pr-check-presentation.ts index 03421e52da0..65e3922f10d 100644 --- a/src/renderer/src/components/github/pr-check-presentation.ts +++ b/src/renderer/src/components/github/pr-check-presentation.ts @@ -5,7 +5,7 @@ import type { PRCheckAnnotation, PRCheckDetail, PRCheckJob -} from '../../../../shared/github/check-types' +} from '../../../../shared/github/checks/check-types' export function getCheckStatusLabel(check: PRCheckDetail, t: Translate = translate): string { if (check.gitlabAllowedFailure === true && check.conclusion === 'neutral') { diff --git a/src/renderer/src/components/github/pr-file-content-size.ts b/src/renderer/src/components/github/pr-file-content-size.ts index 89051f908af..3a8facc924e 100644 --- a/src/renderer/src/components/github/pr-file-content-size.ts +++ b/src/renderer/src/components/github/pr-file-content-size.ts @@ -2,7 +2,7 @@ import { MAX_RENDERED_DIFF_COMBINED_CHARACTERS } from '../../../../shared/large- import type { GitHubPRFile, GitHubPRFileContents -} from '../../../../shared/github/pull-request-types' +} from '../../../../shared/github/pull-requests/pull-request-types' import { getUtf8ByteLength } from '../../../../shared/utf8-byte-limits' export const PR_FILE_CONTENT_CACHE_MAX_BYTES = MAX_RENDERED_DIFF_COMBINED_CHARACTERS * 4 diff --git a/src/renderer/src/components/github/pr-file-diff-mapping.ts b/src/renderer/src/components/github/pr-file-diff-mapping.ts index d43886e8be3..d5af31995c6 100644 --- a/src/renderer/src/components/github/pr-file-diff-mapping.ts +++ b/src/renderer/src/components/github/pr-file-diff-mapping.ts @@ -9,12 +9,12 @@ import type { GitBranchChangeEntry, GitDiffResult } from '../../../../shared/git/diff/git-diff-compare-types' -import type { PRComment } from '../../../../shared/github/comment-types' +import type { PRComment } from '../../../../shared/github/comments/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../shared/github/pull-request-types' +} from '../../../../shared/github/pull-requests/pull-request-types' export const PR_DIFF_OVERSCAN = 5 diff --git a/src/renderer/src/components/github/pull-request-confirmation-copy.ts b/src/renderer/src/components/github/pull-request-confirmation-copy.ts index eb2e5ec7552..03f30d79131 100644 --- a/src/renderer/src/components/github/pull-request-confirmation-copy.ts +++ b/src/renderer/src/components/github/pull-request-confirmation-copy.ts @@ -1,4 +1,4 @@ -import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-request-types' +import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-requests/pull-request-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/github/work-item-state-badge.tsx b/src/renderer/src/components/github/work-item-state-badge.tsx index 60f8d56a821..a7eceaedf50 100644 --- a/src/renderer/src/components/github/work-item-state-badge.tsx +++ b/src/renderer/src/components/github/work-item-state-badge.tsx @@ -5,7 +5,7 @@ import { getReviewStateTone, OPEN_REVIEW_STATE_TONE } from '@/components/github/review-state-presentation' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { useTranslate } from '@/i18n/use-translate' /** Surfaces may treat closed issues as destructive or neutral; PR state tones stay fixed. */ diff --git a/src/renderer/src/components/github/work-item-state-presentation.tsx b/src/renderer/src/components/github/work-item-state-presentation.tsx index 7ff620967cd..425312e3c59 100644 --- a/src/renderer/src/components/github/work-item-state-presentation.tsx +++ b/src/renderer/src/components/github/work-item-state-presentation.tsx @@ -1,7 +1,7 @@ import React from 'react' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { formatUiRelativeTimeFromDate } from '@/i18n/relative-time-format' import { getTaskPageGitHubWorkItemStateLabel } from '@/components/task-page/github/task-page-github-work-item-status' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/markdown-github-callout.tsx b/src/renderer/src/components/markdown-github-callout.tsx index e8381ac3920..d25f6b1518d 100644 --- a/src/renderer/src/components/markdown-github-callout.tsx +++ b/src/renderer/src/components/markdown-github-callout.tsx @@ -9,7 +9,7 @@ import { } from 'lucide-react' import { translate } from '@/i18n/i18n' import { cn } from '@/lib/class-names' -import type { GitHubCalloutKind } from '../../../shared/github/github-callouts' +import type { GitHubCalloutKind } from '../../../shared/github/markdown/github-callouts' const CALLOUT_PRESENTATIONS: Record< GitHubCalloutKind, diff --git a/src/renderer/src/components/native-chat/links/previews/NativeChatGitHubLinkPeek.tsx b/src/renderer/src/components/native-chat/links/previews/NativeChatGitHubLinkPeek.tsx index 28ecd236164..b5819928b62 100644 --- a/src/renderer/src/components/native-chat/links/previews/NativeChatGitHubLinkPeek.tsx +++ b/src/renderer/src/components/native-chat/links/previews/NativeChatGitHubLinkPeek.tsx @@ -9,8 +9,8 @@ import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { parseGitHubIssueOrPRLink, type GitHubIssueOrPRLink -} from '../../../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +} from '../../../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../../shared/repo-types' import { githubLinkPreviewKey, diff --git a/src/renderer/src/components/native-chat/links/previews/native-chat-github-link-preview.ts b/src/renderer/src/components/native-chat/links/previews/native-chat-github-link-preview.ts index 7e7e78f087c..281b40f8a83 100644 --- a/src/renderer/src/components/native-chat/links/previews/native-chat-github-link-preview.ts +++ b/src/renderer/src/components/native-chat/links/previews/native-chat-github-link-preview.ts @@ -1,8 +1,8 @@ import { lookupGitHubWorkItemByOwnerRepoForSource } from '@/lib/work-items/github/github-work-item-source-lookup' -import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../../shared/repo-types' -import { githubRepoIdentityKey } from '../../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../../shared/github/repositories/repository-identity-key' import { buildTaskSourceContextFromRepo } from '../../../../../../shared/work-items/task-source-context' import { getRepoExecutionHostId } from '../../../../../../shared/execution-host' import { createHoverPreviewCache } from './native-chat-hover-preview-cache' diff --git a/src/renderer/src/components/native-chat/links/previews/native-chat-github-repo.ts b/src/renderer/src/components/native-chat/links/previews/native-chat-github-repo.ts index 8ae7098a1c3..da9e2b6bc6f 100644 --- a/src/renderer/src/components/native-chat/links/previews/native-chat-github-repo.ts +++ b/src/renderer/src/components/native-chat/links/previews/native-chat-github-repo.ts @@ -20,11 +20,11 @@ import { githubHostFromIdentityKey, githubRepoIdentityKey, repoUpstreamIdentityKey -} from '../../../../../../shared/github/repository-identity-key' +} from '../../../../../../shared/github/repositories/repository-identity-key' import { githubReferenceRepo, type GitHubRepoReference -} from '../../../../../../shared/github/github-reference-links' +} from '../../../../../../shared/github/markdown/github-reference-links' export type NativeChatGitHubRepo = { repo: Repo diff --git a/src/renderer/src/components/native-chat/tools/pull-requests/NativeChatOpenedPullRequestCard.tsx b/src/renderer/src/components/native-chat/tools/pull-requests/NativeChatOpenedPullRequestCard.tsx index 1ed15087b49..422883889ff 100644 --- a/src/renderer/src/components/native-chat/tools/pull-requests/NativeChatOpenedPullRequestCard.tsx +++ b/src/renderer/src/components/native-chat/tools/pull-requests/NativeChatOpenedPullRequestCard.tsx @@ -12,8 +12,8 @@ import { useTranslate } from '@/i18n/use-translate' import { cn } from '@/lib/class-names' import { getReviewStateIcon } from '@/components/github/review-state-presentation' import { DiffLineCounts } from '@/components/right-sidebar/source-control/listing/diff-line-counts' -import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import { nativeChatOpenedPullRequestKey, type NativeChatOpenedPullRequest diff --git a/src/renderer/src/components/new-workspace/composer-modal-data.ts b/src/renderer/src/components/new-workspace/composer-modal-data.ts index f81fdbf4d6b..65f375110b3 100644 --- a/src/renderer/src/components/new-workspace/composer-modal-data.ts +++ b/src/renderer/src/components/new-workspace/composer-modal-data.ts @@ -5,7 +5,7 @@ import type { import type { AgentLaunchSurface } from '@/lib/agent-session/launch/routing/agent-session-launch-plan' import type { DirectWorkItemSetupRequest } from '@/lib/workspace/creation/work-item/direct-work-item-setup' import type { LinkedWorkItemSummary } from '@/lib/workspace/creation/new-workspace' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { TuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent' import type { WorkspaceSource as WorkspaceCreateTelemetrySource } from '../../../../shared/workspace-source' import type { WorkspaceStatus } from '../../../../shared/worktree/types' diff --git a/src/renderer/src/components/new-workspace/github-source-consumers.test.tsx b/src/renderer/src/components/new-workspace/github-source-consumers.test.tsx index f0fede1af5c..9f23bb4914a 100644 --- a/src/renderer/src/components/new-workspace/github-source-consumers.test.tsx +++ b/src/renderer/src/components/new-workspace/github-source-consumers.test.tsx @@ -7,7 +7,7 @@ import { createTestStore, resetRemoteRuntimeMocks } from '@/store/github/github- import { useGitHubDuplicateIssueCandidates } from '@/components/github/github-duplicate-issue-candidates' import type { AppState } from '@/store/types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../shared/repo-types' const holder = vi.hoisted((): { store: ReturnType | null } => ({ store: null diff --git a/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts b/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts index 1ef15f63d15..b21ea6202a2 100644 --- a/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts +++ b/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts @@ -15,7 +15,7 @@ import type { WorkspaceCreateErrorDisplay } from '@/lib/workspace/creation/works import type { SmartNameMode } from '../../../../shared/new-workspace/smart-workspace-source-results' import type SmartWorkspaceNameField from './SmartWorkspaceNameField' import type { SmartWorkspaceNameSelection } from '@/components/new-workspace/smart-workspace-name-field-model' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue } from '../../../../shared/jira/jira-types' import type { LinearIssue } from '../../../../shared/linear/issue-types' diff --git a/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.test.ts b/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.test.ts index 5fc53698028..87e6ae7c32d 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.test.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' import type { RepoOption } from './smart-workspace-name-field-model' import { resolveSmartWorkspaceGithubDirectLink } from './smart-workspace-github-direct-link' diff --git a/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.ts b/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.ts index de7a2bc4916..17cac1bfd7c 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-github-direct-link.ts @@ -1,11 +1,11 @@ import type React from 'react' -import type { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' +import type { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' import { lookupGitHubWorkItemByOwnerRepoForSource, lookupGitHubWorkItemForSource } from '@/lib/work-items/github/github-work-item-source-lookup' import { lookupSmartGitHubSubmitItem } from '@/lib/work-items/github/smart-github-submit' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { buildTaskSourceContextFromRepo, type TaskSourceContext diff --git a/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts b/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts index 79bee1ddd88..ef146f2b404 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts @@ -1,7 +1,7 @@ import type React from 'react' import type { useAppStore } from '@/store' -import type { parseGitHubIssueOrPRLink, RepoSlug } from '../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue, JiraSite } from '../../../../shared/jira/jira-types' import type { LinearIssue } from '../../../../shared/linear/issue-types' @@ -83,8 +83,6 @@ export type SmartWorkspaceNameFieldSearchState = { jiraIssues: JiraIssue[] } -export type CachedRepoSlug = RepoSlug - export const EMPTY_REPO_SEARCH_REPOS: readonly RepoOption[] = [] export const SEARCH_DEBOUNCE_MS = 200 export const RESULT_LIMIT = 12 diff --git a/src/renderer/src/components/new-workspace/smart-workspace-repo-slug.ts b/src/renderer/src/components/new-workspace/smart-workspace-repo-slug.ts index 8dfe4a7a9ae..3fa1ae993d0 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-repo-slug.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-repo-slug.ts @@ -1,10 +1,10 @@ -import type { RepoSlug } from '../../../../shared/github/links' +import type { RepoSlug } from '../../../../shared/github/repositories/links' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { getGitHubRuntimeRepoId, getGitHubSourceRuntimeTarget } from '@/lib/work-items/github/github-source-runtime-context' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { getTaskSourceCacheScope, type TaskSourceContext diff --git a/src/renderer/src/components/new-workspace/use-smart-workspace-github-search.ts b/src/renderer/src/components/new-workspace/use-smart-workspace-github-search.ts index c7b10dcdbee..65ff165b4ef 100644 --- a/src/renderer/src/components/new-workspace/use-smart-workspace-github-search.ts +++ b/src/renderer/src/components/new-workspace/use-smart-workspace-github-search.ts @@ -1,13 +1,13 @@ import { useEffect, useMemo } from 'react' import { useAppStore } from '@/store' import { normalizeGitHubLinkQuery } from '../../../../shared/new-workspace/github-links' -import { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' import { lookupGitHubWorkItemByOwnerRepoForSource, lookupGitHubWorkItemForSource } from '@/lib/work-items/github/github-work-item-source-lookup' import { lookupSmartGitHubSubmitItem } from '@/lib/work-items/github/smart-github-submit' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { RESULT_LIMIT } from './smart-workspace-name-field-model' import { resolveSmartWorkspaceGithubDirectLink } from './smart-workspace-github-direct-link' import type { useWorkItemSourceFoundation } from './use-work-item-source-foundation' diff --git a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts index 5b5c1ab4dea..411000ff244 100644 --- a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts +++ b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts @@ -9,7 +9,7 @@ import { type getActiveWorkspaceEmojiShortcode } from '@/lib/workspace/display/workspace-emoji-shortcodes' import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { buildTaskSourceContextFromRepo } from '../../../../shared/work-items/task-source-context' import type { RepoOption, diff --git a/src/renderer/src/components/new-workspace/use-work-item-source-presentation.ts b/src/renderer/src/components/new-workspace/use-work-item-source-presentation.ts index 71f6d1b6d03..6d28c7b02d4 100644 --- a/src/renderer/src/components/new-workspace/use-work-item-source-presentation.ts +++ b/src/renderer/src/components/new-workspace/use-work-item-source-presentation.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo } from 'react' import { CaseSensitive, LoaderCircle, Search } from 'lucide-react' -import { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' import { parseGitLabIssueOrMRLink } from '../../../../shared/new-workspace/gitlab-links' import { isSmartWorkspaceLinearIssueIntentMatch, diff --git a/src/renderer/src/components/new-workspace/use-work-item-source-state.ts b/src/renderer/src/components/new-workspace/use-work-item-source-state.ts index ca66a8e8461..7447094f87b 100644 --- a/src/renderer/src/components/new-workspace/use-work-item-source-state.ts +++ b/src/renderer/src/components/new-workspace/use-work-item-source-state.ts @@ -1,7 +1,7 @@ import { useGitHubTaskItems } from '@/hooks/useGitHubTaskItems' import { useRef, useState } from 'react' -import type { RepoSlug } from '../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { RepoSlug } from '../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue } from '../../../../shared/jira/jira-types' import type { BaseRefSearchResult } from '../../../../shared/repo-types' diff --git a/src/renderer/src/components/new-workspace/work-item-source-selection.ts b/src/renderer/src/components/new-workspace/work-item-source-selection.ts index d7890992fbd..7d4a4fa2efd 100644 --- a/src/renderer/src/components/new-workspace/work-item-source-selection.ts +++ b/src/renderer/src/components/new-workspace/work-item-source-selection.ts @@ -8,7 +8,7 @@ import { buildJiraWorkspaceSource, type WorkspaceSourceLinkedItem } from '../../../../shared/new-workspace/workspace-source' -import { parseGitHubIssueOrPRLink } from '../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../shared/github/repositories/links' import { parseGitLabIssueOrMRLink } from '../../../../shared/new-workspace/gitlab-links' import { bindJiraIssueSourceContext } from './use-jira-url-source' import type { RowEntry } from './smart-workspace-name-field-model' diff --git a/src/renderer/src/components/pr-check-counts.ts b/src/renderer/src/components/pr-check-counts.ts index 772b8a0f117..9f1995affda 100644 --- a/src/renderer/src/components/pr-check-counts.ts +++ b/src/renderer/src/components/pr-check-counts.ts @@ -1,13 +1,13 @@ -import { normalizePRCheckHeadSha } from '../../../shared/github/pr-check-head' +import { normalizePRCheckHeadSha } from '../../../shared/github/checks/pr-check-head' import { isCompletePRCheckOwner, samePRCheckObservation -} from '../../../shared/github/complete-pr-check-owner' -import type { ProviderCheckSummary } from '../../../shared/github/pull-request-types' +} from '../../../shared/github/checks/complete-pr-check-owner' +import type { ProviderCheckSummary } from '../../../shared/github/pull-requests/pull-request-types' import { translate } from '../i18n/i18n' import type { Translate } from '../i18n/use-translate' import { summarizeProviderChecks } from '../../../shared/source-control/checks/provider-check-summary' -import type { PRCheckDetail } from '../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../shared/github/checks/check-types' export type PRCheckCounts = { passing: number diff --git a/src/renderer/src/components/pr-check-empty-details.fixture.tsx b/src/renderer/src/components/pr-check-empty-details.fixture.tsx index 2d0b262e68c..bc567cbf17f 100644 --- a/src/renderer/src/components/pr-check-empty-details.fixture.tsx +++ b/src/renderer/src/components/pr-check-empty-details.fixture.tsx @@ -4,7 +4,7 @@ import { ChecksCompactHeader } from './pull-request-page/checks/toolbar' import { ChecksTabCompactHeader } from './github-item-dialog/inspect-pull-request/checks-tab-header' import { getChecksTabSummaryPresentation } from './github-item-dialog/inspect-pull-request/checks-tab-summary' import { getOverallCheckCounts, getChecksSummaryLabel } from './pr-check-counts' -import { acceptPRCheckObservation } from '../../../shared/github/complete-pr-check-owner' +import { acceptPRCheckObservation } from '../../../shared/github/checks/complete-pr-check-owner' import { Button } from './ui/button' const owner = acceptPRCheckObservation( diff --git a/src/renderer/src/components/pr-check-empty-details.tsx b/src/renderer/src/components/pr-check-empty-details.tsx index cc953b338f5..6b854b09c4b 100644 --- a/src/renderer/src/components/pr-check-empty-details.tsx +++ b/src/renderer/src/components/pr-check-empty-details.tsx @@ -2,8 +2,8 @@ import type React from 'react' import { CircleDashed } from 'lucide-react' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' -import { isCompletePRCheckOwner } from '../../../shared/github/complete-pr-check-owner' -import type { ProviderCheckSummary } from '../../../shared/github/pull-request-types' +import { isCompletePRCheckOwner } from '../../../shared/github/checks/complete-pr-check-owner' +import type { ProviderCheckSummary } from '../../../shared/github/pull-requests/pull-request-types' export function PRCheckEmptyDetails({ owner, diff --git a/src/renderer/src/components/pr-check-owner-presentation.test.tsx b/src/renderer/src/components/pr-check-owner-presentation.test.tsx deleted file mode 100644 index 98cb9d1667c..00000000000 --- a/src/renderer/src/components/pr-check-owner-presentation.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, expect, it, vi } from 'vitest' -import { acceptPRCheckObservation } from '../../../shared/github/complete-pr-check-owner' -import { PRTriageStrip } from './right-sidebar/checks-panel/presentation/triage-strip' -import { i18n } from '@/i18n/i18n' -import { gitLabPipelineJobsToPRChecks } from '../../../shared/source-control/checks/gitlab/gitlab-pipeline-checks' -import type { PRCheckDetail } from '../../../shared/github/check-types' - -const owner = { - number: 42, - url: 'https://github.com/acme/widgets/pull/42', - headSha: 'a'.repeat(40), - mergeable: 'UNKNOWN' as const -} -const context = { scope: 'account:runtime', complete: true, current: true } -const blocked = { - state: 'failure' as const, - total: 101, - passed: 100, - failed: 1, - pending: 0, - neutral: 0 -} -const rows: PRCheckDetail[] = [ - { - name: 'Approval', - status: 'completed', - conclusion: 'action_required', - url: null, - observedHeadSha: 'a'.repeat(40) - } -] -afterEach(async () => { - cleanup() - await i18n.changeLanguage('en') -}) - -it.each([false, true])('keeps a failed parent pipeline fixable with empty jobs: %s', (empty) => { - const fix = vi.fn() - const props = { - review: owner, - reviewKind: 'MR' as const, - checks: empty ? [] : rows.map((row) => ({ ...row, conclusion: 'success' as const })), - pipelineCheck: { - name: 'Pipeline', - status: 'completed' as const, - conclusion: 'failure' as const, - url: null - }, - isResolvingConflictsWithAI: false, - onResolveConflictsWithAI: () => {}, - isFixingChecksWithAI: false, - onFixChecksWithAI: fix - } - const view = render() - fireEvent.click(screen.getByRole('button', { name: 'Fix' })) - expect(fix).toHaveBeenCalledOnce() - view.rerender() - expect(screen.getByRole('button', { name: 'Fix' }).hasAttribute('disabled')).toBe(true) - view.rerender() - expect(screen.getByRole('button', { name: 'Fix' }).hasAttribute('disabled')).toBe(true) - const accepted = acceptPRCheckObservation({ ...owner, checksSummary: blocked }, null, context)! - view.rerender( - - ) - expect(screen.getByRole('button', { name: 'Fix' }).hasAttribute('disabled')).toBe(true) -}) - -it('offers automatic failure repair only for a required failed GitLab job', () => { - const check = (allowFailure: boolean) => - gitLabPipelineJobsToPRChecks([ - { - id: 7, - name: 'test', - stage: 'test', - status: 'failed', - allowFailure, - webUrl: '', - duration: null - } - ]) - const props = { - review: owner, - reviewKind: 'MR' as const, - isResolvingConflictsWithAI: false, - onResolveConflictsWithAI: () => {}, - isFixingChecksWithAI: false, - onFixChecksWithAI: vi.fn() - } - const view = render() - expect(screen.queryByRole('button')).toBeNull() - view.rerender() - fireEvent.click(screen.getByRole('button')) - expect(props.onFixChecksWithAI).toHaveBeenCalledOnce() -}) diff --git a/src/renderer/src/components/pr-check-refresh.test.ts b/src/renderer/src/components/pr-check-refresh.test.ts deleted file mode 100644 index b1406b3347e..00000000000 --- a/src/renderer/src/components/pr-check-refresh.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { refreshPullRequestChecks } from './pull-request-page/checks/refresh' -import { - refreshGitHubChecksTab, - type ChecksTabActionContext -} from './github-item-dialog/inspect-pull-request/checks-tab-actions' -import { createGitHubChecksTabState } from './github-checks-tab-state' -import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/hosts/capabilities/local-runtime-capabilities' -import { GITHUB_COMPLETE_PR_CHECK_OBSERVATION_CAPABILITY as capability } from '../../../shared/source-control/runtime/source-control-runtime-capabilities' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import { getOverallCheckCounts } from './pr-check-counts' -const { lookup, rows } = vi.hoisted(() => ({ lookup: vi.fn(), rows: vi.fn() })) -vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } })) -vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn() } })) -const item: GitHubWorkItem = { - id: 'pr-42', - repoId: 'repo-1', - number: 42, - type: 'pr', - title: 'PR', - state: 'open', - url: 'https://github.com/acme/widgets/pull/42', - labels: [], - author: null, - updatedAt: '2026-11-04', - headSha: 'head', - prRepo: { owner: 'acme', repo: 'widgets', host: 'github.com' } -} -const summary = { - state: 'failure' as const, - total: 101, - passed: 100, - failed: 1, - cancelled: 0, - pending: 0, - neutral: 0 -} -function context() { - return { - canUseChecksRepoContext: true, - runtimeHost: null, - repoId: item.repoId, - repoPath: '/repo', - item, - itemNumber: 42, - itemRepoId: item.repoId, - headSha: 'head', - prRepo: item.prRepo!, - mountedRef: { current: true }, - committedChecksContextOwnerRef: { - current: createGitHubChecksTabState([], 'repo').contextOwner - }, - nextChecksRefreshRequestIdRef: { current: 0 }, - activeChecksRefreshRequestIdRef: { current: null }, - nextCheckDetailsRequestIdRef: { current: 0 }, - setRefreshingOwner: vi.fn(), - setChecksState: vi.fn(), - setRerunningOwner: vi.fn(), - onChecksUpdated: vi.fn(), - onOwnerUpdated: vi.fn() - } satisfies ChecksTabActionContext -} -beforeEach(() => { - lookup.mockReset() - rows.mockReset() - Object.assign(globalThis, { - window: { api: { gh: { prChecks: rows, workItemByOwnerRepo: lookup } } } - }) - rows.mockResolvedValue([{ name: 'Build', status: 'completed', conclusion: 'success', url: null }]) - setLocalRuntimeCapabilitiesForTests([capability]) -}) -afterEach(() => setLocalRuntimeCapabilitiesForTests(null)) - -it.each(['page', 'dialog'])( - 'refreshes the %s owner and uses its counts while returned rows remain partial', - async (surface) => { - const ctx = context() - const refresh = () => - surface === 'page' ? refreshPullRequestChecks(ctx) : refreshGitHubChecksTab(ctx) - lookup.mockResolvedValue({ ...item, checksSummary: summary }) - const checks = await refresh() - const owner = ctx.onOwnerUpdated.mock.calls[0][0] - expect(getOverallCheckCounts(checks!, owner)).toMatchObject({ passing: 100, failing: 1 }) - lookup.mockResolvedValue({ - ...item, - checksSummary: { ...summary, state: 'success', total: 101, passed: 101, failed: 0 } - }) - await refresh() - expect(ctx.onOwnerUpdated.mock.calls[1][0].checksSummary).toMatchObject({ - state: 'success', - passed: 101 - }) - const deferred = Promise.withResolvers() - lookup.mockReturnValue(deferred.promise) - const pending = refresh() - ctx.committedChecksContextOwnerRef.current = {} - deferred.resolve({ ...item, checksSummary: summary }) - await pending - expect(ctx.onOwnerUpdated).toHaveBeenCalledTimes(2) - } -) - -it('keeps the supported old-host refresh parameters and row-only publication', async () => { - setLocalRuntimeCapabilitiesForTests([]) - const ctx = context() - await refreshPullRequestChecks(ctx) - expect(lookup).not.toHaveBeenCalled() - expect(ctx.onOwnerUpdated).not.toHaveBeenCalled() - expect(ctx.onChecksUpdated.mock.calls[0][0]).toHaveLength(1) - expect(rows.mock.calls[0][0]).toMatchObject({ prNumber: 42, headSha: 'head', noCache: true }) -}) - -it.each(['page', 'dialog'])('settles %s owner and row failures independently', async (surface) => { - const ctx = context() - const refresh = () => - surface === 'page' ? refreshPullRequestChecks(ctx) : refreshGitHubChecksTab(ctx) - lookup.mockResolvedValue({ ...item, checksSummary: summary }) - rows.mockRejectedValueOnce(new Error('rows failed')) - await refresh() - expect(ctx.onOwnerUpdated.mock.calls[0][0].checksSummary).toEqual(summary) - expect(ctx.onChecksUpdated).not.toHaveBeenCalled() - lookup.mockRejectedValueOnce(new Error('owner failed')) - await refresh() - expect(ctx.onChecksUpdated.mock.calls[0][0]).toHaveLength(1) - expect(ctx.onOwnerUpdated.mock.calls[1][0].checksSummary).toBeUndefined() -}) diff --git a/src/renderer/src/components/pr-check-tab-actions.test.tsx b/src/renderer/src/components/pr-check-tab-actions.test.tsx deleted file mode 100644 index 80db3bd8849..00000000000 --- a/src/renderer/src/components/pr-check-tab-actions.test.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// @vitest-environment happy-dom -import { useSyncExternalStore } from 'react' -import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/hosts/capabilities/local-runtime-capabilities' -import { GITHUB_COMPLETE_PR_CHECK_OBSERVATION_CAPABILITY as capability } from '../../../shared/source-control/runtime/source-control-runtime-capabilities' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../shared/github/check-types' -import { TooltipProvider } from '@/components/ui/tooltip' -import { - patchCachedPRChecks, - subscribeWorkItemDetailsCache, - touchWorkItemDetailsCache, - workItemDetailsCache -} from './github-item-dialog/load-item-details/work-item-details-cache' -import { ChecksTab as PageChecks } from './pull-request-page/checks/tab' -import { ChecksTab as DialogChecks } from './github-item-dialog/inspect-pull-request/checks-tab' - -const { lookup, rows, rerun } = vi.hoisted(() => ({ - lookup: vi.fn(), - rows: vi.fn(), - rerun: vi.fn() -})) -vi.mock('@/store', async () => { - const { getDefaultSettings } = await import('../../../shared/constants') - const state = { - settings: getDefaultSettings('/tmp/pr-check-tab-actions'), - repos: [], - projects: [], - runtimeStatusByEnvironmentId: new Map() - } - return { - useAppStore: Object.assign((select: (state: unknown) => unknown) => select(state), { - getState: () => state - }) - } -}) -vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn() } })) -function actions({ - onRefresh, - onRerun -}: { - onRefresh: () => void - onRerun: (failedOnly: boolean) => void -}) { - const refreshAction = - const rerunAction = - return { - refreshAction, - rerunAction, - actions: ( - <> - {refreshAction} - {rerunAction} - - ), - secondaryActions: null, - fixBrokenChecksAction: null - } -} -vi.mock('./pull-request-page/checks/toolbar', async (original) => ({ - ...(await original>()), - buildChecksToolbar: actions -})) -vi.mock('./github-item-dialog/inspect-pull-request/checks-tab-header', async (original) => ({ - ...(await original>()), - ChecksTabActions: actions -})) -const initial: GitHubWorkItem = { - id: 'pr-42', - repoId: 'repo-1', - number: 42, - type: 'pr', - title: 'PR', - state: 'open', - url: 'https://github.com/acme/widgets/pull/42', - labels: [], - author: null, - updatedAt: '2026-11-04', - headSha: 'a'.repeat(40), - prRepo: { owner: 'acme', repo: 'widgets', host: 'github.com' } -} -const failed: PRCheckDetail[] = [ - { name: 'Build', status: 'completed', conclusion: 'failure', url: null } -] -const summary = { - state: 'failure' as const, - total: 1, - passed: 0, - failed: 1, - cancelled: 0, - pending: 0, - neutral: 0 -} -const cacheKey = 'pr-checks-partial-actions-test' -function Harness({ Tab, parent = initial }: { Tab: typeof PageChecks; parent?: GitHubWorkItem }) { - const checks = useSyncExternalStore( - subscribeWorkItemDetailsCache, - () => workItemDetailsCache.get(cacheKey)?.details?.checks ?? failed - ) - return ( - - patchCachedPRChecks(cacheKey, next)} - /> - - ) -} - -beforeEach(() => { - vi.clearAllMocks() - touchWorkItemDetailsCache(cacheKey, { - details: { item: initial, body: '', comments: [], checks: failed }, - fetchedAt: 0 - }) - lookup.mockResolvedValue({ ...initial, headSha: 'b'.repeat(40), checksSummary: summary }) - rows.mockResolvedValue(failed) - rerun.mockResolvedValue({ ok: true, count: 1 }) - Object.assign(window, { - api: { gh: { prChecks: rows, workItemByOwnerRepo: lookup, rerunPRChecks: rerun } } - }) - setLocalRuntimeCapabilitiesForTests([capability]) -}) -afterEach(() => { - cleanup() - workItemDetailsCache.delete(cacheKey) - setLocalRuntimeCapabilitiesForTests(null) -}) - -it.each([PageChecks, DialogChecks])( - 'reruns the refreshed head while the parent still has the prior head', - async (Tab) => { - render() - await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'Refresh' })) - }) - expect(rows.mock.calls[0][0]).toMatchObject({ headSha: 'b'.repeat(40) }) - lookup.mockRejectedValueOnce(new Error('owner unavailable')) - await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'Rerun' })) - }) - expect(rerun.mock.calls[0][0]).toMatchObject({ - prNumber: 42, - headSha: 'b'.repeat(40), - failedOnly: true - }) - expect(rows.mock.calls[1][0]).toMatchObject({ headSha: 'b'.repeat(40) }) - expect(screen.getByText('1 check failing')).toBeTruthy() - } -) diff --git a/src/renderer/src/components/pull-request-page/actions/merge-actions.test.ts b/src/renderer/src/components/pull-request-page/actions/merge-actions.test.ts deleted file mode 100644 index 007d143dd6d..00000000000 --- a/src/renderer/src/components/pull-request-page/actions/merge-actions.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import { changePullRequestState, mergePullRequest } from './merge-actions' - -const mocks = vi.hoisted(() => ({ - toastError: vi.fn(), - toastSuccess: vi.fn(), - runPullRequestStateUpdate: vi.fn() -})) - -vi.mock('sonner', () => ({ - toast: { error: mocks.toastError, success: mocks.toastSuccess } -})) - -vi.mock('@/components/github/github-work-item-edit-mutations', () => ({ - runPullRequestStateUpdate: mocks.runPullRequestStateUpdate -})) - -const item: GitHubWorkItem = { - id: 'pr:7', - type: 'pr', - number: 7, - repoId: 'repo-1', - title: 'PR 7', - state: 'open', - url: 'https://github.com/acme/widgets/pull/7', - labels: [], - updatedAt: '2026-11-01T00:00:00Z', - author: null -} - -function recordEvents(): { - events: string[] - setPending: (value: boolean) => void - applyStatePatch: (state: GitHubWorkItem['state']) => void - onMutated: () => void -} { - const events: string[] = [] - return { - events, - setPending: (value) => events.push(`pending:${value}`), - applyStatePatch: (state) => events.push(`patch:${state}`), - onMutated: () => events.push('reread') - } -} - -describe('PR page actions reconcile after failed writes', () => { - afterEach(() => { - vi.unstubAllGlobals() - vi.clearAllMocks() - }) - - it('rereads after a returned merge failure without applying the merged patch', async () => { - const mergePR = vi.fn().mockResolvedValue({ ok: false, error: 'Connection lost' }) - vi.stubGlobal('window', { api: { gh: { mergePR } } }) - const recorder = recordEvents() - - await mergePullRequest({ - mergeDisabled: false, - method: 'squash', - item, - repoPath: '/repo', - repoId: 'repo-1', - prRepo: null, - mergeTarget: { kind: 'local' }, - confirm: vi.fn().mockResolvedValue(true), - setMergePending: recorder.setPending, - applyStatePatch: recorder.applyStatePatch, - onMutated: recorder.onMutated - }) - - expect(mergePR).toHaveBeenCalledTimes(1) - expect(mocks.toastError).toHaveBeenCalledWith('Connection lost') - expect(mocks.toastSuccess).not.toHaveBeenCalled() - expect(recorder.events).toEqual(['pending:true', 'pending:false', 'reread']) - }) - - it('rereads after reverting a thrown close', async () => { - mocks.runPullRequestStateUpdate.mockRejectedValue(new Error('Connection lost')) - const recorder = recordEvents() - - await changePullRequestState({ - canMutateState: true, - statePending: false, - nextState: 'closed', - localState: 'open', - item, - repoPath: '/repo', - repoId: 'repo-1', - projectOrigin: undefined, - prRepo: null, - confirm: vi.fn().mockResolvedValue(true), - setStatePending: recorder.setPending, - applyStatePatch: recorder.applyStatePatch, - onMutated: recorder.onMutated - }) - - expect(mocks.toastError).toHaveBeenCalledWith('Connection lost') - expect(recorder.events).toEqual([ - 'pending:true', - 'patch:closed', - 'patch:open', - 'pending:false', - 'reread' - ]) - }) -}) diff --git a/src/renderer/src/components/pull-request-page/actions/merge-actions.ts b/src/renderer/src/components/pull-request-page/actions/merge-actions.ts index 38db102669b..5cebe128a25 100644 --- a/src/renderer/src/components/pull-request-page/actions/merge-actions.ts +++ b/src/renderer/src/components/pull-request-page/actions/merge-actions.ts @@ -9,12 +9,10 @@ import { getPullRequestMergeMethodLabel, getPullRequestStateConfirmationTitle } from '@/components/github/pull-request-confirmation-copy' -import type { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods' -import type { - GitHubOwnerRepo, - GitHubPRMergeMethod -} from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { PullRequestPageProjectOrigin } from '../page-types' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/pull-request-page/actions/panel.tsx b/src/renderer/src/components/pull-request-page/actions/panel.tsx index 83a1af9ccc3..2a84fb07d8e 100644 --- a/src/renderer/src/components/pull-request-page/actions/panel.tsx +++ b/src/renderer/src/components/pull-request-page/actions/panel.tsx @@ -23,11 +23,11 @@ import { cn } from '@/lib/class-names' import { useAppStore } from '@/store/index' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { presentGitHubPRMergeState } from '@/components/github-pr-merge-state' -import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods' +import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { useTranslate } from '@/i18n/use-translate' import { getPullRequestMergeMethodLabel } from '@/components/github/pull-request-confirmation-copy' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { PullRequestPageProjectOrigin } from '../page-types' import { WorkItemStateBadge } from '../presentation/state-badge' diff --git a/src/renderer/src/components/pull-request-page/cache/file-content.test.ts b/src/renderer/src/components/pull-request-page/cache/file-content.test.ts deleted file mode 100644 index adba59de67a..00000000000 --- a/src/renderer/src/components/pull-request-page/cache/file-content.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { GitHubPRFileContents } from '../../../../../shared/github/pull-request-types' -import { evictPRFileContentRequest, loadPRFileContents } from './file-content' - -const args = { - repoPath: '/repo', - repoId: 'repo-id', - prNumber: 42, - file: { - path: 'src/file.ts', - status: 'modified' as const, - additions: 1, - deletions: 1, - isBinary: false - }, - headSha: 'head', - baseSha: 'base' -} - -function deferredContents(): { - promise: Promise - resolve: (contents: GitHubPRFileContents) => void -} { - let resolve!: (contents: GitHubPRFileContents) => void - const promise = new Promise((resolvePromise) => { - resolve = resolvePromise - }) - return { promise, resolve } -} - -describe('PR file content cache', () => { - const prFileContents = vi.fn() - - beforeEach(() => { - prFileContents.mockReset() - vi.stubGlobal('window', { api: { gh: { prFileContents } } }) - }) - - it('starts a new request after a pending request is evicted', async () => { - const first = deferredContents() - const retry = deferredContents() - prFileContents.mockReturnValueOnce(first.promise).mockReturnValueOnce(retry.promise) - - const firstRequest = loadPRFileContents(args) - expect(loadPRFileContents(args)).toBe(firstRequest) - - evictPRFileContentRequest(args, firstRequest) - const retryRequest = loadPRFileContents(args) - - expect(retryRequest).not.toBe(firstRequest) - expect(prFileContents).toHaveBeenCalledTimes(2) - - first.resolve({ - original: 'stale', - modified: 'stale', - originalIsBinary: false, - modifiedIsBinary: false - }) - await firstRequest - - expect(loadPRFileContents(args)).toBe(retryRequest) - - retry.resolve({ - original: 'fresh', - modified: 'fresh', - originalIsBinary: false, - modifiedIsBinary: false - }) - await expect(retryRequest).resolves.toMatchObject({ modified: 'fresh' }) - }) -}) diff --git a/src/renderer/src/components/pull-request-page/cache/file-content.ts b/src/renderer/src/components/pull-request-page/cache/file-content.ts index 647e15a164d..d44d10e4996 100644 --- a/src/renderer/src/components/pull-request-page/cache/file-content.ts +++ b/src/renderer/src/components/pull-request-page/cache/file-content.ts @@ -1,11 +1,14 @@ +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../../shared/github/pull-request-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +} from '../../../../../shared/github/pull-requests/pull-request-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' + +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { getGitHubRuntimeRepoId, diff --git a/src/renderer/src/components/pull-request-page/checks/details-request.ts b/src/renderer/src/components/pull-request-page/checks/details-request.ts index ec258b68b6e..6c6fb1fc32f 100644 --- a/src/renderer/src/components/pull-request-page/checks/details-request.ts +++ b/src/renderer/src/components/pull-request-page/checks/details-request.ts @@ -5,7 +5,7 @@ import { normalizeCheckRunDetailsLogTails } from '@/lib/source-control/checks/ch import { GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE, isGitHubCheckDetailsTimeout -} from '../../../../../shared/github/check-details-deadline' +} from '../../../../../shared/github/checks/check-details-deadline' import { beginGitHubChecksTabDetails, settleGitHubChecksTabDetails, @@ -17,9 +17,9 @@ import { type GitHubRuntimeHost } from '@/lib/work-items/github/github-source-runtime-context' import { translate } from '@/i18n/i18n' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' export function requestPullRequestCheckDetails(args: { diff --git a/src/renderer/src/components/pull-request-page/checks/details.tsx b/src/renderer/src/components/pull-request-page/checks/details.tsx index aaf07e57ead..62e0f26b9b5 100644 --- a/src/renderer/src/components/pull-request-page/checks/details.tsx +++ b/src/renderer/src/components/pull-request-page/checks/details.tsx @@ -11,7 +11,7 @@ import { } from '@/components/github/pr-check-presentation' import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { assignUniqueListKeys } from './details-list-keys' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/pull-request-page/checks/fix-dialog.tsx b/src/renderer/src/components/pull-request-page/checks/fix-dialog.tsx index e436ebb835a..56dde07088a 100644 --- a/src/renderer/src/components/pull-request-page/checks/fix-dialog.tsx +++ b/src/renderer/src/components/pull-request-page/checks/fix-dialog.tsx @@ -5,7 +5,7 @@ import { SourceControlAgentActionDialog } from '@/components/right-sidebar/sourc import { readSourceControlLaunchRecipeAgentId } from '../../../../../shared/source-control/ai/source-control-launch-agent-selection' import type { SourceControlActionRecipe } from '../../../../../shared/source-control/ai/recipes/source-control-ai-actions' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { saveFixChecksActionDefault, startFixChecksFromDialog } from './fix-launch' export function ChecksFixDialog({ diff --git a/src/renderer/src/components/pull-request-page/checks/fix-launch.ts b/src/renderer/src/components/pull-request-page/checks/fix-launch.ts index d6279ea6bdc..cbbf22f6028 100644 --- a/src/renderer/src/components/pull-request-page/checks/fix-launch.ts +++ b/src/renderer/src/components/pull-request-page/checks/fix-launch.ts @@ -15,8 +15,8 @@ import type { SourceControlLaunchActionId } from '../../../../../shared/source-control/ai/recipes/source-control-ai-actions' import { translate } from '@/i18n/i18n' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' type StoreState = ReturnType diff --git a/src/renderer/src/components/pull-request-page/checks/layouts.tsx b/src/renderer/src/components/pull-request-page/checks/layouts.tsx index f0c538c85f9..bbccfb943d8 100644 --- a/src/renderer/src/components/pull-request-page/checks/layouts.tsx +++ b/src/renderer/src/components/pull-request-page/checks/layouts.tsx @@ -1,4 +1,4 @@ -import type { ProviderCheckSummary } from '../../../../../shared/github/pull-request-types' +import type { ProviderCheckSummary } from '../../../../../shared/github/pull-requests/pull-request-types' import { PRCheckEmptyDetails } from '@/components/pr-check-empty-details' import React from 'react' import { LoaderCircle } from 'lucide-react' @@ -6,7 +6,7 @@ import { cn } from '@/lib/class-names' import { CHECK_COLOR } from '@/components/right-sidebar/checks-panel/presentation/check-presentation' import { getCheckCountChips, type getCheckCounts } from '@/components/pr-check-counts' import { getCheckDetailsKey } from '@/components/github/pr-check-presentation' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { useTranslate } from '@/i18n/use-translate' export function ChecksTabLayouts({ diff --git a/src/renderer/src/components/pull-request-page/checks/refresh.ts b/src/renderer/src/components/pull-request-page/checks/refresh.ts index fd647303677..e00c7c3f392 100644 --- a/src/renderer/src/components/pull-request-page/checks/refresh.ts +++ b/src/renderer/src/components/pull-request-page/checks/refresh.ts @@ -11,9 +11,9 @@ import { type GitHubChecksTabState } from '@/components/github-checks-tab-state' import { translate } from '@/i18n/i18n' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' export async function refreshPullRequestChecks(args: { diff --git a/src/renderer/src/components/pull-request-page/checks/rerun.ts b/src/renderer/src/components/pull-request-page/checks/rerun.ts index b422731ef80..dfccf1efd66 100644 --- a/src/renderer/src/components/pull-request-page/checks/rerun.ts +++ b/src/renderer/src/components/pull-request-page/checks/rerun.ts @@ -5,9 +5,9 @@ import { type GitHubRuntimeHost } from '@/lib/work-items/github/github-source-runtime-context' import { translate } from '@/i18n/i18n' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { GitHubChecksTabState } from '../../github-checks-tab-state' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/components/pull-request-page/checks/row.tsx b/src/renderer/src/components/pull-request-page/checks/row.tsx index f7b9027398c..423a7c1c0ea 100644 --- a/src/renderer/src/components/pull-request-page/checks/row.tsx +++ b/src/renderer/src/components/pull-request-page/checks/row.tsx @@ -8,7 +8,7 @@ import { import { getCheckConclusion } from '@/components/pr-check-counts' import { getCheckDetailsKey, getCheckStatusLabel } from '@/components/github/pr-check-presentation' import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { CheckDetailsPanel } from './details' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/pull-request-page/checks/tab.tsx b/src/renderer/src/components/pull-request-page/checks/tab.tsx index 6d9f51e2b2e..55aea01f13b 100644 --- a/src/renderer/src/components/pull-request-page/checks/tab.tsx +++ b/src/renderer/src/components/pull-request-page/checks/tab.tsx @@ -1,4 +1,4 @@ -import { isCompletePRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import { isCompletePRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import { usePRCheckRefreshOwner } from '@/hooks/use-pr-check-refresh-owner' import { getChecksTabSummaryPresentation } from '@/components/github-item-dialog/inspect-pull-request/checks-tab-summary' import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' @@ -7,9 +7,10 @@ import { useMountedRef } from '@/hooks/useMountedRef' import { createGitHubChecksTabState, resolveGitHubChecksTabState, - toggleGitHubChecksTabExpandedKey + toggleGitHubChecksTabExpandedKey, + type GitHubChecksContextOwner } from '@/components/github-checks-tab-state' -import type { GitHubChecksContextOwner } from '@/components/github-checks-tab-state' + import { getCheckDetailsKey } from '@/components/github/pr-check-presentation' import { getCheckCounts, @@ -18,9 +19,12 @@ import { } from '@/components/pr-check-counts' import { getBrokenChecks } from '../../../../../shared/source-control/checks/pr-checks-fix-prompt' import { sortChecksBySeverity } from '../../../../../shared/source-control/checks/pr-check-severity-order' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import { + getTaskSourceCacheScope, + type TaskSourceContext +} from '../../../../../shared/work-items/task-source-context' + import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { canUseGitHubRepoContext, @@ -42,8 +46,8 @@ import { useTranslate } from '@/i18n/use-translate' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +} from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' export function ChecksTab({ item, diff --git a/src/renderer/src/components/pull-request-page/comments/composer.tsx b/src/renderer/src/components/pull-request-page/comments/composer.tsx index 4aa3280a7fa..9e3563a51cb 100644 --- a/src/renderer/src/components/pull-request-page/comments/composer.tsx +++ b/src/renderer/src/components/pull-request-page/comments/composer.tsx @@ -15,8 +15,8 @@ import { import { addIssueCommentForRepo } from '@/components/github/github-work-item-comment-mutations' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { MentionOption } from '../page-types' import { MentionTextarea } from '../mentions/textarea' diff --git a/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx b/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx index 4dfb096679c..aacf1ffc418 100644 --- a/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx @@ -15,8 +15,9 @@ import { CommentCodeContext } from '@/components/github/CommentCodeContext' import { CommentReactions } from '@/components/github/CommentReactions' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { MentionOption } from '../page-types' import { loadPRFileContents } from '../cache/file-content' diff --git a/src/renderer/src/components/pull-request-page/conversation/comment-group.tsx b/src/renderer/src/components/pull-request-page/conversation/comment-group.tsx index cff7b48732d..9fcb15bfe61 100644 --- a/src/renderer/src/components/pull-request-page/conversation/comment-group.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/comment-group.tsx @@ -13,8 +13,9 @@ import { type PRCommentGroup } from '../../../../../shared/source-control/review-comments/pr-comment-groups' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { MentionOption } from '../page-types' import { ConversationCommentCard } from './comment-card' diff --git a/src/renderer/src/components/pull-request-page/conversation/comments-list.tsx b/src/renderer/src/components/pull-request-page/conversation/comments-list.tsx index e8439d9e593..2f1e01096a2 100644 --- a/src/renderer/src/components/pull-request-page/conversation/comments-list.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/comments-list.tsx @@ -10,8 +10,9 @@ import { type PRCommentGroup } from '../../../../../shared/source-control/review-comments/pr-comment-groups' import type { PRCommentAudienceFilter } from '../../../../../shared/source-control/review-comments/pr-comment-audience' -import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { MentionOption } from '../page-types' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/pull-request-page/conversation/mention-error.test.tsx b/src/renderer/src/components/pull-request-page/conversation/mention-error.test.tsx deleted file mode 100644 index a69bf4d0d7d..00000000000 --- a/src/renderer/src/components/pull-request-page/conversation/mention-error.test.tsx +++ /dev/null @@ -1,219 +0,0 @@ -// @vitest-environment happy-dom -import React, { act } from 'react' -import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { useAppStore } from '@/store' -import type { AppState } from '@/store/types' -import { i18n, loadRendererUiLanguage } from '@/i18n/i18n' -import { ConfirmationDialogProvider } from '@/components/confirmation-dialog' -import { TooltipProvider } from '@/components/ui/tooltip' -import { GitHubWorkItemTargetContext } from '@/components/github-item-dialog/load-item-details/github-work-item-target-context' -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../../shared/github/work-item-target' -import { GITHUB_EXACT_WORK_ITEM_TARGET_RUNTIME_CAPABILITY } from '../../../../../shared/source-control/runtime/source-control-runtime-capabilities' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' -import type { Repo } from '../../../../../shared/repo-types' -import { ConversationTab } from './tab' - -const io = vi.hoisted(() => ({ rpc: vi.fn() })) -vi.mock('@/runtime/rpc/calls/runtime-rpc-call', () => ({ - callRuntimeRpc: io.rpc -})) -vi.mock('@/runtime/rpc/compatibility/runtime-compatibility-cache', () => ({ - clearRuntimeCompatibilityCache: vi.fn(), - clearRecentRuntimeCompatibilityFailure: vi.fn() -})) -vi.mock('@/store', async () => { - const { create } = await import('zustand') - const { getDefaultSettings } = await import('../../../../../shared/constants') - return { - useAppStore: create(() => ({ - settings: getDefaultSettings('/mention-fixture'), - repos: [], - projects: [], - projectHostSetups: [], - folderWorkspacePathStatuses: {}, - ...({ - sshConnectionStates: new Map(), - sshStateByEnvironment: new Map(), - runtimeStatusByEnvironmentId: new Map() - } satisfies Pick< - AppState, - 'sshConnectionStates' | 'sshStateByEnvironment' | 'runtimeStatusByEnvironmentId' - >), - patchWorkItem: vi.fn(), - patchProjectRowContent: vi.fn(), - recordFeatureInteraction: vi.fn(), - setContextualToursBlockingSurfaceVisible: vi.fn() - })) - } -}) - -let fixtureNumber = 0 -let environmentId: string -let capable: boolean -let assignableUsers: GitHubAssignableUser[] -let targetContext: React.ContextType -const repository = { host: 'github.corp', owner: 'fork', repo: 'widgets' } -const item: GitHubWorkItem = { - id: 'pr:93', - repoId: 'clone', - type: 'pr', - number: 93, - title: 'Selected pull request', - state: 'open', - labels: [], - author: 'pr-author', - updatedAt: '2026-10-29T12:00:00Z', - url: 'https://github.corp/fork/widgets/pull/93', - prRepo: repository -} -function repo(user = 'A'): Repo { - return { - id: 'clone', - path: '/clone', - displayName: 'clone', - badgeColor: '', - addedAt: 0, - executionHostId: `runtime:${environmentId}`, - ghAccount: { host: repository.host, user } - } -} - -beforeEach(async () => { - environmentId = `mention-fixture-${++fixtureNumber}` - targetContext = { - resource: { repository, type: 'pr', number: item.number }, - target: { kind: 'environment', environmentId } - } - capable = false - assignableUsers = [] - io.rpc.mockReset() - io.rpc.mockImplementation( - async ( - target: RuntimeClientTarget, - method: string, - _params: unknown, - options?: { beforeSend?: () => void } - ) => { - expect(target).toEqual({ kind: 'environment', environmentId }) - options?.beforeSend?.() - if (method === 'status.get') { - return { - runtimeId: environmentId, - capabilities: capable ? [GITHUB_EXACT_WORK_ITEM_TARGET_RUNTIME_CAPABILITY] : [] - } - } - if (method === 'github.project.listAssignableUsersBySlug') { - return { ok: true, users: [] } - } - if (method === 'github.listAssignableUsers') { - return assignableUsers - } - throw new Error(`Unexpected Conversation I/O: ${method}`) - } - ) - useAppStore.setState({ repos: [repo()] }) - await i18n.changeLanguage(await loadRendererUiLanguage('en')) -}) - -afterEach(async () => { - cleanup() - await i18n.changeLanguage(await loadRendererUiLanguage('en')) - vi.restoreAllMocks() -}) - -function mountConversation(onKeyDown?: React.KeyboardEventHandler) { - return render( - - - -
- -
-
-
-
- ) -} - -function edit(input: HTMLTextAreaElement, value: string) { - fireEvent.change(input, { - target: { value, selectionStart: value.length, selectionEnd: value.length } - }) -} - -it('recovers the same Conversation draft through real owner metadata and keeps no-error empty matches collapsed', async () => { - mountConversation() - const input = screen.getByPlaceholderText('Add a comment…') - const unmatched = 'Keep this @zzzz36172nomatch' - edit(input, unmatched) - await screen.findByText(GITHUB_EXACT_OPERATION_UNSUPPORTED) - capable = true - assignableUsers = ['team-one', 'team-two'].map((login) => ({ login, name: null, avatarUrl: '' })) - await act(async () => { - useAppStore.setState({ repos: [repo('B')] }) - }) - await waitFor(() => - expect(io.rpc.mock.calls.some((call) => call[1] === 'github.listAssignableUsers')).toBe(true) - ) - await waitFor(() => expect(screen.queryByText(GITHUB_EXACT_OPERATION_UNSUPPORTED)).toBeNull()) - expect(screen.getByPlaceholderText('Add a comment…')).toBe(input) - expect(input.value).toBe(unmatched) - expect(screen.queryByRole('listbox')).toBeNull() - expect(input.getAttribute('aria-expanded')).toBe('false') - expect(input.hasAttribute('aria-activedescendant')).toBe(false) - edit(input, 'No mention query') - expect(screen.queryByRole('listbox')).toBeNull() - edit(input, 'Keep this @team') - const listbox = await screen.findByRole('listbox') - const options = within(listbox).getAllByRole('option') - expect(options).toHaveLength(2) - expect(within(options[0]).getByText('Team member')).toBeTruthy() - expect(input.getAttribute('aria-activedescendant')).toBe(options[0].id) - expect(fireEvent.keyDown(input, { key: 'ArrowDown' })).toBe(false) - expect(input.getAttribute('aria-activedescendant')).toBe(options[1].id) - expect(options[1].getAttribute('aria-selected')).toBe('true') - expect(fireEvent.keyDown(input, { key: 'Tab' })).toBe(false) - expect(input.value).toBe('Keep this @team-two') - expect(screen.queryByRole('listbox')).toBeNull() - expect(screen.getByRole('button', { name: 'Send comment' }).disabled).toBe( - false - ) - expect( - io.rpc.mock.calls - .filter((call) => call[1] === 'github.listAssignableUsers') - .map((call) => call[2]) - ).toEqual([{ repo: 'clone', ownerRepo: repository }]) -}) diff --git a/src/renderer/src/components/pull-request-page/conversation/reply.ts b/src/renderer/src/components/pull-request-page/conversation/reply.ts index 1b67f437a34..03090ef6d17 100644 --- a/src/renderer/src/components/pull-request-page/conversation/reply.ts +++ b/src/renderer/src/components/pull-request-page/conversation/reply.ts @@ -9,9 +9,9 @@ import { addPRReviewCommentReplyForRepo } from '@/components/github/github-work-item-comment-mutations' import { translate } from '@/i18n/i18n' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/components/pull-request-page/conversation/tab.tsx b/src/renderer/src/components/pull-request-page/conversation/tab.tsx index adfe3a42e67..eddf4a7fe4a 100644 --- a/src/renderer/src/components/pull-request-page/conversation/tab.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/tab.tsx @@ -20,10 +20,8 @@ import { import { groupPRComments } from '../../../../../shared/source-control/review-comments/pr-comment-groups' import { resolveCommentReplyTarget } from '@/components/comment-reply-target-state' import { runWorkItemBodyUpdate } from '@/components/github/github-work-item-edit-mutations' -import { - parseOwnerRepoFromItemUrl, - resolvePullRequestRepo -} from '@/components/github/github-work-item-identity' +import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' import { resolveGitHubBodyDraft, shouldSyncGitHubBodyDraft @@ -32,13 +30,13 @@ import type { TaskSourceContext } from '../../../../../shared/work-items/task-so import type { GitHubAssignableUser, GitHubPRFile -} from '../../../../../shared/github/pull-request-types' +} from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +} from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/pull-request-page/edit/issue-updates.ts b/src/renderer/src/components/pull-request-page/edit/issue-updates.ts index 658b428fcfc..e37a53489d3 100644 --- a/src/renderer/src/components/pull-request-page/edit/issue-updates.ts +++ b/src/renderer/src/components/pull-request-page/edit/issue-updates.ts @@ -1,6 +1,6 @@ import { toast } from 'sonner' import { runIssueUpdate } from '@/components/github/github-work-item-edit-mutations' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { PullRequestPageProjectOrigin } from '../page-types' diff --git a/src/renderer/src/components/pull-request-page/edit/section.tsx b/src/renderer/src/components/pull-request-page/edit/section.tsx index a99cb40c564..2078aace747 100644 --- a/src/renderer/src/components/pull-request-page/edit/section.tsx +++ b/src/renderer/src/components/pull-request-page/edit/section.tsx @@ -9,7 +9,7 @@ import { useImmediateMutation } from '@/hooks/useIssueMetadata' import { useRepoAssignees, useRepoLabels } from '@/hooks/useGitHubRepoMetadata' import { useRepoAssigneesBySlug, useRepoLabelsBySlug } from '@/hooks/useGitHubSlugMetadata' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { getStateLabel } from '@/components/github/work-item-state-presentation' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx b/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx index 17adf04787d..84dc788944c 100644 --- a/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx +++ b/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx @@ -24,7 +24,7 @@ import { gitHubPRFileToBranchEntry, type PRFilesCombinedDiffViewerProps } from '@/components/github/pr-file-diff-mapping' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { LOCAL_EXECUTION_HOST_ID } from '../../../../../shared/execution-host' import { prFilesDiffScrollTopCache, prFilesDiffViewStateCache } from '../cache/files-diff-view' import { PRFilesDiffToolbar } from './toolbar' diff --git a/src/renderer/src/components/pull-request-page/files/inline-comments.ts b/src/renderer/src/components/pull-request-page/files/inline-comments.ts index b1b0a982620..bf6b9e8eefa 100644 --- a/src/renderer/src/components/pull-request-page/files/inline-comments.ts +++ b/src/renderer/src/components/pull-request-page/files/inline-comments.ts @@ -1,7 +1,7 @@ import type { DecoratedDiffComment } from '@/components/diff-comments/decorated-diff-comment' import { formatRelativeTime } from '@/components/github/work-item-state-presentation' -import type { PRComment } from '../../../../../shared/github/comment-types' -import { getPRInlineCommentPlacement } from '../../../../../shared/github/pr-inline-comment-placement' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import { getPRInlineCommentPlacement } from '../../../../../shared/github/comments/pr-inline-comment-placement' export function buildInlineReviewComments( comments: PRComment[], diff --git a/src/renderer/src/components/pull-request-page/files/line-comment.ts b/src/renderer/src/components/pull-request-page/files/line-comment.ts index c569a09c13b..5d2e3b42c22 100644 --- a/src/renderer/src/components/pull-request-page/files/line-comment.ts +++ b/src/renderer/src/components/pull-request-page/files/line-comment.ts @@ -2,8 +2,8 @@ import { toast } from 'sonner' import { addPRReviewCommentForRepo } from '@/components/github/github-work-item-comment-mutations' import { translate } from '@/i18n/i18n' import type { DiffSection } from '@/components/editor/diff/sections/diff-section-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/components/pull-request-page/files/section-loader.ts b/src/renderer/src/components/pull-request-page/files/section-loader.ts index 09f67a3ae03..0e7b39e2d6d 100644 --- a/src/renderer/src/components/pull-request-page/files/section-loader.ts +++ b/src/renderer/src/components/pull-request-page/files/section-loader.ts @@ -11,11 +11,11 @@ import { } from '@/components/editor/diff/large-files/large-diff-section-content' import type { DiffSection } from '@/components/editor/diff/sections/diff-section-types' import type { GitDiffResult } from '../../../../../shared/git/diff/git-diff-compare-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { - GitHubOwnerRepo, GitHubPRFile, GitHubPRFileContents -} from '../../../../../shared/github/pull-request-types' +} from '../../../../../shared/github/pull-requests/pull-request-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { evictPRFileContentRequest, loadPRFileContents } from '../cache/file-content' diff --git a/src/renderer/src/components/pull-request-page/files/toolbar.tsx b/src/renderer/src/components/pull-request-page/files/toolbar.tsx index 5097eab7fbc..40cf35317df 100644 --- a/src/renderer/src/components/pull-request-page/files/toolbar.tsx +++ b/src/renderer/src/components/pull-request-page/files/toolbar.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { isPRFileViewed } from '@/components/github/pr-file-content-size' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubPRFile } from '../../../../../shared/github/pull-request-types' +import type { GitHubPRFile } from '../../../../../shared/github/pull-requests/pull-request-types' export function PRFilesDiffToolbar({ files, diff --git a/src/renderer/src/components/pull-request-page/mentions/options.ts b/src/renderer/src/components/pull-request-page/mentions/options.ts index 7ace9a8b8d7..568ec66fd09 100644 --- a/src/renderer/src/components/pull-request-page/mentions/options.ts +++ b/src/renderer/src/components/pull-request-page/mentions/options.ts @@ -1,6 +1,6 @@ -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' import type { MentionOption } from '../page-types' diff --git a/src/renderer/src/components/pull-request-page/page-types.ts b/src/renderer/src/components/pull-request-page/page-types.ts index 61205268092..47d25fabba9 100644 --- a/src/renderer/src/components/pull-request-page/page-types.ts +++ b/src/renderer/src/components/pull-request-page/page-types.ts @@ -1,5 +1,5 @@ -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' import type { GitHubWorkItemProjectOrigin, diff --git a/src/renderer/src/components/pull-request-page/page/header.tsx b/src/renderer/src/components/pull-request-page/page/header.tsx index a19de7e5853..cf5e8594d61 100644 --- a/src/renderer/src/components/pull-request-page/page/header.tsx +++ b/src/renderer/src/components/pull-request-page/page/header.tsx @@ -20,11 +20,11 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' -import { parseOwnerRepoFromItemUrl } from '@/components/github/github-work-item-identity' +import { parseGitHubItemRepository as parseOwnerRepoFromItemUrl } from '../../../../../shared/github/work-items/work-item-target' import { formatRelativeTime, getStateLabel } from '@/components/github/work-item-state-presentation' import { cn } from '@/lib/class-names' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getSolidStateTone } from '../presentation/state-badge' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/pull-request-page/page/surface.tsx b/src/renderer/src/components/pull-request-page/page/surface.tsx index 15602e80dd4..5bbacc18b36 100644 --- a/src/renderer/src/components/pull-request-page/page/surface.tsx +++ b/src/renderer/src/components/pull-request-page/page/surface.tsx @@ -21,7 +21,7 @@ import { } from '@/components/github-link-copy-state' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { PullRequestPageProps } from '../page-types' import { GitHubWorkItemTargetContext } from '@/components/github-item-dialog/load-item-details/github-work-item-target-context' import { useGitHubWorkItemDetails } from '@/components/github-item-dialog/load-item-details/use-github-work-item-details' diff --git a/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx b/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx index a3284037fab..00042544c5f 100644 --- a/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx +++ b/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx @@ -10,13 +10,13 @@ import { useTranslate } from '@/i18n/use-translate' import type { GitHubAssignableUser, GitHubPRFile -} from '../../../../../shared/github/pull-request-types' +} from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRComment } from '../../../../../shared/github/comment-types' +} from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { PullRequestPageProjectOrigin } from '../page-types' import { diff --git a/src/renderer/src/components/pull-request-page/presentation/state-badge.tsx b/src/renderer/src/components/pull-request-page/presentation/state-badge.tsx index e50b76344e4..f2c1b3aa770 100644 --- a/src/renderer/src/components/pull-request-page/presentation/state-badge.tsx +++ b/src/renderer/src/components/pull-request-page/presentation/state-badge.tsx @@ -3,7 +3,7 @@ import { getWorkItemStateTone, WorkItemStateBadge as SharedWorkItemStateBadge } from '@/components/github/work-item-state-badge' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' export function getStateTone(item: GitHubWorkItem): string { return getWorkItemStateTone(item, 'destructive') diff --git a/src/renderer/src/components/pull-request-page/reviewers/panel.tsx b/src/renderer/src/components/pull-request-page/reviewers/panel.tsx index 960b20ad4f3..d2b8d36592a 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/panel.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/panel.tsx @@ -4,11 +4,11 @@ import { useAppStore } from '@/store' import { useRepoAssignees } from '@/hooks/useGitHubRepoMetadata' import { useRepoAssigneesBySlug } from '@/hooks/useGitHubSlugMetadata' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' -import { getGitHubPRReviewerRows } from '../../../../../shared/github/pr-reviewer-display' +import { getGitHubPRReviewerRows } from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { mergeReviewerSuggestions } from '@/components/github/work-item-state-presentation' import { filterGitHubPRReviewerCandidates, diff --git a/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx b/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx index d4206306ba8..53ed41d70bc 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx @@ -3,7 +3,7 @@ import { Check } from 'lucide-react' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' export function ReviewerPickerRow({ id, diff --git a/src/renderer/src/components/pull-request-page/reviewers/picker.tsx b/src/renderer/src/components/pull-request-page/reviewers/picker.tsx index 916a5253311..5c9e4c53084 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/picker.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/picker.tsx @@ -3,7 +3,7 @@ import { LoaderCircle, Pencil } from 'lucide-react' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { ReviewerPickerRow } from './picker-row' export function ReviewerPicker({ diff --git a/src/renderer/src/components/pull-request-page/reviewers/request-actions.ts b/src/renderer/src/components/pull-request-page/reviewers/request-actions.ts index 2c13ff20a06..bf2099431b5 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/request-actions.ts +++ b/src/renderer/src/components/pull-request-page/reviewers/request-actions.ts @@ -3,16 +3,14 @@ import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { normalizeGitHubReviewerLogins, parseGitHubReviewerInputLogins -} from '../../../../../shared/github/pr-reviewer-display' +} from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { notifyWorkItemDetailsMutation } from '@/components/github/github-work-item-comment-mutations' import { buildRequestedReviewUsers } from '@/components/github/work-item-state-presentation' import { getGitHubRuntimeRepoId } from '@/lib/work-items/github/github-source-runtime-context' import { translate } from '@/i18n/i18n' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/components/pull-request-page/reviewers/requested-list.tsx b/src/renderer/src/components/pull-request-page/reviewers/requested-list.tsx index 3cff92a8933..0bf33d4d4ed 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/requested-list.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/requested-list.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ReviewerAvatar } from '@/components/github/work-item-state-presentation' import { useTranslate } from '@/i18n/use-translate' -import type { getGitHubPRReviewerRows } from '../../../../../shared/github/pr-reviewer-display' +import type { getGitHubPRReviewerRows } from '../../../../../shared/github/pull-requests/pr-reviewer-display' export function ReviewerRequestedList({ reviewers, diff --git a/src/renderer/src/components/repo/repo-fork-indicator.tsx b/src/renderer/src/components/repo/repo-fork-indicator.tsx index 27933daf223..77f20ca4635 100644 --- a/src/renderer/src/components/repo/repo-fork-indicator.tsx +++ b/src/renderer/src/components/repo/repo-fork-indicator.tsx @@ -1,6 +1,6 @@ import React from 'react' import { GitFork } from 'lucide-react' -import type { GitHubRepositoryIdentity } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../shared/github/repositories/repository-identity' import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip' import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx b/src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx index 94469ccad75..be983d25bd3 100644 --- a/src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx +++ b/src/renderer/src/components/right-sidebar/activity-bar-buttons.tsx @@ -1,7 +1,7 @@ import React from 'react' import { MoreHorizontal } from 'lucide-react' import type { ActiveRightSidebarTab } from '../../../../shared/ui-chrome-types' -import type { CheckStatus as CoarseCheckStatus } from '../../../../shared/github/pull-request-types' +import type { CheckStatus as CoarseCheckStatus } from '../../../../shared/github/pull-requests/pull-request-types' type CheckStatus = CoarseCheckStatus | 'cancelled' import { cn } from '@/lib/class-names' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.test.ts b/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.test.ts index a18d4d6114d..44538a94ddf 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.test.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from 'vitest' import { clearActiveChecksStatusCacheForTests, getActiveChecksStatus } from './active-checks-status' import type { AppState } from '../../../store/types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' function makePR(status: PRInfo['checksStatus']): PRInfo { return { diff --git a/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.ts b/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.ts index 5cbb6e7b937..55793ea194e 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/active-checks-status.ts @@ -3,9 +3,9 @@ import { getProviderChecksOutcome } from '../../../../../shared/source-control/c import type { AppState } from '../../../store/types' import { getIndexedRepoMap } from '../../../store/repos/repository-catalog-index' import { getIndexedWorktreeMap } from '../../../store/slices/worktrees/listing/worktree-catalog-index' -import type { CheckStatus as CoarseCheckStatus } from '../../../../../shared/github/pull-request-types' +import type { CheckStatus as CoarseCheckStatus } from '../../../../../shared/github/pull-requests/pull-request-types' type CheckStatus = CoarseCheckStatus | 'cancelled' -import { getGitHubPRCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey } from '../../../store/slices/hosted-review/hosted-review-cache-identity' import { isGitHubPRSuppressed } from '../../../../../shared/worktree/github-pr-suppression' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/checks-list-expanded-details.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel/checks-list-expanded-details.test.tsx index c91e105857d..a76151d7430 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/checks-list-expanded-details.test.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/checks-list-expanded-details.test.tsx @@ -4,7 +4,10 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { TooltipProvider } from '@/components/ui/tooltip' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../shared/github/checks/check-types' import { ChecksList } from './presentation/checks-list' globalThis.IS_REACT_ACT_ENVIRONMENT = true @@ -197,21 +200,3 @@ describe('ChecksList expanded check details', () => { expect(container.textContent).toContain('ERROR: Job failed: exit code 1') }) }) - -describe('ChecksList row state', () => { - it('names the state on the status icon instead of beside it', () => { - renderChecksList() - const row = container.querySelector('.group\\/check-row') - expect(row?.querySelector('[role="img"]')?.getAttribute('aria-label')).toBe('Failed') - expect(row?.textContent).toBe('verify') - }) - - it('gives a finished check without a conclusion the icon its label names', () => { - renderChecksList({ - checks: [{ name: 'external', status: 'completed', conclusion: null, url: null }] - }) - const icon = container.querySelector('[role="img"]') - expect(icon?.getAttribute('aria-label')).toBe('Neutral') - expect(icon?.querySelector('svg')?.getAttribute('class')).not.toContain('animate-spin') - }) -}) diff --git a/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review-state-model.ts b/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review-state-model.ts index c29fc17a8d7..37e6ba51a44 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review-state-model.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review-state-model.ts @@ -2,7 +2,7 @@ import type { HostedReviewCreationBlockedReason } from '../../../../../shared/so import type { GitHubPRRefreshSkippedReason, PRRefreshErrorType -} from '../../../../../shared/github/pull-request-refresh-types' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { ChecksPanelReviewLookup } from './checks-panel-review-lookup-authority' /** Refresh failures may preserve a confirmed composer but must never open a new one. */ diff --git a/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review.ts b/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review.ts index 4bd665b6330..1df48aa9aa2 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/checks-panel-review.ts @@ -1,4 +1,4 @@ -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import type { HostedReviewInfo } from '../../../../../shared/source-control/hosted-review/hosted-review-contracts' import { hostedReviewInfoFromGitHubPRInfo as gitHubPRToChecksPanelReview } from '../../../../../shared/source-control/hosted-review/hosted-review-github' import { isGitHubPRSuppressed } from '../../../../../shared/worktree/github-pr-suppression' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-controls.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-controls.tsx index 6bb0a8b7f5c..afdc6ff08b5 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-controls.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-controls.tsx @@ -24,7 +24,7 @@ import { basename } from '@/lib/path' import { isBotPRComment } from '../../../../../../shared/source-control/review-comments/pr-comment-audience' import { setPRBotAuthorOverride } from '@/lib/source-control/review/pr-bot-author-overrides' import { normalizePRCommentAuthorLogin } from '../../../../../../shared/source-control/review-comments/pr-bot-author-overrides' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' import type { PRCommentGroupActionState } from '@/lib/source-control/review/comments/pr-comment-action-state' import type { PRCommentPresentationClasses } from './pr-comment-presentation' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-group.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-group.tsx index 97550a88035..52b17b3102f 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-group.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-group.tsx @@ -21,7 +21,7 @@ import { import type { GitHubReactionContent, PRComment -} from '../../../../../../shared/github/comment-types' +} from '../../../../../../shared/github/comments/comment-types' import { getPRCommentGroupId, type PRCommentGroup diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-row.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-row.tsx index fff48a70414..32ebd47e725 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-row.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/comment-row.tsx @@ -13,7 +13,7 @@ import { isBotPRComment } from '../../../../../../shared/source-control/review-c import type { GitHubReactionContent, PRComment -} from '../../../../../../shared/github/comment-types' +} from '../../../../../../shared/github/comments/comment-types' import type { PRCommentGroupActionState } from '@/lib/source-control/review/comments/pr-comment-action-state' import type { PRCommentPresentationClasses } from './pr-comment-presentation' import { formatPrCommentRelativeTime } from '../../../../../../shared/source-control/review-comments/pr-comment-time' @@ -27,7 +27,7 @@ import { QueueForAgentButton, ResolveButton } from './comment-controls' -import { isMutablePRConversationComment } from '../../../../../../shared/github/pr-comment-mutability' +import { isMutablePRConversationComment } from '../../../../../../shared/github/comments/pr-comment-mutability' export function CommentRow({ imageUploadAddress, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comment-fixing-reply-body.ts b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comment-fixing-reply-body.ts index fe884eca640..6f04779d73e 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comment-fixing-reply-body.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comment-fixing-reply-body.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' /** Posted when a selected review comment is sent to AI. */ export const PR_COMMENT_AI_FIXING_REPLY = 'Fixing. Will be in the next commit' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.ts b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.ts index e6704d11153..5ddfafce48e 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.ts @@ -1,5 +1,5 @@ -import type { PRComment } from '../../../../../../shared/github/comment-types' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' import { getPRCommentGroupRoot as getPRCommentGroupReplyTarget, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx deleted file mode 100644 index 05c532296bb..00000000000 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx +++ /dev/null @@ -1,232 +0,0 @@ -// @vitest-environment happy-dom - -globalThis.IS_REACT_ACT_ENVIRONMENT = true - -import { act, StrictMode, type ReactNode } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { fireEvent, waitFor } from '@testing-library/react' -import { TooltipProvider } from '@/components/ui/tooltip' - -const uploads = vi.hoisted(() => ({ capture: vi.fn(), upload: vi.fn() })) -vi.mock('@/components/github/github-image-upload', () => ({ - captureGitHubImageUpload: uploads.capture -})) -vi.mock('@/components/ui/dropdown-menu', () => { - // Radix keeps the selection callback on the group, so the mocked items need it too. - let onRadioValueChange: ((value: string) => void) | undefined - return { - DropdownMenu: ({ children }: { children: ReactNode }) => <>{children}, - DropdownMenuContent: ({ children }: { children: ReactNode }) =>
{children}
, - DropdownMenuLabel: ({ children }: { children: ReactNode }) =>
{children}
, - DropdownMenuItem: ({ - children, - onSelect - }: { - children: ReactNode - onSelect?: (event: Event) => void - }) => ( - - ), - DropdownMenuRadioGroup: ({ - children, - onValueChange - }: { - children: ReactNode - onValueChange?: (value: string) => void - }) => { - onRadioValueChange = onValueChange - return <>{children} - }, - DropdownMenuRadioItem: ({ - children, - value, - onSelect - }: { - children: ReactNode - value?: string - onSelect?: (event: Event) => void - }) => ( - - ), - DropdownMenuSeparator: () =>
, - DropdownMenuTrigger: ({ children }: { children: ReactNode }) => <>{children} - } -}) -import type { PRComment } from '../../../../../../shared/github/comment-types' -import type { PRCommentGroup } from '../../../../../../shared/source-control/review-comments/pr-comment-groups' -import { - clearPRCommentsListSelectionsForTests, - type PRCommentsListSelectionClearRequest -} from './pr-comments-list-selection' -import { PRCommentsList } from './comments-list' - -let container: HTMLDivElement -let root: Root - -beforeEach(() => { - clearPRCommentsListSelectionsForTests() - container = document.createElement('div') - document.body.appendChild(container) - root = createRoot(container) -}) - -afterEach(() => { - act(() => { - root.unmount() - }) - container.remove() - clearPRCommentsListSelectionsForTests() -}) - -function comment(overrides: Partial): PRComment { - return { - id: 1, - author: 'alice', - authorAvatarUrl: '', - body: 'Please update this.', - createdAt: '2026-05-14T00:00:00Z', - url: 'https://github.com/acme/widgets/pull/42#discussion_r1', - ...overrides - } -} - -function renderList(props: { - comments: PRComment[] - contextKey?: string - strictMode?: boolean - onResolveSelectedCommentsWithAI?: (groups: PRCommentGroup[]) => void - clearRequest?: PRCommentsListSelectionClearRequest | null -}): void { - const list = ( - - - - ) - act(() => { - root.render(props.strictMode ? {list} : list) - }) -} - -function clickButton(label: string): void { - const button = - [...container.querySelectorAll('button')].find( - (candidate) => - candidate.textContent === label || candidate.getAttribute('aria-label') === label - ) ?? - [...container.querySelectorAll('button')].find( - (candidate) => - candidate.textContent?.includes(label) || - candidate.getAttribute('aria-label')?.includes(label) - ) - if (!button) { - const availableButtons = [...container.querySelectorAll('button')] - .map( - (candidate) => - candidate.getAttribute('aria-label') ?? candidate.textContent?.trim() ?? '' - ) - .join(', ') - throw new Error(`Button not found: ${label}. Available buttons: ${availableButtons}`) - } - act(() => { - button.dispatchEvent(new MouseEvent('click', { bubbles: true })) - }) -} - -function hasButton(label: string): boolean { - return [...container.querySelectorAll('button')].some( - (candidate) => - candidate.textContent?.includes(label) || - candidate.getAttribute('aria-label')?.includes(label) - ) -} - -describe('PRCommentsList comment resolution selection', () => { - it('keeps a clear request pending until its review context is mounted', () => { - const queuedComments = [ - comment({ - id: 1, - author: 'coderabbitai', - body: 'Review Change Stack. No actionable comments were generated.', - isBot: true - }) - ] - renderList({ comments: queuedComments, contextKey: 'review:42' }) - clickButton('Queue for agent') - - expect(hasButton('Send 1 queued comment to AI')).toBe(true) - - renderList({ - comments: [comment({ id: 2, body: 'Other review comment.' })], - contextKey: 'review:99', - clearRequest: { contextKey: 'review:42', token: 1 } - }) - renderList({ - comments: queuedComments, - contextKey: 'review:42', - clearRequest: { contextKey: 'review:42', token: 1 } - }) - - expect(hasButton('Send 1 queued comment to AI')).toBe(false) - expect(container.querySelector('button[role="checkbox"]')).toBeNull() - }) -}) - -it('routes an image from a resolved thread reply to that list owner', async () => { - const address = { - runtime: { kind: 'local' as const }, - target: { kind: 'repo' as const, repo: 'resolved-repo', source: 'origin' as const } - } - uploads.capture.mockReset().mockReturnValue(uploads.upload) - uploads.upload - .mockReset() - .mockResolvedValue('https://github.com/user-attachments/assets/resolved') - act(() => - root.render( - - - - ) - ) - clickButton('Resolved') - clickButton('Reply') - const textarea = container.querySelector('textarea') - if (!textarea) { - throw new Error('Reply field did not open') - } - fireEvent.paste(textarea, { - clipboardData: { files: [new File(['png'], 'reply.png', { type: 'image/png' })] } - }) - await waitFor(() => expect(uploads.capture).toHaveBeenCalledWith(address)) - await waitFor(() => - expect(textarea.value).toContain('https://github.com/user-attachments/assets/resolved') - ) -}) diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.ts b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.ts index 11426914894..81b07f322e8 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.ts @@ -5,7 +5,7 @@ import { type PRCommentGroup } from '../../../../../../shared/source-control/review-comments/pr-comment-groups' import { isPRCommentGroupQueueableForAI } from '@/lib/source-control/review/comments/pr-comment-action-state' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' export type PRCommentsListSelection = { isSelectingForAI: boolean diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.test.tsx index 3e3ea353ab6..b02a445eb53 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.test.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.test.tsx @@ -4,7 +4,7 @@ import { act, cleanup, renderHook } from '@testing-library/react' import type { SetStateAction } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { makeWorktree } from '@/store/slices/worktrees/worktree.test-fixture' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import { useChecksPanelCommentMutations } from './use-checks-panel-comment-mutations' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.tsx index 7ccd8d54ec7..535ab3e0057 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-mutations.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { forgeOwnerHostIdForWorkspace } from '@/runtime/source-control/forge/forge-credential-target' import { toast } from 'sonner' import { mergePRCommentIntoList } from '@/store/github/comments/pr-comment-cache' -import { githubProjectHost } from '../../../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../../../shared/github/projects/project-identity' import { setReactionOnSubject, restoreReactionOnSubject @@ -18,11 +18,11 @@ import type { ChecksPanelCommentResolutionState } from './use-checks-panel-comme import type { ChecksPanelComposerState } from '../use-checks-panel-composer-state' import type { ChecksPanelContextState } from '../use-checks-panel-context-state' import { checksPanelAsyncResultKey } from '../refresh/checks-panel-async-result-key' -import { isMutablePRConversationComment } from '../../../../../../shared/github/pr-comment-mutability' +import { isMutablePRConversationComment } from '../../../../../../shared/github/comments/pr-comment-mutability' import type { GitHubReactionContent, PRComment -} from '../../../../../../shared/github/comment-types' +} from '../../../../../../shared/github/comments/comment-types' import { translate } from '@/i18n/i18n' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' import { gitHubSlugApiForWorkspace } from '@/runtime/repository-github/github-slug-owner-api' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-resolution.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-resolution.tsx index 07dfa1e3c01..0d5bea25048 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-resolution.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-checks-panel-comment-resolution.tsx @@ -15,7 +15,7 @@ import type { ChecksPanelReviewState } from '../use-checks-panel-review-state' import { checksPanelAsyncResultKey } from '../refresh/checks-panel-async-result-key' import { resolveGitLabMRDiscussionForChecks } from '../gitlab-review-client' import { checksPanelReviewStableKey, clearPendingPRCommentAiAck } from './pr-comments-ai-launch-ack' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-comments-list-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-comments-list-state.tsx index 75f47541d91..611c4376824 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/comments/use-comments-list-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/comments/use-comments-list-state.tsx @@ -22,7 +22,7 @@ import { getPRCommentPresentationClasses } from './pr-comment-presentation' import type { GitHubReactionContent, PRComment -} from '../../../../../../shared/github/comment-types' +} from '../../../../../../shared/github/comments/comment-types' import { RightPanelCommentComposer, type RightPanelCommentSubmitResult diff --git a/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts b/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts index 4833019b889..499d3615519 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import type { GitLabDiscussionResolveResult, GitLabWorkItemDetails diff --git a/src/renderer/src/components/right-sidebar/checks-panel/logs/check-job-log-tail.tsx b/src/renderer/src/components/right-sidebar/checks-panel/logs/check-job-log-tail.tsx index 14390d5fb94..daf73e3a936 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/logs/check-job-log-tail.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/logs/check-job-log-tail.tsx @@ -4,7 +4,7 @@ import { useTranslate } from '@/i18n/use-translate' import { cn } from '@/lib/class-names' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import type { PRCheckJob } from '../../../../../../shared/github/check-types' +import type { PRCheckJob } from '../../../../../../shared/github/checks/check-types' import type { CheckDetailsReadingOwner } from '@/components/editor/check-details/reading/check-details-reading-owner' import { resolveStepOutcome } from '../../../../../../shared/source-control/checks/pr-check-step-status' import { useCheckLogReadingPosition } from './use-check-log-reading-position' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/logs/use-check-log-reading-position.ts b/src/renderer/src/components/right-sidebar/checks-panel/logs/use-check-log-reading-position.ts index 770c818f140..9dfb5185c20 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/logs/use-check-log-reading-position.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/logs/use-check-log-reading-position.ts @@ -1,5 +1,5 @@ import { useLayoutEffect, useMemo, useRef } from 'react' -import type { PRCheckJob } from '../../../../../../shared/github/check-types' +import type { PRCheckJob } from '../../../../../../shared/github/checks/check-types' import { scrollTopCache } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { checkLogDigest, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-details-model.ts b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-details-model.ts index 227226ba154..dd38188cb17 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-details-model.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-details-model.ts @@ -1,4 +1,7 @@ -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import { getCheckConclusion } from '../../../../../../shared/source-control/checks/pr-check-status-label' import { getIntlLocale } from '@/i18n/i18n' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-presentation.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-presentation.tsx index f624051bc98..c7b5ec0bfee 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-presentation.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-presentation.tsx @@ -9,8 +9,8 @@ import { CircleMinus, GitPullRequest } from 'lucide-react' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' export const PullRequestIcon = GitPullRequest diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-details.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-details.tsx index dd6fe729dc3..53ebd424e25 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-details.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-details.tsx @@ -5,8 +5,8 @@ import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import { cn } from '@/lib/class-names' import { useAppStore } from '@/store/index' import type { GitLabProjectRef } from '../../../../../../shared/gitlab-types' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' -import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/repositories/repository-identity' import { CheckJobLogTail } from '../logs/check-job-log-tail' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { checkJobIdentities } from '@/components/editor/check-details/reading/check-details-reading-cache' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-time.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-time.tsx index a68cc97ea14..e1ef9d25089 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-time.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/check-run-time.tsx @@ -7,7 +7,7 @@ import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translat import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' import { formatUiNarrowDuration, formatUiNarrowRelativeTime } from '@/i18n/relative-time-format' import { formatCheckTimestamp } from '@/components/github/pr-check-presentation' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' const MINUTE_MS = 60_000 const HOUR_MS = 60 * MINUTE_MS diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-list.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-list.tsx index 2de0803e4a8..58d235c8e1b 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-list.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-list.tsx @@ -23,7 +23,7 @@ import { openAppHttpLink, openAppHttpLinkFromAnchor } from '@/lib/browser/navigation/app-http-link-open' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' export function ChecksList(props: ChecksListProps): React.JSX.Element { const t = useTranslate() diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-panel-review-copy.ts b/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-panel-review-copy.ts index b9e1683a586..41fe31cc5fc 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-panel-review-copy.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/checks-panel-review-copy.ts @@ -1,4 +1,4 @@ -import type { GitHubPRRefreshSkippedReason } from '../../../../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshSkippedReason } from '../../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' import { getGitHubUnavailableEmptyStateCopy } from '../../hosted-review/provider-actions/github-refresh-error-copy' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/conflict-summary.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/conflict-summary.tsx index 087bf249e34..6d1d44a3208 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/conflict-summary.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/conflict-summary.tsx @@ -5,7 +5,7 @@ import type { PRConflictSummary, ProviderCheckSummary, PRMergeableState -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' import { useTranslate } from '@/i18n/use-translate' export type ConflictReview = { diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.fixture.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.fixture.tsx index 34508ccb0f0..40abe23d143 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.fixture.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.fixture.tsx @@ -1,4 +1,4 @@ -import { acceptPRCheckObservation } from '../../../../../../shared/github/complete-pr-check-owner' +import { acceptPRCheckObservation } from '../../../../../../shared/github/checks/complete-pr-check-owner' import { defineFixture } from '../../../../component-fixtures/component-fixture' import { PRTriageStrip } from './triage-strip' import { ChecksList } from './checks-list' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.tsx b/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.tsx index 8e7109c83d8..e219cae7608 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/presentation/triage-strip.tsx @@ -1,4 +1,4 @@ -import { isCompletePRCheckOwner } from '../../../../../../shared/github/complete-pr-check-owner' +import { isCompletePRCheckOwner } from '../../../../../../shared/github/checks/complete-pr-check-owner' import React from 'react' import { AlertTriangle, @@ -10,7 +10,7 @@ import { Sparkles } from 'lucide-react' import { Button } from '@/components/ui/button' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' import type { ConflictReview } from './conflict-summary' import { summarizeProviderChecks, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-async-result-key.ts b/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-async-result-key.ts index a1cb206062e..7a27aefb0e3 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-async-result-key.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-async-result-key.ts @@ -1,5 +1,5 @@ -import type { GitHubOwnerRepo } from '../../../../../../shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../../../../../shared/github/repository-identity-key' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../../../shared/github/repositories/repository-identity-key' function normalizedPRRepoIdentity(prRepo?: GitHubOwnerRepo | null): string { return prRepo ? githubRepoIdentityKey(prRepo) : 'none' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-polling-test-model.ts b/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-polling-test-model.ts index bdd3f56a405..728798fac7f 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-polling-test-model.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/checks-panel-polling-test-model.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' import type { useChecksPanelPolling } from './use-checks-panel-polling' type PollingInput = Parameters[0] diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-detail-timer.ts b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-detail-timer.ts index 7378d311c3a..b3513630940 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-detail-timer.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-detail-timer.ts @@ -1,7 +1,7 @@ import { useEffect, useRef, type RefObject } from 'react' import { REVIEW_REFRESH_COOLDOWN_MS } from '../../../../../../shared/review-refresh-policy' import { installWindowVisibilityTimeoutPoller } from '@/lib/window-activity/window-visibility-timeout-poller' -import type { ChecksDetailPollingPolicy } from '../../../../../../shared/github/checks-detail-polling-policy' +import type { ChecksDetailPollingPolicy } from '../../../../../../shared/github/checks/checks-detail-polling-policy' import type { ChecksPanelPollingInput, ChecksPanelPollingState } from './use-checks-panel-polling' export function useChecksDetailTimer({ diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-list-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-list-state.tsx index b2e775ed66a..10800ce097d 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-list-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-list-state.tsx @@ -2,8 +2,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { useAppStore } from '@/store/index' import { useActiveWorktree } from '@/store/workspaces/selection/use-selected-worktree' import type { GitLabProjectRef } from '../../../../../../shared/gitlab-types' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' -import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/pull-request-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/repositories/repository-identity' import { sortChecksBySeverity } from '../../../../../../shared/source-control/checks/pr-check-severity-order' import { summarizeProviderChecks } from '../../../../../../shared/source-control/checks/provider-check-summary' import { createCheckRunDetailsRequestId } from '@/components/editor/check-details/actions/check-run-details-tab' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh.tsx b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh.tsx index 34e3a3cef18..8f886140179 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh.tsx @@ -14,7 +14,7 @@ import { import { checksPanelAsyncResultKey } from './checks-panel-async-result-key' import { recordChecksPanelPRRefreshBreadcrumb } from './checks-panel-pr-refresh-breadcrumb' import { getHostedReviewManualRefreshError } from '../../hosted-review/provider-actions/github-refresh-error-copy' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import { checksPanelManualRefreshProvider, refreshChecksPanelHostedReview, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.tsx b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.tsx index f02b511d3f2..54238a68101 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.tsx @@ -3,7 +3,7 @@ import { useChecksDetailTimer } from './use-checks-detail-timer' import { ChecksDetailPollingPolicy, nextChecksPollBackoffMs -} from '../../../../../../shared/github/checks-detail-polling-policy' +} from '../../../../../../shared/github/checks/checks-detail-polling-policy' import { useCallback, useEffect, useRef, useState } from 'react' import { gitLabPipelineJobsToPRChecks, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/review-actions/checks-panel-review-creation.ts b/src/renderer/src/components/right-sidebar/checks-panel/review-actions/checks-panel-review-creation.ts index 972ad3c4eec..8bb43be656b 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/review-actions/checks-panel-review-creation.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/review-actions/checks-panel-review-creation.ts @@ -2,7 +2,7 @@ import type { HostedReviewCreationEligibility, HostedReviewLookupOutcome } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' -import type { PRRefreshErrorType } from '../../../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshErrorType } from '../../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { normalizeHostedReviewBaseRef } from '../../../../../../shared/source-control/hosted-review/hosted-review-refs' import { isHostedReviewCreateEligibilityConfirmable } from '../../../../../../shared/source-control/hosted-review/hosted-review-create-confirmation' import type { ChecksPanelReviewLookup } from '../checks-panel-review-lookup-authority' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/review-actions/use-checks-panel-ai-queue.tsx b/src/renderer/src/components/right-sidebar/checks-panel/review-actions/use-checks-panel-ai-queue.tsx index 5efa092007e..2c798357477 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/review-actions/use-checks-panel-ai-queue.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/review-actions/use-checks-panel-ai-queue.tsx @@ -12,7 +12,7 @@ import { forgeOwnerHostIdForWorkspace } from '@/runtime/source-control/forge/for import { toast } from 'sonner' import { buildResolvePullRequestConflictsPrompt } from '../../../../../../shared/source-control/recovery/source-control-conflict-prompts' import { buildPRCommentsResolutionPrompt } from '../../../../../../shared/source-control/review-comments/pr-comments-resolution-prompt' -import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' import type { PRCommentGroup } from '../../../../../../shared/source-control/review-comments/pr-comment-groups' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx index c32257f84f1..eb4d4c60c32 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { useAppStore } from '@/store' import { useNow } from '@/hooks/use-now' import { isFolderRepo } from '../../../../../shared/repo-kind' -import { getGitHubPRCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-review-cache-identity' import { isStaleMergedGitHubReviewForHead } from '@/store/slices/hosted-review/hosted-review-cache-state' import { selectReviewCacheEntry } from '../source-control/review/review-cache-entry-selection' @@ -16,8 +16,8 @@ import { import type { ChecksPanelControllerState } from './use-checks-panel-controller-state' import { recordChecksPanelPRRefreshBreadcrumb } from './refresh/checks-panel-pr-refresh-breadcrumb' import { isChecksPanelHardRefreshErrorType } from './review-actions/checks-panel-review-creation' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' -import type { PRRefreshErrorType } from '../../../../../shared/github/pull-request-refresh-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { PRRefreshErrorType } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' type ChecksPanelContextStateInput = Pick< ChecksPanelControllerState, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx index eb2ced512ce..3e9d59693f5 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx @@ -20,9 +20,9 @@ import type { SourceControlActionRecipe, SourceControlLaunchActionId } from '../../../../../shared/source-control/ai/recipes/source-control-ai-actions' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { PRRefreshErrorType } from '../../../../../shared/github/pull-request-refresh-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { PRRefreshErrorType } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { GitLabProjectRef } from '../../../../../shared/gitlab-types' import type { PRCommentsListSelectionClearRequest } from './comments/pr-comments-list-selection' import { diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-data.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-data.tsx index cb200cb414b..ef1fbea2e53 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-data.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-data.tsx @@ -4,8 +4,8 @@ import { checksPanelAsyncResultKey } from './refresh/checks-panel-async-result-k import { loadGitLabJobLogDetails } from '@/runtime/repository-gitlab/gitlab-job-trace-client' import { checksPanelLoadErrorMessage } from './checks-panel-forge-owner' import { forgeOwnerHostIdForWorkspace } from '@/runtime/source-control/forge/forge-credential-target' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import type { ChecksPanelContextState } from './use-checks-panel-context-state' import type { ChecksPanelControllerState } from './use-checks-panel-controller-state' diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-state.tsx index 347f339a99f..5e5097fa17c 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-state.tsx @@ -1,7 +1,7 @@ import { useCallback, useMemo, useRef } from 'react' import { useAppStore } from '@/store' import { prChecksCacheSuffix, prCommentsCacheSuffix } from '@/store/github/cache/cache-identity' -import { getGitHubRepoCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubRepoCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { buildChecksPanelEligibilityGitFingerprint, readChecksPanelGitStatusSnapshot, diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksPanel.tsx b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksPanel.tsx index b4675e7bdb6..f9e3df5ce8e 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksPanel.tsx @@ -10,7 +10,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import { getAttachedWorktreesForFolderWorkspace } from '../../folder-workspace-attached-worktrees' import { FolderWorkspacePrChecksRow } from './FolderWorkspacePrChecksRow' import type { ParentPrChecksRefreshOutcome, ParentPrChecksRow } from './parent-pr-checks-row-types' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksRow.tsx b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksRow.tsx index 8e9b2527fd1..813cdc8b245 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksRow.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/FolderWorkspacePrChecksRow.tsx @@ -4,7 +4,10 @@ import { cn } from '@/lib/class-names' import { observeOffscreenAnimation } from '@/lib/window-activity/offscreen-animation-pause' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' import { useTranslate } from '@/i18n/use-translate' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../../shared/github/checks/check-types' import { CHECK_COLOR, CHECK_ICON, diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache.ts index e3be0de9cea..e3f4573a3e9 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache.ts @@ -1,5 +1,5 @@ import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import { isGitHubPRSuppressed } from '../../../../../../shared/worktree/github-pr-suppression' @@ -12,7 +12,7 @@ import type { AppState } from '@/store/types' import { getGitHubPRCacheKey, getLegacyGitHubPRCacheKey -} from '../../../../../../shared/github/github-cache-key' +} from '../../../../../../shared/github/cache/github-cache-key' import type { ParentPrChecksCacheEntry } from './parent-pr-checks-row-types' export function canUseParentPrChecksGitHubPRCacheEntry( diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache.ts index b6833dffc4f..0c78813c3f2 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache.ts @@ -1,5 +1,5 @@ import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Worktree } from '../../../../../../shared/worktree/types' import { isGitHubPRSuppressed } from '../../../../../../shared/worktree/github-pr-suppression' import { diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-refresh.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-refresh.ts index 04e5ebe10a8..87d1bba0870 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-refresh.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-refresh.ts @@ -1,5 +1,5 @@ -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' -import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/repositories/repository-identity' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-status.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-status.ts index 3ee420e8e1c..94ae0e2ae75 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-status.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-status.ts @@ -1,5 +1,5 @@ import { getProviderChecksOutcome } from '../../../../../../shared/source-control/checks/provider-check-summary' -import type { CheckStatus } from '../../../../../../shared/github/pull-request-types' +import type { CheckStatus } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' import { translate } from '@/i18n/i18n' import type { diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-types.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-types.ts index a984ffd0e2f..e7b8a8b2ef3 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-types.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-row-types.ts @@ -1,10 +1,10 @@ -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' import type { CheckStatus, ProviderCheckSummary, - GitHubRepositoryIdentity, PRInfo -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../../../shared/github/repositories/repository-identity' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.test.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.test.ts index b0dcefbc8c2..3857f205f52 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.test.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' @@ -8,7 +8,7 @@ import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-rev import { getGitHubPRCacheKey, getGitHubRepoCacheKey -} from '../../../../../../shared/github/github-cache-key' +} from '../../../../../../shared/github/cache/github-cache-key' import { prChecksCacheSuffix } from '@/store/github/cache/cache-identity' import { buildParentPrChecksProjection, diff --git a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.ts b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.ts index 0a20c66da02..255ce442892 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/folder-checks/parent-pr-checks-rows.ts @@ -1,4 +1,4 @@ -import type { PRCheckDetail } from '../../../../../../shared/github/check-types' +import type { PRCheckDetail } from '../../../../../../shared/github/checks/check-types' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' @@ -7,7 +7,7 @@ import { isFolderRepo } from '../../../../../../shared/repo-kind' import { isGitHubPRSuppressed } from '../../../../../../shared/worktree/github-pr-suppression' import { getWorktreeCardPrDisplay } from '@/components/sidebar/workspace-card/review/worktree-card-pr-display' import { getWorktreeGitIdentityDisplay } from '../../../../../../shared/worktree/git-identity-display' -import { getGitHubRepoCacheKey } from '../../../../../../shared/github/github-cache-key' +import { getGitHubRepoCacheKey } from '../../../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey, linkedReviewHintKey diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/GitHubPRStackMap.tsx b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/GitHubPRStackMap.tsx index 14c45fe31c3..5744c4a7eda 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/GitHubPRStackMap.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/GitHubPRStackMap.tsx @@ -3,7 +3,7 @@ import { ChevronDown, GitBranch, GitPullRequest } from 'lucide-react' import type { GitHubPRStack, GitHubPRStackEntry -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' import { cn } from '@/lib/class-names' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/HostedReviewActions.tsx b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/HostedReviewActions.tsx index 81c32c2c2d3..90003e90c97 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/HostedReviewActions.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/HostedReviewActions.tsx @@ -12,15 +12,15 @@ import { DropdownMenuSeparator } from '@/components/ui/dropdown-menu' import { presentGitHubPRMergeState } from '@/components/github-pr-merge-state' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' -import { resolveGitHubPRMergeMethods } from '../../../../../../shared/github/pull-request-merge-methods' +import { resolveGitHubPRMergeMethods } from '../../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' import { getPullRequestMergeMethodLabel } from '@/components/github/pull-request-confirmation-copy' import { isGitHubPRStackMergeQueueRequired, resolveGitHubPRStackMergeDecision -} from '../../../../../../shared/github/pull-request-stack-merge' +} from '../../../../../../shared/github/pull-requests/merge/pull-request-stack-merge' import { runWorktreeDelete } from '../../../sidebar/workspace-actions/deletion/delete-worktree-flow' import { getDeleteStateForWorktreeHost } from '../../../sidebar/workspace-actions/deletion/worktree-delete-state-host-match' import { presentGitLabMRMergeState } from './gitlab-mr-merge-state' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-confirmation.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-confirmation.ts index 11b773e5883..72786b51347 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-confirmation.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-confirmation.ts @@ -3,7 +3,7 @@ import { translate } from '@/i18n/i18n' import type { GitHubPRMergeMethod, GitHubPRStack -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' import { getGitHubPRStackMergeScope } from './github-pr-stack-merge' export function buildGitHubPRStackMergeConfirmation({ diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-merge.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-merge.ts index 05c0b1d4f6a..1d9e8d777a6 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-merge.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-pr-stack-merge.ts @@ -1,9 +1,9 @@ -import type { GitHubPRStack } from '../../../../../../shared/github/pull-request-types' +import type { GitHubPRStack } from '../../../../../../shared/github/pull-requests/pull-request-types' import { getGitHubPRStackMergeBlocker as resolveGitHubPRStackMergeBlocker, getGitHubPRStackMergeScope as resolveGitHubPRStackMergeScope, type GitHubPRStackMergeScope as SharedGitHubPRStackMergeScope -} from '../../../../../../shared/github/pull-request-stack-merge' +} from '../../../../../../shared/github/pull-requests/merge/pull-request-stack-merge' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-refresh-error-copy.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-refresh-error-copy.ts index 22ecb80f045..3ca24f29165 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-refresh-error-copy.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/github-refresh-error-copy.ts @@ -3,7 +3,7 @@ import { localizedHostedReviewCopy, resolveSupportedHostedReviewCopyProvider } from '@/i18n/hosted-review-localized-copy' -import type { PRRefreshUpstreamErrorType } from '../../../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshErrorType as PRRefreshUpstreamErrorType } from '../../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/hosted-review-github-actions.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/hosted-review-github-actions.ts index 4f4e82f7786..559ab01dae1 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/hosted-review-github-actions.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/hosted-review-github-actions.ts @@ -1,7 +1,7 @@ import type { GitHubPRMergeMethod, PRInfo -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import { getRepoExecutionHostId, diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.test.tsx b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.test.tsx index fa42cc9b039..a193357045c 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.test.tsx +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.test.tsx @@ -4,7 +4,7 @@ import { act, createElement } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import { useHostedReviewActions, type HostedReviewActionInfo } from './use-hosted-review-actions' import { presentGitHubPRMergeState } from '../../../github-pr-merge-state' diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.ts index 492b7056a3a..bfdbada79f5 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-hosted-review-actions.ts @@ -8,7 +8,7 @@ import type { HostedReviewInfo } from '../../../../../../shared/source-control/h import type { GitHubPRMergeMethod, PRInfo -} from '../../../../../../shared/github/pull-request-types' +} from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import { mergeGitHubHostedReview, diff --git a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-ready-hosted-review-action.ts b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-ready-hosted-review-action.ts index 28eb23a865e..59836dc3960 100644 --- a/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-ready-hosted-review-action.ts +++ b/src/renderer/src/components/right-sidebar/hosted-review/provider-actions/use-ready-hosted-review-action.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react' import { toast } from 'sonner' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import { translate } from '@/i18n/i18n' import { markGitHubHostedReviewReadyForReview } from './hosted-review-github-actions' diff --git a/src/renderer/src/components/right-sidebar/right-sidebar-top-activity-bar.tsx b/src/renderer/src/components/right-sidebar/right-sidebar-top-activity-bar.tsx index dce541828ca..69ae7136506 100644 --- a/src/renderer/src/components/right-sidebar/right-sidebar-top-activity-bar.tsx +++ b/src/renderer/src/components/right-sidebar/right-sidebar-top-activity-bar.tsx @@ -1,12 +1,16 @@ import React from 'react' import type { ActiveRightSidebarTab, ActivityBarPosition } from '../../../../shared/ui-chrome-types' -import type { CheckStatus as CoarseCheckStatus } from '../../../../shared/github/pull-request-types' +import type { CheckStatus as CoarseCheckStatus } from '../../../../shared/github/pull-requests/pull-request-types' type CheckStatus = CoarseCheckStatus | 'cancelled' import { cn } from '@/lib/class-names' import { TooltipProvider } from '@/components/ui/tooltip' import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu' -import { ActivityBarButton, TopActivityOverflowMenu } from './activity-bar-buttons' -import type { ActivityBarItem } from './activity-bar-buttons' +import { + ActivityBarButton, + TopActivityOverflowMenu, + type ActivityBarItem +} from './activity-bar-buttons' + import { ActivityBarPositionMenu, useActivityBarContextPlugin } from './activity-bar-position-menu' import { RIGHT_SIDEBAR_HEADER_NO_DRAG_CLASS_NAME, diff --git a/src/renderer/src/components/right-sidebar/source-control/listing/use-worktree-context.ts b/src/renderer/src/components/right-sidebar/source-control/listing/use-worktree-context.ts index d66e767655b..44fbd06fe40 100644 --- a/src/renderer/src/components/right-sidebar/source-control/listing/use-worktree-context.ts +++ b/src/renderer/src/components/right-sidebar/source-control/listing/use-worktree-context.ts @@ -13,7 +13,7 @@ import { getWorktreeGitIdentityDisplay } from '../../../../../../shared/worktree import type { AppState } from '@/store/types' import { getIndexedWorktreeMap } from '@/store/slices/worktrees/listing/worktree-catalog-index' import { selectRepoByIdForActiveWorkspace } from '@/store/repos/active-workspace-repository' -import { getGitHubPRCacheKey } from '../../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../../shared/github/cache/github-cache-key' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-review-cache-identity' import type { GitBranchChangeEntry } from '../../../../../../shared/git/diff/git-diff-compare-types' import type { GitStatusEntry } from '../../../../../../shared/git/status/git-status-types' diff --git a/src/renderer/src/components/right-sidebar/source-control/review/source-control-primary-action-types.ts b/src/renderer/src/components/right-sidebar/source-control/review/source-control-primary-action-types.ts index 957971cf79e..0b8f87dc491 100644 --- a/src/renderer/src/components/right-sidebar/source-control/review/source-control-primary-action-types.ts +++ b/src/renderer/src/components/right-sidebar/source-control/review/source-control-primary-action-types.ts @@ -1,6 +1,6 @@ import type { HostedReviewCreationEligibility } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' import type { GitUpstreamStatus } from '../../../../../../shared/git/status/git-status-types' -import type { PRState } from '../../../../../../shared/github/pull-request-types' +import type { PRState } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { SourceControlPrimaryActionKind, SourceControlRemoteOpKind diff --git a/src/renderer/src/components/settings/navigation/SettingsSidebar.tsx b/src/renderer/src/components/settings/navigation/SettingsSidebar.tsx index 81bbba0985d..bd4cc3f2ee2 100644 --- a/src/renderer/src/components/settings/navigation/SettingsSidebar.tsx +++ b/src/renderer/src/components/settings/navigation/SettingsSidebar.tsx @@ -1,12 +1,12 @@ -import type { RefObject } from 'react' -import { useMemo } from 'react' +import { type RefObject, useMemo } from 'react' + import { ArrowLeft, Search, Server } from 'lucide-react' import type { RepoIcon } from '../../../../../shared/repo-icon' import type { SettingsNavIcon, SettingsNavInstallStatus } from '@/components/settings/navigation/settings-navigation-types' -import type { GitHubRepositoryIdentity } from '../../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../../shared/github/repositories/repository-identity' import type { GlobalSettings } from '../../../../../shared/global-settings-types' import { useShortcutKeyComboDetails } from '@/hooks/useShortcutLabel' import { ShortcutKeyCombo } from '../../ShortcutKeyCombo' @@ -16,8 +16,11 @@ import { RepoForkIndicator } from '../../repo/repo-fork-indicator' import { Button } from '../../ui/button' import { Input } from '../../ui/input' import { SetupGuideProgressRing } from '../../setup-guide/SetupGuideProgressRing' -import { useSettingsSetupGuideProgress } from '../setup-guide/settings-setup-guide-progress' -import type { SettingsSetupGuideProgress } from '../setup-guide/settings-setup-guide-progress' +import { + useSettingsSetupGuideProgress, + type SettingsSetupGuideProgress +} from '../setup-guide/settings-setup-guide-progress' + import { useTranslate } from '@/i18n/use-translate' import { resolveLeftSidebarStyleVariables } from '@/app-shell/appearance/left-sidebar-appearance' import { useAppStore } from '@/store/index' diff --git a/src/renderer/src/components/settings/repository/RepositoryPane.tsx b/src/renderer/src/components/settings/repository/RepositoryPane.tsx index 812884e088d..1a228d8aa07 100644 --- a/src/renderer/src/components/settings/repository/RepositoryPane.tsx +++ b/src/renderer/src/components/settings/repository/RepositoryPane.tsx @@ -1,4 +1,4 @@ -import type { GhAccountBinding } from '../../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../../shared/github/accounts/account-binding' import { useCallback, useRef, useState } from 'react' import type { OrcaHooks, RepoHookSettings } from '../../../../../shared/orca-yaml-hook-types' import type { diff --git a/src/renderer/src/components/settings/repository/source-control/RepositoryGitHubAccountSection.tsx b/src/renderer/src/components/settings/repository/source-control/RepositoryGitHubAccountSection.tsx index cc1b39dc96e..c9616fa6abd 100644 --- a/src/renderer/src/components/settings/repository/source-control/RepositoryGitHubAccountSection.tsx +++ b/src/renderer/src/components/settings/repository/source-control/RepositoryGitHubAccountSection.tsx @@ -1,8 +1,8 @@ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' import { RefreshCw } from 'lucide-react' -import type { GhAccountBinding } from '../../../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../../../shared/github/accounts/account-binding' import type { Repo } from '../../../../../../shared/repo-types' -import type { GhAccountBindingInventory } from '../../../../../../shared/github/auth-types' +import type { GhAccountBindingInventory } from '../../../../../../shared/github/accounts/auth-types' import { Button } from '../../../ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/select' import { SearchableSetting } from '../../section-layout/SearchableSetting' diff --git a/src/renderer/src/components/sidebar/CommentMarkdown.tsx b/src/renderer/src/components/sidebar/CommentMarkdown.tsx index 3be70d85897..7a58a007a81 100644 --- a/src/renderer/src/components/sidebar/CommentMarkdown.tsx +++ b/src/renderer/src/components/sidebar/CommentMarkdown.tsx @@ -37,7 +37,7 @@ import { import { linkGitHubReferences, type GitHubRepoReference -} from '../../../../shared/github/github-reference-links' +} from '../../../../shared/github/markdown/github-reference-links' export type { CommentMarkdownLinkClickHandler } from './comment-markdown-element-renderers' diff --git a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx index 510002e5b21..96d2afa6933 100644 --- a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx +++ b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx @@ -15,7 +15,7 @@ import { } from './comment-markdown-github-attachment-media' import { ExpandableMarkdownImage } from './MarkdownImageLightbox' import { MarkdownGitHubCallout } from '@/components/markdown-github-callout' -import { readGitHubCalloutKind } from '../../../../shared/github/github-callouts' +import { readGitHubCalloutKind } from '../../../../shared/github/markdown/github-callouts' export type CommentMarkdownLinkClickHandler = ( event: React.MouseEvent, diff --git a/src/renderer/src/components/sidebar/comment-markdown-sanitize.ts b/src/renderer/src/components/sidebar/comment-markdown-sanitize.ts index 3907493a98d..98ab0485aa2 100644 --- a/src/renderer/src/components/sidebar/comment-markdown-sanitize.ts +++ b/src/renderer/src/components/sidebar/comment-markdown-sanitize.ts @@ -1,6 +1,6 @@ import { defaultSchema, type Options as SanitizeSchema } from 'rehype-sanitize' import { defaultUrlTransform, type Options as MarkdownOptions } from 'react-markdown' -import { GITHUB_CALLOUT_SANITIZE_ATTRIBUTE } from '../../../../shared/github/github-callouts' +import { GITHUB_CALLOUT_SANITIZE_ATTRIBUTE } from '../../../../shared/github/markdown/github-callouts' import { jiraImageReference } from '../../../../shared/jira/jira-attachment-image' export function isTrustedCompactImageSrc(src: string | undefined): src is string { diff --git a/src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.tsx b/src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.tsx index 45e50ed5af3..402c474191e 100644 --- a/src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.tsx +++ b/src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.tsx @@ -13,7 +13,7 @@ import { import { useMountedRef } from '@/hooks/useMountedRef' import { useAppStore } from '@/store/index' import { cn } from '@/lib/class-names' -import type { GitHubViewer } from '../../../../../shared/github/pull-request-types' +import type { GitHubViewer } from '../../../../../shared/github/pull-requests/pull-request-types' import { useTranslate } from '@/i18n/use-translate' import { extractImageFilesFromDataTransfer, diff --git a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-linked-details.ts b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-linked-details.ts index 9991bb2f07f..3e8d4350224 100644 --- a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-linked-details.ts +++ b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-linked-details.ts @@ -1,6 +1,6 @@ import { useTranslate } from '@/i18n/use-translate' import { getWorkspaceAttachments } from '../../../../../../shared/workspace-attachments/workspace-attachments' -import type { IssueInfo } from '../../../../../../shared/github/pull-request-types' +import type { IssueInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { LinearIssue } from '../../../../../../shared/linear/issue-types' import { getWorktreeCardJiraIssueDisplay } from '../review/worktree-card-jira-issue-display' import type { WorktreeCardIssueDisplay } from '../metadata/worktree-card-meta-types' diff --git a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-review-details.ts b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-review-details.ts index 74645d7eb3e..d82f28bb70a 100644 --- a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-review-details.ts +++ b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-review-details.ts @@ -5,7 +5,7 @@ import { getWorkspaceAttachmentSourceContext } from '../review/workspace-attachm import { getWorkspaceReferenceLinearWorkspaceId } from '../review/workspace-reference-details' import { getWorktreeGitIdentityDisplay } from '../../../../../../shared/worktree/git-identity-display' import { useAppStore } from '@/store' -import { getGitHubPRCacheKey } from '../../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../../shared/github/cache/github-cache-key' import { issueCacheKey as getIssueCacheKey } from '@/store/github/cache/cache-identity' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review/hosted-review-cache-identity' import { hostedReviewInfoFromGitHubPRInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-github' diff --git a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-secondary-details.ts b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-secondary-details.ts index a04c3a6b252..f218f822b62 100644 --- a/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-secondary-details.ts +++ b/src/renderer/src/components/sidebar/workspace-card/details/use-worktree-card-secondary-details.ts @@ -6,7 +6,7 @@ import type { WorktreeCardPrDisplay } from '../review/worktree-card-pr-display' import { getWorkspaceAttachments } from '../../../../../../shared/workspace-attachments/workspace-attachments' import { toast } from 'sonner' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import { translate } from '@/i18n/i18n' import { openWorkspaceBrowserTab } from '@/lib/browser/workspace/workspace-browser-tab-open' import './WorktreeCardMeta' diff --git a/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-attachment-editing.ts b/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-attachment-editing.ts index 403defef276..38b74ef2f61 100644 --- a/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-attachment-editing.ts +++ b/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-attachment-editing.ts @@ -13,7 +13,7 @@ import { getWorkspaceAttachmentKey, getWorkspaceAttachmentUrlScope } from '../../../../../../shared/workspace-attachments/workspace-attachment-normalization' -import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' import { parseGitLabIssueOrMRLink } from '../../../../../../shared/new-workspace/gitlab-links' import { parseLinearIssueInput } from '../../../../../../shared/linear/links' import { parseJiraIssueUrl } from '../../../../../../shared/jira/jira-issue-url' diff --git a/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-card-meta-types.ts b/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-card-meta-types.ts index 176ecaecd79..dd5081a2c2d 100644 --- a/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-card-meta-types.ts +++ b/src/renderer/src/components/sidebar/workspace-card/metadata/worktree-card-meta-types.ts @@ -1,4 +1,4 @@ -import type { IssueInfo } from '../../../../../../shared/github/pull-request-types' +import type { IssueInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Worktree, WorkspaceAttachment, diff --git a/src/renderer/src/components/sidebar/workspace-card/review/folder-workspace-card-pr-display.test.ts b/src/renderer/src/components/sidebar/workspace-card/review/folder-workspace-card-pr-display.test.ts index 12a3d0a86a2..2bf3202bd06 100644 --- a/src/renderer/src/components/sidebar/workspace-card/review/folder-workspace-card-pr-display.test.ts +++ b/src/renderer/src/components/sidebar/workspace-card/review/folder-workspace-card-pr-display.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from 'vitest' -import type { CheckStatus, PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { + CheckStatus, + PRInfo +} from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../../shared/repo-types' import type { WorkspaceLineage, diff --git a/src/renderer/src/components/sidebar/workspace-card/review/workspace-attachment-source-label.ts b/src/renderer/src/components/sidebar/workspace-card/review/workspace-attachment-source-label.ts index 39cb444ae03..32a8835d0b2 100644 --- a/src/renderer/src/components/sidebar/workspace-card/review/workspace-attachment-source-label.ts +++ b/src/renderer/src/components/sidebar/workspace-card/review/workspace-attachment-source-label.ts @@ -1,5 +1,5 @@ import type { WorkspaceAttachment } from '../../../../../../shared/worktree/types' -import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' import { parseGitLabIssueOrMRLink } from '../../../../../../shared/new-workspace/gitlab-links' import { parseLinearIssueInput } from '../../../../../../shared/linear/links' import { parseJiraIssueUrl } from '../../../../../../shared/jira/jira-issue-url' diff --git a/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-display.ts b/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-display.ts index 6ac932598c7..7561f7434ee 100644 --- a/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-display.ts +++ b/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-display.ts @@ -1,7 +1,7 @@ import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { PRInfo } from '../../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { Worktree } from '../../../../../../shared/worktree/types' import { isGitHubPRSuppressed } from '../../../../../../shared/worktree/github-pr-suppression' diff --git a/src/renderer/src/components/sidebar/workspace-card/review/worktree-task-state.ts b/src/renderer/src/components/sidebar/workspace-card/review/worktree-task-state.ts index d7285e60be9..334ac36f59d 100644 --- a/src/renderer/src/components/sidebar/workspace-card/review/worktree-task-state.ts +++ b/src/renderer/src/components/sidebar/workspace-card/review/worktree-task-state.ts @@ -8,7 +8,7 @@ import { } from 'lucide-react' import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { IssueInfo } from '../../../../../../shared/github/pull-request-types' +import type { IssueInfo } from '../../../../../../shared/github/pull-requests/pull-request-types' export type WorktreeTaskState = { Icon: LucideIcon diff --git a/src/renderer/src/components/sidebar/worktree-list/parity/sidebar-list-golden-factories.test-fixture.ts b/src/renderer/src/components/sidebar/worktree-list/parity/sidebar-list-golden-factories.test-fixture.ts index 93bdd26a981..13717b7b028 100644 --- a/src/renderer/src/components/sidebar/worktree-list/parity/sidebar-list-golden-factories.test-fixture.ts +++ b/src/renderer/src/components/sidebar/worktree-list/parity/sidebar-list-golden-factories.test-fixture.ts @@ -1,5 +1,5 @@ import type { FolderWorkspace } from '../../../../../../shared/folder-workspaces/folder-workspace-types' -import type { PRState } from '../../../../../../shared/github/pull-request-types' +import type { PRState } from '../../../../../../shared/github/pull-requests/pull-request-types' import type { ProjectGroup } from '../../../../../../shared/project-group-types' import type { Repo } from '../../../../../../shared/repo-types' import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types' diff --git a/src/renderer/src/components/status-bar/hosted-review-label.ts b/src/renderer/src/components/status-bar/hosted-review-label.ts index 7f75c0d254f..d9de1f5fe24 100644 --- a/src/renderer/src/components/status-bar/hosted-review-label.ts +++ b/src/renderer/src/components/status-bar/hosted-review-label.ts @@ -1,4 +1,4 @@ -import type { ProviderCheckSummary } from '../../../../shared/github/pull-request-types' +import type { ProviderCheckSummary } from '../../../../shared/github/pull-requests/pull-request-types' import { getProviderChecksOutcome } from '../../../../shared/source-control/checks/provider-check-summary' import type { HostedReviewProvider } from '../../../../shared/source-control/hosted-review/hosted-review-contracts' import { diff --git a/src/renderer/src/components/task-page/github/AssigneesCell.tsx b/src/renderer/src/components/task-page/github/AssigneesCell.tsx index eee2321aebd..6d923beb901 100644 --- a/src/renderer/src/components/task-page/github/AssigneesCell.tsx +++ b/src/renderer/src/components/task-page/github/AssigneesCell.tsx @@ -1,18 +1,18 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import React, { useMemo, useState, useCallback } from 'react' import { useAppStore } from '@/store/index' import { useShallow } from 'zustand/react/shallow' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { useRepoAssigneesBySlug } from '@/hooks/useGitHubSlugMetadata' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { gitHubSlugApiForTarget } from '@/runtime/repository-github/github-slug-owner-api' -import { githubProjectHost } from '../../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../../shared/github/projects/project-identity' import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover' import { cn } from '@/lib/class-names' import { LoaderCircle, ChevronDown, Check } from 'lucide-react' diff --git a/src/renderer/src/components/task-page/github/Avatars.tsx b/src/renderer/src/components/task-page/github/Avatars.tsx index d8ea7fc18a9..6305ad6c57e 100644 --- a/src/renderer/src/components/task-page/github/Avatars.tsx +++ b/src/renderer/src/components/task-page/github/Avatars.tsx @@ -1,8 +1,8 @@ -import type { GitHubPRPrimaryReviewer } from '../../../../../shared/github/pr-reviewer-display' +import type { GitHubPRPrimaryReviewer } from '../../../../../shared/github/pull-requests/pr-reviewer-display' import React from 'react' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' import { Users } from 'lucide-react' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' export function ReviewChipAvatar({ reviewer, avatarHost diff --git a/src/renderer/src/components/task-page/github/ChecksCell.tsx b/src/renderer/src/components/task-page/github/ChecksCell.tsx index 613e05cbba1..86b3b6cdb83 100644 --- a/src/renderer/src/components/task-page/github/ChecksCell.tsx +++ b/src/renderer/src/components/task-page/github/ChecksCell.tsx @@ -1,5 +1,5 @@ import { getProviderChecksOutcome } from '../../../../../shared/source-control/checks/provider-check-summary' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import React, { useRef, useEffect } from 'react' import { useTranslate } from '@/i18n/use-translate' import { CheckCircle2, AlertCircle, Clock3, Minus } from 'lucide-react' diff --git a/src/renderer/src/components/task-page/github/DuplicatePicker.tsx b/src/renderer/src/components/task-page/github/DuplicatePicker.tsx index 02c04ccf771..7d6b03e5b5f 100644 --- a/src/renderer/src/components/task-page/github/DuplicatePicker.tsx +++ b/src/renderer/src/components/task-page/github/DuplicatePicker.tsx @@ -2,7 +2,7 @@ import type React from 'react' import { CheckCircle2, ChevronLeft, CircleDot, Copy, Search } from 'lucide-react' import { useTranslate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' diff --git a/src/renderer/src/components/task-page/github/IssueSelectors.tsx b/src/renderer/src/components/task-page/github/IssueSelectors.tsx index a506995b5aa..3e8f289e0fd 100644 --- a/src/renderer/src/components/task-page/github/IssueSelectors.tsx +++ b/src/renderer/src/components/task-page/github/IssueSelectors.tsx @@ -4,7 +4,7 @@ import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover import { Button } from '@/components/ui/button' import { LoaderCircle, Check } from 'lucide-react' import { cn } from '@/lib/class-names' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { GitHubAssigneeAvatar } from './Avatars' export function GitHubIssueLabelSelector({ labels, diff --git a/src/renderer/src/components/task-page/github/MergeCell.tsx b/src/renderer/src/components/task-page/github/MergeCell.tsx index 218cf52cb83..90adc647649 100644 --- a/src/renderer/src/components/task-page/github/MergeCell.tsx +++ b/src/renderer/src/components/task-page/github/MergeCell.tsx @@ -1,5 +1,5 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubPRMergeMethod } from '../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import React, { useState } from 'react' @@ -13,7 +13,7 @@ import { getPullRequestMergeMethodLabel } from '@/components/github/pull-request-confirmation-copy' import { presentGitHubPRMergeState } from '@/components/github-pr-merge-state' -import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods' +import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-requests/merge/pull-request-merge-methods' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { DropdownMenu, diff --git a/src/renderer/src/components/task-page/github/ReviewCell.tsx b/src/renderer/src/components/task-page/github/ReviewCell.tsx index 188fe87f5a9..210f99b7647 100644 --- a/src/renderer/src/components/task-page/github/ReviewCell.tsx +++ b/src/renderer/src/components/task-page/github/ReviewCell.tsx @@ -1,5 +1,5 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import React, { useState, useMemo, useRef, useCallback } from 'react' @@ -16,7 +16,7 @@ import { getGitHubPRPrimaryReviewer, getGitHubPRReviewerRows, getGitHubPRReviewLabel -} from '../../../../../shared/github/pr-reviewer-display' +} from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { cn } from '@/lib/class-names' import { ChevronDown } from 'lucide-react' import { Popover, PopoverTrigger } from '@/components/ui/popover' diff --git a/src/renderer/src/components/task-page/github/ReviewerPicker.tsx b/src/renderer/src/components/task-page/github/ReviewerPicker.tsx index 5b5df583f6a..1517f28a104 100644 --- a/src/renderer/src/components/task-page/github/ReviewerPicker.tsx +++ b/src/renderer/src/components/task-page/github/ReviewerPicker.tsx @@ -6,7 +6,7 @@ import { cn } from '@/lib/class-names' import { Input } from '@/components/ui/input' import { PopoverContent } from '@/components/ui/popover' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../shared/repo-types' import { GitHubUserAvatar } from '@/components/github/github-user-avatar' diff --git a/src/renderer/src/components/task-page/github/StatusCell.tsx b/src/renderer/src/components/task-page/github/StatusCell.tsx index 7ca9809331b..5fdb7f29398 100644 --- a/src/renderer/src/components/task-page/github/StatusCell.tsx +++ b/src/renderer/src/components/task-page/github/StatusCell.tsx @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitHubIssueUpdate } from '../../../../../shared/issue-mutation-types' import type { Repo } from '../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' @@ -11,7 +11,7 @@ import { import { useAppStore } from '@/store/index' import { useShallow } from 'zustand/react/shallow' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { getTaskPageGitHubDuplicateCandidates, validateTaskPageGitHubDuplicateTarget, @@ -23,7 +23,7 @@ import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { gitHubSlugApiForTarget } from '@/runtime/repository-github/github-slug-owner-api' -import { githubProjectHost } from '../../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../../shared/github/projects/project-identity' import { TaskPageGitHubWorkItemStateBadge } from '@/components/task-page/github/task-page-github-work-item-status-badge' import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover' import { cn } from '@/lib/class-names' diff --git a/src/renderer/src/components/task-page/github/pr-check-hydration.test.tsx b/src/renderer/src/components/task-page/github/pr-check-hydration.test.tsx index a09d3171298..7bc7faeedbb 100644 --- a/src/renderer/src/components/task-page/github/pr-check-hydration.test.tsx +++ b/src/renderer/src/components/task-page/github/pr-check-hydration.test.tsx @@ -2,7 +2,7 @@ import { Suspense, startTransition, useState } from 'react' // @vitest-environment happy-dom import { act, cleanup, render, renderHook, screen } from '@testing-library/react' import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageJiraCreationMetadataModel } from '../jira/use-task-page-jira-creation-metadata' import { GITHUB_COMPLETE_PR_CHECK_OBSERVATION_CAPABILITY as capability } from '../../../../../shared/source-control/runtime/source-control-runtime-capabilities' import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/hosts/capabilities/local-runtime-capabilities' diff --git a/src/renderer/src/components/task-page/github/task-page-github-confirmed-client-values.ts b/src/renderer/src/components/task-page/github/task-page-github-confirmed-client-values.ts index 877916e6b35..074ecc96171 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-confirmed-client-values.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-confirmed-client-values.ts @@ -1,5 +1,5 @@ -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { taskPageGitHubLastConfirmedKey } from './task-page-github-work-item-mutation-keys' const lastConfirmedClientValues = new Map< diff --git a/src/renderer/src/components/task-page/github/task-page-github-dialog-state-authority.ts b/src/renderer/src/components/task-page/github/task-page-github-dialog-state-authority.ts index 49bcb91aff2..3761ec4101d 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-dialog-state-authority.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-dialog-state-authority.ts @@ -1,5 +1,5 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import { getTaskSourceCacheScope, type TaskSourceContext diff --git a/src/renderer/src/components/task-page/github/task-page-github-landing-refresh-run.tsx b/src/renderer/src/components/task-page/github/task-page-github-landing-refresh-run.tsx index 121ef25c076..4119be279bf 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-landing-refresh-run.tsx +++ b/src/renderer/src/components/task-page/github/task-page-github-landing-refresh-run.tsx @@ -5,7 +5,7 @@ import { taskPageGitHubResumeCache, TASK_PAGE_GITHUB_RESUME_FRESH_MS } from '@/components/task-page/github/task-page-github-resume-cache' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getTaskPageRepoSourceContext } from '../../../lib/work-items/task-repo-source-context' import { sortWorkItemsByNumber } from '../../../../../shared/work-items/work-item-order' import { taskPageToGitHubApiPage } from '../../../../../shared/work-items/work-item-pagination' diff --git a/src/renderer/src/components/task-page/github/task-page-github-quiet-refresh-run.tsx b/src/renderer/src/components/task-page/github/task-page-github-quiet-refresh-run.tsx index af199d28ce3..afd6b0e815b 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-quiet-refresh-run.tsx +++ b/src/renderer/src/components/task-page/github/task-page-github-quiet-refresh-run.tsx @@ -27,7 +27,7 @@ import { } from '@/components/task-page/github/task-page-github-work-item-quiet-adopt' import { stripRepoQualifiers } from '../../../../../shared/work-items/task-query' import { getTaskPageRepoSourceContext } from '../../../lib/work-items/task-repo-source-context' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { taskPageToGitHubApiPage } from '../../../../../shared/work-items/work-item-pagination' import { useAppStore } from '@/store/index' export function runTaskPageGitHubQuietRefresh(model: TaskPageGitHubQuietRefreshPreludeModel) { diff --git a/src/renderer/src/components/task-page/github/task-page-github-resume-cache.ts b/src/renderer/src/components/task-page/github/task-page-github-resume-cache.ts index d3224a8c93b..3306b23b7a6 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-resume-cache.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-resume-cache.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' export const TASK_PAGE_GITHUB_RESUME_CACHE_LIMIT = 5 export const TASK_PAGE_GITHUB_RESUME_CACHE_TTL_MS = 10 * 60_000 diff --git a/src/renderer/src/components/task-page/github/task-page-github-review-model.tsx b/src/renderer/src/components/task-page/github/task-page-github-review-model.tsx index 8a3a5aea8c4..1b86933b814 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-review-model.tsx +++ b/src/renderer/src/components/task-page/github/task-page-github-review-model.tsx @@ -1,11 +1,9 @@ -import type { - GitHubOwnerRepo, - GitHubAssignableUser -} from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' -import { githubProjectHost } from '../../../../../shared/github/project-identity' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' +import { githubProjectHost } from '../../../../../shared/github/projects/project-identity' export function sameOptionalGitHubOwnerRepo( left: GitHubOwnerRepo | null | undefined, right: GitHubOwnerRepo | null | undefined diff --git a/src/renderer/src/components/task-page/github/task-page-github-reviewer-actions.ts b/src/renderer/src/components/task-page/github/task-page-github-reviewer-actions.ts index c06e09b03dd..7a12f17a108 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-reviewer-actions.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-reviewer-actions.ts @@ -7,14 +7,12 @@ import type { TaskPageGitHubMutationIntent } from '@/components/task-page/github import { normalizeGitHubReviewerLogins, parseGitHubReviewerInputLogins -} from '../../../../../shared/github/pr-reviewer-display' +} from '../../../../../shared/github/pull-requests/pr-reviewer-display' import { toast } from 'sonner' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { TaskPageGitHubWorkItemMutationRunner } from '../linear/task-page-linear-jira-list-model' diff --git a/src/renderer/src/components/task-page/github/task-page-github-status-actions.ts b/src/renderer/src/components/task-page/github/task-page-github-status-actions.ts index 84ee625c368..01628da6fef 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-status-actions.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-status-actions.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitHubIssueCloseReason, GitHubIssueUpdate diff --git a/src/renderer/src/components/task-page/github/task-page-github-status-state.ts b/src/renderer/src/components/task-page/github/task-page-github-status-state.ts index b4dac8ba879..7ebd889cafd 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-status-state.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-status-state.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' type GitHubStatusItem = Pick diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-filter-membership.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-filter-membership.ts index 7257e86fab7..a234297feed 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-filter-membership.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-filter-membership.ts @@ -1,6 +1,6 @@ import type { ParsedTaskQuery } from '../../../../../shared/work-items/task-query' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { StickyHideEntry } from './task-page-github-work-item-registry-types' function includesLogin(users: readonly GitHubAssignableUser[] | undefined, login: string): boolean { diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-composition.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-composition.ts index ddbd2a5f720..d35a96e0bc9 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-composition.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-composition.ts @@ -1,8 +1,8 @@ -import { projectPRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import { projectPRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import type { ParsedTaskQuery } from '../../../../../shared/work-items/task-query' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { recomputeTaskPageGitHubItemSoftHide, shouldSoftHideTaskPageGitHubWorkItem diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-lifecycle.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-lifecycle.ts index 88ab31d80f2..97342909657 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-lifecycle.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-lifecycle.ts @@ -1,5 +1,5 @@ import type { ParsedTaskQuery } from '../../../../../shared/work-items/task-query' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { applyTaskPageGitHubListOps } from './task-page-github-work-item-mutation-patches' import { diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-pages.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-pages.ts index 09e8ea4ef2c..9e30d7a143b 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-pages.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-pages.ts @@ -1,7 +1,7 @@ -import { projectPRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import { projectPRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { getRegistryMergedTaskPageGitHubWorkItem } from './task-page-github-work-item-mutation-composition' import { getStickyHideEntry, diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-patches.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-patches.ts index 96234db7a72..d3a1659df87 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-patches.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-patches.ts @@ -1,5 +1,5 @@ -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageGitHubCloseAction } from './task-page-github-status-actions' import { taskPageGitHubListOpKey } from './task-page-github-work-item-mutation-keys' import type { diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-registry.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-registry.ts index 3877327e966..41472fa4635 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-registry.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-registry.ts @@ -4,10 +4,8 @@ import { getLastConfirmedClientValue, setLastConfirmedClientValue as storeLastConfirmedClientValue } from './task-page-github-confirmed-client-values' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { PendingOp, StickyHideEntry, diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-types.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-types.ts index 1e379156ea1..0d59ae7b9c4 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-types.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutation-types.ts @@ -1,5 +1,5 @@ import type { ParsedTaskQuery } from '../../../../../shared/work-items/task-query' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { GitHubPatchWorkItemOptions } from '../../../store/github/work-items/work-item-patch-options' import type { TaskPageGitHubMutationIntent } from './task-page-github-work-item-mutation-patches' diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutations.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutations.ts index d40609edcd7..ff4f5ec992b 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-mutations.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-mutations.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getTaskSourceCacheScope, type TaskSourceContext diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-adopt.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-adopt.ts index 76e24ab6fd7..418beba587f 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-adopt.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-adopt.ts @@ -1,6 +1,6 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { loginSetOfUsers, loginSetsEqual } from './task-page-github-work-item-mutation-patches' import { familiesFromPendingOp, diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-revalidate.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-revalidate.ts index 82686225b77..b25c4d16daf 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-revalidate.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-quiet-revalidate.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { adoptQuietSearchFieldsForItem, diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-registry-types.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-registry-types.ts index 8b419cc9c2f..26cbb52c7a7 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-registry-types.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-registry-types.ts @@ -1,5 +1,5 @@ -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' export type TaskPageGitHubListFamily = 'assignees' | 'reviewRequests' export type TaskPageGitHubMutationKey = { diff --git a/src/renderer/src/components/task-page/github/task-page-github-work-item-status.ts b/src/renderer/src/components/task-page/github/task-page-github-work-item-status.ts index f0fdc016ee5..139ad4c6410 100644 --- a/src/renderer/src/components/task-page/github/task-page-github-work-item-status.ts +++ b/src/renderer/src/components/task-page/github/task-page-github-work-item-status.ts @@ -1,6 +1,6 @@ import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' export type GitHubWorkItemStatusItem = Pick diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-cache-reconciliation.ts b/src/renderer/src/components/task-page/github/use-task-page-github-cache-reconciliation.ts index deb05262511..6bdf228e00e 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-cache-reconciliation.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-cache-reconciliation.ts @@ -1,7 +1,7 @@ import type { TaskPageGitHubDetailModel } from './use-task-page-github-detail' import { useCallback, useMemo, useEffect, useRef, useState } from 'react' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { patchTaskPageGitHubWorkItemPages, overlayPendingOnTaskPagePages diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-collection-state.ts b/src/renderer/src/components/task-page/github/use-task-page-github-collection-state.ts index 94d204fad8d..f2792452c49 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-collection-state.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-collection-state.ts @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo, useSyncExternalStore, type SetStateAction } from 'react' import type { TaskPageProviderStateModel } from '../use-task-page-provider-state' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { sortWorkItemsByNumber } from '../../../../../shared/work-items/work-item-order' import { stripRepoQualifiers } from '../../../../../shared/work-items/task-query' import type { GitHubSourceCollections } from '@/store/github/sources/source-collections' diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-detail.ts b/src/renderer/src/components/task-page/github/use-task-page-github-detail.ts index 33d1ce9948b..da60f83304a 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-detail.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-detail.ts @@ -7,7 +7,7 @@ import type { ItemDialogTab } from '@/components/github/github-work-item-identit import { stripRepoQualifiers } from '../../../../../shared/work-items/task-query' import { createTaskPageDialogWorkItemReader } from '@/components/task-page/task-page-dialog-work-item' import { useTaskPageWorkItemsCacheSelection } from './use-task-page-work-items-cache-selection' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../../shared/gitlab-types' import { getTaskPageRepoSourceContext } from '../../../lib/work-items/task-repo-source-context' import { startGitHubListScrollRestore } from './task-page-github-list-scroll-restore' diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.test.tsx b/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.test.tsx deleted file mode 100644 index 5889ea7e994..00000000000 --- a/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.test.tsx +++ /dev/null @@ -1,264 +0,0 @@ -// @vitest-environment happy-dom -import { act, useRef } from 'react' -import '@testing-library/jest-dom/vitest' -import { cleanup, renderHook, render, screen, fireEvent, within } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useStore } from 'zustand' -import { TooltipProvider } from '@/components/ui/tooltip' -import { TaskPageGitHubIssueDialog } from './IssueDialog' -import { useTaskPageWorkItemsCacheSelection } from './use-task-page-work-items-cache-selection' -import { buildTaskPageRepoSourceState } from '../task-page-cache-selectors' -import type { TaskPageComposerActionsModel } from '../use-task-page-composer-actions' -import { createGitHubSlice } from '@/store/github/github' -import { createRepoUpdateActions } from '@/store/repos/repo-update' -import { createGlobalSettingsFixture } from '../../../../../shared/global-settings-test-fixture' -import { createUIStore } from '@/store/slices/ui/ui-slice-test-harness' -import { useTaskPageGitHubDetail } from '@/components/task-page/github/use-task-page-github-detail' -import { useTaskPageGitHubIssueCreation } from '@/components/task-page/github/use-task-page-github-issue-creation' -import { useTaskPageWorkspaceActions } from '@/components/task-page/use-task-page-workspace-actions' -import type { GitHubListRestoreWrite } from './task-page-github-list-scroll-restore' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { Repo } from '../../../../../shared/repo-types' -import type { AppState } from '@/store/types' -import type { StoreApi } from 'zustand/vanilla' - -const holders = vi.hoisted((): { store: StoreApi | null } => ({ store: null })) -const mocks = vi.hoisted(() => ({ createIssue: vi.fn(), workItem: vi.fn(), updateRepo: vi.fn() })) -vi.mock('@/store/index', async () => { - const { useStore } = await import('zustand') - return { - useAppStore: Object.assign( - (selector: (state: AppState) => T) => useStore(holders.store!, selector), - { getState: () => holders.store!.getState() } - ) - } -}) -vi.mock('@/runtime/rpc/calls/runtime-rpc-call', () => ({ - callRuntimeRpc: vi.fn() -})) -vi.mock('@/components/github/GitHubMarkdownComposer', () => ({ - GitHubMarkdownComposer: () => null -})) -vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() } })) - -function item(number: number): GitHubWorkItem { - return { - id: `issue:${number}`, - type: 'issue', - number, - title: `Issue ${number}`, - state: 'open', - url: `https://github.com/o/r/issues/${number}`, - labels: [], - assignees: [], - updatedAt: '2026-10-28T00:00:00Z', - author: null, - repoId: 'repo-1' - } -} -function setup() { - const store = createUIStore() - store.setState({ workItemsCache: {} }) - holders.store = store - const repo: Repo = { - id: 'repo-1', - executionHostId: 'local', - path: '/repo', - displayName: 'Repo', - badgeColor: '', - addedAt: 1 - } - store.setState({ - ...createGitHubSlice(store.setState, store.getState, store), - ...createRepoUpdateActions(store.setState, store.getState), - repos: [repo], - projects: [], - projectHostSetups: [], - settings: createGlobalSettingsFixture(), - fetchReposForAllHosts: vi.fn().mockResolvedValue(undefined) - }) - const setter = vi.fn() - const rendered = renderHook(() => { - const detail = useTaskPageGitHubDetail({ - pageData: useStore(store, (s) => s.taskPageData), - openTaskPage: store.getState().openTaskPage, - repos: [], - selectedRepos: [], - primaryRepo: repo, - setGithubMode: setter, - gitlabDialogItem: null, - setGitlabDialogItem: setter, - appliedTaskSearch: '', - githubPerRepoPageLimit: 36, - githubResumeContextKey: 'local', - pages: [], - currentPage: 0, - currentPageRef: useRef(0), - githubListScrollRef: useRef(null), - githubListScrollTopRef: useRef(0), - pendingGithubScrollRestoreRef: useRef(null), - githubListRestoreWriteRef: useRef(null), - taskListPositionRef: useRef(null) - }) - const workspace = useTaskPageWorkspaceActions({ - ...detail, - openModal: store.getState().openModal - }) - return useTaskPageGitHubIssueCreation({ - ...workspace, - setNewIssueOpen: setter, - newIssueTitle: 'Created issue', - setNewIssueTitle: setter, - newIssueBody: 'Body', - setNewIssueBody: setter, - newIssueLabels: [], - setNewIssueLabels: setter, - newIssueAssignees: [], - setNewIssueAssignees: setter, - newIssueSubmitting: false, - setNewIssueSubmitting: setter, - setTaskRefreshNonce: setter, - setNewIssueDraft: setter, - clearNewIssueDraft: setter, - newIssueTargetRepo: repo, - newIssueSourceContext: null, - newIssueRuntimeTarget: null - }) - }) - return { ...rendered, store, repo, setter } -} - -beforeEach(() => { - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - vi.clearAllMocks() - mocks.createIssue.mockResolvedValue({ ok: true, number: 1, url: item(1).url }) - Object.defineProperty(window, 'api', { - configurable: true, - value: { - gh: { createIssue: mocks.createIssue, workItem: mocks.workItem }, - repos: { update: mocks.updateRepo } - } - }) -}) -afterEach(() => { - cleanup() - vi.unstubAllGlobals() - holders.store = null -}) - -async function createIssuePendingDetail() { - const pending = Promise.withResolvers() - mocks.workItem.mockReturnValue(pending.promise) - const rendered = setup() - await act(async () => { - await rendered.result.current.handleCreateNewIssue() - }) - const resolveDetail = async (detail: GitHubWorkItem): Promise => { - await act(async () => { - pending.resolve(detail) - await pending.promise - }) - } - return { ...rendered, resolveDetail } -} - -describe('created issue hydration ownership', () => { - it('preserves a newer selection of another issue', async () => { - const { result, store, resolveDetail } = await createIssuePendingDetail() - const next = item(2) - act(() => { - result.current.openGitHubDetailPage(next) - }) - await resolveDetail(item(1)) - expect(store.getState().githubTaskDrawerWorkItem).toBe(next) - expect(store.getState().taskPageData.openGitHubWorkItem).toBe(next) - }) - it('keeps the list open after navigating back from the created issue', async () => { - const { store, resolveDetail } = await createIssuePendingDetail() - act(() => { - store.getState().openTaskPage({ taskSource: 'github' }) - }) - await resolveDetail(item(1)) - expect(store.getState().githubTaskDrawerWorkItem).toBeNull() - }) - it('keeps the drawer closed after using the issue in the workspace composer', async () => { - const { result, store, resolveDetail } = await createIssuePendingDetail() - const stub = store.getState().githubTaskDrawerWorkItem - if (!stub) { - throw new Error('Expected the created issue to be open') - } - act(() => { - result.current.setDialogWorkItem(null) - result.current.handleUseWorkItem(stub) - }) - await resolveDetail(item(1)) - expect(store.getState().activeModal).toBe('new-workspace-composer') - expect(store.getState().githubTaskDrawerWorkItem).toBeNull() - }) -}) - -describe('New Issue source-save dependency', () => { - it('keeps the mounted New Issue selector and draft across a held source save', async () => { - const { store, repo, result } = setup() - const origin = { owner: 'fork', repo: 'project' } - const upstream = { owner: 'parent', repo: 'project' } - Object.assign(window.api.gh, { - listWorkItems: vi.fn().mockResolvedValue({ - items: [], - sources: { - issues: upstream, - prs: upstream, - originCandidate: origin, - upstreamCandidate: upstream - } - }) - }) - await act(async () => { - await store.getState().fetchWorkItems(repo.id, repo.path, 20, '') - }) - function Composer() { - const repos = useStore(store, (s) => s.repos) - const selected = useTaskPageWorkItemsCacheSelection(repos, 20, '') - const fields = { - ...result.current, - selectedRepos: repos, - newIssueTargetRepo: repos[0], - githubSourceDisplayState: buildTaskPageRepoSourceState( - repos, - selected.sourceDisplayWorkItemsCacheEntries - ), - newIssueOpen: true, - setIssueSourcePreference: store.getState().setIssueSourcePreference, - submitShortcutLabel: 'Ctrl+Enter', - newIssueRepoId: repo.id, - setNewIssueRepoId: vi.fn(), - newIssueRepoLabels: { data: [], loading: false, error: null }, - newIssueRepoAssignees: { data: [], loading: false, error: null } - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: these fields cover every IssueDialog input; later provider/composer stages add no fields read by this dialog. - const model = fields as unknown as TaskPageComposerActionsModel - return ( - - - - ) - } - render() - const dialog = within(screen.getByRole('dialog')) - const picker = dialog.getByRole('group', { name: 'Issue source' }) - const title = dialog.getByDisplayValue('Created issue') - const held = Promise.withResolvers() - mocks.updateRepo.mockReturnValueOnce(held.promise) - await act(async () => { - fireEvent.click(dialog.getByRole('button', { name: 'Origin' })) - await Promise.resolve() - }) - expect(dialog.getByRole('group', { name: 'Issue source' })).toBe(picker) - expect(dialog.getByDisplayValue('Created issue')).toBe(title) - expect(dialog.getByRole('button', { name: 'Origin' })).toHaveAttribute('aria-pressed', 'true') - await act(async () => { - held.resolve({ ...repo, issueSourcePreference: 'origin' }) - await held.promise - }) - }) -}) diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.ts b/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.ts index b3b9e1608a8..1c96f6cdba2 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-issue-creation.ts @@ -4,7 +4,7 @@ import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { useAppStore } from '@/store/index' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' type TaskPageGitHubIssueCreationInput = Pick< diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-issue-draft.ts b/src/renderer/src/components/task-page/github/use-task-page-github-issue-draft.ts index dc1a0e3cd2f..86f78ea3bce 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-issue-draft.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-issue-draft.ts @@ -1,6 +1,6 @@ import type { TaskPageGitHubCacheReconciliationModel } from './use-task-page-github-cache-reconciliation' import { useState, useMemo, useEffect } from 'react' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import { useAppStore } from '@/store/index' import { getGitHubRepoRoutingTarget } from '@/lib/work-items/github/github-source-runtime-context' import { useRepoLabels, useRepoAssignees } from '@/hooks/useGitHubRepoMetadata' diff --git a/src/renderer/src/components/task-page/github/use-task-page-github-list-projection.ts b/src/renderer/src/components/task-page/github/use-task-page-github-list-projection.ts index abed3b8deae..1945976b170 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-github-list-projection.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-github-list-projection.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo } from 'react' import { useGitHubTaskItems } from '@/hooks/useGitHubTaskItems' import type { TaskPageJiraCreationMetadataModel } from '../jira/use-task-page-jira-creation-metadata' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { taskPageGitHubItemKey } from '@/components/task-page/github/task-page-github-work-item-mutation-keys' import { deriveAdvertisedTotalPages } from '../../../../../shared/work-items/work-item-empty-page' import { GITHUB_PR_TASK_GRID_CLASS, GITHUB_TASK_GRID_CLASS } from '../task-page-source-context' diff --git a/src/renderer/src/components/task-page/github/use-task-page-pr-check-hydration.ts b/src/renderer/src/components/task-page/github/use-task-page-pr-check-hydration.ts index 173d259ba8b..5db2fe96aac 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-pr-check-hydration.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-pr-check-hydration.ts @@ -22,9 +22,9 @@ import { acceptPRCheckObservation, projectPRCheckOwner, completePRCheckOwnerScope -} from '../../../../../shared/github/complete-pr-check-owner' +} from '../../../../../shared/github/checks/complete-pr-check-owner' import { getWorkItemRepo } from '@/lib/work-items/work-item-repo-row' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskPageJiraCreationMetadataModel } from '../jira/use-task-page-jira-creation-metadata' import { deriveTaskPagePRCheckSummary } from '../task-page-pr-check-summary' import { PR_CHECKS_EAGER_PREFETCH_LIMIT } from '../task-page-source-context' diff --git a/src/renderer/src/components/task-page/github/use-task-page-work-items-cache-selection.ts b/src/renderer/src/components/task-page/github/use-task-page-work-items-cache-selection.ts index 6700da67c00..7e432e11be5 100644 --- a/src/renderer/src/components/task-page/github/use-task-page-work-items-cache-selection.ts +++ b/src/renderer/src/components/task-page/github/use-task-page-work-items-cache-selection.ts @@ -5,7 +5,7 @@ import { getTaskPageRepoCacheRows, createTaskPageWorkItemsCacheSelector } from '@/components/task-page/task-page-cache-selectors' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items-query-bounds' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items/work-items-query-bounds' import type { Repo } from '../../../../../shared/repo-types' import { getTaskPageRepoCacheInput } from '../task-page-source-context' diff --git a/src/renderer/src/components/task-page/linear/task-page-linear-jira-list-model.tsx b/src/renderer/src/components/task-page/linear/task-page-linear-jira-list-model.tsx index 68f277ef36c..45d7e9d57d0 100644 --- a/src/renderer/src/components/task-page/linear/task-page-linear-jira-list-model.tsx +++ b/src/renderer/src/components/task-page/linear/task-page-linear-jira-list-model.tsx @@ -1,5 +1,5 @@ import type { LinearIssue } from '../../../../../shared/linear/issue-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getLinearPriorityLabel } from '@/components/task-page/task-page-localized-options' import type { LinearOrderBy, diff --git a/src/renderer/src/components/task-page/task-page-cache-selectors.test.ts b/src/renderer/src/components/task-page/task-page-cache-selectors.test.ts index 4ab79151235..fab6f68e1b4 100644 --- a/src/renderer/src/components/task-page/task-page-cache-selectors.test.ts +++ b/src/renderer/src/components/task-page/task-page-cache-selectors.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import type { CacheEntry } from '@/store/github/cache/cache-model' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { reconcileTaskPagePagesWithWorkItemsCache } from './task-page-cache-selectors' function entry(data: T): CacheEntry { diff --git a/src/renderer/src/components/task-page/task-page-cache-selectors.ts b/src/renderer/src/components/task-page/task-page-cache-selectors.ts index 5f281500fa2..807dada3070 100644 --- a/src/renderer/src/components/task-page/task-page-cache-selectors.ts +++ b/src/renderer/src/components/task-page/task-page-cache-selectors.ts @@ -8,7 +8,7 @@ import type { WorkItemsCacheError, WorkItemsCacheSources } from '@/store/github/cache/cache-model' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { taskPageWorkItemKey, taskPageWorkItemStatusSignature, diff --git a/src/renderer/src/components/task-page/task-page-checks-pill.ts b/src/renderer/src/components/task-page/task-page-checks-pill.ts index 40b6bd0cc37..e57c7662703 100644 --- a/src/renderer/src/components/task-page/task-page-checks-pill.ts +++ b/src/renderer/src/components/task-page/task-page-checks-pill.ts @@ -4,7 +4,7 @@ import { getCancelledCheckCount, getProviderChecksOutcome } from '../../../../shared/source-control/checks/provider-check-summary' -import type { ProviderCheckSummary } from '../../../../shared/github/pull-request-types' +import type { ProviderCheckSummary } from '../../../../shared/github/pull-requests/pull-request-types' type ChecksPillItem = { checksSummary?: ProviderCheckSummary } diff --git a/src/renderer/src/components/task-page/task-page-dialog-work-item.ts b/src/renderer/src/components/task-page/task-page-dialog-work-item.ts index e89de32c902..c099e908b55 100644 --- a/src/renderer/src/components/task-page/task-page-dialog-work-item.ts +++ b/src/renderer/src/components/task-page/task-page-dialog-work-item.ts @@ -1,5 +1,5 @@ import type { CacheEntry } from '@/store/github/cache/cache-model' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' type WorkItemsCache = Record> diff --git a/src/renderer/src/components/task-page/task-page-draft-storage.tsx b/src/renderer/src/components/task-page/task-page-draft-storage.tsx index b6ff5883cce..a10d7af8c59 100644 --- a/src/renderer/src/components/task-page/task-page-draft-storage.tsx +++ b/src/renderer/src/components/task-page/task-page-draft-storage.tsx @@ -1,5 +1,5 @@ import type { TaskPageRepoSourceState } from '@/components/task-page/task-page-cache-selectors' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator' import { type NewLinearProjectDraft, diff --git a/src/renderer/src/components/task-page/task-page-new-issue-draft.ts b/src/renderer/src/components/task-page/task-page-new-issue-draft.ts index 844388e2cce..d233a7d4435 100644 --- a/src/renderer/src/components/task-page/task-page-new-issue-draft.ts +++ b/src/renderer/src/components/task-page/task-page-new-issue-draft.ts @@ -1,4 +1,4 @@ -import type { GitHubAssignableUser } from '../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../shared/github/pull-requests/pull-request-types' import type { NewIssueDraft } from '@/store/slices/task-creation/new-issue-draft' export type NewIssueOpenSeed = { diff --git a/src/renderer/src/components/task-page/task-page-pr-check-summary.ts b/src/renderer/src/components/task-page/task-page-pr-check-summary.ts index 21e593d7053..5bb72ef95a4 100644 --- a/src/renderer/src/components/task-page/task-page-pr-check-summary.ts +++ b/src/renderer/src/components/task-page/task-page-pr-check-summary.ts @@ -1,6 +1,6 @@ import { summarizeProviderChecks } from '../../../../shared/source-control/checks/provider-check-summary' -import type { PRCheckDetail } from '../../../../shared/github/check-types' -import type { ProviderCheckSummary } from '../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../shared/github/checks/check-types' +import type { ProviderCheckSummary } from '../../../../shared/github/pull-requests/pull-request-types' export function deriveTaskPagePRCheckSummary(checks: PRCheckDetail[]): ProviderCheckSummary { const summary = summarizeProviderChecks(checks) diff --git a/src/renderer/src/components/task-page/task-page-pr-delta-summary.ts b/src/renderer/src/components/task-page/task-page-pr-delta-summary.ts index 1f244598157..c79d1baa5b9 100644 --- a/src/renderer/src/components/task-page/task-page-pr-delta-summary.ts +++ b/src/renderer/src/components/task-page/task-page-pr-delta-summary.ts @@ -1,6 +1,6 @@ import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' export function formatPRDelta(item: GitHubWorkItem, t: Translate = translate): string | null { const parts: string[] = [] diff --git a/src/renderer/src/components/task-page/task-page-source-context.tsx b/src/renderer/src/components/task-page/task-page-source-context.tsx index 8a369f7df9d..d3ac5e745f6 100644 --- a/src/renderer/src/components/task-page/task-page-source-context.tsx +++ b/src/renderer/src/components/task-page/task-page-source-context.tsx @@ -8,7 +8,7 @@ import type { GitLabTaskFilter, GitLabIssueFilter } from '@/components/task-page/task-page-localized-options' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue } from '../../../../shared/jira/jira-types' import type { Repo } from '../../../../shared/repo-types' diff --git a/src/renderer/src/components/task-page/task-page-work-item-pagination.test.ts b/src/renderer/src/components/task-page/task-page-work-item-pagination.test.ts index f6194058e15..d3b05ec0a59 100644 --- a/src/renderer/src/components/task-page/task-page-work-item-pagination.test.ts +++ b/src/renderer/src/components/task-page/task-page-work-item-pagination.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { accumulateWorkItemPages } from './task-page-work-item-pagination' function item(repoId: string, id: string, updatedAt: string): GitHubWorkItem { diff --git a/src/renderer/src/components/task-page/task-page-work-item-pagination.ts b/src/renderer/src/components/task-page/task-page-work-item-pagination.ts index e9774f5247b..b93c6f7158a 100644 --- a/src/renderer/src/components/task-page/task-page-work-item-pagination.ts +++ b/src/renderer/src/components/task-page/task-page-work-item-pagination.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' /** Inclusive updatedAt cursors preserve timestamp ties; dedup boundary rows and backfill full display pages, flushing partials on exhaustion/stall. */ diff --git a/src/renderer/src/components/task-page/task-page-work-item-signatures.ts b/src/renderer/src/components/task-page/task-page-work-item-signatures.ts index 2e2589853cf..a9b40a254a9 100644 --- a/src/renderer/src/components/task-page/task-page-work-item-signatures.ts +++ b/src/renderer/src/components/task-page/task-page-work-item-signatures.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' // Why the host: a twin's two hosts return the same PR under one repo id. export function taskPageWorkItemKey(item: GitHubWorkItem): string { diff --git a/src/renderer/src/components/task-page/use-task-page-source-display.test-fixture.tsx b/src/renderer/src/components/task-page/use-task-page-source-display.test-fixture.tsx index 6ceab2f68ad..d576373a064 100644 --- a/src/renderer/src/components/task-page/use-task-page-source-display.test-fixture.tsx +++ b/src/renderer/src/components/task-page/use-task-page-source-display.test-fixture.tsx @@ -9,7 +9,7 @@ import { TooltipProvider } from '@/components/ui/tooltip' import { useAppStore } from '@/store' import { i18n } from '@/i18n/i18n' import type { WorkItemsCacheSources } from '@/store/github/cache/cache-model' -import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' import type { Repo } from '../../../../shared/repo-types' import { getGitHubTaskKind } from '../../../../shared/work-items/task-github-search' import { parseTaskQuery } from '../../../../shared/work-items/task-query' diff --git a/src/renderer/src/components/task-page/use-task-page-source-display.ts b/src/renderer/src/components/task-page/use-task-page-source-display.ts index 85137ebe39e..880a4da8946 100644 --- a/src/renderer/src/components/task-page/use-task-page-source-display.ts +++ b/src/renderer/src/components/task-page/use-task-page-source-display.ts @@ -1,7 +1,7 @@ import type { TaskPageLinearCreationStateModel } from './linear/use-task-page-linear-creation-state' import { useMemo } from 'react' import type { GitHubTaskKind } from '../../../../shared/work-items/task-github-search' -import { buildGitHubRepoUrl } from '../../../../shared/github/links' +import { buildGitHubRepoUrl } from '../../../../shared/github/repositories/links' import { getTaskSourceContextSummary } from '../task-source-context-summary' import { getGitHubProjectSourceSummary } from './task-page-source-context' diff --git a/src/renderer/src/components/task-page/use-task-page-workspace-actions.ts b/src/renderer/src/components/task-page/use-task-page-workspace-actions.ts index 90b24da2c30..e853d7a7eda 100644 --- a/src/renderer/src/components/task-page/use-task-page-workspace-actions.ts +++ b/src/renderer/src/components/task-page/use-task-page-workspace-actions.ts @@ -1,6 +1,6 @@ import type { TaskPageSearchActionsModel } from './use-task-page-search-actions' import { useCallback } from 'react' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { LinkedWorkItemSummary } from '@/lib/workspace/creation/new-workspace' import { useAppStore } from '@/store' diff --git a/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-search.test.tsx b/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-search.test.tsx index 4f84f3d90f9..e07cc3062cd 100644 --- a/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-search.test.tsx +++ b/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-search.test.tsx @@ -11,7 +11,7 @@ import { makeCmdJTaskSearchResult } from '../cmd-j/task-search-results' import { useAppStore } from '@/store' import type { Repo } from '../../../../shared/repo-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { buildTaskSourceContextFromRepo } from '../../../../shared/work-items/task-source-context' vi.mock('../cmd-j/task-search-requests', () => ({ createCmdJTaskSearchRequests: vi.fn() })) @@ -66,35 +66,6 @@ it('publishes a fast source while a second is pending, then discards both after expect(result.current.taskItems).toEqual([]) }) -it('refreshes host labels without rerunning completed or pending searches', async () => { - let rejectPending: (error: Error) => void = () => {} - const pending = new Promise<(typeof jiraTaskResult)[]>((_, reject) => { - rejectPending = reject - }) - const run = async () => [jiraTaskResult] - const runPending = () => pending - vi.mocked(createCmdJTaskSearchRequests).mockReturnValue([ - { id: 'ok', provider: 'jira', hostId: 'local', hostLabel: 'Before', run }, - { id: 'failed', provider: 'jira', hostId: 'local', hostLabel: 'Before', run: runPending } - ]) - const host = { - id: 'local' as const, - label: 'Before', - detail: '', - kind: 'local' as const, - health: 'local' as const, - presence: 'local' as const - } - const { result, rerender } = renderHook(useWorktreeJumpPaletteTaskSearch, { - initialProps: { ...input, hostOptions: [host] } - }) - await act(async () => vi.advanceTimersByTimeAsync(600)) - rerender({ ...input, hostOptions: [{ ...host, label: 'After' }] }) - await act(async () => rejectPending(new Error('unavailable'))) - expect(result.current.taskItems[0].result.hostLabel).toBe('After') - expect(result.current.taskSearchFailures).toEqual(['jira · After']) -}) - it('filters to open after pending edits apply: a pending reopen shows and a failure removes it', async () => { const repo: Repo = { id: 'repo', path: '/repo', displayName: 'Repo', badgeColor: '', addedAt: 1 } const issue = (number: number, state: GitHubWorkItem['state']): GitHubWorkItem => ({ diff --git a/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-url.ts b/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-url.ts index f23f1d0e238..0199c13b63e 100644 --- a/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-url.ts +++ b/src/renderer/src/components/worktree-jump/use-worktree-jump-palette-task-url.ts @@ -17,7 +17,7 @@ import { type CmdJTaskSourceUrl, getCmdJTaskUrlCreatePreview } from '@/lib/workspace/search/worktrees/worktree-palette-task-url-match' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { LinearIssue } from '../../../../shared/linear/issue-types' import { diff --git a/src/renderer/src/components/worktree-jump/worktree-jump-palette-create-worktree.ts b/src/renderer/src/components/worktree-jump/worktree-jump-palette-create-worktree.ts index 8bbe8c86e3c..fd63ae84f89 100644 --- a/src/renderer/src/components/worktree-jump/worktree-jump-palette-create-worktree.ts +++ b/src/renderer/src/components/worktree-jump/worktree-jump-palette-create-worktree.ts @@ -4,7 +4,7 @@ import { findRepoForHost } from '@/store/repos/repo-host-identity' import { parseGitHubIssueOrPRLink, parseGitHubIssueOrPRNumber -} from '../../../../shared/github/links' +} from '../../../../shared/github/repositories/links' import { getLinkedWorkItemSuggestedName, getLinkedWorkItemWorkspaceName diff --git a/src/renderer/src/hooks/composer-state/async-composer-state.ts b/src/renderer/src/hooks/composer-state/async-composer-state.ts index e2b6ecf958d..547f1b8d427 100644 --- a/src/renderer/src/hooks/composer-state/async-composer-state.ts +++ b/src/renderer/src/hooks/composer-state/async-composer-state.ts @@ -26,9 +26,9 @@ import { useState, useRef, useEffect, useCallback } from 'react' import type { OrcaHooks, SetupAgentStartupPolicy } from '../../../../shared/orca-yaml-hook-types' import type { IssueCommandReadResult } from '@/runtime/repo-hooks/runtime-hooks-client' import type { WorkspaceCreateErrorDisplay } from '@/lib/workspace/creation/workspace-create-error-format' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT } from '@/components/contextual-tours/contextual-tour-composer-events' -import type { GitHubRepositoryIdentity } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../shared/github/repositories/repository-identity' import { getRepoSetupAgentStartupPolicy } from './setup-policy-decisions' import { getJiraIssueKeyCasingSeedOptions } from '@/lib/work-items/jira/jira-issue-key-casing' import type { diff --git a/src/renderer/src/hooks/composer-state/async-model.ts b/src/renderer/src/hooks/composer-state/async-model.ts index 46dadb1a029..4da3d9ecbcd 100644 --- a/src/renderer/src/hooks/composer-state/async-model.ts +++ b/src/renderer/src/hooks/composer-state/async-model.ts @@ -1,8 +1,8 @@ import type * as React from 'react' import type { RefObject } from 'react' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { OrcaHooks, SetupAgentStartupPolicy } from '../../../../shared/orca-yaml-hook-types' -import type { GitHubRepositoryIdentity } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../shared/github/repositories/repository-identity' import type { WorkspaceCreateErrorDisplay } from '@/lib/workspace/creation/workspace-create-error-format' import type { IssueCommandReadResult } from '@/runtime/repo-hooks/runtime-hooks-client' import type { SmartSourceSelection } from './source-selection-decisions' diff --git a/src/renderer/src/hooks/composer-state/branch-start-point-actions.ts b/src/renderer/src/hooks/composer-state/branch-start-point-actions.ts index 0e1a5bfa498..08e235b78cd 100644 --- a/src/renderer/src/hooks/composer-state/branch-start-point-actions.ts +++ b/src/renderer/src/hooks/composer-state/branch-start-point-actions.ts @@ -32,7 +32,7 @@ type BranchStartPointActionsInput = Pick< import { useCallback } from 'react' import type { SparsePreset } from '../../../../shared/worktree/create-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitPushTarget } from '../../../../shared/worktree/types' import { resolveGitHubWorkItemIdentity } from '../../../../shared/new-workspace/github-work-item-identity' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' diff --git a/src/renderer/src/hooks/composer-state/composer-decisions.ts b/src/renderer/src/hooks/composer-state/composer-decisions.ts index 8074d655cd3..f6dd9fb3093 100644 --- a/src/renderer/src/hooks/composer-state/composer-decisions.ts +++ b/src/renderer/src/hooks/composer-state/composer-decisions.ts @@ -10,7 +10,7 @@ import { getGitHubLinkedWorkItemIdentity, type SmartGitHubPrStartPointSelection } from './source-selection-decisions' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' import type { JiraIssueKeyCasing } from '../../../../shared/ui-chrome-types' diff --git a/src/renderer/src/hooks/composer-state/composer-source-model.ts b/src/renderer/src/hooks/composer-state/composer-source-model.ts index 5ae4f517485..8ccc0f8b7a6 100644 --- a/src/renderer/src/hooks/composer-state/composer-source-model.ts +++ b/src/renderer/src/hooks/composer-state/composer-source-model.ts @@ -1,5 +1,5 @@ import type { GitLabWorkItem } from '../../../../shared/gitlab-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitPushTarget } from '../../../../shared/worktree/types' import type { JiraIssue } from '../../../../shared/jira/jira-types' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' diff --git a/src/renderer/src/hooks/composer-state/composer-target-store.ts b/src/renderer/src/hooks/composer-state/composer-target-store.ts index 20e7bd89948..bca8aa2aa26 100644 --- a/src/renderer/src/hooks/composer-state/composer-target-store.ts +++ b/src/renderer/src/hooks/composer-state/composer-target-store.ts @@ -5,7 +5,7 @@ import { getNewWorkspaceDialogEligibleRepos } from '../../../../shared/new-works import { resolveComposerSeedRepoId } from '@/lib/workspace/creation/new-workspace-composer-repo' import { buildExecutionHostRegistry } from '../../../../shared/execution-host/execution-host-registry' import { getHostDisplayLabelOverrides } from '../../../../shared/host-setting-overrides' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' import type { WorkspaceStatus } from '../../../../shared/worktree/types' import type { WorkspaceSource as WorkspaceCreateTelemetrySource } from '../../../../shared/workspace-source' diff --git a/src/renderer/src/hooks/composer-state/derived-composer-state.ts b/src/renderer/src/hooks/composer-state/derived-composer-state.ts index 5abdb1d1083..69b43669fbc 100644 --- a/src/renderer/src/hooks/composer-state/derived-composer-state.ts +++ b/src/renderer/src/hooks/composer-state/derived-composer-state.ts @@ -9,9 +9,9 @@ import { import { parseGitHubIssueOrPRNumber, parseGitHubIssueOrPRLink -} from '../../../../shared/github/links' +} from '../../../../shared/github/repositories/links' import { normalizeGitHubLinkQuery } from '../../../../shared/new-workspace/github-links' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { getWorkspaceSourceProvider } from '../../../../shared/new-workspace/workspace-source' import { canUseIssueCommandForLinkedItemProvider, diff --git a/src/renderer/src/hooks/composer-state/derived-model.ts b/src/renderer/src/hooks/composer-state/derived-model.ts index a3162e5030e..e249611cc24 100644 --- a/src/renderer/src/hooks/composer-state/derived-model.ts +++ b/src/renderer/src/hooks/composer-state/derived-model.ts @@ -1,5 +1,5 @@ import type { FolderSetupSupport } from '@/lib/workspace/creation/setup/folder-setup-support' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { OrcaHooks, SetupRunPolicy } from '../../../../shared/orca-yaml-hook-types' import type { SparsePreset } from '../../../../shared/worktree/create-types' import type { RetiredNameRegistry } from '../../../../shared/worktree/retired-name-registry' diff --git a/src/renderer/src/hooks/composer-state/github-provider-selection.ts b/src/renderer/src/hooks/composer-state/github-provider-selection.ts index d88b96962df..2b300f40aab 100644 --- a/src/renderer/src/hooks/composer-state/github-provider-selection.ts +++ b/src/renderer/src/hooks/composer-state/github-provider-selection.ts @@ -32,7 +32,7 @@ type GitHubProviderSelectionInput = Pick< > import { useCallback } from 'react' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { resolveGitHubWorkItemIdentity } from '../../../../shared/new-workspace/github-work-item-identity' import '@/components/sidebar/workspace-creation/folder/folder-workspace-source-repos' import { diff --git a/src/renderer/src/hooks/composer-state/github-source-application.ts b/src/renderer/src/hooks/composer-state/github-source-application.ts index 0036ab809f0..72f6aec5ad1 100644 --- a/src/renderer/src/hooks/composer-state/github-source-application.ts +++ b/src/renderer/src/hooks/composer-state/github-source-application.ts @@ -19,7 +19,7 @@ type GitHubSourceApplicationInput = Pick< > import { useCallback } from 'react' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { resolveGitHubWorkItemIdentity } from '../../../../shared/new-workspace/github-work-item-identity' import '@/lib/workspace/creation/new-workspace' import { diff --git a/src/renderer/src/hooks/composer-state/github-submit-resolution.ts b/src/renderer/src/hooks/composer-state/github-submit-resolution.ts index 1848dec8179..f2b7351161e 100644 --- a/src/renderer/src/hooks/composer-state/github-submit-resolution.ts +++ b/src/renderer/src/hooks/composer-state/github-submit-resolution.ts @@ -48,9 +48,12 @@ import { lookupGitHubWorkItemForSource, lookupGitHubWorkItemByOwnerRepoForSource } from '@/lib/work-items/github/github-work-item-source-lookup' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' -import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions' -import { getGitHubLinkedWorkItemIdentity } from './source-selection-decisions' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' +import { + type PendingSmartGitHubSubmitResolution, + getGitHubLinkedWorkItemIdentity +} from './source-selection-decisions' + import { getUnresolvedSmartGitHubSubmitMessage } from './github-submit-unresolved-message' export function useGitHubSubmitResolution(input: GitHubSubmitResolutionInput) { diff --git a/src/renderer/src/hooks/composer-state/github-submit-unresolved-message.ts b/src/renderer/src/hooks/composer-state/github-submit-unresolved-message.ts index 2f7375bafd6..c24672d37f2 100644 --- a/src/renderer/src/hooks/composer-state/github-submit-unresolved-message.ts +++ b/src/renderer/src/hooks/composer-state/github-submit-unresolved-message.ts @@ -1,6 +1,6 @@ import { translate } from '@/i18n/i18n' import type { SmartGitHubSubmitIntent } from '@/lib/work-items/github/smart-github-submit' -import { isDefaultGitHubHost } from '../../../../shared/github/repository-identity-key' +import { isDefaultGitHubHost } from '../../../../shared/github/repositories/repository-identity-key' export function getUnresolvedSmartGitHubSubmitMessage( intent: SmartGitHubSubmitIntent, diff --git a/src/renderer/src/hooks/composer-state/linked-item-lookup-effects.ts b/src/renderer/src/hooks/composer-state/linked-item-lookup-effects.ts index c398def2c2c..4ae21cd18f1 100644 --- a/src/renderer/src/hooks/composer-state/linked-item-lookup-effects.ts +++ b/src/renderer/src/hooks/composer-state/linked-item-lookup-effects.ts @@ -30,7 +30,7 @@ import { canUseRepoBackedComposerSources } from '@/lib/workspace/creation/new-wo import { useEffect } from 'react' import { PER_REPO_FETCH_LIMIT } from '../../../../shared/work-items/work-item-pagination' import { getDefaultSetupDecision } from '../../../../shared/orca-hook-setup-config' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { lookupGitHubWorkItemByOwnerRepoForSource, lookupGitHubWorkItemForSource diff --git a/src/renderer/src/hooks/composer-state/provider-runtime-sync.ts b/src/renderer/src/hooks/composer-state/provider-runtime-sync.ts index 5a27bb67bad..b3aa9596b5d 100644 --- a/src/renderer/src/hooks/composer-state/provider-runtime-sync.ts +++ b/src/renderer/src/hooks/composer-state/provider-runtime-sync.ts @@ -28,7 +28,7 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' import { type HookCheckResult, checkRuntimeHooks } from '@/runtime/repo-hooks/runtime-hooks-client' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' -import type { GitHubRepositoryIdentity } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../shared/github/repositories/repository-identity' import { getRepoSetupAgentStartupPolicy, buildSetupAgentStartupHookSettings diff --git a/src/renderer/src/hooks/composer-state/source-identity-actions.ts b/src/renderer/src/hooks/composer-state/source-identity-actions.ts index ecfc2b44209..78e2c67c80c 100644 --- a/src/renderer/src/hooks/composer-state/source-identity-actions.ts +++ b/src/renderer/src/hooks/composer-state/source-identity-actions.ts @@ -40,7 +40,7 @@ import { getLinkedWorkItemSuggestedName, getLinkedWorkItemWorkspaceName } from '../../../../shared/workspace-name' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import { shouldApplyWorkspaceSourceAutoName } from '../../../../shared/new-workspace/workspace-source' import { isLinearLinkedWorkItem } from '@/lib/work-items/linear/linear-linked-work-item' import { resolveComposerManualBranchNameChange } from '../composer-branch-selection' diff --git a/src/renderer/src/hooks/composer-state/source-selection-decisions.ts b/src/renderer/src/hooks/composer-state/source-selection-decisions.ts index ee5e4bf8f0e..8e182195439 100644 --- a/src/renderer/src/hooks/composer-state/source-selection-decisions.ts +++ b/src/renderer/src/hooks/composer-state/source-selection-decisions.ts @@ -1,5 +1,5 @@ import type { GitHubPrStartPoint, GitPushTarget } from '../../../../shared/worktree/types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import { getWorkspaceSourceProvider } from '../../../../shared/new-workspace/workspace-source' import type { LinkedWorkItemSummary } from '@/lib/workspace/creation/new-workspace' diff --git a/src/renderer/src/hooks/composer-state/target-store-model.ts b/src/renderer/src/hooks/composer-state/target-store-model.ts index 233b75fab9d..b28ae303641 100644 --- a/src/renderer/src/hooks/composer-state/target-store-model.ts +++ b/src/renderer/src/hooks/composer-state/target-store-model.ts @@ -1,5 +1,5 @@ import type { ExecutionHostId } from '../../../../shared/execution-host' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { ProjectGroup } from '../../../../shared/project-group-types' import type { Repo } from '../../../../shared/repo-types' import type { SshConnectionState } from '../../../../shared/ssh-types' diff --git a/src/renderer/src/hooks/use-pr-check-refresh-owner.ts b/src/renderer/src/hooks/use-pr-check-refresh-owner.ts index 6b03c69dabb..ddc3e8901bd 100644 --- a/src/renderer/src/hooks/use-pr-check-refresh-owner.ts +++ b/src/renderer/src/hooks/use-pr-check-refresh-owner.ts @@ -1,13 +1,16 @@ import { useCallback, useState } from 'react' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../shared/work-items/task-source-context' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../shared/work-items/task-source-context' + import { acceptPRCheckObservation, completePRCheckOwnerScope, projectPRCheckOwner, samePRCheckObservation -} from '../../../shared/github/complete-pr-check-owner' +} from '../../../shared/github/checks/complete-pr-check-owner' export function usePRCheckRefreshOwner(item: GitHubWorkItem, source?: TaskSourceContext | null) { const sourceKey = source ? getTaskSourceCacheScope(source) : 'local' diff --git a/src/renderer/src/hooks/useComposerState.ts b/src/renderer/src/hooks/useComposerState.ts index 2ac5e320549..5392913e265 100644 --- a/src/renderer/src/hooks/useComposerState.ts +++ b/src/renderer/src/hooks/useComposerState.ts @@ -1,7 +1,7 @@ import type { RefObject } from 'react' import '@/lib/workspace/creation/new-workspace' import type { LinkedWorkItemSummary } from '@/lib/workspace/creation/new-workspace' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' import type { TuiAgent } from '../../../shared/agent-launch/catalog/tui-agent' import type { QuickSubmitOptions } from './composer-state/quick-creation-request' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' diff --git a/src/renderer/src/hooks/useGitHubOperationScope.ts b/src/renderer/src/hooks/useGitHubOperationScope.ts index 4ee061ae3a7..d79e372ca3f 100644 --- a/src/renderer/src/hooks/useGitHubOperationScope.ts +++ b/src/renderer/src/hooks/useGitHubOperationScope.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { normalizeGhAccountBinding } from '../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../shared/github/accounts/account-binding' import { useShallow } from 'zustand/react/shallow' import { isWebClientLocation } from '@/app-shell/platform/web-client-location' import { webRuntimeState } from '@/web/preload-api/web-runtime-session' diff --git a/src/renderer/src/hooks/useGitHubRepoMetadata.ts b/src/renderer/src/hooks/useGitHubRepoMetadata.ts index 9c814be0a90..940b84d06b0 100644 --- a/src/renderer/src/hooks/useGitHubRepoMetadata.ts +++ b/src/renderer/src/hooks/useGitHubRepoMetadata.ts @@ -4,11 +4,9 @@ import { callGitHubTargetOperation } from '@/lib/work-items/github/github-work-i import { useGitHubOperationOwner } from './useGitHubOperationScope' import { useGitHubWorkItemTarget } from '@/components/github-item-dialog/load-item-details/github-work-item-target-context' import type { RuntimeClientTarget } from '../../../shared/runtime-client-target' -import type { - GitHubAssignableUser, - GitHubOwnerRepo -} from '../../../shared/github/pull-request-types' -import { githubRepoIdentityKey } from '../../../shared/github/repository-identity-key' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../shared/github/repositories/repository-identity' +import { githubRepoIdentityKey } from '../../../shared/github/repositories/repository-identity-key' import { createMetadataRequestStore } from './metadata-request-cache' import { useMetadataListRequest, type MetadataListState } from './useMetadataListRequest' diff --git a/src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx b/src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx index fbe7309ea43..4aa898b8a47 100644 --- a/src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx +++ b/src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx @@ -3,7 +3,7 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { GitHubAssignableUser } from '../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' import type { RuntimeClientTarget } from '../../../shared/runtime-client-target' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { clearGitHubSlugMetadataCache, useRepoAssigneesBySlug } from './useGitHubSlugMetadata' diff --git a/src/renderer/src/hooks/useGitHubSlugMetadata.ts b/src/renderer/src/hooks/useGitHubSlugMetadata.ts index 8657673579a..b1622b48b94 100644 --- a/src/renderer/src/hooks/useGitHubSlugMetadata.ts +++ b/src/renderer/src/hooks/useGitHubSlugMetadata.ts @@ -2,10 +2,10 @@ import { useGitHubOperationScope } from './useGitHubOperationScope' import { useMemo } from 'react' import { gitHubSlugApiForTarget } from '@/runtime/repository-github/github-slug-owner-api' import type { RuntimeClientTarget } from '../../../shared/runtime-client-target' -import type { GitHubAssignableUser } from '../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' import { clearMetadataRequestStore, createMetadataRequestStore } from './metadata-request-cache' -import { githubRepoIdentityKey } from '../../../shared/github/repository-identity-key' -import { githubProjectHost } from '../../../shared/github/project-identity' +import { githubRepoIdentityKey } from '../../../shared/github/repositories/repository-identity-key' +import { githubProjectHost } from '../../../shared/github/projects/project-identity' import { useMetadataListRequest, type MetadataListState } from './useMetadataListRequest' const slugLabelStore = createMetadataRequestStore() diff --git a/src/renderer/src/hooks/useGitHubTaskItems.ts b/src/renderer/src/hooks/useGitHubTaskItems.ts index 3ec3ad0f683..781094c683a 100644 --- a/src/renderer/src/hooks/useGitHubTaskItems.ts +++ b/src/renderer/src/hooks/useGitHubTaskItems.ts @@ -1,8 +1,8 @@ import { useMemo, useSyncExternalStore } from 'react' import { useAppStore } from '@/store' import { projectViewSourceScope } from '@/store/github/cache/cache-identity' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import type { GitHubProjectTable } from '../../../shared/github/project-types' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' +import type { GitHubProjectTable } from '../../../shared/github/projects/project-types' import type { RuntimeClientTarget } from '../../../shared/runtime-client-target' import { recomputeOn } from '@/lib/recompute-on' diff --git a/src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts b/src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts index 72983353fe3..3b6d9700e84 100644 --- a/src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts +++ b/src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts @@ -1,11 +1,14 @@ import { getWorkItemRepo } from '@/lib/work-items/work-item-repo-row' -import { buildTaskPageGitHubWorkItemMutationPatch } from '@/components/task-page/github/task-page-github-work-item-mutation-patches' +import { + buildTaskPageGitHubWorkItemMutationPatch, + type TaskPageGitHubMutationIntent +} from '@/components/task-page/github/task-page-github-work-item-mutation-patches' import { useCallback, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' import { parseExecutionHostId } from '../../../shared/execution-host' import type { ParsedTaskQuery } from '../../../shared/work-items/task-query' -import type { GitHubWorkItem } from '../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../shared/github/pull-requests/pull-request-types' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' import { useAppStore } from '@/store' import { @@ -16,7 +19,7 @@ import { confirmTaskPageGitHubWorkItemMutation, rollbackTaskPageGitHubWorkItemMutation } from '@/components/task-page/github/task-page-github-work-item-mutation-lifecycle' -import type { TaskPageGitHubMutationIntent } from '@/components/task-page/github/task-page-github-work-item-mutation-patches' + import type { TaskPageGitHubPatchWorkItem } from '@/components/task-page/github/task-page-github-work-item-mutation-types' import { getTaskPageGitHubSoftHiddenItemKeys, diff --git a/src/renderer/src/lib/markdown/remark-github-callouts.ts b/src/renderer/src/lib/markdown/remark-github-callouts.ts index 9377a5fda6c..da33e1d5056 100644 --- a/src/renderer/src/lib/markdown/remark-github-callouts.ts +++ b/src/renderer/src/lib/markdown/remark-github-callouts.ts @@ -1,5 +1,5 @@ import type { Root } from 'mdast' -import { forEachGitHubCallout } from '../../../../shared/github/github-callouts' +import { forEachGitHubCallout } from '../../../../shared/github/markdown/github-callouts' // Why: must run before remark-breaks, which splits the marker's newline into a break node. export function remarkGitHubCallouts(): (tree: Root) => void { diff --git a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-measurement-test-fixture.ts b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-measurement-test-fixture.ts index 7d7e28af23c..71cd91de82c 100644 --- a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-measurement-test-fixture.ts +++ b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-measurement-test-fixture.ts @@ -2,7 +2,7 @@ import { performance } from 'node:perf_hooks' import { normalizeCheckRunDetailsLogTails } from './check-run-details-log-normalization' import { checkLogDetails, checkLogJob } from './check-run-details-log-normalization-test-fixture' import { resetOwnRetainedStringCopier } from '../../../../../shared/own-retained-string' -import type { PRCheckRunDetails } from '../../../../../shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../../shared/github/checks/check-types' type NormalizationTiming = { inputKind: 'plain' | 'malformed' | 'multibyte' diff --git a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-test-fixture.ts b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-test-fixture.ts index aeadf2e431c..b243fde51c2 100644 --- a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-test-fixture.ts +++ b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-test-fixture.ts @@ -1,4 +1,4 @@ -import type { PRCheckJob, PRCheckRunDetails } from '../../../../../shared/github/check-types' +import type { PRCheckJob, PRCheckRunDetails } from '../../../../../shared/github/checks/check-types' import { createCheckLogFixture } from '../../../../../../config/scripts/performance/check-job-log/check-job-log-test-fixture.mjs' const captured = createCheckLogFixture() diff --git a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.ts b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.ts index b6772a35745..53058c03ee3 100644 --- a/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.ts +++ b/src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.ts @@ -1,4 +1,4 @@ -import type { PRCheckRunDetails } from '../../../../../shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../../shared/github/checks/check-types' import { sanitizeCheckJobLogExcerpt } from '../../../../../shared/source-control/checks/logs/check-job-log-excerpt' export function normalizeCheckRunDetailsLogTails( diff --git a/src/renderer/src/lib/source-control/checks/fix-checks-agent-launch-request.ts b/src/renderer/src/lib/source-control/checks/fix-checks-agent-launch-request.ts index edf6cc382d7..17163c26403 100644 --- a/src/renderer/src/lib/source-control/checks/fix-checks-agent-launch-request.ts +++ b/src/renderer/src/lib/source-control/checks/fix-checks-agent-launch-request.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { WorkspaceSource as WorkspaceCreateTelemetrySource } from '../../../../../shared/workspace-source' import type { LaunchSource } from '../../../../../shared/telemetry-property-schemas' diff --git a/src/renderer/src/lib/source-control/checks/pr-check-refresh-owner.ts b/src/renderer/src/lib/source-control/checks/pr-check-refresh-owner.ts index 95bfb0af7fd..09d7d09e3c3 100644 --- a/src/renderer/src/lib/source-control/checks/pr-check-refresh-owner.ts +++ b/src/renderer/src/lib/source-control/checks/pr-check-refresh-owner.ts @@ -1,6 +1,6 @@ -import { acceptPRCheckObservation } from '../../../../../shared/github/complete-pr-check-owner' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import { acceptPRCheckObservation } from '../../../../../shared/github/checks/complete-pr-check-owner' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import { capturePRCheckContract } from './pr-check-observation-contract' import { getGitHubSourceRuntimeTarget } from '../../work-items/github/github-source-runtime-context' diff --git a/src/renderer/src/lib/source-control/review/comments/pr-comment-action-state.ts b/src/renderer/src/lib/source-control/review/comments/pr-comment-action-state.ts index 7615d073525..8bbdeb953e6 100644 --- a/src/renderer/src/lib/source-control/review/comments/pr-comment-action-state.ts +++ b/src/renderer/src/lib/source-control/review/comments/pr-comment-action-state.ts @@ -2,7 +2,7 @@ import { getPRCommentGroupRoot, type PRCommentGroup } from '../../../../../../shared/source-control/review-comments/pr-comment-groups' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' /** How a comment group should read in the PR sidebar triage UI. */ export type PRCommentGroupActionState = 'open' | 'conversation' | 'resolved' diff --git a/src/renderer/src/lib/source-control/review/comments/pr-comment-reactions.ts b/src/renderer/src/lib/source-control/review/comments/pr-comment-reactions.ts index 91258bb4bb7..646d7b98ec9 100644 --- a/src/renderer/src/lib/source-control/review/comments/pr-comment-reactions.ts +++ b/src/renderer/src/lib/source-control/review/comments/pr-comment-reactions.ts @@ -2,7 +2,7 @@ import type { GitHubReaction, GitHubReactionContent, PRComment -} from '../../../../../../shared/github/comment-types' +} from '../../../../../../shared/github/comments/comment-types' export const GITHUB_REACTION_ORDER: readonly GitHubReactionContent[] = [ '+1', diff --git a/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts b/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts index 98e5e9fdc88..6c7b3815595 100644 --- a/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts +++ b/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' import { restorePRCommentThreadSnapshot } from './pr-comment-thread-resolution' function comment(overrides: Partial): PRComment { diff --git a/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.ts b/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.ts index 8654c8e6547..4bfa40f9e6d 100644 --- a/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.ts +++ b/src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../../shared/github/comments/comment-types' export function markPRCommentThreadResolved( comments: PRComment[], diff --git a/src/renderer/src/lib/work-items/github/github-task-target.ts b/src/renderer/src/lib/work-items/github/github-task-target.ts index f4d82dfad11..2098feb59b2 100644 --- a/src/renderer/src/lib/work-items/github/github-task-target.ts +++ b/src/renderer/src/lib/work-items/github/github-task-target.ts @@ -1,14 +1,17 @@ -import { normalizeGhAccountBinding } from '../../../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../../../shared/github/accounts/account-binding' import { githubTaskBinding } from './github-task-observation' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../shared/repo-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' + +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { parseHostAuthorityKey } from '../../../../../shared/host-authority' import type { AppState } from '@/store/types' diff --git a/src/renderer/src/lib/work-items/github/github-work-item-source-lookup.ts b/src/renderer/src/lib/work-items/github/github-work-item-source-lookup.ts index 39d33aff4a7..c97455da1e6 100644 --- a/src/renderer/src/lib/work-items/github/github-work-item-source-lookup.ts +++ b/src/renderer/src/lib/work-items/github/github-work-item-source-lookup.ts @@ -2,16 +2,16 @@ import { capturePRCheckContract, type PRCheckContract } from '../../source-control/checks/pr-check-observation-contract' -import { acceptPRCheckObservation } from '../../../../../shared/github/complete-pr-check-owner' -import type { GhAccountBinding } from '../../../../../shared/github/account-binding' +import { acceptPRCheckObservation } from '../../../../../shared/github/checks/complete-pr-check-owner' +import type { GhAccountBinding } from '../../../../../shared/github/accounts/account-binding' import { getWorkItemRepo } from '../work-item-repo-row' import type { GitHubWorkItem, GitHubWorkItemDetails -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import { callGitHubTargetOperation } from './github-work-item-target-request' import { @@ -19,7 +19,7 @@ import { matchesGitHubWorkItemTarget, GitHubWorkItemTargetError, requireGitHubWorkItemTargetDetails -} from '../../../../../shared/github/work-item-target' +} from '../../../../../shared/github/work-items/work-item-target' import { getGitHubRuntimeRepoId, getGitHubSourceRuntimeHost, diff --git a/src/renderer/src/lib/work-items/github/github-work-item-target-request.ts b/src/renderer/src/lib/work-items/github/github-work-item-target-request.ts index 8b14c802f09..cde6dce36f1 100644 --- a/src/renderer/src/lib/work-items/github/github-work-item-target-request.ts +++ b/src/renderer/src/lib/work-items/github/github-work-item-target-request.ts @@ -1,8 +1,8 @@ -import type { GhAccountBinding } from '../../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../../shared/github/accounts/account-binding' import { isWebClientLocation } from '../../../app-shell/platform/web-client-location' import { GITHUB_EXACT_WORK_ITEM_TARGET_RUNTIME_CAPABILITY } from '../../../../../shared/source-control/runtime/source-control-runtime-capabilities' -import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../../shared/github/work-item-target' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import { GITHUB_EXACT_OPERATION_UNSUPPORTED } from '../../../../../shared/github/work-items/work-item-target' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import type { RuntimeStatus } from '../../../../../shared/runtime-session-contracts' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' diff --git a/src/renderer/src/lib/work-items/github/github-work-item-workspace-attachment.ts b/src/renderer/src/lib/work-items/github/github-work-item-workspace-attachment.ts index 6544461f53d..53d35ca6a7b 100644 --- a/src/renderer/src/lib/work-items/github/github-work-item-workspace-attachment.ts +++ b/src/renderer/src/lib/work-items/github/github-work-item-workspace-attachment.ts @@ -1,9 +1,9 @@ import type { Repo } from '../../../../../shared/repo-types' import { normalizeGitRemoteUrl } from '../../../../../shared/git/remotes/git-remote-identity' import { getWorkspaceAttachmentUrlScope } from '../../../../../shared/workspace-attachments/workspace-attachment-normalization' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { getWorkspaceAttachments } from '../../../../../shared/workspace-attachments/workspace-attachments' import type { Worktree } from '../../../../../shared/worktree/types' import { parseWorkspaceKey } from '../../../../../shared/workspace-scope' diff --git a/src/renderer/src/lib/work-items/github/smart-github-submit.ts b/src/renderer/src/lib/work-items/github/smart-github-submit.ts index 6a2842cb9c2..986e16793a6 100644 --- a/src/renderer/src/lib/work-items/github/smart-github-submit.ts +++ b/src/renderer/src/lib/work-items/github/smart-github-submit.ts @@ -1,12 +1,15 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitPushTarget } from '../../../../../shared/worktree/types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' + import { getLinkedWorkItemWorkspaceName } from '../../../../../shared/workspace-name' import type { LinkedWorkItemSummary } from '../../workspace/creation/new-workspace' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { resolveGitHubWorkItemIdentity } from '../../../../../shared/new-workspace/github-work-item-identity' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' export type SmartGitHubSubmitIntent = | { diff --git a/src/renderer/src/lib/workspace/catalog/repo-slug-cache.ts b/src/renderer/src/lib/workspace/catalog/repo-slug-cache.ts index 3177e279f23..22e71a0dbf6 100644 --- a/src/renderer/src/lib/workspace/catalog/repo-slug-cache.ts +++ b/src/renderer/src/lib/workspace/catalog/repo-slug-cache.ts @@ -5,7 +5,7 @@ import { runtimeTargetForRepoOwner } from '../ownership/repo-runtime-owner' import { githubRepoIdentityKey, repoUpstreamIdentityKey -} from '../../../../../shared/github/repository-identity-key' +} from '../../../../../shared/github/repositories/repository-identity-key' /** Lowercased `owner/repo` → Repo[]. */ export type SlugIndex = Map diff --git a/src/renderer/src/lib/workspace/catalog/repo-slug-index.ts b/src/renderer/src/lib/workspace/catalog/repo-slug-index.ts index 0070e4d9009..c6b2e0b802c 100644 --- a/src/renderer/src/lib/workspace/catalog/repo-slug-index.ts +++ b/src/renderer/src/lib/workspace/catalog/repo-slug-index.ts @@ -18,7 +18,7 @@ import { import { githubRepoIdentityKey, repoUpstreamIdentityKey -} from '../../../../../shared/github/repository-identity-key' +} from '../../../../../shared/github/repositories/repository-identity-key' import { createBoundedGenerationMap } from '../../bounded-generation-map' const slugResolutionInFlight = new Map>() diff --git a/src/renderer/src/lib/workspace/search/palette/cmd-j-github-url-lookup.ts b/src/renderer/src/lib/workspace/search/palette/cmd-j-github-url-lookup.ts index 087b8b60703..993542ac70d 100644 --- a/src/renderer/src/lib/workspace/search/palette/cmd-j-github-url-lookup.ts +++ b/src/renderer/src/lib/workspace/search/palette/cmd-j-github-url-lookup.ts @@ -1,5 +1,5 @@ -import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/links' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubIssueOrPRLink } from '../../../../../../shared/github/repositories/links' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../../shared/repo-types' import type { TaskSourceContext } from '../../../../../../shared/work-items/task-source-context' import { lookupGitHubWorkItemByOwnerRepoForSource } from '../../../work-items/github/github-work-item-source-lookup' diff --git a/src/renderer/src/lib/workspace/search/worktrees/worktree-palette-task-url-match.ts b/src/renderer/src/lib/workspace/search/worktrees/worktree-palette-task-url-match.ts index 6752f99c1bf..9eee29ffc3a 100644 --- a/src/renderer/src/lib/workspace/search/worktrees/worktree-palette-task-url-match.ts +++ b/src/renderer/src/lib/workspace/search/worktrees/worktree-palette-task-url-match.ts @@ -9,8 +9,8 @@ import { parseGitHubIssueOrPRLink, type GitHubIssueOrPRLink, type RepoSlug -} from '../../../../../../shared/github/links' -import { githubRepoIdentityKey } from '../../../../../../shared/github/repository-identity-key' +} from '../../../../../../shared/github/repositories/links' +import { githubRepoIdentityKey } from '../../../../../../shared/github/repositories/repository-identity-key' import { parseGitLabIssueOrMRLink } from '../../../../../../shared/new-workspace/gitlab-links' import { parseJiraIssueUrl, diff --git a/src/renderer/src/runtime/repository-github/github-check-details-timeout.ts b/src/renderer/src/runtime/repository-github/github-check-details-timeout.ts index 334223dca8e..ceb57f39181 100644 --- a/src/renderer/src/runtime/repository-github/github-check-details-timeout.ts +++ b/src/renderer/src/runtime/repository-github/github-check-details-timeout.ts @@ -2,7 +2,7 @@ import { translate } from '@/i18n/i18n' import { GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE, isGitHubCheckDetailsTimeout -} from '../../../../shared/github/check-details-deadline' +} from '../../../../shared/github/checks/check-details-deadline' export const GITHUB_CHECK_DETAILS_TIMEOUT_MS = 30_000 diff --git a/src/renderer/src/runtime/repository-github/repository-github-account.ts b/src/renderer/src/runtime/repository-github/repository-github-account.ts index 339e010567e..65fec426fad 100644 --- a/src/renderer/src/runtime/repository-github/repository-github-account.ts +++ b/src/renderer/src/runtime/repository-github/repository-github-account.ts @@ -2,11 +2,11 @@ import type { Repo } from '../../../../shared/repo-types' import type { GhAccountBindingInventory, GhAccountBindingValidationResult -} from '../../../../shared/github/auth-types' +} from '../../../../shared/github/accounts/auth-types' import { type GhAccountBinding, ghAccountBindingsEqual -} from '../../../../shared/github/account-binding' +} from '../../../../shared/github/accounts/account-binding' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' diff --git a/src/renderer/src/runtime/repository-github/repository-icon-github.ts b/src/renderer/src/runtime/repository-github/repository-icon-github.ts index 933f9c1b093..df30047e6e5 100644 --- a/src/renderer/src/runtime/repository-github/repository-icon-github.ts +++ b/src/renderer/src/runtime/repository-github/repository-icon-github.ts @@ -1,7 +1,7 @@ -import type { GitHubRepositoryIdentity } from '../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../../../shared/github/repositories/repository-identity' import type { Repo } from '../../../../shared/repo-types' import { githubAvatarIcon, githubAvatarSlug, type RepoIcon } from '../../../../shared/repo-icon' -import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../shared/github/repositories/repository-identity-key' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' diff --git a/src/renderer/src/runtime/repository-gitlab/gitlab-job-trace-client.ts b/src/renderer/src/runtime/repository-gitlab/gitlab-job-trace-client.ts index 6b5218c7b76..8634e15d2a2 100644 --- a/src/renderer/src/runtime/repository-gitlab/gitlab-job-trace-client.ts +++ b/src/renderer/src/runtime/repository-gitlab/gitlab-job-trace-client.ts @@ -4,7 +4,7 @@ import { gitLabJobTraceToCheckRunDetails } from '../../../../shared/source-control/checks/gitlab/gitlab-job-trace-check-details' import type { GitLabProjectRef } from '../../../../shared/gitlab-types' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../shared/github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../../../shared/github/checks/check-types' import { gitLabApiFor, JOB_TRACE_TIMEOUT_MS } from './gitlab-owner-api' import { withGitLabIpcTimeout } from './gitlab-ipc-timeout' import { codeHostErrorText } from '@/lib/source-control/remote/code-host-error-copy' diff --git a/src/renderer/src/store/github/cache/cache-identity.ts b/src/renderer/src/store/github/cache/cache-identity.ts index e2f8b7832a3..1b44ca602db 100644 --- a/src/renderer/src/store/github/cache/cache-identity.ts +++ b/src/renderer/src/store/github/cache/cache-identity.ts @@ -1,11 +1,14 @@ -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' -import { githubProjectIdentityKey } from '../../../../../shared/github/project-identity' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' +import { githubProjectIdentityKey } from '../../../../../shared/github/projects/project-identity' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import type { GetProjectViewTableArgs } from '../../../../../shared/github/project-request-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' +import type { GetProjectViewTableArgs } from '../../../../../shared/github/projects/project-request-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' + import { LOCAL_EXECUTION_HOST_ID, getRepoExecutionHostId, @@ -17,7 +20,7 @@ import { getGitHubRepoCacheKey, repoCacheKeyHostId, type RuntimeFocusSettings -} from '../../../../../shared/github/github-cache-key' +} from '../../../../../shared/github/cache/github-cache-key' import type { Repo } from '../../../../../shared/repo-types' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import type { CacheEntry } from './cache-model' diff --git a/src/renderer/src/store/github/cache/cache-model.ts b/src/renderer/src/store/github/cache/cache-model.ts index 65c4d27414e..d3b6d0bcf0e 100644 --- a/src/renderer/src/store/github/cache/cache-model.ts +++ b/src/renderer/src/store/github/cache/cache-model.ts @@ -1,7 +1,7 @@ import type { ClassifiedError } from '../../../../../shared/classified-error' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' -import type { GitHubPRRefreshAlias } from '../../../../../shared/github/pull-request-refresh-types' -import type { GitHubProjectViewError } from '../../../../../shared/github/project-result-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' +import type { GitHubPRRefreshAlias } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { GitHubProjectViewError } from '../../../../../shared/github/projects/project-result-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' export type WorkItemsCacheSources = { diff --git a/src/renderer/src/store/github/checks/check-actions.ts b/src/renderer/src/store/github/checks/check-actions.ts index 5208d02f821..8eb0f7cb5a0 100644 --- a/src/renderer/src/store/github/checks/check-actions.ts +++ b/src/renderer/src/store/github/checks/check-actions.ts @@ -2,7 +2,10 @@ import { capturePRCheckContract } from '@/lib/source-control/checks/pr-check-obs import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../../shared/github/check-types' +import type { + PRCheckDetail, + PRCheckRunDetails +} from '../../../../../shared/github/checks/check-types' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { withGitHubCheckDetailsTimeout } from '@/runtime/repository-github/github-check-details-timeout' import { createRuntimeRpcAbortError } from '@/runtime/rpc/calls/abortable-runtime-environment-call' @@ -11,7 +14,7 @@ import { translate } from '@/i18n/i18n' import { GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE, isGitHubCheckDetailsTimeout -} from '../../../../../shared/github/check-details-deadline' +} from '../../../../../shared/github/checks/check-details-deadline' import { syncPRChecksStatus } from './github-checks' import { isWslDistroNotRunningRefusal } from '../../../../../shared/wsl-distro-not-running-refusal' import { findRepoForGitHubOwner } from '../repositories/repository-routing' diff --git a/src/renderer/src/store/github/checks/github-checks.ts b/src/renderer/src/store/github/checks/github-checks.ts index c0c12cb2f5a..14cd22c3946 100644 --- a/src/renderer/src/store/github/checks/github-checks.ts +++ b/src/renderer/src/store/github/checks/github-checks.ts @@ -1,12 +1,12 @@ import { getHostedReviewCacheKey } from '../../slices/hosted-review/hosted-review-cache-identity' import type { AppState } from '../../types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' import { getGitHubPRCacheKey, type RuntimeFocusSettings -} from '../../../../../shared/github/github-cache-key' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +} from '../../../../../shared/github/cache/github-cache-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { derivePRCheckStatus } from '../../../../../shared/source-control/checks/pr-check-status' export function normalizeBranchName(branch: string): string { diff --git a/src/renderer/src/store/github/checks/pr-check-owner-publication.test.ts b/src/renderer/src/store/github/checks/pr-check-owner-publication.test.ts index 280b78abaf0..d1880eebebd 100644 --- a/src/renderer/src/store/github/checks/pr-check-owner-publication.test.ts +++ b/src/renderer/src/store/github/checks/pr-check-owner-publication.test.ts @@ -9,9 +9,9 @@ import { } from '../github-slice-test-harness' import { setLocalRuntimeCapabilitiesForTests } from '../../../runtime/hosts/capabilities/local-runtime-capabilities' import { GITHUB_COMPLETE_PR_CHECK_OBSERVATION_CAPABILITY as capability } from '../../../../../shared/source-control/runtime/source-control-runtime-capabilities' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' -import { isCompletePRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' +import { isCompletePRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import { createCompatibleRuntimeStatusResponse } from '../../../runtime/rpc/testing/runtime-compatibility-test-fixture' import { getOverallCheckCounts } from '../../../components/pr-check-counts' diff --git a/src/renderer/src/store/github/checks/pr-check-status-cache.ts b/src/renderer/src/store/github/checks/pr-check-status-cache.ts index d5f8d85015f..72ad89e0b6e 100644 --- a/src/renderer/src/store/github/checks/pr-check-status-cache.ts +++ b/src/renderer/src/store/github/checks/pr-check-status-cache.ts @@ -1,11 +1,11 @@ import { capturePRCheckContract } from '@/lib/source-control/checks/pr-check-observation-contract' -import { isCompletePRCheckOwner } from '../../../../../shared/github/complete-pr-check-owner' +import { isCompletePRCheckOwner } from '../../../../../shared/github/checks/complete-pr-check-owner' import type { AppState } from '../../types' -import type { GitHubPRRefreshAlias } from '../../../../../shared/github/pull-request-refresh-types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { GitHubPRRefreshAlias } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import { derivePRCheckStatus as deriveCheckStatusFromChecks } from '../../../../../shared/source-control/checks/pr-check-status' import { getPRChecksCacheTtl, prChecksCacheSuffix } from '../cache/cache-identity' -import { getGitHubRepoCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubRepoCacheKey } from '../../../../../shared/github/cache/github-cache-key' export function applyCachedChecksStatus( state: AppState, diff --git a/src/renderer/src/store/github/comments/comment-fetch-actions.ts b/src/renderer/src/store/github/comments/comment-fetch-actions.ts index ea12eb4897d..5fdd219ed43 100644 --- a/src/renderer/src/store/github/comments/comment-fetch-actions.ts +++ b/src/renderer/src/store/github/comments/comment-fetch-actions.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { prCommentsCacheSuffix, sourceScopedRepoCacheKey } from '../cache/cache-identity' import { isFresh, withBoundedCacheEntry } from '../cache/cache-policy' diff --git a/src/renderer/src/store/github/comments/comment-mutation-actions.ts b/src/renderer/src/store/github/comments/comment-mutation-actions.ts index 9f31323fc00..5a7f587b24e 100644 --- a/src/renderer/src/store/github/comments/comment-mutation-actions.ts +++ b/src/renderer/src/store/github/comments/comment-mutation-actions.ts @@ -1,7 +1,10 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GitHubCommentResult, PRComment } from '../../../../../shared/github/comment-types' +import type { + GitHubCommentResult, + PRComment +} from '../../../../../shared/github/comments/comment-types' import { translate } from '@/i18n/i18n' import { findRepoForGitHubOwner } from '../repositories/repository-routing' import { hostedReviewOwnerIpcArgs } from '../../slices/hosted-review/hosted-review-cache-state' diff --git a/src/renderer/src/store/github/comments/conversation-comment-actions.ts b/src/renderer/src/store/github/comments/conversation-comment-actions.ts index 4db941bee41..63c5c313642 100644 --- a/src/renderer/src/store/github/comments/conversation-comment-actions.ts +++ b/src/renderer/src/store/github/comments/conversation-comment-actions.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GitHubCommentResult } from '../../../../../shared/github/comment-types' +import type { GitHubCommentResult } from '../../../../../shared/github/comments/comment-types' import { translate } from '@/i18n/i18n' import { findRepoForGitHubOwner } from '../repositories/repository-routing' import { hostedReviewOwnerIpcArgs } from '../../slices/hosted-review/hosted-review-cache-state' diff --git a/src/renderer/src/store/github/comments/github-pr-comment-read-order.test.ts b/src/renderer/src/store/github/comments/github-pr-comment-read-order.test.ts index 99e15234c7b..bb0f3869e7f 100644 --- a/src/renderer/src/store/github/comments/github-pr-comment-read-order.test.ts +++ b/src/renderer/src/store/github/comments/github-pr-comment-read-order.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, it, vi } from 'vitest' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' const repoPath = '/repo' diff --git a/src/renderer/src/store/github/comments/github-pr-comments.test.ts b/src/renderer/src/store/github/comments/github-pr-comments.test.ts index a221453ec6f..2947322e6e4 100644 --- a/src/renderer/src/store/github/comments/github-pr-comments.test.ts +++ b/src/renderer/src/store/github/comments/github-pr-comments.test.ts @@ -1,73 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRComment } from '../../../../../shared/github/comment-types' + import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' import type { AppState } from '../../types' -describe('createGitHubSlice.fetchPRComments', () => { - beforeEach(() => { - vi.clearAllMocks() - resetRemoteRuntimeMocks() - mockApi.gh.prComments.mockResolvedValue([]) - }) - - it.each([false, true])( - 'an explicit refresh replaces a pre-click read and keeps comments posted during either (click fails: %s)', - async (clickFails) => { - const store = createTestStore() - const comment = (id: number, body: string): PRComment => ({ - id, - author: 'octocat', - authorAvatarUrl: '', - body, - createdAt: `2026-08-23T00:00:0${id}Z`, - url: '' - }) - const old = Promise.withResolvers() - const fresh = Promise.withResolvers() - mockApi.gh.prComments.mockReturnValueOnce(old.promise).mockReturnValueOnce(fresh.promise) - mockApi.gh.addIssueComment - .mockResolvedValueOnce({ ok: true, comment: comment(2, 'posted before') }) - .mockResolvedValueOnce({ ok: true, comment: comment(3, 'posted during') }) - const options = { repoId: 'repo-1' } - const before = store.getState().fetchPRComments('/repo/one', 12, options) - let click: Promise = Promise.resolve([]) - try { - await store.getState().addPRConversationComment('/repo/one', 12, 'posted before', options) - click = store - .getState() - .fetchPRComments('/repo/one', 12, { ...options, force: true, userRefresh: true }) - await store.getState().addPRConversationComment('/repo/one', 12, 'posted during', options) - - expect(mockApi.gh.prComments).toHaveBeenLastCalledWith( - expect.objectContaining({ noCache: true }) - ) - if (clickFails) { - // The pre-click read's answer is still newer than the cache, so it lands instead. - fresh.reject(new Error('lost reply')) - old.resolve([comment(1, 'old')]) - } else { - fresh.resolve([comment(1, 'new')]) - } - const shown = await click - expect(shown.map((entry) => entry.body).sort()).toEqual([ - clickFails ? 'old' : 'new', - 'posted before', - 'posted during' - ]) - old.resolve([comment(1, 'old')]) - await expect(before).resolves.toEqual(shown) - expect(Object.values(store.getState().commentsCache).map((entry) => entry.data)).toEqual([ - shown - ]) - } finally { - old.resolve([]) - fresh.resolve([]) - await Promise.all([before, click]) - } - } - ) -}) - describe('createGitHubSlice PR comment mutations', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/src/renderer/src/store/github/comments/pr-comment-cache.ts b/src/renderer/src/store/github/comments/pr-comment-cache.ts index f6da762890d..dc532e85f4f 100644 --- a/src/renderer/src/store/github/comments/pr-comment-cache.ts +++ b/src/renderer/src/store/github/comments/pr-comment-cache.ts @@ -1,4 +1,7 @@ -import type { GitHubCommentResult, PRComment } from '../../../../../shared/github/comment-types' +import type { + GitHubCommentResult, + PRComment +} from '../../../../../shared/github/comments/comment-types' function commentTimestamp(comment: PRComment): number { const timestamp = new Date(comment.createdAt).getTime() diff --git a/src/renderer/src/store/github/github-slice-contract.ts b/src/renderer/src/store/github/github-slice-contract.ts index 31500796fd5..182a10164f0 100644 --- a/src/renderer/src/store/github/github-slice-contract.ts +++ b/src/renderer/src/store/github/github-slice-contract.ts @@ -1,32 +1,29 @@ import type { GitHubSourceCollections } from './sources/source-collections' import type { GitHubTaskRecordCache } from './task-records/task-record-cache' import type { ClassifiedError } from '../../../../shared/classified-error' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../../shared/github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../../../shared/github/checks/check-types' import type { GitHubCommentResult, GitHubReactionContent, PRComment -} from '../../../../shared/github/comment-types' +} from '../../../../shared/github/comments/comment-types' import type { GitHubPRRefreshEvent, GitHubPRRefreshReason -} from '../../../../shared/github/pull-request-refresh-types' -import type { - GitHubOwnerRepo, - IssueInfo, - PRInfo -} from '../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../shared/github/repositories/repository-identity' +import type { IssueInfo, PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../shared/github/work-items/work-item-types' import type { IssueSourcePreference } from '../../../../shared/repo-types' import type { GitHubProjectFieldMutationValue, GitHubProjectTable -} from '../../../../shared/github/project-types' +} from '../../../../shared/github/projects/project-types' import type { GetProjectViewTableResult, GitHubProjectMutationResult -} from '../../../../shared/github/project-result-types' -import type { GetProjectViewTableArgs } from '../../../../shared/github/project-request-types' +} from '../../../../shared/github/projects/project-result-types' +import type { GetProjectViewTableArgs } from '../../../../shared/github/projects/project-request-types' import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' diff --git a/src/renderer/src/store/github/github-slice-test-harness.ts b/src/renderer/src/store/github/github-slice-test-harness.ts index 8d7115f85fb..cf43db8cbae 100644 --- a/src/renderer/src/store/github/github-slice-test-harness.ts +++ b/src/renderer/src/store/github/github-slice-test-harness.ts @@ -3,7 +3,7 @@ import { create } from 'zustand' import { createGitHubSlice } from './github' import { createHostedReviewSlice } from '../slices/hosted-review/hosted-review' import type { AppState } from '../types' -import type { PRInfo } from '../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../shared/github/pull-requests/pull-request-types' import type { Worktree } from '../../../../shared/worktree/types' import { createCompatibleRuntimeStatusResponseIfNeeded, @@ -14,7 +14,7 @@ import type { TaskSourceContext } from '../../../../shared/work-items/task-sourc import type { GitHubPRRefreshCandidate, GitHubPRRefreshReason -} from '../../../../shared/github/pull-request-refresh-types' +} from '../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' /** Bare `vi.fn()` infers @vitest/spy's un-nameable `Procedure`, which breaks declaration emit. */ export type StubMock = Mock<(...args: TArgs) => unknown> diff --git a/src/renderer/src/store/github/issues/issue-actions.ts b/src/renderer/src/store/github/issues/issue-actions.ts index 8052fc56269..9ba7b9fd8ea 100644 --- a/src/renderer/src/store/github/issues/issue-actions.ts +++ b/src/renderer/src/store/github/issues/issue-actions.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { IssueInfo } from '../../../../../shared/github/pull-request-types' +import type { IssueInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import { isWslDistroNotRunningRefusal } from '../../../../../shared/wsl-distro-not-running-refusal' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { sourceScopedRepoCacheKey } from '../cache/cache-identity' diff --git a/src/renderer/src/store/github/projects/project-actions.ts b/src/renderer/src/store/github/projects/project-actions.ts index 510c2ca35ec..0fecb084504 100644 --- a/src/renderer/src/store/github/projects/project-actions.ts +++ b/src/renderer/src/store/github/projects/project-actions.ts @@ -2,7 +2,7 @@ import { translate } from '@/i18n/i18n' import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GetProjectViewTableResult } from '../../../../../shared/github/project-result-types' +import type { GetProjectViewTableResult } from '../../../../../shared/github/projects/project-result-types' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { projectViewCacheKey, diff --git a/src/renderer/src/store/github/projects/project-cache.ts b/src/renderer/src/store/github/projects/project-cache.ts index d8723f95a56..f80a0473548 100644 --- a/src/renderer/src/store/github/projects/project-cache.ts +++ b/src/renderer/src/store/github/projects/project-cache.ts @@ -3,7 +3,7 @@ import type { GitHubProjectFieldMutationValue, GitHubProjectRow, GitHubProjectTable -} from '../../../../../shared/github/project-types' +} from '../../../../../shared/github/projects/project-types' // Patch field optimistically for immediate render; next authoritative refresh replaces it. export function optimisticFieldValueFromMutation( diff --git a/src/renderer/src/store/github/projects/project-field-mutations.test.ts b/src/renderer/src/store/github/projects/project-field-mutations.test.ts index 3a455893f3f..d56f4944063 100644 --- a/src/renderer/src/store/github/projects/project-field-mutations.test.ts +++ b/src/renderer/src/store/github/projects/project-field-mutations.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' import { projectViewCacheKey } from '../cache/cache-identity' -import type { GitHubProjectTable } from '../../../../../shared/github/project-types' -import type { GitHubProjectMutationResult } from '../../../../../shared/github/project-result-types' +import type { GitHubProjectTable } from '../../../../../shared/github/projects/project-types' +import type { GitHubProjectMutationResult } from '../../../../../shared/github/projects/project-result-types' const key = projectViewCacheKey('user', 'owner', 1, 'view') const failed: GitHubProjectMutationResult = { diff --git a/src/renderer/src/store/github/projects/project-field-mutations.ts b/src/renderer/src/store/github/projects/project-field-mutations.ts index 1dcf6e51d98..4c87d43280d 100644 --- a/src/renderer/src/store/github/projects/project-field-mutations.ts +++ b/src/renderer/src/store/github/projects/project-field-mutations.ts @@ -5,13 +5,13 @@ import type { GitHubProjectFieldMutationValue, GitHubProjectFieldValue, GitHubProjectView -} from '../../../../../shared/github/project-types' -import type { GitHubProjectMutationResult } from '../../../../../shared/github/project-result-types' +} from '../../../../../shared/github/projects/project-types' +import type { GitHubProjectMutationResult } from '../../../../../shared/github/projects/project-result-types' import { translate } from '@/i18n/i18n' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { projectViewCacheKeyTarget, projectViewSourceScope } from '../cache/cache-identity' import { applyRowPatch, optimisticFieldValueFromMutation } from './project-cache' -import { githubProjectHost } from '../../../../../shared/github/project-identity' +import { githubProjectHost } from '../../../../../shared/github/projects/project-identity' export function createProjectFieldActions( set: Parameters>[0], diff --git a/src/renderer/src/store/github/projects/project-row-actions.ts b/src/renderer/src/store/github/projects/project-row-actions.ts index 9e605da22bc..bf521639e08 100644 --- a/src/renderer/src/store/github/projects/project-row-actions.ts +++ b/src/renderer/src/store/github/projects/project-row-actions.ts @@ -1,8 +1,8 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GitHubProjectRow } from '../../../../../shared/github/project-types' -import type { GitHubProjectMutationResult } from '../../../../../shared/github/project-result-types' +import type { GitHubProjectRow } from '../../../../../shared/github/projects/project-types' +import type { GitHubProjectMutationResult } from '../../../../../shared/github/projects/project-result-types' import type { ProjectRowContentPatch } from './project-row-content' import { translate } from '@/i18n/i18n' import { gitHubSlugApiForTarget } from '../../../runtime/repository-github/github-slug-owner-api' diff --git a/src/renderer/src/store/github/pull-requests/github-linked-review-clear-ownership.test.ts b/src/renderer/src/store/github/pull-requests/github-linked-review-clear-ownership.test.ts index 1953641f01e..e6cd91a1850 100644 --- a/src/renderer/src/store/github/pull-requests/github-linked-review-clear-ownership.test.ts +++ b/src/renderer/src/store/github/pull-requests/github-linked-review-clear-ownership.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../../shared/repo-types' import { createWorktreeIdentity } from '../../../../../shared/worktree/identity' -import { getGitHubPRCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { forgeOwnerHostIdForWorkspace } from '../../../runtime/source-control/forge/forge-credential-target' import { _clearGitHubPRRefreshStartedEntriesForTest } from '../requests/request-coordination' import { createUpdateWorktreeMeta } from '../../slices/worktrees/metadata/update-worktree-meta' diff --git a/src/renderer/src/store/github/pull-requests/github-pr-request-lifetime.test.ts b/src/renderer/src/store/github/pull-requests/github-pr-request-lifetime.test.ts index 05dbfd926da..e97a508a13f 100644 --- a/src/renderer/src/store/github/pull-requests/github-pr-request-lifetime.test.ts +++ b/src/renderer/src/store/github/pull-requests/github-pr-request-lifetime.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PRRefreshOutcome } from '../../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshOutcome } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { createTestStore, makePR, diff --git a/src/renderer/src/store/github/pull-requests/linked-review-clear.ts b/src/renderer/src/store/github/pull-requests/linked-review-clear.ts index 2e60229f851..1e0779f8957 100644 --- a/src/renderer/src/store/github/pull-requests/linked-review-clear.ts +++ b/src/renderer/src/store/github/pull-requests/linked-review-clear.ts @@ -3,7 +3,7 @@ import { CLEARED_HOSTED_REVIEW_SELECTION_UPDATES } from '../../../../../shared/w import { shouldApplyBranchMismatchedLinkedPRClear, shouldApplyDivergedLinkedPRClear -} from '../../../../../shared/github/linked-pr-staleness' +} from '../../../../../shared/github/pull-requests/refresh/linked-pr-staleness' import { getHostedReviewLinkMutationGeneration } from '../../slices/worktrees/metadata/hosted-review-link-mutation' import { tryRuntimeTargetForWorktreeOwner } from '../../slices/worktrees/listing/worktree-owner-target' import { hasUniqueIndexedRepositoryHost } from '@/lib/workspace/ownership/repository-owner-index' diff --git a/src/renderer/src/store/github/pull-requests/pr-for-branch-ipc-fallback.ts b/src/renderer/src/store/github/pull-requests/pr-for-branch-ipc-fallback.ts index 03d31b88668..1bb7cfe552e 100644 --- a/src/renderer/src/store/github/pull-requests/pr-for-branch-ipc-fallback.ts +++ b/src/renderer/src/store/github/pull-requests/pr-for-branch-ipc-fallback.ts @@ -1,7 +1,7 @@ import type { GitHubPRRefreshCandidate, GitHubPRRefreshReason -} from '../../../../../shared/github/pull-request-refresh-types' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' /** The direct lookup when main has no refresh coordinator; only a manual one may start a stopped WSL distro (E9). */ export function lookupPRForBranchOverIpc( diff --git a/src/renderer/src/store/github/pull-requests/pr-refresh-cached-candidate-fields.ts b/src/renderer/src/store/github/pull-requests/pr-refresh-cached-candidate-fields.ts index 7365ab361eb..4631e2bb17f 100644 --- a/src/renderer/src/store/github/pull-requests/pr-refresh-cached-candidate-fields.ts +++ b/src/renderer/src/store/github/pull-requests/pr-refresh-cached-candidate-fields.ts @@ -1,5 +1,5 @@ -import type { PRInfo } from '../../../../../shared/github/pull-request-types' -import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-request-refresh-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { CacheEntry } from '../cache/cache-model' /** What a PR refresh candidate tells main about the cached answer it may keep. */ diff --git a/src/renderer/src/store/github/pull-requests/pr-result-cache.ts b/src/renderer/src/store/github/pull-requests/pr-result-cache.ts index 07d1de53d35..fafc8f2f5f7 100644 --- a/src/renderer/src/store/github/pull-requests/pr-result-cache.ts +++ b/src/renderer/src/store/github/pull-requests/pr-result-cache.ts @@ -2,12 +2,12 @@ import type { PRCheckContract } from '@/lib/source-control/checks/pr-check-obser import { acceptPRCheckObservation, completePRCheckOwnerScope -} from '../../../../../shared/github/complete-pr-check-owner' +} from '../../../../../shared/github/checks/complete-pr-check-owner' import { applyCachedChecksStatus } from '../checks/pr-check-status-cache' import type { LinkedReviewClearScope } from './linked-review-clear' import type { AppState } from '../../types' -import type { RuntimeFocusSettings } from '../../../../../shared/github/github-cache-key' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { RuntimeFocusSettings } from '../../../../../shared/github/cache/github-cache-key' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { getHostedReviewCacheKey } from '../../slices/hosted-review/hosted-review-cache-identity' import type { GitHubPRFallbackSource } from '../cache/cache-model' diff --git a/src/renderer/src/store/github/pull-requests/pr-result-routing.ts b/src/renderer/src/store/github/pull-requests/pr-result-routing.ts index 1be7a3e7882..65085b7e75e 100644 --- a/src/renderer/src/store/github/pull-requests/pr-result-routing.ts +++ b/src/renderer/src/store/github/pull-requests/pr-result-routing.ts @@ -1,6 +1,6 @@ import type { AppState } from '../../types' -import type { RuntimeFocusSettings } from '../../../../../shared/github/github-cache-key' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { RuntimeFocusSettings } from '../../../../../shared/github/cache/github-cache-key' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import { hostedReviewInfoFromGitHubPRInfo } from '../../../../../shared/source-control/hosted-review/hosted-review-github' import { getHostedReviewCacheKey, diff --git a/src/renderer/src/store/github/pull-requests/pull-request-actions.ts b/src/renderer/src/store/github/pull-requests/pull-request-actions.ts index 785a427f4c3..8280dca0ec6 100644 --- a/src/renderer/src/store/github/pull-requests/pull-request-actions.ts +++ b/src/renderer/src/store/github/pull-requests/pull-request-actions.ts @@ -1,12 +1,12 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import type { GitHubPRFallbackSource } from '../cache/cache-model' import { getHostedReviewCacheKey } from '../../slices/hosted-review/hosted-review-cache-identity' import { findRepoForGitHubOwner } from '../repositories/repository-routing' import '../cache/cache-identity' -import { getGitHubRepoCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubRepoCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { isFresh } from '../cache/cache-policy' import { buildGitHubPRRefreshStateClearToken } from '../refresh/pr-refresh-state' import { githubHostedReviewFallbackPRNumber, prLookupHintKey } from './pr-result-routing' @@ -14,7 +14,7 @@ import { nextLookupGeneration } from '../../slices/hosted-review/refresh/lookup- import { inflightPRRequests, prRequestGenerations } from '../requests/request-coordination' import { getGitHubSourceTarget, settingsForGitHubRepoOwner } from '../work-items/work-item-routing' import { captureLinkedReviewClearScope, clearLinkedReview } from './linked-review-clear' -import { shouldClearDivergedLinkedMergedPR } from '../../../../../shared/github/linked-pr-staleness' +import { shouldClearDivergedLinkedMergedPR } from '../../../../../shared/github/pull-requests/refresh/linked-pr-staleness' import { startPullRequestLookup } from './pull-request-execution' export const createPullRequestActions = ( diff --git a/src/renderer/src/store/github/pull-requests/pull-request-execution.ts b/src/renderer/src/store/github/pull-requests/pull-request-execution.ts index 18445f4fae6..317b4d28b4c 100644 --- a/src/renderer/src/store/github/pull-requests/pull-request-execution.ts +++ b/src/renderer/src/store/github/pull-requests/pull-request-execution.ts @@ -2,10 +2,10 @@ import { capturePRCheckContract } from '@/lib/source-control/checks/pr-check-obs import type { StateCreator } from 'zustand' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' import type { AppState } from '../../types' -import type { RuntimeFocusSettings } from '../../../../../shared/github/github-cache-key' +import type { RuntimeFocusSettings } from '../../../../../shared/github/cache/github-cache-key' import type { GitHubSlice } from '../github-slice-contract' import type { Repo } from '../../../../../shared/repo-types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import type { CacheEntry, GitHubPRFallbackSource } from '../cache/cache-model' import { withDirectPRRefreshSettlement, @@ -15,11 +15,11 @@ import { import type { GitHubPRRefreshCandidate, PRRefreshOutcome -} from '../../../../../shared/github/pull-request-refresh-types' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { normalizeGitHubPRForBranchOutcome, githubPRTransportFailureOutcome -} from '../../../../../shared/github/pull-request-for-branch-outcome' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-for-branch-outcome' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { inflightPRRequests, prRequestGenerations } from '../requests/request-coordination' @@ -32,7 +32,7 @@ import { findWorktreeById, isStaleExactLinkedPRLookup } from '../refresh/worktre import { shouldClearBranchMismatchedLinkedOpenPR, shouldClearDivergedLinkedMergedPR -} from '../../../../../shared/github/linked-pr-staleness' +} from '../../../../../shared/github/pull-requests/refresh/linked-pr-staleness' import { captureLinkedReviewClearScope, clearLinkedReview } from './linked-review-clear' export function startPullRequestLookup(args: { set: Parameters>[0] diff --git a/src/renderer/src/store/github/refresh/github-pr-manual-refresh-broadcast.test.ts b/src/renderer/src/store/github/refresh/github-pr-manual-refresh-broadcast.test.ts index 61341e58ead..66ec6d17912 100644 --- a/src/renderer/src/store/github/refresh/github-pr-manual-refresh-broadcast.test.ts +++ b/src/renderer/src/store/github/refresh/github-pr-manual-refresh-broadcast.test.ts @@ -7,9 +7,8 @@ import { mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' -import type { PRRefreshOutcome } from '../../../../../shared/github/pull-request-refresh-types' -import type { PRInfo, CheckStatus } from '../../../../../shared/github/pull-request-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +import type { PRRefreshOutcome } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' function createRefreshContext(pr: PRInfo | null = null) { const store = createTestStore() @@ -77,86 +76,4 @@ describe('manual PR refresh whose outcome main also broadcasts', () => { fetchedAt: 1_300 }) }) - - const checksDeliveryCases: { - raw: CheckStatus - expected: CheckStatus - check: PRCheckDetail - order: 'broadcast-first' | 'reply-first' - }[] = [ - { - raw: 'pending', - expected: 'success', - check: { name: 'build', status: 'completed', conclusion: 'success', url: null }, - order: 'broadcast-first' - }, - { - raw: 'success', - expected: 'failure', - check: { name: 'build', status: 'completed', conclusion: 'failure', url: null }, - order: 'reply-first' - } - ] - it.each(checksDeliveryCases)( - 'keeps $expected checks from a concurrent details fetch with $order delivery', - async ({ raw, expected, check, order }) => { - vi.useFakeTimers() - vi.setSystemTime(1_100) - const prRepo = { owner: 'test-owner', repo: 'test-repo', host: 'github.com' } - const pr = makePR({ number: 6, headSha: 'head-oid', prRepo, checksStatus: raw }) - const { store, repoPath, repoId, branch, worktreeId, cacheKey } = createRefreshContext(pr) - let deliverOutcome: () => void = () => { - throw new Error('PR lookup has not started') - } - mockApi.gh.prChecks.mockResolvedValue([check]) - mockApi.gh.refreshPRNow.mockImplementationOnce(async ({ candidate }) => { - store.getState().applyGitHubPRRefreshEvent({ - sequence: 1, - aliases: [candidate], - reason: 'manual', - requestStartedAt: 1_100, - status: 'in-flight' - }) - // A detail poll can finish while the earlier PR snapshot is still loading other metadata. - vi.setSystemTime(1_200) - await store - .getState() - .fetchPRChecks(repoPath, 6, branch, pr.headSha, prRepo, { force: true, repoId }) - expect(store.getState().prCache[cacheKey]?.data?.checksStatus).toBe(expected) - vi.setSystemTime(1_300) - const outcome: PRRefreshOutcome = { kind: 'found', pr, fetchedAt: 1_300 } - deliverOutcome = () => - store.getState().applyGitHubPRRefreshEvent({ - sequence: 1, - aliases: [candidate], - reason: 'manual', - requestStartedAt: 1_100, - outcome - }) - if (order === 'broadcast-first') { - deliverOutcome() - } - return outcome - }) - - await store.getState().fetchPRForBranch(repoPath, branch, { - force: true, - repoId, - worktreeId, - fallbackPRNumber: 6, - reason: 'manual' - }) - if (order === 'reply-first') { - deliverOutcome() - } - - expect(store.getState().prCache[cacheKey]).toMatchObject({ - data: { number: 6, checksStatus: expected }, - fetchedAt: 1_300 - }) - expect(Object.values(store.getState().hostedReviewCache)).toMatchObject([ - { data: { provider: 'github', number: 6, status: expected }, fetchedAt: 1_300 } - ]) - } - ) }) diff --git a/src/renderer/src/store/github/refresh/github-pr-refresh-states-leak.test.ts b/src/renderer/src/store/github/refresh/github-pr-refresh-states-leak.test.ts index 5030ac0e9dc..9b9daf60510 100644 --- a/src/renderer/src/store/github/refresh/github-pr-refresh-states-leak.test.ts +++ b/src/renderer/src/store/github/refresh/github-pr-refresh-states-leak.test.ts @@ -4,7 +4,7 @@ import { create } from 'zustand' import { createGitHubSlice } from '../github' import { createHostedReviewSlice } from '../../slices/hosted-review/hosted-review' import type { AppState } from '../../types' -import type { GitHubPRRefreshEvent } from '../../../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshEvent } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' const mockApi = { gh: { diff --git a/src/renderer/src/store/github/refresh/pr-refresh-state.ts b/src/renderer/src/store/github/refresh/pr-refresh-state.ts index 260fc529532..7a7565539da 100644 --- a/src/renderer/src/store/github/refresh/pr-refresh-state.ts +++ b/src/renderer/src/store/github/refresh/pr-refresh-state.ts @@ -3,7 +3,7 @@ import type { GitHubPRRefreshSkippedReason, PRRefreshErrorType, PRRefreshOutcome -} from '../../../../../shared/github/pull-request-refresh-types' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import '../cache/cache-policy' export type PRRefreshState = { diff --git a/src/renderer/src/store/github/refresh/refresh-event-actions.ts b/src/renderer/src/store/github/refresh/refresh-event-actions.ts index 27f56d4be48..0feb59c1623 100644 --- a/src/renderer/src/store/github/refresh/refresh-event-actions.ts +++ b/src/renderer/src/store/github/refresh/refresh-event-actions.ts @@ -23,12 +23,15 @@ import { } from '../pull-requests/linked-review-clear' import { getGitHubRepoLookupIndex } from '../repositories/github-repo-lookup-index' import { getRefreshAliasExecutionHostId } from '../repositories/repository-routing' -import { buildWorktreeLookupIndex, isStaleExactLinkedPRLookup } from './worktree-refresh' +import { + buildWorktreeLookupIndex, + isStaleExactLinkedPRLookup, + type WorktreeLookupIndex +} from './worktree-refresh' import { shouldClearBranchMismatchedLinkedOpenPR, shouldClearDivergedLinkedMergedPR -} from '../../../../../shared/github/linked-pr-staleness' -import type { WorktreeLookupIndex } from './worktree-refresh' +} from '../../../../../shared/github/pull-requests/refresh/linked-pr-staleness' export const createRefreshEventActions = ( set: Parameters>[0], diff --git a/src/renderer/src/store/github/refresh/refresh-routing-actions.ts b/src/renderer/src/store/github/refresh/refresh-routing-actions.ts index 11c84b035e8..0cd50386047 100644 --- a/src/renderer/src/store/github/refresh/refresh-routing-actions.ts +++ b/src/renderer/src/store/github/refresh/refresh-routing-actions.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { bypassesGitHubPRRefreshFreshness } from './pr-refresh-state' import { diff --git a/src/renderer/src/store/github/refresh/worktree-refresh.ts b/src/renderer/src/store/github/refresh/worktree-refresh.ts index faa2c76f9a5..f4d0856b268 100644 --- a/src/renderer/src/store/github/refresh/worktree-refresh.ts +++ b/src/renderer/src/store/github/refresh/worktree-refresh.ts @@ -3,9 +3,10 @@ import { LOCAL_EXECUTION_HOST_ID, normalizeExecutionHostId, parseExecutionHostId, - toRuntimeExecutionHostId + toRuntimeExecutionHostId, + type ExecutionHostId } from '../../../../../shared/execution-host' -import type { ExecutionHostId } from '../../../../../shared/execution-host' + import { isMacAppDataPath } from '@/app-shell/platform/passive-macos-app-data-access' import { resolveReviewRepository, @@ -17,13 +18,13 @@ import { } from '@/lib/workspace/ownership/repository-owner-index' import { worktreeOwnerHostIds } from '@/lib/workspace/ownership/workspace-catalog-owner-index' import '../cache/cache-identity' -import { getGitHubRepoCacheKey } from '../../../../../shared/github/github-cache-key' +import { getGitHubRepoCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { settingsForGitHubRepoOwner } from '../work-items/work-item-routing' import { githubHostedReviewFallbackPRNumber } from '../pull-requests/pr-result-routing' -import { resolvePRBranchFallback } from '../../../../../shared/github/pr-branch-fallback-rule' +import { resolvePRBranchFallback } from '../../../../../shared/github/pull-requests/refresh/pr-branch-fallback-rule' import { selectWorktreesByRepo } from '../../workspaces/catalog/workspace-record-fields' import type { AppState } from '../../types' -import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { Repo } from '../../../../../shared/repo-types' import type { Worktree } from '../../../../../shared/worktree/types' diff --git a/src/renderer/src/store/github/repositories/repository-routing.ts b/src/renderer/src/store/github/repositories/repository-routing.ts index 0f14540fb52..3909dea1179 100644 --- a/src/renderer/src/store/github/repositories/repository-routing.ts +++ b/src/renderer/src/store/github/repositories/repository-routing.ts @@ -3,7 +3,7 @@ import type { GitHubPRRefreshAlias, GitHubPRRefreshCandidate, GitHubPRRefreshReason -} from '../../../../../shared/github/pull-request-refresh-types' +} from '../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import type { Repo } from '../../../../../shared/repo-types' import { runtimeTargetForOwnerEnvironment } from '../../../runtime/rpc/targets/runtime-client-target' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' diff --git a/src/renderer/src/store/github/requests/provider-request-coalescing.test.ts b/src/renderer/src/store/github/requests/provider-request-coalescing.test.ts index 2d6482893cf..606e0e7e1da 100644 --- a/src/renderer/src/store/github/requests/provider-request-coalescing.test.ts +++ b/src/renderer/src/store/github/requests/provider-request-coalescing.test.ts @@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { GitHubWorkItem, ListWorkItemsResult -} from '../../../../../shared/github/work-item-types' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' +} from '../../../../../shared/github/work-items/work-item-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' diff --git a/src/renderer/src/store/github/requests/request-coordination.ts b/src/renderer/src/store/github/requests/request-coordination.ts index 99d30e50ff4..311fc38181b 100644 --- a/src/renderer/src/store/github/requests/request-coordination.ts +++ b/src/renderer/src/store/github/requests/request-coordination.ts @@ -1,9 +1,12 @@ import type { PRCheckContract } from '@/lib/source-control/checks/pr-check-observation-contract' -import type { PRCheckDetail } from '../../../../../shared/github/check-types' -import type { PRComment } from '../../../../../shared/github/comment-types' -import type { IssueInfo, PRInfo } from '../../../../../shared/github/pull-request-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GetProjectViewTableResult } from '../../../../../shared/github/project-result-types' +import type { PRCheckDetail } from '../../../../../shared/github/checks/check-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' +import type { + IssueInfo, + PRInfo +} from '../../../../../shared/github/pull-requests/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GetProjectViewTableResult } from '../../../../../shared/github/projects/project-result-types' import type { LinkedReviewClearScope } from '../pull-requests/linked-review-clear' import type { AppState } from '../../types' import { matchesRepoCacheEviction, type RepoCacheEviction } from '../cache/cache-identity' diff --git a/src/renderer/src/store/github/review-threads/github-review-thread-rollback.test.ts b/src/renderer/src/store/github/review-threads/github-review-thread-rollback.test.ts index f99016e6031..a93e8ded25a 100644 --- a/src/renderer/src/store/github/review-threads/github-review-thread-rollback.test.ts +++ b/src/renderer/src/store/github/review-threads/github-review-thread-rollback.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, it, vi } from 'vitest' -import type { PRComment } from '../../../../../shared/github/comment-types' +import type { PRComment } from '../../../../../shared/github/comments/comment-types' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' const repoPath = '/repo' diff --git a/src/renderer/src/store/github/sources/github-source-lifetime.test.tsx b/src/renderer/src/store/github/sources/github-source-lifetime.test.tsx index f8d1ff7f31d..f79887fd371 100644 --- a/src/renderer/src/store/github/sources/github-source-lifetime.test.tsx +++ b/src/renderer/src/store/github/sources/github-source-lifetime.test.tsx @@ -8,7 +8,7 @@ import { taskPageGitHubResumeCache } from '@/components/task-page/github/task-pa import type { AppState } from '../../types' import { createGlobalSettingsFixture } from '../../../../../shared/global-settings-test-fixture' import type { Repo } from '../../../../../shared/repo-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' const holder = vi.hoisted((): { store: ReturnType | null } => ({ store: null diff --git a/src/renderer/src/store/github/sources/source-collections.ts b/src/renderer/src/store/github/sources/source-collections.ts index 8d3a5daf18f..6d56e2f4591 100644 --- a/src/renderer/src/store/github/sources/source-collections.ts +++ b/src/renderer/src/store/github/sources/source-collections.ts @@ -1,9 +1,9 @@ import type { Repo } from '../../../../../shared/repo-types' -import type { ListWorkItemsResult } from '../../../../../shared/github/work-item-types' +import type { ListWorkItemsResult } from '../../../../../shared/github/work-items/work-item-types' import type { WorkItemsCacheSources } from '../cache/cache-model' import { githubTaskBinding } from '@/lib/work-items/github/github-task-observation' import { structuralValuesEqual } from '../../../../../shared/structural-value-equality' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { bindingKey } from '../task-records/task-record-model' import { nextProviderRequestId } from '../requests/request-coordination' import { withBoundedCacheEntry, evictStaleEntries } from '../cache/cache-policy' diff --git a/src/renderer/src/store/github/sources/source-preference-ownership.ts b/src/renderer/src/store/github/sources/source-preference-ownership.ts index ef96b538af8..b72b12e1c20 100644 --- a/src/renderer/src/store/github/sources/source-preference-ownership.ts +++ b/src/renderer/src/store/github/sources/source-preference-ownership.ts @@ -1,10 +1,13 @@ import type { StoreApi } from 'zustand' import type { AppState } from '../../types' import type { Repo } from '../../../../../shared/repo-types' -import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import { + type TaskSourceContext, + getTaskSourceCacheScope +} from '../../../../../shared/work-items/task-source-context' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { CacheEntry } from '../cache/cache-model' -import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' + import { githubTaskBinding } from '@/lib/work-items/github/github-task-observation' import { bindingKey, diff --git a/src/renderer/src/store/github/task-records/github-task-record-retries.test.ts b/src/renderer/src/store/github/task-records/github-task-record-retries.test.ts index ae3da680c80..020f4564a24 100644 --- a/src/renderer/src/store/github/task-records/github-task-record-retries.test.ts +++ b/src/renderer/src/store/github/task-records/github-task-record-retries.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../../shared/repo-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' import { getTaskPageRepoSourceContext } from '@/lib/work-items/task-repo-source-context' diff --git a/src/renderer/src/store/github/task-records/github-task-records.test.ts b/src/renderer/src/store/github/task-records/github-task-records.test.ts index e6628ee12c8..ea149195500 100644 --- a/src/renderer/src/store/github/task-records/github-task-records.test.ts +++ b/src/renderer/src/store/github/task-records/github-task-records.test.ts @@ -1,7 +1,7 @@ import { answered } from '../requests/answered-request.test-fixture' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../../shared/repo-types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' import { getTaskPageRepoSourceContext } from '@/lib/work-items/task-repo-source-context' import { resetTaskPageGitHubMutationRegistryForTests } from '@/components/task-page/github/task-page-github-work-item-mutation-registry' diff --git a/src/renderer/src/store/github/task-records/task-change.ts b/src/renderer/src/store/github/task-records/task-change.ts index 7eaee4048c7..6aa6470aab2 100644 --- a/src/renderer/src/store/github/task-records/task-change.ts +++ b/src/renderer/src/store/github/task-records/task-change.ts @@ -1,9 +1,9 @@ import type { StoreApi } from 'zustand' import type { AppState } from '../../types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' /** One edit's own semantic change: set title or state, add or remove named members (logins lower-cased). */ export type TaskChange = { diff --git a/src/renderer/src/store/github/task-records/task-record-cache.ts b/src/renderer/src/store/github/task-records/task-record-cache.ts index 9da2a8436fb..d43cf5a7b5f 100644 --- a/src/renderer/src/store/github/task-records/task-record-cache.ts +++ b/src/renderer/src/store/github/task-records/task-record-cache.ts @@ -1,5 +1,5 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' import type { Repo } from '../../../../../shared/repo-types' import type { AppState } from '../../types' import type { StoreApi } from 'zustand' @@ -10,14 +10,14 @@ import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { structuralValuesEqual } from '../../../../../shared/structural-value-equality' import { projectViewSourceScope } from '../cache/cache-identity' import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { patchGitHubProjectRowContent } from '../projects/project-row-actions' import { buildProjectWorkItem } from '@/components/github-project/project-work-item' import { isEmptyTaskChange, type TaskChange, type TaskFacts } from './task-change' import type { GitHubProjectRow, GitHubProjectTable -} from '../../../../../shared/github/project-types' +} from '../../../../../shared/github/projects/project-types' import { PROJECT_ASSIGNEE_LIMIT, PROJECT_LABEL_LIMIT, diff --git a/src/renderer/src/store/github/task-records/task-record-model.ts b/src/renderer/src/store/github/task-records/task-record-model.ts index ed3fc2bccb3..1756d0c25e9 100644 --- a/src/renderer/src/store/github/task-records/task-record-model.ts +++ b/src/renderer/src/store/github/task-records/task-record-model.ts @@ -1,10 +1,10 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../shared/repo-types' -import type { GitHubProjectRow } from '../../../../../shared/github/project-types' +import type { GitHubProjectRow } from '../../../../../shared/github/projects/project-types' import type { GitHubTaskObservation } from '@/lib/work-items/github/github-task-observation' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import type { HostIdentityFence } from '../../../runtime/rpc/environment-revisions/host-identity-fence' import { applyTaskChange, type TaskChange, type TaskFacts } from './task-change' diff --git a/src/renderer/src/store/github/task-records/task-record-owners.ts b/src/renderer/src/store/github/task-records/task-record-owners.ts index 7f40f61a19a..4e1752c04e8 100644 --- a/src/renderer/src/store/github/task-records/task-record-owners.ts +++ b/src/renderer/src/store/github/task-records/task-record-owners.ts @@ -1,6 +1,6 @@ import { deleteConfirmedListSnapshot } from '@/components/task-page/github/task-page-github-work-item-mutation-registry' import { deleteLastConfirmedClientValue } from '@/components/task-page/github/task-page-github-confirmed-client-values' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' import type { Repo } from '../../../../../shared/repo-types' import type { AppState } from '../../types' @@ -12,7 +12,7 @@ import { } from '@/lib/work-items/github/github-task-observation' import { githubTaskIdentity } from '@/lib/work-items/github/github-task-target' import { getWorkItemRepo } from '@/lib/work-items/work-item-repo-row' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { nextProviderRequestId } from '../requests/request-coordination' import { diff --git a/src/renderer/src/store/github/task-records/task-record-reads.ts b/src/renderer/src/store/github/task-records/task-record-reads.ts index 1f8b338e75b..033a6777553 100644 --- a/src/renderer/src/store/github/task-records/task-record-reads.ts +++ b/src/renderer/src/store/github/task-records/task-record-reads.ts @@ -1,8 +1,8 @@ -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import { githubTaskIdentity } from '@/lib/work-items/github/github-task-target' import { lookupGitHubWorkItemByOwnerRepoForSource } from '@/lib/work-items/github/github-work-item-source-lookup' import { RuntimeRpcCallError } from '@/runtime/rpc/calls/runtime-rpc-result' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' import { nextProviderRequestId } from '../requests/request-coordination' import { applyTaskChange, diff --git a/src/renderer/src/store/github/work-items/github-work-items-error-envelope.test.ts b/src/renderer/src/store/github/work-items/github-work-items-error-envelope.test.ts index a1f78abd762..f6ac82df1b7 100644 --- a/src/renderer/src/store/github/work-items/github-work-items-error-envelope.test.ts +++ b/src/renderer/src/store/github/work-items/github-work-items-error-envelope.test.ts @@ -7,7 +7,7 @@ import { runtimeEnvironmentCall } from '../github-slice-test-harness' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' describe('createGitHubSlice.fetchWorkItems source/error envelope', () => { beforeEach(() => { diff --git a/src/renderer/src/store/github/work-items/github-work-items-runtime-routing.test.ts b/src/renderer/src/store/github/work-items/github-work-items-runtime-routing.test.ts index c8bbef7aacc..b90f3bf736c 100644 --- a/src/renderer/src/store/github/work-items/github-work-items-runtime-routing.test.ts +++ b/src/renderer/src/store/github/work-items/github-work-items-runtime-routing.test.ts @@ -9,7 +9,7 @@ import { } from '../github-slice-test-harness' import { runtimeOwnedRepoState } from '../../repos/repo.test-fixture' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' describe('createGitHubSlice.fetchWorkItems source/error envelope', () => { beforeEach(() => { diff --git a/src/renderer/src/store/github/work-items/github-work-items-twin-cache-host.test.ts b/src/renderer/src/store/github/work-items/github-work-items-twin-cache-host.test.ts index 09222feb969..ac98ad6981a 100644 --- a/src/renderer/src/store/github/work-items/github-work-items-twin-cache-host.test.ts +++ b/src/renderer/src/store/github/work-items/github-work-items-twin-cache-host.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createTestStore, mockApi, resetRemoteRuntimeMocks } from '../github-slice-test-harness' import { selectTaskPageWorkItemsCacheEntries } from '../../../components/task-page/task-page-cache-selectors' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { Repo } from '../../../../../shared/repo-types' const localRow: Repo = { diff --git a/src/renderer/src/store/github/work-items/work-item-aggregate-actions.ts b/src/renderer/src/store/github/work-items/work-item-aggregate-actions.ts index 44375e43bdd..28db5239cbb 100644 --- a/src/renderer/src/store/github/work-items/work-item-aggregate-actions.ts +++ b/src/renderer/src/store/github/work-items/work-item-aggregate-actions.ts @@ -1,15 +1,15 @@ import { githubTaskBinding } from '@/lib/work-items/github/github-task-observation' -import { getWorkItemPageErrorTypes } from '../../../../../shared/github/work-item-page-errors' +import { getWorkItemPageErrorTypes } from '../../../../../shared/github/work-items/work-item-page-errors' import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' import type { ClassifiedError } from '../../../../../shared/classified-error' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' -import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../../shared/github/work-item-ssh-error' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' +import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../../shared/github/work-items/work-item-ssh-error' import { sortWorkItemsByNumber } from '../../../../../shared/work-items/work-item-order' import { PER_REPO_FETCH_LIMIT } from '../../../../../shared/work-items/work-item-pagination' import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items-query-bounds' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items/work-items-query-bounds' import { workItemsCacheKey, workItemsInflightRequestKey } from '../cache/cache-identity' import { isFresh, WORK_ITEMS_CACHE_TTL } from '../cache/cache-policy' import { diff --git a/src/renderer/src/store/github/work-items/work-item-count-actions.ts b/src/renderer/src/store/github/work-items/work-item-count-actions.ts index ece6d2edcac..ddd4ef650f6 100644 --- a/src/renderer/src/store/github/work-items/work-item-count-actions.ts +++ b/src/renderer/src/store/github/work-items/work-item-count-actions.ts @@ -4,7 +4,7 @@ import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' import { getWorkItemCountedTotalPages } from '../../../../../shared/work-items/work-item-pagination' import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items-query-bounds' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items/work-items-query-bounds' import { workItemsCacheKey, workItemsInflightRequestKey } from '../cache/cache-identity' import { acquireProviderRequestSlot as acquireWorkItemSlot, diff --git a/src/renderer/src/store/github/work-items/work-item-fetch-actions.ts b/src/renderer/src/store/github/work-items/work-item-fetch-actions.ts index eebf1aebe1b..93924b2f59b 100644 --- a/src/renderer/src/store/github/work-items/work-item-fetch-actions.ts +++ b/src/renderer/src/store/github/work-items/work-item-fetch-actions.ts @@ -2,11 +2,11 @@ import { githubTaskBinding } from '@/lib/work-items/github/github-task-observati import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { WorkItemsCacheError } from '../cache/cache-model' -import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../../shared/github/work-item-ssh-error' +import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../../shared/github/work-items/work-item-ssh-error' import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' -import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items-query-bounds' +import { isGitHubWorkItemsQueryTooLarge } from '../../../../../shared/github/work-items/work-items-query-bounds' import { reconcileCatalogRows } from '../../../../../shared/catalog-row-reconcile' import { structuralValuesEqual } from '../../../../../shared/structural-value-equality' import { workItemsCacheKey, workItemsInflightRequestKey } from '../cache/cache-identity' diff --git a/src/renderer/src/store/github/work-items/work-item-mutation-actions.ts b/src/renderer/src/store/github/work-items/work-item-mutation-actions.ts index ec31d562d62..7c598fbd980 100644 --- a/src/renderer/src/store/github/work-items/work-item-mutation-actions.ts +++ b/src/renderer/src/store/github/work-items/work-item-mutation-actions.ts @@ -2,8 +2,8 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import type { GitHubSlice } from '../github-slice-contract' import { toast } from 'sonner' -import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/links' -import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { parseGitHubIssueOrPRLink } from '../../../../../shared/github/repositories/links' +import { githubRepoIdentityKey } from '../../../../../shared/github/repositories/repository-identity-key' import { getTaskSourceCacheScope } from '../../../../../shared/work-items/task-source-context' import { getRepoExecutionHostId, type ExecutionHostId } from '../../../../../shared/execution-host' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/store/github/work-items/work-item-patch-options.ts b/src/renderer/src/store/github/work-items/work-item-patch-options.ts index fdf0888abf8..120ee9e99c9 100644 --- a/src/renderer/src/store/github/work-items/work-item-patch-options.ts +++ b/src/renderer/src/store/github/work-items/work-item-patch-options.ts @@ -1,5 +1,5 @@ import type { TaskSourceContext } from '../../../../../shared/work-items/task-source-context' -import type { GitHubOwnerRepo } from '../../../../../shared/github/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../../../../../shared/github/repositories/repository-identity' export type GitHubPatchWorkItemOptions = { sourceContext?: TaskSourceContext | null diff --git a/src/renderer/src/store/github/work-items/work-item-routing.ts b/src/renderer/src/store/github/work-items/work-item-routing.ts index 34d0a058b25..db9449eac25 100644 --- a/src/renderer/src/store/github/work-items/work-item-routing.ts +++ b/src/renderer/src/store/github/work-items/work-item-routing.ts @@ -1,12 +1,12 @@ import { getGitHubRepoLookupIndex } from '../repositories/github-repo-lookup-index' import { githubTaskBinding } from '@/lib/work-items/github/github-task-observation' import type { AppState } from '../../types' -import type { RuntimeFocusSettings } from '../../../../../shared/github/github-cache-key' +import type { RuntimeFocusSettings } from '../../../../../shared/github/cache/github-cache-key' import type { Repo } from '../../../../../shared/repo-types' import type { GitHubWorkItem, ListWorkItemsResult -} from '../../../../../shared/github/work-item-types' +} from '../../../../../shared/github/work-items/work-item-types' import { getTaskSourceCacheScope, type TaskSourceContext @@ -16,7 +16,7 @@ import { parseExecutionHostId, type ExecutionHostId } from '../../../../../shared/execution-host' -import { classifyGitHubUnavailable } from '../../../../../shared/github/api-availability' +import { classifyGitHubUnavailable } from '../../../../../shared/github/api/api-availability' import { callRuntimeRpc } from '../../../runtime/rpc/calls/runtime-rpc-call' import { RuntimeRpcCallError } from '../../../runtime/rpc/calls/runtime-rpc-result' import { getHostlessSourceRepoOwnerEnvironmentId } from '@/lib/workspace/ownership/repo-runtime-owner' diff --git a/src/renderer/src/store/repos/repo-state.ts b/src/renderer/src/store/repos/repo-state.ts index 5b3018182d7..b09e7489bd5 100644 --- a/src/renderer/src/store/repos/repo-state.ts +++ b/src/renderer/src/store/repos/repo-state.ts @@ -1,6 +1,6 @@ import type { WslHostWindowsDriveRefusal } from '../../../../shared/wsl-host-windows-drive-refusal' import type { WorkspaceAttachmentMutation } from '../../../../shared/workspace-attachments/workspace-attachment-mutation' -import type { GhAccountBinding } from '../../../../shared/github/account-binding' +import type { GhAccountBinding } from '../../../../shared/github/accounts/account-binding' import type { SshRepoReadoption } from '../../../../shared/ssh-types' import type { FolderWorkspace } from '../../../../shared/folder-workspaces/folder-workspace-types' import type { diff --git a/src/renderer/src/store/repos/repo-update.ts b/src/renderer/src/store/repos/repo-update.ts index 339eccbe05c..194ea4ab604 100644 --- a/src/renderer/src/store/repos/repo-update.ts +++ b/src/renderer/src/store/repos/repo-update.ts @@ -3,7 +3,7 @@ import type { AppState } from '../types' import type { Repo } from '../../../../shared/repo-types' import { sanitizeRepoIcon } from '../../../../shared/repo-icon' import { normalizeRepoBadgeColor } from '../../../../shared/repo-badge-color' -import { normalizeGhAccountBinding } from '../../../../shared/github/account-binding' +import { normalizeGhAccountBinding } from '../../../../shared/github/accounts/account-binding' import { normalizeJiraProjectReference } from '../../../../shared/jira/jira-project-reference' import { sanitizeAgentWorkspaceTrustDecision } from '../../../../shared/agent-launch/permissions/agent-workspace-trust-decision' import { findRepoForHost, repoMatchesHostIdentity } from './repo-host-identity' diff --git a/src/renderer/src/store/slices/editor/checks/editor-check-details-tab-owner.test.ts b/src/renderer/src/store/slices/editor/checks/editor-check-details-tab-owner.test.ts index bfe9b9954e3..f70cfb6d3c1 100644 --- a/src/renderer/src/store/slices/editor/checks/editor-check-details-tab-owner.test.ts +++ b/src/renderer/src/store/slices/editor/checks/editor-check-details-tab-owner.test.ts @@ -4,7 +4,7 @@ import { createEditorTabsStore } from '../editor-slice-test-harness' import { makeWorktree } from '../../worktrees/worktree.test-fixture' import { TEST_REPO } from '../../../repos/repo.test-fixture' import type { AppState } from '../../../types' -import type { PRCheckRunDetails } from '../../../../../../shared/github/check-types' +import type { PRCheckRunDetails } from '../../../../../../shared/github/checks/check-types' // Main's answer to the window's layout commands, as a switch-on build sends them. vi.mock('@/runtime/rpc/calls/runtime-rpc-call', async (importOriginal) => @@ -154,43 +154,4 @@ describe('check-details tab owner', () => { ) } ) - - it('refuses to reload an unpinned tab when a detected checkout rivals the tracked row', async () => { - const localRepo = { ...TEST_REPO, id: 'repo-1', path: '/repo' } - const { store, fetchPRCheckDetails } = createOwnerStore({ - repos: [localRepo, { ...localRepo, path: '/remote', executionHostId: 'runtime:server' }], - worktreesByRepo: { - 'repo-1': [makeWorktree({ id: 'wt-1', repoId: 'repo-1', path: '/repo' })] - }, - detectedWorktreesByRepo: { - 'repo-1': { - repoId: 'repo-1', - authoritative: true, - source: 'git', - worktrees: [ - { - ...makeWorktree({ - id: 'wt-1', - repoId: 'repo-1', - path: '/remote', - hostId: 'runtime:server', - runtimeOwnerEnvironmentId: 'server' - }), - ownership: 'external', - selectedCheckout: false, - visible: true - } - ] - } - } - }) - const tabId = open(store) - - await store.getState().reloadOpenCheckRunDetailsTab(tabId) - - expect(fetchPRCheckDetails).not.toHaveBeenCalled() - expect(store.getState().openFiles[0]).toMatchObject({ - operationProvenance: undefined - }) - }) }) diff --git a/src/renderer/src/store/slices/hosted-review/hosted-review-cache-identity.ts b/src/renderer/src/store/slices/hosted-review/hosted-review-cache-identity.ts index 4351297ff1f..0c1facad889 100644 --- a/src/renderer/src/store/slices/hosted-review/hosted-review-cache-identity.ts +++ b/src/renderer/src/store/slices/hosted-review/hosted-review-cache-identity.ts @@ -1,5 +1,5 @@ import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import { composeRepoCacheKey } from '../../../../../shared/github/github-cache-key' +import { composeRepoCacheKey } from '../../../../../shared/github/cache/github-cache-key' import { getSettingsFocusedExecutionHostId, normalizeExecutionHostId, diff --git a/src/renderer/src/store/slices/hosted-review/hosted-review-cache-state.ts b/src/renderer/src/store/slices/hosted-review/hosted-review-cache-state.ts index 9eca4c2416f..cfde3768e8b 100644 --- a/src/renderer/src/store/slices/hosted-review/hosted-review-cache-state.ts +++ b/src/renderer/src/store/slices/hosted-review/hosted-review-cache-state.ts @@ -1,16 +1,17 @@ import type { LinkedReviewHints } from './hosted-review-cache-identity' -import type { - CreateHostedReviewInput, - CreateStackedHostedReviewInput, - HostedReviewCreationEligibility, - HostedReviewForBranchArgs, - HostedReviewForBranchOutcome, - HostedReviewForBranchReply, - HostedReviewInfo +import { + type CreateHostedReviewInput, + type CreateStackedHostedReviewInput, + type HostedReviewCreationEligibility, + type HostedReviewForBranchArgs, + type HostedReviewForBranchOutcome, + type HostedReviewForBranchReply, + type HostedReviewInfo, + hostedReviewForBranchOutcome } from '../../../../../shared/source-control/hosted-review/hosted-review-contracts' -import { hostedReviewForBranchOutcome } from '../../../../../shared/source-control/hosted-review/hosted-review-contracts' + import type { Repo } from '../../../../../shared/repo-types' -import type { PRInfo } from '../../../../../shared/github/pull-request-types' +import type { PRInfo } from '../../../../../shared/github/pull-requests/pull-request-types' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { ipcOrRuntimeTargetForHostId } from '@/runtime/rpc/targets/runtime-client-target' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' diff --git a/src/renderer/src/store/slices/hosted-review/hosted-review-pr-cache.ts b/src/renderer/src/store/slices/hosted-review/hosted-review-pr-cache.ts index 12b7e09f83f..3391d029815 100644 --- a/src/renderer/src/store/slices/hosted-review/hosted-review-pr-cache.ts +++ b/src/renderer/src/store/slices/hosted-review/hosted-review-pr-cache.ts @@ -4,7 +4,7 @@ import type { AppState } from '../../types' import { getGitHubPRCacheKey, getLegacyGitHubPRCacheKey -} from '../../../../../shared/github/github-cache-key' +} from '../../../../../shared/github/cache/github-cache-key' export function clearHostedReviewConflictingPrCache(args: { cache: NonNullable diff --git a/src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-ownership.ts b/src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-ownership.ts index 2c7f6241a00..a85dbef00f7 100644 --- a/src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-ownership.ts +++ b/src/renderer/src/store/slices/hosted-review/refresh/visible-hosted-review-refresh-ownership.ts @@ -1,6 +1,6 @@ import type { AppState } from '../../../types' import type { Worktree } from '../../../../../../shared/worktree/types' -import type { GitHubPRRefreshCandidate } from '../../../../../../shared/github/pull-request-refresh-types' +import type { GitHubPRRefreshCandidate } from '../../../../../../shared/github/pull-requests/refresh/pull-request-refresh-types' import { getHostedReviewCacheKey } from '../hosted-review-cache-identity' import { findIndexedRepoOwnerForHost } from '@/lib/workspace/ownership/repo-owner-index' import { getRepoExecutionHostId } from '../../../../../../shared/execution-host' diff --git a/src/renderer/src/store/slices/task-creation/new-issue-draft.ts b/src/renderer/src/store/slices/task-creation/new-issue-draft.ts index 7045215ca76..2e7fe8e1a90 100644 --- a/src/renderer/src/store/slices/task-creation/new-issue-draft.ts +++ b/src/renderer/src/store/slices/task-creation/new-issue-draft.ts @@ -1,6 +1,6 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' -import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' +import type { GitHubAssignableUser } from '../../../../../shared/github/pull-requests/pull-request-types' /** Keep issue draft in session memory across accidental dismissal; clear on successful submit/restart, never persist to disk. */ export type NewIssueDraft = { diff --git a/src/renderer/src/store/slices/ui/ui-slice-contract-core.ts b/src/renderer/src/store/slices/ui/ui-slice-contract-core.ts index bf8095b974c..39ecf6ab0c9 100644 --- a/src/renderer/src/store/slices/ui/ui-slice-contract-core.ts +++ b/src/renderer/src/store/slices/ui/ui-slice-contract-core.ts @@ -4,7 +4,7 @@ import type { } from '@/attention/agent-subject-read-actions' import type { StateCreator } from 'zustand' import type { AppState } from '../../types' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../../../../shared/gitlab-types' import type { JiraIssue } from '../../../../../shared/jira/jira-types' import type { LinearIssue } from '../../../../../shared/linear/issue-types' diff --git a/src/renderer/src/store/slices/ui/workspace-navigation-history.ts b/src/renderer/src/store/slices/ui/workspace-navigation-history.ts index 0d02acc50d5..2fa75839ad7 100644 --- a/src/renderer/src/store/slices/ui/workspace-navigation-history.ts +++ b/src/renderer/src/store/slices/ui/workspace-navigation-history.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../../types' import { findFirstIndexedWorktree } from '../../../lib/workspace/ownership/worktree-runtime-owner-index' -import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../shared/github/work-items/work-item-types' import type { JiraIssue } from '../../../../../shared/jira/jira-types' import type { LinearIssue } from '../../../../../shared/linear/issue-types' import type { GitLabWorkItem } from '../../../../../shared/gitlab-types' diff --git a/src/renderer/src/store/slices/worktrees/metadata/worktree-meta-state-update.ts b/src/renderer/src/store/slices/worktrees/metadata/worktree-meta-state-update.ts index f4615526561..e5c06ba5530 100644 --- a/src/renderer/src/store/slices/worktrees/metadata/worktree-meta-state-update.ts +++ b/src/renderer/src/store/slices/worktrees/metadata/worktree-meta-state-update.ts @@ -8,7 +8,7 @@ import type { Repo } from '../../../../../../shared/repo-types' import { applyWorktreeUpdates } from './worktree-meta-update-application' import { applyDetectedWorktreeUpdates } from '../listing/detected-worktree-meta' import { getHostedReviewCacheKey } from '../../hosted-review/hosted-review-cache-identity' -import { getGitHubPRCacheKeyCandidates } from '../../../../../../shared/github/github-cache-key' +import { getGitHubPRCacheKeyCandidates } from '../../../../../../shared/github/cache/github-cache-key' type WorktreeMetaStateUpdate = WorktreeMetaBatchUpdate & { reviewRepo?: Repo diff --git a/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-pr-exact-observation.test.ts b/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-pr-exact-observation.test.ts index 74e0d5066fb..c1626871d07 100644 --- a/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-pr-exact-observation.test.ts +++ b/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-pr-exact-observation.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import type { HostedReviewInfo } from '../../../../../../shared/source-control/hosted-review/hosted-review-contracts' import { getWorkspaceAttachments } from '../../../../../../shared/workspace-attachments/workspace-attachments' import { makeWorktree } from '../worktree.test-fixture' diff --git a/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-admission.test.ts b/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-admission.test.ts index 7d2898677db..d41b7cc4cc3 100644 --- a/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-admission.test.ts +++ b/src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-admission.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, it, vi } from 'vitest' -import type { GitHubWorkItem } from '../../../../../../shared/github/work-item-types' +import type { GitHubWorkItem } from '../../../../../../shared/github/work-items/work-item-types' import { getWorkspaceAttachments } from '../../../../../../shared/workspace-attachments/workspace-attachments' import { makePaneKey } from '../../../../../../shared/workspace-layout/stable-pane-id' import { singlePaneLayoutSnapshot } from '../../../terminals/terminal-single-pane-layout' diff --git a/src/renderer/src/web/preload-api/web-github-api.ts b/src/renderer/src/web/preload-api/web-github-api.ts index 1cc369338a1..9a50e7103d1 100644 --- a/src/renderer/src/web/preload-api/web-github-api.ts +++ b/src/renderer/src/web/preload-api/web-github-api.ts @@ -1,15 +1,15 @@ import { callGitHubTargetOperation } from '@/lib/work-items/github/github-work-item-target-request' import { requireActiveEnvironment } from './web-runtime-session' -import { requireGitHubWorkItemTargetDetails } from '../../../../shared/github/work-item-target' -import { shouldAcceptMergedFallbackPR } from '../../../../shared/github/pr-branch-fallback-rule' +import { requireGitHubWorkItemTargetDetails } from '../../../../shared/github/work-items/work-item-target' +import { shouldAcceptMergedFallbackPR } from '../../../../shared/github/pull-requests/refresh/pr-branch-fallback-rule' import type { PreloadApi } from '../../../../preload/api-types' import { GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY, GITHUB_MARK_PR_READY_UPDATE_REQUIRED_MESSAGE } from '../../../../shared/source-control/runtime/source-control-runtime-capabilities' import { translate } from '@/i18n/i18n' -import { GITHUB_WEB_RPC_METHODS } from './web-github-routes' -import type { WebGitHubRuntimeMethod } from './web-github-routes' +import { GITHUB_WEB_RPC_METHODS, type WebGitHubRuntimeMethod } from './web-github-routes' + import { mapRepoPathArg } from '@/runtime/source-control/forge/runtime-repo-selector-params' import { callRuntimeResult, getRemoteRuntimeStatus } from './web-runtime-calls' import { noopUnsubscribe } from './web-storage' diff --git a/src/shared/editor-tab-view-types.ts b/src/shared/editor-tab-view-types.ts index d730111aea3..0218ce1fe72 100644 --- a/src/shared/editor-tab-view-types.ts +++ b/src/shared/editor-tab-view-types.ts @@ -12,8 +12,8 @@ import type { GitStatusEntry } from './git/status/git-status-types' import type { GitLabProjectRef } from './gitlab-types' -import type { PRCheckDetail, PRCheckRunDetails } from './github/check-types' -import type { GitHubRepositoryIdentity } from './github/pull-request-types' +import type { PRCheckDetail, PRCheckRunDetails } from './github/checks/check-types' +import type { GitHubRepositoryIdentity } from './github/repositories/repository-identity' export type DiffSource = | 'unstaged' diff --git a/src/shared/github/account-binding.test.ts b/src/shared/github/accounts/account-binding.test.ts similarity index 100% rename from src/shared/github/account-binding.test.ts rename to src/shared/github/accounts/account-binding.test.ts diff --git a/src/shared/github/account-binding.ts b/src/shared/github/accounts/account-binding.ts similarity index 100% rename from src/shared/github/account-binding.ts rename to src/shared/github/accounts/account-binding.ts diff --git a/src/shared/github/auth-types.ts b/src/shared/github/accounts/auth-types.ts similarity index 100% rename from src/shared/github/auth-types.ts rename to src/shared/github/accounts/auth-types.ts diff --git a/src/shared/github/api-availability.test.ts b/src/shared/github/api/api-availability.test.ts similarity index 100% rename from src/shared/github/api-availability.test.ts rename to src/shared/github/api/api-availability.test.ts diff --git a/src/shared/github/api-availability.ts b/src/shared/github/api/api-availability.ts similarity index 100% rename from src/shared/github/api-availability.ts rename to src/shared/github/api/api-availability.ts diff --git a/src/shared/github/rate-limit-types.ts b/src/shared/github/api/rate-limit-types.ts similarity index 100% rename from src/shared/github/rate-limit-types.ts rename to src/shared/github/api/rate-limit-types.ts diff --git a/src/shared/github/image-attachment.ts b/src/shared/github/attachments/image-attachment.ts similarity index 100% rename from src/shared/github/image-attachment.ts rename to src/shared/github/attachments/image-attachment.ts diff --git a/src/shared/github/github-cache-key.ts b/src/shared/github/cache/github-cache-key.ts similarity index 95% rename from src/shared/github/github-cache-key.ts rename to src/shared/github/cache/github-cache-key.ts index 7318a2da408..be772cd6118 100644 --- a/src/shared/github/github-cache-key.ts +++ b/src/shared/github/cache/github-cache-key.ts @@ -1,11 +1,11 @@ -import type { GlobalSettings } from '../global-settings-types' +import type { GlobalSettings } from '../../global-settings-types' import { LOCAL_EXECUTION_HOST_ID, normalizeExecutionHostId, toSshExecutionHostId, type ExecutionHostId -} from '../execution-host' -import { taskSourceCacheScopeHostId } from '../work-items/task-source-context' +} from '../../execution-host' +import { taskSourceCacheScopeHostId } from '../../work-items/task-source-context' export type RuntimeFocusSettings = | Pick diff --git a/src/shared/github/check-details-deadline.ts b/src/shared/github/checks/check-details-deadline.ts similarity index 100% rename from src/shared/github/check-details-deadline.ts rename to src/shared/github/checks/check-details-deadline.ts diff --git a/src/shared/github/check-types.ts b/src/shared/github/checks/check-types.ts similarity index 100% rename from src/shared/github/check-types.ts rename to src/shared/github/checks/check-types.ts diff --git a/src/shared/github/checks-detail-polling-policy.ts b/src/shared/github/checks/checks-detail-polling-policy.ts similarity index 91% rename from src/shared/github/checks-detail-polling-policy.ts rename to src/shared/github/checks/checks-detail-polling-policy.ts index 1032e957552..813a1e031e4 100644 --- a/src/shared/github/checks-detail-polling-policy.ts +++ b/src/shared/github/checks/checks-detail-polling-policy.ts @@ -1,7 +1,7 @@ import type { PRCheckDetail } from './check-types' -import type { CheckStatus } from './pull-request-types' -import type { HostedReviewState } from '../source-control/hosted-review/hosted-review-contracts' -import { reviewRefreshIntervalMs } from '../review-refresh-policy' +import type { CheckStatus } from '../pull-requests/pull-request-types' +import type { HostedReviewState } from '../../source-control/hosted-review/hosted-review-contracts' +import { reviewRefreshIntervalMs } from '../../review-refresh-policy' export function detailedChecksStatus(checks: readonly PRCheckDetail[]): CheckStatus | undefined { if (checks.length === 0) { diff --git a/src/shared/github/complete-pr-check-owner.test.ts b/src/shared/github/checks/complete-pr-check-owner.test.ts similarity index 60% rename from src/shared/github/complete-pr-check-owner.test.ts rename to src/shared/github/checks/complete-pr-check-owner.test.ts index 5c4f36c817f..8242a9be778 100644 --- a/src/shared/github/complete-pr-check-owner.test.ts +++ b/src/shared/github/checks/complete-pr-check-owner.test.ts @@ -1,9 +1,5 @@ import { expect, it } from 'vitest' -import { - acceptPRCheckObservation, - isCompletePRCheckOwner, - projectPRCheckOwner -} from './complete-pr-check-owner' +import { acceptPRCheckObservation } from './complete-pr-check-owner' const summary = { state: 'success' as const, @@ -49,17 +45,3 @@ it('retains last-good only for the same proved source, target and nonempty head' acceptPRCheckObservation({ ...pr, checksSummary: undefined }, pr, context)?.checksSummary ).toBeUndefined() }) - -it('carries unavailable authority explicitly through display and review projections without lending it to another head', () => { - const unavailable = acceptPRCheckObservation({ ...pr, checksSummary: undefined }, null, context)! - const display = projectPRCheckOwner(unavailable, { ...unavailable }) - expect(isCompletePRCheckOwner(display)).toBe(true) - const review = projectPRCheckOwner(display, { ...display, status: display.checksStatus }) - expect(isCompletePRCheckOwner(review)).toBe(true) - expect(isCompletePRCheckOwner({ ...review })).toBe(false) - expect(isCompletePRCheckOwner(projectPRCheckOwner(review, { ...review, headSha: 'h2' }))).toBe( - false - ) - const full = acceptPRCheckObservation(pr, null, context)! - expect(isCompletePRCheckOwner({ ...pr, checksSummary: full.checksSummary })).toBe(false) -}) diff --git a/src/shared/github/complete-pr-check-owner.ts b/src/shared/github/checks/complete-pr-check-owner.ts similarity index 91% rename from src/shared/github/complete-pr-check-owner.ts rename to src/shared/github/checks/complete-pr-check-owner.ts index d46f7daf459..821a8a2d890 100644 --- a/src/shared/github/complete-pr-check-owner.ts +++ b/src/shared/github/checks/complete-pr-check-owner.ts @@ -1,6 +1,10 @@ -import type { CheckStatus, GitHubOwnerRepo, ProviderCheckSummary } from './pull-request-types' -import { githubRepoIdentityKey } from './repository-identity-key' -import { matchesGitHubWorkItemTarget, parseGitHubItemRepository } from './work-item-target' +import type { CheckStatus, ProviderCheckSummary } from '../pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../repositories/repository-identity' +import { githubRepoIdentityKey } from '../repositories/repository-identity-key' +import { + matchesGitHubWorkItemTarget, + parseGitHubItemRepository +} from '../work-items/work-item-target' type CheckOwner = { number?: number diff --git a/src/shared/github/complete-pr-check-test-fixture.ts b/src/shared/github/checks/complete-pr-check-test-fixture.ts similarity index 100% rename from src/shared/github/complete-pr-check-test-fixture.ts rename to src/shared/github/checks/complete-pr-check-test-fixture.ts diff --git a/src/shared/github/pr-check-head.ts b/src/shared/github/checks/pr-check-head.ts similarity index 100% rename from src/shared/github/pr-check-head.ts rename to src/shared/github/checks/pr-check-head.ts diff --git a/src/shared/github/comment-types.ts b/src/shared/github/comments/comment-types.ts similarity index 96% rename from src/shared/github/comment-types.ts rename to src/shared/github/comments/comment-types.ts index 5dc2f36a580..0cf9e9ebd07 100644 --- a/src/shared/github/comment-types.ts +++ b/src/shared/github/comments/comment-types.ts @@ -1,4 +1,4 @@ -import type { GitHubRepositoryIdentity } from './pull-request-types' +import type { GitHubRepositoryIdentity } from '../repositories/repository-identity' export type GitHubReactionContent = | '+1' diff --git a/src/shared/github/pr-comment-diff-hunk.ts b/src/shared/github/comments/pr-comment-diff-hunk.ts similarity index 100% rename from src/shared/github/pr-comment-diff-hunk.ts rename to src/shared/github/comments/pr-comment-diff-hunk.ts diff --git a/src/shared/github/pr-comment-mutability.ts b/src/shared/github/comments/pr-comment-mutability.ts similarity index 100% rename from src/shared/github/pr-comment-mutability.ts rename to src/shared/github/comments/pr-comment-mutability.ts diff --git a/src/shared/github/pr-inline-comment-placement.ts b/src/shared/github/comments/pr-inline-comment-placement.ts similarity index 100% rename from src/shared/github/pr-inline-comment-placement.ts rename to src/shared/github/comments/pr-inline-comment-placement.ts diff --git a/src/shared/github/github-callouts.ts b/src/shared/github/markdown/github-callouts.ts similarity index 100% rename from src/shared/github/github-callouts.ts rename to src/shared/github/markdown/github-callouts.ts diff --git a/src/shared/github/github-reference-links.ts b/src/shared/github/markdown/github-reference-links.ts similarity index 95% rename from src/shared/github/github-reference-links.ts rename to src/shared/github/markdown/github-reference-links.ts index 2107b18cdec..142ec79562c 100644 --- a/src/shared/github/github-reference-links.ts +++ b/src/shared/github/markdown/github-reference-links.ts @@ -1,5 +1,8 @@ -import type { Repo } from '../repo-types' -import { githubHostFromIdentityKey, repoUpstreamIdentityKey } from './repository-identity-key' +import type { Repo } from '../../repo-types' +import { + githubHostFromIdentityKey, + repoUpstreamIdentityKey +} from '../repositories/repository-identity-key' export type GitHubRepoReference = { owner: string diff --git a/src/shared/github/project-ref-input.test.ts b/src/shared/github/project-ref-input.test.ts deleted file mode 100644 index d0b5e6c907b..00000000000 --- a/src/shared/github/project-ref-input.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { - GITHUB_PROJECT_REF_INPUT_MAX_BYTES, - hasBoundedGitHubProjectRefInputText, - isGitHubProjectRefInputTooLarge -} from './project-ref-input' - -describe('GitHub project reference input limits', () => { - it('rejects oversized whitespace before submit checks trim the reference', () => { - const oversizedWhitespace = ' '.repeat(GITHUB_PROJECT_REF_INPUT_MAX_BYTES + 1) - - expect(isGitHubProjectRefInputTooLarge(oversizedWhitespace)).toBe(true) - expect(hasBoundedGitHubProjectRefInputText(oversizedWhitespace)).toBe(false) - expect(hasBoundedGitHubProjectRefInputText(' acme/42 ')).toBe(true) - expect(hasBoundedGitHubProjectRefInputText(' ')).toBe(false) - }) -}) diff --git a/src/shared/github/project-board-columns.test.ts b/src/shared/github/projects/project-board-columns.test.ts similarity index 100% rename from src/shared/github/project-board-columns.test.ts rename to src/shared/github/projects/project-board-columns.test.ts diff --git a/src/shared/github/project-board-columns.ts b/src/shared/github/projects/project-board-columns.ts similarity index 100% rename from src/shared/github/project-board-columns.ts rename to src/shared/github/projects/project-board-columns.ts diff --git a/src/shared/github/project-content-field.ts b/src/shared/github/projects/project-content-field.ts similarity index 100% rename from src/shared/github/project-content-field.ts rename to src/shared/github/projects/project-content-field.ts diff --git a/src/shared/github/project-group-sort.ts b/src/shared/github/projects/project-group-sort.ts similarity index 100% rename from src/shared/github/project-group-sort.ts rename to src/shared/github/projects/project-group-sort.ts diff --git a/src/shared/github/project-identity.test.ts b/src/shared/github/projects/project-identity.test.ts similarity index 72% rename from src/shared/github/project-identity.test.ts rename to src/shared/github/projects/project-identity.test.ts index cc340c2be65..58a9199d263 100644 --- a/src/shared/github/project-identity.test.ts +++ b/src/shared/github/projects/project-identity.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { githubProjectHost, githubProjectIdentityKey } from './project-identity' +import { githubProjectIdentityKey } from './project-identity' describe('GitHub project identity', () => { it('keeps legacy github.com keys while isolating Enterprise hosts and ports', () => { @@ -14,9 +14,4 @@ describe('GitHub project identity', () => { 'ghe.example:8443:organization:acme:7' ) }) - - it('pins host-less projects to github.com', () => { - expect(githubProjectHost()).toBe('github.com') - expect(githubProjectHost('ghe.example:8443')).toBe('ghe.example:8443') - }) }) diff --git a/src/shared/github/project-identity.ts b/src/shared/github/projects/project-identity.ts similarity index 94% rename from src/shared/github/project-identity.ts rename to src/shared/github/projects/project-identity.ts index 6edf05a057b..8fa10b9d145 100644 --- a/src/shared/github/project-identity.ts +++ b/src/shared/github/projects/project-identity.ts @@ -1,4 +1,4 @@ -import { isDefaultGitHubHost } from './repository-identity-key' +import { isDefaultGitHubHost } from '../repositories/repository-identity-key' export type GitHubProjectIdentity = { owner: string diff --git a/src/shared/github/project-ref-input.ts b/src/shared/github/projects/project-ref-input.ts similarity index 87% rename from src/shared/github/project-ref-input.ts rename to src/shared/github/projects/project-ref-input.ts index a3420cf44c3..8013c68313c 100644 --- a/src/shared/github/project-ref-input.ts +++ b/src/shared/github/projects/project-ref-input.ts @@ -1,4 +1,4 @@ -import { isClipboardTextByteLengthOverLimit } from '../clipboard-text' +import { isClipboardTextByteLengthOverLimit } from '../../clipboard-text' export const GITHUB_PROJECT_REF_INPUT_MAX_BYTES = 2 * 1024 export const GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR = 'Project reference is too large to resolve.' diff --git a/src/shared/github/project-relations.test-fixture.ts b/src/shared/github/projects/project-relations.test-fixture.ts similarity index 100% rename from src/shared/github/project-relations.test-fixture.ts rename to src/shared/github/projects/project-relations.test-fixture.ts diff --git a/src/shared/github/project-request-types.ts b/src/shared/github/projects/project-request-types.ts similarity index 97% rename from src/shared/github/project-request-types.ts rename to src/shared/github/projects/project-request-types.ts index 709810e77f0..6b8bc04405b 100644 --- a/src/shared/github/project-request-types.ts +++ b/src/shared/github/projects/project-request-types.ts @@ -1,6 +1,6 @@ // Keep Project request payloads separate from ProjectV2 domain shapes so renderer domain imports do not pull IPC arguments. import type { GitHubProjectFieldMutationValue, GitHubProjectOwnerType } from './project-types' -import type { GitHubIssueUpdate } from '../issue-mutation-types' +import type { GitHubIssueUpdate } from '../../issue-mutation-types' /** Set by a click: it may start the stopped WSL distro gh lives in (E9). Loads that also run * unprompted (mount effects, restore) leave it unset. Local IPC only. */ diff --git a/src/shared/github/project-result-types.ts b/src/shared/github/projects/project-result-types.ts similarity index 94% rename from src/shared/github/project-result-types.ts rename to src/shared/github/projects/project-result-types.ts index 92d3736002f..63de8f6f70f 100644 --- a/src/shared/github/project-result-types.ts +++ b/src/shared/github/projects/project-result-types.ts @@ -1,5 +1,5 @@ // Project IPC envelopes share classified failures and cross-module work-item/comment/user types unnecessary in the ProjectV2 domain model. -import type { PRComment } from './comment-types' +import type { PRComment } from '../comments/comment-types' import type { GitHubIssueType, GitHubProjectOwnerType, @@ -7,8 +7,8 @@ import type { GitHubProjectTable, GitHubProjectViewSummary } from './project-types' -import type { GitHubAssignableUser } from './pull-request-types' -import type { GitHubWorkItemDetails } from './work-item-types' +import type { GitHubAssignableUser } from '../pull-requests/pull-request-types' +import type { GitHubWorkItemDetails } from '../work-items/work-item-types' export type GitHubProjectViewErrorType = | 'auth_required' diff --git a/src/shared/github/project-roadmap-timeline.test.ts b/src/shared/github/projects/project-roadmap-timeline.test.ts similarity index 100% rename from src/shared/github/project-roadmap-timeline.test.ts rename to src/shared/github/projects/project-roadmap-timeline.test.ts diff --git a/src/shared/github/project-roadmap-timeline.ts b/src/shared/github/projects/project-roadmap-timeline.ts similarity index 100% rename from src/shared/github/project-roadmap-timeline.ts rename to src/shared/github/projects/project-roadmap-timeline.ts diff --git a/src/shared/github/project-types.ts b/src/shared/github/projects/project-types.ts similarity index 100% rename from src/shared/github/project-types.ts rename to src/shared/github/projects/project-types.ts diff --git a/src/shared/github/pull-request-auto-merge-availability.test.ts b/src/shared/github/pull-requests/merge/pull-request-auto-merge-availability.test.ts similarity index 100% rename from src/shared/github/pull-request-auto-merge-availability.test.ts rename to src/shared/github/pull-requests/merge/pull-request-auto-merge-availability.test.ts diff --git a/src/shared/github/pull-request-auto-merge-availability.ts b/src/shared/github/pull-requests/merge/pull-request-auto-merge-availability.ts similarity index 97% rename from src/shared/github/pull-request-auto-merge-availability.ts rename to src/shared/github/pull-requests/merge/pull-request-auto-merge-availability.ts index 533760baf76..b4505630c24 100644 --- a/src/shared/github/pull-request-auto-merge-availability.ts +++ b/src/shared/github/pull-requests/merge/pull-request-auto-merge-availability.ts @@ -1,4 +1,4 @@ -import type { PRMergeableState, PRReviewDecision, PRState } from './pull-request-types' +import type { PRMergeableState, PRReviewDecision, PRState } from '../pull-request-types' export type GitHubPRAutoMergeAvailabilityInput = { state: PRState diff --git a/src/shared/github/pull-request-merge-methods.test.ts b/src/shared/github/pull-requests/merge/pull-request-merge-methods.test.ts similarity index 81% rename from src/shared/github/pull-request-merge-methods.test.ts rename to src/shared/github/pull-requests/merge/pull-request-merge-methods.test.ts index 072e95de670..96da8dddf86 100644 --- a/src/shared/github/pull-request-merge-methods.test.ts +++ b/src/shared/github/pull-requests/merge/pull-request-merge-methods.test.ts @@ -5,13 +5,6 @@ import { } from './pull-request-merge-methods' describe('GitHub PR merge methods', () => { - it('keeps the historical squash-first fallback when repository metadata is missing', () => { - expect(resolveGitHubPRMergeMethods()).toEqual({ - defaultMethod: 'squash', - methods: ['squash', 'merge', 'rebase'] - }) - }) - it('uses GitHub viewer defaults and hides methods disabled by the repository', () => { const settings = normalizeGitHubPRMergeMethodSettings({ defaultMethod: 'REBASE', diff --git a/src/shared/github/pull-request-merge-methods.ts b/src/shared/github/pull-requests/merge/pull-request-merge-methods.ts similarity index 98% rename from src/shared/github/pull-request-merge-methods.ts rename to src/shared/github/pull-requests/merge/pull-request-merge-methods.ts index c2188275f2a..b2c36463512 100644 --- a/src/shared/github/pull-request-merge-methods.ts +++ b/src/shared/github/pull-requests/merge/pull-request-merge-methods.ts @@ -1,4 +1,4 @@ -import type { GitHubPRMergeMethod, GitHubPRMergeMethodSettings } from './pull-request-types' +import type { GitHubPRMergeMethod, GitHubPRMergeMethodSettings } from '../pull-request-types' export const GITHUB_PR_MERGE_METHODS = ['squash', 'merge', 'rebase'] as const diff --git a/src/shared/github/pull-request-merge-state.ts b/src/shared/github/pull-requests/merge/pull-request-merge-state.ts similarity index 95% rename from src/shared/github/pull-request-merge-state.ts rename to src/shared/github/pull-requests/merge/pull-request-merge-state.ts index e480c249ac6..e1d05797527 100644 --- a/src/shared/github/pull-request-merge-state.ts +++ b/src/shared/github/pull-requests/merge/pull-request-merge-state.ts @@ -1,5 +1,5 @@ -import { getProviderChecksOutcome } from '../source-control/checks/provider-check-summary' -import type { CheckStatus, ProviderCheckSummary } from './pull-request-types' +import { getProviderChecksOutcome } from '../../../source-control/checks/provider-check-summary' +import type { CheckStatus, ProviderCheckSummary } from '../pull-request-types' import { canShowGitHubPRAutoMergeControl, type GitHubPRAutoMergeAvailabilityInput diff --git a/src/shared/github/pull-request-stack-merge.ts b/src/shared/github/pull-requests/merge/pull-request-stack-merge.ts similarity index 97% rename from src/shared/github/pull-request-stack-merge.ts rename to src/shared/github/pull-requests/merge/pull-request-stack-merge.ts index 3f2ea456bc4..f48521d5e8f 100644 --- a/src/shared/github/pull-request-stack-merge.ts +++ b/src/shared/github/pull-requests/merge/pull-request-stack-merge.ts @@ -1,4 +1,4 @@ -import type { GitHubPRStack, GitHubPRStackEntry } from './pull-request-types' +import type { GitHubPRStack, GitHubPRStackEntry } from '../pull-request-types' export type GitHubPRStackMergeScope = { count: number diff --git a/src/shared/github/pr-reviewer-display.ts b/src/shared/github/pull-requests/pr-reviewer-display.ts similarity index 97% rename from src/shared/github/pr-reviewer-display.ts rename to src/shared/github/pull-requests/pr-reviewer-display.ts index a7a921b146e..7b9e24b9739 100644 --- a/src/shared/github/pr-reviewer-display.ts +++ b/src/shared/github/pull-requests/pr-reviewer-display.ts @@ -1,6 +1,6 @@ import type { GitHubAssignableUser } from './pull-request-types' -import type { GitHubWorkItem } from './work-item-types' -import { isClipboardTextByteLengthOverLimit } from '../clipboard-text' +import type { GitHubWorkItem } from '../work-items/work-item-types' +import { isClipboardTextByteLengthOverLimit } from '../../clipboard-text' type ReviewDisplayItem = Pick export type GitHubPRPrimaryReviewer = Pick & { diff --git a/src/shared/github/pull-request-types.ts b/src/shared/github/pull-requests/pull-request-types.ts similarity index 92% rename from src/shared/github/pull-request-types.ts rename to src/shared/github/pull-requests/pull-request-types.ts index cfe2a344b18..e0bb937348b 100644 --- a/src/shared/github/pull-request-types.ts +++ b/src/shared/github/pull-requests/pull-request-types.ts @@ -1,8 +1,13 @@ +import type { GitHubRepositoryIdentity } from '../repositories/repository-identity' + export type PRState = 'open' | 'closed' | 'merged' | 'draft' + export type IssueState = 'open' | 'closed' + export type CheckStatus = 'pending' | 'success' | 'failure' | 'neutral' export type PRMergeableState = 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN' + export type PRReviewDecision = 'APPROVED' | 'CHANGES_REQUESTED' | 'REVIEW_REQUIRED' export type PRConflictSummary = { @@ -13,9 +18,6 @@ export type PRConflictSummary = { localMergeState?: 'clean' } -// Keep GHES host identity across renderer/RPC boundaries to prevent actions targeting same-named github.com repos. -export type GitHubRepositoryIdentity = { owner: string; repo: string; host?: string } - export type GitHubPRMergeMethod = 'merge' | 'squash' | 'rebase' export type GitHubPRMergeMethodSettings = { @@ -150,6 +152,3 @@ export type GitHubPRFileContents = { originalTooLarge?: boolean modifiedTooLarge?: boolean } - -// Share the identity shape across IPC/renderer without main imports; main’s gh-utils.ts retains the OwnerRepo alias. -export type GitHubOwnerRepo = GitHubRepositoryIdentity diff --git a/src/shared/github/linked-pr-staleness.ts b/src/shared/github/pull-requests/refresh/linked-pr-staleness.ts similarity index 96% rename from src/shared/github/linked-pr-staleness.ts rename to src/shared/github/pull-requests/refresh/linked-pr-staleness.ts index b3ddc6e9a19..35f0cae6e68 100644 --- a/src/shared/github/linked-pr-staleness.ts +++ b/src/shared/github/pull-requests/refresh/linked-pr-staleness.ts @@ -1,5 +1,5 @@ -import type { PRInfo } from './pull-request-types' -import type { Worktree } from '../worktree/types' +import type { PRInfo } from '../pull-request-types' +import type { Worktree } from '../../../worktree/types' export function shouldClearDivergedLinkedMergedPR(args: { pr: PRInfo | null diff --git a/src/shared/github/pr-branch-fallback-rule.test.ts b/src/shared/github/pull-requests/refresh/pr-branch-fallback-rule.test.ts similarity index 100% rename from src/shared/github/pr-branch-fallback-rule.test.ts rename to src/shared/github/pull-requests/refresh/pr-branch-fallback-rule.test.ts diff --git a/src/shared/github/pr-branch-fallback-rule.ts b/src/shared/github/pull-requests/refresh/pr-branch-fallback-rule.ts similarity index 97% rename from src/shared/github/pr-branch-fallback-rule.ts rename to src/shared/github/pull-requests/refresh/pr-branch-fallback-rule.ts index 3c299532b0d..f0efcb52607 100644 --- a/src/shared/github/pr-branch-fallback-rule.ts +++ b/src/shared/github/pull-requests/refresh/pr-branch-fallback-rule.ts @@ -1,4 +1,4 @@ -import type { PRInfo } from './pull-request-types' +import type { PRInfo } from '../pull-request-types' import type { GitHubPRRefreshCandidate } from './pull-request-refresh-types' export type PRBranchFallback = { diff --git a/src/shared/github/pull-request-for-branch-outcome.test.ts b/src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.test.ts similarity index 67% rename from src/shared/github/pull-request-for-branch-outcome.test.ts rename to src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.test.ts index 288d131f46a..8dabf2549dd 100644 --- a/src/shared/github/pull-request-for-branch-outcome.test.ts +++ b/src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { PRInfo } from './pull-request-types' +import type { PRInfo } from '../pull-request-types' import { normalizeGitHubPRForBranchOutcome } from './pull-request-for-branch-outcome' const PR: PRInfo = { @@ -14,13 +14,6 @@ const PR: PRInfo = { } describe('normalizeGitHubPRForBranchOutcome', () => { - it.each([ - { kind: 'no-pr' as const, fetchedAt: 10 }, - { kind: 'no-pr' as const, fetchedAt: 10, rejectedPRUrls: [PR.url] } - ])('preserves no-PR responses from older and newer hosts', (outcome) => { - expect(normalizeGitHubPRForBranchOutcome(outcome, 20)).toEqual(outcome) - }) - it('normalizes legacy PRInfo and null responses', () => { expect(normalizeGitHubPRForBranchOutcome(PR, 20)).toEqual({ kind: 'found', diff --git a/src/shared/github/pull-request-for-branch-outcome.ts b/src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.ts similarity index 93% rename from src/shared/github/pull-request-for-branch-outcome.ts rename to src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.ts index 6e3a43045cc..c0d0093f0dd 100644 --- a/src/shared/github/pull-request-for-branch-outcome.ts +++ b/src/shared/github/pull-requests/refresh/pull-request-for-branch-outcome.ts @@ -1,5 +1,5 @@ import type { PRRefreshOutcome } from './pull-request-refresh-types' -import type { PRInfo } from './pull-request-types' +import type { PRInfo } from '../pull-request-types' export type GitHubPRForBranchResponse = PRRefreshOutcome | PRInfo | null diff --git a/src/shared/github/pull-request-refresh-types.ts b/src/shared/github/pull-requests/refresh/pull-request-refresh-types.ts similarity index 94% rename from src/shared/github/pull-request-refresh-types.ts rename to src/shared/github/pull-requests/refresh/pull-request-refresh-types.ts index 35e78f85361..bce9d7bac2d 100644 --- a/src/shared/github/pull-request-refresh-types.ts +++ b/src/shared/github/pull-requests/refresh/pull-request-refresh-types.ts @@ -1,5 +1,5 @@ -import type { RepoKind } from '../repo-types' -import type { CheckStatus, PRInfo, PRMergeableState, PRState } from './pull-request-types' +import type { RepoKind } from '../../../repo-types' +import type { CheckStatus, PRInfo, PRMergeableState, PRState } from '../pull-request-types' /** Classified failures drive stable empty-state copy. Auth/permission/repo_unavailable/gh_unavailable make existing-review lookup impossible and must hide Create. */ export type PRRefreshErrorType = @@ -12,9 +12,6 @@ export type PRRefreshErrorType = | 'server_error' | 'unknown' -// Backward-compatible name used by outage-copy consumers added on main. -export type PRRefreshUpstreamErrorType = PRRefreshErrorType - export type PRRefreshOutcome = | { kind: 'found'; pr: PRInfo; fetchedAt: number } | { kind: 'no-pr'; fetchedAt: number; rejectedPRUrls?: string[] } diff --git a/src/shared/github/links.ts b/src/shared/github/repositories/links.ts similarity index 100% rename from src/shared/github/links.ts rename to src/shared/github/repositories/links.ts diff --git a/src/shared/github/owner-slug.ts b/src/shared/github/repositories/owner-slug.ts similarity index 100% rename from src/shared/github/owner-slug.ts rename to src/shared/github/repositories/owner-slug.ts diff --git a/src/shared/github/repository-identity-key.test.ts b/src/shared/github/repositories/repository-identity-key.test.ts similarity index 100% rename from src/shared/github/repository-identity-key.test.ts rename to src/shared/github/repositories/repository-identity-key.test.ts diff --git a/src/shared/github/repository-identity-key.ts b/src/shared/github/repositories/repository-identity-key.ts similarity index 97% rename from src/shared/github/repository-identity-key.ts rename to src/shared/github/repositories/repository-identity-key.ts index a31ccad2f18..afca0304eb4 100644 --- a/src/shared/github/repository-identity-key.ts +++ b/src/shared/github/repositories/repository-identity-key.ts @@ -1,4 +1,4 @@ -import type { Repo } from '../repo-types' +import type { Repo } from '../../repo-types' // Share github.com/GHES detection across cache identity, quota scoping and execution routing. export function isDefaultGitHubHost(host?: string): boolean { diff --git a/src/shared/github/repositories/repository-identity.ts b/src/shared/github/repositories/repository-identity.ts new file mode 100644 index 00000000000..e30d088d369 --- /dev/null +++ b/src/shared/github/repositories/repository-identity.ts @@ -0,0 +1,2 @@ +// Keep GHES host identity across renderer/RPC boundaries to prevent actions targeting same-named github.com repos. +export type GitHubRepositoryIdentity = { owner: string; repo: string; host?: string } diff --git a/src/shared/github/exact-work-item-operation.ts b/src/shared/github/work-items/exact-work-item-operation.ts similarity index 88% rename from src/shared/github/exact-work-item-operation.ts rename to src/shared/github/work-items/exact-work-item-operation.ts index c6221efc092..3c6e370bf5f 100644 --- a/src/shared/github/exact-work-item-operation.ts +++ b/src/shared/github/work-items/exact-work-item-operation.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import { SlugRepo } from '../rpc-contract/github-repo-target-params' -import { ExpectedRuntimeSourceSchema } from '../runtime-rpc-envelope' +import { SlugRepo } from '../../rpc-contract/github-repo-target-params' +import { ExpectedRuntimeSourceSchema } from '../../runtime-rpc-envelope' const methods = new Set([ 'github.workItemDetails', diff --git a/src/shared/github/work-item-page-errors.ts b/src/shared/github/work-items/work-item-page-errors.ts similarity index 95% rename from src/shared/github/work-item-page-errors.ts rename to src/shared/github/work-items/work-item-page-errors.ts index 19fba08569c..6da1a161f31 100644 --- a/src/shared/github/work-item-page-errors.ts +++ b/src/shared/github/work-items/work-item-page-errors.ts @@ -1,4 +1,4 @@ -import type { ClassifiedError } from '../classified-error' +import type { ClassifiedError } from '../../classified-error' import { GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN } from './work-items-query-bounds' export function getWorkItemPageErrorTypes(errors: unknown): ClassifiedError['type'][] { diff --git a/src/shared/github/work-item-ssh-error.ts b/src/shared/github/work-items/work-item-ssh-error.ts similarity index 100% rename from src/shared/github/work-item-ssh-error.ts rename to src/shared/github/work-items/work-item-ssh-error.ts diff --git a/src/shared/github/work-item-target.ts b/src/shared/github/work-items/work-item-target.ts similarity index 93% rename from src/shared/github/work-item-target.ts rename to src/shared/github/work-items/work-item-target.ts index 701ba9d4297..3b69ef9b3ba 100644 --- a/src/shared/github/work-item-target.ts +++ b/src/shared/github/work-items/work-item-target.ts @@ -1,6 +1,6 @@ -import type { GitHubOwnerRepo } from './pull-request-types' +import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../repositories/repository-identity' import type { GitHubWorkItem, GitHubWorkItemDetails } from './work-item-types' -import { githubRepoIdentityKey } from './repository-identity-key' +import { githubRepoIdentityKey } from '../repositories/repository-identity-key' export type GitHubWorkItemTarget = { repository: GitHubOwnerRepo diff --git a/src/shared/github/work-item-types.ts b/src/shared/github/work-items/work-item-types.ts similarity index 89% rename from src/shared/github/work-item-types.ts rename to src/shared/github/work-items/work-item-types.ts index ae8cfd238ce..23c8c9ada5b 100644 --- a/src/shared/github/work-item-types.ts +++ b/src/shared/github/work-items/work-item-types.ts @@ -1,18 +1,20 @@ -import type { ClassifiedError } from '../classified-error' -import type { ExecutionHostId } from '../execution-host' -import type { PRCheckDetail } from './check-types' -import type { GitHubIssueTimelineItem, PRComment } from './comment-types' +import type { ClassifiedError } from '../../classified-error' +import type { ExecutionHostId } from '../../execution-host' +import type { PRCheckDetail } from '../checks/check-types' +import type { GitHubIssueTimelineItem, PRComment } from '../comments/comment-types' import type { GitHubAssignableUser, - GitHubOwnerRepo, GitHubPRFile, GitHubPRMergeMethodSettings, GitHubPRReviewSummary, - GitHubRepositoryIdentity, PRMergeableState, PRReviewDecision, ProviderCheckSummary -} from './pull-request-types' +} from '../pull-requests/pull-request-types' +import type { + GitHubRepositoryIdentity as GitHubOwnerRepo, + GitHubRepositoryIdentity +} from '../repositories/repository-identity' export type GitHubWorkItem = { id: string diff --git a/src/shared/github/work-items-query-bounds.ts b/src/shared/github/work-items/work-items-query-bounds.ts similarity index 87% rename from src/shared/github/work-items-query-bounds.ts rename to src/shared/github/work-items/work-items-query-bounds.ts index be09c66e624..549c7c52782 100644 --- a/src/shared/github/work-items-query-bounds.ts +++ b/src/shared/github/work-items/work-items-query-bounds.ts @@ -1,4 +1,4 @@ -import { isClipboardTextByteLengthOverLimit } from '../clipboard-text' +import { isClipboardTextByteLengthOverLimit } from '../../clipboard-text' export const GITHUB_WORK_ITEMS_QUERY_MAX_BYTES = 8 * 1024 diff --git a/src/shared/gitlab-types.ts b/src/shared/gitlab-types.ts index a842ba9bf71..111b2d9e2f4 100644 --- a/src/shared/gitlab-types.ts +++ b/src/shared/gitlab-types.ts @@ -5,7 +5,7 @@ import type { CheckStatus, PRConflictSummary, ProviderCheckSummary -} from './github/pull-request-types' +} from './github/pull-requests/pull-request-types' import type { HostedReviewDecision } from './source-control/hosted-review/hosted-review-contracts' // Why: flat owner/repo is inadequate — projects nest (`group/subgroup/project`) and self-hosted hosts must travel with the path for URL/glab targeting. diff --git a/src/shared/global-settings-types.ts b/src/shared/global-settings-types.ts index 0051f482fe2..0586388fcf9 100644 --- a/src/shared/global-settings-types.ts +++ b/src/shared/global-settings-types.ts @@ -4,7 +4,7 @@ import type { BrowserProfileScope } from './browser/profiles/browser-profile-sco import type { GlobalSettingsBase } from './global-settings-base' import type { ExecutionHostId } from './execution-host' import type { OrcaWorkspaceLayout } from './orca-workspace-layout' -import type { GitHubProjectSettings } from './github/project-types' +import type { GitHubProjectSettings } from './github/projects/project-types' import type { VoiceSettings } from './speech-types' import type { AiVaultSearchSettings } from './ai-vault/search/ai-vault-search-settings' import type { GitLabProjectSettings } from './gitlab-types' diff --git a/src/shared/native-chat/tools/native-chat-opened-pull-requests.ts b/src/shared/native-chat/tools/native-chat-opened-pull-requests.ts index 5f2f401b8e7..0c1341f74f1 100644 --- a/src/shared/native-chat/tools/native-chat-opened-pull-requests.ts +++ b/src/shared/native-chat/tools/native-chat-opened-pull-requests.ts @@ -1,4 +1,4 @@ -import { parseGitHubIssueOrPRLink, type RepoSlug } from '../../github/links' +import { parseGitHubIssueOrPRLink, type RepoSlug } from '../../github/repositories/links' import { parseGitLabIssueOrMRLink, type ProjectSlug } from '../../new-workspace/gitlab-links' import type { NativeChatMessage } from '../native-chat-types' import { nativeChatToolCallScript } from './native-chat-tool-call-script' diff --git a/src/shared/new-workspace/github-links.ts b/src/shared/new-workspace/github-links.ts index 381cdb9f35d..37fe24077c3 100644 --- a/src/shared/new-workspace/github-links.ts +++ b/src/shared/new-workspace/github-links.ts @@ -2,11 +2,9 @@ import { type GitHubIssueOrPRLink, parseGitHubIssueOrPRLink, parseGitHubIssueOrPRNumber -} from '../github/links' +} from '../github/repositories/links' import { isWorkItemLinkQueryTooLarge } from './work-item-link-query-bounds' -export * from '../github/links' - const HTTP_URL_PREFIX_RE = /^https?:\/\//i export type GitHubLinkQuery = { diff --git a/src/shared/new-workspace/github-work-item-identity.ts b/src/shared/new-workspace/github-work-item-identity.ts index 58e96724c35..d81ce278b37 100644 --- a/src/shared/new-workspace/github-work-item-identity.ts +++ b/src/shared/new-workspace/github-work-item-identity.ts @@ -1,4 +1,4 @@ -import { parseGitHubIssueOrPRLink } from './github-links' +import { parseGitHubIssueOrPRLink } from '../github/repositories/links' export type GitHubWorkItemIdentity = { type: 'issue' | 'pr' diff --git a/src/shared/new-workspace/smart-workspace-source-results.ts b/src/shared/new-workspace/smart-workspace-source-results.ts index 37d4b206f9b..f20f3276c85 100644 --- a/src/shared/new-workspace/smart-workspace-source-results.ts +++ b/src/shared/new-workspace/smart-workspace-source-results.ts @@ -1,4 +1,4 @@ -import type { GitHubWorkItem } from '../github/work-item-types' +import type { GitHubWorkItem } from '../github/work-items/work-item-types' import type { GitLabWorkItem } from '../gitlab-types' import type { JiraIssue } from '../jira/jira-types' import type { LinearIssue } from '../linear/issue-types' @@ -6,7 +6,7 @@ import type { LinearCollectionResult } from '../linear/workspace-types' import type { BaseRefSearchResult } from '../repo-types' import { parseJiraIssueUrl } from '../jira/jira-issue-url' import { buildJiraTextMatchJql } from '../jira/jira-search-input-jql' -import type { GitHubIssueOrPRLink } from '../github/links' +import type { GitHubIssueOrPRLink } from '../github/repositories/links' import { buildSmartWorkspaceUrlSourceRows, type SmartWorkspaceGitLabUrlIntent diff --git a/src/shared/new-workspace/smart-workspace-url-source-results.ts b/src/shared/new-workspace/smart-workspace-url-source-results.ts index a1aa75b82f0..1bfbcb42598 100644 --- a/src/shared/new-workspace/smart-workspace-url-source-results.ts +++ b/src/shared/new-workspace/smart-workspace-url-source-results.ts @@ -1,6 +1,6 @@ -import { parseGitHubIssueOrPRLink, type GitHubIssueOrPRLink } from '../github/links' -import { githubRepoIdentityKey } from '../github/repository-identity-key' -import type { GitHubWorkItem } from '../github/work-item-types' +import { parseGitHubIssueOrPRLink, type GitHubIssueOrPRLink } from '../github/repositories/links' +import { githubRepoIdentityKey } from '../github/repositories/repository-identity-key' +import type { GitHubWorkItem } from '../github/work-items/work-item-types' import type { GitLabWorkItem } from '../gitlab-types' import type { LinearIssue } from '../linear/issue-types' import { parseGitLabIssueOrMRLink } from './gitlab-links' diff --git a/src/shared/new-workspace/work-item-lookup-text.ts b/src/shared/new-workspace/work-item-lookup-text.ts index 45f4c963ee2..d25b447e6dc 100644 --- a/src/shared/new-workspace/work-item-lookup-text.ts +++ b/src/shared/new-workspace/work-item-lookup-text.ts @@ -1,4 +1,4 @@ -import { parseGitHubIssueOrPRLink, parseGitHubIssueOrPRNumber } from './github-links' +import { parseGitHubIssueOrPRLink, parseGitHubIssueOrPRNumber } from '../github/repositories/links' import { parseGitLabIssueOrMRLink } from './gitlab-links' import { parseJiraIssueUrl } from '../jira/jira-issue-url' diff --git a/src/shared/persisted-state-types.ts b/src/shared/persisted-state-types.ts index 753f39251a0..cd6b05e6f1d 100644 --- a/src/shared/persisted-state-types.ts +++ b/src/shared/persisted-state-types.ts @@ -15,7 +15,7 @@ import type { DiffComment } from './diff-comment-types' import type { FolderWorkspace } from './folder-workspaces/folder-workspace-types' import type { WorkspaceKey } from './workspace-scope' import type { GlobalSettings } from './global-settings-types' -import type { IssueInfo, PRInfo } from './github/pull-request-types' +import type { IssueInfo, PRInfo } from './github/pull-requests/pull-request-types' import type { OnboardingState } from './onboarding-state-types' import type { PersistedUIState } from './persisted-ui-state-types' import type { ProjectGroup } from './project-group-types' diff --git a/src/shared/project-host-setup-projection.ts b/src/shared/project-host-setup-projection.ts index 2eb7d0e1ab1..5d9680355f2 100644 --- a/src/shared/project-host-setup-projection.ts +++ b/src/shared/project-host-setup-projection.ts @@ -4,7 +4,10 @@ import { normalizeProjectRow } from './project-catalog-row-normalization' import { normalizeGitHubRemoteHost } from './git/remotes/git-remote-host-alias' -import { githubRepoIdentityKey, isDefaultGitHubHost } from './github/repository-identity-key' +import { + githubRepoIdentityKey, + isDefaultGitHubHost +} from './github/repositories/repository-identity-key' import type { Project, ProjectHostSetup, ProjectProviderIdentity } from './project-types' import type { Repo } from './repo-types' diff --git a/src/shared/repo-types.ts b/src/shared/repo-types.ts index 012b0d1d1af..f33a24cec48 100644 --- a/src/shared/repo-types.ts +++ b/src/shared/repo-types.ts @@ -1,7 +1,7 @@ import type { DictationTerm } from './dictation-dictionary' import type { RepoIcon } from './repo-icon' -import type { GhAccountBinding } from './github/account-binding' -import type { GitHubRepositoryIdentity } from './github/pull-request-types' +import type { GhAccountBinding } from './github/accounts/account-binding' +import type { GitHubRepositoryIdentity } from './github/repositories/repository-identity' import type { RepoHookSettings } from './orca-yaml-hook-types' import type { ForkSyncMode } from './git/remotes/git-fork-sync' import type { GitRemoteIdentity } from './git/remotes/git-remote-identity' diff --git a/src/shared/review-refresh-policy.ts b/src/shared/review-refresh-policy.ts index 3905c261b1c..c6076569559 100644 --- a/src/shared/review-refresh-policy.ts +++ b/src/shared/review-refresh-policy.ts @@ -1,5 +1,5 @@ import type { HostedReviewState } from './source-control/hosted-review/hosted-review-contracts' -import type { CheckStatus } from './github/pull-request-types' +import type { CheckStatus } from './github/pull-requests/pull-request-types' export const REVIEW_REFRESH_COOLDOWN_MS = 10_000 export const CLOSED_REVIEW_REFRESH_INTERVAL_MS = 15 * 60_000 diff --git a/src/shared/rpc-contract/github-pull-request-params.ts b/src/shared/rpc-contract/github-pull-request-params.ts index 452ceea0a1f..24562bf5b57 100644 --- a/src/shared/rpc-contract/github-pull-request-params.ts +++ b/src/shared/rpc-contract/github-pull-request-params.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import type { GitHubPRRefreshReason } from '../github/pull-request-refresh-types' +import type { GitHubPRRefreshReason } from '../github/pull-requests/refresh/pull-request-refresh-types' import { OptionalGhAccountExpectation, RepoSelector, SlugRepo } from './github-repo-target-params' import { OptionalString, requiredString } from './rpc-param-primitives' diff --git a/src/shared/rpc-contract/github-repo-target-params.ts b/src/shared/rpc-contract/github-repo-target-params.ts index fd6d27fb746..e207b3b9ef2 100644 --- a/src/shared/rpc-contract/github-repo-target-params.ts +++ b/src/shared/rpc-contract/github-repo-target-params.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import { normalizeGhAccountBinding } from '../github/account-binding' +import { normalizeGhAccountBinding } from '../github/accounts/account-binding' import { OptionalBoolean, OptionalString, requiredString } from './rpc-param-primitives' import { OptionalExecutionHostId } from './worktree-params' diff --git a/src/shared/rpc-contract/repo-update-params.ts b/src/shared/rpc-contract/repo-update-params.ts index c2be6d0a49a..e17467183d0 100644 --- a/src/shared/rpc-contract/repo-update-params.ts +++ b/src/shared/rpc-contract/repo-update-params.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { normalizeRepoSourceControlAiOverrides } from '../source-control/ai/settings/source-control-ai-repo-settings' import { normalizeRepoBadgeColor } from '../repo-badge-color' import { sanitizeRepoIcon } from '../repo-icon' -import { normalizeGhAccountBinding } from '../github/account-binding' +import { normalizeGhAccountBinding } from '../github/accounts/account-binding' import { normalizeJiraProjectReference } from '../jira/jira-project-reference' import { normalizeCustomWorktreeVisibilitySources, diff --git a/src/shared/runtime-worktree-contracts.ts b/src/shared/runtime-worktree-contracts.ts index eb8c9cc191c..7d8e68293f1 100644 --- a/src/shared/runtime-worktree-contracts.ts +++ b/src/shared/runtime-worktree-contracts.ts @@ -8,7 +8,7 @@ import type { import type { AgentMainAgentStatus } from './agent-status/lifecycle/main-agent-status' import type { ProgramStatusFacet } from './program-status/facet' import type { AgentListVerdict } from './agent-status/presentation/agent-list-status' -import type { CheckStatus, ProviderCheckSummary } from './github/pull-request-types' +import type { CheckStatus, ProviderCheckSummary } from './github/pull-requests/pull-request-types' import type { AgentChildWorkView } from './agent-status/child-work/agent-status-child-work-view' import type { AgentSubagentSnapshot } from './agent-status/contracts/agent-status-subagent-snapshot' import type { RuntimeTerminalInteractiveWait } from './runtime-terminal-contracts' diff --git a/src/shared/sidebar-list/workspace-review-facts.ts b/src/shared/sidebar-list/workspace-review-facts.ts index 20a84bd12ba..07dd5e38356 100644 --- a/src/shared/sidebar-list/workspace-review-facts.ts +++ b/src/shared/sidebar-list/workspace-review-facts.ts @@ -1,7 +1,7 @@ import * as z from 'zod' import type { Repo } from '../repo-types' import type { Worktree } from '../worktree/types' -import { getGitHubPRCacheKeyCandidates } from '../github/github-cache-key' +import { getGitHubPRCacheKeyCandidates } from '../github/cache/github-cache-key' import { isGitHubPRSuppressed } from '../worktree/github-pr-suppression' import { getPRGroupKeyForState, type PRGroupKey } from '../workspace-view/workspace-pr-groups' import type { PairedServerWorkspaceRow } from './paired-server-rows' diff --git a/src/shared/source-control/checks/gitlab/gitlab-job-trace-check-details.ts b/src/shared/source-control/checks/gitlab/gitlab-job-trace-check-details.ts index 9b7f391715d..0e772c2d8bc 100644 --- a/src/shared/source-control/checks/gitlab/gitlab-job-trace-check-details.ts +++ b/src/shared/source-control/checks/gitlab/gitlab-job-trace-check-details.ts @@ -1,5 +1,5 @@ import { gitLabJobTraceToLogExcerpt } from './gitlab-job-log-excerpt' -import type { PRCheckDetail, PRCheckRunDetails } from '../../../github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../../github/checks/check-types' export type GitLabJobTraceCheckDetailsStrings = { /** Shown for a job with no log: never ran, waiting on a human, or log erased/expired. */ diff --git a/src/shared/source-control/checks/gitlab/gitlab-pipeline-checks.ts b/src/shared/source-control/checks/gitlab/gitlab-pipeline-checks.ts index 4f37e6bca0d..2ff62564000 100644 --- a/src/shared/source-control/checks/gitlab/gitlab-pipeline-checks.ts +++ b/src/shared/source-control/checks/gitlab/gitlab-pipeline-checks.ts @@ -1,6 +1,6 @@ import type { GitLabPipeline, GitLabPipelineJob } from '../../../gitlab-types' -import type { PRCheckDetail } from '../../../github/check-types' -import type { CheckStatus } from '../../../github/pull-request-types' +import type { PRCheckDetail } from '../../../github/checks/check-types' +import type { CheckStatus } from '../../../github/pull-requests/pull-request-types' import { checkRunTimes } from '../pr-check-time' export function mapGitLabPipelineJobStatusToCheckStatus(status: string): PRCheckDetail['status'] { diff --git a/src/shared/source-control/checks/pr-check-severity-order.ts b/src/shared/source-control/checks/pr-check-severity-order.ts index 4e5b0b93df0..61e914fcb95 100644 --- a/src/shared/source-control/checks/pr-check-severity-order.ts +++ b/src/shared/source-control/checks/pr-check-severity-order.ts @@ -1,4 +1,4 @@ -import type { PRCheckDetail } from '../../github/check-types' +import type { PRCheckDetail } from '../../github/checks/check-types' // Neutral/skipped carry no signal and must sink below success. Map provider values to avoid prototype-name ranks // (e.g. constructor/toString resolving as functions on plain objects). diff --git a/src/shared/source-control/checks/pr-check-status-label.ts b/src/shared/source-control/checks/pr-check-status-label.ts index 86531dddb89..1bddbde3ba5 100644 --- a/src/shared/source-control/checks/pr-check-status-label.ts +++ b/src/shared/source-control/checks/pr-check-status-label.ts @@ -1,4 +1,4 @@ -import type { PRCheckDetail } from '../../github/check-types' +import type { PRCheckDetail } from '../../github/checks/check-types' export function getCheckConclusion(check: PRCheckDetail): NonNullable { return check.conclusion ?? 'pending' diff --git a/src/shared/source-control/checks/pr-check-status.ts b/src/shared/source-control/checks/pr-check-status.ts index 8a0cd022e5f..cfd1e674bd7 100644 --- a/src/shared/source-control/checks/pr-check-status.ts +++ b/src/shared/source-control/checks/pr-check-status.ts @@ -4,8 +4,11 @@ import { summarizeProviderChecks, type CheckOutcomeInput } from './provider-check-summary' -import type { PRCheckDetail } from '../../github/check-types' -import type { CheckStatus, ProviderCheckSummary } from '../../github/pull-request-types' +import type { PRCheckDetail } from '../../github/checks/check-types' +import type { + CheckStatus, + ProviderCheckSummary +} from '../../github/pull-requests/pull-request-types' export function derivePRCheckStatus(checks: readonly PRCheckDetail[]): CheckStatus { const { state } = summarizeProviderChecks(checks) diff --git a/src/shared/source-control/checks/pr-check-step-status.ts b/src/shared/source-control/checks/pr-check-step-status.ts index 6ff804b1f9c..1c9ff56681e 100644 --- a/src/shared/source-control/checks/pr-check-step-status.ts +++ b/src/shared/source-control/checks/pr-check-step-status.ts @@ -1,4 +1,4 @@ -import type { PRCheckJob, PRCheckStep } from '../../github/check-types' +import type { PRCheckJob, PRCheckStep } from '../../github/checks/check-types' export type StepOutcome = 'success' | 'failure' | 'skipped' | 'pending' diff --git a/src/shared/source-control/checks/pr-check-time.ts b/src/shared/source-control/checks/pr-check-time.ts index 1ba1aee196e..b3c9ccb3083 100644 --- a/src/shared/source-control/checks/pr-check-time.ts +++ b/src/shared/source-control/checks/pr-check-time.ts @@ -1,4 +1,4 @@ -import type { PRCheckDetail } from '../../github/check-types' +import type { PRCheckDetail } from '../../github/checks/check-types' // GitHub reports an unset time as the zero date; anything before the epoch is that sentinel, not a run. export function normalizeCheckTimestamp(value: unknown): string | undefined { diff --git a/src/shared/source-control/checks/pr-checks-fix-prompt.test.ts b/src/shared/source-control/checks/pr-checks-fix-prompt.test.ts index 5831190cc6a..b5b174b178a 100644 --- a/src/shared/source-control/checks/pr-checks-fix-prompt.test.ts +++ b/src/shared/source-control/checks/pr-checks-fix-prompt.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { buildFixBrokenChecksPrompt, getCheckDetailsPromptKey } from './pr-checks-fix-prompt' -import type { PRCheckDetail, PRCheckJob, PRCheckRunDetails } from '../../github/check-types' +import type { PRCheckDetail, PRCheckJob, PRCheckRunDetails } from '../../github/checks/check-types' const failingCheck: PRCheckDetail = { name: 'unit', diff --git a/src/shared/source-control/checks/pr-checks-fix-prompt.ts b/src/shared/source-control/checks/pr-checks-fix-prompt.ts index dca7aac4ea7..0a269ee3bc7 100644 --- a/src/shared/source-control/checks/pr-checks-fix-prompt.ts +++ b/src/shared/source-control/checks/pr-checks-fix-prompt.ts @@ -1,4 +1,4 @@ -import type { PRCheckDetail, PRCheckRunDetails } from '../../github/check-types' +import type { PRCheckDetail, PRCheckRunDetails } from '../../github/checks/check-types' import { getCheckStatusLabel } from './pr-check-status-label' export const PROMPT_LOG_TAIL_LINES = 150 diff --git a/src/shared/source-control/checks/provider-check-summary.ts b/src/shared/source-control/checks/provider-check-summary.ts index 12bc1f7f384..3cce016c589 100644 --- a/src/shared/source-control/checks/provider-check-summary.ts +++ b/src/shared/source-control/checks/provider-check-summary.ts @@ -1,4 +1,7 @@ -import type { CheckStatus, ProviderCheckSummary } from '../../github/pull-request-types' +import type { + CheckStatus, + ProviderCheckSummary +} from '../../github/pull-requests/pull-request-types' export type CheckOutcome = 'passed' | 'failed' | 'pending' | 'neutral' diff --git a/src/shared/source-control/decisions/source-control-next-step-decision.ts b/src/shared/source-control/decisions/source-control-next-step-decision.ts index ece8744032f..cd7136b4720 100644 --- a/src/shared/source-control/decisions/source-control-next-step-decision.ts +++ b/src/shared/source-control/decisions/source-control-next-step-decision.ts @@ -1,10 +1,10 @@ import type { GitConflictOperation } from '../../git/status/git-status-types' -import type { PRInfo } from '../../github/pull-request-types' +import type { PRInfo } from '../../github/pull-requests/pull-request-types' import { resolveGitHubPRMergeState, type GitHubPRMergeStateKind -} from '../../github/pull-request-merge-state' -import { resolveGitHubPRStackMergeDecision } from '../../github/pull-request-stack-merge' +} from '../../github/pull-requests/merge/pull-request-merge-state' +import { resolveGitHubPRStackMergeDecision } from '../../github/pull-requests/merge/pull-request-stack-merge' import type { SourceControlCommitAreaPrimaryActionDecision } from './source-control-primary-action-decision-types' // What the branch's hosted review creation offers once nothing local is left to do. diff --git a/src/shared/source-control/decisions/source-control-primary-action-decision-types.ts b/src/shared/source-control/decisions/source-control-primary-action-decision-types.ts index 287009bf53b..ef91aa8e3c8 100644 --- a/src/shared/source-control/decisions/source-control-primary-action-decision-types.ts +++ b/src/shared/source-control/decisions/source-control-primary-action-decision-types.ts @@ -1,6 +1,6 @@ import type { HostedReviewCreationEligibility } from '../hosted-review/hosted-review-contracts' import type { GitUpstreamStatus } from '../../git/status/git-status-types' -import type { PRState } from '../../github/pull-request-types' +import type { PRState } from '../../github/pull-requests/pull-request-types' export type SourceControlPrimaryActionKind = | 'commit' diff --git a/src/shared/source-control/hosted-review/hosted-review-contracts.ts b/src/shared/source-control/hosted-review/hosted-review-contracts.ts index 27ec25599bf..9467bb52e35 100644 --- a/src/shared/source-control/hosted-review/hosted-review-contracts.ts +++ b/src/shared/source-control/hosted-review/hosted-review-contracts.ts @@ -1,12 +1,12 @@ import type { StackedReviewFailureReason } from './stacked-hosted-review-failure-copy' import type { CheckStatus, - GitHubRepositoryIdentity, PRConflictSummary, PRMergeableState, PRReviewDecision, ProviderCheckSummary -} from '../../github/pull-request-types' +} from '../../github/pull-requests/pull-request-types' +import type { GitHubRepositoryIdentity } from '../../github/repositories/repository-identity' export type HostedReviewProvider = | 'github' diff --git a/src/shared/source-control/hosted-review/hosted-review-github.ts b/src/shared/source-control/hosted-review/hosted-review-github.ts index a50d857fb9d..7f36c200860 100644 --- a/src/shared/source-control/hosted-review/hosted-review-github.ts +++ b/src/shared/source-control/hosted-review/hosted-review-github.ts @@ -1,6 +1,6 @@ -import { projectPRCheckOwner } from '../../github/complete-pr-check-owner' +import { projectPRCheckOwner } from '../../github/checks/complete-pr-check-owner' import type { HostedReviewInfo } from './hosted-review-contracts' -import type { PRInfo } from '../../github/pull-request-types' +import type { PRInfo } from '../../github/pull-requests/pull-request-types' export function hostedReviewInfoFromGitHubPRInfo(pr: PRInfo): HostedReviewInfo { const review: HostedReviewInfo = { diff --git a/src/shared/source-control/review-comments/pr-comment-audience.ts b/src/shared/source-control/review-comments/pr-comment-audience.ts index 63c80dc84a0..6c284d372ad 100644 --- a/src/shared/source-control/review-comments/pr-comment-audience.ts +++ b/src/shared/source-control/review-comments/pr-comment-audience.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../github/comment-types' +import type { PRComment } from '../../github/comments/comment-types' import { normalizePRCommentAuthorLogin } from './pr-bot-author-overrides' export type PRCommentAudienceFilter = 'all' | 'human' | 'bot' diff --git a/src/shared/source-control/review-comments/pr-comment-groups.ts b/src/shared/source-control/review-comments/pr-comment-groups.ts index 728bb888efa..04ac5344b49 100644 --- a/src/shared/source-control/review-comments/pr-comment-groups.ts +++ b/src/shared/source-control/review-comments/pr-comment-groups.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../github/comment-types' +import type { PRComment } from '../../github/comments/comment-types' export type PRCommentGroup = | { kind: 'standalone'; comment: PRComment } diff --git a/src/shared/source-control/review-comments/pr-comments-resolution-prompt.ts b/src/shared/source-control/review-comments/pr-comments-resolution-prompt.ts index 17cd982080b..b3a934f1974 100644 --- a/src/shared/source-control/review-comments/pr-comments-resolution-prompt.ts +++ b/src/shared/source-control/review-comments/pr-comments-resolution-prompt.ts @@ -1,4 +1,4 @@ -import type { PRComment } from '../../github/comment-types' +import type { PRComment } from '../../github/comments/comment-types' import type { PRCommentGroup } from './pr-comment-groups' export type PRCommentsResolutionReviewKind = 'PR' | 'MR' diff --git a/src/shared/terminal/links/terminal-github-pr-link-detector.ts b/src/shared/terminal/links/terminal-github-pr-link-detector.ts index 1a71b780ee5..5d7f29d4a79 100644 --- a/src/shared/terminal/links/terminal-github-pr-link-detector.ts +++ b/src/shared/terminal/links/terminal-github-pr-link-detector.ts @@ -1,7 +1,7 @@ /* eslint-disable no-control-regex -- ANSI SGR sequences are raw PTY input. */ /** Share chunk carry and dedupe across main’s local/SSH fact scan and renderer remote-runtime/kill-switch byte scans. */ -import { type RepoSlug, parseGitHubIssueOrPRLink } from '../../github/links' +import { type RepoSlug, parseGitHubIssueOrPRLink } from '../../github/repositories/links' const GITHUB_PR_PATH_MARKER = '/pull/' const TERMINAL_SGR_PATTERN = /\x1b\[[0-?]*[ -/]*m/g diff --git a/src/shared/work-items/task-provider-identity.ts b/src/shared/work-items/task-provider-identity.ts index e62b1ee86f0..9a1753ec07b 100644 --- a/src/shared/work-items/task-provider-identity.ts +++ b/src/shared/work-items/task-provider-identity.ts @@ -1,4 +1,4 @@ -import { githubRepoIdentityKey } from '../github/repository-identity-key' +import { githubRepoIdentityKey } from '../github/repositories/repository-identity-key' import type { TaskProvider } from './task-providers' import type { ProjectProviderIdentity } from '../project-types' diff --git a/src/shared/work-items/task-source-runtime-capabilities.ts b/src/shared/work-items/task-source-runtime-capabilities.ts index 9dafd42775a..9d935c9fe7e 100644 --- a/src/shared/work-items/task-source-runtime-capabilities.ts +++ b/src/shared/work-items/task-source-runtime-capabilities.ts @@ -1,4 +1,4 @@ -import { GITHUB_IMAGE_ATTACHMENT_CAPABILITY } from '../github/image-attachment' +import { GITHUB_IMAGE_ATTACHMENT_CAPABILITY } from '../github/attachments/image-attachment' import { JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY } from '../jira/jira-agent-capability' import { JIRA_CURRENT_SPRINT_RUNTIME_CAPABILITY, diff --git a/src/shared/workspace-review-checks.ts b/src/shared/workspace-review-checks.ts index d7deb1024c1..212341c1ad8 100644 --- a/src/shared/workspace-review-checks.ts +++ b/src/shared/workspace-review-checks.ts @@ -1,4 +1,4 @@ -import type { CheckStatus, ProviderCheckSummary } from './github/pull-request-types' +import type { CheckStatus, ProviderCheckSummary } from './github/pull-requests/pull-request-types' import type { HostedReviewState } from './source-control/hosted-review/hosted-review-contracts' import { resolveProviderCheckState, diff --git a/tests/e2e/github-project-board.spec.ts b/tests/e2e/github-project-board.spec.ts index 056793d3028..a9ff2eee83b 100644 --- a/tests/e2e/github-project-board.spec.ts +++ b/tests/e2e/github-project-board.spec.ts @@ -1,11 +1,11 @@ import { test, expect } from './helpers/orca-app' import { waitForSessionReady } from './helpers/store' -import { githubProjectIdentityKey } from '../../src/shared/github/project-identity' -import type { GitHubProjectTable } from '../../src/shared/github/project-types' +import { githubProjectIdentityKey } from '../../src/shared/github/projects/project-identity' +import type { GitHubProjectTable } from '../../src/shared/github/projects/project-types' import type { ClearProjectItemFieldArgs, UpdateProjectItemFieldArgs -} from '../../src/shared/github/project-request-types' +} from '../../src/shared/github/projects/project-request-types' type BoardFieldCall = UpdateProjectItemFieldArgs | ClearProjectItemFieldArgs diff --git a/tests/e2e/github-project-provider-columns.unit.test.ts b/tests/e2e/github-project-provider-columns.unit.test.ts index afba40c8249..d8239055fec 100644 --- a/tests/e2e/github-project-provider-columns.unit.test.ts +++ b/tests/e2e/github-project-provider-columns.unit.test.ts @@ -13,20 +13,29 @@ import { _resetProjectViewCachesForTests, type OptionalProjectItemField } from '../../src/main/github/project-view/project-view-cache' -import { ghExecFileAsync } from '../../src/main/github/project-view/internals' -import type { GitHubProjectRow } from '../../src/shared/github/project-types' +import { ghExecFileAsync } from '../../src/main/git/command-runner/hosted-cli/gh-exec-file' +import type { GitHubProjectRow } from '../../src/shared/github/projects/project-types' import { normalizeLinkedPullRequests } from '../../src/main/github/project-view/project-view-field-normalization' -import { projectRelationsTableFixture } from '../../src/shared/github/project-relations.test-fixture' -import { sortRows } from '../../src/shared/github/project-group-sort' +import { projectRelationsTableFixture } from '../../src/shared/github/projects/project-relations.test-fixture' +import { sortRows } from '../../src/shared/github/projects/project-group-sort' import ProjectCell from '../../src/renderer/src/components/github-project/ProjectCell' -vi.mock('../../src/main/github/project-view/internals', () => ({ +vi.mock('../../src/main/github/execution/github-operation-slots', () => ({ acquire: vi.fn(), - release: vi.fn(), - ghExecFileAsync: vi.fn(), + release: vi.fn() +})) + +vi.mock('../../src/main/git/command-runner/hosted-cli/gh-exec-file', () => ({ + ghExecFileAsync: vi.fn() +})) + +vi.mock('../../src/main/github/project-view/project-host-execution', () => ({ projectHostAuthenticationError: vi.fn().mockResolvedValue(null), - projectGhExecOptions: (host?: string) => ({ host }), + projectGhExecOptions: (host?: string) => ({ host }) +})) + +vi.mock('../../src/main/github/api/rate-limit', () => ({ repositoryRateLimitGuard: () => ({ blocked: false }), noteRepositoryRateLimitSpend: vi.fn() })) @@ -167,23 +176,6 @@ function renderColumns(row: GitHubProjectRow): void { } describe('Project columns from the items response', () => { - it('keeps old-host, draft, PR, redacted and zero-total rows blank', () => { - for (const raw of [ - { id: 'old', type: 'ISSUE', content: {} }, - { id: 'draft', type: 'DRAFT_ISSUE', content: {} }, - { id: 'pr', type: 'PULL_REQUEST', content: {} }, - { id: 'redacted', type: 'REDACTED', content: null }, - { - id: 'empty', - type: 'ISSUE', - content: { subIssuesSummary: { total: 0, completed: 0, percentCompleted: 0 } } - } - ]) { - renderColumns(normalized(raw)) - } - expect(document.body.textContent).toBe('') - }) - it.each([ { errors: [missingSummary], parentDropped: false, summary: false, prs: true }, { errors: [missingPrs], parentDropped: false, summary: true, prs: false }, @@ -248,79 +240,6 @@ describe('Project columns from the items response', () => { } ) - it('shares a pending owner capability probe between concurrent callers', async () => { - let entered = () => {} - let release = () => {} - const started = new Promise((resolve) => { - entered = resolve - }) - const pending = new Promise((resolve) => { - release = resolve - }) - vi.mocked(ghExecFileAsync).mockImplementationOnce(async () => { - entered() - await pending - return { stdout: JSON.stringify({ errors: [missingSummary] }), stderr: '' } - }) - const first = fetchAllItems(args) - await started - const second = fetchAllItems(args) - expect(ghExecFileAsync).toHaveBeenCalledTimes(1) - release() - const results = await Promise.all([first, second]) - expect(results).toEqual([ - expect.objectContaining({ ok: true, parentFieldDropped: false }), - expect.objectContaining({ ok: true, parentFieldDropped: false }) - ]) - expect(ghExecFileAsync).toHaveBeenCalledTimes(3) - expect(vi.mocked(ghExecFileAsync).mock.calls[2][0].join(' ')).not.toContain( - 'subIssuesSummary {' - ) - }) - - it('fetches unrelated projects concurrently when all optional selections are unsupported', async () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(ghExecFileAsync).mockResolvedValueOnce({ - stdout: JSON.stringify({ errors: [missingParent, missingSummary, missingPrs] }), - stderr: '' - }) - expect(await fetchAllItems(args)).toMatchObject({ ok: true, parentFieldDropped: true }) - vi.mocked(ghExecFileAsync).mockClear() - let entered = () => {} - let release = () => {} - const started = new Promise((resolve) => { - entered = resolve - }) - const pending = new Promise((resolve) => { - release = resolve - }) - vi.mocked(ghExecFileAsync).mockImplementationOnce(async (arguments_) => { - entered() - await pending - return selectedResponse(arguments_) - }) - const first = fetchAllItems(args) - await started - const second = fetchAllItems({ ...args, projectNumber: 2 }) - // Flush the normal request's async auth boundary without releasing the first response. - await new Promise((resolve) => setTimeout(resolve, 0)) - const concurrentCalls = vi.mocked(ghExecFileAsync).mock.calls.slice() - release() - expect(await Promise.all([first, second])).toEqual([ - expect.objectContaining({ ok: true, parentFieldDropped: true }), - expect.objectContaining({ ok: true, parentFieldDropped: true }) - ]) - expect(concurrentCalls).toHaveLength(2) - expect( - concurrentCalls.map(([arguments_]) => arguments_.find((arg) => arg.startsWith('num='))) - ).toEqual(['num=1', 'num=2']) - for (const [arguments_] of concurrentCalls) { - expect(arguments_.join(' ')).not.toMatch( - /parent \{|subIssuesSummary \{|\.\.\. on ProjectV2ItemFieldPullRequestValue/ - ) - } - }) - it('keeps supported fields and the cursor on pages after a capability retry', async () => { vi.mocked(ghExecFileAsync).mockResolvedValueOnce({ stdout: JSON.stringify({ errors: [missingSummary] }), @@ -345,19 +264,6 @@ describe('Project columns from the items response', () => { expect(last.join(' ')).toContain('parent {') expect(last.join(' ')).toContain('... on ProjectV2ItemFieldPullRequestValue') }) - - it.each([10, 12])('shows exact counts for a linked PR list with total %i', (totalCount) => { - const row = normalized(item) - if (!row.content.linkedPullRequests) { - throw new Error('Expected linked PR content') - } - row.content.linkedPullRequests.totalCount = totalCount - renderColumns(row) - expect( - screen.getByText(cellText(totalCount === 12 ? prText : prText.replace(' +2 more', ''))) - .textContent - ).toBeTruthy() - }) }) it('sorts linked PRs by their first number and treats count-only metadata as an unknown sort key', () => { diff --git a/tests/e2e/github-url-smart-input-transition.spec.ts b/tests/e2e/github-url-smart-input-transition.spec.ts index cda26aeb660..1ff3a56000f 100644 --- a/tests/e2e/github-url-smart-input-transition.spec.ts +++ b/tests/e2e/github-url-smart-input-transition.spec.ts @@ -1,6 +1,6 @@ import { openSidebarWorkspaceComposer } from './helpers/sidebar-project-dialog' import type { ElectronApplication, Locator, Page } from '@stablyai/playwright-test' -import type { GitHubWorkItem } from '../../src/shared/github/work-item-types' +import type { GitHubWorkItem } from '../../src/shared/github/work-items/work-item-types' import type { GitLabWorkItem } from '../../src/shared/gitlab-types' import { test, expect } from './helpers/orca-app' import { waitForActiveWorktree, waitForSessionReady } from './helpers/store' diff --git a/tests/e2e/helpers/pr-comments-sidebar-fixture.ts b/tests/e2e/helpers/pr-comments-sidebar-fixture.ts index 8374e6f6d01..efbe603a29a 100644 --- a/tests/e2e/helpers/pr-comments-sidebar-fixture.ts +++ b/tests/e2e/helpers/pr-comments-sidebar-fixture.ts @@ -1,6 +1,6 @@ import type { Page } from '@stablyai/playwright-test' -import type { PRComment } from '../../../src/shared/github/comment-types' -import type { PRInfo } from '../../../src/shared/github/pull-request-types' +import type { PRComment } from '../../../src/shared/github/comments/comment-types' +import type { PRInfo } from '../../../src/shared/github/pull-requests/pull-request-types' import { getDefaultSourceControlAiSettings } from '../../../src/shared/source-control/ai/settings/source-control-ai-settings' export type PRCommentsSidebarSeed = {