Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ jobs:
with:
args: lua tests

- name: Install LuaLS
env:
LUALS_VERSION: 3.18.1
run: |
curl -fsSL -o /tmp/lua-language-server.tar.gz "https://github.com/LuaLS/lua-language-server/releases/download/${LUALS_VERSION}/lua-language-server-${LUALS_VERSION}-linux-x64.tar.gz"
mkdir -p /tmp/lua-language-server
tar -C /tmp/lua-language-server -xzf /tmp/lua-language-server.tar.gz
echo "/tmp/lua-language-server/bin" >> "$GITHUB_PATH"

- name: Run LuaLS diagnostics
run: lua-language-server --check=. --checklevel=Warning --check_format=pretty --configpath=.luarc.ci.json

test:
name: Test
runs-on: ubuntu-latest
Expand Down
16 changes: 16 additions & 0 deletions .luarc.ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "LuaJIT",
"diagnostics.globals": [
"vim"
],
"workspace.library": [
"${workspaceFolder}/lua",
"${3rd}/luv/library"
],
"workspace.ignoreDir": [
".deps",
".git",
"tests"
]
}
20 changes: 17 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
- Formatting is defined by `.stylua.toml`:
- tabs, width 2, max column 100, Unix line endings.
- Linting is defined by `.luacheckrc`.
- LuaLS diagnostics are defined by `.luarc.json` for editor use and `.luarc.ci.json` for CI. The
CI check runs at warning level and must fail on poor types, nil-flow issues, and annotation
mismatches in source files.
- Module/file names use lowercase grouped paths, for example `runtime/session.lua`.
- Prefer small modules with explicit `require(...)` boundaries.
- Avoid compatibility wrappers, alias modules, and speculative abstractions.
Expand All @@ -84,14 +87,25 @@
runtime paths.
- `stylua --check lua tests`: formatting check.
- `luacheck lua tests`: lint Lua source and specs.
- `lua-language-server --check=. --checklevel=Warning --check_format=pretty --configpath=.luarc.ci.json`:
fail on LuaLS source diagnostics.
- Optional direct test invocation:
- `nvim --headless -u tests/minimal_init.lua -i NONE -c "PlenaryBustedDirectory tests/spec { minimal_init = 'tests/minimal_init.lua' }" -c "qa"`

## Commit And Pull Request Guidelines

