mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
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.
This commit is contained in:
@@ -140,7 +140,7 @@ describe('maybeAutoRenameBranchOnFirstWork', () => {
|
||||
})
|
||||
|
||||
it('strips a prefix the model leaked into the slug from both branch and display name', async () => {
|
||||
// Model ignored "no prefixes" and echoed `you/worktree-spinner`, which the
|
||||
// Model echoed `you/worktree-spinner`, which the
|
||||
// sanitizer folds to `you-worktree-spinner`; without stripping it would
|
||||
// double-prefix the branch (`you/you-...`) and show "You worktree spinner".
|
||||
generateBranchNameMock.mockResolvedValue({ success: true, slug: 'you-worktree-spinner' })
|
||||
|
||||
@@ -283,9 +283,8 @@ async function runAutoRename(
|
||||
const username = provider
|
||||
? (await getSshGitUsername(provider, repo.path)) || null
|
||||
: (await resolveLocalGitUsername(repo.path)) || null
|
||||
// The model is told not to add a prefix, but sometimes echoes the configured
|
||||
// one (e.g. `tmchow/...`); strip it so it doesn't double-prefix the branch or
|
||||
// leak into the display name.
|
||||
// The model sometimes echoes the configured prefix (e.g. `tmchow/...`); strip
|
||||
// it so it doesn't double-prefix the branch or leak into the display name.
|
||||
const slug = stripConfiguredBranchPrefix(
|
||||
generated.slug,
|
||||
getConfiguredBranchPrefix(settings, username)
|
||||
|
||||
@@ -1879,8 +1879,9 @@ describe('generateBranchNameFromContext', () => {
|
||||
}
|
||||
)
|
||||
|
||||
expect(prompt).toContain('Additional user prompt:')
|
||||
expect(prompt.startsWith('Prefer auth terminology.')).toBe(true)
|
||||
expect(prompt).toContain('Prefer auth terminology.')
|
||||
expect(prompt).not.toContain('Additional user prompt:')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -100,6 +100,16 @@ describe('buildBranchNamePrompt', () => {
|
||||
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',
|
||||
@@ -109,12 +119,14 @@ describe('buildBranchNamePrompt', () => {
|
||||
expect(prompt).toContain("I'll wire it into the header.")
|
||||
})
|
||||
|
||||
it('appends a custom branch-name prompt when present', () => {
|
||||
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).toContain('Additional user prompt:')
|
||||
expect(prompt).toContain('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('Add a logout button')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { MARINE_CREATURES } from './marine-creatures'
|
||||
|
||||
// Why: the work-derived branch name stays short on purpose — long, sentence-like
|
||||
// branches read worse than the creature name they replace. Two-to-four words is
|
||||
// the sweet spot the feature targets.
|
||||
// Why: post-generation sanitization still bounds the leaf so a long model dump
|
||||
// cannot become an unreadable branch. The *prompt* stays general — users can
|
||||
// override naming style via Source Control AI instructions / templates.
|
||||
export const MAX_BRANCH_NAME_WORDS = 4
|
||||
const MIN_BRANCH_NAME_WORDS = 2
|
||||
|
||||
const CREATURE_NAMES_LOWER = new Set(MARINE_CREATURES.map((name) => name.toLowerCase()))
|
||||
|
||||
@@ -46,16 +45,15 @@ export function sanitizeBranchSlug(raw: string, maxWords = MAX_BRANCH_NAME_WORDS
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a leading prefix segment the generation model prepended despite the
|
||||
* "no prefixes" instruction — e.g. with a `tmchow/` branch prefix the model
|
||||
* emits `tmchow/worktree-spinner`, which `sanitizeBranchSlug` folds to
|
||||
* `tmchow-worktree-spinner`. Left alone, that leaks the prefix into both the
|
||||
* branch leaf (yielding a doubled `tmchow/tmchow-...`) and the humanized
|
||||
* display name. Strips only when the leading segment matches the *configured*
|
||||
* prefix, so a work-derived name that merely starts with a real word survives.
|
||||
* Prefix-only output (the model echoed just `tmchow`) yields an empty slug so
|
||||
* the caller skips the rename — otherwise it would double-prefix to
|
||||
* `tmchow/tmchow`.
|
||||
* Drop a leading prefix segment the generation model sometimes prepends (e.g.
|
||||
* with a `tmchow/` branch prefix it emits `tmchow/worktree-spinner`, which
|
||||
* `sanitizeBranchSlug` folds to `tmchow-worktree-spinner`). Left alone, that
|
||||
* leaks the prefix into both the branch leaf (yielding a doubled
|
||||
* `tmchow/tmchow-...`) and the humanized display name. Strips only when the
|
||||
* leading segment matches the *configured* prefix, so a work-derived name that
|
||||
* merely starts with a real word survives. Prefix-only output (the model
|
||||
* echoed just `tmchow`) yields an empty slug so the caller skips the rename —
|
||||
* otherwise it would double-prefix to `tmchow/tmchow`.
|
||||
*/
|
||||
export function stripConfiguredBranchPrefix(
|
||||
slug: string,
|
||||
@@ -100,30 +98,32 @@ export type BranchNameWorkContext = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the text-generation prompt that asks the configured agent to summarize
|
||||
* the work into a branch name. Kept in shared so the prompt is identical across
|
||||
* local and SSH generation targets.
|
||||
* Build the text-generation prompt that asks the configured agent to name the
|
||||
* work. Kept in shared so the prompt is identical across local and SSH targets.
|
||||
*
|
||||
* Why: the shipped default stays general. Naming style is left open so users
|
||||
* who override via Source Control AI instructions / the branch-name command
|
||||
* template are not fighting a rigid built-in rule list. Git-safety (kebab,
|
||||
* length) is enforced after generation by sanitizeBranchSlug.
|
||||
*/
|
||||
export function buildBranchNamePrompt(context: BranchNameWorkContext, customPrompt = ''): string {
|
||||
const sections = [
|
||||
'Generate a git branch name that summarizes the coding task described below.',
|
||||
'Rules:',
|
||||
`- Use between ${MIN_BRANCH_NAME_WORDS} and ${MAX_BRANCH_NAME_WORDS} words.`,
|
||||
'- Lowercase kebab-case only (words joined by single hyphens).',
|
||||
'- No slashes, no prefixes, no quotes, no trailing punctuation.',
|
||||
'- Describe the work itself, not the agent or the repository.',
|
||||
'- Output ONLY the branch name on a single line, nothing else.',
|
||||
'',
|
||||
'User request:',
|
||||
context.firstPrompt.trim()
|
||||
]
|
||||
const sections: string[] = []
|
||||
const prompt = customPrompt.trim()
|
||||
// Why: when the user supplies naming guidance, lead with it so their
|
||||
// override owns style rather than sitting under a prescriptive default.
|
||||
if (prompt) {
|
||||
sections.push(prompt, '')
|
||||
} else {
|
||||
sections.push(
|
||||
'Generate a short git branch name that summarizes the coding task described below.',
|
||||
'Output ONLY the branch name on a single line, nothing else.',
|
||||
''
|
||||
)
|
||||
}
|
||||
sections.push('User request:', context.firstPrompt.trim())
|
||||
const assistant = context.assistantMessage?.trim()
|
||||
if (assistant) {
|
||||
sections.push('', "Agent's initial response:", assistant)
|
||||
}
|
||||
const prompt = customPrompt.trim()
|
||||
if (prompt) {
|
||||
sections.push('', 'Additional user prompt:', prompt)
|
||||
}
|
||||
return sections.join('\n')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user