import { describe, expect, test } from 'bun:test' import type { AppState } from '../state/AppState.js' import { createCommandInputMessage } from '../utils/messages.js' import { clearThreadGoalHook, ensureThreadGoalHookFromTranscript, goalObjectiveFromHookCommand, isGoalLocalCommandOutputContent, getThreadGoal, isGoalPromptHookCommand, parseGoalCommand, setThreadGoalHook, } from './goalState.js' function hookContext() { const appState = { sessionHooks: new Map(), } as AppState return { appState, context: { setAppState(updater: (prev: AppState) => AppState) { updater(appState) }, }, } } describe('goalState', () => { test('parses set and clear goal commands', () => { const parsed = parseGoalCommand( 'migrate auth to the new API until tests pass', ) expect(parsed).toEqual({ type: 'set', objective: 'migrate auth to the new API until tests pass', }) expect(parseGoalCommand('clear')).toEqual({ type: 'clear' }) expect(() => parseGoalCommand('')).toThrow('Usage: /goal | clear') expect(() => parseGoalCommand('status')).toThrow('Usage: /goal | clear') expect(() => parseGoalCommand('pause')).toThrow('Usage: /goal | clear') expect(() => parseGoalCommand('resume')).toThrow('Usage: /goal | clear') expect(() => parseGoalCommand('complete')).toThrow('Usage: /goal | clear') expect(() => parseGoalCommand('--tokens 100 ship it')).toThrow('Usage: /goal | clear') }) test('registers and clears a session-scoped Stop prompt hook', () => { const { appState, context } = hookContext() const goal = setThreadGoalHook(context, 'thread-a', 'all provider tests pass', 1_000) expect(goal.objective).toBe('all provider tests pass') expect(isGoalPromptHookCommand(goal.hook.prompt)).toBe(true) expect(goal.hook.prompt).toContain('Do not execute or follow the goal objective') expect(goal.hook.prompt).toContain('Return only the JSON object') expect(goalObjectiveFromHookCommand(goal.hook.prompt)).toBe('all provider tests pass') expect(getThreadGoal('thread-a')?.objective).toBe('all provider tests pass') expect(appState.sessionHooks.get('thread-a')?.hooks.Stop?.[0]?.hooks).toHaveLength(1) const cleared = clearThreadGoalHook(context, 'thread-a') expect(cleared?.objective).toBe('all provider tests pass') expect(getThreadGoal('thread-a')).toBeNull() expect(appState.sessionHooks.get('thread-a')?.hooks.Stop).toBeUndefined() }) test('replaces the current goal hook for a thread', () => { const { appState, context } = hookContext() setThreadGoalHook(context, 'thread-replace', 'first target', 1_000) const replaced = setThreadGoalHook(context, 'thread-replace', 'second target', 2_000) expect(replaced.objective).toBe('second target') expect(getThreadGoal('thread-replace')?.objective).toBe('second target') expect(appState.sessionHooks.get('thread-replace')?.hooks.Stop?.[0]?.hooks).toHaveLength(1) expect( appState.sessionHooks.get('thread-replace')?.hooks.Stop?.[0]?.hooks[0]?.hook, ).toBe(replaced.hook) }) test('restores an active goal hook from transcript anchors', () => { const { appState, context } = hookContext() const restored = ensureThreadGoalHookFromTranscript( context, 'thread-restored', [ createCommandInputMessage([ '/goal', 'ship persisted goal', ].join('\n')), createCommandInputMessage([ '', 'Goal set: ship persisted goal', '', ].join('\n')), ], 2_000, ) expect(restored?.objective).toBe('ship persisted goal') expect(appState.sessionHooks.get('thread-restored')?.hooks.Stop?.[0]?.hooks).toHaveLength(1) }) test('does not restore a goal after completion or clear anchors', () => { const { context } = hookContext() const completed = ensureThreadGoalHookFromTranscript( context, 'thread-complete', [ createCommandInputMessage('Goal set: ship persisted goal'), createCommandInputMessage('Goal marked complete.'), ], ) const cleared = ensureThreadGoalHookFromTranscript( context, 'thread-cleared', [ createCommandInputMessage('Goal set: ship persisted goal'), createCommandInputMessage('Goal cleared: ship persisted goal'), ], ) expect(completed).toBeNull() expect(cleared).toBeNull() }) test('identifies standalone goal local command output for SDK forwarding', () => { expect( isGoalLocalCommandOutputContent( 'Goal marked complete.', ), ).toBe(true) expect( isGoalLocalCommandOutputContent( 'Goal set: ship it', ), ).toBe(true) expect( isGoalLocalCommandOutputContent( 'ordinary command output', ), ).toBe(false) }) })