From 9c5e74f4e22cdbcb1a50ec27db994666b66cde27 Mon Sep 17 00:00:00 2001 From: oberonix <362965+oberonix@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:45:03 -0700 Subject: [PATCH 1/3] feat(work): make description optional, default to branch name Initialize description from the branch name up front when the input is missing or whitespace-only. Downstream code (thread name, embed, persisted mapping) is unchanged and works without any conditional handling. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/commands/work.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/commands/work.ts b/src/commands/work.ts index a1fce3a..6b5d19f 100644 --- a/src/commands/work.ts +++ b/src/commands/work.ts @@ -14,14 +14,15 @@ export const work: Command = { ) .addStringOption(option => option.setName('description') - .setDescription('Description of the work') - .setRequired(true) + .setDescription('Description of the work (defaults to branch name)') + .setRequired(false) ) as SlashCommandBuilder, execute: async (interaction: any) => { const i = interaction as ChatInputCommandInteraction; const branchInput = i.options.getString('branch', true); - const description = i.options.getString('description', true); + const sanitizedBranch = worktreeManager.sanitizeBranchName(branchInput); + const description = i.options.getString('description')?.trim() || sanitizedBranch; const channel = i.channel; if (!channel) { @@ -54,8 +55,6 @@ export const work: Command = { return; } - const sanitizedBranch = worktreeManager.sanitizeBranchName(branchInput); - const existingMapping = dataStore.getWorktreeMappingByBranch(projectPath, sanitizedBranch); if (existingMapping) { await i.reply({ From 9a59daadbb68714f83c629da1a37883f693c0dbf Mon Sep 17 00:00:00 2001 From: oberonix <362965+oberonix@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:45:11 -0700 Subject: [PATCH 2/3] test(work): cover description fallback to branch name Tests the four meaningful inputs: omitted, whitespace-only, explicit distinct, and the 100-character thread-name truncation. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/__tests__/work.test.ts | 92 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/__tests__/work.test.ts diff --git a/src/__tests__/work.test.ts b/src/__tests__/work.test.ts new file mode 100644 index 0000000..c39bce2 --- /dev/null +++ b/src/__tests__/work.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ChannelType } from 'discord.js'; + +vi.mock('../services/dataStore.js'); +vi.mock('../services/worktreeManager.js'); + +import { work } from '../commands/work.js'; +import * as dataStore from '../services/dataStore.js'; +import * as worktreeManager from '../services/worktreeManager.js'; + +function makeInteraction(branch: string, description: string | null) { + const threadSend = vi.fn().mockResolvedValue(undefined); + const createdThread = { id: 'thread-1', send: threadSend }; + const threadsCreate = vi.fn().mockResolvedValue(createdThread); + + const channel = { + isThread: () => false, + type: ChannelType.GuildText, + threads: { create: threadsCreate }, + }; + + const optsMap = new Map([ + ['branch', branch], + ['description', description], + ]); + + return { + threadsCreate, + threadSend, + interaction: { + channelId: 'channel-1', + user: { id: 'user-1' }, + channel, + options: { + getString: (name: string, _required?: boolean) => optsMap.get(name) ?? null, + }, + reply: vi.fn().mockResolvedValue(undefined), + deferReply: vi.fn().mockResolvedValue(undefined), + editReply: vi.fn().mockResolvedValue(undefined), + } as any, + }; +} + +describe('/work — description fallback', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(worktreeManager.sanitizeBranchName).mockImplementation((b: string) => b); + vi.mocked(worktreeManager.createWorktree).mockResolvedValue('/tmp/worktree'); + vi.mocked(dataStore.getChannelProjectPath).mockReturnValue('/tmp/project'); + vi.mocked(dataStore.getWorktreeMappingByBranch).mockReturnValue(undefined); + vi.mocked(dataStore.setWorktreeMapping).mockReturnValue(undefined); + }); + + it('uses branch name as description when description is omitted', async () => { + const { interaction, threadsCreate } = makeInteraction('feat-foo', null); + + await work.execute(interaction); + + expect(threadsCreate.mock.calls[0][0].name).toBe('🌳 feat-foo: feat-foo'); + const mapping = vi.mocked(dataStore.setWorktreeMapping).mock.calls[0][0]; + expect(mapping.description).toBe('feat-foo'); + }); + + it('falls back to branch name when description is whitespace-only', async () => { + const { interaction, threadsCreate } = makeInteraction('feat-bar', ' '); + + await work.execute(interaction); + + expect(threadsCreate.mock.calls[0][0].name).toBe('🌳 feat-bar: feat-bar'); + const mapping = vi.mocked(dataStore.setWorktreeMapping).mock.calls[0][0]; + expect(mapping.description).toBe('feat-bar'); + }); + + it('uses provided description verbatim in the thread name', async () => { + const { interaction, threadsCreate } = makeInteraction('feat-baz', 'build the thing'); + + await work.execute(interaction); + + expect(threadsCreate.mock.calls[0][0].name).toBe('🌳 feat-baz: build the thing'); + const mapping = vi.mocked(dataStore.setWorktreeMapping).mock.calls[0][0]; + expect(mapping.description).toBe('build the thing'); + }); + + it('truncates the thread name to 100 characters', async () => { + const longDesc = 'x'.repeat(200); + const { interaction, threadsCreate } = makeInteraction('feat-long', longDesc); + + await work.execute(interaction); + + expect(threadsCreate.mock.calls[0][0].name.length).toBe(100); + }); +}); From fbc8cfd62d156801cf76821099107cefd05f3c32 Mon Sep 17 00:00:00 2001 From: oberonix <362965+oberonix@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:45:16 -0700 Subject: [PATCH 3/3] docs(work): note that description is now optional Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1545cac..2719809 100644 --- a/README.md +++ b/README.md @@ -243,12 +243,13 @@ Start isolated work on a new branch with its own worktree. ``` /work branch:feature/dark-mode description:Implement dark mode toggle +/work branch:feature/dark-mode # description defaults to the branch name ``` -| Parameter | Description | -| ------------- | ----------------------------------- | -| `branch` | Git branch name (will be sanitized) | -| `description` | Brief description of the work | +| Parameter | Description | +| ------------- | --------------------------------------------------- | +| `branch` | Git branch name (will be sanitized) | +| `description` | Optional. Defaults to the branch name when omitted. | **Features:**