- Follow Angular-style conventional commits seen in history:
- `feat(config): ...`, `refactor(lua): ...`, `test(nvim): ...`, `docs(readme): ...`,
`chore(lua): ...`.
- Branch names must be scoped by intent, for example `feature/...`, `fix/...`, `refactor/...`,
`docs/...`, `test/...`, or `ci/...`.
- Follow Angular-style conventional commits.
- Commit scopes are optional. When used, scopes must name the project area being changed, not the
implementation language. Prefer scopes such as `config`, `runtime`, `context`, `ui`, `docs`, or
`ci`; avoid broad scopes such as `lua`.
- Examples:
- `feat(config): add agent preset`
- `refactor(runtime): flatten session flow`
- `test(config): cover invalid agent setup`
- `docs(readme): simplify agent summary`
- `ci(luals): add source diagnostics gate`
- Keep commits atomic and scoped to one logical change.
- PRs should include:
- concise summary,
Expand Down
18 changes: 16 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Prerequisites:
- [`nvim-lua/plenary.nvim`](https://github.com/nvim-lua/plenary.nvim)
- `stylua`
- `luacheck`
- `lua-language-server`
- Any terminal-agent commands needed for manual testing

If Plenary is not installed in a standard runtime path, set `PLENARY_PATH` when running tests.
Expand Down Expand Up @@ -82,13 +83,18 @@ Formatting and linting:
```sh
stylua --check lua tests
luacheck lua tests
lua-language-server --check=. --checklevel=Warning --check_format=pretty --configpath=.luarc.ci.json
```

LuaLS diagnostics run at warning level and are treated as a quality gate. The CI config checks
source files and fails on poor annotations, unresolved nil handling, and type mismatches.

Before opening a PR, run:

```sh
stylua --check lua tests
luacheck lua tests
lua-language-server --check=. --checklevel=Warning --check_format=pretty --configpath=.luarc.ci.json
./run_tests.sh
```

Expand Down Expand Up @@ -125,9 +131,17 @@ PRs should include:
- test evidence with command and result,
- screenshots or gifs for UI-visible float or panel changes.

Use conventional commit style for commits where practical, for example:
Branch names should be scoped by intent, for example `feature/...`, `fix/...`, `refactor/...`,
`docs/...`, `test/...`, or `ci/...`.

Use Angular-style conventional commits. Commit scopes are optional; when used, they should name the
project area being changed rather than the implementation language. Prefer scopes such as `config`,
`runtime`, `context`, `ui`, `docs`, or `ci`; avoid broad scopes such as `lua`.

Examples:

- `feat(config): add agent preset`
- `fix(context): preserve source buffer context`
- `refactor(runtime): flatten session flow`
- `test(nvim): cover panel focus behavior`
- `docs(readme): simplify agent summary`
- `ci(luals): add source diagnostics gate`
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Supported enum values are available at `require("agent_term.enums").agent`.
</tr>
<tr>
<td>Run checks locally</td>
<td><code>./run_tests.sh</code>, <code>stylua --check lua tests</code>, <code>luacheck lua tests</code></td>
<td><code>./run_tests.sh</code>, <code>stylua --check lua tests</code>, <code>luacheck lua tests</code>, <code>lua-language-server --check=. --checklevel=Warning --check_format=pretty --configpath=.luarc.ci.json</code></td>
<td><a href="CONTRIBUTING.md">Contribution Guide</a></td>
</tr>
</table>
Expand Down
28 changes: 20 additions & 8 deletions lua/agent_term/context/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@ local view_controller = require("agent_term.ui.controller")

local M = {}

---@return boolean
local function is_panel_focused()
local session = state.session()
return state.has_valid_panel_win()
and session
and session ~= nil
and vim.api.nvim_get_current_win() == session.panel_win
end

---@param sent boolean
---@param used_hook boolean
---@param hook_failure string|nil
---@return boolean
local function notify_send_failure(sent, used_hook, hook_failure)
if sent then
return false
end
if used_hook and hook_failure then
notify.error(("Failed to write agent hook context: %s"):format(hook_failure))
return true
end
notify.error("Agent session is not running.")
return true
end

---@param kind "buffer"|"selection"|"diagnostics"
---@param builder fun(): string|nil, string|nil
local function send_context(kind, builder)
Expand All @@ -28,12 +45,7 @@ local function send_context(kind, builder)
end

local sent, used_hook, hook_failure = submission.submit(message, kind)
if not sent then
if used_hook and hook_failure then
notify.error(("Failed to write agent hook context: %s"):format(hook_failure))
return
end
notify.error("Agent session is not running.")
if notify_send_failure(sent, used_hook, hook_failure) then
return
end

Expand All @@ -50,7 +62,7 @@ function M.send_buffer_context()
end)
end

