---@diagnostic disable: duplicate-set-field, need-check-nil, param-type-mismatch, assign-type-mismatch local help = require 'bugabinga.pivi.help' local session = require 'bugabinga.pivi.session' local transcript = require 'bugabinga.pivi.transcript' local publish = require 'bugabinga.pivi.publish' local fake = dofile 'tests/helpers/pivi_fake.lua' describe( 'pivi help', function() after_each( function() fake.drain() session.stop_all() transcript.forget_all() publish.reset() fake.close_extra_windows() end ) it( 'runs without persisting a session', function() local channel = fake.new() help.ask( 'what is a quickfix list', { spawn = channel.spawn, } ) assert.is_true( vim.list_contains( channel.command, '--no-session' ) ) assert.is_true( vim.list_contains( channel.command, '--append-system-prompt' ) ) assert.is_false( vim.list_contains( channel.command, '--no-tools' ) ) end ) it( 'offers only read-only tools', function() local command = help.build_command {} local index = vim.fn.index( command, '--tools' ) + 1 assert.is_true( index > 0 ) local allowed = vim.split( command[index + 1], ',', { plain = true, } ) assert.is_true( vim.list_contains( allowed, 'nvim_help' ) ) assert.is_true( vim.list_contains( allowed, 'read' ) ) for _, forbidden in ipairs { 'write', 'edit', 'bash', 'powershell', } do assert.is_false( vim.list_contains( allowed, forbidden ) ) end end ) it( 'carries the pivi skill', function() local command = help.build_command {} local index = vim.fn.index( command, '--skill' ) + 1 assert.is_true( index > 0 ) assert.is_true( command[index + 1]:find( 'SKILL.md', 1, true ) ~= nil ) assert.is_true( command[index + 1]:find( 'pivi', 1, true ) ~= nil ) end ) it( 'attaches what the user currently sees to the question', function() vim.cmd 'enew' local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_buf_set_lines( bufnr, 0, -1, false, { 'a distinctive visible line', } ) local channel = fake.new() help.ask( 'why does this look wrong', { spawn = channel.spawn, } ) local sent = channel.commands[1].message assert.is_true( sent:find( '', 1, true ) ~= nil ) assert.is_true( sent:find( 'a distinctive visible line', 1, true ) ~= nil ) assert.is_true( sent:find( 'why does this look wrong', 1, true ) ~= nil ) end ) it( 'shows what it is doing in its own window', function() local channel = fake.new() local run = help.ask( 'a question', { spawn = channel.spawn, } ) local win = transcript.show( run.buffer ) transcript.set_state( run.buffer, help.buffer_name, 'thinking' ) assert.is_true( vim.wo[win].winbar:find( 'thinking', 1, true ) ~= nil ) channel.frame { type = 'tool_execution_start', toolCallId = 'c1', toolName = 'nvim_help', args = { tag = 'quickfix', }, } channel.flush() assert.is_true( vim.wo[win].winbar:find( 'quickfix', 1, true ) ~= nil ) assert.is_true( vim.list_contains( transcript.lines( run.buffer ), 'nvim_help quickfix' ) ) channel.frame { type = 'agent_settled', } channel.flush() assert.is_nil( vim.wo[win].winbar:find( 'quickfix', 1, true ) ) assert.is_true( vim.wo[win].winbar:find( help.buffer_name, 1, true ) ~= nil ) end ) it( 'drops the blank lines a model opens with', function() local channel = fake.new() local run = help.ask( 'a question', { spawn = channel.spawn, } ) channel.frame { type = 'message_update', assistantMessageEvent = { type = 'text_delta', contentIndex = 0, delta = '\n\nUse :cnext.', }, } channel.flush() assert.equal( 'Use :cnext.', run.answer ) end ) it( 'can ask without any view', function() local channel = fake.new() help.ask( 'plain question', { spawn = channel.spawn, view = false, } ) assert.equal( 'plain question', channel.commands[1].message ) end ) it( 'still loads the user\'s own pi configuration', function() local channel = fake.new() help.ask( 'anything', { spawn = channel.spawn, } ) -- Help isolates session state, never the user's providers, extensions, -- skills, prompts, or context files. for _, flag in ipairs { '--no-extensions', '--no-skills', '--no-prompt-templates', '--no-context-files', '--system-prompt', } do assert.is_false( vim.list_contains( channel.command, flag ) ) end end ) it( 'streams the answer into its own buffer', function() local channel = fake.new() local run = help.ask( 'how do I open the quickfix list', { spawn = channel.spawn, } ) channel.frame { type = 'message_update', assistantMessageEvent = { type = 'text_delta', contentIndex = 0, delta = 'use :copen', }, } assert.is_true( channel.settle( function() return run.answer == 'use :copen' end ) ) assert.is_true( vim.list_contains( transcript.lines( run.buffer ), 'use :copen' ) ) end ) it( 'ends its own process once the answer settles', function() local channel = fake.new() local finished local run = help.ask( 'anything', { spawn = channel.spawn, on_done = function( value ) finished = value end, } ) channel.frame { type = 'agent_settled', } assert.is_true( channel.settle( function() return finished ~= nil end ) ) assert.is_true( run.done ) -- The child is ended by closing its input, not by an immediate kill. assert.is_true( channel.stdin_closed ) end ) it( 'answers while a working session runs, leaving it untouched', function() local work = fake.new() local working = session.create { spawn = work.spawn, name = 'work', extension = false, skill = false, env = {}, } working.submit( 'long task', { capture = false, } ) work.frame { type = 'agent_start', } assert.is_true( work.settle( function() return working.status.run == 'running' end ) ) local transcript_before = vim.deepcopy( transcript.lines( working.buffer ) ) local commands_before = #work.commands local status_before = vim.deepcopy( working.status ) local channel = fake.new() local run = help.ask( 'what does :checktime do', { spawn = channel.spawn, } ) channel.frame { type = 'message_update', assistantMessageEvent = { type = 'text_delta', contentIndex = 0, delta = 'it reloads changed files', }, } assert.is_true( channel.settle( function() return run.answer ~= '' end ) ) assert.is_not.equal( run.buffer, working.buffer ) assert.same( transcript_before, transcript.lines( working.buffer ) ) assert.equal( commands_before, #work.commands ) assert.same( status_before.queue, working.status.queue ) assert.equal( status_before.run, working.status.run ) end ) it( 'reports its own failure without disturbing a working session', function() local work = fake.new() local working = session.create { spawn = work.spawn, name = 'work', extension = false, skill = false, env = {}, } local channel = fake.new() local run = help.ask( 'anything', { spawn = channel.spawn, } ) local id = channel.commands[1].id channel.frame { id = id, type = 'response', command = 'prompt', success = false, error = 'help unavailable', } assert.is_true( channel.settle( function() return run.done end ) ) assert.is_true( vim.list_contains( transcript.lines( run.buffer ), 'help unavailable' ) ) assert.equal( 'running', working.status.connection ) assert.is_nil( working.status.error ) end ) end )