diff --git a/src/main/hooks-issue-command.test.ts b/src/main/hooks-issue-command.test.ts index 675e32c7fa5..42686241930 100644 --- a/src/main/hooks-issue-command.test.ts +++ b/src/main/hooks-issue-command.test.ts @@ -3,7 +3,6 @@ import type * as GitRunner from './git/runner' import { join } from 'node:path' import { describe, expect, it, vi } from 'vitest' import { - makeHookTestRepo, TEST_GITIGNORE_PATH, TEST_ISSUE_COMMAND_PATH, TEST_REPO_ORCA_YAML_PATH, @@ -143,92 +142,3 @@ describe('writeIssueCommand', () => { }) }) }) - -describe('createIssueCommandRunnerScript', () => { - const makeRepo = () => makeHookTestRepo({ mode: 'auto', scripts: { setup: '', archive: '' } }) - - it('writes a POSIX issue-command runner when a shebang declares bash and setup resolves to Git Bash', async () => { - gitExecFileSyncMock.mockReset() - gitExecFileSyncMock.mockReturnValue('C:\\repo\\.git\\orca\\issue-command-runner.sh\n') - const fs = await import('node:fs') - const writeFileSyncMock = vi.mocked(fs.writeFileSync) - writeFileSyncMock.mockClear() - const originalPlatform = process.platform - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript( - makeRepo(), - 'C:\\repo-worktree', - '#!/usr/bin/env bash\ngh issue view 42', - undefined, - { family: 'posix' } - ) - - expect(gitExecFileSyncMock).toHaveBeenCalledWith( - ['rev-parse', '--git-path', 'orca/issue-command-runner.sh'], - { cwd: 'C:\\repo-worktree' } - ) - expect(writeFileSyncMock).toHaveBeenCalledWith( - 'C:\\repo\\.git\\orca\\issue-command-runner.sh', - '#!/usr/bin/env bash\nset -e\ngh issue view 42\n', - 'utf-8' - ) - expect(result.shell).toEqual({ family: 'posix' }) - } finally { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - } - }) - - it('keeps a plain issue command on the cmd runner under a Git Bash terminal', async () => { - gitExecFileSyncMock.mockReset() - gitExecFileSyncMock.mockReturnValue('C:\\repo\\.git\\orca\\issue-command-runner.cmd\n') - const originalPlatform = process.platform - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript( - makeRepo(), - 'C:\\repo-worktree', - 'gh issue view 42', - undefined, - { family: 'posix' } - ) - - expect(gitExecFileSyncMock).toHaveBeenCalledWith( - ['rev-parse', '--git-path', 'orca/issue-command-runner.cmd'], - { cwd: 'C:\\repo-worktree' } - ) - // The pane remains Git Bash even when it launches a batch runner. - expect(result.shell).toEqual({ family: 'posix' }) - } finally { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - } - }) - - it('keeps the cmd issue-command runner when no setup shell is resolved', async () => { - gitExecFileSyncMock.mockReset() - gitExecFileSyncMock.mockReturnValue('C:\\repo\\.git\\orca\\issue-command-runner.cmd\n') - const originalPlatform = process.platform - Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript( - makeRepo(), - 'C:\\repo-worktree', - 'gh issue view 42' - ) - - expect(gitExecFileSyncMock).toHaveBeenCalledWith( - ['rev-parse', '--git-path', 'orca/issue-command-runner.cmd'], - { cwd: 'C:\\repo-worktree' } - ) - expect(result.shell).toEqual({ family: 'cmd' }) - } finally { - Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform }) - } - }) -}) diff --git a/src/main/hooks-runner.test.ts b/src/main/hooks-runner.test.ts index 68b807b7a45..85681b9369d 100644 --- a/src/main/hooks-runner.test.ts +++ b/src/main/hooks-runner.test.ts @@ -354,109 +354,3 @@ describe('createSetupRunnerScript', () => { } }) }) - -describe('createIssueCommandRunnerScript', () => { - const makeRepo = () => - ({ - id: 'test-id', - path: '/test/repo', - displayName: 'Test Repo', - badgeColor: '#000', - addedAt: Date.now() - }) as unknown as Repo - - it('writes a POSIX runner under the worktree git dir for long issue commands', async () => { - const fs = await import('node:fs') - const originalPlatform = process.platform - - execFileSyncMock.mockReturnValue( - '/test/repo/.git/worktrees/feature/orca/issue-command-runner.sh' - ) - Object.defineProperty(process, 'platform', { - configurable: true, - value: 'linux' - }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript( - makeRepo(), - '/test/repo-feature', - 'codex exec "long command"\nclaude -p "review it"' - ) - - expect(result).toEqual({ - runnerScriptPath: '/test/repo/.git/worktrees/feature/orca/issue-command-runner.sh', - envVars: expect.objectContaining({ - ORCA_ROOT_PATH: '/test/repo', - ORCA_WORKTREE_PATH: '/test/repo-feature' - }) - }) - expect(vi.mocked(fs.writeFileSync)).toHaveBeenCalledWith( - '/test/repo/.git/worktrees/feature/orca/issue-command-runner.sh', - '#!/usr/bin/env bash\nset -e\ncodex exec "long command"\nclaude -p "review it"\n', - 'utf-8' - ) - expect(vi.mocked(fs.chmodSync)).toHaveBeenCalledWith( - '/test/repo/.git/worktrees/feature/orca/issue-command-runner.sh', - 0o755 - ) - } finally { - Object.defineProperty(process, 'platform', { - configurable: true, - value: originalPlatform - }) - } - }) - - it('carries the WSL launch shell for a Windows-drive worktree routed through WSL', async () => { - const originalPlatform = process.platform - - execFileSyncMock.mockReturnValue('/mnt/c/repo/.git/orca/issue-command-runner.sh') - Object.defineProperty(process, 'platform', { - configurable: true, - value: 'win32' - }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript( - makeRepo(), - 'C:\\repo\\feature', - 'codex exec "long command"', - { wslDistro: 'Ubuntu' } - ) - - // Runner paths are stored in Windows form and need /mnt translation again at launch. - expect(result.runnerScriptPath).toBe('C:\\repo\\.git\\orca\\issue-command-runner.sh') - expect(result.shell).toEqual({ family: 'posix', executable: 'wsl.exe' }) - } finally { - Object.defineProperty(process, 'platform', { - configurable: true, - value: originalPlatform - }) - } - }) - - it('keeps native Windows issue runners on the cmd launch shell', async () => { - const originalPlatform = process.platform - - execFileSyncMock.mockReturnValue('C:\\repo\\.git\\orca\\issue-command-runner.cmd') - Object.defineProperty(process, 'platform', { - configurable: true, - value: 'win32' - }) - - try { - const { createIssueCommandRunnerScript } = await import('./worktree-runner-script') - const result = createIssueCommandRunnerScript(makeRepo(), 'C:\\repo\\feature', 'pnpm install') - - expect(result.shell).toEqual({ family: 'cmd' }) - } finally { - Object.defineProperty(process, 'platform', { - configurable: true, - value: originalPlatform - }) - } - }) -}) diff --git a/src/main/ipc/hooks/register-worktree-hook-runner-handler.ts b/src/main/ipc/hooks/register-worktree-hook-runner-handler.ts deleted file mode 100644 index b6386616bf2..00000000000 --- a/src/main/ipc/hooks/register-worktree-hook-runner-handler.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ipcMain } from 'electron' -import { - createIssueCommandRunnerScript, - resolveSetupRunnerShell -} from '../../worktree-runner-script' -import { getLocalProjectWorktreeGitOptions } from '../../project-runtime-git-options' -import type { WorktreeIpcContext } from '../worktrees/worktree-ipc-context' - -export function registerWorktreeHookRunnerHandler(context: WorktreeIpcContext): void { - const { store } = context - - ipcMain.handle( - 'hooks:createIssueCommandRunner', - (_event, args: { repoId: string; worktreePath: string; command: string }) => { - const repo = store.getRepo(args.repoId) - if (!repo) { - throw new Error(`Repo not found: ${args.repoId}`) - } - - return createIssueCommandRunnerScript( - repo, - args.worktreePath, - args.command, - getLocalProjectWorktreeGitOptions(store, repo), - resolveSetupRunnerShell(store.getSettings()) - ) - } - ) -} diff --git a/src/main/ipc/worktrees-create-metadata-persistence.test.ts b/src/main/ipc/worktrees-create-metadata-persistence.test.ts index 1a8fde8a393..ca1fb0945a7 100644 --- a/src/main/ipc/worktrees-create-metadata-persistence.test.ts +++ b/src/main/ipc/worktrees-create-metadata-persistence.test.ts @@ -47,7 +47,6 @@ const WORKTREE_HANDLER_CHANNELS = [ 'worktrees:getBranchRenameFailureOutput', 'hooks:check', 'hooks:inspectSetupScriptImports', - 'hooks:createIssueCommandRunner', 'hooks:readIssueCommand', 'hooks:writeIssueCommand' ] as const diff --git a/src/main/ipc/worktrees-issue-command-overrides.test.ts b/src/main/ipc/worktrees-issue-command-overrides.test.ts index cc11ed94ff4..f9144a060cb 100644 --- a/src/main/ipc/worktrees-issue-command-overrides.test.ts +++ b/src/main/ipc/worktrees-issue-command-overrides.test.ts @@ -4,11 +4,7 @@ import { join } from 'node:path' import * as issueCommandFile from '../issue-command-file' import * as projectGitOptions from '../project-runtime-git-options' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { - createIssueCommandRunnerScriptMock, - addWorktreeMock, - listWorktreesMock -} from './worktrees-test-module-mocks' +import { addWorktreeMock, listWorktreesMock } from './worktrees-test-module-mocks' import { handlers, setupWorktreeHandlers, store } from './worktrees-test-harness' import { SshHostNotServedError } from '../providers/ssh-host-not-served-error' @@ -173,30 +169,6 @@ describe('registerWorktreeHandlers', () => { } }) - it('creates an issue-command runner for an existing repo/worktree pair', async () => { - const result = await handlers['hooks:createIssueCommandRunner'](null, { - repoId: 'repo-1', - worktreePath: '/workspace/improve-dashboard', - command: 'codex exec "long command"' - }) - - expect(createIssueCommandRunnerScriptMock).toHaveBeenCalledWith( - expect.objectContaining({ id: 'repo-1' }), - '/workspace/improve-dashboard', - 'codex exec "long command"', - {}, - // Why: issue runners take the resolved setup shell; it is undefined off Windows. - undefined - ) - expect(result).toMatchObject({ - runnerScriptPath: '/workspace/repo/.git/orca/issue-command-runner.sh', - envVars: { - ORCA_ROOT_PATH: '/workspace/repo', - ORCA_WORKTREE_PATH: '/workspace/improve-dashboard' - } - }) - }) - it('rejects SSH issue-command writes, which this process does not serve', async () => { const repo = { id: 'repo-ssh', diff --git a/src/main/ipc/worktrees-test-harness.ts b/src/main/ipc/worktrees-test-harness.ts index ad241e29203..6e4972769a6 100644 --- a/src/main/ipc/worktrees-test-harness.ts +++ b/src/main/ipc/worktrees-test-harness.ts @@ -36,7 +36,6 @@ import { getWorkItemMock, getPullRequestPushTargetMock, getEffectiveHooksMock, - createIssueCommandRunnerScriptMock, createSetupRunnerScriptMock, getEffectiveHooksFromConfigMock, getDefaultTabsLaunchMock, @@ -114,7 +113,6 @@ export function setupWorktreeHandlers(): WorktreeRuntimeStub { getEffectiveHooksFromConfigMock, getDefaultTabsLaunchMock, parseOrcaYamlMock, - createIssueCommandRunnerScriptMock, createSetupRunnerScriptMock, buildPosixRunnerScriptMock, buildWindowsRunnerScriptMock, @@ -265,13 +263,6 @@ export function setupWorktreeHandlers(): WorktreeRuntimeStub { ORCA_WORKTREE_PATH: '/workspace/improve-dashboard' } }) - createIssueCommandRunnerScriptMock.mockReturnValue({ - runnerScriptPath: '/workspace/repo/.git/orca/issue-command-runner.sh', - envVars: { - ORCA_ROOT_PATH: '/workspace/repo', - ORCA_WORKTREE_PATH: '/workspace/improve-dashboard' - } - }) computeWorktreePathMock.mockImplementation( ( sanitizedName: string, diff --git a/src/main/ipc/worktrees-test-module-mocks.ts b/src/main/ipc/worktrees-test-module-mocks.ts index 3ea28266547..61d430bf538 100644 --- a/src/main/ipc/worktrees-test-module-mocks.ts +++ b/src/main/ipc/worktrees-test-module-mocks.ts @@ -64,7 +64,6 @@ export const getWorkItemMock: ModuleMock = vi.fn() export const getPullRequestPushTargetMock: ModuleMock = vi.fn() export const getEffectiveHooksMock: Mock<(repo?: unknown, worktreePath?: string) => unknown> = vi.fn() -export const createIssueCommandRunnerScriptMock: ModuleMock = vi.fn() export const createSetupRunnerScriptMock: ModuleMock = vi.fn() export const getEffectiveHooksFromConfigMock: ModuleMock = vi.fn() export const getDefaultTabsLaunchMock: ModuleMock = vi.fn() @@ -184,7 +183,6 @@ export const setupRunnerScriptTextModuleMock = (actual: Record) export const worktreeRunnerScriptModuleMock = (actual: Record) => ({ ...actual, - createIssueCommandRunnerScript: createIssueCommandRunnerScriptMock, createSetupRunnerScript: createSetupRunnerScriptMock, resolveSetupRunnerShell: resolveSetupRunnerShellMock }) diff --git a/src/main/ipc/worktrees-windows.test.ts b/src/main/ipc/worktrees-windows.test.ts index 3e619bc1652..c9e8e875690 100644 --- a/src/main/ipc/worktrees-windows.test.ts +++ b/src/main/ipc/worktrees-windows.test.ts @@ -18,7 +18,6 @@ const { getEffectiveHooksMock, getEffectiveHooksFromConfigMock, getDefaultTabsLaunchMock, - createIssueCommandRunnerScriptMock, createSetupRunnerScriptMock, resolveSetupRunnerShellMock, shouldRunSetupForCreateMock, @@ -48,7 +47,6 @@ const { getEffectiveHooksMock: vi.fn(), getEffectiveHooksFromConfigMock: vi.fn(), getDefaultTabsLaunchMock: vi.fn(), - createIssueCommandRunnerScriptMock: vi.fn(), createSetupRunnerScriptMock: vi.fn(), resolveSetupRunnerShellMock: vi.fn(), shouldRunSetupForCreateMock: vi.fn(), @@ -114,7 +112,6 @@ vi.mock('../hooks', () => ({ })) vi.mock('../worktree-runner-script', () => ({ - createIssueCommandRunnerScript: createIssueCommandRunnerScriptMock, createSetupRunnerScript: createSetupRunnerScriptMock, resolveSetupRunnerShell: resolveSetupRunnerShellMock })) @@ -195,7 +192,6 @@ describe('registerWorktreeHandlers – Windows path handling', () => { getEffectiveHooksMock.mockReset() getEffectiveHooksFromConfigMock.mockReset() getDefaultTabsLaunchMock.mockReset() - createIssueCommandRunnerScriptMock.mockReset() createSetupRunnerScriptMock.mockReset() resolveSetupRunnerShellMock.mockReset() shouldRunSetupForCreateMock.mockReset() @@ -609,29 +605,4 @@ describe('registerWorktreeHandlers – Windows path handling', () => { repoId: 'repo-1' }) }) - it('gives the issue-command runner the same setup shell as the setup runner', () => { - // Regression (C4): native Windows issue runners stayed .cmd even when setup - // resolved to Git Bash, so same-session bash issue templates broke. - resolveSetupRunnerShellMock.mockReturnValue({ family: 'posix' }) - createIssueCommandRunnerScriptMock.mockReturnValue({ - runnerScriptPath: 'C:\\repo\\.git\\orca\\issue-command-runner.sh', - envVars: {}, - shell: { family: 'posix' } - }) - - handlers['hooks:createIssueCommandRunner'](null, { - repoId: 'repo-1', - worktreePath: 'C:\\workspaces\\improve-dashboard', - command: 'gh issue view 42' - }) - - expect(resolveSetupRunnerShellMock).toHaveBeenCalledWith(store.getSettings()) - expect(createIssueCommandRunnerScriptMock).toHaveBeenCalledWith( - expect.objectContaining({ id: 'repo-1' }), - 'C:\\workspaces\\improve-dashboard', - 'gh issue view 42', - expect.anything(), - { family: 'posix' } - ) - }) }) diff --git a/src/main/ipc/worktrees.ts b/src/main/ipc/worktrees.ts index 69d3420e062..117c1ec2c3d 100644 --- a/src/main/ipc/worktrees.ts +++ b/src/main/ipc/worktrees.ts @@ -9,7 +9,6 @@ import { registerReviewBaseHandlers } from './worktrees/create/register-review-b import { registerWorktreeHookCheckHandler } from './hooks/register-worktree-hook-check-handler' import { registerWorktreeHookFileHandlers } from './hooks/register-worktree-hook-file-handlers' import { registerWorktreeHookInspectionHandler } from './hooks/register-worktree-hook-inspection-handler' -import { registerWorktreeHookRunnerHandler } from './hooks/register-worktree-hook-runner-handler' import { registerDetectedWorktreeHandlers } from './worktrees/listing/register-detected-worktree-handlers' import { registerHostCatalogHandlers } from './worktrees/listing/register-host-catalog-handlers' import { registerWorktreeCatalogHandlers } from './worktrees/listing/register-worktree-catalog-handlers' @@ -54,7 +53,6 @@ const WORKTREE_HANDLER_CHANNELS = [ 'worktrees:getBranchRenameFailureOutput', 'hooks:check', 'hooks:inspectSetupScriptImports', - 'hooks:createIssueCommandRunner', 'hooks:readIssueCommand', 'hooks:writeIssueCommand' ] as const @@ -96,7 +94,6 @@ export function registerWorktreeHandlers( registerWorktreeForgetHandlers(context) registerWorktreeMetadataHandlers(context) registerWorktreeHookCheckHandler(context) - registerWorktreeHookRunnerHandler(context) registerWorktreeHookInspectionHandler(context) registerWorktreeHookFileHandlers(context) } diff --git a/src/main/worktree-runner-script.ts b/src/main/worktree-runner-script.ts index b4bf33296da..40cac5e477f 100644 --- a/src/main/worktree-runner-script.ts +++ b/src/main/worktree-runner-script.ts @@ -48,29 +48,11 @@ export function createSetupRunnerScript( }) } -export function createIssueCommandRunnerScript( - repo: Repo, - worktreePath: string, - command: string, - projectRuntime?: ProjectExecutionRuntimeResolution | HookRuntimeTarget, - setupShell?: SetupRunnerShell -): WorktreeSetupLaunch { - // Runner files avoid PTY line-editor wrapping or truncation of long commands. - return createWorktreeRunnerScript({ - repo, - worktreePath, - script: command, - runnerBaseName: 'issue-command-runner', - runtimeTarget: getHookRuntimeTarget(projectRuntime), - setupShell - }) -} - function createWorktreeRunnerScript(args: { repo: Repo worktreePath: string script: string - runnerBaseName: 'setup-runner' | 'issue-command-runner' + runnerBaseName: 'setup-runner' runtimeTarget?: HookRuntimeTarget waitForAgentStartup?: boolean setupShell?: SetupRunnerShell diff --git a/src/preload/api/agent-hook-api.ts b/src/preload/api/agent-hook-api.ts index c01e8e9d23d..db222d45332 100644 --- a/src/preload/api/agent-hook-api.ts +++ b/src/preload/api/agent-hook-api.ts @@ -1,5 +1,4 @@ import type { OrcaHooks } from '../../shared/orca-yaml-hook-types' -import type { WorktreeSetupLaunch } from '../../shared/worktree/launch-types' import type { ExecutionHostId } from '../../shared/execution-host' import type { SetupScriptImportCandidate } from '../../shared/setup-script-imports' @@ -14,11 +13,6 @@ export type HooksApi = { repoId: string hostId?: ExecutionHostId }) => Promise - createIssueCommandRunner: (args: { - repoId: string - worktreePath: string - command: string - }) => Promise readIssueCommand: (args: { repoId: string; hostId?: ExecutionHostId }) => Promise<{ status?: 'ok' | 'error' localContent: string | null diff --git a/src/preload/api/hooks-bridge.ts b/src/preload/api/hooks-bridge.ts index 75c48281b16..a4d846d235d 100644 --- a/src/preload/api/hooks-bridge.ts +++ b/src/preload/api/hooks-bridge.ts @@ -1,5 +1,4 @@ import { ipcRenderer } from 'electron' -import type { WorktreeSetupLaunch } from '../../shared/worktree/launch-types' import type { ExecutionHostId } from '../../shared/execution-host' import type { PreloadApi } from '../api-types' @@ -10,12 +9,6 @@ export const hooksApi = { inspectSetupScriptImports: (args: { repoId: string; hostId?: ExecutionHostId }) => ipcRenderer.invoke('hooks:inspectSetupScriptImports', args), - createIssueCommandRunner: (args: { - repoId: string - worktreePath: string - command: string - }): Promise => ipcRenderer.invoke('hooks:createIssueCommandRunner', args), - readIssueCommand: (args: { repoId: string hostId?: ExecutionHostId diff --git a/src/renderer/src/web/preload-api/web-review-api.ts b/src/renderer/src/web/preload-api/web-review-api.ts index d77eb519101..d796d98ee57 100644 --- a/src/renderer/src/web/preload-api/web-review-api.ts +++ b/src/renderer/src/web/preload-api/web-review-api.ts @@ -15,7 +15,6 @@ export function createHooksApi(): NonNullable['hooks']> { check: async ({ repoId }) => callRuntimeResult('repo.hooksCheck', { repo: repoId }), inspectSetupScriptImports: async ({ repoId }) => callRuntimeResult('repo.setupScriptImports', { repo: repoId }), - createIssueCommandRunner: async () => ({ launched: false }) as never, readIssueCommand: async ({ repoId }) => callRuntimeResult('repo.issueCommandRead', { repo: repoId }), writeIssueCommand: async ({ repoId, content }) => {