Files
orca/src/shared/source-control-ai.test.ts
T
Brennan Benson ce910e5d52 fix(naming): keep the built-in branch-name prompt general (#9088)
* fix(naming): keep the built-in branch-name prompt general

The shipped auto-rename prompt no longer hard-codes style rules (word
count, kebab-case, no prefixes). Users can already override naming via
Source Control AI instructions and the branch-name command template; a
prescriptive default fought those overrides. Git-safe sanitization still
runs after generation.

* fix(naming): preserve branch prompt overrides

* chore(skills): refresh rc.2 manifest version
2026-07-16 18:33:34 -07:00

756 lines
23 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getDefaultSettings } from './constants'
import {
clearSourceControlAiModelChoiceForHost,
hasConfiguredSourceControlAiInstructions,
mergeLegacyCommitMessageAiIntoSourceControlAi,
normalizeRepoSourceControlAiOverrides,
projectSourceControlAiToLegacyCommitMessageAi,
resolveSourceControlAiEnabled,
readSourceControlAiModelChoiceForHost,
resolveSourceControlAiForOperation,
resolveSourceControlAiPrCreationDefaults,
selectSourceControlAiModelChoiceForHost,
sourceControlAiSettingsFromLegacy
} from './source-control-ai'
import type {
RepoSourceControlAiOverrides,
SourceControlAiOperation
} from './source-control-ai-types'
import type { GlobalSettings } from './types'
function settings(): GlobalSettings {
const base = getDefaultSettings('/tmp')
return {
...base,
defaultTuiAgent: 'codex' as const,
sourceControlAi: {
...base.sourceControlAi!,
enabled: true,
agentId: 'codex' as const,
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: { 'gpt-5.5': 'medium', 'gpt-5.4': 'high' },
instructionsByOperation: {
commitMessage: 'Global commit style',
pullRequest: 'Global PR style',
branchName: 'Global branch style'
}
}
}
}
function resolve(operation: SourceControlAiOperation, overrides?: RepoSourceControlAiOverrides) {
const result = resolveSourceControlAiForOperation({
settings: settings(),
repo: overrides ? { sourceControlAi: overrides } : null,
operation,
discoveryHostKey: 'local',
prCreationProductDefaults: {
draft: false,
useTemplate: false,
generateDetailsOnOpen: false,
openAfterCreate: false
}
})
expect(result.ok).toBe(true)
if (!result.ok) {
throw new Error(result.error)
}
return result.value
}
describe('source-control AI resolution', () => {
it('uses the global default model for every operation', () => {
expect(resolve('commitMessage').params.model).toBe('gpt-5.5')
expect(resolve('pullRequest').params.model).toBe('gpt-5.5')
expect(resolve('branchName').params.model).toBe('gpt-5.5')
})
it('resolves generation config and PR defaults when Source Control AI actions are hidden', () => {
const base = settings()
base.sourceControlAi = {
...base.sourceControlAi!,
enabled: false,
prCreationDefaults: {
draft: true,
useTemplate: true,
generateDetailsOnOpen: false,
openAfterCreate: false
}
}
const generation = resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'pullRequest'
})
expect(generation.ok).toBe(true)
expect(generation.ok && generation.value.params.model).toBe('gpt-5.5')
expect(
resolveSourceControlAiPrCreationDefaults({
settings: base,
repo: {
sourceControlAi: {
prCreationDefaults: {
draft: null,
generateDetailsOnOpen: true,
openAfterCreate: true
}
}
},
prCreationProductDefaults: {
draft: false,
useTemplate: false,
generateDetailsOnOpen: false,
openAfterCreate: false
}
})
).toEqual({
draft: true,
useTemplate: true,
generateDetailsOnOpen: true,
openAfterCreate: true
})
})
it('lets repo-hidden Source Control AI actions keep operation generation valid', () => {
const result = resolveSourceControlAiForOperation({
settings: settings(),
repo: { sourceControlAi: { enabled: false } },
operation: 'branchName',
discoveryHostKey: 'local'
})
expect(result.ok).toBe(true)
expect(result.ok && result.value.params.model).toBe('gpt-5.5')
})
it('lets repo action visibility override the global default', () => {
const base = settings()
base.sourceControlAi = {
...base.sourceControlAi!,
enabled: false
}
expect(resolveSourceControlAiEnabled({ settings: base, repo: null })).toBe(false)
expect(
resolveSourceControlAiEnabled({
settings: base,
repo: { sourceControlAi: { enabled: true } }
})
).toBe(true)
base.sourceControlAi.enabled = true
expect(
resolveSourceControlAiEnabled({
settings: base,
repo: { sourceControlAi: { enabled: false } }
})
).toBe(false)
})
it('resolves PR defaults even when generation config is invalid', () => {
const base = settings()
base.sourceControlAi = {
...base.sourceControlAi!,
agentId: 'custom',
customAgentCommand: '',
prCreationDefaults: {
draft: false,
useTemplate: true,
generateDetailsOnOpen: true,
openAfterCreate: false
}
}
const generation = resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'pullRequest'
})
expect(generation.ok).toBe(false)
expect(
resolveSourceControlAiPrCreationDefaults({
settings: base,
repo: { sourceControlAi: { prCreationDefaults: { draft: true } } }
})
).toEqual({
draft: true,
useTemplate: true,
generateDetailsOnOpen: true,
openAfterCreate: false
})
})
it('treats a normalized null agent as default instead of using stale legacy agent', () => {
const base = settings()
base.defaultTuiAgent = 'codex'
base.commitMessageAi = {
...base.commitMessageAi!,
agentId: 'claude',
selectedModelByAgent: { claude: 'opus' },
selectedThinkingByModel: { opus: 'max' }
}
base.sourceControlAi = {
...base.sourceControlAi!,
agentId: null,
selectedModelByAgent: { codex: 'gpt-5.4' }
}
const result = resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'commitMessage',
discoveryHostKey: 'local'
})
expect(result.ok && result.value.params.agentId).toBe('codex')
expect(result.ok && result.value.params.model).toBe('gpt-5.4')
})
it('lets a global operation model override win over the global default', () => {
const base = settings()
base.sourceControlAi!.modelOverridesByOperation = {
pullRequest: { selectedModelByAgent: { codex: 'gpt-5.4' } }
}
const result = resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'pullRequest',
discoveryHostKey: 'local'
})
expect(result.ok && result.value.params.model).toBe('gpt-5.4')
})
it('lets a repo operation model override win over global operation override', () => {
const base = settings()
base.sourceControlAi!.modelOverridesByOperation = {
commitMessage: { selectedModelByAgent: { codex: 'gpt-5.4' } }
}
const result = resolveSourceControlAiForOperation({
settings: base,
repo: {
sourceControlAi: {
modelOverridesByOperation: {
commitMessage: { selectedModelByAgent: { codex: 'gpt-5.4-mini' } }
}
}
},
operation: 'commitMessage',
discoveryHostKey: 'local'
})
expect(result.ok && result.value.params.model).toBe('gpt-5.4-mini')
})
it('uses the repo custom command before the global custom command', () => {
const base = settings()
base.sourceControlAi = {
...base.sourceControlAi!,
actions: {
...base.sourceControlAi!.actions,
commitMessage: {
agentId: 'custom',
commandInputTemplate: '{basePrompt}'
}
},
customAgentCommand: 'global-agent {prompt}'
}
const result = resolveSourceControlAiForOperation({
settings: base,
repo: {
sourceControlAi: {
customAgentCommand: 'repo-agent {prompt}'
}
},
operation: 'commitMessage',
discoveryHostKey: 'local'
})
expect(result.ok && result.value.params.customAgentCommand).toBe('repo-agent {prompt}')
})
it('resolves thinking effort with override precedence and model default fallback', () => {
expect(resolve('commitMessage').params.thinkingLevel).toBe('medium')
expect(
resolve('commitMessage', {
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedThinkingByModel: { 'gpt-5.4': 'xhigh' }
}
}
}).params.thinkingLevel
).toBe('xhigh')
const base = settings()
base.sourceControlAi!.selectedThinkingByModel = {
'gpt-5.5': 'unsupported'
} as Record<string, string>
const result = resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'commitMessage',
discoveryHostKey: 'local'
})
expect(result.ok && result.value.params.thinkingLevel).toBe('low')
})
it('resolves repo instructions as replacement overrides, including explicit empty', () => {
expect(resolve('commitMessage').params.customPrompt).toBe('Global commit style')
expect(
resolve('commitMessage', {
instructionsByOperation: { commitMessage: null }
}).params.customPrompt
).toBe('Global commit style')
expect(
resolve('commitMessage', {
instructionsByOperation: { commitMessage: '' }
}).params.customPrompt
).toBe('')
expect(
resolve('commitMessage', {
instructionsByOperation: { commitMessage: 'Repo commit style' }
}).params.customPrompt
).toBe('Repo commit style')
expect(resolve('branchName').params.customPrompt).toBe('Global branch style')
expect(resolve('branchName').params.commandInputTemplate).toBe(
'Global branch style\n\n{basePrompt}'
)
expect(
resolve('branchName', {
instructionsByOperation: { branchName: 'Repo branch style' }
}).params.customPrompt
).toBe('Repo branch style')
expect(
resolve('branchName', {
instructionsByOperation: { branchName: 'Repo branch style' }
}).params.commandInputTemplate
).toBe('Repo branch style\n\n{basePrompt}')
})
it('does not treat null repo instructions as configured overrides', () => {
const base = settings()
base.sourceControlAi = {
...base.sourceControlAi!,
instructionsByOperation: {
commitMessage: '',
pullRequest: '',
branchName: ''
}
}
expect(
hasConfiguredSourceControlAiInstructions({
settings: base,
repo: { sourceControlAi: { instructionsByOperation: { commitMessage: null } } },
operation: 'commitMessage'
})
).toBe(false)
expect(
hasConfiguredSourceControlAiInstructions({
settings: base,
repo: { sourceControlAi: { instructionsByOperation: { commitMessage: '' } } },
operation: 'commitMessage'
})
).toBe(true)
})
it('resolves repo tri-state PR defaults through inherit on and off', () => {
expect(resolve('pullRequest').prCreationDefaults.draft).toBe(false)
expect(
resolve('pullRequest', {
prCreationDefaults: { draft: true, openAfterCreate: false }
}).prCreationDefaults
).toMatchObject({ draft: true, openAfterCreate: false })
})
it('maps legacy custom prompt to released split instructions', () => {
const migrated = sourceControlAiSettingsFromLegacy({
enabled: true,
agentId: 'codex',
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: {},
customPrompt: 'Legacy commit prompt',
customAgentCommand: ''
})
expect(migrated.instructionsByOperation.commitMessage).toBe('Legacy commit prompt')
expect(migrated.instructionsByOperation.pullRequest).toBe('')
expect(migrated.instructionsByOperation.branchName).toBe('Legacy commit prompt')
expect(migrated.actions?.commitMessage?.commandInputTemplate).toBe(
'{basePrompt}\n\nLegacy commit prompt'
)
expect(migrated.actions?.branchName?.commandInputTemplate).toBe(
'Legacy commit prompt\n\n{basePrompt}'
)
})
it('merges legacy commit-message updates without wiping PR-only settings', () => {
const base = settings().sourceControlAi!
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(base, {
enabled: false,
agentId: 'claude',
selectedModelByAgent: { claude: 'sonnet' },
selectedThinkingByModel: { sonnet: 'medium' },
customPrompt: 'Legacy commit prompt',
customAgentCommand: 'claude'
})
expect(merged).toMatchObject({
enabled: false,
agentId: 'claude',
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: { 'gpt-5.5': 'medium', 'gpt-5.4': 'high' },
customAgentCommand: 'claude',
instructionsByOperation: {
commitMessage: 'Legacy commit prompt',
pullRequest: 'Global PR style',
branchName: 'Global branch style'
}
})
expect(merged.modelOverridesByOperation?.commitMessage).toEqual({
selectedModelByAgent: { claude: 'sonnet' },
selectedThinkingByModel: { sonnet: 'medium' }
})
})
it('can map explicit legacy PR generation instructions for old runtime callers', () => {
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(
undefined,
{
enabled: true,
agentId: 'codex',
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: {},
customPrompt: 'Legacy PR prompt',
customAgentCommand: ''
},
{ pullRequestInstructionsFromLegacy: true }
)
expect(merged.instructionsByOperation.pullRequest).toBe('Legacy PR prompt')
})
it('projects commit-message operation model overrides into legacy settings', () => {
const legacy = projectSourceControlAiToLegacyCommitMessageAi({
...settings().sourceControlAi!,
selectedModelByAgent: { codex: 'gpt-5.5', claude: 'sonnet' },
selectedModelByAgentByHost: {
local: { codex: 'gpt-5.5' },
'ssh:conn-1': { codex: 'gpt-5.5', claude: 'sonnet' }
},
selectedThinkingByModel: {
'gpt-5.4': 'high',
'gpt-5.5': 'medium'
},
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedModelByAgentByHost: {
local: { codex: 'gpt-5.4' },
'ssh:conn-1': { codex: 'gpt-5.4-mini' }
},
selectedThinkingByModel: {
'gpt-5.4': 'xhigh',
'gpt-5.4-mini': 'medium'
}
},
pullRequest: {
selectedModelByAgent: { codex: 'gpt-5.2' },
selectedThinkingByModel: { 'gpt-5.2': 'low' }
}
}
})
expect(legacy.selectedModelByAgent).toMatchObject({
codex: 'gpt-5.4',
claude: 'sonnet'
})
expect(legacy.selectedModelByAgentByHost).toMatchObject({
local: { codex: 'gpt-5.4' },
'ssh:conn-1': { codex: 'gpt-5.4-mini', claude: 'sonnet' }
})
expect(legacy.selectedThinkingByModel).toMatchObject({
'gpt-5.4': 'xhigh',
'gpt-5.4-mini': 'medium',
'gpt-5.5': 'medium'
})
expect(legacy.selectedThinkingByModel['gpt-5.2']).toBeUndefined()
})
it('merges projected legacy commit-message models without changing PR defaults', () => {
const source = {
...settings().sourceControlAi!,
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: { 'gpt-5.5': 'medium' },
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedThinkingByModel: { 'gpt-5.4': 'high' }
}
}
}
const legacy = projectSourceControlAiToLegacyCommitMessageAi(source)
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(source, legacy)
expect(merged.selectedModelByAgent.codex).toBe('gpt-5.5')
expect(merged.modelOverridesByOperation?.commitMessage?.selectedModelByAgent?.codex).toBe(
'gpt-5.4'
)
const base = settings()
base.sourceControlAi = merged
expect(
resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'commitMessage',
discoveryHostKey: 'local'
})
).toMatchObject({ ok: true, value: { params: { model: 'gpt-5.4' } } })
expect(
resolveSourceControlAiForOperation({
settings: base,
repo: null,
operation: 'pullRequest',
discoveryHostKey: 'local'
})
).toMatchObject({ ok: true, value: { params: { model: 'gpt-5.5' } } })
})
it('does not synthesize a commit-message override from projected global defaults', () => {
const source = {
...settings().sourceControlAi!,
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: { 'gpt-5.5': 'medium' },
modelOverridesByOperation: undefined
}
const legacy = projectSourceControlAiToLegacyCommitMessageAi(source)
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(source, legacy)
expect(merged.selectedModelByAgent.codex).toBe('gpt-5.5')
expect(merged.modelOverridesByOperation?.commitMessage).toBeUndefined()
})
it('merges only rollback legacy model deltas into commit-message overrides', () => {
const source = {
...settings().sourceControlAi!,
selectedModelByAgent: { codex: 'gpt-5.5', claude: 'sonnet' },
selectedModelByAgentByHost: {
local: { codex: 'gpt-5.5', claude: 'sonnet' }
},
selectedThinkingByModel: {
'gpt-5.5': 'medium',
sonnet: 'high'
},
modelOverridesByOperation: undefined
}
const legacy = projectSourceControlAiToLegacyCommitMessageAi(source)
legacy.selectedModelByAgent = {
...legacy.selectedModelByAgent,
codex: 'gpt-5.4'
}
legacy.selectedModelByAgentByHost = {
...legacy.selectedModelByAgentByHost,
local: {
...legacy.selectedModelByAgentByHost?.local,
codex: 'gpt-5.4'
}
}
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(source, legacy)
expect(merged.selectedModelByAgent).toEqual({ codex: 'gpt-5.5', claude: 'sonnet' })
expect(merged.modelOverridesByOperation?.commitMessage).toEqual({
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedModelByAgentByHost: { local: { codex: 'gpt-5.4' } }
})
})
it('removes projected commit-message overrides cleared by legacy settings', () => {
const source = {
...settings().sourceControlAi!,
selectedModelByAgent: { codex: 'gpt-5.5' },
selectedThinkingByModel: { 'gpt-5.5': 'medium' },
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedThinkingByModel: { 'gpt-5.4': 'high' }
}
}
}
const legacy = projectSourceControlAiToLegacyCommitMessageAi(source)
delete legacy.selectedModelByAgent.codex
delete legacy.selectedThinkingByModel['gpt-5.4']
const merged = mergeLegacyCommitMessageAiIntoSourceControlAi(source, legacy)
expect(merged.modelOverridesByOperation?.commitMessage).toBeUndefined()
})
it('reads and selects host-scoped model choices with local fallback rules', () => {
const localChoice = selectSourceControlAiModelChoiceForHost(
undefined,
'local',
'codex',
'gpt-5.4'
)
expect(localChoice).toEqual({
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedModelByAgentByHost: { local: { codex: 'gpt-5.4' } }
})
const remoteChoice = selectSourceControlAiModelChoiceForHost(
localChoice,
'ssh:conn-1',
'codex',
'remote-model'
)
expect(readSourceControlAiModelChoiceForHost(remoteChoice, 'local', 'codex')).toBe('gpt-5.4')
expect(readSourceControlAiModelChoiceForHost(remoteChoice, 'ssh:conn-1', 'codex')).toBe(
'remote-model'
)
expect(
readSourceControlAiModelChoiceForHost(remoteChoice, 'ssh:conn-2', 'codex')
).toBeUndefined()
expect(
readSourceControlAiModelChoiceForHost(
{ selectedModelByAgent: { codex: 'global-model' } },
'local',
'codex'
)
).toBe('global-model')
})
it('clears only the selected host model override when inheriting', () => {
const cleared = clearSourceControlAiModelChoiceForHost(
{
selectedModelByAgent: { codex: 'local-model' },
selectedModelByAgentByHost: {
local: { codex: 'local-model' },
'ssh:conn-1': { codex: 'remote-model' }
},
selectedThinkingByModel: { 'remote-model': 'high' }
},
'local',
'codex'
)
expect(cleared).toEqual({
selectedModelByAgentByHost: {
'ssh:conn-1': { codex: 'remote-model' }
},
selectedThinkingByModel: { 'remote-model': 'high' }
})
})
it('normalizes repo overrides defensively and preserves explicit inherit sentinels', () => {
const normalized = normalizeRepoSourceControlAiOverrides({
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: {
codex: 'gpt-5.4',
claude: 42,
constructor: 'polluted'
},
selectedModelByAgentByHost: {
local: { codex: 'gpt-5.4' },
'ssh:conn-1': { codex: 'remote-model', claude: false },
malformed: 'not-a-record',
prototype: { codex: 'polluted' }
},
selectedThinkingByModel: {
'gpt-5.4': 'xhigh',
'remote-model': 'high',
bad: true,
constructor: 'polluted'
}
},
pullRequest: {
selectedModelByAgent: []
},
branchName: {
selectedModelByAgent: { codex: 'gpt-5.4' }
},
unknown: {
selectedModelByAgent: { codex: 'ignored' }
}
},
instructionsByOperation: {
commitMessage: null,
pullRequest: '',
branchName: 'branch style',
unknown: 'ignored'
},
prCreationDefaults: {
draft: true,
useTemplate: null,
generateDetailsOnOpen: 'yes',
openAfterCreate: false
}
})
expect(normalized).toEqual({
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedModelByAgentByHost: {
local: { codex: 'gpt-5.4' },
'ssh:conn-1': { codex: 'remote-model' }
},
selectedThinkingByModel: {
'gpt-5.4': 'xhigh',
'remote-model': 'high'
}
},
branchName: {
selectedModelByAgent: { codex: 'gpt-5.4' }
}
},
instructionsByOperation: {
commitMessage: null,
pullRequest: '',
branchName: 'branch style'
},
actionOverrides: {
pullRequest: {
commandInputTemplate: '{basePrompt}'
},
branchName: {
commandInputTemplate: 'branch style\n\n{basePrompt}'
}
},
prCreationDefaults: {
draft: true,
useTemplate: null,
openAfterCreate: false
}
})
expect(normalizeRepoSourceControlAiOverrides(null)).toBeUndefined()
expect(normalizeRepoSourceControlAiOverrides([])).toBeUndefined()
})
it('preserves repo null command templates without requiring another override field', () => {
expect(
normalizeRepoSourceControlAiOverrides({
instructionsByOperation: {
commitMessage: 'legacy repo style'
},
actionOverrides: {
commitMessage: {
commandInputTemplate: null
}
}
})
).toEqual({
instructionsByOperation: {
commitMessage: 'legacy repo style'
},
actionOverrides: {
commitMessage: {
commandInputTemplate: null
}
}
})
})
})