---@param opts? vim.api.keyset.create_user_command.command_args
---@param opts? table
function M.send_selection_context(opts)
send_context("selection", function()
return captured_context.resolve_message("selection", function()
Expand Down
6 changes: 1 addition & 5 deletions lua/agent_term/context/file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ local config = require("agent_term.setup.runtime_config")
local M = {}

local function context_file_path()
local context_opts = config.context()
local configured = ".agent-term/context.json"
if type(context_opts) == "table" and type(context_opts.file_path) == "string" then
configured = context_opts.file_path
end
local configured = config.context().file_path
return vim.fn.fnamemodify(vim.fn.expand(configured), ":p")
end

Expand Down
8 changes: 1 addition & 7 deletions lua/agent_term/context/submission.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ local terminal = require("agent_term.runtime.session")

local M = {}

local function auto_hook_enabled()
local context = config.context()
local hook = type(context) == "table" and context.hook or nil
return type(hook) == "table" and hook.enabled == true
end

---@param message string
---@param kind "buffer"|"selection"|"diagnostics"
---@return boolean ok, boolean used_hook, string? hook_failure
function M.submit(message, kind)
if auto_hook_enabled() then
if config.auto_hook_enabled() then
local ok, err = context_file.write(kind, message)
if not ok then
return false, true, err
Expand Down
27 changes: 4 additions & 23 deletions lua/agent_term/hooks/autocmds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ local state = require("agent_term.runtime.state")
local M = {}
local augroup = vim.api.nvim_create_augroup("agent_term_hooks", { clear = true })

local function auto_hook_enabled()
local context = config.context()
local hook = type(context) == "table" and context.hook or nil
return type(hook) == "table" and hook.enabled == true
end

local function is_editor_buffer(bufnr)
if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
return false
Expand All @@ -20,7 +14,7 @@ local function is_editor_buffer(bufnr)
end

local function update_context()
if not auto_hook_enabled() or not state.has_running_job() then
if not config.auto_hook_enabled() or not state.has_running_job() then
return
end

Expand All @@ -40,7 +34,6 @@ local function update_context()
end
end

-- For selection, we check if it should be included and if a selection exists
local sel_msg = context_builder.selection_message({})
if sel_msg then
context_file.write("selection", sel_msg)
Expand All @@ -54,11 +47,9 @@ local function update_context()
end

local function clear_context()
if not auto_hook_enabled() then
if not config.auto_hook_enabled() then
return
end
-- Mark stale by writing empty content. The python hook script
-- checks for empty content and returns 0 (no context) in that case.
context_file.write("buffer", "")
end

Expand All @@ -74,33 +65,23 @@ function M.setup()

vim.api.nvim_create_autocmd({ "BufDelete", "BufUnload" }, {
group = augroup,
callback = function()
-- Small delay to ensure we don't clear if we are just switching buffers
-- and another BufEnter is about to fire.
-- Actually, BufDelete is for the buffer being removed.
-- If it's the current buffer, we might want to clear.
clear_context()
end,
callback = clear_context,
})

vim.api.nvim_create_autocmd("ModeChanged", {
group = augroup,
pattern = { "[vV\x16sS]:*", "*:[vV\x16sS]" },
callback = function()
local mode = vim.api.nvim_get_mode().mode
-- If we are no longer in any visual or select mode, we just left it.
if not mode:find("^[vV\x16sS]") then
update_context()
end
end,
})

-- Also update when diagnostics change, as they might arrive after BufEnter
vim.api.nvim_create_autocmd("DiagnosticChanged", {
group = augroup,
callback = function()
update_context()
end,
callback = update_context,
})
end

Expand Down
22 changes: 3 additions & 19 deletions lua/agent_term/hooks/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,11 @@ local installers = {
}

local function command_name()
local agent = config.agent()
local cmd = type(agent) == "table" and agent.cmd or nil
if type(cmd) ~= "table" or type(cmd[1]) ~= "string" then
return nil
end
return vim.fn.fnamemodify(cmd[1], ":t")
return vim.fn.fnamemodify(config.agent().cmd[1], ":t")
end

local function context_file_path()
local context_opts = config.context()
if type(context_opts) == "table" and type(context_opts.file_path) == "string" then
return context_opts.file_path
end
return ".agent-term/context.json"
end

local function auto_hook_enabled()
local context = config.context()
local hook = type(context) == "table" and context.hook or nil
return type(hook) == "table" and hook.enabled == true
return config.context().file_path
end

function M.install()
Expand All @@ -54,7 +39,6 @@ function M.install()
end

local context = config.context()
context.hook = context.hook or {}
context.hook.enabled = true

local msg
Expand All @@ -77,7 +61,7 @@ function M.detect()
local agent_name = command_name()
local installer = agent_name and installers[agent_name] or nil

if not auto_hook_enabled() then
if not config.auto_hook_enabled() then
return false
end

Expand Down
16 changes: 10 additions & 6 deletions lua/agent_term/runtime/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ function M.send(text)
return false
end
local session = state.session()
if not session then
return false
end
vim.api.nvim_chan_send(session.job_id, text)
return true
end
Expand All @@ -82,7 +85,7 @@ function M.ensure_session(cmd, agent_name)
end

local agent = config.agent(agent_name)
local run_cmd = cmd or config.auto_resume_command(agent_name) or (agent and agent.cmd)
local run_cmd = cmd or config.auto_resume_command(agent_name) or agent.cmd
if not can_run_cmd(run_cmd) then
notify.error(("Agent command not found: %s"):format(command_name(run_cmd)))
return nil
Expand Down Expand Up @@ -131,12 +134,13 @@ end

function M.close_all_views_except(agent_name)
state.each_session(function(name, session)
if name ~= agent_name then
close_win_if_valid(session.float_win)
close_win_if_valid(session.panel_win)
state.reset_float_win(name)
state.reset_panel_win(name)
if name == agent_name then
return
end
close_win_if_valid(session.float_win)
close_win_if_valid(session.panel_win)
state.reset_float_win(name)
state.reset_panel_win(name)
end)
end

Expand Down
Loading