fix(ci): unblock main: split over-long files and pin scanner test file times (#34072)

This commit is contained in:
Jinwoo Hong
2026-10-26 17:14:54 -04:00
committed by GitHub
parent 82ab53ba61
commit b7fb07c379
7 changed files with 739 additions and 146 deletions
@@ -1,4 +1,4 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { mkdtemp, rm, utimes } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
@@ -109,6 +109,15 @@ async function writeSharedRootTranscripts(sessionsDir: string): Promise<void> {
runtime
}
])
// Linux mtimes tick coarsely, so back-to-back writes can tie and change which row the cap keeps.
for (const [path, lastWrite] of [
[join(piDir, '2026-05-01T09-00-00-000Z_pi.jsonl'), '2026-05-01T09:00:01.000Z'],
[join(ompDir, `${OMP_STEM}.jsonl`), '2026-05-01T10:00:02.000Z'],
[join(ompDir, '2026-04-01T10-00-00-000Z_legacy.jsonl'), '2026-04-01T10:00:01.000Z'],
[join(ompDir, OMP_STEM, 'Worker.jsonl'), '2026-05-01T10:00:06.000Z']
]) {
await utimes(path, new Date(lastWrite), new Date(lastWrite))
}
}
describe('scanAiVaultSessions with overlapping Pi and OMP roots', () => {
@@ -0,0 +1,205 @@
import {
labelOnlyCreateRequests,
renameCreatedWorktreeOnFirstWork
} from '../worktree-create-first-work-test-harness'
import { isAutoGeneratedCreatureBranchName } from '../../shared/branch-name-from-work'
import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { describeCreatedWorktreeMock, gitExecFileAsyncMock } from './worktrees-test-module-mocks'
import { handlers, setupWorktreeHandlers, store } from './worktrees-test-harness'
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
vi.mock('../text-generation/commit-message-text-generation', () => ({
resolveTextGenerationParams: () => ({
ok: true,
params: { agentId: 'claude', model: 'fixture' }
}),
generateBranchNameFromContext: async () => ({ success: true, slug: 'fix-auth' })
}))
vi.mock('../text-generation/commit-message-agent-environment', () => ({
prepareLocalCommitMessageAgentEnv: async () => ({ ok: true })
}))
vi.mock('electron', async () =>
(await import('./worktrees-test-module-mocks')).electronModuleMock()
)
vi.mock('../git/worktree', async () =>
(await import('./worktrees-test-module-mocks')).gitWorktreeModuleMock()
)
vi.mock('../git/runner', async () =>
(await import('./worktrees-test-module-mocks')).gitRunnerModuleMock()
)
vi.mock('../git/repo', async () =>
(await import('./worktrees-test-module-mocks')).gitRepoModuleMock()
)
vi.mock('../git/git-username', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
resolveLocalGitUsername: (await import('./worktrees-test-module-mocks'))
.resolveLocalGitUsernameMock
}))
vi.mock('../github/client', async () =>
(await import('./worktrees-test-module-mocks')).githubClientModuleMock()
)
vi.mock('../source-control/hosted-review', async () =>
(await import('./worktrees-test-module-mocks')).hostedReviewModuleMock()
)
vi.mock('./worktree-symlinks', async () =>
(await import('./worktrees-test-module-mocks')).worktreeSymlinksModuleMock()
)
vi.mock('../hooks', async () => (await import('./worktrees-test-module-mocks')).hooksModuleMock())
vi.mock('../setup-runner-script-text', async (importOriginal) =>
(await import('./worktrees-test-module-mocks')).setupRunnerScriptTextModuleMock(
(await importOriginal()) as Record<string, unknown>
)
)
vi.mock('../worktree-runner-script', async (importOriginal) =>
(await import('./worktrees-test-module-mocks')).worktreeRunnerScriptModuleMock(
(await importOriginal()) as Record<string, unknown>
)
)
vi.mock('../effective-hook-config', async (importOriginal) =>
(await import('./worktrees-test-module-mocks')).effectiveHookConfigModuleMock(
(await importOriginal()) as Record<string, unknown>
)
)
vi.mock('../setup-hook-env-vars', async (importOriginal) =>
(await import('./worktrees-test-module-mocks')).setupHookEnvVarsModuleMock(
(await importOriginal()) as Record<string, unknown>
)
)
vi.mock('./worktree-logic', async (importOriginal) =>
(await import('./worktrees-test-module-mocks')).worktreeLogicModuleMock(
(await importOriginal()) as Record<string, unknown>
)
)
vi.mock('../terminal-history-deletion', async () =>
(await import('./worktrees-test-module-mocks')).terminalHistoryDeletionModuleMock()
)
vi.mock('../ports/advertised-url-watcher', async () =>
(await import('./worktrees-test-module-mocks')).advertisedUrlWatcherModuleMock()
)
vi.mock('../workspace-cleanup-scan-snapshot', async () =>
(await import('./worktrees-test-module-mocks')).workspaceCleanupScanSnapshotModuleMock()
)
vi.mock('../workspace-space-analysis-snapshot', async () =>
(await import('./worktrees-test-module-mocks')).workspaceSpaceAnalysisSnapshotModuleMock()
)
vi.mock('../workspace-cleanup-removal-snapshot-prune', async () =>
(await import('./worktrees-test-module-mocks')).workspaceCleanupRemovalSnapshotPruneModuleMock()
)
vi.mock('../runtime/worktree-teardown', async () =>
(await import('./worktrees-test-module-mocks')).worktreeTeardownModuleMock()
)
vi.mock('./pty/provider/registry', async () =>
(await import('./worktrees-test-module-mocks')).ptyRegistryModuleMock()
)
vi.mock('./pty/provider/state-cleanup', async () =>
(await import('./worktrees-test-module-mocks')).ptyStateCleanupModuleMock()
)
describe('desktop label-only creation through first work', () => {
function expectCreatedWorktree(result: unknown): asserts result is CreateWorktreeResult {
expect(result).toEqual(
expect.objectContaining({
worktree: expect.objectContaining({
id: expect.any(String),
repoId: 'repo-1',
path: expect.any(String),
head: expect.any(String),
branch: expect.stringMatching(/^refs\/heads\//),
isBare: false,
isMainWorktree: false,
displayName: expect.any(String),
displayNameMode: expect.stringMatching(/^(fixed|automatic)$/),
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: expect.any(Number),
lastActivityAt: expect.any(Number)
})
})
)
}
let savedMeta: Partial<WorktreeMeta>
beforeEach(() => {
setupWorktreeHandlers()
savedMeta = {}
store.getSettings.mockReturnValue({
branchPrefix: 'none',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
workspaceDir: '/workspace',
autoRenameBranchFromWork: true
})
store.setWorktreeMeta.mockImplementation((_target, updates) => {
savedMeta = { ...savedMeta, ...updates }
return savedMeta
})
describeCreatedWorktreeMock.mockImplementation(async (_repoPath, path, branch) => ({
path,
branch: `refs/heads/${branch}`,
head: 'abc123',
isBare: false,
isMainWorktree: false
}))
})
it.each(labelOnlyCreateRequests)(
'creates and renames a creature branch for $name while projecting its label',
async (request) => {
const result = await handlers['worktrees:create'](null, {
repoId: 'repo-1',
...request
})
expectCreatedWorktree(result)
const { worktree } = result
const branch = worktree.branch.replace(/^refs\/heads\//, '')
expect(isAutoGeneratedCreatureBranchName(branch)).toBe(true)
expect(worktree.displayName).toBe(request.name)
expect(worktree.displayNameMode).toBe(request.nameWasGenerated ? 'automatic' : 'fixed')
const renamed = await renameCreatedWorktreeOnFirstWork(
worktree,
savedMeta,
gitExecFileAsyncMock
)
expect(renamed).toMatchObject({
branch: 'refs/heads/fix-auth',
displayName: request.nameWasGenerated ? 'Fix auth' : request.name,
displayNameMode: 'fixed'
})
}
)
it.each([
{ branchNameOverride: 'feature/exact', enabled: true, branch: 'feature/exact' },
{ branchNameOverride: undefined, enabled: false, branch: 'typed-label' }
])(
'preserves branch choice $branch (automatic=$enabled)',
async ({ branchNameOverride, enabled, branch }) => {
store.getSettings.mockReturnValue({
branchPrefix: 'none',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
workspaceDir: '/workspace',
autoRenameBranchFromWork: enabled
})
const result = await handlers['worktrees:create'](null, {
repoId: 'repo-1',
name: 'typed-label',
branchNameOverride
})
expectCreatedWorktree(result)
const { worktree } = result
expect(worktree).toMatchObject({
branch: `refs/heads/${branch}`,
displayName: 'typed-label',
displayNameMode: 'fixed'
})
}
)
})
@@ -0,0 +1,265 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { resolve } from 'node:path'
const {
runtimeCreateMocks: mocks,
worktreePath,
pickedBranch,
createWorktree,
resetRuntimeCreateMocks
} = await vi.hoisted(async () => await import('./runtime-local-worktree-create-test-harness'))
vi.mock('../text-generation/commit-message-text-generation', () => ({
resolveTextGenerationParams: () => ({
ok: true,
params: { agentId: 'claude', model: 'fixture' }
}),
generateBranchNameFromContext: async () => ({ success: true, slug: 'fix-auth' })
}))
vi.mock('../text-generation/commit-message-agent-environment', () => ({
prepareLocalCommitMessageAgentEnv: async () => ({ ok: true })
}))
vi.mock('../git/runner', () => ({ gitExecFileAsync: mocks.git }))
vi.mock('../project-runtime-git-options', () => ({
getLocalProjectGitExecOptions: () => ({ cwd: '/repo', ...mocks.routing() }),
getLocalProjectWorktreeGitOptions: mocks.routing,
getWorktreeMirrorDistro: () => undefined
}))
vi.mock('../git/repo', () => ({
getBaseRefDefault: mocks.defaultBase,
resolveDefaultBaseRefWithLocalGit: mocks.defaultBase,
getBranchConflictKind: mocks.branchConflict
}))
vi.mock('../git/git-username', () => ({ resolveLocalGitUsername: async () => '' }))
vi.mock('../hooks', () => ({ getEffectiveHooks: mocks.effectiveHooks }))
vi.mock('../git/worktree-base-ref-probe', () => ({ hasLocalWorktreeBaseRef: mocks.hasBase }))
vi.mock('./runtime-worktree-create-git', () => ({
resolveCreateBranchName: mocks.branchName,
canCheckoutExistingLocalBranch: mocks.canCheckout,
getLocalGitHubPrForBranch: mocks.githubPr,
getSelectedHostedReviewForBranch: vi.fn()
}))
vi.mock('./runtime-worktree-filesystem', () => ({ runtimePathExists: mocks.pathExists }))
vi.mock('../worktree-create-preparation', () => ({ consumePreparedWorktreeCreate: mocks.consume }))
vi.mock('../git/worktree', () => ({ addWorktree: mocks.add, addSparseWorktree: mocks.addSparse }))
vi.mock('../ipc/worktree-remote', () => ({ configureCreatedWorktreePushTarget: mocks.pushTarget }))
vi.mock('../ipc/created-worktree-reconciliation', () => ({ resolveCreatedWorktree: mocks.listing }))
vi.mock('../worktree-name-retirement', () => ({
failedWorktreeCreationNeedsRetirement: vi.fn(),
retireGeneratedWorktreeName: vi.fn()
}))
vi.mock('../git/worktree-shared-directories', () => ({
resolveWorktreeSharedDirectories: mocks.resolveShared
}))
vi.mock('../git/worktree-include-file', () => ({
resolveWorktreeIncludePaths: mocks.resolveInclude
}))
vi.mock('../ipc/worktree-symlinks', () => ({
createWorktreeCopiedPaths: mocks.copyPaths,
createWorktreeLinkedPaths: vi.fn(),
createWorktreeSharedPaths: vi.fn()
}))
beforeEach(resetRuntimeCreateMocks)
describe('runtime create PR conflict probe', () => {
const remoteTrackingBase = {
remote: 'origin',
branch: 'main',
base: 'origin/main',
ref: 'refs/remotes/origin/main'
}
beforeEach(() => {
mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name)
})
it.each([1, 10])(
'keeps the selected name and source through %i occupied paths, without PR eligibility',
async (occupied) => {
const options = { wslDistro: 'Ubuntu' }
mocks.routing.mockReturnValue(options)
mocks.remoteBase.mockResolvedValue(remoteTrackingBase)
mocks.githubPr.mockResolvedValue({ number: 42 })
let pathChecks = 0
mocks.pathExists.mockImplementation(async () => pathChecks++ < occupied)
await createWorktree({ ...pickedBranch, sparseCheckout: { directories: ['src'] } })
expect(mocks.remoteBase.mock.calls).toEqual([['/repo', 'origin/main', options]])
expect(mocks.branchName).toHaveBeenCalledOnce()
expect(mocks.branchConflict.mock.calls).toEqual([['/repo', 'main', 'origin/main', options]])
expect(mocks.pathExists).toHaveBeenCalledTimes(occupied + 1)
expect(mocks.githubPr).not.toHaveBeenCalled()
expect(mocks.consume).not.toHaveBeenCalled()
expect(mocks.addSparse).toHaveBeenCalledExactlyOnceWith(
'/repo',
resolve('/worktrees', `app-${occupied + 1}`),
'main',
['src'],
remoteTrackingBase.base,
false,
expect.objectContaining({ remoteTrackingBase, ...options })
)
}
)
it.each([true, false])(
'keeps local eligibility with picker intent (reusable: %s)',
async (reusable) => {
mocks.branchConflict.mockImplementation(async (_path, branch: string) =>
branch === 'main' ? 'local' : null
)
mocks.canCheckout.mockImplementation(
async (_path, branch: string) => reusable && branch === 'main'
)
mocks.pathExists.mockImplementation(async (path) => path === worktreePath)
mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' })
await createWorktree(pickedBranch)
expect(mocks.add).toHaveBeenCalledWith(
'/repo',
resolve('/worktrees', 'app-2'),
reusable ? 'main' : 'main-2',
'origin/main',
false,
false,
expect.objectContaining(reusable ? { checkoutExistingBranch: true } : {})
)
expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(
reusable ? [] : ['main-2']
)
expect(mocks.add.mock.calls[0][6].checkoutExistingBranch ?? false).toBe(reusable)
}
)
it('keeps unflagged legacy, mobile New branch and Advanced collisions on baseline allocation', async () => {
mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' })
mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name)
mocks.branchConflict.mockImplementation(async (_path, branch: string) =>
branch === 'feature/test' ? 'remote' : null
)
await createWorktree({ branchNameOverride: 'feature/test', baseBranch: 'origin/main' })
expect(mocks.add.mock.calls[0][2]).toBe('feature/test-2')
})
it('runs the first name PR lookup while the base fetch is still in flight', async () => {
mocks.remoteBase.mockResolvedValue(remoteTrackingBase)
let finishFetch!: () => void
mocks.refresh.mockReturnValue(
new Promise((resolve) => {
finishFetch = () => resolve({ ok: true })
})
)
let fetchPendingAtLookup = false
mocks.githubPr.mockImplementation(async () => {
fetchPendingAtLookup = mocks.refresh.mock.calls.length === 1
finishFetch?.()
return null
})
await createWorktree()
expect(fetchPendingAtLookup).toBe(true)
expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app', {})
expect(mocks.consume).toHaveBeenCalledWith(
expect.objectContaining({
branch: 'app',
options: expect.objectContaining({ remoteTrackingBase })
})
)
})
it('suffixes past a PR on the first name', async () => {
mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null)
await createWorktree()
expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app', 'app-2'])
expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-2' }))
})
it.each([undefined, 'Ubuntu'])(
'probes the next name for a PR once the first name collides, routed for %s',
async (wslDistro) => {
const routing = wslDistro ? { wslDistro } : {}
mocks.routing.mockReturnValue(routing)
mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null)
await createWorktree()
expect(mocks.githubPr).toHaveBeenCalledOnce()
expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app-2', routing)
}
)
it('skips a later name that already has a PR', async () => {
mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null)
mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null)
await createWorktree()
expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app-2', 'app-3'])
expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-3' }))
})
it('reports the naming failure when the started base fetch also fails', async () => {
mocks.remoteBase.mockResolvedValue(remoteTrackingBase)
mocks.refresh.mockRejectedValue(new Error('offline'))
mocks.branchConflict.mockResolvedValue('local')
const unhandled = vi.fn()
process.on('unhandledRejection', unhandled)
try {
await expect(createWorktree()).rejects.toThrow('Branch "app-')
await new Promise((resolve) => setImmediate(resolve))
expect(mocks.refresh).toHaveBeenCalledOnce()
expect(unhandled).not.toHaveBeenCalled()
expect(mocks.consume).not.toHaveBeenCalled()
} finally {
process.off('unhandledRejection', unhandled)
}
})
it('runs the first name PR lookup while the origin fetch is still in flight', async () => {
mocks.hasBase.mockResolvedValue(false)
let finishFetch!: () => void
mocks.fetch.mockReturnValue(
new Promise<void>((resolve) => {
finishFetch = resolve
})
)
let fetchPendingAtLookup = false
mocks.githubPr.mockImplementation(async () => {
fetchPendingAtLookup = mocks.fetch.mock.calls.length === 1
finishFetch?.()
return null
})
await createWorktree()
expect(fetchPendingAtLookup).toBe(true)
expect(mocks.fetch).toHaveBeenCalledWith('/repo', 'origin', {})
expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app' }))
})
it('reports the naming failure when the started origin fetch also fails', async () => {
mocks.hasBase.mockResolvedValue(false)
mocks.fetch.mockRejectedValue(new Error('offline'))
mocks.branchConflict.mockResolvedValue('local')
const unhandled = vi.fn()
process.on('unhandledRejection', unhandled)
try {
await expect(createWorktree()).rejects.toThrow('Branch "app-')
await new Promise((resolve) => setImmediate(resolve))
expect(mocks.fetch).toHaveBeenCalledOnce()
expect(unhandled).not.toHaveBeenCalled()
expect(mocks.consume).not.toHaveBeenCalled()
} finally {
process.off('unhandledRejection', unhandled)
}
})
})
@@ -0,0 +1,189 @@
import { expect, vi, type Mock } from 'vitest'
import { resolve } from 'node:path'
import type { Store } from '../persistence'
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
import type { HostWorktreeTarget } from '../../shared/worktree/host-qualified-identity'
import type { Repo } from '../../shared/repo-types'
import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types'
import { resolveGitAdmissionTier } from '../git/command-runner/git-operation-executor'
import type { PreparationRearmHolder } from '../worktree-create-preparation'
import {
createWorktreeCreateTimingRecorder,
type WorktreeCreateTimingRecorder
} from '../worktree-create-timing'
import type { SparsePreset } from '../../shared/worktree/create-types'
type RuntimeCreateMocks = {
git: Mock
rearm: Mock
routing: Mock<() => { wslDistro?: string }>
defaultBase: Mock
hasBase: Mock
branchName: Mock
canCheckout: Mock
pathExists: Mock
branchConflict: Mock
githubPr: Mock
consume: Mock
add: Mock
addSparse: Mock
pushTarget: Mock
listing: Mock
remoteBase: Mock
hasRemoteRef: Mock
refresh: Mock
fetch: Mock
resolveShared: Mock<() => Promise<string[]>>
resolveInclude: Mock<() => Promise<string[]>>
copyPaths: Mock<() => Promise<string[]>>
effectiveHooks: Mock
created: {
path: string
head: string
branch: string
isBare: boolean
isMainWorktree: boolean
}
}
export const runtimeCreateMocks: RuntimeCreateMocks = {
git: vi.fn(),
rearm: vi.fn(),
routing: vi.fn<() => { wslDistro?: string }>(),
defaultBase: vi.fn(),
hasBase: vi.fn(),
branchName: vi.fn(),
canCheckout: vi.fn(),
pathExists: vi.fn(),
branchConflict: vi.fn(),
githubPr: vi.fn(),
consume: vi.fn(),
add: vi.fn(),
addSparse: vi.fn(),
pushTarget: vi.fn(),
listing: vi.fn(),
remoteBase: vi.fn(),
hasRemoteRef: vi.fn(),
refresh: vi.fn(),
fetch: vi.fn(),
resolveShared: vi.fn<() => Promise<string[]>>(),
resolveInclude: vi.fn<() => Promise<string[]>>(),
copyPaths: vi.fn<() => Promise<string[]>>(),
effectiveHooks: vi.fn(),
created: {
path: '',
head: 'abc123',
branch: 'app',
isBare: false,
isMainWorktree: false
}
}
export const worktreePath = resolve('/worktrees', 'app')
export const pickedBranch = {
baseBranch: 'origin/main',
branchNameOverride: 'main',
branchIntent: 'open-existing'
} as const
export const writtenMeta: Partial<WorktreeMeta>[] = []
const WEB_PRESET: SparsePreset = {
id: 'preset-web',
repoId: 'repo-1',
name: 'web',
directories: ['apps/web', 'packages/ui'],
createdAt: 0,
updatedAt: 0
}
export const readSparsePresets = vi.fn<(repoId: string) => SparsePreset[]>()
export async function createWorktree(
request: Partial<RuntimeManagedWorktreeCreateArgs> = {},
rearm: PreparationRearmHolder = { fire: () => {} },
timing: WorktreeCreateTimingRecorder = createWorktreeCreateTimingRecorder(),
repoOverrides: Partial<Repo> = {},
autoRenameBranchFromWork = false
) {
const store = {
getSettings: () => ({
workspaceDir: '/worktrees',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
branchPrefix: '',
autoRenameBranchFromWork
}),
setWorktreeMeta: (_target: HostWorktreeTarget, updates: Partial<WorktreeMeta>) => {
writtenMeta.push(updates)
return updates
},
getSparsePresets: (repoId: string): SparsePreset[] => readSparsePresets(repoId)
}
// Loaded on call so module mock factories can import this harness without a cycle.
const { createRuntimeLocalManagedWorktree } = await import('./runtime-local-worktree-create')
return createRuntimeLocalManagedWorktree({
request: { repoSelector: 'repo-1', name: 'app', baseBranch: 'main', ...request },
repo: {
id: 'repo-1',
path: '/repo',
displayName: 'Repo',
badgeColor: '#000000',
addedAt: 0,
...repoOverrides
},
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: All store methods reached by this isolated create path are supplied above.
store: store as Store,
createdWithAgent: undefined,
resolveRemoteTrackingBase: runtimeCreateMocks.remoteBase,
hasRemoteTrackingRef: runtimeCreateMocks.hasRemoteRef,
// vi.fn marks returned promises handled; a fresh promise exposes unawaited fetch rejection.
refreshRemoteTrackingBase: async (...refreshArgs) =>
await runtimeCreateMocks.refresh(...refreshArgs),
fetchRemote: async (...fetchArgs) => await runtimeCreateMocks.fetch(...fetchArgs),
onWorktreeMetadataPersisted: () => undefined,
rearm,
timing
})
}
export function resetRuntimeCreateMocks(): void {
vi.resetAllMocks()
writtenMeta.length = 0
runtimeCreateMocks.created.branch = 'app'
runtimeCreateMocks.git.mockResolvedValue({ stdout: '', stderr: '' })
runtimeCreateMocks.created.path = worktreePath
runtimeCreateMocks.routing.mockReturnValue({})
runtimeCreateMocks.defaultBase.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return 'main'
})
runtimeCreateMocks.hasBase.mockResolvedValue(true)
runtimeCreateMocks.branchName.mockResolvedValue('app')
runtimeCreateMocks.canCheckout.mockResolvedValue(false)
runtimeCreateMocks.pathExists.mockResolvedValue(false)
runtimeCreateMocks.branchConflict.mockResolvedValue(null)
runtimeCreateMocks.githubPr.mockResolvedValue(null)
runtimeCreateMocks.consume.mockResolvedValue({
status: 'hit',
result: {},
rearm: runtimeCreateMocks.rearm
})
runtimeCreateMocks.add.mockResolvedValue({})
runtimeCreateMocks.addSparse.mockResolvedValue({})
runtimeCreateMocks.listing.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return { created: runtimeCreateMocks.created }
})
runtimeCreateMocks.remoteBase.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return null
})
runtimeCreateMocks.hasRemoteRef.mockResolvedValue(true)
runtimeCreateMocks.refresh.mockResolvedValue({ ok: true })
runtimeCreateMocks.fetch.mockResolvedValue(undefined)
runtimeCreateMocks.resolveShared.mockResolvedValue([])
runtimeCreateMocks.resolveInclude.mockResolvedValue(['.env'])
runtimeCreateMocks.copyPaths.mockResolvedValue([])
runtimeCreateMocks.effectiveHooks.mockReturnValue(null)
readSparsePresets.mockReturnValue([WEB_PRESET])
}
@@ -6,52 +6,24 @@ import {
import { isAutoGeneratedCreatureBranchName } from '../../shared/branch-name-from-work'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { resolve } from 'node:path'
import type { Store } from '../persistence'
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
import type { HostWorktreeTarget } from '../../shared/worktree/host-qualified-identity'
import type { Repo } from '../../shared/repo-types'
import { getDefaultRepoHookSettings } from '../../shared/constants'
import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types'
import type { AddWorktreeOptions } from '../git/worktree'
import {
acquireGitAdmission,
GitAdmissionScheduler,
_resetGitAdmissionForTests
} from '../git/command-runner/git-subprocess-admission'
import { resolveGitAdmissionTier } from '../git/command-runner/git-operation-executor'
const mocks = vi.hoisted(() => ({
git: vi.fn(),
rearm: vi.fn(),
routing: vi.fn<() => { wslDistro?: string }>(),
defaultBase: vi.fn(),
hasBase: vi.fn(),
branchName: vi.fn(),
canCheckout: vi.fn(),
pathExists: vi.fn(),
branchConflict: vi.fn(),
githubPr: vi.fn(),
consume: vi.fn(),
add: vi.fn(),
addSparse: vi.fn(),
pushTarget: vi.fn(),
listing: vi.fn(),
remoteBase: vi.fn(),
hasRemoteRef: vi.fn(),
refresh: vi.fn(),
fetch: vi.fn(),
resolveShared: vi.fn<() => Promise<string[]>>(),
resolveInclude: vi.fn<() => Promise<string[]>>(),
copyPaths: vi.fn<() => Promise<string[]>>(),
effectiveHooks: vi.fn(),
created: {
path: '',
head: 'abc123',
branch: 'app',
isBare: false,
isMainWorktree: false
}
}))
const {
runtimeCreateMocks: mocks,
worktreePath,
pickedBranch,
writtenMeta,
readSparsePresets,
createWorktree,
resetRuntimeCreateMocks
} = await vi.hoisted(async () => await import('./runtime-local-worktree-create-test-harness'))
vi.mock('../text-generation/commit-message-text-generation', () => ({
resolveTextGenerationParams: () => ({
@@ -105,117 +77,12 @@ vi.mock('../ipc/worktree-symlinks', () => ({
createWorktreeSharedPaths: vi.fn()
}))
import { createRuntimeLocalManagedWorktree } from './runtime-local-worktree-create'
import { WorktreeCreateSetupChoiceRefusedError } from './worktree-create-setup-choice-refusal'
import type { PreparationRearmHolder } from '../worktree-create-preparation'
import {
createWorktreeCreateTimingRecorder,
type WorktreeCreateTimingRecorder
} from '../worktree-create-timing'
import type { SparsePreset } from '../../shared/worktree/create-types'
import { createWorktreeCreateTimingRecorder } from '../worktree-create-timing'
import { withoutInFlightWorktreeCreates } from '../worktree-create-in-flight'
const worktreePath = resolve('/worktrees', 'app')
const pickedBranch = {
baseBranch: 'origin/main',
branchNameOverride: 'main',
branchIntent: 'open-existing'
} as const
const writtenMeta: Partial<WorktreeMeta>[] = []
const WEB_PRESET: SparsePreset = {
id: 'preset-web',
repoId: 'repo-1',
name: 'web',
directories: ['apps/web', 'packages/ui'],
createdAt: 0,
updatedAt: 0
}
const readSparsePresets = vi.fn<(repoId: string) => SparsePreset[]>()
function createWorktree(
request: Partial<RuntimeManagedWorktreeCreateArgs> = {},
rearm: PreparationRearmHolder = { fire: () => {} },
timing: WorktreeCreateTimingRecorder = createWorktreeCreateTimingRecorder(),
repoOverrides: Partial<Repo> = {},
autoRenameBranchFromWork = false
) {
const store = {
getSettings: () => ({
workspaceDir: '/worktrees',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
branchPrefix: '',
autoRenameBranchFromWork
}),
setWorktreeMeta: (_target: HostWorktreeTarget, updates: Partial<WorktreeMeta>) => {
writtenMeta.push(updates)
return updates
},
getSparsePresets: (repoId: string): SparsePreset[] => readSparsePresets(repoId)
}
return createRuntimeLocalManagedWorktree({
request: { repoSelector: 'repo-1', name: 'app', baseBranch: 'main', ...request },
repo: {
id: 'repo-1',
path: '/repo',
displayName: 'Repo',
badgeColor: '#000000',
addedAt: 0,
...repoOverrides
},
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: All store methods reached by this isolated create path are supplied above.
store: store as Store,
createdWithAgent: undefined,
resolveRemoteTrackingBase: mocks.remoteBase,
hasRemoteTrackingRef: mocks.hasRemoteRef,
// vi.fn marks returned promises handled; a fresh promise exposes unawaited fetch rejection.
refreshRemoteTrackingBase: async (...refreshArgs) => await mocks.refresh(...refreshArgs),
fetchRemote: async (...fetchArgs) => await mocks.fetch(...fetchArgs),
onWorktreeMetadataPersisted: () => undefined,
rearm,
timing
})
}
beforeEach(() => {
vi.resetAllMocks()
writtenMeta.length = 0
mocks.created.branch = 'app'
mocks.git.mockResolvedValue({ stdout: '', stderr: '' })
mocks.created.path = worktreePath
mocks.routing.mockReturnValue({})
mocks.defaultBase.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return 'main'
})
mocks.hasBase.mockResolvedValue(true)
mocks.branchName.mockResolvedValue('app')
mocks.canCheckout.mockResolvedValue(false)
mocks.pathExists.mockResolvedValue(false)
mocks.branchConflict.mockResolvedValue(null)
mocks.githubPr.mockResolvedValue(null)
mocks.consume.mockResolvedValue({ status: 'hit', result: {}, rearm: mocks.rearm })
mocks.add.mockResolvedValue({})
mocks.addSparse.mockResolvedValue({})
mocks.listing.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return { created: mocks.created }
})
mocks.remoteBase.mockImplementation(async () => {
expect(resolveGitAdmissionTier()).toBe('interactive')
return null
})
mocks.hasRemoteRef.mockResolvedValue(true)
mocks.refresh.mockResolvedValue({ ok: true })
mocks.fetch.mockResolvedValue(undefined)
mocks.resolveShared.mockResolvedValue([])
mocks.resolveInclude.mockResolvedValue(['.env'])
mocks.copyPaths.mockResolvedValue([])
mocks.effectiveHooks.mockReturnValue(null)
readSparsePresets.mockReturnValue([WEB_PRESET])
})
beforeEach(resetRuntimeCreateMocks)
describe('runtime create listing hold', () => {
const rows = [{ path: worktreePath }]
@@ -0,0 +1,57 @@
import { PanelBottomClose, PanelRightClose, X } from 'lucide-react'
import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut
} from '@/components/ui/dropdown-menu'
import { translate } from '@/i18n/i18n'
import { useAppStore } from '@/store'
import { NativeChatCopyOrcaSessionIdMenuItem } from './NativeChatCopyOrcaSessionIdMenuItem'
import { useStructuredChatPaneActions } from './use-structured-chat-pane-actions'
export function NativeChatStructuredPaneMenuItems({
pane,
leadingSeparator,
splitShortcutLabels,
resolveOrcaSessionId
}: {
pane: { tabId: string; worktreeId: string; groupId: string }
leadingSeparator: boolean
splitShortcutLabels?: { right: string; down: string }
resolveOrcaSessionId?: () => Promise<string | null>
}): React.JSX.Element {
const actions = useStructuredChatPaneActions(pane.tabId, pane.worktreeId, pane.groupId)
const hasSplitPanes = useAppStore(
(state) => (state.groupsByWorktree[pane.worktreeId]?.length ?? 0) > 1
)
return (
<>
{leadingSeparator ? <DropdownMenuSeparator /> : null}
<DropdownMenuItem onSelect={actions.onSplitRight}>
<PanelRightClose />
{translate('auto.components.cmd.j.quick.actions.splitChatRight', 'Split Chat Right')}
<DropdownMenuShortcut>{splitShortcutLabels?.right}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem onSelect={actions.onSplitDown}>
<PanelBottomClose />
{translate('auto.components.cmd.j.quick.actions.splitChatDown', 'Split Chat Down')}
<DropdownMenuShortcut>{splitShortcutLabels?.down}</DropdownMenuShortcut>
</DropdownMenuItem>
{resolveOrcaSessionId ? (
<>
<DropdownMenuSeparator />
<NativeChatCopyOrcaSessionIdMenuItem resolveOrcaSessionId={resolveOrcaSessionId} />
</>
) : null}
{hasSplitPanes && actions.canClosePane ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive" onSelect={actions.onClosePane}>
<X />
{actions.closeLabel}
</DropdownMenuItem>
</>
) : null}
</>
)
}
@@ -32,6 +32,7 @@ import { NativeChatCopyOrcaSessionIdMenuItem } from './NativeChatCopyOrcaSession
import { NativeChatSelectionQuote } from './NativeChatSelectionQuote'
import { StructuredChatPaneMenuItems } from './StructuredChatPaneMenuItems'
import { NativeChatPaneMenuItems } from './NativeChatPaneMenuItems'
import { NativeChatStructuredPaneMenuItems } from './NativeChatStructuredPaneMenuItems'
import type { NativeChatComposerHandle } from './native-chat-composer-types'
import { AgentSessionContinuationMenuItem } from '@/components/terminal-pane/AgentSessionContinuationMenuItem'
import { AgentSessionContextCopyMenuItem } from '@/components/terminal-pane/AgentSessionContextCopyMenuItem'
@@ -313,7 +314,7 @@ export function useNativeChatContextMenu({
</>
) : null}
{chatPane ? (
<StructuredChatPaneMenuItems
<NativeChatStructuredPaneMenuItems
pane={chatPane}
leadingSeparator={hasEditItems || hasSessionItems}
splitShortcutLabels={splitShortcutLabels}