Hide unfinished goal command entrypoints

The goal implementation is being kept in the tree for later redesign, but the CLI and desktop slash-command surfaces should not advertise or route new users into it while the long-running goal experience is incomplete. Existing transcript parsing and goal event rendering remain intact so historical sessions still load.

Constraint: Keep the implementation code available for future iteration while removing discoverable entrypoints.
Confidence: high
Scope-risk: narrow
Directive: Do not re-expose /goal until the persistent goal/runtime design is revisited.
Tested: bun test src/commands/headless.test.ts
Tested: cd desktop && bun run test -- composerUtils.test.ts pages.test.tsx
Tested: NODE_ENV=test ANTHROPIC_API_KEY=dummy bun -e getCommands assertion for hidden goal command
This commit is contained in:
程序员阿江(Relakkes) 2026-05-17 00:05:04 +08:00
parent 66fd78f0c5
commit 8ecc757729
5 changed files with 18 additions and 28 deletions

View File

@ -159,7 +159,7 @@ describe('Content-only pages render without errors', () => {
expect(screen.queryByText('/internal-only')).not.toBeInTheDocument()
})
it('EmptySession shows /goal as one command with argument hints, not pseudo subcommands', async () => {
it('EmptySession does not expose the paused /goal command', async () => {
vi.mocked(skillsApi.list).mockResolvedValueOnce({ skills: [] })
render(<EmptySession />)
@ -168,9 +168,12 @@ describe('Content-only pages render without errors', () => {
target: { value: '/goal', selectionStart: 5 },
})
expect(await screen.findAllByText('/goal')).toHaveLength(2)
expect(screen.getByText('[<condition> | clear]')).toBeInTheDocument()
expect(screen.getByText('Set a completion goal')).toBeInTheDocument()
await waitFor(() => {
expect(skillsApi.list).toHaveBeenCalled()
})
expect(screen.queryByText('/goal', { selector: 'span' })).not.toBeInTheDocument()
expect(screen.queryByText('[<condition> | clear]')).not.toBeInTheDocument()
expect(screen.queryByText('Set a completion goal')).not.toBeInTheDocument()
expect(screen.queryByText('/goal status')).not.toBeInTheDocument()
expect(screen.queryByText('/goal --tokens')).not.toBeInTheDocument()
})
@ -625,7 +628,7 @@ describe('Content-only pages render without errors', () => {
expect(screen.getByText('Slash commands')).toBeInTheDocument()
expect(screen.getByText('/clear')).toBeInTheDocument()
expect(screen.getByText('/cost')).toBeInTheDocument()
expect(screen.getByText('14 more commands available. Type / to search the full command list.')).toBeInTheDocument()
expect(screen.getByText('13 more commands available. Type / to search the full command list.')).toBeInTheDocument()
resetPageStores()
})

View File

@ -66,7 +66,7 @@ describe('composerUtils', () => {
expect(
mergeSlashCommands([
{
name: 'goal',
name: 'compact',
description: '',
argumentHint: '',
},
@ -74,24 +74,18 @@ describe('composerUtils', () => {
).toEqual(
expect.arrayContaining([
{
name: 'goal',
description: 'Set a completion goal',
argumentHint: '[<condition> | clear]',
name: 'compact',
description: 'Compact conversation context',
},
]),
)
})
it('keeps /goal as a single command with argument hints instead of pseudo subcommands', () => {
it('does not expose paused /goal fallback commands', () => {
const commands = filterSlashCommands(mergeSlashCommands([]), 'goal')
expect(
commands.map((command) => command.name),
).toEqual(['goal'])
expect(commands[0]).toMatchObject({
description: 'Set a completion goal',
argumentHint: '[<condition> | clear]',
})
expect(commands.map((command) => command.name)).toEqual([])
expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal')
expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal status')
expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal --tokens')
})

View File

@ -24,11 +24,6 @@ export const FALLBACK_SLASH_COMMANDS = [
...SETTINGS_SLASH_COMMANDS.map(({ name, description }) => ({ name, description })),
{ name: 'compact', description: 'Compact conversation context' },
{ name: 'clear', description: 'Clear conversation history' },
{
name: 'goal',
description: 'Set a completion goal',
argumentHint: '[<condition> | clear]',
},
{ name: 'review', description: 'Review code changes' },
{ name: 'commit', description: 'Create a git commit' },
{ name: 'pr', description: 'Create a pull request' },

View File

@ -6,7 +6,6 @@ import btw from './commands/btw/index.js'
import goodClaude from './commands/good-claude/index.js'
import issue from './commands/issue/index.js'
import feedback from './commands/feedback/index.js'
import goal from './commands/goal/index.js'
import clear from './commands/clear/index.js'
import color from './commands/color/index.js'
import commit from './commands/commit.js'
@ -304,7 +303,6 @@ const COMMANDS = memoize((): Command[] => [
tag,
theme,
feedback,
goal,
review,
ultrareview,
rewind,

View File

@ -3,13 +3,13 @@ import type { Command } from '../types/command.js'
import { filterCommandsForHeadlessMode } from './headless.js'
describe('filterCommandsForHeadlessMode', () => {
test('keeps /goal without exposing other local-jsx UI commands', () => {
test('keeps non-interactive commands without exposing local-jsx UI commands', () => {
const commands = [
{
type: 'local-jsx',
supportsNonInteractive: true,
name: 'goal',
description: 'Set a goal',
name: 'cost',
description: 'Show cost',
load: async () => ({ call: async () => null }),
},
{
@ -40,7 +40,7 @@ describe('filterCommandsForHeadlessMode', () => {
] satisfies Command[]
expect(filterCommandsForHeadlessMode(commands).map(command => command.name)).toEqual([
'goal',
'cost',
'review',
])
})