import { describe, expect, it, vi } from 'vitest' const { callMock, runtimeClientConstructorMock, serveOrcaAppMock, getDefaultUserDataPathMock, addEnvironmentFromPairingCodeMock, listEnvironmentsMock, spawnMock } = vi.hoisted(() => ({ callMock: vi.fn(), runtimeClientConstructorMock: vi.fn(), serveOrcaAppMock: vi.fn(), getDefaultUserDataPathMock: vi.fn(() => '/tmp/orca-user-data'), addEnvironmentFromPairingCodeMock: vi.fn(), listEnvironmentsMock: vi.fn(), spawnMock: vi.fn() })) vi.mock('./runtime-client', async () => { const { createRuntimeClientModuleMock } = await import('./index-test-harness.js') return createRuntimeClientModuleMock({ callMock, runtimeClientConstructorMock, serveOrcaAppMock, getDefaultUserDataPathMock }) }) vi.mock('./runtime/environments', () => ({ addEnvironmentFromPairingCode: addEnvironmentFromPairingCodeMock, listEnvironments: listEnvironmentsMock, removeEnvironment: vi.fn(), resolveEnvironment: vi.fn() })) vi.mock('child_process', async () => { const { createChildProcessModuleMock } = await import('./index-test-harness.js') return createChildProcessModuleMock(spawnMock) }) import { main } from './index' import { buildWorktree, okFixture, queueFixtures, worktreeListFixture } from './test-fixtures' import { useWorktreeAwarenessEnvironment } from './index-test-harness' describe('orca cli worktree awareness', () => { useWorktreeAwarenessEnvironment({ callMock, serveOrcaAppMock, getDefaultUserDataPathMock, addEnvironmentFromPairingCodeMock, listEnvironmentsMock, spawnMock }) it('opts into setup and activation when worktree.create runs hooks', async () => { queueFixtures( callMock, worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]), okFixture('req_create', { worktree: buildWorktree('/tmp/repo/feature', 'feature', 'abc', 'repo-1') }) ) vi.spyOn(console, 'log').mockImplementation(() => {}) await main( ['worktree', 'create', '--repo', 'id:repo-1', '--name', 'feature', '--run-hooks', '--json'], '/tmp/repo' ) expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', { repo: 'id:repo-1', name: 'feature', displayName: 'feature', displayNameKind: 'user', baseBranch: undefined, linkedIssue: undefined, comment: undefined, runHooks: true, activate: true, // Why: the CLI pairs as a runtime device but has no viewer, so --activate must // stay an explicit all-surface reveal rather than caller-scoped navigation. navigation: 'all', parentWorktree: undefined, cwdParentWorktree: 'id:repo-1::/tmp/repo', noParent: false, callerTerminalHandle: undefined, cliProvenanceRequest: {} }) }) it('starts an agent worktree in the background unless activation is explicit', async () => { queueFixtures( callMock, worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]), okFixture('req_create', { worktree: buildWorktree('/tmp/repo/agent-task', 'agent-task', 'abc', 'repo-1'), lineage: null, warnings: [] }) ) vi.spyOn(console, 'log').mockImplementation(() => {}) await main( [ 'worktree', 'create', '--repo', 'id:repo-1', '--name', 'agent-task', '--agent', 'codex', '--prompt', 'hi', '--setup', 'run', '--json' ], '/tmp/repo/src' ) expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', { repo: 'id:repo-1', name: 'agent-task', displayName: 'agent-task', displayNameKind: 'user', baseBranch: undefined, linkedIssue: undefined, comment: undefined, runHooks: false, activate: false, setupDecision: 'run', parentWorktree: undefined, cwdParentWorktree: 'id:repo-1::/tmp/repo', noParent: false, callerTerminalHandle: undefined, cliProvenanceRequest: {}, startupAgent: 'codex', startupPrompt: 'hi' }) }) it('infers the repo and honors explicit activation on worktree.create', async () => { queueFixtures( callMock, worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]), okFixture('req_create', { worktree: buildWorktree('/tmp/repo/agent-task', 'agent-task', 'abc', 'repo-1'), lineage: null, warnings: [] }) ) vi.spyOn(console, 'log').mockImplementation(() => {}) await main( [ 'worktree', 'create', '--name', 'agent-task', '--agent', 'codex', '--prompt', 'hi', '--activate', '--json' ], '/tmp/repo/src' ) expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', { repo: 'id:repo-1', name: 'agent-task', displayName: 'agent-task', displayNameKind: 'user', baseBranch: undefined, linkedIssue: undefined, comment: undefined, runHooks: false, activate: true, navigation: 'all', parentWorktree: undefined, cwdParentWorktree: 'id:repo-1::/tmp/repo', noParent: false, callerTerminalHandle: undefined, cliProvenanceRequest: {}, startupAgent: 'codex', startupPrompt: 'hi' }) }) it('rejects prompt without agent on worktree.create', async () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const priorExitCode = process.exitCode await main( ['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--prompt', 'hi', '--json'], '/tmp/repo' ) expect(callMock).not.toHaveBeenCalled() expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain( '--prompt requires --agent' ) expect(process.exitCode).toBe(1) process.exitCode = priorExitCode }) it('rejects unknown agents on worktree.create', async () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) const priorExitCode = process.exitCode await main( ['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--agent', 'wat', '--json'], '/tmp/repo' ) expect(callMock).not.toHaveBeenCalled() expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toMatchObject({ ok: false, error: { code: 'invalid_argument', message: 'Unknown TUI agent "wat"' } }) expect(process.exitCode).toBe(1) process.exitCode = priorExitCode }) it('rejects agent, prompt, and setup flags without values on worktree.create', async () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const priorExitCode = process.exitCode await main( ['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--agent'], '/tmp/repo' ) expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false) expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain( 'Missing value for --agent' ) callMock.mockClear() logSpy.mockClear() errSpy.mockClear() process.exitCode = priorExitCode await main( [ 'worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--agent', 'codex', '--prompt' ], '/tmp/repo' ) expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false) expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain( 'Missing value for --prompt' ) callMock.mockClear() logSpy.mockClear() errSpy.mockClear() process.exitCode = priorExitCode await main( ['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--setup'], '/tmp/repo' ) expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false) expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain( 'Missing value for --setup' ) process.exitCode = priorExitCode }) it('rejects contradictory setup flags on worktree.create', async () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const priorExitCode = process.exitCode await main( [ 'worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--run-hooks', '--setup', 'skip', '--json' ], '/tmp/repo' ) expect(callMock).not.toHaveBeenCalled() expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain( 'Choose either --run-hooks or --setup run' ) expect(process.exitCode).toBe(1) process.exitCode = priorExitCode }) })