Files
orca/src/shared/branch-name-from-work.test.ts
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

135 lines
5.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildBranchNamePrompt,
humanizeBranchSlug,
isAutoGeneratedCreatureBranchName,
sanitizeBranchSlug,
stripConfiguredBranchPrefix
} from './branch-name-from-work'
describe('sanitizeBranchSlug', () => {
it('lowercases and kebab-cases free-form model output', () => {
expect(sanitizeBranchSlug('Fix the auth bug')).toBe('fix-the-auth-bug')
})
it('caps at four words', () => {
expect(sanitizeBranchSlug('add dark mode toggle to settings page')).toBe('add-dark-mode-toggle')
})
it('honors a custom word cap', () => {
expect(sanitizeBranchSlug('add dark mode toggle', 2)).toBe('add-dark')
})
it('collapses punctuation, quotes, and slashes into single hyphens', () => {
expect(sanitizeBranchSlug('"feat/login_flow!!"')).toBe('feat-login-flow')
})
it('returns empty string when nothing usable remains', () => {
expect(sanitizeBranchSlug(' !!! ___ ')).toBe('')
expect(sanitizeBranchSlug('')).toBe('')
})
})
describe('isAutoGeneratedCreatureBranchName', () => {
it('matches a creature name case-insensitively', () => {
expect(isAutoGeneratedCreatureBranchName('Nautilus')).toBe(true)
expect(isAutoGeneratedCreatureBranchName('octopus')).toBe(true)
})
it('matches numbered collision variants', () => {
expect(isAutoGeneratedCreatureBranchName('Nautilus-2')).toBe(true)
expect(isAutoGeneratedCreatureBranchName('Seahorse-17')).toBe(true)
})
it('rejects user-chosen and work-derived names', () => {
expect(isAutoGeneratedCreatureBranchName('fix-auth-bug')).toBe(false)
expect(isAutoGeneratedCreatureBranchName('my-feature')).toBe(false)
expect(isAutoGeneratedCreatureBranchName('')).toBe(false)
})
})
describe('stripConfiguredBranchPrefix', () => {
it('strips a leaked username prefix the model folded into the slug', () => {
expect(stripConfiguredBranchPrefix('tmchow-worktree-creation-spinner', 'tmchow')).toBe(
'worktree-creation-spinner'
)
})
it('strips a multi-token custom prefix', () => {
expect(stripConfiguredBranchPrefix('my-team-add-logout', 'my-team')).toBe('add-logout')
})
it('normalizes the prefix the same way the slug was normalized', () => {
expect(stripConfiguredBranchPrefix('jane-doe-fix-auth', 'Jane.Doe')).toBe('fix-auth')
})
it('leaves a work-derived name that merely starts with a real word', () => {
// The leading word matches no configured prefix, so it is content, not a prefix.
expect(stripConfiguredBranchPrefix('add-logout-button', 'tmchow')).toBe('add-logout-button')
})
it('does not strip when no prefix is configured', () => {
expect(stripConfiguredBranchPrefix('tmchow-fix-auth', null)).toBe('tmchow-fix-auth')
expect(stripConfiguredBranchPrefix('tmchow-fix-auth', undefined)).toBe('tmchow-fix-auth')
expect(stripConfiguredBranchPrefix('tmchow-fix-auth', '')).toBe('tmchow-fix-auth')
})
it('returns empty for prefix-only output so the caller skips the rename', () => {
// The model echoed just the prefix; renaming would double it (`tmchow/tmchow`).
expect(stripConfiguredBranchPrefix('tmchow', 'tmchow')).toBe('')
// Confirm a real work-derived name still strips correctly.
expect(stripConfiguredBranchPrefix('tmchow-fix-auth', 'tmchow')).toBe('fix-auth')
})
})
describe('humanizeBranchSlug', () => {
it('turns a kebab slug into a readable label', () => {
expect(humanizeBranchSlug('supported-models-list')).toBe('Supported models list')
expect(humanizeBranchSlug('fix-auth')).toBe('Fix auth')
})
it('returns empty string for an empty slug', () => {
expect(humanizeBranchSlug('')).toBe('')
})
})
describe('buildBranchNamePrompt', () => {
it('includes the user prompt and omits the assistant section when absent', () => {
const prompt = buildBranchNamePrompt({ firstPrompt: 'Add a logout button' })
expect(prompt).toContain('Add a logout button')
expect(prompt).not.toContain("Agent's initial response")
})
it('keeps the default prompt general without style rules', () => {
const prompt = buildBranchNamePrompt({ firstPrompt: 'Add a logout button' })
expect(prompt).toContain('Generate a short git branch name')
expect(prompt).toContain('Output ONLY the branch name on a single line')
expect(prompt).not.toContain('Rules:')
expect(prompt).not.toMatch(/kebab-case/i)
expect(prompt).not.toMatch(/between \d+ and \d+ words/i)
expect(prompt).not.toMatch(/no prefixes/i)
})
it('includes the assistant response when present', () => {
const prompt = buildBranchNamePrompt({
firstPrompt: 'Add a logout button',
assistantMessage: "I'll wire it into the header."
})
expect(prompt).toContain("Agent's initial response")
expect(prompt).toContain("I'll wire it into the header.")
})
it('leads with a custom naming prompt so overrides can own style', () => {
const prompt = buildBranchNamePrompt(
{ firstPrompt: 'Add a logout button' },
'Prefer product nouns.'
)
expect(prompt.startsWith('Prefer product nouns.')).toBe(true)
expect(prompt).not.toContain('Additional user prompt:')
expect(prompt).not.toContain('Rules:')
expect(prompt).toContain('Generate a git branch name')
expect(prompt).toContain('Output ONLY the branch name on a single line')
expect(prompt).toContain('Add a logout button')
})
})