mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
* fix(naming): lead workspace and tab names with the work-item identifier Auto-generated workspace names, branch-rename display names, and tab titles now lead with the referenced PR/MR/issue/ticket — e.g. `PR 1033 - Review` instead of a paraphrase like "Review community pr 1094" that buried or dropped the number. Identifiers are the highest- signal, most searchable token, so leading with them makes the sidebar and tabs scannable and disambiguates same-verb work items. - New shared `work-item-reference.ts` extracts the identifier from the raw prompt: URLs are validated by path structure (owner/repo/pull/N, GitLab's `/-/` marker) so GitHub Enterprise / self-hosted GitLab still resolve while stray `/pull/<n>` paths (CDN, docs) do not; a ticket- prefix denylist keeps `SHA-256` / `UTF-8` / `ISO-8601` from being read as Jira/Linear keys. - Reconciles the existing create-from-work-item naming (was action-first `Review PR 1033`) with the first-work auto-rename onto one identifier- first format via a shared `formatIdentifierFirst`, so the two paths can't drift. - Fixes a pre-existing tab-title bug where markdown punctuation was stripped before URLs, splitting a GitLab `merge_requests` URL at its underscore and leaking "requests" into the title. * fix(naming): keep emphasis-wrapped URLs intact in generated names The URL/markdown strip reorder left the tab-title URL strip anchored on \b, which fails when a URL is wrapped in markdown emphasis (_...pull/5_) and leaked URL fragments into the title. Drop the \b anchor and trim trailing markdown emphasis (*_~) in the URL identifier parser, keeping interior underscores (merge_requests) intact. Adds regression tests. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import { humanizeBranchSlug } from './branch-name-from-work'
|
|
import { extractWorkIdentifier, formatIdentifierFirst } from './work-item-reference'
|
|
|
|
// Why: the git branch must stay lowercase kebab-case, but the sidebar display
|
|
// name is free-form. When the first prompt references a review target, that
|
|
// identifier is the highest-signal, most searchable token — so we lead with it
|
|
// (`PR 1033 - Review`) instead of a paraphrased slug like "Review community pr
|
|
// 1033". The identifier comes from the raw prompt (via work-item-reference),
|
|
// where the number still exists, not from the lossy branch slug.
|
|
|
|
// Type words that are never a useful action verb, skipped even if a slug leads
|
|
// with one (e.g. `pr-1094-review` → `Review`, not `Pr`).
|
|
const ACTION_STOPWORDS = new Set([
|
|
'pr',
|
|
'mr',
|
|
'pull',
|
|
'merge',
|
|
'request',
|
|
'requests',
|
|
'issue',
|
|
'issues',
|
|
'the',
|
|
'a',
|
|
'an',
|
|
'to',
|
|
'for',
|
|
'of'
|
|
])
|
|
|
|
// The first slug word that is neither a digit nor an identifier/type token,
|
|
// capitalized — the task verb ("Review", "Fix"). '' when none remains.
|
|
function leadingActionWord(slug: string, identifierTokens: string[]): string {
|
|
const skip = new Set([...identifierTokens, ...ACTION_STOPWORDS])
|
|
for (const word of slug.split('-').filter(Boolean)) {
|
|
if (/^\d+$/.test(word) || skip.has(word)) {
|
|
continue
|
|
}
|
|
return word.charAt(0).toUpperCase() + word.slice(1)
|
|
}
|
|
return ''
|
|
}
|
|
|
|
// A `-N` collision suffix (`fix-auth` → `fix-auth-2`) that resolveUniqueBranchName
|
|
// appended, so two worktrees on the same identifier stay distinguishable.
|
|
function collisionSuffixFromLeaf(
|
|
baseSlug: string,
|
|
resolvedLeaf: string | undefined
|
|
): number | null {
|
|
if (!resolvedLeaf || resolvedLeaf === baseSlug || !resolvedLeaf.startsWith(`${baseSlug}-`)) {
|
|
return null
|
|
}
|
|
const rest = resolvedLeaf.slice(baseSlug.length + 1)
|
|
return /^\d+$/.test(rest) ? Number(rest) : null
|
|
}
|
|
|
|
/**
|
|
* Build the sidebar display name for a freshly auto-renamed workspace. When the
|
|
* prompt names a review target, returns `<identifier> - <action>` (e.g.
|
|
* `PR 1033 - Review`); otherwise falls back to the plain humanized branch slug.
|
|
* `resolvedLeaf` is the final branch leaf (after any collision suffix); omit it
|
|
* for folder workspaces that have no git branch.
|
|
*/
|
|
export function deriveWorkspaceDisplayName(input: {
|
|
prompt: string
|
|
slug: string
|
|
resolvedLeaf?: string
|
|
}): string {
|
|
const identifier = extractWorkIdentifier(input.prompt)
|
|
if (!identifier) {
|
|
return humanizeBranchSlug(input.resolvedLeaf ?? input.slug)
|
|
}
|
|
const action = leadingActionWord(input.slug, identifier.tokens)
|
|
const base = formatIdentifierFirst(identifier.label, action)
|
|
const suffix = collisionSuffixFromLeaf(input.slug, input.resolvedLeaf)
|
|
return suffix ? `${base} (${suffix})` : base
|
|
}
|