fix(worktree): make generated workspace names unique across repos (#2857)
Auto-generated workspace names walked the marine-creature list in order and deduped only within the active repo (the default when workspaces are nested), so the first auto-named worktree in every repo landed on "Nautilus". That guaranteed identical branch names across repos, which appear flat and indistinguishable in the sidebar. The in-order walk also produced "Nautilus-2", "Nautilus-3", ... within a single repo: the suggester ignores branches left behind by deleted worktrees, so it kept re-proposing "Nautilus", and the create-time retry loop suffixed it to dodge the lingering branch. Random selection now yields a fresh creature each time instead of marching the same name. - Dedup against worktrees in every repo, not just the active one - Pick randomly from the unused pool instead of the first list entry - Lowercase the result to match branch-name convention (fix/seahorse) - Expand the corpus 260 -> 552 (more marine species plus public-domain mythological sea creatures) so random picks rarely repeat getSuggestedCreatureName drops the now-unused repoId/nestWorkspaces params; the RNG is injectable for deterministic tests. Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
a538431140
commit
bf51593706
|
|
@ -0,0 +1,16 @@
|
|||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function readCreatureNames(path) {
|
||||
const source = readFileSync(path, 'utf8')
|
||||
return Array.from(source.matchAll(/^ '([^']+)',?$/gm), (match) => match[1])
|
||||
}
|
||||
|
||||
describe('marine creature corpus mirrors', () => {
|
||||
it('keeps the mobile mirror in parity with the shared corpus', () => {
|
||||
const sharedNames = readCreatureNames('src/shared/marine-creatures.ts')
|
||||
const mobileNames = readCreatureNames('mobile/src/constants/marine-creatures.ts')
|
||||
|
||||
expect(mobileNames).toEqual(sharedNames)
|
||||
})
|
||||
})
|
||||
|
|
@ -23,20 +23,29 @@ function normalize(name: string): string {
|
|||
return name.trim().toLowerCase()
|
||||
}
|
||||
|
||||
export function getSuggestedCreatureName(existingPaths: readonly string[]): string {
|
||||
function pickRandom<T>(items: readonly T[], random: () => number): T {
|
||||
return items[Math.floor(random() * items.length)]
|
||||
}
|
||||
|
||||
// Why: pick randomly from the unused pool (not the first in list order) so
|
||||
// fresh worktrees don't all default to "Nautilus" and collide across repos.
|
||||
export function getSuggestedCreatureName(
|
||||
existingPaths: readonly string[],
|
||||
random: () => number = Math.random
|
||||
): string {
|
||||
const used = new Set<string>()
|
||||
for (const p of existingPaths) {
|
||||
used.add(normalize(pathBasename(p)))
|
||||
}
|
||||
for (const candidate of MARINE_CREATURES) {
|
||||
if (!used.has(normalize(candidate))) return candidate
|
||||
}
|
||||
// Lowercased to match branch-name convention (fix/seahorse, not fix/Seahorse).
|
||||
const available = MARINE_CREATURES.map(normalize).filter((name) => !used.has(name))
|
||||
if (available.length > 0) return pickRandom(available, random)
|
||||
let suffix = 2
|
||||
while (true) {
|
||||
for (const candidate of MARINE_CREATURES) {
|
||||
const numbered = `${candidate}-${suffix}`
|
||||
if (!used.has(normalize(numbered))) return numbered
|
||||
}
|
||||
const numbered = MARINE_CREATURES.map((name) => `${normalize(name)}-${suffix}`).filter(
|
||||
(name) => !used.has(name)
|
||||
)
|
||||
if (numbered.length > 0) return pickRandom(numbered, random)
|
||||
suffix += 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-lines -- Curated marine-creature name corpus; one entry per line is the readable format for a flat data list. */
|
||||
export const MARINE_CREATURES = [
|
||||
'Nautilus',
|
||||
'Seahorse',
|
||||
|
|
@ -258,5 +259,299 @@ export const MARINE_CREATURES = [
|
|||
'Leviathan',
|
||||
'Wolffish',
|
||||
'Wreckfish',
|
||||
'Horseshoe'
|
||||
'Horseshoe',
|
||||
'Orca',
|
||||
'Cachalot',
|
||||
'Rorqual',
|
||||
'Grampus',
|
||||
'Humpback',
|
||||
'Bowhead',
|
||||
'Finback',
|
||||
'Sealion',
|
||||
'Pinniped',
|
||||
'Cetacean',
|
||||
'Mako',
|
||||
'Porbeagle',
|
||||
'Dogfish',
|
||||
'Spurdog',
|
||||
'Tope',
|
||||
'Bonnethead',
|
||||
'Sixgill',
|
||||
'Sevengill',
|
||||
'Angelshark',
|
||||
'Houndshark',
|
||||
'Megamouth',
|
||||
'Cookiecutter',
|
||||
'Bramble',
|
||||
'Ghostshark',
|
||||
'Sandtiger',
|
||||
'Galeocerdo',
|
||||
'Nursehound',
|
||||
'Cownose',
|
||||
'Devilray',
|
||||
'Eagleray',
|
||||
'Butterflyray',
|
||||
'Numbfish',
|
||||
'Stingaree',
|
||||
'Mantaray',
|
||||
'Tuna',
|
||||
'Albacore',
|
||||
'Bonito',
|
||||
'Skipjack',
|
||||
'Wahoo',
|
||||
'Kingfish',
|
||||
'Mackerel',
|
||||
'Cero',
|
||||
'Dorado',
|
||||
'Mahimahi',
|
||||
'Escolar',
|
||||
'Pomfret',
|
||||
'Butterfish',
|
||||
'Cod',
|
||||
'Haddock',
|
||||
'Pollock',
|
||||
'Whiting',
|
||||
'Hake',
|
||||
'Ling',
|
||||
'Cusk',
|
||||
'Burbot',
|
||||
'Saithe',
|
||||
'Sablefish',
|
||||
'Lingcod',
|
||||
'Greenling',
|
||||
'Rockling',
|
||||
'Halibut',
|
||||
'Flounder',
|
||||
'Sole',
|
||||
'Plaice',
|
||||
'Turbot',
|
||||
'Brill',
|
||||
'Dab',
|
||||
'Fluke',
|
||||
'Megrim',
|
||||
'Sanddab',
|
||||
'Herring',
|
||||
'Sardine',
|
||||
'Anchovy',
|
||||
'Sprat',
|
||||
'Pilchard',
|
||||
'Menhaden',
|
||||
'Shad',
|
||||
'Alewife',
|
||||
'Salmon',
|
||||
'Trout',
|
||||
'Char',
|
||||
'Grayling',
|
||||
'Steelhead',
|
||||
'Kokanee',
|
||||
'Chinook',
|
||||
'Coho',
|
||||
'Sockeye',
|
||||
'Taimen',
|
||||
'Huchen',
|
||||
'Cisco',
|
||||
'Inconnu',
|
||||
'Vendace',
|
||||
'Whitefish',
|
||||
'Perch',
|
||||
'Bass',
|
||||
'Snapper',
|
||||
'Grouper',
|
||||
'Seabass',
|
||||
'Tilefish',
|
||||
'Bream',
|
||||
'Porgy',
|
||||
'Sheepshead',
|
||||
'Pinfish',
|
||||
'Scup',
|
||||
'Tautog',
|
||||
'Sander',
|
||||
'Zander',
|
||||
'Walleye',
|
||||
'Sauger',
|
||||
'Ruffe',
|
||||
'Comber',
|
||||
'Hind',
|
||||
'Coney',
|
||||
'Graysby',
|
||||
'Margate',
|
||||
'Pompano',
|
||||
'Amberjack',
|
||||
'Yellowtail',
|
||||
'Scad',
|
||||
'Runner',
|
||||
'Mullet',
|
||||
'Goatfish',
|
||||
'Threadfin',
|
||||
'Snook',
|
||||
'Barramundi',
|
||||
'Milkfish',
|
||||
'Mojarra',
|
||||
'Weakfish',
|
||||
'Corbina',
|
||||
'Queenfish',
|
||||
'Kahawai',
|
||||
'Tilapia',
|
||||
'Cunner',
|
||||
'Tuskfish',
|
||||
'Razorfish',
|
||||
'Tang',
|
||||
'Porcupinefish',
|
||||
'Burrfish',
|
||||
'Eel',
|
||||
'Wolfeel',
|
||||
'Gardeneel',
|
||||
'Ribboneel',
|
||||
'Cuskeel',
|
||||
'Catfish',
|
||||
'Cory',
|
||||
'Ratfish',
|
||||
'Elephantfish',
|
||||
'Goosefish',
|
||||
'Monkfish',
|
||||
'Sargassum',
|
||||
'Coffinfish',
|
||||
'Seadevil',
|
||||
'Scorpionfish',
|
||||
'Rockfish',
|
||||
'Cabezon',
|
||||
'Rosefish',
|
||||
'Redfish',
|
||||
'Bocaccio',
|
||||
'Thornyhead',
|
||||
'Gurnard',
|
||||
'Searobin',
|
||||
'Fangtooth',
|
||||
'Bristlemouth',
|
||||
'Barreleye',
|
||||
'Spookfish',
|
||||
'Telescopefish',
|
||||
'Lancetfish',
|
||||
'Tripodfish',
|
||||
'Spiderfish',
|
||||
'Daggertooth',
|
||||
'Pearleye',
|
||||
'Ridgehead',
|
||||
'Snaggletooth',
|
||||
'Sturgeon',
|
||||
'Sterlet',
|
||||
'Kaluga',
|
||||
'Cockle',
|
||||
'Mussel',
|
||||
'Clam',
|
||||
'Oyster',
|
||||
'Scallop',
|
||||
'Abalone',
|
||||
'Periwinkle',
|
||||
'Cerith',
|
||||
'Turban',
|
||||
'Tellin',
|
||||
'Geoduck',
|
||||
'Quahog',
|
||||
'Piddock',
|
||||
'Shipworm',
|
||||
'Lobster',
|
||||
'Crayfish',
|
||||
'Shrimp',
|
||||
'Prawn',
|
||||
'Langostino',
|
||||
'Langouste',
|
||||
'Yabby',
|
||||
'Marron',
|
||||
'Dungeness',
|
||||
'Seafan',
|
||||
'Staghorn',
|
||||
'Elkhorn',
|
||||
'Hydroid',
|
||||
'Zoanthid',
|
||||
'Gorgonian',
|
||||
'Seanettle',
|
||||
'Moonjelly',
|
||||
'Sanddollar',
|
||||
'Brittlestar',
|
||||
'Basketstar',
|
||||
'Sunstar',
|
||||
'Seacucumber',
|
||||
'Seabiscuit',
|
||||
'Hearturchin',
|
||||
'Bristleworm',
|
||||
'Featherduster',
|
||||
'Lugworm',
|
||||
'Sandworm',
|
||||
'Palolo',
|
||||
'Arrowworm',
|
||||
'Acornworm',
|
||||
'Flatworm',
|
||||
'Ribbonworm',
|
||||
'Booby',
|
||||
'Frigatebird',
|
||||
'Tropicbird',
|
||||
'Noddy',
|
||||
'Kittiwake',
|
||||
'Dovekie',
|
||||
'Shag',
|
||||
'Anhinga',
|
||||
'Skimmer',
|
||||
'Jaeger',
|
||||
'Oystercatcher',
|
||||
'Eider',
|
||||
'Scoter',
|
||||
'Merganser',
|
||||
'Brant',
|
||||
'Ridley',
|
||||
'Flatback',
|
||||
'Terrapin',
|
||||
'Seasnake',
|
||||
'Diatom',
|
||||
'Plankton',
|
||||
'Larvacean',
|
||||
'Doliolid',
|
||||
'Kelp',
|
||||
'Seagrass',
|
||||
'Eelgrass',
|
||||
'Dulse',
|
||||
'Bladderwrack',
|
||||
'Mola',
|
||||
// Mythological sea & water creatures from public-domain folklore — joining
|
||||
// Kraken, Leviathan, Siren, Triton, Hydra, Medusa, Nereid, and Selkie above.
|
||||
'Scylla',
|
||||
'Charybdis',
|
||||
'Cetus',
|
||||
'Proteus',
|
||||
'Glaucus',
|
||||
'Hippocamp',
|
||||
'Jormungandr',
|
||||
'Hafgufa',
|
||||
'Kelpie',
|
||||
'Merrow',
|
||||
'Nuckelavee',
|
||||
'Afanc',
|
||||
'Rusalka',
|
||||
'Vodyanoy',
|
||||
'Umibozu',
|
||||
'Isonade',
|
||||
'Ningyo',
|
||||
'Mizuchi',
|
||||
'Naga',
|
||||
'Makara',
|
||||
'Bunyip',
|
||||
'Taniwha',
|
||||
'Marakihau',
|
||||
'Lusca',
|
||||
'Undine',
|
||||
'Nixie',
|
||||
'Melusine',
|
||||
'Ondine',
|
||||
'Tiamat',
|
||||
'Dagon',
|
||||
'Aspidochelone',
|
||||
'Capricorn',
|
||||
'Merfolk',
|
||||
'Merman',
|
||||
'Mermaid',
|
||||
'Timingila',
|
||||
'Bakekujira',
|
||||
'Jiaolong',
|
||||
'Encantado',
|
||||
'Hraesvelg'
|
||||
] as const
|
||||
|
|
|
|||
|
|
@ -7,97 +7,79 @@ import {
|
|||
shouldApplySuggestedName
|
||||
} from './worktree-name-suggestions'
|
||||
|
||||
// Always selects the first element of the unused pool, so assertions are exact.
|
||||
const pickFirst = () => 0
|
||||
// Suggestions are lowercased (branch-name convention), so expectations are too.
|
||||
const lower = (index: number) => MARINE_CREATURES[index].toLowerCase()
|
||||
|
||||
describe('getSuggestedCreatureName', () => {
|
||||
it('returns the first creature name when no repo is selected', () => {
|
||||
expect(getSuggestedCreatureName('', {}, false)).toBe(MARINE_CREATURES[0])
|
||||
it('picks the first unused name when the RNG selects index 0', () => {
|
||||
expect(getSuggestedCreatureName({}, pickFirst)).toBe(lower(0))
|
||||
})
|
||||
|
||||
it('skips names already used in the selected repo', () => {
|
||||
it('dedupes against worktrees in EVERY repo, not just one', () => {
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [{ path: '/tmp/worktrees/Nautilus' }, { path: '/tmp/worktrees/Seahorse' }]
|
||||
'repo-1': [{ path: '/tmp/worktrees/Nautilus' }],
|
||||
'repo-2': [{ path: '/tmp/worktrees/Seahorse' }]
|
||||
},
|
||||
true
|
||||
pickFirst
|
||||
)
|
||||
).toBe('Starfish')
|
||||
).toBe(lower(2))
|
||||
})
|
||||
|
||||
it('checks all repos when nestWorkspaces is false', () => {
|
||||
it('never reuses a name already used in another repo', () => {
|
||||
// Regression guard: the old per-repo scoping would have returned Nautilus
|
||||
// here because the active repo had no worktrees of its own.
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [],
|
||||
'repo-2': [{ path: '/tmp/worktrees/Nautilus' }]
|
||||
'repo-2': [{ path: `/tmp/worktrees/${MARINE_CREATURES[0]}` }]
|
||||
},
|
||||
false
|
||||
pickFirst
|
||||
)
|
||||
).toBe('Seahorse')
|
||||
).toBe(lower(1))
|
||||
})
|
||||
|
||||
it('only checks the selected repo when nestWorkspaces is true', () => {
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [],
|
||||
'repo-2': [{ path: '/tmp/worktrees/Nautilus' }]
|
||||
},
|
||||
true
|
||||
)
|
||||
).toBe('Nautilus')
|
||||
it('selects randomly from the unused pool', () => {
|
||||
// random() = i/N ⇒ pickRandom returns the pool's i-th entry.
|
||||
const pickIndex = (index: number, poolSize: number) => () => index / poolSize
|
||||
expect(getSuggestedCreatureName({}, pickIndex(2, MARINE_CREATURES.length))).toBe(lower(2))
|
||||
expect(getSuggestedCreatureName({}, pickIndex(5, MARINE_CREATURES.length))).toBe(lower(5))
|
||||
})
|
||||
|
||||
it('falls back to suffixed variants after the base list is exhausted', () => {
|
||||
const usedWorktrees = MARINE_CREATURES.map((name) => ({ path: `/tmp/worktrees/${name}` }))
|
||||
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': usedWorktrees
|
||||
},
|
||||
true
|
||||
)
|
||||
).toBe(`${MARINE_CREATURES[0]}-2`)
|
||||
expect(getSuggestedCreatureName({ 'repo-1': usedWorktrees }, pickFirst)).toBe(`${lower(0)}-2`)
|
||||
})
|
||||
|
||||
it('treats used names case-insensitively', () => {
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [{ path: '/tmp/worktrees/nAuTiLuS' }]
|
||||
},
|
||||
true
|
||||
)
|
||||
).toBe('Seahorse')
|
||||
getSuggestedCreatureName({ 'repo-1': [{ path: '/tmp/worktrees/nAuTiLuS' }] }, pickFirst)
|
||||
).toBe(lower(1))
|
||||
})
|
||||
|
||||
it('handles Windows-style worktree paths when deriving used basenames', () => {
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [{ path: 'C:\\worktrees\\Nautilus' }]
|
||||
},
|
||||
true
|
||||
)
|
||||
).toBe('Seahorse')
|
||||
getSuggestedCreatureName({ 'repo-1': [{ path: 'C:\\worktrees\\Nautilus' }] }, pickFirst)
|
||||
).toBe(lower(1))
|
||||
})
|
||||
|
||||
it('handles stored worktree paths with trailing separators', () => {
|
||||
expect(
|
||||
getSuggestedCreatureName(
|
||||
'repo-1',
|
||||
{
|
||||
'repo-1': [{ path: 'C:\\worktrees\\Nautilus\\\\' }, { path: '/tmp/worktrees/Seahorse///' }]
|
||||
'repo-1': [
|
||||
{ path: 'C:\\worktrees\\Nautilus\\\\' },
|
||||
{ path: '/tmp/worktrees/Seahorse///' }
|
||||
]
|
||||
},
|
||||
true
|
||||
pickFirst
|
||||
)
|
||||
).toBe('Starfish')
|
||||
).toBe(lower(2))
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -118,7 +100,7 @@ describe('shouldApplySuggestedName', () => {
|
|||
|
||||
describe('MARINE_CREATURES', () => {
|
||||
it('is non-empty and unique after normalization and sanitization', () => {
|
||||
expect(MARINE_CREATURES.length).toBeGreaterThanOrEqual(260)
|
||||
expect(MARINE_CREATURES.length).toBeGreaterThanOrEqual(500)
|
||||
|
||||
const normalizedNames = MARINE_CREATURES.map(normalizeSuggestedName)
|
||||
const sanitizedNames = MARINE_CREATURES.map((name) => sanitizeWorktreeName(name))
|
||||
|
|
@ -136,7 +118,13 @@ describe('MARINE_CREATURES', () => {
|
|||
'Hogchoker',
|
||||
'Hogsucker',
|
||||
'Mudsucker',
|
||||
'Hardhead'
|
||||
'Hardhead',
|
||||
// Real marine organisms, but the bare word reads as a fruit, flower, or
|
||||
// land insect rather than something from the sea.
|
||||
'Olive',
|
||||
'Tulip',
|
||||
'Cone',
|
||||
'Mantis'
|
||||
]
|
||||
|
||||
for (const disallowedName of disallowedNames) {
|
||||
|
|
|
|||
|
|
@ -5,39 +5,46 @@ type WorktreePathLike = {
|
|||
path: string
|
||||
}
|
||||
|
||||
export function getSuggestedCreatureName(
|
||||
repoId: string,
|
||||
worktreesByRepo: Record<string, WorktreePathLike[]>,
|
||||
nestWorkspaces: boolean
|
||||
): string {
|
||||
if (!repoId) {
|
||||
return MARINE_CREATURES[0]
|
||||
}
|
||||
|
||||
// Why: dedup across every repo, not just the active one — branch names appear
|
||||
// flat in the sidebar, so per-repo scoping let two repos collide on one name.
|
||||
function collectUsedNames(worktreesByRepo: Record<string, WorktreePathLike[]>): Set<string> {
|
||||
const usedNames = new Set<string>()
|
||||
const relevantWorktrees = nestWorkspaces
|
||||
? [worktreesByRepo[repoId] ?? []]
|
||||
: Object.values(worktreesByRepo)
|
||||
|
||||
for (const worktrees of relevantWorktrees) {
|
||||
for (const worktrees of Object.values(worktreesByRepo)) {
|
||||
for (const worktree of worktrees) {
|
||||
usedNames.add(normalizeSuggestedName(basename(worktree.path)))
|
||||
}
|
||||
}
|
||||
return usedNames
|
||||
}
|
||||
|
||||
for (const candidate of MARINE_CREATURES) {
|
||||
if (!usedNames.has(normalizeSuggestedName(candidate))) {
|
||||
return candidate
|
||||
}
|
||||
function pickRandom<T>(items: readonly T[], random: () => number): T {
|
||||
return items[Math.floor(random() * items.length)]
|
||||
}
|
||||
|
||||
export function getSuggestedCreatureName(
|
||||
worktreesByRepo: Record<string, WorktreePathLike[]>,
|
||||
random: () => number = Math.random
|
||||
): string {
|
||||
const usedNames = collectUsedNames(worktreesByRepo)
|
||||
|
||||
// Why: names are lowercased (branch names are conventionally lowercase, e.g.
|
||||
// fix/seahorse), and a random pick keeps fresh worktrees from all starting at
|
||||
// the same creature and marching down the list in lockstep.
|
||||
const available = MARINE_CREATURES.map(normalizeSuggestedName).filter(
|
||||
(name) => !usedNames.has(name)
|
||||
)
|
||||
if (available.length > 0) {
|
||||
return pickRandom(available, random)
|
||||
}
|
||||
|
||||
// Every base name is taken — fall back to numbered variants.
|
||||
let suffix = 2
|
||||
while (true) {
|
||||
for (const candidate of MARINE_CREATURES) {
|
||||
const numberedCandidate = `${candidate}-${suffix}`
|
||||
if (!usedNames.has(normalizeSuggestedName(numberedCandidate))) {
|
||||
return numberedCandidate
|
||||
}
|
||||
const numbered = MARINE_CREATURES.map(
|
||||
(name) => `${normalizeSuggestedName(name)}-${suffix}`
|
||||
).filter((name) => !usedNames.has(name))
|
||||
if (numbered.length > 0) {
|
||||
return pickRandom(numbered, random)
|
||||
}
|
||||
suffix += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -654,12 +654,13 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
const shouldWaitForSetupCheck = Boolean(selectedRepo) && selectedRepoIsGit && isSetupCheckPending
|
||||
|
||||
// Why: when the user leaves the workspace name blank and provides no other
|
||||
// seed source (prompt, linked issue/PR), pick a repo-scoped unique marine
|
||||
// seed source (prompt, linked issue/PR), pick a globally-unique marine
|
||||
// creature name so the workspace gets a distinct, readable identifier
|
||||
// instead of colliding on a literal "workspace" default.
|
||||
// instead of colliding on a literal "workspace" default — or on the same
|
||||
// creature already used in another repo.
|
||||
const fallbackCreatureName = useMemo(
|
||||
() => getSuggestedCreatureName(repoId, worktreesByRepo, settings?.nestWorkspaces ?? true),
|
||||
[repoId, worktreesByRepo, settings?.nestWorkspaces]
|
||||
() => getSuggestedCreatureName(worktreesByRepo),
|
||||
[worktreesByRepo]
|
||||
)
|
||||
const workspaceSeedName = useMemo(
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-lines -- Curated marine-creature name corpus; one entry per line is the readable format for a flat data list. */
|
||||
// Why: the auto-generated workspace name pool lives in shared (not renderer)
|
||||
// so the main process can recognize an Orca-generated branch name when deciding
|
||||
// whether auto-rename-from-work is allowed to overwrite it.
|
||||
|
|
@ -261,5 +262,299 @@ export const MARINE_CREATURES = [
|
|||
'Leviathan',
|
||||
'Wolffish',
|
||||
'Wreckfish',
|
||||
'Horseshoe'
|
||||
'Horseshoe',
|
||||
'Orca',
|
||||
'Cachalot',
|
||||
'Rorqual',
|
||||
'Grampus',
|
||||
'Humpback',
|
||||
'Bowhead',
|
||||
'Finback',
|
||||
'Sealion',
|
||||
'Pinniped',
|
||||
'Cetacean',
|
||||
'Mako',
|
||||
'Porbeagle',
|
||||
'Dogfish',
|
||||
'Spurdog',
|
||||
'Tope',
|
||||
'Bonnethead',
|
||||
'Sixgill',
|
||||
'Sevengill',
|
||||
'Angelshark',
|
||||
'Houndshark',
|
||||
'Megamouth',
|
||||
'Cookiecutter',
|
||||
'Bramble',
|
||||
'Ghostshark',
|
||||
'Sandtiger',
|
||||
'Galeocerdo',
|
||||
'Nursehound',
|
||||
'Cownose',
|
||||
'Devilray',
|
||||
'Eagleray',
|
||||
'Butterflyray',
|
||||
'Numbfish',
|
||||
'Stingaree',
|
||||
'Mantaray',
|
||||
'Tuna',
|
||||
'Albacore',
|
||||
'Bonito',
|
||||
'Skipjack',
|
||||
'Wahoo',
|
||||
'Kingfish',
|
||||
'Mackerel',
|
||||
'Cero',
|
||||
'Dorado',
|
||||
'Mahimahi',
|
||||
'Escolar',
|
||||
'Pomfret',
|
||||
'Butterfish',
|
||||
'Cod',
|
||||
'Haddock',
|
||||
'Pollock',
|
||||
'Whiting',
|
||||
'Hake',
|
||||
'Ling',
|
||||
'Cusk',
|
||||
'Burbot',
|
||||
'Saithe',
|
||||
'Sablefish',
|
||||
'Lingcod',
|
||||
'Greenling',
|
||||
'Rockling',
|
||||
'Halibut',
|
||||
'Flounder',
|
||||
'Sole',
|
||||
'Plaice',
|
||||
'Turbot',
|
||||
'Brill',
|
||||
'Dab',
|
||||
'Fluke',
|
||||
'Megrim',
|
||||
'Sanddab',
|
||||
'Herring',
|
||||
'Sardine',
|
||||
'Anchovy',
|
||||
'Sprat',
|
||||
'Pilchard',
|
||||
'Menhaden',
|
||||
'Shad',
|
||||
'Alewife',
|
||||
'Salmon',
|
||||
'Trout',
|
||||
'Char',
|
||||
'Grayling',
|
||||
'Steelhead',
|
||||
'Kokanee',
|
||||
'Chinook',
|
||||
'Coho',
|
||||
'Sockeye',
|
||||
'Taimen',
|
||||
'Huchen',
|
||||
'Cisco',
|
||||
'Inconnu',
|
||||
'Vendace',
|
||||
'Whitefish',
|
||||
'Perch',
|
||||
'Bass',
|
||||
'Snapper',
|
||||
'Grouper',
|
||||
'Seabass',
|
||||
'Tilefish',
|
||||
'Bream',
|
||||
'Porgy',
|
||||
'Sheepshead',
|
||||
'Pinfish',
|
||||
'Scup',
|
||||
'Tautog',
|
||||
'Sander',
|
||||
'Zander',
|
||||
'Walleye',
|
||||
'Sauger',
|
||||
'Ruffe',
|
||||
'Comber',
|
||||
'Hind',
|
||||
'Coney',
|
||||
'Graysby',
|
||||
'Margate',
|
||||
'Pompano',
|
||||
'Amberjack',
|
||||
'Yellowtail',
|
||||
'Scad',
|
||||
'Runner',
|
||||
'Mullet',
|
||||
'Goatfish',
|
||||
'Threadfin',
|
||||
'Snook',
|
||||
'Barramundi',
|
||||
'Milkfish',
|
||||
'Mojarra',
|
||||
'Weakfish',
|
||||
'Corbina',
|
||||
'Queenfish',
|
||||
'Kahawai',
|
||||
'Tilapia',
|
||||
'Cunner',
|
||||
'Tuskfish',
|
||||
'Razorfish',
|
||||
'Tang',
|
||||
'Porcupinefish',
|
||||
'Burrfish',
|
||||
'Eel',
|
||||
'Wolfeel',
|
||||
'Gardeneel',
|
||||
'Ribboneel',
|
||||
'Cuskeel',
|
||||
'Catfish',
|
||||
'Cory',
|
||||
'Ratfish',
|
||||
'Elephantfish',
|
||||
'Goosefish',
|
||||
'Monkfish',
|
||||
'Sargassum',
|
||||
'Coffinfish',
|
||||
'Seadevil',
|
||||
'Scorpionfish',
|
||||
'Rockfish',
|
||||
'Cabezon',
|
||||
'Rosefish',
|
||||
'Redfish',
|
||||
'Bocaccio',
|
||||
'Thornyhead',
|
||||
'Gurnard',
|
||||
'Searobin',
|
||||
'Fangtooth',
|
||||
'Bristlemouth',
|
||||
'Barreleye',
|
||||
'Spookfish',
|
||||
'Telescopefish',
|
||||
'Lancetfish',
|
||||
'Tripodfish',
|
||||
'Spiderfish',
|
||||
'Daggertooth',
|
||||
'Pearleye',
|
||||
'Ridgehead',
|
||||
'Snaggletooth',
|
||||
'Sturgeon',
|
||||
'Sterlet',
|
||||
'Kaluga',
|
||||
'Cockle',
|
||||
'Mussel',
|
||||
'Clam',
|
||||
'Oyster',
|
||||
'Scallop',
|
||||
'Abalone',
|
||||
'Periwinkle',
|
||||
'Cerith',
|
||||
'Turban',
|
||||
'Tellin',
|
||||
'Geoduck',
|
||||
'Quahog',
|
||||
'Piddock',
|
||||
'Shipworm',
|
||||
'Lobster',
|
||||
'Crayfish',
|
||||
'Shrimp',
|
||||
'Prawn',
|
||||
'Langostino',
|
||||
'Langouste',
|
||||
'Yabby',
|
||||
'Marron',
|
||||
'Dungeness',
|
||||
'Seafan',
|
||||
'Staghorn',
|
||||
'Elkhorn',
|
||||
'Hydroid',
|
||||
'Zoanthid',
|
||||
'Gorgonian',
|
||||
'Seanettle',
|
||||
'Moonjelly',
|
||||
'Sanddollar',
|
||||
'Brittlestar',
|
||||
'Basketstar',
|
||||
'Sunstar',
|
||||
'Seacucumber',
|
||||
'Seabiscuit',
|
||||
'Hearturchin',
|
||||
'Bristleworm',
|
||||
'Featherduster',
|
||||
'Lugworm',
|
||||
'Sandworm',
|
||||
'Palolo',
|
||||
'Arrowworm',
|
||||
'Acornworm',
|
||||
'Flatworm',
|
||||
'Ribbonworm',
|
||||
'Booby',
|
||||
'Frigatebird',
|
||||
'Tropicbird',
|
||||
'Noddy',
|
||||
'Kittiwake',
|
||||
'Dovekie',
|
||||
'Shag',
|
||||
'Anhinga',
|
||||
'Skimmer',
|
||||
'Jaeger',
|
||||
'Oystercatcher',
|
||||
'Eider',
|
||||
'Scoter',
|
||||
'Merganser',
|
||||
'Brant',
|
||||
'Ridley',
|
||||
'Flatback',
|
||||
'Terrapin',
|
||||
'Seasnake',
|
||||
'Diatom',
|
||||
'Plankton',
|
||||
'Larvacean',
|
||||
'Doliolid',
|
||||
'Kelp',
|
||||
'Seagrass',
|
||||
'Eelgrass',
|
||||
'Dulse',
|
||||
'Bladderwrack',
|
||||
'Mola',
|
||||
// Mythological sea & water creatures from public-domain folklore — joining
|
||||
// Kraken, Leviathan, Siren, Triton, Hydra, Medusa, Nereid, and Selkie above.
|
||||
'Scylla',
|
||||
'Charybdis',
|
||||
'Cetus',
|
||||
'Proteus',
|
||||
'Glaucus',
|
||||
'Hippocamp',
|
||||
'Jormungandr',
|
||||
'Hafgufa',
|
||||
'Kelpie',
|
||||
'Merrow',
|
||||
'Nuckelavee',
|
||||
'Afanc',
|
||||
'Rusalka',
|
||||
'Vodyanoy',
|
||||
'Umibozu',
|
||||
'Isonade',
|
||||
'Ningyo',
|
||||
'Mizuchi',
|
||||
'Naga',
|
||||
'Makara',
|
||||
'Bunyip',
|
||||
'Taniwha',
|
||||
'Marakihau',
|
||||
'Lusca',
|
||||
'Undine',
|
||||
'Nixie',
|
||||
'Melusine',
|
||||
'Ondine',
|
||||
'Tiamat',
|
||||
'Dagon',
|
||||
'Aspidochelone',
|
||||
'Capricorn',
|
||||
'Merfolk',
|
||||
'Merman',
|
||||
'Mermaid',
|
||||
'Timingila',
|
||||
'Bakekujira',
|
||||
'Jiaolong',
|
||||
'Encantado',
|
||||
'Hraesvelg'
|
||||
] as const
|
||||
|
|
|
|||
Loading…
Reference in New Issue