cc-haha/src/goals/goalState.test.ts
程序员阿江(Relakkes) 1e32942eab feat: enable autonomous goal loops from slash commands
Coding sessions need a native way to keep working until a stated completion condition is actually met. This adds a CLI /goal command, goal state and evaluator logic, and a headless-safe command filter so interactive and print-mode runs can both start a goal and continue when the evaluator says more work remains.

Constraint: The evaluator must work with saved third-party provider runtimes that may reject structured output schemas.
Constraint: Completion evidence must come from visible transcript text, not hidden goal prompts or assistant thinking blocks.
Rejected: Implement /goal as a bundled skill only | it would not own the query loop or support native continuation semantics.
Rejected: Expose all local-jsx commands to headless mode | interactive UI commands would become available in print mode.
Confidence: high
Scope-risk: moderate
Directive: Keep /goal evaluator evidence scoped to visible transcript text before adding richer completion signals.
Tested: bun test src/commands/headless.test.ts src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts
Tested: bun run check:server
Tested: bun run verify
Tested: Real provider /goal smoke with MiniMax-M2.7-highspeed, including a two-turn continuation that logged Goal continuation #1 and completed with GOAL_SMOKE_LOOP_DONE.
Not-tested: Durable cross-process goal persistence for resumed sessions.
2026-05-13 18:08:46 +08:00

81 lines
2.6 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import {
buildGoalContinuationPrompt,
clearThreadGoal,
formatGoalStatus,
getThreadGoal,
markThreadGoalComplete,
parseGoalCommand,
setThreadGoal,
updateThreadGoalStatus,
} from './goalState.js'
describe('goalState', () => {
test('parses token budget before the goal objective', () => {
const parsed = parseGoalCommand(
'--tokens 250K migrate auth to the new API until tests pass',
)
expect(parsed).toEqual({
type: 'set',
objective: 'migrate auth to the new API until tests pass',
tokenBudget: 250_000,
})
})
test('stores and formats the current thread goal', () => {
const goal = setThreadGoal('thread-a', {
objective: 'all provider tests pass',
tokenBudget: 10_000,
now: 1_000,
})
expect(goal.status).toBe('active')
expect(getThreadGoal('thread-a')?.objective).toBe('all provider tests pass')
expect(formatGoalStatus(goal, 61_000)).toContain('Goal: active')
expect(formatGoalStatus(goal, 61_000)).toContain('Budget: 0 / 10,000 tokens')
expect(formatGoalStatus(goal, 61_000)).toContain('Elapsed: 1m')
})
test('pause, resume, complete, and clear are scoped to the thread', () => {
setThreadGoal('thread-a', { objective: 'ship feature', now: 1_000 })
setThreadGoal('thread-b', { objective: 'different work', now: 1_000 })
expect(updateThreadGoalStatus('thread-a', 'paused', 2_000)?.status).toBe(
'paused',
)
expect(updateThreadGoalStatus('thread-a', 'active', 3_000)?.status).toBe(
'active',
)
expect(
markThreadGoalComplete('thread-a', {
reason: 'Done according to the transcript.',
now: 4_000,
})?.status,
).toBe('complete')
expect(formatGoalStatus(getThreadGoal('thread-a'), 4_000)).toContain(
'Latest reason: Done according to the transcript.',
)
expect(getThreadGoal('thread-b')?.status).toBe('active')
expect(clearThreadGoal('thread-a')).toBe(true)
expect(getThreadGoal('thread-a')).toBeNull()
})
test('builds a native-style continuation prompt', () => {
const goal = setThreadGoal('thread-c', {
objective: 'PR is ready and all tests pass',
now: 1_000,
})
expect(
buildGoalContinuationPrompt(goal, 'Tests have not been run yet.'),
).toContain('Continue working toward the active /goal')
expect(
buildGoalContinuationPrompt(goal, 'Tests have not been run yet.'),
).toContain('<objective>PR is ready and all tests pass</objective>')
expect(
buildGoalContinuationPrompt(goal, 'Tests have not been run yet.'),
).toContain('Tests have not been run yet.')
})
})